package server import ( "errors" "net/http" "strings" "github.com/gin-gonic/gin" "gorm.io/gorm" "code.littlelan.cn/CarrotAssistant/backend/internal/security" "code.littlelan.cn/CarrotAssistant/backend/internal/store" ) type modelConfigReq struct { Name string `json:"name" binding:"required"` BaseURL string `json:"base_url" binding:"required"` APIKey string `json:"api_key"` DefaultModel string `json:"default_model" binding:"required"` SupportsTools *bool `json:"supports_tools"` MaxTokens int `json:"max_tokens"` } // GET /admin/model-configs func (d Deps) handleListModels(c *gin.Context) { var list []store.ModelConfig d.DB.Order("id DESC").Find(&list) c.JSON(http.StatusOK, list) } // POST /admin/model-configs func (d Deps) handleCreateModel(c *gin.Context) { var req modelConfigReq if !bind(c, &req) { return } req.BaseURL = strings.TrimRight(strings.TrimSpace(req.BaseURL), "/") enc, err := d.Crypto.Encrypt(req.APIKey) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "加密失败"}) return } m := store.ModelConfig{ Name: req.Name, BaseURL: req.BaseURL, APIKeyEncrypted: enc, APIKeyPreview: security.GenerateAPIKeyPreview(req.APIKey), DefaultModel: req.DefaultModel, SupportsTools: req.SupportsTools == nil || *req.SupportsTools, MaxTokens: req.MaxTokens, } if err := d.DB.Create(&m).Error; err != nil { if errors.Is(err, gorm.ErrDuplicatedKey) { c.JSON(http.StatusConflict, gin.H{"error": "名称已存在"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "创建失败"}) return } d.audit(c, nil, nil, "model_config.create", map[string]any{"id": m.ID, "name": m.Name}) c.JSON(http.StatusOK, m) } // PUT /admin/model-configs/:id — api_key is optional on update; an empty // value keeps the existing key, a non-empty value replaces it. func (d Deps) handleUpdateModel(c *gin.Context) { id := c.Param("id") var m store.ModelConfig if err := d.DB.First(&m, "id = ?", id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "未找到"}) return } var req modelConfigReq if !bind(c, &req) { return } req.BaseURL = strings.TrimRight(strings.TrimSpace(req.BaseURL), "/") m.Name = req.Name m.BaseURL = req.BaseURL m.DefaultModel = req.DefaultModel m.MaxTokens = req.MaxTokens if req.SupportsTools != nil { m.SupportsTools = *req.SupportsTools } if strings.TrimSpace(req.APIKey) != "" { enc, err := d.Crypto.Encrypt(req.APIKey) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "加密失败"}) return } m.APIKeyEncrypted = enc m.APIKeyPreview = security.GenerateAPIKeyPreview(req.APIKey) } if err := d.DB.Save(&m).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "保存失败"}) return } d.audit(c, nil, nil, "model_config.update", map[string]any{"id": m.ID}) c.JSON(http.StatusOK, m) } // DELETE /admin/model-configs/:id func (d Deps) handleDeleteModel(c *gin.Context) { id := c.Param("id") res := d.DB.Delete(&store.ModelConfig{}, "id = ?", id) if res.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"}) return } if res.RowsAffected == 0 { c.JSON(http.StatusNotFound, gin.H{"error": "未找到"}) return } d.audit(c, nil, nil, "model_config.delete", map[string]any{"id": id}) c.JSON(http.StatusOK, gin.H{"ok": true}) }