package config import ( "encoding/json" "fmt" "strconv" "strings" "code.tczkiot.com/wlw/ai-agent/internal/pkg/enums" "github.com/spf13/viper" ) const SettingsPrefix = "ai_agent_" func FromSettings(values map[string]string) (*Config, error) { cfg := defaultConfig() setString(values, SettingsPrefix+"language", &cfg.Language) setString(values, SettingsPrefix+"storage_default", (*string)(&cfg.Storage.Default)) if err := setInt64(values, SettingsPrefix+"storage_max_upload_size_mb", &cfg.Storage.MaxUploadSizeMB); err != nil { return nil, err } setString(values, SettingsPrefix+"storage_local_root", &cfg.Storage.Local.Root) setString(values, SettingsPrefix+"storage_local_base_url", &cfg.Storage.Local.BaseURL) setString(values, SettingsPrefix+"storage_oss_endpoint", &cfg.Storage.OSS.Endpoint) setString(values, SettingsPrefix+"storage_oss_bucket", &cfg.Storage.OSS.Bucket) setString(values, SettingsPrefix+"storage_oss_access_key_id", &cfg.Storage.OSS.AccessKeyID) setString(values, SettingsPrefix+"storage_oss_access_key_secret", &cfg.Storage.OSS.AccessKeySecret) setString(values, SettingsPrefix+"storage_oss_base_url", &cfg.Storage.OSS.BaseURL) if err := setBool(values, SettingsPrefix+"storage_oss_private", &cfg.Storage.OSS.Private); err != nil { return nil, err } if err := setInt(values, SettingsPrefix+"storage_oss_signed_url_expire", &cfg.Storage.OSS.SignedURLExpire); err != nil { return nil, err } setString(values, "ai_vector_path", &cfg.VectorDB.Path) if err := setBool(values, SettingsPrefix+"wxwork_enabled", &cfg.WxWork.Enabled); err != nil { return nil, err } setString(values, SettingsPrefix+"wxwork_corp_id", &cfg.WxWork.CorpID) setString(values, SettingsPrefix+"wxwork_corp_secret", &cfg.WxWork.CorpSecret) setString(values, SettingsPrefix+"wxwork_agent_id", &cfg.WxWork.AgentID) setString(values, SettingsPrefix+"wxwork_rsa_private_key", &cfg.WxWork.RSAPrivateKey) setString(values, SettingsPrefix+"wxwork_token", &cfg.WxWork.Token) setString(values, SettingsPrefix+"wxwork_encoding_aes_key", &cfg.WxWork.EncodingAESKey) if err := setBool(values, SettingsPrefix+"wxwork_notify_enabled", &cfg.WxWork.Notify.Enabled); err != nil { return nil, err } if err := setJSON(values, SettingsPrefix+"wxwork_notify_to_users", &cfg.WxWork.Notify.ToUsers); err != nil { return nil, err } if err := setBool(values, SettingsPrefix+"wxwork_notify_safe", &cfg.WxWork.Notify.Safe); err != nil { return nil, err } if err := setBool(values, SettingsPrefix+"wxwork_notify_dedupe", &cfg.WxWork.Notify.EnableDuplicateCheck); err != nil { return nil, err } if err := setInt(values, SettingsPrefix+"wxwork_notify_dedupe_interval", &cfg.WxWork.Notify.DuplicateCheckInterval); err != nil { return nil, err } return cfg, nil } func defaultConfig() *Config { return &Config{ Language: "zh-CN", Server: ServerConfig{Port: 8080}, Storage: StorageConfig{ Default: enums.AssetProviderLocal, MaxUploadSizeMB: 20, Local: LocalStorageConfig{Root: "data/storage", BaseURL: "/storage"}, OSS: OSSStorageConfig{SignedURLExpire: 600}, }, VectorDB: VectorDBConfig{Path: "data/agent/vectors.db"}, WxWork: WxWorkConfig{Notify: WxWorkNotifyConfig{ EnableDuplicateCheck: true, DuplicateCheckInterval: 1800, }}, } } func setString(values map[string]string, key string, target *string) { if value, ok := values[key]; ok { *target = strings.TrimSpace(value) } } func setInt(values map[string]string, key string, target *int) error { value, ok := values[key] if !ok || strings.TrimSpace(value) == "" { return nil } parsed, err := strconv.Atoi(strings.TrimSpace(value)) if err != nil { return fmt.Errorf("invalid setting %s: %w", key, err) } *target = parsed return nil } func setInt64(values map[string]string, key string, target *int64) error { value, ok := values[key] if !ok || strings.TrimSpace(value) == "" { return nil } parsed, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64) if err != nil { return fmt.Errorf("invalid setting %s: %w", key, err) } *target = parsed return nil } func setBool(values map[string]string, key string, target *bool) error { value, ok := values[key] if !ok || strings.TrimSpace(value) == "" { return nil } parsed, err := strconv.ParseBool(strings.TrimSpace(value)) if err != nil { return fmt.Errorf("invalid setting %s: %w", key, err) } *target = parsed return nil } func setJSON(values map[string]string, key string, target any) error { value, ok := values[key] if !ok || strings.TrimSpace(value) == "" { return nil } if err := json.Unmarshal([]byte(value), target); err != nil { return fmt.Errorf("invalid setting %s: %w", key, err) } return nil } type Config struct { Language string `yaml:"language"` Server ServerConfig `yaml:"server"` DB DBConfig `yaml:"db"` Logger LoggerConfig `yaml:"logger"` Storage StorageConfig `yaml:"storage"` VectorDB VectorDBConfig `yaml:"vectorDB"` WxWork WxWorkConfig `yaml:"wxWork"` } func (c Config) LanguageOrDefault() string { switch strings.ToLower(strings.TrimSpace(c.Language)) { case "zh", "zh-cn", "zh_cn", "zh-hans": return "zh-CN" case "en", "en-us", "en_us": return "en-US" default: return "zh-CN" } } type WxWorkNotifyConfig struct { Enabled bool `yaml:"enabled"` ToUsers []int64 `yaml:"toUsers"` Safe bool `yaml:"safe"` EnableDuplicateCheck bool `yaml:"enableDuplicateCheck"` DuplicateCheckInterval int `yaml:"duplicateCheckInterval"` } type ServerConfig struct { Port int `yaml:"port"` FrontendURL string `yaml:"frontendUrl"` CORS CORSConfig `yaml:"cors"` } func (s ServerConfig) Address() string { if s.Port <= 0 { return ":8080" } return fmt.Sprintf(":%d", s.Port) } func (s ServerConfig) FrontendBaseURL() string { if value := strings.TrimRight(strings.TrimSpace(s.FrontendURL), "/"); value != "" { return value } return "http://127.0.0.1:3000" } type CORSConfig struct { // AllowedOrigins 是允许浏览器跨域访问的 Origin 白名单,必须包含协议和域名。 // 留空表示不允许跨域请求;同源请求通常不会携带 Origin,不受影响。 AllowedOrigins []string `yaml:"allowedOrigins"` } type DBConfig struct { Type string `yaml:"type"` DSN string `yaml:"dsn"` MaxIdleConns int `yaml:"maxIdleConns"` MaxOpenConns int `yaml:"maxOpenConns"` ConnMaxIdleTimeSeconds int `yaml:"connMaxIdleTimeSeconds"` ConnMaxLifetimeSeconds int `yaml:"connMaxLifetimeSeconds"` } type LoggerConfig struct { Level string `yaml:"level"` Format string `yaml:"format"` AddSource bool `yaml:"addSource"` } type StorageConfig struct { Default enums.AssetProvider `yaml:"default"` MaxUploadSizeMB int64 `yaml:"maxUploadSizeMB"` Local LocalStorageConfig `yaml:"local"` OSS OSSStorageConfig `yaml:"oss"` } func (s StorageConfig) MaxUploadSizeBytes() int64 { if s.MaxUploadSizeMB <= 0 { return 5 << 20 } return s.MaxUploadSizeMB << 20 } func (s StorageConfig) MaxRequestBodySizeBytes() int64 { limit := s.MaxUploadSizeBytes() return limit + (1 << 20) } type LocalStorageConfig struct { Root string `yaml:"root"` BaseURL string `yaml:"baseUrl"` } type OSSStorageConfig struct { Endpoint string `yaml:"endpoint"` Bucket string `yaml:"bucket"` AccessKeyID string `yaml:"accessKeyId"` AccessKeySecret string `yaml:"accessKeySecret"` BaseURL string `yaml:"baseUrl"` Private bool `yaml:"private"` SignedURLExpire int `yaml:"signedUrlExpireSeconds"` } type VectorDBConfig struct { Path string `yaml:"path"` } // WxWorkConfig defines the WeCom application used for customer-service // callbacks and notifications. Dashboard login is owned by be-system. type WxWorkConfig struct { // Enabled controls whether the WeCom SDK is initialized. Enabled bool `yaml:"enabled"` // CorpID 为企业微信公司 ID,例如 wwxxxxxxxxxxxxxxxx。 CorpID string `yaml:"corpId"` // CorpSecret 为企业微信应用 Secret,用于换取 access_token。 CorpSecret string `yaml:"corpSecret"` // AgentID 为企业微信自建应用 AgentID。 AgentID string `yaml:"agentId"` // RSAPrivateKey 为企业微信回调解密私钥。 RSAPrivateKey string `yaml:"rsaPrivateKey"` // Token 为企业微信回调 Token。 Token string `yaml:"token"` // EncodingAESKey 为企业微信消息加解密密钥。 EncodingAESKey string `yaml:"encodingAESKey"` // Notify 为企业微信应用消息通知配置。 Notify WxWorkNotifyConfig `yaml:"notify"` } func Load(path string) (*Config, error) { v := viper.New() v.SetConfigFile(path) v.SetConfigType("yaml") v.SetEnvPrefix("AGENT_DESK") v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.AutomaticEnv() if err := v.ReadInConfig(); err != nil { return nil, err } cfg := &Config{} if err := v.Unmarshal(cfg); err != nil { return nil, err } return cfg, nil }