diff --git a/frontend/src/api/endpoints/global-models.ts b/frontend/src/api/endpoints/global-models.ts index d437ff3..ba0d4bf 100644 --- a/frontend/src/api/endpoints/global-models.ts +++ b/frontend/src/api/endpoints/global-models.ts @@ -6,6 +6,16 @@ import type { GlobalModelWithStats, GlobalModelListResponse, ModelCatalogProviderDetail, + ModelRoutingPreviewResponse, +} from './types' + +// 重新导出路由相关类型供外部使用 +export type { + RoutingKeyInfo, + RoutingEndpointInfo, + RoutingModelMapping, + RoutingProviderInfo, + ModelRoutingPreviewResponse, } from './types' /** @@ -97,3 +107,15 @@ export async function getGlobalModelProviders(globalModelId: string): Promise<{ ) return response.data } + +/** + * 获取 GlobalModel 的请求链路预览 + */ +export async function getGlobalModelRoutingPreview( + globalModelId: string +): Promise { + const response = await client.get( + `/api/admin/models/global/${globalModelId}/routing` + ) + return response.data +} diff --git a/frontend/src/api/endpoints/types.ts b/frontend/src/api/endpoints/types.ts index 0200809..c318766 100644 --- a/frontend/src/api/endpoints/types.ts +++ b/frontend/src/api/endpoints/types.ts @@ -622,3 +622,83 @@ export interface ImportFromUpstreamResponse { success: ImportFromUpstreamSuccessItem[] errors: ImportFromUpstreamErrorItem[] } + +// ========== 路由预览相关类型 ========== + +/** + * Key 路由信息 + */ +export interface RoutingKeyInfo { + id: string + name: string + masked_key: string + internal_priority: number + global_priority?: number | null + rpm_limit?: number | null + is_adaptive: boolean + effective_rpm?: number | null + cache_ttl_minutes: number + health_score: number + is_active: boolean + api_formats: string[] + circuit_breaker_open: boolean + circuit_breaker_formats: string[] +} + +/** + * Endpoint 路由信息 + */ +export interface RoutingEndpointInfo { + id: string + api_format: string + base_url: string + custom_path?: string | null + is_active: boolean + keys: RoutingKeyInfo[] + total_keys: number + active_keys: number +} + +/** + * 模型名称映射信息 + */ +export interface RoutingModelMapping { + name: string + priority: number + api_formats?: string[] | null +} + +/** + * Provider 路由信息 + */ +export interface RoutingProviderInfo { + id: string + name: string + model_id: string + provider_priority: number + billing_type?: string | null + monthly_quota_usd?: number | null + monthly_used_usd?: number | null + is_active: boolean + provider_model_name: string + model_mappings: RoutingModelMapping[] + model_is_active: boolean + endpoints: RoutingEndpointInfo[] + total_endpoints: number + active_endpoints: number +} + +/** + * 模型请求链路预览响应 + */ +export interface ModelRoutingPreviewResponse { + global_model_id: string + global_model_name: string + display_name: string + is_active: boolean + providers: RoutingProviderInfo[] + total_providers: number + active_providers: number + scheduling_mode: string + priority_mode: string +} diff --git a/frontend/src/api/global-models.ts b/frontend/src/api/global-models.ts index 42884a9..1c92a3c 100644 --- a/frontend/src/api/global-models.ts +++ b/frontend/src/api/global-models.ts @@ -21,4 +21,5 @@ export { deleteGlobalModel, batchAssignToProviders, getGlobalModelProviders, + getGlobalModelRoutingPreview, } from './endpoints/global-models' diff --git a/frontend/src/components/ui/collapsible-trigger.vue b/frontend/src/components/ui/collapsible-trigger.vue index b12d852..27fca42 100644 --- a/frontend/src/components/ui/collapsible-trigger.vue +++ b/frontend/src/components/ui/collapsible-trigger.vue @@ -5,7 +5,10 @@ const props = defineProps() diff --git a/frontend/src/features/models/components/ModelDetailDrawer.vue b/frontend/src/features/models/components/ModelDetailDrawer.vue index abd5f6a..666e555 100644 --- a/frontend/src/features/models/components/ModelDetailDrawer.vue +++ b/frontend/src/features/models/components/ModelDetailDrawer.vue @@ -95,14 +95,14 @@ type="button" class="flex-1 px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium rounded-md transition-all duration-200" :class="[ - detailTab === 'providers' + detailTab === 'routing' ? 'bg-primary text-primary-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground hover:bg-background/50' ]" - @click="detailTab = 'providers'" + @click="detailTab = 'routing'" > - - 提供商 + + 链路 @@ -407,269 +407,17 @@ - -
- - -
-
-
-

- 关联提供商列表 -

-
-
- - -
-
-
- - -
- -
- - - -
- - -

- 暂无关联提供商 -

