219 lines
4.5 KiB
Go
219 lines
4.5 KiB
Go
// Dynagate config manager
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/csv"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type ModelConfig struct {
|
|
Model string
|
|
Key string
|
|
Endpoint string
|
|
Extra string
|
|
}
|
|
|
|
type ConfigManager struct {
|
|
filePath string
|
|
mu sync.RWMutex
|
|
configs []ModelConfig
|
|
uniqueModels []string
|
|
lastModTime time.Time
|
|
lastSize int64
|
|
}
|
|
|
|
func NewConfigManager(filePath string) *ConfigManager {
|
|
cm := &ConfigManager{
|
|
filePath: filePath,
|
|
}
|
|
if err := cm.Load(); err != nil {
|
|
log.Printf("Initial configuration load failed or file not found (will retry on change): %v", err)
|
|
}
|
|
return cm
|
|
}
|
|
|
|
func (cm *ConfigManager) Load() error {
|
|
file, err := os.Open(cm.filePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
stat, err := file.Stat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
reader := csv.NewReader(file)
|
|
reader.FieldsPerRecord = -1
|
|
rows, err := reader.ReadAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var configs []ModelConfig
|
|
if len(rows) == 0 {
|
|
cm.update(configs, stat.ModTime(), stat.Size())
|
|
return nil
|
|
}
|
|
|
|
// Detect header row
|
|
hasHeader := false
|
|
firstRow := rows[0]
|
|
for _, col := range firstRow {
|
|
colLower := strings.ToLower(strings.TrimSpace(col))
|
|
if colLower == "model" || colLower == "key" || colLower == "endpoint" {
|
|
hasHeader = true
|
|
break
|
|
}
|
|
}
|
|
|
|
var startIdx int
|
|
modelIdx, keyIdx, endpointIdx, extraIdx := 0, 1, 2, -1
|
|
if hasHeader {
|
|
startIdx = 1
|
|
modelIdx, keyIdx, endpointIdx, extraIdx = -1, -1, -1, -1
|
|
for i, col := range firstRow {
|
|
colLower := strings.ToLower(strings.TrimSpace(col))
|
|
switch colLower {
|
|
case "model":
|
|
modelIdx = i
|
|
case "key":
|
|
keyIdx = i
|
|
case "endpoint":
|
|
endpointIdx = i
|
|
case "extra":
|
|
extraIdx = i
|
|
}
|
|
}
|
|
// Fallbacks if columns are not named exactly
|
|
if modelIdx == -1 {
|
|
modelIdx = 0
|
|
}
|
|
if keyIdx == -1 {
|
|
keyIdx = 1
|
|
}
|
|
if endpointIdx == -1 {
|
|
endpointIdx = 2
|
|
}
|
|
} else {
|
|
startIdx = 0
|
|
}
|
|
|
|
for i := startIdx; i < len(rows); i++ {
|
|
row := rows[i]
|
|
maxIdx := modelIdx
|
|
if keyIdx > maxIdx {
|
|
maxIdx = keyIdx
|
|
}
|
|
if endpointIdx > maxIdx {
|
|
maxIdx = endpointIdx
|
|
}
|
|
if len(row) <= maxIdx {
|
|
continue
|
|
}
|
|
|
|
m := strings.TrimSpace(row[modelIdx])
|
|
k := strings.TrimSpace(row[keyIdx])
|
|
ep := strings.TrimSpace(row[endpointIdx])
|
|
|
|
if m == "" || k == "" || ep == "" {
|
|
continue
|
|
}
|
|
|
|
extra := ""
|
|
if hasHeader {
|
|
if extraIdx != -1 && len(row) > extraIdx {
|
|
extra = strings.TrimSpace(row[extraIdx])
|
|
}
|
|
} else {
|
|
if len(row) > 3 {
|
|
extra = strings.TrimSpace(row[3])
|
|
}
|
|
}
|
|
|
|
configs = append(configs, ModelConfig{
|
|
Model: m,
|
|
Key: k,
|
|
Endpoint: ep,
|
|
Extra: extra,
|
|
})
|
|
}
|
|
|
|
cm.update(configs, stat.ModTime(), stat.Size())
|
|
log.Printf("Loaded %d model configurations from %s", len(configs), cm.filePath)
|
|
return nil
|
|
}
|
|
|
|
func (cm *ConfigManager) update(configs []ModelConfig, modTime time.Time, size int64) {
|
|
cm.mu.Lock()
|
|
defer cm.mu.Unlock()
|
|
|
|
cm.configs = configs
|
|
cm.lastModTime = modTime
|
|
cm.lastSize = size
|
|
|
|
var uniqueModels []string
|
|
seen := make(map[string]bool)
|
|
for _, cfg := range configs {
|
|
if !seen[cfg.Model] {
|
|
seen[cfg.Model] = true
|
|
uniqueModels = append(uniqueModels, cfg.Model)
|
|
}
|
|
}
|
|
cm.uniqueModels = uniqueModels
|
|
}
|
|
|
|
func (cm *ConfigManager) GetConfigs() []ModelConfig {
|
|
cm.mu.RLock()
|
|
defer cm.mu.RUnlock()
|
|
res := make([]ModelConfig, len(cm.configs))
|
|
copy(res, cm.configs)
|
|
return res
|
|
}
|
|
|
|
func (cm *ConfigManager) GetUniqueModels() []string {
|
|
cm.mu.RLock()
|
|
defer cm.mu.RUnlock()
|
|
res := make([]string, len(cm.uniqueModels))
|
|
copy(res, cm.uniqueModels)
|
|
return res
|
|
}
|
|
|
|
func (cm *ConfigManager) Watch(ctx context.Context) {
|
|
ticker := time.NewTicker(1 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
stat, err := os.Stat(cm.filePath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
cm.mu.RLock()
|
|
lastMod := cm.lastModTime
|
|
lastSz := cm.lastSize
|
|
cm.mu.RUnlock()
|
|
|
|
if stat.ModTime().After(lastMod) || stat.Size() != lastSz {
|
|
log.Printf("Detected changes in %s, reloading...", cm.filePath)
|
|
if err := cm.Load(); err != nil {
|
|
log.Printf("Error reloading configuration: %v", err)
|
|
} else {
|
|
log.Printf("Configuration reloaded successfully")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|