From 3073c6fd5e926bb60f5ccbb7fd7dbbc09c395a07 Mon Sep 17 00:00:00 2001 From: qian Date: Tue, 18 Aug 2026 00:12:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(channel):=E4=BF=AE=E5=A4=8D=E6=B8=A0?= =?UTF-8?q?=E9=81=93=E4=BF=AE=E6=94=B9base=20url=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E5=90=8E=E4=B8=8D=E5=8C=B9=E9=85=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/admin/v1/channel.go | 2 +- internal/logic/admin/channel.go | 42 +++++++++++++++++++++++++ web/admin/src/constants/channel.ts | 2 +- web/admin/src/views/ai/ChannelsPage.vue | 13 ++++---- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/api/admin/v1/channel.go b/api/admin/v1/channel.go index 65ab80f..96af813 100644 --- a/api/admin/v1/channel.go +++ b/api/admin/v1/channel.go @@ -81,7 +81,7 @@ type ChannelUpdateReq struct { ID int64 `json:"id" in:"path" v:"required" dc:"渠道ID"` Name string `json:"name" dc:"渠道名称"` Type *int `json:"type" v:"min:1#请选择供应商类型" dc:"供应商类型(留空不修改)"` - BaseURL string `json:"base_url" dc:"API 基础地址"` + BaseURL string `json:"base_url" dc:"API 基础地址(留空不更新;切换类型且旧地址为该类型默认值时自动跟随新类型默认地址)"` ApiKey *string `json:"api_key" dc:"更新 API Key(留空不更新)"` Priority *int `json:"priority" dc:"优先级(留空不更新)"` Weight *int `json:"weight" dc:"权重(留空不更新)"` diff --git a/internal/logic/admin/channel.go b/internal/logic/admin/channel.go index 6fbd7b9..e16fbe6 100644 --- a/internal/logic/admin/channel.go +++ b/internal/logic/admin/channel.go @@ -352,6 +352,19 @@ func (s *sAdmin) UpdateChannel(ctx context.Context, req *v1.ChannelUpdateReq) (* } if req.BaseURL != "" { data.BaseUrl = req.BaseURL + } else if req.Type != nil { + // 切换供应商类型且未显式传 base_url:旧地址为空或等于旧类型默认值(用户未自定义过)时, + // 自动跟随为新类型默认地址,避免残留旧供应商端点导致上游请求 404;自定义地址(如中转站)保留不动 + var old struct { + Type int `json:"type"` + BaseUrl string `json:"base_url"` + } + if err := dao.ChnChannels.Ctx(ctx).Where("id", req.ID).Fields("type, base_url").Scan(&old); err == nil { + if newDefault := defaultProviderURL(*req.Type); newDefault != "" && + (old.BaseUrl == "" || old.BaseUrl == defaultProviderURL(old.Type)) { + data.BaseUrl = newDefault + } + } } // priority/weight 为指针字段,仅在显式传入时更新,避免不含这两个字段的 // 局部更新(如仅切换状态、仅换 Key)把已有值洗成 0。 @@ -721,6 +734,35 @@ var defaultProviderURLs = map[int]string{ 14: "https://api.cohere.com", 15: "https://api.mistral.ai", 16: "https://api.x.ai", + // 零一万物 OpenAI 兼容端点,adaptor 自拼 /v1/chat/completions + 18: "https://api.lingyiwanwu.com", + // 百度千帆 v2:qianfan.baidubce.com/v2/chat/completions + 19: "https://qianfan.baidubce.com", + // Cloudflare 填裸域名即可:adaptor 自拼 /client/v4/accounts/{id}/ai/v1, + // account_id 从 ApiKey(token|accountid 格式)解析,无需写在地址里 + 20: "https://api.cloudflare.com", + // Ollama 本地服务,adaptor 自拼 /api/chat + 22: "http://localhost:11434", + // 硅基流动(国内站;国际站为 api.siliconflow.com) + 25: "https://api.siliconflow.cn", + // 讯飞 OpenAI 兼容端点是 spark-api-open(注意 -open 后缀), + // 旧版 spark-api.xf-yun.com 是 WebSocket 协议主机,走这里会 404 + 26: "https://spark-api-open.xf-yun.com", + // OpenRouter:openrouter.ai/api/v1/chat/completions + 27: "https://openrouter.ai/api", + // XInference 默认本地端口 + 28: "http://localhost:9997", + // MiniMax 国内站(海外站为 api.minimaxi.chat),adaptor 自拼 /v1/text/chatcompletion_v2 + 29: "https://api.minimax.chat", + // Coze 国内站(海外站为 api.coze.com),adaptor 自拼 /v3/chat + 32: "https://api.coze.cn", + // Dify 云端默认(api.dify.ai/v1/chat-messages);自部署实例需自行填写。 + // 填裸域名即可,adaptor 自拼 /v1/chat-messages,带 /v1 会双拼 + 33: "https://api.dify.ai", + // 即梦:适配器拼 /v2/images/generations,无官方对齐端点可验证,需自行填写 + 34: "", + // Codex 直连 chatgpt.com/backend-api/codex/responses(对齐 Codex CLI) + 35: "https://chatgpt.com", 41: "", // New API 自引用渠道,需自行填写实例地址 42: "", // Sub2API 渠道,需自行填写实例地址 } diff --git a/web/admin/src/constants/channel.ts b/web/admin/src/constants/channel.ts index 4a068ba..d0aeb6f 100644 --- a/web/admin/src/constants/channel.ts +++ b/web/admin/src/constants/channel.ts @@ -12,7 +12,7 @@ export const providerTypeOptions: { label: string; value: number }[] = [ { label: 'AWS Bedrock', value: 11 }, { label: 'Azure OpenAI', value: 12 }, { label: 'Vertex AI', value: 13 }, { label: 'Mistral', value: 15 }, { label: 'xAI (Grok)', value: 16 }, - { label: '360 智脑', value: 17 }, { label: 'Lingyi (零一万物)', value: 18 }, + { label: 'Lingyi (零一万物)', value: 18 }, { label: 'Baidu V2', value: 19 }, { label: 'Cloudflare Workers AI', value: 20 }, { label: 'Ollama', value: 22 }, { label: 'SiliconFlow (硅基流动)', value: 25 }, { label: 'Xunfei (讯飞)', value: 26 }, diff --git a/web/admin/src/views/ai/ChannelsPage.vue b/web/admin/src/views/ai/ChannelsPage.vue index 36c1f6d..814ecef 100644 --- a/web/admin/src/views/ai/ChannelsPage.vue +++ b/web/admin/src/views/ai/ChannelsPage.vue @@ -231,7 +231,7 @@ const providerDefaultURLs: Record = { 3: 'https://generativelanguage.googleapis.com', 4: 'https://dashscope.aliyuncs.com', 6: 'https://hunyuan.tencentcloudapi.com', - 7: 'https://open.bigmodel.cn/api/paas', + 7: 'https://open.bigmodel.cn', 8: 'https://api.deepseek.com', 9: 'https://api.moonshot.cn', 10: 'https://ark.cn-beijing.volces.com/api', @@ -240,21 +240,20 @@ const providerDefaultURLs: Record = { 13: 'https://us-central1-aiplatform.googleapis.com', 15: 'https://api.mistral.ai', 16: 'https://api.x.ai', - 17: 'https://ai.360.cn', 18: 'https://api.lingyiwanwu.com', 19: 'https://qianfan.baidubce.com', - 20: 'https://api.cloudflare.com/client/v4/accounts/{account_id}', + 20: 'https://api.cloudflare.com', 22: 'http://localhost:11434', 25: 'https://api.siliconflow.cn', - 26: 'https://spark-api.xf-yun.com', + 26: 'https://spark-api-open.xf-yun.com', 27: 'https://openrouter.ai/api', 28: 'http://localhost:9997', 29: 'https://api.minimax.chat', 30: '', 32: 'https://api.coze.cn', - 33: 'https://api.dify.ai/v1', - 34: 'https://jimeng.jianying.com', - 35: 'https://api.openai.com', + 33: 'https://api.dify.ai', + 34: '', + 35: 'https://chatgpt.com', } const form = reactive({ name: '', type: 1, base_url: '', api_key: '', priority: 0, weight: 100, max_concurrency: 100, tier: 'primary', strict_capacity: false, test_model: '', remark: '', status: 'active', is_vip: false, use_proxy: false, sharing_threshold: null as number | null, preemption_threshold: null as number | null, borrowing_cooldown_seconds: null as number | null })