117 lines
3.4 KiB
Go
117 lines
3.4 KiB
Go
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})
|
|
}
|