package handler import ( "errors" "net/http" "strings" "github.com/gin-gonic/gin" "gorm.io/gorm" "infogenie-backend/config" "infogenie-backend/internal/database" "infogenie-backend/internal/model" ) type AIRuntimeHandler struct{} func NewAIRuntimeHandler() *AIRuntimeHandler { return &AIRuntimeHandler{} } func maskAPIKey(k string) (set bool, hint string) { k = strings.TrimSpace(k) if k == "" { return false, "" } if len(k) <= 4 { return true, "****" } return true, "****" + k[len(k)-4:] } // GetAIRuntime 管理员读取当前 AI 上游配置(密钥脱敏) func (h *AIRuntimeHandler) GetAIRuntime(c *gin.Context) { if config.Cfg == nil || strings.TrimSpace(config.Cfg.SiteAdminToken) == "" { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "admin_not_configured"}) return } if !siteAdminTokenOK(c.GetHeader("X-Site-Admin-Token")) { c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"}) return } var row model.SiteAIRuntime _ = database.DB.First(&row, 1).Error keySet, keyHint := maskAPIKey(row.APIKey) c.JSON(http.StatusOK, gin.H{ "api_base": strings.TrimSpace(row.APIBase), "api_key_set": keySet, "api_key_hint": keyHint, "default_model": strings.TrimSpace(row.DefaultModel), "default_provider": strings.TrimSpace(row.DefaultProv), }) } type putAIRuntimeBody struct { APIBase string `json:"api_base"` APIKey string `json:"api_key"` DefaultModel string `json:"default_model"` DefaultProvider string `json:"default_provider"` } // PutAIRuntime 管理员写入;api_key 留空或不传则保留原密钥 func (h *AIRuntimeHandler) PutAIRuntime(c *gin.Context) { if config.Cfg == nil || strings.TrimSpace(config.Cfg.SiteAdminToken) == "" { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "admin_not_configured"}) return } if !siteAdminTokenOK(c.GetHeader("X-Site-Admin-Token")) { c.JSON(http.StatusForbidden, gin.H{"error": "forbidden"}) return } var body putAIRuntimeBody if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_json"}) return } var row model.SiteAIRuntime err := database.DB.First(&row, 1).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { row = model.SiteAIRuntime{ID: 1} } else { c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"}) return } } row.APIBase = strings.TrimSpace(body.APIBase) if strings.TrimSpace(body.DefaultModel) != "" { row.DefaultModel = strings.TrimSpace(body.DefaultModel) } if strings.TrimSpace(body.DefaultProvider) != "" { row.DefaultProv = strings.TrimSpace(body.DefaultProvider) } else if row.DefaultProv == "" { row.DefaultProv = "deepseek" } newKey := strings.TrimSpace(body.APIKey) if newKey != "" && !strings.Contains(newKey, "****") { row.APIKey = newKey } if err := database.DB.Save(&row).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"}) return } c.JSON(http.StatusOK, gin.H{"ok": true}) }