feat(channel): 对齐 vivid 最新渠道/模型管理设计

渠道管理(对齐 9db9971/9f6eeca/b607a8d/268a8f7/7f8a87e):
- 列表新增「运行中」(Redis 并发槽位实时计数,与调度同源)和「失败数」
  (event_logs 按渠道聚合)两列
- 行内「同步上游模型」恢复弹窗:默认全开、右侧开关、彩点+单行别名,
  底部「同步模型」批量导入;编辑表单去掉获取模型列表按钮(保存已自动
  全量同步);去掉勾选即解绑语义与 unbind-model 端点
- 导入加固:别名与上游同名报错、同批别名复用拒绝、本地类型冲突拒绝、
  单次上限 50

模型管理(对齐 5545195):
- 模型表单/CreateModel 补齐 max_reference_videos/audios 与
  max_ref_video/audio_seconds 透传

产物(对齐 2253e58):
- 下载结果仅信任 image/*/video/* 响应头,text/plain 等乱报一律按魔数
  嗅探定 MIME

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-10-19 19:14:25 +08:00
co-authored by Devin
parent 72d4eec203
commit 4632b1ab04
9 changed files with 129 additions and 159 deletions
@@ -125,24 +125,6 @@ func (h *ChannelAdminHandler) ImportModels(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// UnbindModel detaches one upstream model from a channel; the local model row
// is deleted too when no other channel still binds it.
func (h *ChannelAdminHandler) UnbindModel(c *gin.Context) {
var body struct {
ID string `json:"id"`
Model string `json:"model"`
}
if err := c.ShouldBindJSON(&body); err != nil || strings.TrimSpace(body.ID) == "" || strings.TrimSpace(body.Model) == "" {
c.JSON(http.StatusBadRequest, gin.H{"detail": "id and model required"})
return
}
if err := h.channels.UnbindModel(c.Request.Context(), body.ID, body.Model); err != nil {
writeUpstreamErr(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// writeUpstreamErr translates upstream-protocol errors into sensible admin
// responses (auth → 400 with the upstream's message, temporary → 502).
func writeUpstreamErr(c *gin.Context, err error) {
-1
View File
@@ -148,7 +148,6 @@ func New(cfg *config.Config, auth *service.AuthService, handlers Handlers) *gin.
authed.POST("/channels/fetch-models", handlers.ChannelAdmin.FetchModels)
authed.POST("/channels/sync-models", handlers.ChannelAdmin.SyncModels)
authed.POST("/channels/import-models", handlers.ChannelAdmin.ImportModels)
authed.POST("/channels/unbind-model", handlers.ChannelAdmin.UnbindModel)
authed.GET("/providers", handlers.AdminRead.Providers)
authed.GET("/images", handlers.AdminRead.Images)
authed.DELETE("/images", handlers.AdminRead.DeleteImage)