package api import ( "errors" "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/openteam/server/internal/pkg/resp" "github.com/openteam/server/internal/store" "gorm.io/gorm" ) // AdminModels GET /api/v1/admin/models — 模型列表(含价格与渠道绑定)。 func (h *Handler) AdminModels(c *gin.Context) { var ms []store.Model if err := h.a.DB.Order("sort ASC, id ASC").Find(&ms).Error; err != nil { resp.Fail(c, http.StatusInternalServerError, "failed to load models") return } out := make([]gin.H, 0, len(ms)) for _, m := range ms { var bindings []store.ChannelModelBinding h.a.DB.Preload("Channel").Where("model_id = ?", m.ID).Find(&bindings) chs := make([]gin.H, 0, len(bindings)) for _, b := range bindings { chs = append(chs, gin.H{ "id": b.ID, "channel_id": b.ChannelID, "channel_name": b.Channel.Name, "upstream_model": b.UpstreamModel, "weight": b.Weight, }) } out = append(out, gin.H{ "id": m.ID, "name": m.Name, "display_name": m.DisplayName, "input_price": m.InputPrice, "output_price": m.OutputPrice, "cache_read_price": m.CacheReadPrice, "enabled": m.Enabled, "sort": m.Sort, "channels": chs, }) } resp.OK(c, gin.H{"items": out}) } // AdminCreateModel POST /api/v1/admin/models func (h *Handler) AdminCreateModel(c *gin.Context) { var req struct { Name string `json:"name" binding:"required,min=1,max=128"` DisplayName string `json:"display_name"` InputPrice float64 `json:"input_price"` OutputPrice float64 `json:"output_price"` CacheReadPrice float64 `json:"cache_read_price"` Enabled *bool `json:"enabled"` } if err := c.ShouldBindJSON(&req); err != nil { resp.Fail(c, http.StatusBadRequest, "invalid input: "+err.Error()) return } m := store.Model{ Name: req.Name, DisplayName: req.DisplayName, InputPrice: req.InputPrice, OutputPrice: req.OutputPrice, CacheReadPrice: req.CacheReadPrice, Enabled: boolOr(req.Enabled, true), } if m.DisplayName == "" { m.DisplayName = m.Name } if err := h.a.DB.Create(&m).Error; err != nil { resp.Fail(c, http.StatusConflict, "failed to create model (name may already exist)") return } resp.Created(c, gin.H{"id": m.ID, "name": m.Name}) } // AdminUpdateModel PUT /api/v1/admin/models/:id — 价格/展示名/启停/排序。 func (h *Handler) AdminUpdateModel(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { resp.Fail(c, http.StatusBadRequest, "invalid model id") return } var req struct { DisplayName *string `json:"display_name"` InputPrice *float64 `json:"input_price"` OutputPrice *float64 `json:"output_price"` CacheReadPrice *float64 `json:"cache_read_price"` Enabled *bool `json:"enabled"` Sort *int `json:"sort"` } if err := c.ShouldBindJSON(&req); err != nil { resp.Fail(c, http.StatusBadRequest, "invalid input") return } var m store.Model if err := h.a.DB.First(&m, id).Error; err != nil { resp.Fail(c, http.StatusNotFound, "model not found") return } updates := map[string]any{} if req.DisplayName != nil { updates["display_name"] = *req.DisplayName } if req.InputPrice != nil { updates["input_price"] = *req.InputPrice } if req.OutputPrice != nil { updates["output_price"] = *req.OutputPrice } if req.CacheReadPrice != nil { updates["cache_read_price"] = *req.CacheReadPrice } if req.Enabled != nil { updates["enabled"] = *req.Enabled } if req.Sort != nil { updates["sort"] = *req.Sort } if len(updates) > 0 { if err := h.a.DB.Model(&m).Updates(updates).Error; err != nil { resp.Fail(c, http.StatusInternalServerError, "failed to update model") return } } resp.OK(c, gin.H{"ok": true}) } // AdminDeleteModel DELETE /api/v1/admin/models/:id func (h *Handler) AdminDeleteModel(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { resp.Fail(c, http.StatusBadRequest, "invalid model id") return } res := h.a.DB.Delete(&store.Model{}, id) if res.Error != nil { resp.Fail(c, http.StatusInternalServerError, "failed to delete model") return } if res.RowsAffected == 0 { resp.Fail(c, http.StatusNotFound, "model not found") return } h.a.DB.Where("model_id = ?", id).Delete(&store.ChannelModelBinding{}) resp.OK(c, gin.H{"ok": true}) } // AdminCreateModelBinding POST /api/v1/admin/models/:id/bindings func (h *Handler) AdminCreateModelBinding(c *gin.Context) { modelID, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { resp.Fail(c, http.StatusBadRequest, "invalid model id") return } var req struct { ChannelID uint64 `json:"channel_id" binding:"required"` UpstreamModel string `json:"upstream_model" binding:"required"` Weight *int `json:"weight"` } if err := c.ShouldBindJSON(&req); err != nil { resp.Fail(c, http.StatusBadRequest, "invalid input: channel_id and upstream_model required") return } var m store.Model if err := h.a.DB.First(&m, modelID).Error; err != nil { resp.Fail(c, http.StatusNotFound, "model not found") return } var ch store.Channel if err := h.a.DB.First(&ch, req.ChannelID).Error; err != nil { resp.Fail(c, http.StatusNotFound, "channel not found") return } b := store.ChannelModelBinding{ ChannelID: req.ChannelID, ModelID: modelID, UpstreamModel: req.UpstreamModel, Weight: intOr(req.Weight, 1), } if err := h.a.DB.Create(&b).Error; err != nil { resp.Fail(c, http.StatusConflict, "binding may already exist") return } resp.Created(c, gin.H{"id": b.ID}) } // AdminDeleteModelBinding DELETE /api/v1/admin/models/:id/bindings/:bid func (h *Handler) AdminDeleteModelBinding(c *gin.Context) { bid, err := strconv.ParseUint(c.Param("bid"), 10, 64) if err != nil { resp.Fail(c, http.StatusBadRequest, "invalid binding id") return } res := h.a.DB.Delete(&store.ChannelModelBinding{}, bid) if res.Error != nil { resp.Fail(c, http.StatusInternalServerError, "failed to delete binding") return } if res.RowsAffected == 0 { resp.Fail(c, http.StatusNotFound, "binding not found") return } resp.OK(c, gin.H{"ok": true}) } var _ = errors.Is var _ = gorm.ErrRecordNotFound