- -
-
+ +
+
@@ -688,12 +436,8 @@ import { Zap, Image, Building2, - Plus, Edit, - Trash2, Power, - Loader2, - RefreshCw, Copy, Layers, BarChart3 @@ -711,14 +455,15 @@ import TableBody from '@/components/ui/table-body.vue' import TableRow from '@/components/ui/table-row.vue' import TableHead from '@/components/ui/table-head.vue' import TableCell from '@/components/ui/table-cell.vue' +import RoutingTab from './RoutingTab.vue' // 使用外部类型定义 import type { GlobalModelResponse } from '@/api/global-models' import type { TieredPricingConfig, PricingTier } from '@/api/endpoints/types' import type { CapabilityDefinition } from '@/api/endpoints' +import type { RoutingProviderInfo } from '@/api/global-models' const props = withDefaults(defineProps(), { - loadingProviders: false, hasBlockingDialogOpen: false, }) const emit = defineEmits<{ @@ -729,7 +474,6 @@ const emit = defineEmits<{ 'editProvider': [provider: any] 'deleteProvider': [provider: any] 'toggleProviderStatus': [provider: any] - 'refreshProviders': [] }>() const { success: showSuccess, error: showError } = useToast() const { copyToClipboard } = useClipboard() @@ -737,12 +481,48 @@ const { copyToClipboard } = useClipboard() interface Props { model: GlobalModelResponse | null open: boolean - providers: any[] - loadingProviders?: boolean hasBlockingDialogOpen?: boolean capabilities?: CapabilityDefinition[] } +// RoutingTab 引用 +const routingTabRef = ref | null>(null) + +// 将 RoutingProviderInfo 转换为父组件期望的格式 +function convertRoutingProviderToLegacyFormat(provider: RoutingProviderInfo) { + return { + id: provider.id, + model_id: provider.model_id, + name: provider.name, + is_active: provider.model_is_active + } +} + +// 处理从 RoutingTab 来的编辑事件 +function handleEditProviderFromRouting(provider: RoutingProviderInfo) { + emit('editProvider', convertRoutingProviderToLegacyFormat(provider)) +} + +// 处理从 RoutingTab 来的状态切换事件 +function handleToggleProviderFromRouting(provider: RoutingProviderInfo) { + emit('toggleProviderStatus', convertRoutingProviderToLegacyFormat(provider)) +} + +// 处理从 RoutingTab 来的删除事件 +function handleDeleteProviderFromRouting(provider: RoutingProviderInfo) { + emit('deleteProvider', convertRoutingProviderToLegacyFormat(provider)) +} + +// 刷新路由数据 +function refreshRoutingData() { + routingTabRef.value?.loadRoutingData?.() +} + +// 暴露刷新方法给父组件 +defineExpose({ + refreshRoutingData +}) + // 根据能力名称获取显示名称 function getCapabilityDisplayName(capName: string): string { const cap = props.capabilities?.find(c => c.name === capName) diff --git a/frontend/src/features/models/components/RoutingTab.vue b/frontend/src/features/models/components/RoutingTab.vue new file mode 100644 index 0000000..ce948f4 --- /dev/null +++ b/frontend/src/features/models/components/RoutingTab.vue @@ -0,0 +1,1014 @@ + + + + + diff --git a/frontend/src/features/providers/components/BatchAssignModelsDialog.vue b/frontend/src/features/providers/components/BatchAssignModelsDialog.vue index 3fd230d..38f3f34 100644 --- a/frontend/src/features/providers/components/BatchAssignModelsDialog.vue +++ b/frontend/src/features/providers/components/BatchAssignModelsDialog.vue @@ -107,8 +107,12 @@ />
-

{{ model.display_name }}

-

{{ model.name }}

+

+ {{ model.display_name }} +

+

+ {{ model.name }} +

@@ -159,8 +163,12 @@ />
-

{{ model.id }}

-

{{ model.owned_by || model.id }}

+

+ {{ model.id }} +

+

+ {{ model.owned_by || model.id }} +

@@ -172,7 +180,9 @@ class="flex flex-col items-center justify-center py-12 text-muted-foreground" > -

{{ searchQuery ? '无匹配结果' : '暂无可用模型' }}

+

+ {{ searchQuery ? '无匹配结果' : '暂无可用模型' }} +

-

+
diff --git a/frontend/src/features/providers/components/KeyAllowedModelsEditDialog.vue b/frontend/src/features/providers/components/KeyAllowedModelsEditDialog.vue index 7db2cea..a25e8ea 100644 --- a/frontend/src/features/providers/components/KeyAllowedModelsEditDialog.vue +++ b/frontend/src/features/providers/components/KeyAllowedModelsEditDialog.vue @@ -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' : ''" > - +
{{ model }}
@@ -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' : ''" > - +
-

{{ model.display_name }}

-

{{ model.name }}

+

+ {{ model.display_name }} +

+

+ {{ model.name }} +

-
+
- +
{{ model.id }}
@@ -235,8 +251,15 @@ class="flex flex-col items-center justify-center py-12 text-muted-foreground" > -

{{ searchQuery ? '无匹配结果' : '暂无可选模型' }}

-

点击闪电按钮从上游获取模型

+

+ {{ searchQuery ? '无匹配结果' : '暂无可选模型' }} +

+

+ 点击闪电按钮从上游获取模型 +

@@ -250,10 +273,18 @@ {{ hasChanges ? '有未保存的更改' : '' }}

- - +
diff --git a/frontend/src/features/providers/components/ProviderDetailDrawer.vue b/frontend/src/features/providers/components/ProviderDetailDrawer.vue index 20f66ff..0883669 100644 --- a/frontend/src/features/providers/components/ProviderDetailDrawer.vue +++ b/frontend/src/features/providers/components/ProviderDetailDrawer.vue @@ -83,7 +83,10 @@
-