feat(models): 路由预览显示 Key 白名单的模型映射匹配

- 后端返回 GlobalModel 的 model_mappings 正则模式
- 前端用映射模式匹配 Key 白名单,显示匹配的模型名
This commit is contained in:
fawney19
2026-01-15 02:36:00 +08:00
parent 4835ca13f5
commit 5d34426428
3 changed files with 89 additions and 5 deletions

View File

@@ -707,6 +707,7 @@ export interface ModelRoutingPreviewResponse {
global_model_name: string
display_name: string
is_active: boolean
global_model_mappings: string[] // GlobalModel 的模型映射规则(正则模式)
providers: RoutingProviderInfo[]
total_providers: number
active_providers: number

View File

@@ -174,11 +174,24 @@
>
{{ keyEntry.key.name }}
</div>
<!-- 第二行提供商名 · sk -->
<!-- 第二行提供商名 · sk · 模型映射 -->
<div class="flex items-center gap-1 text-[10px] text-muted-foreground">
<span>{{ keyEntry.provider.name }}</span>
<span>·</span>
<code class="text-muted-foreground/60">{{ keyEntry.key.masked_key }}</code>
<!-- Key 白名单映射显示 -->
<template v-if="getKeyMatchedModels(keyEntry.key).length > 0">
<span>·</span>
<span
class="text-primary/70"
:title="getKeyMatchedModels(keyEntry.key).join(', ')"
>{{ formatMatchedModels(getKeyMatchedModels(keyEntry.key)) }}</span>
</template>
<!-- Provider 模型映射显示 -->
<template v-else-if="hasModelMapping(keyEntry.provider)">
<span>·</span>
<span class="text-primary/70">{{ keyEntry.provider.provider_model_name }}</span>
</template>
</div>
</div>
@@ -374,6 +387,14 @@
v-if="isProviderInFormatExpanded(formatGroup.api_format, providerEntry.provider.id, providerEntry.endpoint?.id)"
class="border-t border-border/30 p-2.5"
>
<!-- 模型映射显示 -->
<div
v-if="hasModelMapping(providerEntry.provider)"
class="flex items-center gap-1.5 text-[10px] text-muted-foreground mb-2 px-1"
>
<span class="text-muted-foreground/60">映射:</span>
<span class="text-primary/70 font-medium">{{ providerEntry.provider.provider_model_name }}</span>
</div>
<!-- Keys 列表 -->
<div
v-if="providerEntry.keys.length > 0"
@@ -430,7 +451,7 @@
]"
:title="getKeyTooltip(key)"
>
<!-- 名称 + sk垂直堆叠 -->
<!-- 名称 + sk + 映射垂直堆叠 -->
<div class="min-w-0 flex flex-col">
<span
class="font-medium truncate"
@@ -438,9 +459,18 @@
>
{{ key.name }}
</span>
<code class="font-mono text-[10px] text-muted-foreground/60">
{{ key.masked_key }}
</code>
<div class="flex items-center gap-1">
<code class="font-mono text-[10px] text-muted-foreground/60">
{{ key.masked_key }}
</code>
<template v-if="getKeyMatchedModels(key).length > 0">
<span class="text-[10px] text-muted-foreground/40">·</span>
<span
class="text-[10px] text-primary/70"
:title="getKeyMatchedModels(key).join(', ')"
>{{ formatMatchedModels(getKeyMatchedModels(key)) }}</span>
</template>
</div>
</div>
<span class="flex-1" />
<!-- 熔断徽章带倒计时- 靠右 -->
@@ -816,6 +846,47 @@ function hasModelMapping(provider: RoutingProviderInfo): boolean {
return provider.provider_model_name !== routingData.value?.global_model_name
}
// 获取 Key 的 allowed_models 中匹配当前 GlobalModel 的所有模型名
// 逻辑:用 GlobalModel 的 model_mappings正则模式去匹配 Key 的 allowed_models 中的值
function getKeyMatchedModels(key: RoutingKeyInfo): string[] {
if (!key.allowed_models || key.allowed_models.length === 0) {
return []
}
const globalModelName = routingData.value?.global_model_name
const globalModelMappings = routingData.value?.global_model_mappings || []
if (!globalModelName) {
return []
}
const matched: string[] = []
// 遍历 Key 的白名单
for (const allowedModel of key.allowed_models) {
// 如果完全匹配 GlobalModel 名称,跳过(不需要显示)
if (allowedModel === globalModelName) {
continue
}
// 用 GlobalModel 的映射模式匹配白名单中的模型名
for (const pattern of globalModelMappings) {
try {
if (new RegExp(`^${pattern}$`, 'i').test(allowedModel)) {
matched.push(allowedModel)
break // 该 allowedModel 已匹配,不需要继续检查其他 pattern
}
} catch {
// 正则无效,跳过
}
}
}
return matched
}
// 格式化匹配的模型显示文本
function formatMatchedModels(models: string[]): string {
if (models.length === 0) return ''
if (models.length === 1) return models[0]
return `${models[0]} +${models.length - 1}`
}
// 按优先级分组 Keys提供商优先模式下使用
interface KeyPriorityGroup {
priority: number | null