mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-03 00:02:28 +08:00
refactor(frontend): 优化功能模块组件
- 更新 api-keys 模块: StandaloneKeyFormDialog - 改进 auth 模块: LoginDialog - 优化 models 模块: AliasDialog, GlobalModelFormDialog, ModelDetailDrawer, TieredPricingEditor - 重构 providers 模块: 多个表单和对话框组件 - 更新 usage 模块: 时间线、表格和详情组件 - 调整 users 模块: UserFormDialog
This commit is contained in:
@@ -8,7 +8,10 @@
|
||||
@click.self="handleBackdropClick"
|
||||
>
|
||||
<!-- 背景遮罩 -->
|
||||
<div class="absolute inset-0 bg-black/30 backdrop-blur-sm" @click="handleBackdropClick"></div>
|
||||
<div
|
||||
class="absolute inset-0 bg-black/30 backdrop-blur-sm"
|
||||
@click="handleBackdropClick"
|
||||
/>
|
||||
|
||||
<!-- 抽屉内容 -->
|
||||
<Card class="relative h-full w-[700px] rounded-none shadow-2xl overflow-y-auto">
|
||||
@@ -16,8 +19,13 @@
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="space-y-1 flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-xl font-bold truncate">{{ model.display_name }}</h3>
|
||||
<Badge :variant="model.is_active ? 'default' : 'secondary'" class="text-xs shrink-0">
|
||||
<h3 class="text-xl font-bold truncate">
|
||||
{{ model.display_name }}
|
||||
</h3>
|
||||
<Badge
|
||||
:variant="model.is_active ? 'default' : 'secondary'"
|
||||
class="text-xs shrink-0"
|
||||
>
|
||||
{{ model.is_active ? '活跃' : '停用' }}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -32,23 +40,36 @@
|
||||
</button>
|
||||
<template v-if="model.description">
|
||||
<span class="shrink-0">·</span>
|
||||
<span class="text-xs truncate" :title="model.description">{{ model.description }}</span>
|
||||
<span
|
||||
class="text-xs truncate"
|
||||
:title="model.description"
|
||||
>{{ model.description }}</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 shrink-0">
|
||||
<Button variant="ghost" size="icon" @click="$emit('edit-model', model)" title="编辑模型">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
title="编辑模型"
|
||||
@click="$emit('edit-model', model)"
|
||||
>
|
||||
<Edit class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@click="$emit('toggle-model-status', model)"
|
||||
:title="model.is_active ? '点击停用' : '点击启用'"
|
||||
@click="$emit('toggle-model-status', model)"
|
||||
>
|
||||
<Power class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" @click="handleClose" title="关闭">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
title="关闭"
|
||||
@click="handleClose"
|
||||
>
|
||||
<X class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -56,110 +77,154 @@
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<!-- 自定义 Tab 切换 -->
|
||||
<div class="flex gap-1 p-1 bg-muted/40 rounded-lg mb-4">
|
||||
<button
|
||||
type="button"
|
||||
:class="[
|
||||
'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200',
|
||||
detailTab === 'basic'
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
|
||||
]"
|
||||
@click="detailTab = 'basic'"
|
||||
>
|
||||
基本信息
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="[
|
||||
'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200',
|
||||
detailTab === 'providers'
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
|
||||
]"
|
||||
@click="detailTab = 'providers'"
|
||||
>
|
||||
关联提供商
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="[
|
||||
'flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200',
|
||||
detailTab === 'aliases'
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
|
||||
]"
|
||||
@click="detailTab = 'aliases'"
|
||||
>
|
||||
别名/映射
|
||||
</button>
|
||||
</div>
|
||||
<!-- 自定义 Tab 切换 -->
|
||||
<div class="flex gap-1 p-1 bg-muted/40 rounded-lg mb-4">
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200"
|
||||
:class="[
|
||||
detailTab === 'basic'
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
|
||||
]"
|
||||
@click="detailTab = 'basic'"
|
||||
>
|
||||
基本信息
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200"
|
||||
:class="[
|
||||
detailTab === 'providers'
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
|
||||
]"
|
||||
@click="detailTab = 'providers'"
|
||||
>
|
||||
关联提供商
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200"
|
||||
:class="[
|
||||
detailTab === 'aliases'
|
||||
? 'bg-primary text-primary-foreground shadow-sm'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
|
||||
]"
|
||||
@click="detailTab = 'aliases'"
|
||||
>
|
||||
别名/映射
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab 内容 -->
|
||||
<div v-show="detailTab === 'basic'" class="space-y-6">
|
||||
<!-- Tab 内容 -->
|
||||
<div
|
||||
v-show="detailTab === 'basic'"
|
||||
class="space-y-6"
|
||||
>
|
||||
<!-- 基础属性 -->
|
||||
<div class="space-y-4">
|
||||
<h4 class="font-semibold text-sm">基础属性</h4>
|
||||
<h4 class="font-semibold text-sm">
|
||||
基础属性
|
||||
</h4>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label class="text-xs text-muted-foreground">创建时间</Label>
|
||||
<p class="text-sm mt-1">{{ formatDate(model.created_at) }}</p>
|
||||
<p class="text-sm mt-1">
|
||||
{{ formatDate(model.created_at) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型能力 -->
|
||||
<div class="space-y-3">
|
||||
<h4 class="font-semibold text-sm">模型能力</h4>
|
||||
<h4 class="font-semibold text-sm">
|
||||
模型能力
|
||||
</h4>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="flex items-center gap-2 p-3 rounded-lg border">
|
||||
<Zap class="w-5 h-5 text-muted-foreground" />
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-medium">Streaming</p>
|
||||
<p class="text-xs text-muted-foreground">流式输出</p>
|
||||
<p class="text-sm font-medium">
|
||||
Streaming
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
流式输出
|
||||
</p>
|
||||
</div>
|
||||
<Badge :variant="model.default_supports_streaming ?? false ? 'default' : 'secondary'" class="text-xs">
|
||||
<Badge
|
||||
:variant="model.default_supports_streaming ?? false ? 'default' : 'secondary'"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ model.default_supports_streaming ?? false ? '支持' : '不支持' }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 p-3 rounded-lg border">
|
||||
<Image class="w-5 h-5 text-muted-foreground" />
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-medium">Image Generation</p>
|
||||
<p class="text-xs text-muted-foreground">图像生成</p>
|
||||
<p class="text-sm font-medium">
|
||||
Image Generation
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
图像生成
|
||||
</p>
|
||||
</div>
|
||||
<Badge :variant="model.default_supports_image_generation ?? false ? 'default' : 'secondary'" class="text-xs">
|
||||
<Badge
|
||||
:variant="model.default_supports_image_generation ?? false ? 'default' : 'secondary'"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ model.default_supports_image_generation ?? false ? '支持' : '不支持' }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 p-3 rounded-lg border">
|
||||
<Eye class="w-5 h-5 text-muted-foreground" />
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-medium">Vision</p>
|
||||
<p class="text-xs text-muted-foreground">视觉理解</p>
|
||||
<p class="text-sm font-medium">
|
||||
Vision
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
视觉理解
|
||||
</p>
|
||||
</div>
|
||||
<Badge :variant="model.default_supports_vision ?? false ? 'default' : 'secondary'" class="text-xs">
|
||||
<Badge
|
||||
:variant="model.default_supports_vision ?? false ? 'default' : 'secondary'"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ model.default_supports_vision ?? false ? '支持' : '不支持' }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 p-3 rounded-lg border">
|
||||
<Wrench class="w-5 h-5 text-muted-foreground" />
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-medium">Tool Use</p>
|
||||
<p class="text-xs text-muted-foreground">工具调用</p>
|
||||
<p class="text-sm font-medium">
|
||||
Tool Use
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
工具调用
|
||||
</p>
|
||||
</div>
|
||||
<Badge :variant="model.default_supports_function_calling ?? false ? 'default' : 'secondary'" class="text-xs">
|
||||
<Badge
|
||||
:variant="model.default_supports_function_calling ?? false ? 'default' : 'secondary'"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ model.default_supports_function_calling ?? false ? '支持' : '不支持' }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 p-3 rounded-lg border">
|
||||
<Brain class="w-5 h-5 text-muted-foreground" />
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-medium">Extended Thinking</p>
|
||||
<p class="text-xs text-muted-foreground">深度思考</p>
|
||||
<p class="text-sm font-medium">
|
||||
Extended Thinking
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
深度思考
|
||||
</p>
|
||||
</div>
|
||||
<Badge :variant="model.default_supports_extended_thinking ?? false ? 'default' : 'secondary'" class="text-xs">
|
||||
<Badge
|
||||
:variant="model.default_supports_extended_thinking ?? false ? 'default' : 'secondary'"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ model.default_supports_extended_thinking ?? false ? '支持' : '不支持' }}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -167,8 +232,13 @@
|
||||
</div>
|
||||
|
||||
<!-- 模型偏好 -->
|
||||
<div v-if="model.supported_capabilities && model.supported_capabilities.length > 0" class="space-y-3">
|
||||
<h4 class="font-semibold text-sm">模型偏好</h4>
|
||||
<div
|
||||
v-if="model.supported_capabilities && model.supported_capabilities.length > 0"
|
||||
class="space-y-3"
|
||||
>
|
||||
<h4 class="font-semibold text-sm">
|
||||
模型偏好
|
||||
</h4>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Badge
|
||||
v-for="cap in model.supported_capabilities"
|
||||
@@ -183,10 +253,15 @@
|
||||
|
||||
<!-- 默认定价 -->
|
||||
<div class="space-y-3">
|
||||
<h4 class="font-semibold text-sm">默认定价</h4>
|
||||
<h4 class="font-semibold text-sm">
|
||||
默认定价
|
||||
</h4>
|
||||
|
||||
<!-- 单阶梯(固定价格)展示 -->
|
||||
<div v-if="getTierCount(model.default_tiered_pricing) <= 1" class="space-y-3">
|
||||
<div
|
||||
v-if="getTierCount(model.default_tiered_pricing) <= 1"
|
||||
class="space-y-3"
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<!-- 按 Token 计费 -->
|
||||
<div class="p-3 rounded-lg border">
|
||||
@@ -215,19 +290,28 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 1h 缓存 -->
|
||||
<div v-if="getFirst1hCachePrice(model.default_tiered_pricing) !== '-'" class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20">
|
||||
<div
|
||||
v-if="getFirst1hCachePrice(model.default_tiered_pricing) !== '-'"
|
||||
class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20"
|
||||
>
|
||||
<Label class="text-xs text-muted-foreground whitespace-nowrap">1h 缓存创建</Label>
|
||||
<span class="text-sm font-mono">{{ getFirst1hCachePrice(model.default_tiered_pricing) }}</span>
|
||||
</div>
|
||||
<!-- 按次计费 -->
|
||||
<div v-if="model.default_price_per_request && model.default_price_per_request > 0" class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20">
|
||||
<div
|
||||
v-if="model.default_price_per_request && model.default_price_per_request > 0"
|
||||
class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20"
|
||||
>
|
||||
<Label class="text-xs text-muted-foreground whitespace-nowrap">按次计费</Label>
|
||||
<span class="text-sm font-mono">${{ model.default_price_per_request.toFixed(3) }}/次</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 多阶梯计费展示 -->
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-else
|
||||
class="space-y-3"
|
||||
>
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Layers class="w-4 h-4" />
|
||||
<span>阶梯计费 ({{ getTierCount(model.default_tiered_pricing) }} 档)</span>
|
||||
@@ -238,12 +322,24 @@
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow class="bg-muted/30">
|
||||
<TableHead class="text-xs h-9">阶梯</TableHead>
|
||||
<TableHead class="text-xs h-9 text-right">输入 ($/M)</TableHead>
|
||||
<TableHead class="text-xs h-9 text-right">输出 ($/M)</TableHead>
|
||||
<TableHead class="text-xs h-9 text-right">缓存创建</TableHead>
|
||||
<TableHead class="text-xs h-9 text-right">缓存读取</TableHead>
|
||||
<TableHead class="text-xs h-9 text-right">1h 缓存</TableHead>
|
||||
<TableHead class="text-xs h-9">
|
||||
阶梯
|
||||
</TableHead>
|
||||
<TableHead class="text-xs h-9 text-right">
|
||||
输入 ($/M)
|
||||
</TableHead>
|
||||
<TableHead class="text-xs h-9 text-right">
|
||||
输出 ($/M)
|
||||
</TableHead>
|
||||
<TableHead class="text-xs h-9 text-right">
|
||||
缓存创建
|
||||
</TableHead>
|
||||
<TableHead class="text-xs h-9 text-right">
|
||||
缓存读取
|
||||
</TableHead>
|
||||
<TableHead class="text-xs h-9 text-right">
|
||||
1h 缓存
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -253,7 +349,10 @@
|
||||
class="text-xs"
|
||||
>
|
||||
<TableCell class="py-2">
|
||||
<span v-if="tier.up_to === null" class="text-muted-foreground">
|
||||
<span
|
||||
v-if="tier.up_to === null"
|
||||
class="text-muted-foreground"
|
||||
>
|
||||
{{ index === 0 ? '所有' : `> ${formatTierLimit((model.default_tiered_pricing?.tiers || [])[index - 1]?.up_to)}` }}
|
||||
</span>
|
||||
<span v-else>
|
||||
@@ -281,7 +380,10 @@
|
||||
</div>
|
||||
|
||||
<!-- 按次计费(多阶梯时也显示) -->
|
||||
<div v-if="model.default_price_per_request && model.default_price_per_request > 0" class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20">
|
||||
<div
|
||||
v-if="model.default_price_per_request && model.default_price_per_request > 0"
|
||||
class="flex items-center gap-3 p-3 rounded-lg border bg-muted/20"
|
||||
>
|
||||
<Label class="text-xs text-muted-foreground whitespace-nowrap">按次计费</Label>
|
||||
<span class="text-sm font-mono">${{ model.default_price_per_request.toFixed(3) }}/次</span>
|
||||
</div>
|
||||
@@ -290,42 +392,50 @@
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="space-y-3">
|
||||
<h4 class="font-semibold text-sm">统计信息</h4>
|
||||
<h4 class="font-semibold text-sm">
|
||||
统计信息
|
||||
</h4>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="p-3 rounded-lg border bg-muted/20">
|
||||
<div class="flex items-center justify-between">
|
||||
<Label class="text-xs text-muted-foreground">关联提供商</Label>
|
||||
<Building2 class="w-4 h-4 text-muted-foreground" />
|
||||
</div>
|
||||
<p class="text-2xl font-bold mt-1">{{ model.provider_count || 0 }}</p>
|
||||
<p class="text-2xl font-bold mt-1">
|
||||
{{ model.provider_count || 0 }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-3 rounded-lg border bg-muted/20">
|
||||
<div class="flex items-center justify-between">
|
||||
<Label class="text-xs text-muted-foreground">别名数量</Label>
|
||||
<Tag class="w-4 h-4 text-muted-foreground" />
|
||||
</div>
|
||||
<p class="text-2xl font-bold mt-1">{{ model.alias_count || 0 }}</p>
|
||||
<p class="text-2xl font-bold mt-1">
|
||||
{{ model.alias_count || 0 }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 2: 关联提供商 -->
|
||||
<div v-show="detailTab === 'providers'">
|
||||
<!-- Tab 2: 关联提供商 -->
|
||||
<div v-show="detailTab === 'providers'">
|
||||
<Card class="overflow-hidden">
|
||||
<!-- 标题栏 -->
|
||||
<div class="px-4 py-3 border-b border-border/60">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold">关联提供商列表</h4>
|
||||
<h4 class="text-sm font-semibold">
|
||||
关联提供商列表
|
||||
</h4>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
@click="$emit('add-provider')"
|
||||
title="添加关联"
|
||||
@click="$emit('add-provider')"
|
||||
>
|
||||
<Plus class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
@@ -333,27 +443,41 @@
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
@click="$emit('refresh-providers')"
|
||||
title="刷新"
|
||||
@click="$emit('refresh-providers')"
|
||||
>
|
||||
<RefreshCw class="w-3.5 h-3.5" :class="loadingProviders ? 'animate-spin' : ''" />
|
||||
<RefreshCw
|
||||
class="w-3.5 h-3.5"
|
||||
:class="loadingProviders ? 'animate-spin' : ''"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格内容 -->
|
||||
<div v-if="loadingProviders" class="flex items-center justify-center py-12">
|
||||
<div
|
||||
v-if="loadingProviders"
|
||||
class="flex items-center justify-center py-12"
|
||||
>
|
||||
<Loader2 class="w-6 h-6 animate-spin text-primary" />
|
||||
</div>
|
||||
|
||||
<Table v-else-if="providers.length > 0">
|
||||
<TableHeader>
|
||||
<TableRow class="border-b border-border/60 hover:bg-transparent">
|
||||
<TableHead class="h-10 font-semibold">Provider</TableHead>
|
||||
<TableHead class="w-[120px] h-10 font-semibold">能力</TableHead>
|
||||
<TableHead class="w-[180px] h-10 font-semibold">价格 ($/M)</TableHead>
|
||||
<TableHead class="w-[80px] h-10 font-semibold text-center">操作</TableHead>
|
||||
<TableHead class="h-10 font-semibold">
|
||||
Provider
|
||||
</TableHead>
|
||||
<TableHead class="w-[120px] h-10 font-semibold">
|
||||
能力
|
||||
</TableHead>
|
||||
<TableHead class="w-[180px] h-10 font-semibold">
|
||||
价格 ($/M)
|
||||
</TableHead>
|
||||
<TableHead class="w-[80px] h-10 font-semibold text-center">
|
||||
操作
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -368,15 +492,27 @@
|
||||
class="w-2 h-2 rounded-full shrink-0"
|
||||
:class="provider.is_active ? 'bg-green-500' : 'bg-gray-300'"
|
||||
:title="provider.is_active ? '活跃' : '停用'"
|
||||
></span>
|
||||
/>
|
||||
<span class="font-medium truncate">{{ provider.display_name }}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="py-3">
|
||||
<div class="flex gap-0.5">
|
||||
<Zap v-if="provider.supports_streaming" class="w-3.5 h-3.5 text-muted-foreground" title="流式输出" />
|
||||
<Eye v-if="provider.supports_vision" class="w-3.5 h-3.5 text-muted-foreground" title="视觉理解" />
|
||||
<Wrench v-if="provider.supports_function_calling" class="w-3.5 h-3.5 text-muted-foreground" title="工具调用" />
|
||||
<Zap
|
||||
v-if="provider.supports_streaming"
|
||||
class="w-3.5 h-3.5 text-muted-foreground"
|
||||
title="流式输出"
|
||||
/>
|
||||
<Eye
|
||||
v-if="provider.supports_vision"
|
||||
class="w-3.5 h-3.5 text-muted-foreground"
|
||||
title="视觉理解"
|
||||
/>
|
||||
<Wrench
|
||||
v-if="provider.supports_function_calling"
|
||||
class="w-3.5 h-3.5 text-muted-foreground"
|
||||
title="工具调用"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="py-3">
|
||||
@@ -386,15 +522,25 @@
|
||||
<span class="text-muted-foreground">输入/输出:</span>
|
||||
<span class="ml-1">${{ (provider.input_price_per_1m || 0).toFixed(1) }}/${{ (provider.output_price_per_1m || 0).toFixed(1) }}</span>
|
||||
<!-- 阶梯标记 -->
|
||||
<span v-if="(provider.tier_count || 1) > 1" class="ml-1 text-muted-foreground" title="阶梯计费">[阶梯]</span>
|
||||
<span
|
||||
v-if="(provider.tier_count || 1) > 1"
|
||||
class="ml-1 text-muted-foreground"
|
||||
title="阶梯计费"
|
||||
>[阶梯]</span>
|
||||
</div>
|
||||
<!-- 缓存价格 -->
|
||||
<div v-if="(provider.cache_creation_price_per_1m || 0) > 0 || (provider.cache_read_price_per_1m || 0) > 0" class="text-muted-foreground">
|
||||
<div
|
||||
v-if="(provider.cache_creation_price_per_1m || 0) > 0 || (provider.cache_read_price_per_1m || 0) > 0"
|
||||
class="text-muted-foreground"
|
||||
>
|
||||
<span>缓存:</span>
|
||||
<span class="ml-1">${{ (provider.cache_creation_price_per_1m || 0).toFixed(2) }}/${{ (provider.cache_read_price_per_1m || 0).toFixed(2) }}</span>
|
||||
</div>
|
||||
<!-- 1h 缓存价格 -->
|
||||
<div v-if="(provider.cache_1h_creation_price_per_1m || 0) > 0" class="text-muted-foreground">
|
||||
<div
|
||||
v-if="(provider.cache_1h_creation_price_per_1m || 0) > 0"
|
||||
class="text-muted-foreground"
|
||||
>
|
||||
<span>1h 缓存:</span>
|
||||
<span class="ml-1">${{ (provider.cache_1h_creation_price_per_1m || 0).toFixed(2) }}</span>
|
||||
</div>
|
||||
@@ -404,7 +550,10 @@
|
||||
<span class="ml-1">${{ (provider.price_per_request || 0).toFixed(3) }}/次</span>
|
||||
</div>
|
||||
<!-- 无定价 -->
|
||||
<span v-if="!(provider.input_price_per_1m || 0) && !(provider.output_price_per_1m || 0) && !(provider.price_per_request || 0)" class="text-muted-foreground">-</span>
|
||||
<span
|
||||
v-if="!(provider.input_price_per_1m || 0) && !(provider.output_price_per_1m || 0) && !(provider.price_per_request || 0)"
|
||||
class="text-muted-foreground"
|
||||
>-</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="py-3 text-center">
|
||||
@@ -413,8 +562,8 @@
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
@click="$emit('edit-provider', provider)"
|
||||
title="编辑此关联"
|
||||
@click="$emit('edit-provider', provider)"
|
||||
>
|
||||
<Edit class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
@@ -422,8 +571,8 @@
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
@click="$emit('toggle-provider-status', provider)"
|
||||
:title="provider.is_active ? '停用此关联' : '启用此关联'"
|
||||
@click="$emit('toggle-provider-status', provider)"
|
||||
>
|
||||
<Power class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
@@ -431,8 +580,8 @@
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
@click="$emit('delete-provider', provider)"
|
||||
title="删除此关联"
|
||||
@click="$emit('delete-provider', provider)"
|
||||
>
|
||||
<Trash2 class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
@@ -443,33 +592,45 @@
|
||||
</Table>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="text-center py-12">
|
||||
<div
|
||||
v-else
|
||||
class="text-center py-12"
|
||||
>
|
||||
<Building2 class="w-12 h-12 mx-auto text-muted-foreground/30 mb-3" />
|
||||
<p class="text-sm text-muted-foreground">暂无关联提供商</p>
|
||||
<Button size="sm" variant="outline" class="mt-4" @click="$emit('add-provider')">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
暂无关联提供商
|
||||
</p>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
class="mt-4"
|
||||
@click="$emit('add-provider')"
|
||||
>
|
||||
<Plus class="w-4 h-4 mr-1" />
|
||||
添加第一个关联
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 3: 别名 -->
|
||||
<div v-show="detailTab === 'aliases'">
|
||||
<!-- Tab 3: 别名 -->
|
||||
<div v-show="detailTab === 'aliases'">
|
||||
<Card class="overflow-hidden">
|
||||
<!-- 标题栏 -->
|
||||
<div class="px-4 py-3 border-b border-border/60">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h4 class="text-sm font-semibold">别名与映射</h4>
|
||||
<h4 class="text-sm font-semibold">
|
||||
别名与映射
|
||||
</h4>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
@click="$emit('add-alias')"
|
||||
title="添加别名/映射"
|
||||
@click="$emit('add-alias')"
|
||||
>
|
||||
<Plus class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
@@ -477,27 +638,41 @@
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
@click="$emit('refresh-aliases')"
|
||||
title="刷新"
|
||||
@click="$emit('refresh-aliases')"
|
||||
>
|
||||
<RefreshCw class="w-3.5 h-3.5" :class="loadingAliases ? 'animate-spin' : ''" />
|
||||
<RefreshCw
|
||||
class="w-3.5 h-3.5"
|
||||
:class="loadingAliases ? 'animate-spin' : ''"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格内容 -->
|
||||
<div v-if="loadingAliases" class="flex items-center justify-center py-12">
|
||||
<div
|
||||
v-if="loadingAliases"
|
||||
class="flex items-center justify-center py-12"
|
||||
>
|
||||
<Loader2 class="w-6 h-6 animate-spin text-primary" />
|
||||
</div>
|
||||
|
||||
<Table v-else-if="aliases.length > 0">
|
||||
<TableHeader>
|
||||
<TableRow class="border-b border-border/60 hover:bg-transparent">
|
||||
<TableHead class="h-10 font-semibold">别名</TableHead>
|
||||
<TableHead class="w-[80px] h-10 font-semibold">类型</TableHead>
|
||||
<TableHead class="w-[100px] h-10 font-semibold">作用域</TableHead>
|
||||
<TableHead class="w-[100px] h-10 font-semibold text-center">操作</TableHead>
|
||||
<TableHead class="h-10 font-semibold">
|
||||
别名
|
||||
</TableHead>
|
||||
<TableHead class="w-[80px] h-10 font-semibold">
|
||||
类型
|
||||
</TableHead>
|
||||
<TableHead class="w-[100px] h-10 font-semibold">
|
||||
作用域
|
||||
</TableHead>
|
||||
<TableHead class="w-[100px] h-10 font-semibold text-center">
|
||||
操作
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -512,12 +687,15 @@
|
||||
class="w-2 h-2 rounded-full shrink-0"
|
||||
:class="alias.is_active ? 'bg-green-500' : 'bg-gray-300'"
|
||||
:title="alias.is_active ? '活跃' : '停用'"
|
||||
></span>
|
||||
/>
|
||||
<code class="text-sm font-medium bg-muted px-1.5 py-0.5 rounded">{{ alias.alias }}</code>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="py-3">
|
||||
<Badge variant="secondary" class="text-xs">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ alias.mapping_type === 'mapping' ? '映射' : '别名' }}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
@@ -530,7 +708,13 @@
|
||||
>
|
||||
{{ alias.provider_name || 'Provider' }}
|
||||
</Badge>
|
||||
<Badge v-else variant="default" class="text-xs">全局</Badge>
|
||||
<Badge
|
||||
v-else
|
||||
variant="default"
|
||||
class="text-xs"
|
||||
>
|
||||
全局
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell class="py-3 text-center">
|
||||
<div class="flex items-center justify-center gap-0.5">
|
||||
@@ -538,8 +722,8 @@
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
@click="$emit('edit-alias', alias)"
|
||||
title="编辑"
|
||||
@click="$emit('edit-alias', alias)"
|
||||
>
|
||||
<Edit class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
@@ -547,8 +731,8 @@
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
@click="$emit('toggle-alias-status', alias)"
|
||||
:title="alias.is_active ? '停用' : '启用'"
|
||||
@click="$emit('toggle-alias-status', alias)"
|
||||
>
|
||||
<Power class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
@@ -556,8 +740,8 @@
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
@click="$emit('delete-alias', alias)"
|
||||
title="删除"
|
||||
@click="$emit('delete-alias', alias)"
|
||||
>
|
||||
<Trash2 class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
@@ -568,17 +752,27 @@
|
||||
</Table>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else class="text-center py-12">
|
||||
<div
|
||||
v-else
|
||||
class="text-center py-12"
|
||||
>
|
||||
<Tag class="w-12 h-12 mx-auto text-muted-foreground/30 mb-3" />
|
||||
<p class="text-sm text-muted-foreground">暂无别名或映射</p>
|
||||
<Button size="sm" variant="outline" class="mt-4" @click="$emit('add-alias')">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
暂无别名或映射
|
||||
</p>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
class="mt-4"
|
||||
@click="$emit('add-alias')"
|
||||
>
|
||||
<Plus class="w-4 h-4 mr-1" />
|
||||
添加别名/映射
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -607,6 +801,26 @@ import {
|
||||
} from 'lucide-vue-next'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
loadingProviders: false,
|
||||
loadingAliases: false,
|
||||
hasBlockingDialogOpen: false
|
||||
})
|
||||
const emit = defineEmits<{
|
||||
'update:open': [value: boolean]
|
||||
'edit-model': [model: GlobalModelResponse]
|
||||
'toggle-model-status': [model: GlobalModelResponse]
|
||||
'add-provider': []
|
||||
'edit-provider': [provider: any]
|
||||
'delete-provider': [provider: any]
|
||||
'toggle-provider-status': [provider: any]
|
||||
'refresh-providers': []
|
||||
'add-alias': []
|
||||
'edit-alias': [alias: ModelAlias]
|
||||
'toggle-alias-status': [alias: ModelAlias]
|
||||
'delete-alias': [alias: ModelAlias]
|
||||
'refresh-aliases': []
|
||||
}>()
|
||||
const { success: showSuccess, error: showError } = useToast()
|
||||
import Card from '@/components/ui/card.vue'
|
||||
import Badge from '@/components/ui/badge.vue'
|
||||
@@ -636,34 +850,12 @@ interface Props {
|
||||
capabilities?: CapabilityDefinition[]
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
loadingProviders: false,
|
||||
loadingAliases: false,
|
||||
hasBlockingDialogOpen: false
|
||||
})
|
||||
|
||||
// 根据能力名称获取显示名称
|
||||
function getCapabilityDisplayName(capName: string): string {
|
||||
const cap = props.capabilities?.find(c => c.name === capName)
|
||||
return cap?.display_name || capName
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:open': [value: boolean]
|
||||
'edit-model': [model: GlobalModelResponse]
|
||||
'toggle-model-status': [model: GlobalModelResponse]
|
||||
'add-provider': []
|
||||
'edit-provider': [provider: any]
|
||||
'delete-provider': [provider: any]
|
||||
'toggle-provider-status': [provider: any]
|
||||
'refresh-providers': []
|
||||
'add-alias': []
|
||||
'edit-alias': [alias: ModelAlias]
|
||||
'toggle-alias-status': [alias: ModelAlias]
|
||||
'delete-alias': [alias: ModelAlias]
|
||||
'refresh-aliases': []
|
||||
}>()
|
||||
|
||||
const detailTab = ref('basic')
|
||||
|
||||
// 处理背景点击
|
||||
|
||||
Reference in New Issue
Block a user