渠道: 分协议 Base URL + 模型抽屉/远程拉取/操作图标

- 渠道支持分协议 base_url(chat/responses/messages 各一), 网关按协议选 base 直通
  (如智谱三种格式不同 base, 一个渠道即可), UpstreamURL 按 proto 拼接
- 渠道模型改为下方抽屉: 当前绑定列表(内联改上游/解除)、从接口拉取(remote 预览+勾选添加)、手动添加
- 新增 /channels/:id/models/remote 预览接口; 操作按钮加 Phosphor 图标
- 修复 formats jsonb 更新未序列化问题; 手机端渠道卡片化

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-08-16 04:31:28 +08:00
co-authored by Claude
parent 7c4e80afac
commit db83972b6f
11 changed files with 484 additions and 246 deletions
+50
View File
@@ -0,0 +1,50 @@
package store
import "testing"
func TestChannelUpstreamURL(t *testing.T) {
cases := []struct {
base, path, want string
}{
// 无版本前缀 → 默认补 /v1(OpenAI 纯域名)
{"https://api.openai.com", "/chat/completions", "https://api.openai.com/v1/chat/completions"},
// 已含 /v1 → 直接拼资源路径
{"https://api.openai.com/v1", "/chat/completions", "https://api.openai.com/v1/chat/completions"},
{"https://api.anthropic.com/v1", "/messages", "https://api.anthropic.com/v1/messages"},
// 自定义版本前缀(如 BigModel 的 /v4) → 直接拼资源路径
{"https://open.bigmodel.cn/api/paas/v4", "/chat/completions", "https://open.bigmodel.cn/api/paas/v4/chat/completions"},
{"https://open.bigmodel.cn/api/paas/v4", "/models", "https://open.bigmodel.cn/api/paas/v4/models"},
// 本地 mock:无版本前缀补 /v1
{"http://localhost:9000", "/chat/completions", "http://localhost:9000/v1/chat/completions"},
{"http://localhost:9000/v1", "/models", "http://localhost:9000/v1/models"},
// 完整端点原样
{"https://api.openai.com/v1/chat/completions", "/chat/completions", "https://api.openai.com/v1/chat/completions"},
// 尾斜杠
{"https://api.openai.com/v1/", "/chat/completions", "https://api.openai.com/v1/chat/completions"},
}
for _, c := range cases {
ch := &Channel{BaseURL: c.base}
got := ch.UpstreamURL("", c.path)
if got != c.want {
t.Errorf("UpstreamURL(%q, %q) = %q, want %q", c.base, c.path, got, c.want)
}
}
// 分协议 base_url 覆盖
ch := &Channel{
BaseURL: "https://open.bigmodel.cn/api/paas/v4",
BaseURLs: map[string]string{"responses": "https://resp.example.com/v4", "messages": "https://msg.example.com/v1"},
}
cases2 := []struct{ proto, path, want string }{
{"chat", "/chat/completions", "https://open.bigmodel.cn/api/paas/v4/chat/completions"}, // 用主 base
{"responses", "/responses", "https://resp.example.com/v4/responses"}, // 用覆盖 base
{"messages", "/messages", "https://msg.example.com/v1/messages"}, // 用覆盖 base
{"", "/models", "https://open.bigmodel.cn/api/paas/v4/models"}, // 空协议用主 base
}
for _, c := range cases2 {
got := ch.UpstreamURL(c.proto, c.path)
if got != c.want {
t.Errorf("UpstreamURL(%q, %q) = %q, want %q", c.proto, c.path, got, c.want)
}
}
}