feat(ui): 新增模型请求链路预览功能

- 添加后端 API 获取 GlobalModel 的请求链路信息 (/api/admin/models/global/{id}/routing)
- 新增 RoutingTab 组件展示模型的请求链路树状结构
- 支持全局 Key 优先和提供商优先两种模式的可视化
- 重构批量模型管理对话框为单列勾选模式
- 修复 errorParser.ts 字符串拼接格式问题
- 若干 Vue 模板格式优化
This commit is contained in:
fawney19
2026-01-12 23:28:37 +08:00
parent bde5bf4c76
commit 9fea71a70c
15 changed files with 1897 additions and 654 deletions

View File

@@ -107,8 +107,12 @@
/>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium truncate">{{ model.display_name }}</p>
<p class="text-xs text-muted-foreground truncate font-mono">{{ model.name }}</p>
<p class="text-sm font-medium truncate">
{{ model.display_name }}
</p>
<p class="text-xs text-muted-foreground truncate font-mono">
{{ model.name }}
</p>
</div>
</div>
</div>
@@ -159,8 +163,12 @@
/>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium truncate">{{ model.id }}</p>
<p class="text-xs text-muted-foreground truncate font-mono">{{ model.owned_by || model.id }}</p>
<p class="text-sm font-medium truncate">
{{ model.id }}
</p>
<p class="text-xs text-muted-foreground truncate font-mono">
{{ model.owned_by || model.id }}
</p>
</div>
</div>
</div>
@@ -172,7 +180,9 @@
class="flex flex-col items-center justify-center py-12 text-muted-foreground"
>
<Layers class="w-10 h-10 mb-2 opacity-30" />
<p class="text-sm">{{ searchQuery ? '无匹配结果' : '暂无可用模型' }}</p>
<p class="text-sm">
{{ searchQuery ? '无匹配结果' : '暂无可用模型' }}
</p>
<p
v-if="!upstreamModelsLoaded"
class="text-xs mt-1"

View File

@@ -76,7 +76,10 @@
</div>
<!-- 模型列表 -->
<div v-else class="space-y-2">
<div
v-else
class="space-y-2"
>
<!-- 全选/取消 -->
<div class="flex items-center justify-between px-1">
<div class="flex items-center gap-2">

View File

@@ -131,7 +131,10 @@
class="w-4 h-4 border rounded flex items-center justify-center shrink-0"
:class="selectedModels.includes(model) ? 'bg-primary border-primary' : ''"
>
<Check v-if="selectedModels.includes(model)" class="w-3 h-3 text-primary-foreground" />
<Check
v-if="selectedModels.includes(model)"
class="w-3 h-3 text-primary-foreground"
/>
</div>
<span class="text-sm font-mono truncate">{{ model }}</span>
</div>
@@ -174,18 +177,28 @@
class="w-4 h-4 border rounded flex items-center justify-center shrink-0"
:class="selectedModels.includes(model.name) ? 'bg-primary border-primary' : ''"
>
<Check v-if="selectedModels.includes(model.name)" class="w-3 h-3 text-primary-foreground" />
<Check
v-if="selectedModels.includes(model.name)"
class="w-3 h-3 text-primary-foreground"
/>
</div>
<div class="flex-1 min-w-0">
<p class="text-sm font-medium truncate">{{ model.display_name }}</p>
<p class="text-xs text-muted-foreground truncate font-mono">{{ model.name }}</p>
<p class="text-sm font-medium truncate">
{{ model.display_name }}
</p>
<p class="text-xs text-muted-foreground truncate font-mono">
{{ model.name }}
</p>
</div>
</div>
</div>
</div>
<!-- 上游模型组 -->
<div v-for="group in filteredUpstreamGroups" :key="group.api_format">
<div
v-for="group in filteredUpstreamGroups"
:key="group.api_format"
>
<div
class="flex items-center justify-between px-3 py-2 bg-muted sticky top-0 z-10 cursor-pointer hover:bg-muted/80 transition-colors"
@click="toggleGroupCollapse(group.api_format)"
@@ -222,7 +235,10 @@
class="w-4 h-4 border rounded flex items-center justify-center shrink-0"
:class="selectedModels.includes(model.id) ? 'bg-primary border-primary' : ''"
>
<Check v-if="selectedModels.includes(model.id)" class="w-3 h-3 text-primary-foreground" />
<Check
v-if="selectedModels.includes(model.id)"
class="w-3 h-3 text-primary-foreground"
/>
</div>
<span class="text-sm font-mono truncate">{{ model.id }}</span>
</div>
@@ -235,8 +251,15 @@
class="flex flex-col items-center justify-center py-12 text-muted-foreground"
>
<Shield class="w-10 h-10 mb-2 opacity-30" />
<p class="text-sm">{{ searchQuery ? '无匹配结果' : '暂无可选模型' }}</p>
<p v-if="!upstreamModelsLoaded" class="text-xs mt-1">点击闪电按钮从上游获取模型</p>
<p class="text-sm">
{{ searchQuery ? '无匹配结果' : '暂无可选模型' }}
</p>
<p
v-if="!upstreamModelsLoaded"
class="text-xs mt-1"
>
点击闪电按钮从上游获取模型
</p>
</div>
</template>
</div>
@@ -250,10 +273,18 @@
{{ hasChanges ? '有未保存的更改' : '' }}
</p>
<div class="flex items-center gap-2">
<Button :disabled="saving || !hasChanges" @click="handleSave">
<Button
:disabled="saving || !hasChanges"
@click="handleSave"
>
{{ saving ? '保存中...' : '保存' }}
</Button>
<Button variant="outline" @click="handleCancel">取消</Button>
<Button
variant="outline"
@click="handleCancel"
>
取消
</Button>
</div>
</div>
</template>

View File

@@ -83,7 +83,10 @@
</div>
<!-- 端点 API 格式 -->
<div class="flex items-center gap-1.5 flex-wrap mt-3">
<template v-for="endpoint in endpoints" :key="endpoint.id">
<template
v-for="endpoint in endpoints"
:key="endpoint.id"
>
<span
class="text-xs px-2 py-0.5 rounded-md border border-border bg-background hover:bg-accent hover:border-accent-foreground/20 cursor-pointer transition-colors font-medium"
:class="{ 'opacity-40': !endpoint.is_active }"
@@ -324,7 +327,10 @@
v-for="(format, idx) in getKeyApiFormats(key, endpoint)"
:key="format"
>
<span v-if="idx > 0" class="text-muted-foreground/40">/</span>
<span
v-if="idx > 0"
class="text-muted-foreground/40"
>/</span>
<span>{{ API_FORMAT_SHORT[format] || format }} {{ getKeyRateMultiplier(key, format) }}x</span>
</template>
<span v-if="key.rate_limit">| {{ key.rate_limit }}rpm</span>

View File

@@ -68,9 +68,15 @@
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="monthly_quota">月卡额度</SelectItem>
<SelectItem value="pay_as_you_go">按量付费</SelectItem>
<SelectItem value="free_tier">免费套餐</SelectItem>
<SelectItem value="monthly_quota">
月卡额度
</SelectItem>
<SelectItem value="pay_as_you_go">
按量付费
</SelectItem>
<SelectItem value="free_tier">
免费套餐
</SelectItem>
</SelectContent>
</Select>
</div>