From 52ef6862026e835355d1bda960de9ae9ca34a2a7 Mon Sep 17 00:00:00 2001 From: qian Date: Thu, 23 Jul 2026 00:32:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=A8=A1=E5=9E=8B=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=A8=A1=E5=9E=8B=E5=8F=AF=E7=94=A8=E6=B8=A0?= =?UTF-8?q?=E9=81=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/admin/v1/model.go | 9 ++++++ internal/logic/admin/model.go | 37 +++++++++++++++++++++++ web/admin/src/views/ai/ChannelsPage.vue | 39 +++++++++++++++++++++++++ web/admin/src/views/ai/ModelsPage.vue | 23 ++++++++++++++- 4 files changed, 107 insertions(+), 1 deletion(-) diff --git a/api/admin/v1/model.go b/api/admin/v1/model.go index ef3efdc..5fd9a07 100644 --- a/api/admin/v1/model.go +++ b/api/admin/v1/model.go @@ -21,6 +21,13 @@ type ModelListRes struct { PageSize int `json:"page_size"` } +// ModelChannelInfo 模型关联的渠道信息 +type ModelChannelInfo struct { + ChannelID int64 `json:"channel_id"` + ChannelName string `json:"channel_name"` + Type int `json:"type"` +} + // ModelItem 模型信息 type ModelItem struct { ID int64 `json:"id"` @@ -43,6 +50,8 @@ type ModelItem struct { InputPrice float64 `json:"input_price"` // $/1M tokens OutputPrice float64 `json:"output_price"` // $/1M tokens PerRequestPrice float64 `json:"per_request_price"` // $/request(按次计费模式) + // 可用渠道列表(来自 chn_abilities + chn_channels) + Channels []ModelChannelInfo `json:"channels"` } // ModelCreateReq 创建模型请求 diff --git a/internal/logic/admin/model.go b/internal/logic/admin/model.go index b101769..5c4ec11 100644 --- a/internal/logic/admin/model.go +++ b/internal/logic/admin/model.go @@ -137,6 +137,39 @@ func (s *sAdmin) ListModels(ctx context.Context, req *v1.ModelListReq) (*v1.Mode pricingMap[p.ModelId] = p } + // 批量查询渠道支持情况 + type channelAbilityRow struct { + ModelName string `json:"model_name"` + ChannelID int64 `json:"channel_id"` + ChannelName string `json:"channel_name"` + Type int `json:"type"` + } + modelIdStrs := make([]string, 0, len(models)) + for _, m := range models { + modelIdStrs = append(modelIdStrs, m.ModelId) + } + var channelRows []channelAbilityRow + if len(modelIdStrs) > 0 { + err = dao.ChnAbilities.Ctx(ctx). + LeftJoin("chn_channels", "chn_channels.id = chn_abilities.channel_id"). + Fields("chn_abilities.model_name, chn_abilities.channel_id, chn_channels.name AS channel_name, chn_channels.type"). + Where("chn_abilities.model_name IN (?)", modelIdStrs). + Where("chn_abilities.enabled", true). + Where("chn_channels.status", "active"). + Scan(&channelRows) + if err = common.IgnoreScanNoRows(err); err != nil { + return nil, err + } + } + channelMap := make(map[string][]v1.ModelChannelInfo, len(channelRows)) + for _, r := range channelRows { + channelMap[r.ModelName] = append(channelMap[r.ModelName], v1.ModelChannelInfo{ + ChannelID: r.ChannelID, + ChannelName: r.ChannelName, + Type: r.Type, + }) + } + list := make([]v1.ModelItem, 0, len(models)) for _, m := range models { item := v1.ModelItem{ @@ -171,6 +204,10 @@ func (s *sAdmin) ListModels(ctx context.Context, req *v1.ModelListReq) (*v1.Mode item.PerRequestPrice = *p.PerRequestPrice } } + // 填充可用渠道 + if chs, ok := channelMap[m.ModelId]; ok { + item.Channels = chs + } list = append(list, item) } diff --git a/web/admin/src/views/ai/ChannelsPage.vue b/web/admin/src/views/ai/ChannelsPage.vue index 6fadfe3..1ac161b 100644 --- a/web/admin/src/views/ai/ChannelsPage.vue +++ b/web/admin/src/views/ai/ChannelsPage.vue @@ -297,6 +297,8 @@ async function handleOAuthExchange() { finally { oauthLoading.value = false } } +const showSchedulingGuide = ref(false) + const { exporting, exportFile } = useExport({ url: '/admin/channels/export', getFilters: () => ({ @@ -323,6 +325,29 @@ const { exporting, exportFile } = useExport({ + +
+ 了解优先级和权重如何影响渠道调度 → +
+ + + +
+

请求到达时,调度器按以下规则从启用渠道中选择一个处理:

+
    +
  1. 排除健康度 < 20 的渠道(如果全部不健康则降级使用全部)
  2. +
  3. 按优先级分组,只取最高优先级组的渠道(数字越大越优先)
  4. +
  5. 组内按权重加权随机选择(权重越高被选中的概率越大)
  6. +
  7. 同时根据健康度对权重降权:健康度 50-79 权重减半,20-49 权重降至 1/4
  8. +
+
+
💡 配置示例
+

两个同供应商渠道分别设 优先级=100(主力)和 优先级=50(备用),调度器始终优先使用主力渠道,仅在主力不可用时切换到备用。

+

同一优先级内两个渠道分别设 权重=70 和 权重=30,则流量按 7:3 比例分发。

+
+
+
+ @@ -474,4 +499,18 @@ const { exporting, exportFile } = useExport({ padding-top: 16px; border-top: 1px solid var(--ta-border-light); } +.scheduling-hint { + display: inline-flex; + align-items: center; + gap: 4px; + margin-bottom: 16px; + font-size: 13px; + color: var(--color-text-3); + cursor: pointer; + transition: color 0.2s; + user-select: none; +} +.scheduling-hint:hover { + color: rgb(var(--arcoblue-6)); +} diff --git a/web/admin/src/views/ai/ModelsPage.vue b/web/admin/src/views/ai/ModelsPage.vue index 8ca6c5b..d3cac58 100644 --- a/web/admin/src/views/ai/ModelsPage.vue +++ b/web/admin/src/views/ai/ModelsPage.vue @@ -1,7 +1,7 @@