package config import ( "bufio" "github.com/spf13/viper" "log" "os" "path/filepath" "strconv" "strings" ) type Config struct { Http struct { IP string Port int } BasicAuthUser string `mapstructure:"basic_auth_user"` BasicAuthPassword string `mapstructure:"basic_auth_password"` FrontendPath string `mapstructure:"frontend_path"` Log struct { Level string LogPath string `mapstructure:"logPath"` } `mapstructure:"log"` Chat struct { Model string `mapstructure:"model"` MaxTokens int `mapstructure:"max_tokens"` Temperature float32 `mapstructure:"temperature"` TopP float32 `mapstructure:"top_p"` PresencePenalty float32 `mapstructure:"presence_penalty"` FrequencyPenalty float32 `mapstructure:"frequency_penalty"` BotDesc string `mapstructure:"bot_desc"` MinResponseTokens int `mapstructure:"min_response_tokens"` ContextTTL int `mapstructure:"context_ttl"` ContextLen int `mapstructure:"context_len"` } DependOn struct { AiChatService struct { Address string AccessToken string } `mapstructure:"ai-chat-service"` } } var conf *Config func InitConfig(filePath string, typ ...string) { loadProjectDotEnv(filePath) v := viper.New() v.SetConfigFile(filePath) if len(typ) > 0 { v.SetConfigType(typ[0]) } err := v.ReadInConfig() if err != nil { log.Fatal(err) } conf = &Config{} err = v.Unmarshal(conf) if err != nil { log.Fatal(err) } normalizeConfig(conf) overrideConfigFromEnv(conf) } func GetConfig() *Config { return conf } func normalizeConfig(conf *Config) { if conf.Http.IP == "" { conf.Http.IP = "0.0.0.0" } if conf.Http.Port == 0 { conf.Http.Port = 7080 } if conf.FrontendPath == "" { conf.FrontendPath = "www" } if conf.Chat.Model == "" { conf.Chat.Model = "kimi-k2.5" } if conf.Chat.MaxTokens == 0 { conf.Chat.MaxTokens = 4096 } if conf.Chat.Temperature == 0 { conf.Chat.Temperature = 1.0 } if conf.Chat.TopP == 0 { conf.Chat.TopP = 1.0 } if conf.Chat.MinResponseTokens == 0 { conf.Chat.MinResponseTokens = 600 } if conf.DependOn.AiChatService.Address == "" { conf.DependOn.AiChatService.Address = "localhost:50055" } } func overrideConfigFromEnv(conf *Config) { overrideString(&conf.Http.IP, os.Getenv("SERVER_HOST")) overrideInt(&conf.Http.Port, os.Getenv("SERVER_PORT")) overrideString(&conf.BasicAuthUser, os.Getenv("BASIC_AUTH_USER")) overrideString(&conf.BasicAuthPassword, os.Getenv("BASIC_AUTH_PASSWORD")) overrideString(&conf.FrontendPath, os.Getenv("FRONTEND_PATH")) overrideString(&conf.Chat.Model, os.Getenv("OPENAI_MODEL")) overrideInt(&conf.Chat.MaxTokens, os.Getenv("OPENAI_MAX_TOKENS")) overrideScaledFloat32(&conf.Chat.Temperature, os.Getenv("OPENAI_TEMPERATURE")) overrideScaledFloat32(&conf.Chat.PresencePenalty, os.Getenv("OPENAI_PRESENCE_PENALTY")) overrideScaledFloat32(&conf.Chat.FrequencyPenalty, os.Getenv("OPENAI_FREQUENCY_PENALTY")) overrideInt(&conf.Chat.MinResponseTokens, os.Getenv("CHAT_MIN_RESPONSE_TOKENS")) overrideString(&conf.DependOn.AiChatService.Address, firstNonEmpty( os.Getenv("AI_CHAT_SERVICE_ADDRESS"), os.Getenv("AI_CHAT_SERVICE_ADDR"), )) overrideString(&conf.DependOn.AiChatService.AccessToken, os.Getenv("AI_CHAT_SERVICE_ACCESS_TOKEN")) } func loadProjectDotEnv(configFilePath string) { projectRoot := filepath.Dir(filepath.Dir(configFilePath)) envPath := filepath.Join(projectRoot, ".env") file, err := os.Open(envPath) if err != nil { return } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "" || strings.HasPrefix(line, "#") { continue } key, value, ok := strings.Cut(line, "=") if !ok { continue } key = strings.TrimSpace(key) value = strings.TrimSpace(value) if key == "" { continue } if _, exists := os.LookupEnv(key); exists { continue } _ = os.Setenv(key, value) } } func firstNonEmpty(values ...string) string { for _, value := range values { if strings.TrimSpace(value) != "" { return value } } return "" } func overrideString(target *string, value string) { if strings.TrimSpace(value) != "" { *target = strings.TrimSpace(value) } } func overrideInt(target *int, value string) { value = strings.TrimSpace(value) if value == "" { return } if parsed, err := strconv.Atoi(value); err == nil { *target = parsed } } func overrideScaledFloat32(target *float32, value string) { value = strings.TrimSpace(value) if value == "" { return } if parsed, err := strconv.ParseFloat(value, 32); err == nil { *target = float32(parsed / 100.0) } }