diff --git a/server/internal/api/admin_channels.go b/server/internal/api/admin_channels.go index ea192d4..83436bd 100644 --- a/server/internal/api/admin_channels.go +++ b/server/internal/api/admin_channels.go @@ -45,8 +45,8 @@ func (h *Handler) AdminChannels(c *gin.Context) { type channelBody struct { Name string `json:"name" binding:"required,min=1,max=64"` - Provider string `json:"provider" binding:"required"` - Formats []string `json:"formats"` // 原生支持的协议 chat|responses|messages,空则按 provider 推断 + Provider string `json:"provider"` // 可选:为空时按 formats 推断(兼容旧数据) + Formats []string `json:"formats"` // 原生支持的协议 chat|responses|messages(主配置) BaseURL string `json:"base_url" binding:"required"` APIKey string `json:"api_key"` Weight *int `json:"weight"` @@ -64,6 +64,29 @@ var validFormats = map[string]bool{ store.FormatChat: true, store.FormatResponses: true, store.FormatMessages: true, } +// deriveProvider 按格式推断供应商(仅作内部字段/兼容用途,不参与路由)。 +func deriveProvider(formats []string) string { + if len(formats) == 0 { + return store.ChannelProviderCompatible + } + messagesOnly, hasResponses := true, false + for _, f := range formats { + if f != store.FormatMessages { + messagesOnly = false + } + if f == store.FormatResponses { + hasResponses = true + } + } + if messagesOnly { + return store.ChannelProviderAnthropic + } + if hasResponses { + return store.ChannelProviderOpenAI + } + return store.ChannelProviderCompatible +} + // resolveFormats 渠道协议格式:显式给出则校验去重;空则按 provider 推断默认。 func resolveFormats(provider string, formats []string) ([]string, error) { if len(formats) == 0 { @@ -97,6 +120,9 @@ func (h *Handler) AdminCreateChannel(c *gin.Context) { resp.Fail(c, http.StatusBadRequest, "invalid input: "+err.Error()) return } + if req.Provider == "" { + req.Provider = deriveProvider(req.Formats) + } if !validateProvider(req.Provider) { resp.Fail(c, http.StatusBadRequest, "provider must be openai, anthropic or compatible") return diff --git a/web/src/views/admin/ChannelsView.vue b/web/src/views/admin/ChannelsView.vue index ad71e08..40074d2 100644 --- a/web/src/views/admin/ChannelsView.vue +++ b/web/src/views/admin/ChannelsView.vue @@ -18,8 +18,7 @@ const busyId = ref(null) const form = reactive({ name: '', - provider: 'openai' as 'openai' | 'anthropic' | 'compatible', - formats: [] as string[], + formats: ['chat'] as string[], base_url: '', api_key: '', weight: 1, @@ -29,17 +28,6 @@ const form = reactive({ enabled: true, }) -// 按供应商推断默认支持的协议格式 -function defaultFormats(p: string): string[] { - if (p === 'anthropic') return ['messages'] - if (p === 'openai') return ['chat', 'responses'] - return ['chat'] -} - -function onProviderChange() { - form.formats = defaultFormats(form.provider) -} - async function load() { try { const { data } = await http.get('/admin/channels') @@ -52,7 +40,7 @@ async function load() { function openCreate() { editing.value = null Object.assign(form, { - name: '', provider: 'openai', formats: defaultFormats('openai'), base_url: '', api_key: '', + name: '', formats: ['chat'], base_url: '', api_key: '', weight: 1, priority: 0, timeout_ms: 120000, max_concurrency: 16, enabled: true, }) editOpen.value = true @@ -61,7 +49,7 @@ function openCreate() { function openEdit(ch: Channel) { editing.value = ch Object.assign(form, { - name: ch.name, provider: ch.provider, formats: [...(ch.formats || defaultFormats(ch.provider))], + name: ch.name, formats: [...(ch.formats?.length ? ch.formats : ['chat'])], base_url: ch.base_url, api_key: '', weight: ch.weight, priority: ch.priority, timeout_ms: ch.timeout_ms, max_concurrency: ch.max_concurrency, enabled: ch.enabled, @@ -70,6 +58,10 @@ function openEdit(ch: Channel) { } async function save() { + if (form.formats.length === 0) { + toast.err('请至少选择一种 API 格式') + return + } saving.value = true const payload = { ...form, @@ -199,21 +191,7 @@ onMounted(load)
-
- - -
+
支持的 API 格式