mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-07 10:12:27 +08:00
refactor(frontend): 优化布局和视图页面
- 更新 MainLayout 布局组件 - 优化 admin 视图: 用户、模型、Provider、API Keys 等管理页面 - 改进 shared 视图: Dashboard、Usage 页面 - 调整 user 视图: ModelCatalog、MyApiKeys、Settings、Announcements 页面 - 更新 public 视图: Home、CliSection、LogoColorDemo 页面
This commit is contained in:
@@ -1,18 +1,27 @@
|
||||
<template>
|
||||
<div class="space-y-6 pb-8">
|
||||
<!-- 公告列表卡片 -->
|
||||
<Card variant="default" class="overflow-hidden">
|
||||
<Card
|
||||
variant="default"
|
||||
class="overflow-hidden"
|
||||
>
|
||||
<!-- 标题和操作栏 -->
|
||||
<div class="px-6 py-3.5 border-b border-border/60">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold">公告管理</h3>
|
||||
<h3 class="text-base font-semibold">
|
||||
公告管理
|
||||
</h3>
|
||||
<p class="text-xs text-muted-foreground mt-0.5">
|
||||
{{ isAdmin ? '管理系统公告和通知' : '查看系统公告和通知' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge v-if="unreadCount > 0" variant="default" class="px-3 py-1">
|
||||
<Badge
|
||||
v-if="unreadCount > 0"
|
||||
variant="default"
|
||||
class="px-3 py-1"
|
||||
>
|
||||
{{ unreadCount }} 条未读
|
||||
</Badge>
|
||||
<div class="h-4 w-px bg-border" />
|
||||
@@ -21,39 +30,80 @@
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
@click="openCreateDialog"
|
||||
title="新建公告"
|
||||
@click="openCreateDialog"
|
||||
>
|
||||
<Plus class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
<RefreshButton :loading="loading" @click="loadAnnouncements(currentPage)" />
|
||||
<RefreshButton
|
||||
:loading="loading"
|
||||
@click="loadAnnouncements(currentPage)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<div
|
||||
v-if="loading"
|
||||
class="flex items-center justify-center py-12"
|
||||
>
|
||||
<Loader2 class="w-8 h-8 animate-spin text-primary" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="announcements.length === 0" class="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div
|
||||
v-else-if="announcements.length === 0"
|
||||
class="flex flex-col items-center justify-center py-12 text-center"
|
||||
>
|
||||
<Bell class="h-12 w-12 text-muted-foreground mb-3" />
|
||||
<h3 class="text-sm font-medium text-foreground">暂无公告</h3>
|
||||
<p class="text-xs text-muted-foreground mt-1">系统暂时没有发布任何公告</p>
|
||||
<h3 class="text-sm font-medium text-foreground">
|
||||
暂无公告
|
||||
</h3>
|
||||
<p class="text-xs text-muted-foreground mt-1">
|
||||
系统暂时没有发布任何公告
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="overflow-x-auto">
|
||||
<div
|
||||
v-else
|
||||
class="overflow-x-auto"
|
||||
>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow class="border-b border-border/60 hover:bg-transparent">
|
||||
<TableHead class="w-[80px] h-12 font-semibold text-center">类型</TableHead>
|
||||
<TableHead class="h-12 font-semibold">概要</TableHead>
|
||||
<TableHead class="w-[120px] h-12 font-semibold">发布者</TableHead>
|
||||
<TableHead class="w-[140px] h-12 font-semibold">发布时间</TableHead>
|
||||
<TableHead class="w-[80px] h-12 font-semibold text-center">状态</TableHead>
|
||||
<TableHead v-if="isAdmin" class="w-[80px] h-12 font-semibold text-center">置顶</TableHead>
|
||||
<TableHead v-if="isAdmin" class="w-[80px] h-12 font-semibold text-center">启用</TableHead>
|
||||
<TableHead v-if="isAdmin" class="w-[100px] h-12 font-semibold text-center">操作</TableHead>
|
||||
<TableHead class="w-[80px] h-12 font-semibold text-center">
|
||||
类型
|
||||
</TableHead>
|
||||
<TableHead class="h-12 font-semibold">
|
||||
概要
|
||||
</TableHead>
|
||||
<TableHead class="w-[120px] h-12 font-semibold">
|
||||
发布者
|
||||
</TableHead>
|
||||
<TableHead class="w-[140px] h-12 font-semibold">
|
||||
发布时间
|
||||
</TableHead>
|
||||
<TableHead class="w-[80px] h-12 font-semibold text-center">
|
||||
状态
|
||||
</TableHead>
|
||||
<TableHead
|
||||
v-if="isAdmin"
|
||||
class="w-[80px] h-12 font-semibold text-center"
|
||||
>
|
||||
置顶
|
||||
</TableHead>
|
||||
<TableHead
|
||||
v-if="isAdmin"
|
||||
class="w-[80px] h-12 font-semibold text-center"
|
||||
>
|
||||
启用
|
||||
</TableHead>
|
||||
<TableHead
|
||||
v-if="isAdmin"
|
||||
class="w-[100px] h-12 font-semibold text-center"
|
||||
>
|
||||
操作
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -65,8 +115,15 @@
|
||||
>
|
||||
<TableCell class="py-4 text-center">
|
||||
<div class="flex flex-col items-center gap-1">
|
||||
<component :is="getAnnouncementIcon(announcement.type)" class="w-5 h-5" :class="getIconColor(announcement.type)" />
|
||||
<span :class="['text-xs font-medium', getTypeTextColor(announcement.type)]">
|
||||
<component
|
||||
:is="getAnnouncementIcon(announcement.type)"
|
||||
class="w-5 h-5"
|
||||
:class="getIconColor(announcement.type)"
|
||||
/>
|
||||
<span
|
||||
class="text-xs font-medium"
|
||||
:class="[getTypeTextColor(announcement.type)]"
|
||||
>
|
||||
{{ getTypeLabel(announcement.type) }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -75,7 +132,10 @@
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-sm font-medium text-foreground">{{ announcement.title }}</span>
|
||||
<Pin v-if="announcement.is_pinned" class="w-3.5 h-3.5 text-muted-foreground flex-shrink-0" />
|
||||
<Pin
|
||||
v-if="announcement.is_pinned"
|
||||
class="w-3.5 h-3.5 text-muted-foreground flex-shrink-0"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground line-clamp-1">
|
||||
{{ getPlainText(announcement.content) }}
|
||||
@@ -89,37 +149,67 @@
|
||||
{{ formatDate(announcement.created_at) }}
|
||||
</TableCell>
|
||||
<TableCell class="py-4 text-center">
|
||||
<Badge v-if="announcement.is_read" variant="secondary" class="text-xs px-2.5 py-0.5">
|
||||
<Badge
|
||||
v-if="announcement.is_read"
|
||||
variant="secondary"
|
||||
class="text-xs px-2.5 py-0.5"
|
||||
>
|
||||
已读
|
||||
</Badge>
|
||||
<Badge v-else variant="default" class="text-xs px-2.5 py-0.5">
|
||||
<Badge
|
||||
v-else
|
||||
variant="default"
|
||||
class="text-xs px-2.5 py-0.5"
|
||||
>
|
||||
未读
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell v-if="isAdmin" class="py-4" @click.stop>
|
||||
<TableCell
|
||||
v-if="isAdmin"
|
||||
class="py-4"
|
||||
@click.stop
|
||||
>
|
||||
<div class="flex items-center justify-center">
|
||||
<Switch
|
||||
:model-value="announcement.is_pinned"
|
||||
@update:model-value="toggleAnnouncementPin(announcement, $event)"
|
||||
class="data-[state=checked]:bg-emerald-500"
|
||||
@update:model-value="toggleAnnouncementPin(announcement, $event)"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell v-if="isAdmin" class="py-4" @click.stop>
|
||||
<TableCell
|
||||
v-if="isAdmin"
|
||||
class="py-4"
|
||||
@click.stop
|
||||
>
|
||||
<div class="flex items-center justify-center">
|
||||
<Switch
|
||||
:model-value="announcement.is_active"
|
||||
@update:model-value="toggleAnnouncementActive(announcement, $event)"
|
||||
class="data-[state=checked]:bg-primary"
|
||||
@update:model-value="toggleAnnouncementActive(announcement, $event)"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell v-if="isAdmin" class="py-4" @click.stop>
|
||||
<TableCell
|
||||
v-if="isAdmin"
|
||||
class="py-4"
|
||||
@click.stop
|
||||
>
|
||||
<div class="flex items-center justify-center gap-1">
|
||||
<Button @click="openEditDialog(announcement)" variant="ghost" size="icon" class="h-8 w-8">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
@click="openEditDialog(announcement)"
|
||||
>
|
||||
<SquarePen class="w-4 h-4" />
|
||||
</Button>
|
||||
<Button @click="confirmDelete(announcement)" variant="ghost" size="icon" class="h-9 w-9 hover:bg-rose-500/10 hover:text-rose-600">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-9 w-9 hover:bg-rose-500/10 hover:text-rose-600"
|
||||
@click="confirmDelete(announcement)"
|
||||
>
|
||||
<Trash2 class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -141,7 +231,10 @@
|
||||
</Card>
|
||||
|
||||
<!-- 创建/编辑公告对话框 -->
|
||||
<Dialog v-model="dialogOpen" size="xl">
|
||||
<Dialog
|
||||
v-model="dialogOpen"
|
||||
size="xl"
|
||||
>
|
||||
<template #header>
|
||||
<div class="border-b border-border px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -149,43 +242,96 @@
|
||||
<Bell class="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-semibold text-foreground leading-tight">{{ editingAnnouncement ? '编辑公告' : '新建公告' }}</h3>
|
||||
<p class="text-xs text-muted-foreground">{{ editingAnnouncement ? '修改公告内容和设置' : '发布新的系统公告' }}</p>
|
||||
<h3 class="text-lg font-semibold text-foreground leading-tight">
|
||||
{{ editingAnnouncement ? '编辑公告' : '新建公告' }}
|
||||
</h3>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
{{ editingAnnouncement ? '修改公告内容和设置' : '发布新的系统公告' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<form @submit.prevent="saveAnnouncement" class="space-y-4">
|
||||
<form
|
||||
class="space-y-4"
|
||||
@submit.prevent="saveAnnouncement"
|
||||
>
|
||||
<div class="space-y-2">
|
||||
<Label for="title" class="text-sm font-medium">标题 *</Label>
|
||||
<Input id="title" v-model="formData.title" placeholder="输入公告标题" class="h-11" required />
|
||||
<Label
|
||||
for="title"
|
||||
class="text-sm font-medium"
|
||||
>标题 *</Label>
|
||||
<Input
|
||||
id="title"
|
||||
v-model="formData.title"
|
||||
placeholder="输入公告标题"
|
||||
class="h-11"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="content" class="text-sm font-medium">内容 * (支持 Markdown)</Label>
|
||||
<Textarea id="content" v-model="formData.content" placeholder="输入公告内容,支持 Markdown 格式" rows="10" required />
|
||||
<Label
|
||||
for="content"
|
||||
class="text-sm font-medium"
|
||||
>内容 * (支持 Markdown)</Label>
|
||||
<Textarea
|
||||
id="content"
|
||||
v-model="formData.content"
|
||||
placeholder="输入公告内容,支持 Markdown 格式"
|
||||
rows="10"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="type" class="text-sm font-medium">类型</Label>
|
||||
<Select v-model="formData.type" v-model:open="typeSelectOpen">
|
||||
<SelectTrigger id="type" class="h-11">
|
||||
<Label
|
||||
for="type"
|
||||
class="text-sm font-medium"
|
||||
>类型</Label>
|
||||
<Select
|
||||
v-model="formData.type"
|
||||
v-model:open="typeSelectOpen"
|
||||
>
|
||||
<SelectTrigger
|
||||
id="type"
|
||||
class="h-11"
|
||||
>
|
||||
<SelectValue placeholder="选择类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="info">信息</SelectItem>
|
||||
<SelectItem value="warning">警告</SelectItem>
|
||||
<SelectItem value="maintenance">维护</SelectItem>
|
||||
<SelectItem value="important">重要</SelectItem>
|
||||
<SelectItem value="info">
|
||||
信息
|
||||
</SelectItem>
|
||||
<SelectItem value="warning">
|
||||
警告
|
||||
</SelectItem>
|
||||
<SelectItem value="maintenance">
|
||||
维护
|
||||
</SelectItem>
|
||||
<SelectItem value="important">
|
||||
重要
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="priority" class="text-sm font-medium">优先级</Label>
|
||||
<Input id="priority" v-model.number="formData.priority" type="number" placeholder="0" class="h-11" min="0" max="10" />
|
||||
<Label
|
||||
for="priority"
|
||||
class="text-sm font-medium"
|
||||
>优先级</Label>
|
||||
<Input
|
||||
id="priority"
|
||||
v-model.number="formData.priority"
|
||||
type="number"
|
||||
placeholder="0"
|
||||
class="h-11"
|
||||
min="0"
|
||||
max="10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -196,27 +342,50 @@
|
||||
v-model="formData.is_pinned"
|
||||
type="checkbox"
|
||||
class="h-4 w-4 rounded border-gray-300 cursor-pointer"
|
||||
/>
|
||||
<Label for="pinned" class="cursor-pointer text-sm">置顶公告</Label>
|
||||
>
|
||||
<Label
|
||||
for="pinned"
|
||||
class="cursor-pointer text-sm"
|
||||
>置顶公告</Label>
|
||||
</div>
|
||||
<div v-if="editingAnnouncement" class="flex items-center gap-2">
|
||||
<div
|
||||
v-if="editingAnnouncement"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<input
|
||||
id="active"
|
||||
v-model="formData.is_active"
|
||||
type="checkbox"
|
||||
class="h-4 w-4 rounded border-gray-300 cursor-pointer"
|
||||
/>
|
||||
<Label for="active" class="cursor-pointer text-sm">启用</Label>
|
||||
>
|
||||
<Label
|
||||
for="active"
|
||||
class="cursor-pointer text-sm"
|
||||
>启用</Label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<template #footer>
|
||||
<Button @click="saveAnnouncement" :disabled="saving" class="h-10 px-5">
|
||||
<Loader2 v-if="saving" class="animate-spin h-4 w-4 mr-2" />
|
||||
<Button
|
||||
:disabled="saving"
|
||||
class="h-10 px-5"
|
||||
@click="saveAnnouncement"
|
||||
>
|
||||
<Loader2
|
||||
v-if="saving"
|
||||
class="animate-spin h-4 w-4 mr-2"
|
||||
/>
|
||||
{{ editingAnnouncement ? '保存' : '创建' }}
|
||||
</Button>
|
||||
<Button variant="outline" @click="dialogOpen = false" type="button" class="h-10 px-5">取消</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
class="h-10 px-5"
|
||||
@click="dialogOpen = false"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
@@ -233,27 +402,40 @@
|
||||
/>
|
||||
|
||||
<!-- 公告详情对话框 -->
|
||||
<Dialog v-model="detailDialogOpen" size="lg">
|
||||
<Dialog
|
||||
v-model="detailDialogOpen"
|
||||
size="lg"
|
||||
>
|
||||
<template #header>
|
||||
<div class="border-b border-border px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex h-9 w-9 items-center justify-center rounded-lg flex-shrink-0" :class="getDialogIconClass(viewingAnnouncement?.type)">
|
||||
<div
|
||||
class="flex h-9 w-9 items-center justify-center rounded-lg flex-shrink-0"
|
||||
:class="getDialogIconClass(viewingAnnouncement?.type)"
|
||||
>
|
||||
<component
|
||||
v-if="viewingAnnouncement"
|
||||
:is="getAnnouncementIcon(viewingAnnouncement.type)"
|
||||
v-if="viewingAnnouncement"
|
||||
class="h-5 w-5"
|
||||
:class="getIconColor(viewingAnnouncement.type)"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-semibold text-foreground leading-tight truncate">{{ viewingAnnouncement?.title || '公告详情' }}</h3>
|
||||
<p class="text-xs text-muted-foreground">系统公告</p>
|
||||
<h3 class="text-lg font-semibold text-foreground leading-tight truncate">
|
||||
{{ viewingAnnouncement?.title || '公告详情' }}
|
||||
</h3>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
系统公告
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="viewingAnnouncement" class="space-y-4">
|
||||
<div
|
||||
v-if="viewingAnnouncement"
|
||||
class="space-y-4"
|
||||
>
|
||||
<div class="flex items-center gap-3 text-xs text-gray-500 dark:text-muted-foreground">
|
||||
<span>{{ viewingAnnouncement.author.username }}</span>
|
||||
<span>·</span>
|
||||
@@ -261,13 +443,20 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-html="renderMarkdown(viewingAnnouncement.content)"
|
||||
class="prose prose-sm dark:prose-invert max-w-none"
|
||||
></div>
|
||||
v-html="renderMarkdown(viewingAnnouncement.content)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button variant="outline" @click="detailDialogOpen = false" type="button" class="h-10 px-5">关闭</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
class="h-10 px-5"
|
||||
@click="detailDialogOpen = false"
|
||||
>
|
||||
关闭
|
||||
</Button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
<div class="px-6 py-3.5 border-b border-border/60">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<!-- 左侧:标题 -->
|
||||
<h3 class="text-base font-semibold">可用模型</h3>
|
||||
<h3 class="text-base font-semibold">
|
||||
可用模型
|
||||
</h3>
|
||||
|
||||
<!-- 右侧:操作区 -->
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -29,8 +31,8 @@
|
||||
<button
|
||||
class="px-2.5 h-full text-xs transition-colors"
|
||||
:class="capabilityFilters.vision ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'"
|
||||
@click="capabilityFilters.vision = !capabilityFilters.vision"
|
||||
title="Vision"
|
||||
@click="capabilityFilters.vision = !capabilityFilters.vision"
|
||||
>
|
||||
<Eye class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
@@ -38,8 +40,8 @@
|
||||
<button
|
||||
class="px-2.5 h-full text-xs transition-colors"
|
||||
:class="capabilityFilters.toolUse ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'"
|
||||
@click="capabilityFilters.toolUse = !capabilityFilters.toolUse"
|
||||
title="Tool Use"
|
||||
@click="capabilityFilters.toolUse = !capabilityFilters.toolUse"
|
||||
>
|
||||
<Wrench class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
@@ -47,8 +49,8 @@
|
||||
<button
|
||||
class="px-2.5 h-full text-xs transition-colors"
|
||||
:class="capabilityFilters.extendedThinking ? 'bg-primary text-primary-foreground' : 'hover:bg-muted'"
|
||||
@click="capabilityFilters.extendedThinking = !capabilityFilters.extendedThinking"
|
||||
title="Extended Thinking"
|
||||
@click="capabilityFilters.extendedThinking = !capabilityFilters.extendedThinking"
|
||||
>
|
||||
<Brain class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
@@ -57,7 +59,10 @@
|
||||
<div class="h-4 w-px bg-border" />
|
||||
|
||||
<!-- 刷新按钮 -->
|
||||
<RefreshButton :loading="loading" @click="refreshData" />
|
||||
<RefreshButton
|
||||
:loading="loading"
|
||||
@click="refreshData"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,21 +71,37 @@
|
||||
<Table class="table-fixed w-full">
|
||||
<TableHeader>
|
||||
<TableRow class="border-b border-border/60 hover:bg-transparent">
|
||||
<TableHead class="w-[140px] h-12 font-semibold">模型名称</TableHead>
|
||||
<TableHead class="w-[120px] h-12 font-semibold">模型偏好</TableHead>
|
||||
<TableHead class="w-[100px] h-12 font-semibold">能力</TableHead>
|
||||
<TableHead class="w-[140px] h-12 font-semibold text-center">价格 ($/M)</TableHead>
|
||||
<TableHead class="w-[70px] h-12 font-semibold text-center">状态</TableHead>
|
||||
<TableHead class="w-[140px] h-12 font-semibold">
|
||||
模型名称
|
||||
</TableHead>
|
||||
<TableHead class="w-[120px] h-12 font-semibold">
|
||||
模型偏好
|
||||
</TableHead>
|
||||
<TableHead class="w-[100px] h-12 font-semibold">
|
||||
能力
|
||||
</TableHead>
|
||||
<TableHead class="w-[140px] h-12 font-semibold text-center">
|
||||
价格 ($/M)
|
||||
</TableHead>
|
||||
<TableHead class="w-[70px] h-12 font-semibold text-center">
|
||||
状态
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-if="loading">
|
||||
<TableCell colspan="5" class="text-center py-12">
|
||||
<TableCell
|
||||
colspan="5"
|
||||
class="text-center py-12"
|
||||
>
|
||||
<Loader2 class="w-6 h-6 animate-spin mx-auto" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow v-else-if="filteredModels.length === 0">
|
||||
<TableCell colspan="5" class="text-center py-12 text-muted-foreground">
|
||||
<TableCell
|
||||
colspan="5"
|
||||
class="text-center py-12 text-muted-foreground"
|
||||
>
|
||||
没有找到匹配的模型
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -115,8 +136,8 @@
|
||||
<button
|
||||
v-for="cap in getModelSupportedCapabilitiesDetails(model)"
|
||||
:key="cap.name"
|
||||
class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium transition-all"
|
||||
:class="[
|
||||
'inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium transition-all',
|
||||
isCapabilityEnabled(model.name, cap.name)
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-transparent text-muted-foreground border border-dashed border-muted-foreground/50 hover:border-primary/50 hover:text-foreground'
|
||||
@@ -124,19 +145,40 @@
|
||||
:title="cap.description"
|
||||
@click.stop="toggleCapability(model.name, cap.name)"
|
||||
>
|
||||
<Check v-if="isCapabilityEnabled(model.name, cap.name)" class="w-3 h-3" />
|
||||
<Plus v-else class="w-3 h-3" />
|
||||
<Check
|
||||
v-if="isCapabilityEnabled(model.name, cap.name)"
|
||||
class="w-3 h-3"
|
||||
/>
|
||||
<Plus
|
||||
v-else
|
||||
class="w-3 h-3"
|
||||
/>
|
||||
{{ cap.short_name || cap.display_name }}
|
||||
</button>
|
||||
</template>
|
||||
<span v-else class="text-muted-foreground text-xs">-</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-muted-foreground text-xs"
|
||||
>-</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="py-4">
|
||||
<div class="flex gap-1.5">
|
||||
<Eye v-if="model.default_supports_vision" class="w-4 h-4 text-muted-foreground" title="Vision" />
|
||||
<Wrench v-if="model.default_supports_function_calling" class="w-4 h-4 text-muted-foreground" title="Tool Use" />
|
||||
<Brain v-if="model.default_supports_extended_thinking" class="w-4 h-4 text-muted-foreground" title="Extended Thinking" />
|
||||
<Eye
|
||||
v-if="model.default_supports_vision"
|
||||
class="w-4 h-4 text-muted-foreground"
|
||||
title="Vision"
|
||||
/>
|
||||
<Wrench
|
||||
v-if="model.default_supports_function_calling"
|
||||
class="w-4 h-4 text-muted-foreground"
|
||||
title="Tool Use"
|
||||
/>
|
||||
<Brain
|
||||
v-if="model.default_supports_extended_thinking"
|
||||
class="w-4 h-4 text-muted-foreground"
|
||||
title="Extended Thinking"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 text-center">
|
||||
@@ -148,7 +190,11 @@
|
||||
<span class="text-muted-foreground mx-1">/</span>
|
||||
<span class="text-muted-foreground">Out:</span>
|
||||
<span class="font-mono ml-1">{{ getFirstTierPrice(model, 'output')?.toFixed(2) || '-' }}</span>
|
||||
<span v-if="hasTieredPricing(model)" class="ml-1 text-muted-foreground" title="阶梯计费">[阶梯]</span>
|
||||
<span
|
||||
v-if="hasTieredPricing(model)"
|
||||
class="ml-1 text-muted-foreground"
|
||||
title="阶梯计费"
|
||||
>[阶梯]</span>
|
||||
</div>
|
||||
<!-- 按次计费 -->
|
||||
<div v-if="model.default_price_per_request && model.default_price_per_request > 0">
|
||||
@@ -156,7 +202,12 @@
|
||||
<span class="font-mono ml-1">${{ model.default_price_per_request.toFixed(3) }}/次</span>
|
||||
</div>
|
||||
<!-- 无计费配置 -->
|
||||
<div v-if="!getFirstTierPrice(model, 'input') && !getFirstTierPrice(model, 'output') && !model.default_price_per_request" class="text-muted-foreground">-</div>
|
||||
<div
|
||||
v-if="!getFirstTierPrice(model, 'input') && !getFirstTierPrice(model, 'output') && !model.default_price_per_request"
|
||||
class="text-muted-foreground"
|
||||
>
|
||||
-
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 text-center">
|
||||
@@ -177,7 +228,7 @@
|
||||
:total="filteredModels.length"
|
||||
:page-size="pageSize"
|
||||
@update:current="currentPage = $event"
|
||||
@update:pageSize="pageSize = $event"
|
||||
@update:page-size="pageSize = $event"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<template>
|
||||
<div class="space-y-6 pb-8">
|
||||
<!-- API Keys 表格 -->
|
||||
<Card variant="default" class="overflow-hidden">
|
||||
<Card
|
||||
variant="default"
|
||||
class="overflow-hidden"
|
||||
>
|
||||
<!-- 标题和操作栏 -->
|
||||
<div class="px-6 py-3.5 border-b border-border/60">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<h3 class="text-base font-semibold">我的 API Keys</h3>
|
||||
<h3 class="text-base font-semibold">
|
||||
我的 API Keys
|
||||
</h3>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -14,32 +19,45 @@
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
@click="showCreateDialog = true"
|
||||
title="创建新 API Key"
|
||||
@click="showCreateDialog = true"
|
||||
>
|
||||
<Plus class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
|
||||
<!-- 刷新按钮 -->
|
||||
<RefreshButton :loading="loading" @click="loadApiKeys" />
|
||||
<RefreshButton
|
||||
:loading="loading"
|
||||
@click="loadApiKeys"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<div
|
||||
v-if="loading"
|
||||
class="flex items-center justify-center py-12"
|
||||
>
|
||||
<LoadingState message="加载中..." />
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="apiKeys.length === 0" class="flex items-center justify-center py-12">
|
||||
<div
|
||||
v-else-if="apiKeys.length === 0"
|
||||
class="flex items-center justify-center py-12"
|
||||
>
|
||||
<EmptyState
|
||||
title="暂无 API 密钥"
|
||||
description="创建你的第一个 API 密钥开始使用"
|
||||
:icon="Key"
|
||||
>
|
||||
<template #actions>
|
||||
<Button @click="showCreateDialog = true" size="lg" class="shadow-lg shadow-primary/20">
|
||||
<Button
|
||||
size="lg"
|
||||
class="shadow-lg shadow-primary/20"
|
||||
@click="showCreateDialog = true"
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
创建新 API Key
|
||||
</Button>
|
||||
@@ -48,18 +66,37 @@
|
||||
</div>
|
||||
|
||||
<!-- 桌面端表格 -->
|
||||
<div v-else class="hidden md:block overflow-x-auto">
|
||||
<div
|
||||
v-else
|
||||
class="hidden md:block overflow-x-auto"
|
||||
>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow class="border-b border-border/60 hover:bg-transparent">
|
||||
<TableHead class="min-w-[200px] h-12 font-semibold">密钥名称</TableHead>
|
||||
<TableHead class="min-w-[80px] h-12 font-semibold">能力</TableHead>
|
||||
<TableHead class="min-w-[160px] h-12 font-semibold">密钥</TableHead>
|
||||
<TableHead class="min-w-[100px] h-12 font-semibold">费用(USD)</TableHead>
|
||||
<TableHead class="min-w-[100px] h-12 font-semibold">请求次数</TableHead>
|
||||
<TableHead class="min-w-[70px] h-12 font-semibold text-center">状态</TableHead>
|
||||
<TableHead class="min-w-[100px] h-12 font-semibold">最后使用</TableHead>
|
||||
<TableHead class="min-w-[80px] h-12 font-semibold text-center">操作</TableHead>
|
||||
<TableHead class="min-w-[200px] h-12 font-semibold">
|
||||
密钥名称
|
||||
</TableHead>
|
||||
<TableHead class="min-w-[80px] h-12 font-semibold">
|
||||
能力
|
||||
</TableHead>
|
||||
<TableHead class="min-w-[160px] h-12 font-semibold">
|
||||
密钥
|
||||
</TableHead>
|
||||
<TableHead class="min-w-[100px] h-12 font-semibold">
|
||||
费用(USD)
|
||||
</TableHead>
|
||||
<TableHead class="min-w-[100px] h-12 font-semibold">
|
||||
请求次数
|
||||
</TableHead>
|
||||
<TableHead class="min-w-[70px] h-12 font-semibold text-center">
|
||||
状态
|
||||
</TableHead>
|
||||
<TableHead class="min-w-[100px] h-12 font-semibold">
|
||||
最后使用
|
||||
</TableHead>
|
||||
<TableHead class="min-w-[80px] h-12 font-semibold text-center">
|
||||
操作
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -71,7 +108,10 @@
|
||||
<!-- 密钥名称 -->
|
||||
<TableCell class="py-4">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-semibold truncate" :title="apiKey.name">
|
||||
<div
|
||||
class="text-sm font-semibold truncate"
|
||||
:title="apiKey.name"
|
||||
>
|
||||
{{ apiKey.name }}
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground mt-0.5">
|
||||
@@ -87,8 +127,8 @@
|
||||
<button
|
||||
v-for="cap in userConfigurableCapabilities"
|
||||
:key="cap.name"
|
||||
class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium transition-all"
|
||||
:class="[
|
||||
'inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium transition-all',
|
||||
isCapabilityEnabled(apiKey, cap.name)
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'bg-transparent text-muted-foreground border border-dashed border-muted-foreground/50 hover:border-primary/50 hover:text-foreground'
|
||||
@@ -96,12 +136,21 @@
|
||||
:title="getCapabilityTooltip(cap, isCapabilityEnabled(apiKey, cap.name))"
|
||||
@click.stop="toggleCapability(apiKey, cap.name)"
|
||||
>
|
||||
<Check v-if="isCapabilityEnabled(apiKey, cap.name)" class="w-3 h-3" />
|
||||
<Plus v-else class="w-3 h-3" />
|
||||
<Check
|
||||
v-if="isCapabilityEnabled(apiKey, cap.name)"
|
||||
class="w-3 h-3"
|
||||
/>
|
||||
<Plus
|
||||
v-else
|
||||
class="w-3 h-3"
|
||||
/>
|
||||
{{ cap.short_name || cap.display_name }}
|
||||
</button>
|
||||
</template>
|
||||
<span v-else class="text-muted-foreground text-xs">-</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-muted-foreground text-xs"
|
||||
>-</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
@@ -112,11 +161,11 @@
|
||||
{{ apiKey.key_display || 'sk-••••••••' }}
|
||||
</code>
|
||||
<Button
|
||||
@click="copyApiKey(apiKey)"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-6 w-6"
|
||||
title="复制完整密钥"
|
||||
@click="copyApiKey(apiKey)"
|
||||
>
|
||||
<Copy class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
@@ -159,20 +208,20 @@
|
||||
<TableCell class="py-4">
|
||||
<div class="flex justify-center gap-1">
|
||||
<Button
|
||||
@click="toggleApiKey(apiKey)"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
:title="apiKey.is_active ? '禁用' : '启用'"
|
||||
@click="toggleApiKey(apiKey)"
|
||||
>
|
||||
<Power class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
@click="confirmDelete(apiKey)"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
title="删除"
|
||||
@click="confirmDelete(apiKey)"
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -184,7 +233,10 @@
|
||||
</div>
|
||||
|
||||
<!-- 移动端卡片列表 -->
|
||||
<div v-if="!loading && apiKeys.length > 0" class="md:hidden space-y-3 p-4">
|
||||
<div
|
||||
v-if="!loading && apiKeys.length > 0"
|
||||
class="md:hidden space-y-3 p-4"
|
||||
>
|
||||
<Card
|
||||
v-for="apiKey in paginatedApiKeys"
|
||||
:key="apiKey.id"
|
||||
@@ -195,7 +247,9 @@
|
||||
<!-- 第一行:名称、状态、操作 -->
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2 min-w-0 flex-1">
|
||||
<h3 class="text-sm font-semibold text-foreground truncate">{{ apiKey.name }}</h3>
|
||||
<h3 class="text-sm font-semibold text-foreground truncate">
|
||||
{{ apiKey.name }}
|
||||
</h3>
|
||||
<Badge
|
||||
:variant="apiKey.is_active ? 'success' : 'secondary'"
|
||||
class="text-xs px-1.5 py-0"
|
||||
@@ -205,29 +259,29 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-0.5 flex-shrink-0">
|
||||
<Button
|
||||
@click="copyApiKey(apiKey)"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
title="复制"
|
||||
@click="copyApiKey(apiKey)"
|
||||
>
|
||||
<Copy class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
@click="toggleApiKey(apiKey)"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
:title="apiKey.is_active ? '禁用' : '启用'"
|
||||
@click="toggleApiKey(apiKey)"
|
||||
>
|
||||
<Power class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
@click="confirmDelete(apiKey)"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-7 w-7"
|
||||
title="删除"
|
||||
@click="confirmDelete(apiKey)"
|
||||
>
|
||||
<Trash2 class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
@@ -264,7 +318,7 @@
|
||||
:total="apiKeys.length"
|
||||
:page-size="pageSize"
|
||||
@update:current="currentPage = $event"
|
||||
@update:pageSize="pageSize = $event"
|
||||
@update:page-size="pageSize = $event"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
@@ -277,8 +331,12 @@
|
||||
<Key class="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-semibold text-foreground leading-tight">创建 API 密钥</h3>
|
||||
<p class="text-xs text-muted-foreground">创建一个新的密钥用于访问 API 服务</p>
|
||||
<h3 class="text-lg font-semibold text-foreground leading-tight">
|
||||
创建 API 密钥
|
||||
</h3>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
创建一个新的密钥用于访问 API 服务
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -286,7 +344,10 @@
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="key-name" class="text-sm font-semibold">密钥名称</Label>
|
||||
<Label
|
||||
for="key-name"
|
||||
class="text-sm font-semibold"
|
||||
>密钥名称</Label>
|
||||
<Input
|
||||
id="key-name"
|
||||
v-model="newKeyName"
|
||||
@@ -295,21 +356,39 @@
|
||||
autocomplete="off"
|
||||
required
|
||||
/>
|
||||
<p class="text-xs text-muted-foreground">给密钥起一个有意义的名称方便识别</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
给密钥起一个有意义的名称方便识别
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button variant="outline" class="h-11 px-6" @click="showCreateDialog = false">取消</Button>
|
||||
<Button class="h-11 px-6 shadow-lg shadow-primary/20" @click="createApiKey" :disabled="creating">
|
||||
<Loader2 v-if="creating" class="animate-spin h-4 w-4 mr-2" />
|
||||
<Button
|
||||
variant="outline"
|
||||
class="h-11 px-6"
|
||||
@click="showCreateDialog = false"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
class="h-11 px-6 shadow-lg shadow-primary/20"
|
||||
:disabled="creating"
|
||||
@click="createApiKey"
|
||||
>
|
||||
<Loader2
|
||||
v-if="creating"
|
||||
class="animate-spin h-4 w-4 mr-2"
|
||||
/>
|
||||
{{ creating ? '创建中...' : '创建' }}
|
||||
</Button>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- 新密钥创建成功对话框 -->
|
||||
<Dialog v-model="showKeyDialog" size="lg">
|
||||
<Dialog
|
||||
v-model="showKeyDialog"
|
||||
size="lg"
|
||||
>
|
||||
<template #header>
|
||||
<div class="border-b border-border px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -317,8 +396,12 @@
|
||||
<CheckCircle class="h-5 w-5 text-emerald-600 dark:text-emerald-400" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-semibold text-foreground leading-tight">创建成功</h3>
|
||||
<p class="text-xs text-muted-foreground">请妥善保管, 切勿泄露给他人</p>
|
||||
<h3 class="text-lg font-semibold text-foreground leading-tight">
|
||||
创建成功
|
||||
</h3>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
请妥善保管, 切勿泄露给他人
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -335,7 +418,10 @@
|
||||
class="flex-1 font-mono text-sm bg-muted/50 h-11"
|
||||
@click="($event.target as HTMLInputElement)?.select()"
|
||||
/>
|
||||
<Button @click="copyTextToClipboard(newKeyValue)" class="h-11">
|
||||
<Button
|
||||
class="h-11"
|
||||
@click="copyTextToClipboard(newKeyValue)"
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
</div>
|
||||
@@ -343,7 +429,12 @@
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button @click="showKeyDialog = false" class="h-10 px-5">确定</Button>
|
||||
<Button
|
||||
class="h-10 px-5"
|
||||
@click="showKeyDialog = false"
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
@@ -440,7 +531,7 @@ async function loadApiKeys() {
|
||||
} else if (error.response.status === 401) {
|
||||
showError('认证失败,请重新登录')
|
||||
} else {
|
||||
showError('加载 API 密钥失败:' + (error.response?.data?.detail || error.message))
|
||||
showError(`加载 API 密钥失败:${ error.response?.data?.detail || error.message}`)
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
|
||||
@@ -1,22 +1,38 @@
|
||||
<template>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h2 class="text-2xl font-bold text-foreground mb-6">个人设置</h2>
|
||||
<h2 class="text-2xl font-bold text-foreground mb-6">
|
||||
个人设置
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- 左侧:个人信息和密码 -->
|
||||
<div class="lg:col-span-2 space-y-6">
|
||||
<!-- 基本信息 -->
|
||||
<Card class="p-6">
|
||||
<h3 class="text-lg font-medium text-foreground mb-4">基本信息</h3>
|
||||
<form @submit.prevent="updateProfile" class="space-y-4">
|
||||
<h3 class="text-lg font-medium text-foreground mb-4">
|
||||
基本信息
|
||||
</h3>
|
||||
<form
|
||||
class="space-y-4"
|
||||
@submit.prevent="updateProfile"
|
||||
>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label for="username">用户名</Label>
|
||||
<Input id="username" v-model="profileForm.username" class="mt-1" />
|
||||
<Input
|
||||
id="username"
|
||||
v-model="profileForm.username"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label for="email">邮箱</Label>
|
||||
<Input id="email" v-model="profileForm.email" type="email" class="mt-1" />
|
||||
<Input
|
||||
id="email"
|
||||
v-model="profileForm.email"
|
||||
type="email"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,11 +48,21 @@
|
||||
|
||||
<div>
|
||||
<Label for="avatar">头像 URL</Label>
|
||||
<Input id="avatar" v-model="preferencesForm.avatar_url" type="url" class="mt-1" />
|
||||
<p class="mt-1 text-sm text-muted-foreground">输入头像图片的 URL 地址</p>
|
||||
<Input
|
||||
id="avatar"
|
||||
v-model="preferencesForm.avatar_url"
|
||||
type="url"
|
||||
class="mt-1"
|
||||
/>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
输入头像图片的 URL 地址
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button type="submit" :disabled="savingProfile">
|
||||
<Button
|
||||
type="submit"
|
||||
:disabled="savingProfile"
|
||||
>
|
||||
{{ savingProfile ? '保存中...' : '保存修改' }}
|
||||
</Button>
|
||||
</form>
|
||||
@@ -44,21 +70,44 @@
|
||||
|
||||
<!-- 修改密码 -->
|
||||
<Card class="p-6">
|
||||
<h3 class="text-lg font-medium text-foreground mb-4">修改密码</h3>
|
||||
<form @submit.prevent="changePassword" class="space-y-4">
|
||||
<h3 class="text-lg font-medium text-foreground mb-4">
|
||||
修改密码
|
||||
</h3>
|
||||
<form
|
||||
class="space-y-4"
|
||||
@submit.prevent="changePassword"
|
||||
>
|
||||
<div>
|
||||
<Label for="old-password">当前密码</Label>
|
||||
<Input id="old-password" v-model="passwordForm.old_password" type="password" class="mt-1" />
|
||||
<Input
|
||||
id="old-password"
|
||||
v-model="passwordForm.old_password"
|
||||
type="password"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label for="new-password">新密码</Label>
|
||||
<Input id="new-password" v-model="passwordForm.new_password" type="password" class="mt-1" />
|
||||
<Input
|
||||
id="new-password"
|
||||
v-model="passwordForm.new_password"
|
||||
type="password"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label for="confirm-password">确认新密码</Label>
|
||||
<Input id="confirm-password" v-model="passwordForm.confirm_password" type="password" class="mt-1" />
|
||||
<Input
|
||||
id="confirm-password"
|
||||
v-model="passwordForm.confirm_password"
|
||||
type="password"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" :disabled="changingPassword">
|
||||
<Button
|
||||
type="submit"
|
||||
:disabled="changingPassword"
|
||||
>
|
||||
{{ changingPassword ? '修改中...' : '修改密码' }}
|
||||
</Button>
|
||||
</form>
|
||||
@@ -66,7 +115,9 @@
|
||||
|
||||
<!-- 偏好设置 -->
|
||||
<Card class="p-6">
|
||||
<h3 class="text-lg font-medium text-foreground mb-4">偏好设置</h3>
|
||||
<h3 class="text-lg font-medium text-foreground mb-4">
|
||||
偏好设置
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
@@ -76,13 +127,22 @@
|
||||
v-model:open="themeSelectOpen"
|
||||
@update:model-value="handleThemeChange"
|
||||
>
|
||||
<SelectTrigger id="theme" class="mt-1">
|
||||
<SelectTrigger
|
||||
id="theme"
|
||||
class="mt-1"
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="light">浅色</SelectItem>
|
||||
<SelectItem value="dark">深色</SelectItem>
|
||||
<SelectItem value="system">跟随系统</SelectItem>
|
||||
<SelectItem value="light">
|
||||
浅色
|
||||
</SelectItem>
|
||||
<SelectItem value="dark">
|
||||
深色
|
||||
</SelectItem>
|
||||
<SelectItem value="system">
|
||||
跟随系统
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -94,62 +154,91 @@
|
||||
v-model:open="languageSelectOpen"
|
||||
@update:model-value="handleLanguageChange"
|
||||
>
|
||||
<SelectTrigger id="language" class="mt-1">
|
||||
<SelectTrigger
|
||||
id="language"
|
||||
class="mt-1"
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="zh-CN">简体中文</SelectItem>
|
||||
<SelectItem value="en">English</SelectItem>
|
||||
<SelectItem value="zh-CN">
|
||||
简体中文
|
||||
</SelectItem>
|
||||
<SelectItem value="en">
|
||||
English
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label for="timezone">时区</Label>
|
||||
<Input id="timezone" v-model="preferencesForm.timezone" placeholder="Asia/Shanghai" class="mt-1" />
|
||||
<Input
|
||||
id="timezone"
|
||||
v-model="preferencesForm.timezone"
|
||||
placeholder="Asia/Shanghai"
|
||||
class="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<h4 class="font-medium text-foreground">通知设置</h4>
|
||||
<h4 class="font-medium text-foreground">
|
||||
通知设置
|
||||
</h4>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between py-2 border-b border-border/40 last:border-0">
|
||||
<div class="flex-1">
|
||||
<Label for="email-notifications" class="text-sm font-medium cursor-pointer">
|
||||
<Label
|
||||
for="email-notifications"
|
||||
class="text-sm font-medium cursor-pointer"
|
||||
>
|
||||
邮件通知
|
||||
</Label>
|
||||
<p class="text-xs text-muted-foreground mt-1">接收系统重要通知</p>
|
||||
<p class="text-xs text-muted-foreground mt-1">
|
||||
接收系统重要通知
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="email-notifications"
|
||||
v-model="preferencesForm.notifications.email"
|
||||
@update:modelValue="updatePreferences"
|
||||
@update:model-value="updatePreferences"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between py-2 border-b border-border/40 last:border-0">
|
||||
<div class="flex-1">
|
||||
<Label for="usage-alerts" class="text-sm font-medium cursor-pointer">
|
||||
<Label
|
||||
for="usage-alerts"
|
||||
class="text-sm font-medium cursor-pointer"
|
||||
>
|
||||
使用提醒
|
||||
</Label>
|
||||
<p class="text-xs text-muted-foreground mt-1">当接近配额限制时提醒</p>
|
||||
<p class="text-xs text-muted-foreground mt-1">
|
||||
当接近配额限制时提醒
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="usage-alerts"
|
||||
v-model="preferencesForm.notifications.usage_alerts"
|
||||
@update:modelValue="updatePreferences"
|
||||
@update:model-value="updatePreferences"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex-1">
|
||||
<Label for="announcement-notifications" class="text-sm font-medium cursor-pointer">
|
||||
<Label
|
||||
for="announcement-notifications"
|
||||
class="text-sm font-medium cursor-pointer"
|
||||
>
|
||||
公告通知
|
||||
</Label>
|
||||
<p class="text-xs text-muted-foreground mt-1">接收系统公告</p>
|
||||
<p class="text-xs text-muted-foreground mt-1">
|
||||
接收系统公告
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="announcement-notifications"
|
||||
v-model="preferencesForm.notifications.announcements"
|
||||
@update:modelValue="updatePreferences"
|
||||
@update:model-value="updatePreferences"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,7 +251,9 @@
|
||||
<div class="space-y-6">
|
||||
<!-- 账户信息 -->
|
||||
<Card class="p-6">
|
||||
<h3 class="text-lg font-medium text-foreground mb-4">账户信息</h3>
|
||||
<h3 class="text-lg font-medium text-foreground mb-4">
|
||||
账户信息
|
||||
</h3>
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-muted-foreground">角色</span>
|
||||
@@ -193,7 +284,9 @@
|
||||
|
||||
<!-- 使用配额 -->
|
||||
<Card class="p-6">
|
||||
<h3 class="text-lg font-medium text-foreground mb-4">使用配额</h3>
|
||||
<h3 class="text-lg font-medium text-foreground mb-4">
|
||||
使用配额
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<div class="flex justify-between text-sm mb-1">
|
||||
@@ -213,7 +306,7 @@
|
||||
<div
|
||||
class="bg-success h-2.5 rounded-full"
|
||||
:style="`width: ${getUsagePercentage()}%`"
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
@click.self="handleClose"
|
||||
>
|
||||
<!-- 背景遮罩 -->
|
||||
<div class="absolute inset-0 bg-black/30 backdrop-blur-sm" @click="handleClose"></div>
|
||||
<div
|
||||
class="absolute inset-0 bg-black/30 backdrop-blur-sm"
|
||||
@click="handleClose"
|
||||
/>
|
||||
|
||||
<!-- 抽屉内容 -->
|
||||
<Card class="relative h-full w-[700px] rounded-none shadow-2xl overflow-y-auto">
|
||||
@@ -15,9 +18,14 @@
|
||||
<div class="sticky top-0 z-10 bg-background border-b p-6">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="space-y-1 flex-1 min-w-0">
|
||||
<h3 class="text-xl font-bold truncate">{{ model.display_name || model.name }}</h3>
|
||||
<h3 class="text-xl font-bold truncate">
|
||||
{{ model.display_name || model.name }}
|
||||
</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge :variant="model.is_active ? 'default' : 'secondary'" class="text-xs">
|
||||
<Badge
|
||||
:variant="model.is_active ? 'default' : 'secondary'"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ model.is_active ? '可用' : '停用' }}
|
||||
</Badge>
|
||||
<span class="text-sm text-muted-foreground font-mono">{{ model.name }}</span>
|
||||
@@ -29,11 +37,19 @@
|
||||
<Copy class="w-3 h-3 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="model.description" class="text-xs text-muted-foreground">
|
||||
<p
|
||||
v-if="model.description"
|
||||
class="text-xs text-muted-foreground"
|
||||
>
|
||||
{{ model.description }}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" @click="handleClose" title="关闭">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
title="关闭"
|
||||
@click="handleClose"
|
||||
>
|
||||
<X class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -42,55 +58,92 @@
|
||||
<div class="p-6 space-y-6">
|
||||
<!-- 模型能力 -->
|
||||
<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">
|
||||
<ImageIcon 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>
|
||||
@@ -98,8 +151,13 @@
|
||||
</div>
|
||||
|
||||
<!-- 模型偏好 -->
|
||||
<div v-if="getModelUserConfigurableCapabilities().length > 0" class="space-y-3">
|
||||
<h4 class="font-semibold text-sm">模型偏好</h4>
|
||||
<div
|
||||
v-if="getModelUserConfigurableCapabilities().length > 0"
|
||||
class="space-y-3"
|
||||
>
|
||||
<h4 class="font-semibold text-sm">
|
||||
模型偏好
|
||||
</h4>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="cap in getModelUserConfigurableCapabilities()"
|
||||
@@ -107,12 +165,19 @@
|
||||
class="flex items-center justify-between p-3 rounded-lg border"
|
||||
>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium">{{ cap.display_name }}</p>
|
||||
<p v-if="cap.description" class="text-xs text-muted-foreground truncate">{{ cap.description }}</p>
|
||||
<p class="text-sm font-medium">
|
||||
{{ cap.display_name }}
|
||||
</p>
|
||||
<p
|
||||
v-if="cap.description"
|
||||
class="text-xs text-muted-foreground truncate"
|
||||
>
|
||||
{{ cap.description }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
:class="[
|
||||
'relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
isCapabilityEnabled(cap.name) ? 'bg-primary' : 'bg-muted'
|
||||
]"
|
||||
role="switch"
|
||||
@@ -120,8 +185,8 @@
|
||||
@click="handleToggleCapability(cap.name)"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none inline-block h-4 w-4 transform rounded-full bg-background shadow-lg ring-0 transition duration-200 ease-in-out"
|
||||
:class="[
|
||||
'pointer-events-none inline-block h-4 w-4 transform rounded-full bg-background shadow-lg ring-0 transition duration-200 ease-in-out',
|
||||
isCapabilityEnabled(cap.name) ? 'translate-x-4' : 'translate-x-0'
|
||||
]"
|
||||
/>
|
||||
@@ -132,10 +197,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">
|
||||
<div class="p-3 rounded-lg border">
|
||||
<Label class="text-xs text-muted-foreground">输入价格 ($/M)</Label>
|
||||
@@ -163,19 +233,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>
|
||||
@@ -186,12 +265,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>
|
||||
@@ -201,7 +292,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>
|
||||
@@ -229,7 +323,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>
|
||||
@@ -269,6 +366,13 @@ import type { PublicGlobalModel } from '@/api/public-models'
|
||||
import type { TieredPricingConfig, PricingTier } from '@/api/endpoints/types'
|
||||
import type { CapabilityDefinition } from '@/api/endpoints'
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:open': [value: boolean]
|
||||
'toggle-capability': [modelName: string, capName: string]
|
||||
}>()
|
||||
|
||||
const { success: showSuccess, error: showError } = useToast()
|
||||
|
||||
interface Props {
|
||||
@@ -279,13 +383,6 @@ interface Props {
|
||||
modelCapabilitySettings?: Record<string, Record<string, boolean>>
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:open': [value: boolean]
|
||||
'toggle-capability': [modelName: string, capName: string]
|
||||
}>()
|
||||
|
||||
// 根据能力名称获取显示名称
|
||||
function getCapabilityDisplayName(capName: string): string {
|
||||
const cap = props.capabilities?.find(c => c.name === capName)
|
||||
|
||||
Reference in New Issue
Block a user