模型定价: 悬空模型标记 + 一键清除 + 允许渠道只读

- 无渠道绑定的模型以「悬空」琥珀色徽标 + 卡片着色标记
- 新增「清除悬空」一键删除未绑定渠道的模型(DELETE /models/unused)
- 移除页面内「绑定渠道」弹窗与 chips 的解除按钮,渠道支持列表只在渠道抽屉管理
- 缺失提示带上渠道真实模型名(upstream_model)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-08-16 18:54:59 +08:00
co-authored by Claude
parent 8194edb63d
commit d0c879a2ed
3 changed files with 83 additions and 79 deletions
+31 -5
View File
@@ -45,19 +45,22 @@ func (h *Handler) AdminModels(c *gin.Context) {
})
}
// 渠道提供了但目录中缺失的模型(孤儿绑定,数据不一致时出现)
// 渠道选中但目录中缺失的模型(孤儿绑定:渠道绑定指向已被删除的模型)
var orphans []struct {
ChannelName string
ModelID uint64
ChannelName string
UpstreamModel string
ModelID uint64
}
h.a.DB.Raw(`SELECT c.name as channel_name, b.model_id
h.a.DB.Raw(`SELECT c.name as channel_name, b.model_id, b.upstream_model
FROM channel_model_bindings b
LEFT JOIN models m ON m.id = b.model_id
LEFT JOIN channels c ON c.id = b.channel_id
WHERE m.id IS NULL`).Scan(&orphans)
missing := make([]gin.H, 0, len(orphans))
for _, o := range orphans {
missing = append(missing, gin.H{"channel": o.ChannelName, "model_id": o.ModelID})
missing = append(missing, gin.H{
"channel": o.ChannelName, "model_id": o.ModelID, "upstream_model": o.UpstreamModel,
})
}
// 在用但未定价的模型数(渠道已提供、需定价)
@@ -210,6 +213,29 @@ func (h *Handler) AdminDeleteModel(c *gin.Context) {
resp.OK(c, gin.H{"ok": true})
}
// AdminDeleteUnusedModels DELETE /api/v1/admin/models/unused — 一键清除未绑定任何渠道的模型。
// 这些通常是渠道抽屉里选中过、后来又取消绑定留下的目录条目,客户端无法调用。
func (h *Handler) AdminDeleteUnusedModels(c *gin.Context) {
var orphans []store.Model
if err := h.a.DB.Where("id NOT IN (SELECT DISTINCT model_id FROM channel_model_bindings)").Find(&orphans).Error; err != nil {
resp.Fail(c, http.StatusInternalServerError, "failed to load models")
return
}
names := make([]string, 0, len(orphans))
ids := make([]uint64, 0, len(orphans))
for _, m := range orphans {
names = append(names, m.Name)
ids = append(ids, m.ID)
}
if len(ids) > 0 {
if err := h.a.DB.Delete(&store.Model{}, ids).Error; err != nil {
resp.Fail(c, http.StatusInternalServerError, "failed to delete models")
return
}
}
resp.OK(c, gin.H{"deleted": names, "count": len(names)})
}
// AdminCreateModelBinding POST /api/v1/admin/models/:id/bindings
func (h *Handler) AdminCreateModelBinding(c *gin.Context) {
modelID, err := strconv.ParseUint(c.Param("id"), 10, 64)