mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
feat: OAuth 账户管理、维护调度、端点健康检查增强及前端优化
- 新增 OAuth 账户管理对话框和提供商详情抽屉中的 OAuth 信息展示 - 新增维护调度器(maintenance_scheduler)支持定时清理和健康检查 - 增强端点健康检查器,支持更多检测策略 - 重构 codex 服务为 metadata_collectors 模块 - 优化 OpenAI CLI normalizer 代码结构 - 前端: 改进使用量表格、统计图表、指南页面和异步任务管理 - 扩展多个数据库字符串列为 TEXT 类型 - 新增倒计时 composable 和 provider OAuth API 端点
This commit is contained in:
@@ -2,71 +2,122 @@
|
||||
<div class="space-y-6 pb-8">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Card variant="default" class="p-4">
|
||||
<Card
|
||||
variant="default"
|
||||
class="p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
||||
<Zap class="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold">{{ stats?.total ?? '-' }}</p>
|
||||
<p class="text-xs text-muted-foreground">总任务数</p>
|
||||
<p class="text-2xl font-bold">
|
||||
{{ stats?.total ?? '-' }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
总任务数
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card variant="default" class="p-4">
|
||||
<Card
|
||||
variant="default"
|
||||
class="p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center">
|
||||
<Loader2 class="w-5 h-5 text-blue-500" :class="{ 'animate-spin': (stats?.processing_count ?? 0) > 0 }" />
|
||||
<Loader2
|
||||
class="w-5 h-5 text-blue-500"
|
||||
:class="{ 'animate-spin': (stats?.processing_count ?? 0) > 0 }"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold">{{ stats?.processing_count ?? stats?.by_status?.processing ?? '-' }}</p>
|
||||
<p class="text-xs text-muted-foreground">处理中</p>
|
||||
<p class="text-2xl font-bold">
|
||||
{{ stats?.processing_count ?? stats?.by_status?.processing ?? '-' }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
处理中
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card variant="default" class="p-4">
|
||||
<Card
|
||||
variant="default"
|
||||
class="p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-green-500/10 flex items-center justify-center">
|
||||
<CheckCircle class="w-5 h-5 text-green-500" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold">{{ stats?.by_status?.completed ?? '-' }}</p>
|
||||
<p class="text-xs text-muted-foreground">已完成</p>
|
||||
<p class="text-2xl font-bold">
|
||||
{{ stats?.by_status?.completed ?? '-' }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
已完成
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card variant="default" class="p-4">
|
||||
<Card
|
||||
variant="default"
|
||||
class="p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-amber-500/10 flex items-center justify-center">
|
||||
<Calendar class="w-5 h-5 text-amber-500" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold">{{ stats?.today_count ?? '-' }}</p>
|
||||
<p class="text-xs text-muted-foreground">今日任务</p>
|
||||
<p class="text-2xl font-bold">
|
||||
{{ stats?.today_count ?? '-' }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
今日任务
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 任务表格 -->
|
||||
<Card variant="default" class="overflow-hidden">
|
||||
<Card
|
||||
variant="default"
|
||||
class="overflow-hidden"
|
||||
>
|
||||
<!-- 标题和筛选器 -->
|
||||
<div class="px-4 sm:px-6 py-3.5 border-b border-border/60">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<h3 class="text-base font-semibold">异步任务</h3>
|
||||
<h3 class="text-base font-semibold">
|
||||
异步任务
|
||||
</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- 状态筛选 -->
|
||||
<Select v-model:open="statusSelectOpen" v-model="filterStatus">
|
||||
<Select
|
||||
v-model:open="statusSelectOpen"
|
||||
v-model="filterStatus"
|
||||
>
|
||||
<SelectTrigger class="w-28 h-8 text-xs border-border/60">
|
||||
<SelectValue placeholder="状态" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
<SelectItem value="submitted">已提交</SelectItem>
|
||||
<SelectItem value="processing">处理中</SelectItem>
|
||||
<SelectItem value="completed">已完成</SelectItem>
|
||||
<SelectItem value="failed">失败</SelectItem>
|
||||
<SelectItem value="cancelled">已取消</SelectItem>
|
||||
<SelectItem value="all">
|
||||
全部状态
|
||||
</SelectItem>
|
||||
<SelectItem value="submitted">
|
||||
已提交
|
||||
</SelectItem>
|
||||
<SelectItem value="processing">
|
||||
处理中
|
||||
</SelectItem>
|
||||
<SelectItem value="completed">
|
||||
已完成
|
||||
</SelectItem>
|
||||
<SelectItem value="failed">
|
||||
失败
|
||||
</SelectItem>
|
||||
<SelectItem value="cancelled">
|
||||
已取消
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<!-- 模型筛选 -->
|
||||
@@ -84,34 +135,62 @@
|
||||
:disabled="loading"
|
||||
@click="fetchTasks"
|
||||
>
|
||||
<RefreshCw class="w-3.5 h-3.5" :class="{ 'animate-spin': loading }" />
|
||||
<RefreshCw
|
||||
class="w-3.5 h-3.5"
|
||||
:class="{ 'animate-spin': loading }"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading && !tasks.length" class="p-8 text-center">
|
||||
<div
|
||||
v-if="loading && !tasks.length"
|
||||
class="p-8 text-center"
|
||||
>
|
||||
<Loader2 class="w-8 h-8 animate-spin mx-auto text-muted-foreground" />
|
||||
<p class="mt-2 text-sm text-muted-foreground">加载中...</p>
|
||||
<p class="mt-2 text-sm text-muted-foreground">
|
||||
加载中...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="!tasks.length" class="p-8 text-center">
|
||||
<div
|
||||
v-else-if="!tasks.length"
|
||||
class="p-8 text-center"
|
||||
>
|
||||
<Zap class="w-12 h-12 mx-auto text-muted-foreground/50" />
|
||||
<p class="mt-2 text-sm text-muted-foreground">暂无异步任务</p>
|
||||
<p class="mt-2 text-sm text-muted-foreground">
|
||||
暂无异步任务
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 桌面端表格 -->
|
||||
<Table v-else class="hidden md:table">
|
||||
<Table
|
||||
v-else
|
||||
class="hidden md:table"
|
||||
>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-[25%]">任务</TableHead>
|
||||
<TableHead class="w-[15%]">用户/Provider</TableHead>
|
||||
<TableHead class="w-[12%]">状态</TableHead>
|
||||
<TableHead class="w-[10%]">参数</TableHead>
|
||||
<TableHead class="w-[15%]">时间</TableHead>
|
||||
<TableHead class="w-[8%] text-center">操作</TableHead>
|
||||
<TableHead class="w-[25%]">
|
||||
任务
|
||||
</TableHead>
|
||||
<TableHead class="w-[15%]">
|
||||
用户/Provider
|
||||
</TableHead>
|
||||
<TableHead class="w-[12%]">
|
||||
状态
|
||||
</TableHead>
|
||||
<TableHead class="w-[10%]">
|
||||
参数
|
||||
</TableHead>
|
||||
<TableHead class="w-[15%]">
|
||||
时间
|
||||
</TableHead>
|
||||
<TableHead class="w-[8%] text-center">
|
||||
操作
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -125,10 +204,16 @@
|
||||
<TableCell>
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<Video v-if="isVideoTask(task)" class="w-4 h-4 text-muted-foreground shrink-0" />
|
||||
<Video
|
||||
v-if="isVideoTask(task)"
|
||||
class="w-4 h-4 text-muted-foreground shrink-0"
|
||||
/>
|
||||
<span class="font-medium text-sm truncate">{{ task.model }}</span>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground truncate max-w-[280px]" :title="task.prompt">
|
||||
<p
|
||||
class="text-xs text-muted-foreground truncate max-w-[280px]"
|
||||
:title="task.prompt"
|
||||
>
|
||||
{{ task.prompt }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -149,10 +234,16 @@
|
||||
<!-- 状态 -->
|
||||
<TableCell>
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<Badge :variant="getStatusVariant(task.status)" class="text-xs">
|
||||
<Badge
|
||||
:variant="getStatusVariant(task.status)"
|
||||
class="text-xs"
|
||||
>
|
||||
{{ getStatusLabel(task.status) }}
|
||||
</Badge>
|
||||
<div v-if="task.progress_percent > 0 && task.status === 'processing'" class="w-full">
|
||||
<div
|
||||
v-if="task.progress_percent > 0 && task.status === 'processing'"
|
||||
class="w-full"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1 h-1.5 bg-muted rounded-full overflow-hidden">
|
||||
<div
|
||||
@@ -168,12 +259,19 @@
|
||||
<!-- 参数 -->
|
||||
<TableCell>
|
||||
<div class="text-xs space-y-0.5 text-muted-foreground">
|
||||
<div v-if="task.duration_seconds" class="flex items-center gap-1">
|
||||
<div
|
||||
v-if="task.duration_seconds"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<Timer class="w-3 h-3" />
|
||||
<span>{{ task.duration_seconds }}s</span>
|
||||
</div>
|
||||
<div v-if="task.resolution">{{ task.resolution }}</div>
|
||||
<div v-if="task.aspect_ratio">{{ task.aspect_ratio }}</div>
|
||||
<div v-if="task.resolution">
|
||||
{{ task.resolution }}
|
||||
</div>
|
||||
<div v-if="task.aspect_ratio">
|
||||
{{ task.aspect_ratio }}
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<!-- 时间 -->
|
||||
@@ -183,7 +281,10 @@
|
||||
<Clock class="w-3 h-3" />
|
||||
<span>{{ formatDate(task.created_at) }}</span>
|
||||
</div>
|
||||
<div v-if="task.completed_at" class="flex items-center gap-1.5 text-green-600 dark:text-green-400">
|
||||
<div
|
||||
v-if="task.completed_at"
|
||||
class="flex items-center gap-1.5 text-green-600 dark:text-green-400"
|
||||
>
|
||||
<CheckCircle class="w-3 h-3" />
|
||||
<span>{{ formatDate(task.completed_at) }}</span>
|
||||
</div>
|
||||
@@ -217,7 +318,10 @@
|
||||
</Table>
|
||||
|
||||
<!-- 移动端卡片列表 -->
|
||||
<div v-if="tasks.length" class="md:hidden divide-y divide-border/60">
|
||||
<div
|
||||
v-if="tasks.length"
|
||||
class="md:hidden divide-y divide-border/60"
|
||||
>
|
||||
<div
|
||||
v-for="task in tasks"
|
||||
:key="`m-${task.id}`"
|
||||
@@ -227,27 +331,40 @@
|
||||
<!-- 顶部:模型和状态 -->
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="flex items-center gap-2 min-w-0 flex-1">
|
||||
<Video v-if="isVideoTask(task)" class="w-4 h-4 text-muted-foreground shrink-0" />
|
||||
<Video
|
||||
v-if="isVideoTask(task)"
|
||||
class="w-4 h-4 text-muted-foreground shrink-0"
|
||||
/>
|
||||
<span class="font-medium text-sm truncate">{{ task.model }}</span>
|
||||
</div>
|
||||
<Badge :variant="getStatusVariant(task.status)" class="text-xs shrink-0">
|
||||
<Badge
|
||||
:variant="getStatusVariant(task.status)"
|
||||
class="text-xs shrink-0"
|
||||
>
|
||||
{{ getStatusLabel(task.status) }}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<!-- 进度条(如果有) -->
|
||||
<div v-if="task.progress_percent > 0 && task.status === 'processing'" class="space-y-1">
|
||||
<div
|
||||
v-if="task.progress_percent > 0 && task.status === 'processing'"
|
||||
class="space-y-1"
|
||||
>
|
||||
<div class="h-1.5 bg-muted rounded-full overflow-hidden">
|
||||
<div
|
||||
class="h-full bg-primary transition-all"
|
||||
:style="{ width: `${task.progress_percent}%` }"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground text-right">{{ task.progress_percent }}%</p>
|
||||
<p class="text-xs text-muted-foreground text-right">
|
||||
{{ task.progress_percent }}%
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Prompt -->
|
||||
<p class="text-sm text-muted-foreground line-clamp-2">{{ task.prompt }}</p>
|
||||
<p class="text-sm text-muted-foreground line-clamp-2">
|
||||
{{ task.prompt }}
|
||||
</p>
|
||||
|
||||
<!-- 信息网格 -->
|
||||
<div class="grid grid-cols-2 gap-2 text-xs">
|
||||
@@ -263,7 +380,10 @@
|
||||
<Clock class="w-3 h-3" />
|
||||
<span>{{ formatDate(task.created_at) }}</span>
|
||||
</div>
|
||||
<div v-if="task.duration_seconds" class="flex items-center gap-1.5 text-muted-foreground">
|
||||
<div
|
||||
v-if="task.duration_seconds"
|
||||
class="flex items-center gap-1.5 text-muted-foreground"
|
||||
>
|
||||
<Timer class="w-3 h-3" />
|
||||
<span>{{ task.duration_seconds }}s</span>
|
||||
</div>
|
||||
@@ -326,9 +446,14 @@
|
||||
<!-- 第一行:标题、模型、状态、操作按钮 -->
|
||||
<div class="flex items-center justify-between gap-4 mb-3">
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<h3 class="text-lg font-semibold">任务详情</h3>
|
||||
<h3 class="text-lg font-semibold">
|
||||
任务详情
|
||||
</h3>
|
||||
<div class="flex items-center gap-1 text-sm font-mono text-muted-foreground bg-muted px-2 py-0.5 rounded">
|
||||
<Video v-if="isVideoTask(selectedTask)" class="w-3.5 h-3.5 mr-1" />
|
||||
<Video
|
||||
v-if="isVideoTask(selectedTask)"
|
||||
class="w-3.5 h-3.5 mr-1"
|
||||
/>
|
||||
<span>{{ selectedTask.model }}</span>
|
||||
</div>
|
||||
<Badge :variant="getStatusVariant(selectedTask.status)">
|
||||
@@ -344,9 +469,18 @@
|
||||
:title="detailAutoRefresh ? '停止自动刷新' : '开启自动刷新(每5秒)'"
|
||||
@click="toggleDetailAutoRefresh"
|
||||
>
|
||||
<RefreshCw class="w-4 h-4" :class="{ 'animate-spin': detailAutoRefresh }" />
|
||||
<RefreshCw
|
||||
class="w-4 h-4"
|
||||
:class="{ 'animate-spin': detailAutoRefresh }"
|
||||
/>
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" title="关闭" @click="closeDetail">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
title="关闭"
|
||||
@click="closeDetail"
|
||||
>
|
||||
<X class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -365,7 +499,10 @@
|
||||
<span>Provider: {{ selectedTask.provider_name }}</span>
|
||||
</div>
|
||||
<!-- 进度条 -->
|
||||
<div v-if="selectedTask.progress_percent > 0 && selectedTask.status === 'processing'" class="mt-3">
|
||||
<div
|
||||
v-if="selectedTask.progress_percent > 0 && selectedTask.status === 'processing'"
|
||||
class="mt-3"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-1 h-2 bg-muted rounded-full overflow-hidden">
|
||||
<div
|
||||
@@ -375,7 +512,10 @@
|
||||
</div>
|
||||
<span class="text-xs text-muted-foreground font-medium">{{ selectedTask.progress_percent }}%</span>
|
||||
</div>
|
||||
<p v-if="selectedTask.progress_message" class="text-xs text-muted-foreground mt-1">
|
||||
<p
|
||||
v-if="selectedTask.progress_message"
|
||||
class="text-xs text-muted-foreground mt-1"
|
||||
>
|
||||
{{ selectedTask.progress_message }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -384,20 +524,31 @@
|
||||
<!-- 可滚动内容区域 -->
|
||||
<div class="flex-1 min-h-0 overflow-y-auto px-3 sm:px-6 py-3 sm:py-4 space-y-5">
|
||||
<!-- 错误信息 -->
|
||||
<div v-if="selectedTask.error_message" class="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
|
||||
<div
|
||||
v-if="selectedTask.error_message"
|
||||
class="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800"
|
||||
>
|
||||
<div class="flex items-start gap-2">
|
||||
<AlertCircle class="w-4 h-4 text-red-500 shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p v-if="selectedTask.error_code" class="text-xs font-medium text-red-600 dark:text-red-400 mb-1">
|
||||
<p
|
||||
v-if="selectedTask.error_code"
|
||||
class="text-xs font-medium text-red-600 dark:text-red-400 mb-1"
|
||||
>
|
||||
错误码: {{ selectedTask.error_code }}
|
||||
</p>
|
||||
<p class="text-sm text-red-600 dark:text-red-400">{{ selectedTask.error_message }}</p>
|
||||
<p class="text-sm text-red-600 dark:text-red-400">
|
||||
{{ selectedTask.error_message }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频结果(放在最前面) -->
|
||||
<div v-if="selectedTask.video_url || selectedTask.video_urls?.length" class="space-y-3">
|
||||
<div
|
||||
v-if="selectedTask.video_url || selectedTask.video_urls?.length"
|
||||
class="space-y-3"
|
||||
>
|
||||
<!-- 主视频 -->
|
||||
<div v-if="selectedTask.video_url">
|
||||
<div class="rounded-lg overflow-hidden border border-border/60 bg-black">
|
||||
@@ -412,7 +563,10 @@
|
||||
<div class="mt-2 space-y-2">
|
||||
<!-- 链接 -->
|
||||
<div class="flex items-center gap-1 p-1.5 bg-muted/50 rounded border border-border/40">
|
||||
<code class="flex-1 text-xs text-foreground truncate px-1" :title="selectedTask.video_url">{{ selectedTask.video_url }}</code>
|
||||
<code
|
||||
class="flex-1 text-xs text-foreground truncate px-1"
|
||||
:title="selectedTask.video_url"
|
||||
>{{ selectedTask.video_url }}</code>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -423,11 +577,17 @@
|
||||
</Button>
|
||||
</div>
|
||||
<!-- 元信息 -->
|
||||
<div v-if="selectedTask.video_size_bytes || selectedTask.video_expires_at" class="flex items-center gap-3 text-xs text-muted-foreground">
|
||||
<div
|
||||
v-if="selectedTask.video_size_bytes || selectedTask.video_expires_at"
|
||||
class="flex items-center gap-3 text-xs text-muted-foreground"
|
||||
>
|
||||
<span v-if="selectedTask.video_size_bytes">
|
||||
大小: {{ formatFileSize(selectedTask.video_size_bytes) }}
|
||||
</span>
|
||||
<span v-if="selectedTask.video_expires_at" class="text-amber-600 dark:text-amber-400">
|
||||
<span
|
||||
v-if="selectedTask.video_expires_at"
|
||||
class="text-amber-600 dark:text-amber-400"
|
||||
>
|
||||
过期: {{ formatDate(selectedTask.video_expires_at) }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -435,14 +595,30 @@
|
||||
</div>
|
||||
|
||||
<!-- 多个视频 -->
|
||||
<div v-else-if="selectedTask.video_urls?.length" class="space-y-4">
|
||||
<div v-for="(url, index) in selectedTask.video_urls" :key="index">
|
||||
<p class="text-xs text-muted-foreground font-medium mb-1.5">视频 {{ index + 1 }}</p>
|
||||
<div
|
||||
v-else-if="selectedTask.video_urls?.length"
|
||||
class="space-y-4"
|
||||
>
|
||||
<div
|
||||
v-for="(url, index) in selectedTask.video_urls"
|
||||
:key="index"
|
||||
>
|
||||
<p class="text-xs text-muted-foreground font-medium mb-1.5">
|
||||
视频 {{ index + 1 }}
|
||||
</p>
|
||||
<div class="rounded-lg overflow-hidden border border-border/60 bg-black">
|
||||
<video :src="getVideoUrl(selectedTask.id, url)" controls preload="none" class="w-full max-h-[250px] object-contain" />
|
||||
<video
|
||||
:src="getVideoUrl(selectedTask.id, url)"
|
||||
controls
|
||||
preload="none"
|
||||
class="w-full max-h-[250px] object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-1.5 flex items-center gap-1 p-1.5 bg-muted/50 rounded border border-border/40">
|
||||
<code class="flex-1 text-xs text-foreground truncate px-1" :title="url">{{ url }}</code>
|
||||
<code
|
||||
class="flex-1 text-xs text-foreground truncate px-1"
|
||||
:title="url"
|
||||
>{{ url }}</code>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@@ -457,15 +633,22 @@
|
||||
</div>
|
||||
|
||||
<!-- 任务完成但无视频 -->
|
||||
<div v-else-if="selectedTask.status === 'completed'" class="p-4 bg-amber-50 dark:bg-amber-900/20 rounded-lg border border-amber-200 dark:border-amber-800 text-center">
|
||||
<div
|
||||
v-else-if="selectedTask.status === 'completed'"
|
||||
class="p-4 bg-amber-50 dark:bg-amber-900/20 rounded-lg border border-amber-200 dark:border-amber-800 text-center"
|
||||
>
|
||||
<Video class="w-8 h-8 mx-auto mb-2 text-amber-500" />
|
||||
<p class="text-sm text-amber-600 dark:text-amber-400">视频链接不可用或已过期</p>
|
||||
<p class="text-sm text-amber-600 dark:text-amber-400">
|
||||
视频链接不可用或已过期
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Prompt -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wide">Prompt</h4>
|
||||
<h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
||||
Prompt
|
||||
</h4>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -482,69 +665,134 @@
|
||||
</div>
|
||||
|
||||
<!-- 视频信息(网格布局) -->
|
||||
<div v-if="selectedTask.video_duration_seconds || selectedTask.resolution || selectedTask.aspect_ratio || selectedTask.size" class="space-y-2">
|
||||
<h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wide">视频信息</h4>
|
||||
<div
|
||||
v-if="selectedTask.video_duration_seconds || selectedTask.resolution || selectedTask.aspect_ratio || selectedTask.size"
|
||||
class="space-y-2"
|
||||
>
|
||||
<h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
||||
视频信息
|
||||
</h4>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
<div v-if="selectedTask.video_duration_seconds" class="p-3 bg-muted/30 rounded-lg">
|
||||
<p class="text-xs text-muted-foreground mb-0.5">视频时长</p>
|
||||
<p class="text-sm font-medium">{{ selectedTask.video_duration_seconds.toFixed(1) }}s</p>
|
||||
<div
|
||||
v-if="selectedTask.video_duration_seconds"
|
||||
class="p-3 bg-muted/30 rounded-lg"
|
||||
>
|
||||
<p class="text-xs text-muted-foreground mb-0.5">
|
||||
视频时长
|
||||
</p>
|
||||
<p class="text-sm font-medium">
|
||||
{{ selectedTask.video_duration_seconds.toFixed(1) }}s
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="selectedTask.resolution" class="p-3 bg-muted/30 rounded-lg">
|
||||
<p class="text-xs text-muted-foreground mb-0.5">分辨率</p>
|
||||
<p class="text-sm font-medium">{{ selectedTask.resolution }}</p>
|
||||
<div
|
||||
v-if="selectedTask.resolution"
|
||||
class="p-3 bg-muted/30 rounded-lg"
|
||||
>
|
||||
<p class="text-xs text-muted-foreground mb-0.5">
|
||||
分辨率
|
||||
</p>
|
||||
<p class="text-sm font-medium">
|
||||
{{ selectedTask.resolution }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="selectedTask.aspect_ratio" class="p-3 bg-muted/30 rounded-lg">
|
||||
<p class="text-xs text-muted-foreground mb-0.5">宽高比</p>
|
||||
<p class="text-sm font-medium">{{ selectedTask.aspect_ratio }}</p>
|
||||
<div
|
||||
v-if="selectedTask.aspect_ratio"
|
||||
class="p-3 bg-muted/30 rounded-lg"
|
||||
>
|
||||
<p class="text-xs text-muted-foreground mb-0.5">
|
||||
宽高比
|
||||
</p>
|
||||
<p class="text-sm font-medium">
|
||||
{{ selectedTask.aspect_ratio }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="selectedTask.size" class="p-3 bg-muted/30 rounded-lg">
|
||||
<p class="text-xs text-muted-foreground mb-0.5">尺寸</p>
|
||||
<p class="text-sm font-medium">{{ selectedTask.size }}</p>
|
||||
<div
|
||||
v-if="selectedTask.size"
|
||||
class="p-3 bg-muted/30 rounded-lg"
|
||||
>
|
||||
<p class="text-xs text-muted-foreground mb-0.5">
|
||||
尺寸
|
||||
</p>
|
||||
<p class="text-sm font-medium">
|
||||
{{ selectedTask.size }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 执行状态(网格布局) -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wide">执行状态</h4>
|
||||
<h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
||||
执行状态
|
||||
</h4>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||
<div class="p-3 bg-muted/30 rounded-lg">
|
||||
<p class="text-xs text-muted-foreground mb-0.5">轮询</p>
|
||||
<p class="text-sm font-medium">{{ selectedTask.poll_count }} / {{ selectedTask.max_poll_count }}</p>
|
||||
<p class="text-xs text-muted-foreground mb-0.5">
|
||||
轮询
|
||||
</p>
|
||||
<p class="text-sm font-medium">
|
||||
{{ selectedTask.poll_count }} / {{ selectedTask.max_poll_count }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-3 bg-muted/30 rounded-lg">
|
||||
<p class="text-xs text-muted-foreground mb-0.5">重试</p>
|
||||
<p class="text-sm font-medium">{{ selectedTask.retry_count }} / {{ selectedTask.max_retries }}</p>
|
||||
<p class="text-xs text-muted-foreground mb-0.5">
|
||||
重试
|
||||
</p>
|
||||
<p class="text-sm font-medium">
|
||||
{{ selectedTask.retry_count }} / {{ selectedTask.max_retries }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-3 bg-muted/30 rounded-lg">
|
||||
<p class="text-xs text-muted-foreground mb-0.5">轮询间隔</p>
|
||||
<p class="text-sm font-medium">{{ selectedTask.poll_interval_seconds }}s</p>
|
||||
<p class="text-xs text-muted-foreground mb-0.5">
|
||||
轮询间隔
|
||||
</p>
|
||||
<p class="text-sm font-medium">
|
||||
{{ selectedTask.poll_interval_seconds }}s
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="selectedTask.next_poll_at" class="p-3 bg-muted/30 rounded-lg">
|
||||
<p class="text-xs text-muted-foreground mb-0.5">下次轮询</p>
|
||||
<p class="text-sm font-medium">{{ formatDate(selectedTask.next_poll_at) }}</p>
|
||||
<div
|
||||
v-if="selectedTask.next_poll_at"
|
||||
class="p-3 bg-muted/30 rounded-lg"
|
||||
>
|
||||
<p class="text-xs text-muted-foreground mb-0.5">
|
||||
下次轮询
|
||||
</p>
|
||||
<p class="text-sm font-medium">
|
||||
{{ formatDate(selectedTask.next_poll_at) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 时间范围 -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wide">时间范围</h4>
|
||||
<h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
||||
时间范围
|
||||
</h4>
|
||||
<div class="flex items-center gap-1 text-sm font-medium">
|
||||
<span>{{ formatTimeWithMs(selectedTask.created_at) }}</span>
|
||||
<span class="time-arrow-container">
|
||||
<span v-if="selectedTask.completed_at" class="time-duration">+{{ calcDuration(selectedTask.created_at, selectedTask.completed_at) }}</span>
|
||||
<span
|
||||
v-if="selectedTask.completed_at"
|
||||
class="time-duration"
|
||||
>+{{ calcDuration(selectedTask.created_at, selectedTask.completed_at) }}</span>
|
||||
<span class="time-arrow">→</span>
|
||||
</span>
|
||||
<template v-if="selectedTask.completed_at">
|
||||
<span>{{ formatTimeWithMs(selectedTask.completed_at) }}</span>
|
||||
</template>
|
||||
<span v-else class="text-muted-foreground">处理中...</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-muted-foreground"
|
||||
>处理中...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 响应数据 -->
|
||||
<div v-if="selectedTask.request_metadata?.poll_raw_response" class="space-y-2">
|
||||
<div
|
||||
v-if="selectedTask.request_metadata?.poll_raw_response"
|
||||
class="space-y-2"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-xs font-medium text-muted-foreground uppercase tracking-wide flex items-center gap-2">
|
||||
<FileJson class="w-3.5 h-3.5" />
|
||||
@@ -566,7 +814,10 @@
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div v-if="canCancel(selectedTask.status)" class="pt-4 border-t border-border/60">
|
||||
<div
|
||||
v-if="canCancel(selectedTask.status)"
|
||||
class="pt-4 border-t border-border/60"
|
||||
>
|
||||
<Button
|
||||
variant="destructive"
|
||||
class="w-full"
|
||||
@@ -591,52 +842,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.drawer-enter-active,
|
||||
.drawer-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.drawer-enter-active > div:first-child,
|
||||
.drawer-leave-active > div:first-child {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.drawer-enter-active > div:last-child,
|
||||
.drawer-leave-active > div:last-child {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.drawer-enter-from,
|
||||
.drawer-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.drawer-enter-from > div:last-child,
|
||||
.drawer-leave-to > div:last-child {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
/* 时间范围箭头容器 */
|
||||
.time-arrow-container {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.time-arrow {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.time-duration {
|
||||
position: absolute;
|
||||
top: -1rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 0.65rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { asyncTasksApi, type AsyncTaskItem, type AsyncTaskDetail, type AsyncTaskStatsResponse, type AsyncTaskStatus } from '@/api/async-tasks'
|
||||
@@ -1043,3 +1248,49 @@ onUnmounted(() => {
|
||||
clearTimeout(filterTimeout)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.drawer-enter-active,
|
||||
.drawer-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.drawer-enter-active > div:first-child,
|
||||
.drawer-leave-active > div:first-child {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.drawer-enter-active > div:last-child,
|
||||
.drawer-leave-active > div:last-child {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.drawer-enter-from,
|
||||
.drawer-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.drawer-enter-from > div:last-child,
|
||||
.drawer-leave-to > div:last-child {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
/* 时间范围箭头容器 */
|
||||
.time-arrow-container {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.time-arrow {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.time-duration {
|
||||
position: absolute;
|
||||
top: -1rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 0.65rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,31 +2,49 @@
|
||||
<div class="space-y-6 px-4 sm:px-6 lg:px-0">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-lg font-semibold">成本分析</h1>
|
||||
<p class="text-xs text-muted-foreground">成本趋势、预测与节省统计</p>
|
||||
<h1 class="text-lg font-semibold">
|
||||
成本分析
|
||||
</h1>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
成本趋势、预测与节省统计
|
||||
</p>
|
||||
</div>
|
||||
<TimeRangePicker v-model="timeRange" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
|
||||
<Card class="p-4 space-y-2">
|
||||
<div class="text-xs text-muted-foreground">缓存节省</div>
|
||||
<div class="text-lg font-semibold">{{ formatCurrency(costSavings?.cache_savings ?? 0) }}</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
缓存节省
|
||||
</div>
|
||||
<div class="text-lg font-semibold">
|
||||
{{ formatCurrency(costSavings?.cache_savings ?? 0) }}
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
读取成本 {{ formatCurrency(costSavings?.cache_read_cost ?? 0) }}
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="p-4 space-y-2">
|
||||
<div class="text-xs text-muted-foreground">缓存读取 Tokens</div>
|
||||
<div class="text-lg font-semibold">{{ formatTokens(costSavings?.cache_read_tokens ?? 0) }}</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
缓存读取 Tokens
|
||||
</div>
|
||||
<div class="text-lg font-semibold">
|
||||
{{ formatTokens(costSavings?.cache_read_tokens ?? 0) }}
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
预计全额成本 {{ formatCurrency(costSavings?.estimated_full_cost ?? 0) }}
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="p-4 space-y-2">
|
||||
<div class="text-xs text-muted-foreground">缓存创建成本</div>
|
||||
<div class="text-lg font-semibold">{{ formatCurrency(costSavings?.cache_creation_cost ?? 0) }}</div>
|
||||
<div class="text-xs text-muted-foreground">基于当前时间范围</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
缓存创建成本
|
||||
</div>
|
||||
<div class="text-lg font-semibold">
|
||||
{{ formatCurrency(costSavings?.cache_creation_cost ?? 0) }}
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
基于当前时间范围
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,56 +2,89 @@
|
||||
<div class="space-y-6 pb-8">
|
||||
<!-- 统计卡片 -->
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Card variant="default" class="p-4">
|
||||
<Card
|
||||
variant="default"
|
||||
class="p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center">
|
||||
<FileUp class="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold">{{ stats?.total_mappings ?? '-' }}</p>
|
||||
<p class="text-xs text-muted-foreground">总文件数</p>
|
||||
<p class="text-2xl font-bold">
|
||||
{{ stats?.total_mappings ?? '-' }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
总文件数
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card variant="default" class="p-4">
|
||||
<Card
|
||||
variant="default"
|
||||
class="p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-green-500/10 flex items-center justify-center">
|
||||
<CheckCircle class="w-5 h-5 text-green-500" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold">{{ stats?.active_mappings ?? '-' }}</p>
|
||||
<p class="text-xs text-muted-foreground">有效文件</p>
|
||||
<p class="text-2xl font-bold">
|
||||
{{ stats?.active_mappings ?? '-' }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
有效文件
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card variant="default" class="p-4">
|
||||
<Card
|
||||
variant="default"
|
||||
class="p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-amber-500/10 flex items-center justify-center">
|
||||
<Clock class="w-5 h-5 text-amber-500" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold">{{ stats?.expired_mappings ?? '-' }}</p>
|
||||
<p class="text-xs text-muted-foreground">已过期</p>
|
||||
<p class="text-2xl font-bold">
|
||||
{{ stats?.expired_mappings ?? '-' }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
已过期
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card variant="default" class="p-4">
|
||||
<Card
|
||||
variant="default"
|
||||
class="p-4"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-blue-500/10 flex items-center justify-center">
|
||||
<Key class="w-5 h-5 text-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold">{{ stats?.capable_keys_count ?? '-' }}</p>
|
||||
<p class="text-xs text-muted-foreground">支持的 Key</p>
|
||||
<p class="text-2xl font-bold">
|
||||
{{ stats?.capable_keys_count ?? '-' }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
支持的 Key
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 上传区域 -->
|
||||
<Card variant="default" class="p-4">
|
||||
<Card
|
||||
variant="default"
|
||||
class="p-4"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-sm font-medium">上传文件</h3>
|
||||
<h3 class="text-sm font-medium">
|
||||
上传文件
|
||||
</h3>
|
||||
<Button
|
||||
v-if="capableKeys.length > 0"
|
||||
variant="ghost"
|
||||
@@ -64,8 +97,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Key 选择器 -->
|
||||
<div v-if="capableKeys.length > 0" class="mb-4">
|
||||
<p class="text-xs text-muted-foreground mb-2">选择要上传到的 Key(可多选):</p>
|
||||
<div
|
||||
v-if="capableKeys.length > 0"
|
||||
class="mb-4"
|
||||
>
|
||||
<p class="text-xs text-muted-foreground mb-2">
|
||||
选择要上传到的 Key(可多选):
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="key in capableKeys"
|
||||
@@ -77,11 +115,17 @@
|
||||
@click="toggleKeySelection(key.id)"
|
||||
>
|
||||
<span class="font-medium">{{ key.name }}</span>
|
||||
<span v-if="key.provider_name" class="text-muted-foreground ml-1">({{ key.provider_name }})</span>
|
||||
<span
|
||||
v-if="key.provider_name"
|
||||
class="text-muted-foreground ml-1"
|
||||
>({{ key.provider_name }})</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="mb-4 text-sm text-amber-600 bg-amber-50 dark:bg-amber-950/30 rounded-lg p-3">
|
||||
<div
|
||||
v-else
|
||||
class="mb-4 text-sm text-amber-600 bg-amber-50 dark:bg-amber-950/30 rounded-lg p-3"
|
||||
>
|
||||
暂无可用的 Key,请先配置具有「Gemini 文件 API」能力的 Key
|
||||
</div>
|
||||
|
||||
@@ -102,12 +146,20 @@
|
||||
type="file"
|
||||
class="hidden"
|
||||
@change="handleFileSelect"
|
||||
/>
|
||||
<div v-if="uploading" class="flex flex-col items-center gap-2">
|
||||
>
|
||||
<div
|
||||
v-if="uploading"
|
||||
class="flex flex-col items-center gap-2"
|
||||
>
|
||||
<Loader2 class="w-8 h-8 animate-spin text-primary" />
|
||||
<p class="text-sm text-muted-foreground">正在上传到 {{ selectedKeyIds.length }} 个 Key...</p>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
正在上传到 {{ selectedKeyIds.length }} 个 Key...
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="flex flex-col items-center gap-2">
|
||||
<div
|
||||
v-else
|
||||
class="flex flex-col items-center gap-2"
|
||||
>
|
||||
<Upload class="w-8 h-8 text-muted-foreground" />
|
||||
<p class="text-sm text-muted-foreground">
|
||||
<template v-if="selectedKeyIds.length > 0">
|
||||
@@ -131,8 +183,14 @@
|
||||
</Card>
|
||||
|
||||
<!-- MIME 类型分布 -->
|
||||
<Card v-if="stats?.by_mime_type && Object.keys(stats.by_mime_type).length > 0" variant="default" class="p-4">
|
||||
<h3 class="text-sm font-medium mb-3">文件类型分布</h3>
|
||||
<Card
|
||||
v-if="stats?.by_mime_type && Object.keys(stats.by_mime_type).length > 0"
|
||||
variant="default"
|
||||
class="p-4"
|
||||
>
|
||||
<h3 class="text-sm font-medium mb-3">
|
||||
文件类型分布
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<Badge
|
||||
v-for="(count, mimeType) in stats.by_mime_type"
|
||||
@@ -146,11 +204,16 @@
|
||||
</Card>
|
||||
|
||||
<!-- 文件映射表格 -->
|
||||
<Card variant="default" class="overflow-hidden">
|
||||
<Card
|
||||
variant="default"
|
||||
class="overflow-hidden"
|
||||
>
|
||||
<!-- 标题和筛选器 -->
|
||||
<div class="px-4 sm:px-6 py-3.5 border-b border-border/60">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<h3 class="text-base font-semibold">文件映射</h3>
|
||||
<h3 class="text-base font-semibold">
|
||||
文件映射
|
||||
</h3>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- 搜索 -->
|
||||
<Input
|
||||
@@ -165,7 +228,7 @@
|
||||
v-model="includeExpired"
|
||||
type="checkbox"
|
||||
class="rounded border-border"
|
||||
/>
|
||||
>
|
||||
包含过期
|
||||
</label>
|
||||
<!-- 清理过期按钮 -->
|
||||
@@ -187,29 +250,45 @@
|
||||
:disabled="loading"
|
||||
@click="fetchData"
|
||||
>
|
||||
<RefreshCw class="w-3.5 h-3.5" :class="{ 'animate-spin': loading }" />
|
||||
<RefreshCw
|
||||
class="w-3.5 h-3.5"
|
||||
:class="{ 'animate-spin': loading }"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading && !mappings.length" class="p-8 text-center">
|
||||
<div
|
||||
v-if="loading && !mappings.length"
|
||||
class="p-8 text-center"
|
||||
>
|
||||
<Loader2 class="w-8 h-8 animate-spin mx-auto text-muted-foreground" />
|
||||
<p class="mt-2 text-sm text-muted-foreground">加载中...</p>
|
||||
<p class="mt-2 text-sm text-muted-foreground">
|
||||
加载中...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="!mappings.length" class="p-8 text-center">
|
||||
<div
|
||||
v-else-if="!mappings.length"
|
||||
class="p-8 text-center"
|
||||
>
|
||||
<FileUp class="w-12 h-12 mx-auto text-muted-foreground/50" />
|
||||
<p class="mt-2 text-sm text-muted-foreground">暂无文件映射</p>
|
||||
<p class="mt-2 text-sm text-muted-foreground">
|
||||
暂无文件映射
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
用户通过 Gemini Files API 上传文件后会在此显示
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<div v-else class="divide-y divide-border/60">
|
||||
<div
|
||||
v-else
|
||||
class="divide-y divide-border/60"
|
||||
>
|
||||
<div
|
||||
v-for="mapping in mappings"
|
||||
:key="mapping.id"
|
||||
@@ -220,30 +299,53 @@
|
||||
<div class="flex-1 min-w-0">
|
||||
<!-- 文件名和状态 -->
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<component :is="getFileIcon(mapping.mime_type)" class="w-4 h-4 text-muted-foreground" />
|
||||
<component
|
||||
:is="getFileIcon(mapping.mime_type)"
|
||||
class="w-4 h-4 text-muted-foreground"
|
||||
/>
|
||||
<span class="font-mono text-sm font-medium">{{ mapping.file_name }}</span>
|
||||
<Badge v-if="mapping.is_expired" variant="secondary" class="text-xs">
|
||||
<Badge
|
||||
v-if="mapping.is_expired"
|
||||
variant="secondary"
|
||||
class="text-xs"
|
||||
>
|
||||
已过期
|
||||
</Badge>
|
||||
<Badge v-else variant="outline" class="text-xs text-green-600">
|
||||
<Badge
|
||||
v-else
|
||||
variant="outline"
|
||||
class="text-xs text-green-600"
|
||||
>
|
||||
有效
|
||||
</Badge>
|
||||
</div>
|
||||
<!-- 显示名 -->
|
||||
<p v-if="mapping.display_name" class="text-sm text-muted-foreground truncate">
|
||||
<p
|
||||
v-if="mapping.display_name"
|
||||
class="text-sm text-muted-foreground truncate"
|
||||
>
|
||||
{{ mapping.display_name }}
|
||||
</p>
|
||||
<!-- 元信息 -->
|
||||
<div class="flex items-center gap-4 mt-2 text-xs text-muted-foreground">
|
||||
<span v-if="mapping.mime_type" class="flex items-center gap-1">
|
||||
<span
|
||||
v-if="mapping.mime_type"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<File class="w-3 h-3" />
|
||||
{{ mapping.mime_type }}
|
||||
</span>
|
||||
<span v-if="mapping.username" class="flex items-center gap-1">
|
||||
<span
|
||||
v-if="mapping.username"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<User class="w-3 h-3" />
|
||||
{{ mapping.username }}
|
||||
</span>
|
||||
<span v-if="mapping.key_name" class="flex items-center gap-1">
|
||||
<span
|
||||
v-if="mapping.key_name"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<Key class="w-3 h-3" />
|
||||
{{ mapping.key_name }}
|
||||
</span>
|
||||
@@ -251,7 +353,10 @@
|
||||
<Clock class="w-3 h-3" />
|
||||
{{ formatDate(mapping.created_at) }}
|
||||
</span>
|
||||
<span class="flex items-center gap-1" :class="{ 'text-red-500': mapping.is_expired }">
|
||||
<span
|
||||
class="flex items-center gap-1"
|
||||
:class="{ 'text-red-500': mapping.is_expired }"
|
||||
>
|
||||
<Timer class="w-3 h-3" />
|
||||
过期: {{ formatDate(mapping.expires_at) }}
|
||||
</span>
|
||||
@@ -274,7 +379,10 @@
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div v-if="totalPages > 1" class="px-4 sm:px-6 py-3 border-t border-border/60 flex items-center justify-between">
|
||||
<div
|
||||
v-if="totalPages > 1"
|
||||
class="px-4 sm:px-6 py-3 border-t border-border/60 flex items-center justify-between"
|
||||
>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
共 {{ total }} 条记录
|
||||
</p>
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
<div class="space-y-6 px-4 sm:px-6 lg:px-0">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-lg font-semibold">性能分析</h1>
|
||||
<p class="text-xs text-muted-foreground">延迟分布与错误统计</p>
|
||||
<h1 class="text-lg font-semibold">
|
||||
性能分析
|
||||
</h1>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
延迟分布与错误统计
|
||||
</p>
|
||||
</div>
|
||||
<TimeRangePicker v-model="timeRange" />
|
||||
</div>
|
||||
@@ -36,22 +40,38 @@
|
||||
/>
|
||||
</Card>
|
||||
<Card class="p-4 space-y-3">
|
||||
<h3 class="text-sm font-semibold">错误趋势</h3>
|
||||
<div v-if="errorLoading" class="p-6">
|
||||
<h3 class="text-sm font-semibold">
|
||||
错误趋势
|
||||
</h3>
|
||||
<div
|
||||
v-if="errorLoading"
|
||||
class="p-6"
|
||||
>
|
||||
<LoadingState />
|
||||
</div>
|
||||
<div v-else class="h-[260px]">
|
||||
<div
|
||||
v-else
|
||||
class="h-[260px]"
|
||||
>
|
||||
<LineChart :data="errorTrendChartData" />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="p-4 space-y-3">
|
||||
<h3 class="text-sm font-semibold">提供商健康度</h3>
|
||||
<div v-if="providerLoading" class="p-4">
|
||||
<h3 class="text-sm font-semibold">
|
||||
提供商健康度
|
||||
</h3>
|
||||
<div
|
||||
v-if="providerLoading"
|
||||
class="p-4"
|
||||
>
|
||||
<LoadingState />
|
||||
</div>
|
||||
<div v-else class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 text-sm">
|
||||
<div
|
||||
v-else
|
||||
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 text-sm"
|
||||
>
|
||||
<div
|
||||
v-for="provider in providerStatus"
|
||||
:key="provider.name"
|
||||
|
||||
@@ -2,12 +2,22 @@
|
||||
<div class="space-y-6 px-4 sm:px-6 lg:px-0">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||
<div>
|
||||
<h1 class="text-lg font-semibold">用户统计</h1>
|
||||
<p class="text-xs text-muted-foreground">查看用户排行榜与使用趋势</p>
|
||||
<h1 class="text-lg font-semibold">
|
||||
用户统计
|
||||
</h1>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
查看用户排行榜与使用趋势
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<TimeRangePicker v-model="timeRange" :allow-hourly="true" />
|
||||
<Select v-model:open="userSelectOpen" v-model="selectedUserId">
|
||||
<TimeRangePicker
|
||||
v-model="timeRange"
|
||||
:allow-hourly="true"
|
||||
/>
|
||||
<Select
|
||||
v-model:open="userSelectOpen"
|
||||
v-model="selectedUserId"
|
||||
>
|
||||
<SelectTrigger class="h-8 text-xs w-52">
|
||||
<SelectValue placeholder="选择用户" />
|
||||
</SelectTrigger>
|
||||
@@ -21,12 +31,17 @@
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select v-model:open="compareUserSelectOpen" v-model="compareUserId">
|
||||
<Select
|
||||
v-model:open="compareUserSelectOpen"
|
||||
v-model="compareUserId"
|
||||
>
|
||||
<SelectTrigger class="h-8 text-xs w-52">
|
||||
<SelectValue placeholder="对比用户(可选)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="__none__">不对比</SelectItem>
|
||||
<SelectItem value="__none__">
|
||||
不对比
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
v-for="user in users"
|
||||
:key="`compare-${user.id}`"
|
||||
@@ -49,43 +64,80 @@
|
||||
/>
|
||||
|
||||
<Card class="p-4 space-y-3">
|
||||
<h3 class="text-sm font-semibold">用户摘要</h3>
|
||||
<div v-if="summaryLoading" class="p-6">
|
||||
<h3 class="text-sm font-semibold">
|
||||
用户摘要
|
||||
</h3>
|
||||
<div
|
||||
v-if="summaryLoading"
|
||||
class="p-6"
|
||||
>
|
||||
<LoadingState />
|
||||
</div>
|
||||
<div v-else class="grid grid-cols-2 gap-3 text-sm">
|
||||
<div
|
||||
v-else
|
||||
class="grid grid-cols-2 gap-3 text-sm"
|
||||
>
|
||||
<div>
|
||||
<div class="text-xs text-muted-foreground">请求数</div>
|
||||
<div class="font-semibold">{{ userSummary?.total_requests ?? 0 }}</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
请求数
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
{{ userSummary?.total_requests ?? 0 }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-muted-foreground">Tokens</div>
|
||||
<div class="font-semibold">{{ formatTokens(userSummary?.total_tokens ?? 0) }}</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
Tokens
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
{{ formatTokens(userSummary?.total_tokens ?? 0) }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-muted-foreground">成本</div>
|
||||
<div class="font-semibold">{{ formatCurrency(userSummary?.total_cost ?? 0) }}</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
成本
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
{{ formatCurrency(userSummary?.total_cost ?? 0) }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-muted-foreground">错误率</div>
|
||||
<div class="font-semibold">{{ userSummary?.error_rate ?? 0 }}%</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
错误率
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
{{ userSummary?.error_rate ?? 0 }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="p-4 space-y-4">
|
||||
<h3 class="text-sm font-semibold">用户使用趋势</h3>
|
||||
<div v-if="seriesLoading" class="p-6">
|
||||
<h3 class="text-sm font-semibold">
|
||||
用户使用趋势
|
||||
</h3>
|
||||
<div
|
||||
v-if="seriesLoading"
|
||||
class="p-6"
|
||||
>
|
||||
<LoadingState />
|
||||
</div>
|
||||
<div v-else class="h-[280px]">
|
||||
<div
|
||||
v-else
|
||||
class="h-[280px]"
|
||||
>
|
||||
<LineChart :data="seriesChartData" />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card v-if="comparisonSeries.length > 0" class="p-4 space-y-4">
|
||||
<h3 class="text-sm font-semibold">用户对比趋势</h3>
|
||||
<Card
|
||||
v-if="comparisonSeries.length > 0"
|
||||
class="p-4 space-y-4"
|
||||
>
|
||||
<h3 class="text-sm font-semibold">
|
||||
用户对比趋势
|
||||
</h3>
|
||||
<div class="h-[280px]">
|
||||
<LineChart :data="comparisonChartData" />
|
||||
</div>
|
||||
|
||||
@@ -247,7 +247,10 @@
|
||||
class="mb-6 text-3xl sm:text-5xl md:text-7xl font-bold text-[#191919] dark:text-white leading-tight transition-all duration-700"
|
||||
:style="getTitleStyle(SECTIONS.HOME)"
|
||||
>
|
||||
欢迎使用 <span class="text-primary typewriter">{{ aetherText }}<span class="cursor" :class="{ 'cursor-hidden': !showCursor }">_</span></span>
|
||||
欢迎使用 <span class="text-primary typewriter">{{ aetherText }}<span
|
||||
class="cursor"
|
||||
:class="{ 'cursor-hidden': !showCursor }"
|
||||
>_</span></span>
|
||||
</h1>
|
||||
<p
|
||||
class="mb-8 text-base sm:text-lg md:text-xl text-[#666663] dark:text-[#c9c3b4] max-w-2xl mx-auto transition-all duration-700"
|
||||
@@ -568,7 +571,7 @@ const aetherText = ref('')
|
||||
const showCursor = ref(true)
|
||||
const typewriterFullText = 'Aether'
|
||||
let typewriterTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let hasTypewriterStarted = ref(false)
|
||||
const hasTypewriterStarted = ref(false)
|
||||
|
||||
const startTypewriter = () => {
|
||||
if (hasTypewriterStarted.value) return
|
||||
|
||||
@@ -88,12 +88,17 @@ const systemSettings = [
|
||||
格式转换
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="p-2 rounded-lg bg-purple-500/10">
|
||||
<Shuffle class="h-5 w-5 text-purple-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">什么是格式转换?</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
什么是格式转换?
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
@@ -112,21 +117,30 @@ const systemSettings = [
|
||||
<ArrowRight class="h-4 w-4 text-[#999]" />
|
||||
<span :class="panelClasses.badgeGreen">{{ example.to }}</span>
|
||||
</div>
|
||||
<p class="text-xs text-[#666663] dark:text-[#a3a094]">{{ example.description }}</p>
|
||||
<p class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
{{ example.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 如何启用 -->
|
||||
<div :class="[panelClasses.section, 'p-5 space-y-4']">
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">如何启用格式转换</h3>
|
||||
<div
|
||||
class="p-5 space-y-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
如何启用格式转换
|
||||
</h3>
|
||||
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
|
||||
1
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">开启系统设置</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
开启系统设置
|
||||
</p>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
在「系统设置」中开启 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">ENABLE_API_FORMAT_CONVERSION</code>
|
||||
</p>
|
||||
@@ -138,7 +152,9 @@ const systemSettings = [
|
||||
2
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">配置端点</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
配置端点
|
||||
</p>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
在端点配置中,启用「格式接受配置」并选择接受的入站格式
|
||||
</p>
|
||||
@@ -150,7 +166,9 @@ const systemSettings = [
|
||||
3
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">使用</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
使用
|
||||
</p>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
用户现在可以用 OpenAI SDK 调用配置在 Claude 格式端点上的模型
|
||||
</p>
|
||||
@@ -158,11 +176,16 @@ const systemSettings = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-4']">
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">注意事项</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
注意事项
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
格式转换会增加少量延迟(通常 <10ms)。部分特有功能(如 Claude 的 thinking、OpenAI 的 function calling)
|
||||
可能无法完美转换,建议在实际场景中测试。
|
||||
@@ -178,25 +201,37 @@ const systemSettings = [
|
||||
请求头规则
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="p-2 rounded-lg bg-green-500/10">
|
||||
<FileCode class="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">什么是请求头规则?</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
什么是请求头规则?
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
请求头规则允许你在转发请求时修改 HTTP 请求头。可以添加、修改或删除特定的请求头。
|
||||
</p>
|
||||
|
||||
<div :class="[panelClasses.section, 'overflow-hidden']">
|
||||
<div
|
||||
class="overflow-hidden"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">类型</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">说明</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
类型
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
说明
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -218,11 +253,16 @@ const systemSettings = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-4']">
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">使用场景</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
使用场景
|
||||
</p>
|
||||
<ul class="mt-1 space-y-1">
|
||||
<li>• 添加额外的认证信息(如 API 版本号)</li>
|
||||
<li>• 添加跟踪标记(如请求 ID、来源标识)</li>
|
||||
@@ -239,12 +279,17 @@ const systemSettings = [
|
||||
代理设置
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="p-2 rounded-lg bg-blue-500/10">
|
||||
<Globe class="h-5 w-5 text-blue-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">HTTP 代理</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
HTTP 代理
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
@@ -253,14 +298,18 @@ const systemSettings = [
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="p-4 rounded-lg bg-[#f5f5f0]/50 dark:bg-[#1f1d1a]/50">
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">端点级代理</h4>
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
端点级代理
|
||||
</h4>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
在端点配置中填写代理地址,格式如 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">http://proxy:8080</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 rounded-lg bg-[#f5f5f0]/50 dark:bg-[#1f1d1a]/50">
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">全局代理</h4>
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
全局代理
|
||||
</h4>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
也可以通过环境变量 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">HTTP_PROXY</code> 设置全局代理
|
||||
</p>
|
||||
@@ -283,11 +332,14 @@ const systemSettings = [
|
||||
<div
|
||||
v-for="category in systemSettings"
|
||||
:key="category.category"
|
||||
:class="[panelClasses.section, 'p-4']"
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<Settings class="h-4 w-4 text-[#cc785c]" />
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">{{ category.category }}</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ category.category }}
|
||||
</h3>
|
||||
</div>
|
||||
<ul class="space-y-2">
|
||||
<li
|
||||
@@ -309,12 +361,17 @@ const systemSettings = [
|
||||
健康监控
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="p-2 rounded-lg bg-green-500/10">
|
||||
<Shield class="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">端点健康检查</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
端点健康检查
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<ul class="space-y-3 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
@@ -337,11 +394,16 @@ const systemSettings = [
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-4']">
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<AlertTriangle class="h-5 w-5 text-yellow-500 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">端点显示不健康?</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
端点显示不健康?
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
检查:1) API URL 是否正确;2) API Key 是否有效;3) 网络是否可达;4) 是否需要配置代理。
|
||||
</p>
|
||||
@@ -354,11 +416,16 @@ const systemSettings = [
|
||||
<section class="pt-4">
|
||||
<RouterLink
|
||||
to="/guide/faq"
|
||||
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">常见问题</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">查看使用中的常见问题和解答</div>
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
常见问题
|
||||
</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
查看使用中的常见问题和解答
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
|
||||
@@ -81,7 +81,10 @@ function toggleAll() {
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div :class="[panelClasses.section, 'p-4']">
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<Search class="h-5 w-5 text-[#999]" />
|
||||
<input
|
||||
@@ -89,7 +92,7 @@ function toggleAll() {
|
||||
type="text"
|
||||
placeholder="搜索问题..."
|
||||
class="flex-1 bg-transparent border-none outline-none text-[#262624] dark:text-[#f1ead8] placeholder:text-[#999]"
|
||||
/>
|
||||
>
|
||||
<button
|
||||
v-if="filteredFaqs.length > 0"
|
||||
class="text-sm text-[#cc785c] hover:underline"
|
||||
@@ -101,7 +104,10 @@ function toggleAll() {
|
||||
</div>
|
||||
|
||||
<!-- FAQ 列表 -->
|
||||
<div v-if="filteredFaqs.length > 0" class="space-y-6">
|
||||
<div
|
||||
v-if="filteredFaqs.length > 0"
|
||||
class="space-y-6"
|
||||
>
|
||||
<div
|
||||
v-for="category in Object.keys(faqsByCategory)"
|
||||
:key="category"
|
||||
@@ -116,7 +122,8 @@ function toggleAll() {
|
||||
<div
|
||||
v-for="faq in faqsByCategory[category]"
|
||||
:key="faq.id"
|
||||
:class="[panelClasses.section, 'overflow-hidden']"
|
||||
class="overflow-hidden"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<button
|
||||
class="w-full px-4 py-3 flex items-center justify-between text-left hover:bg-[#f5f5f0]/50 dark:hover:bg-[#1f1d1a]/50 transition-colors"
|
||||
@@ -144,7 +151,8 @@ function toggleAll() {
|
||||
<!-- 无结果 -->
|
||||
<div
|
||||
v-else
|
||||
:class="[panelClasses.section, 'p-8 text-center']"
|
||||
class="p-8 text-center"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<HelpCircle class="h-12 w-12 text-[#999] mx-auto mb-4" />
|
||||
<p class="text-[#666663] dark:text-[#a3a094]">
|
||||
@@ -163,11 +171,16 @@ function toggleAll() {
|
||||
href="https://github.com/your-repo/aether"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<div class="p-2 rounded-lg bg-gray-500/10">
|
||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
@@ -181,11 +194,18 @@ function toggleAll() {
|
||||
href="https://github.com/your-repo/aether/discussions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<div class="p-2 rounded-lg bg-blue-500/10">
|
||||
<svg class="h-5 w-5 text-blue-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
||||
<svg
|
||||
class="h-5 w-5 text-blue-500"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
type="text"
|
||||
class="w-full px-3 py-2 text-sm rounded-lg border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-white dark:bg-[#1f1d1a] text-[#191919] dark:text-white placeholder-[#91918d] focus:outline-none focus:ring-2 focus:ring-[#cc785c]/30"
|
||||
placeholder="https://your-aether.com"
|
||||
/>
|
||||
>
|
||||
<p class="mt-1.5 text-xs text-[#91918d]">
|
||||
代码示例将使用此 URL
|
||||
</p>
|
||||
@@ -176,7 +176,10 @@
|
||||
<main class="flex-1 min-w-0">
|
||||
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8 md:py-12">
|
||||
<RouterView v-slot="{ Component }">
|
||||
<component :is="Component" :base-url="baseUrl" />
|
||||
<component
|
||||
:is="Component"
|
||||
:base-url="baseUrl"
|
||||
/>
|
||||
</RouterView>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -50,12 +50,17 @@ const lbIcons = {
|
||||
什么是模型?
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="p-2 rounded-lg bg-purple-500/10">
|
||||
<Layers class="h-5 w-5 text-purple-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">模型 (Model)</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
模型 (Model)
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<ul class="space-y-3 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
@@ -78,11 +83,16 @@ const lbIcons = {
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-4']">
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">示例场景</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
示例场景
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
用户请求模型 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">gpt-4</code>,
|
||||
你可以配置它同时使用 OpenAI 官方端点和某个代理端点。
|
||||
@@ -99,13 +109,18 @@ const lbIcons = {
|
||||
创建模型
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5 space-y-4']">
|
||||
<div
|
||||
class="p-5 space-y-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
|
||||
1
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">进入模型管理页面</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
进入模型管理页面
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
在管理后台左侧菜单点击「模型管理」
|
||||
</p>
|
||||
@@ -117,7 +132,9 @@ const lbIcons = {
|
||||
2
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">添加模型</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
添加模型
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
点击「添加模型」,填写模型名称(这是用户调用时使用的名称)
|
||||
</p>
|
||||
@@ -129,7 +146,9 @@ const lbIcons = {
|
||||
3
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">关联端点</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
关联端点
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
选择该模型要使用的端点,可以选择多个。如果端点的模型名与你定义的不同,需要设置目标模型名映射。
|
||||
</p>
|
||||
@@ -141,7 +160,9 @@ const lbIcons = {
|
||||
4
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">配置负载均衡</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
配置负载均衡
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
如果选择了多个端点,可以配置负载均衡策略
|
||||
</p>
|
||||
@@ -156,14 +177,23 @@ const lbIcons = {
|
||||
配置字段说明
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'overflow-hidden']">
|
||||
<div
|
||||
class="overflow-hidden"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">字段</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">说明</th>
|
||||
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">必填</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
字段
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
说明
|
||||
</th>
|
||||
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
必填
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -179,8 +209,14 @@ const lbIcons = {
|
||||
{{ field.description }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<span v-if="field.required" :class="panelClasses.badgeGreen">必填</span>
|
||||
<span v-else class="text-[#999]">可选</span>
|
||||
<span
|
||||
v-if="field.required"
|
||||
:class="panelClasses.badgeGreen"
|
||||
>必填</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-[#999]"
|
||||
>可选</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -202,26 +238,36 @@ const lbIcons = {
|
||||
<div
|
||||
v-for="lb in loadBalanceModes"
|
||||
:key="lb.mode"
|
||||
:class="[panelClasses.section, 'p-4']"
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<component
|
||||
:is="lbIcons[lb.mode as keyof typeof lbIcons] || Shuffle"
|
||||
class="h-5 w-5 text-[#cc785c]"
|
||||
/>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">{{ lb.name }}</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ lb.name }}
|
||||
</h3>
|
||||
</div>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">{{ lb.description }}</p>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
{{ lb.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-4']">
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">推荐配置</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
推荐配置
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
<strong>主备切换场景</strong>:使用「优先级」模式,将官方端点设为高优先级,代理端点设为低优先级。<br />
|
||||
<strong>主备切换场景</strong>:使用「优先级」模式,将官方端点设为高优先级,代理端点设为低优先级。<br>
|
||||
<strong>分摊负载场景</strong>:使用「轮询」或「加权」模式,将多个同质端点平均分配请求。
|
||||
</p>
|
||||
</div>
|
||||
@@ -235,7 +281,10 @@ const lbIcons = {
|
||||
模型名映射
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
有时候你希望用户调用的模型名与实际发给端点的模型名不同。例如:
|
||||
</p>
|
||||
@@ -278,7 +327,10 @@ const lbIcons = {
|
||||
模型别名
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
别名让用户可以用多个名称访问同一个模型。适用于:
|
||||
</p>
|
||||
@@ -304,11 +356,16 @@ const lbIcons = {
|
||||
<section class="pt-4">
|
||||
<RouterLink
|
||||
to="/guide/user-key"
|
||||
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">下一步:用户与密钥</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">管理用户和 API Key</div>
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
下一步:用户与密钥
|
||||
</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
管理用户和 API Key
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
|
||||
@@ -61,16 +61,16 @@ const conceptIconColors = {
|
||||
<div
|
||||
v-for="concept in coreConcepts"
|
||||
:key="concept.name"
|
||||
class="p-4 rounded-xl border-2 transition-all"
|
||||
:class="[
|
||||
'p-4 rounded-xl border-2 transition-all',
|
||||
conceptColors[concept.color as keyof typeof conceptColors]
|
||||
]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<component
|
||||
:is="conceptIcons[concept.color as keyof typeof conceptIcons]"
|
||||
class="h-6 w-6 flex-shrink-0 mt-0.5"
|
||||
:class="[
|
||||
'h-6 w-6 flex-shrink-0 mt-0.5',
|
||||
conceptIconColors[concept.color as keyof typeof conceptIconColors]
|
||||
]"
|
||||
/>
|
||||
@@ -87,7 +87,10 @@ const conceptIconColors = {
|
||||
</div>
|
||||
|
||||
<!-- 关系图 -->
|
||||
<div :class="[panelClasses.section, 'p-6 mt-6']">
|
||||
<div
|
||||
class="p-6 mt-6"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h3 class="text-sm font-medium text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
它们之间的关系
|
||||
</h3>
|
||||
@@ -134,7 +137,8 @@ const conceptIconColors = {
|
||||
<div
|
||||
v-for="step in configSteps"
|
||||
:key="step.step"
|
||||
:class="[panelClasses.section, 'p-4 relative']"
|
||||
class="p-4 relative"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="absolute -top-3 -left-2 w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm">
|
||||
{{ step.step }}
|
||||
@@ -160,15 +164,26 @@ const conceptIconColors = {
|
||||
Aether 支持多种 API 格式,可以作为不同客户端的统一入口:
|
||||
</p>
|
||||
|
||||
<div :class="[panelClasses.section, 'overflow-hidden']">
|
||||
<div
|
||||
class="overflow-hidden"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">格式</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">端点</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">认证方式</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">常用客户端</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
格式
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
端点
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
认证方式
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
常用客户端
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -204,36 +219,51 @@ const conceptIconColors = {
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<RouterLink
|
||||
to="/guide/provider"
|
||||
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<Server class="h-5 w-5 text-[#cc785c]" />
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">供应商管理</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">添加和配置 API 供应商</div>
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
供应商管理
|
||||
</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
添加和配置 API 供应商
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink
|
||||
to="/guide/model"
|
||||
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<Layers class="h-5 w-5 text-[#cc785c]" />
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">模型管理</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">配置模型和负载均衡</div>
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
模型管理
|
||||
</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
配置模型和负载均衡
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink
|
||||
to="/guide/user-key"
|
||||
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<Key class="h-5 w-5 text-[#cc785c]" />
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">用户与密钥</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">管理用户和 API Key</div>
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
用户与密钥
|
||||
</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
管理用户和 API Key
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
|
||||
@@ -84,12 +84,17 @@ const providerExamples = [
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="p-2 rounded-lg bg-blue-500/10">
|
||||
<Server class="h-5 w-5 text-blue-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">供应商 (Provider)</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
供应商 (Provider)
|
||||
</h3>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
@@ -107,12 +112,17 @@ const providerExamples = [
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="p-2 rounded-lg bg-green-500/10">
|
||||
<Settings class="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">端点 (Endpoint)</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
端点 (Endpoint)
|
||||
</h3>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
@@ -138,13 +148,18 @@ const providerExamples = [
|
||||
添加供应商
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5 space-y-4']">
|
||||
<div
|
||||
class="p-5 space-y-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
|
||||
1
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">进入供应商管理页面</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
进入供应商管理页面
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
登录管理后台,在左侧菜单点击「供应商管理」
|
||||
</p>
|
||||
@@ -156,7 +171,9 @@ const providerExamples = [
|
||||
2
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">创建供应商</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
创建供应商
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
点击「添加供应商」按钮,填写供应商名称(如 OpenAI、Anthropic)
|
||||
</p>
|
||||
@@ -168,7 +185,9 @@ const providerExamples = [
|
||||
3
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">添加端点</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
添加端点
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
在供应商下点击「添加端点」,填写 API URL、密钥等配置
|
||||
</p>
|
||||
@@ -183,14 +202,23 @@ const providerExamples = [
|
||||
端点配置字段
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'overflow-hidden']">
|
||||
<div
|
||||
class="overflow-hidden"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">字段</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">说明</th>
|
||||
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">必填</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
字段
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
说明
|
||||
</th>
|
||||
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
必填
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -206,8 +234,14 @@ const providerExamples = [
|
||||
{{ field.description }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<span v-if="field.required" :class="panelClasses.badgeGreen">必填</span>
|
||||
<span v-else class="text-[#999]">可选</span>
|
||||
<span
|
||||
v-if="field.required"
|
||||
:class="panelClasses.badgeGreen"
|
||||
>必填</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-[#999]"
|
||||
>可选</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -222,11 +256,16 @@ const providerExamples = [
|
||||
API 格式选择
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-4']">
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">如何选择正确的 API 格式?</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
如何选择正确的 API 格式?
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
根据目标 API 服务的实际格式选择。大多数第三方 API 代理(如 OpenRouter)都兼容 OpenAI 格式。
|
||||
如果不确定,可以先尝试 OpenAI 格式。
|
||||
@@ -235,14 +274,23 @@ const providerExamples = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="[panelClasses.section, 'overflow-hidden']">
|
||||
<div
|
||||
class="overflow-hidden"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">格式</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">端点路径</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">认证方式</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
格式
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
端点路径
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
认证方式
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -277,7 +325,8 @@ const providerExamples = [
|
||||
<div
|
||||
v-for="example in providerExamples"
|
||||
:key="example.name"
|
||||
:class="[panelClasses.section, 'p-4']"
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ example.name }}
|
||||
@@ -292,7 +341,9 @@ const providerExamples = [
|
||||
<span :class="panelClasses.badgeBlue">{{ example.format }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-[#999]">{{ example.note }}</p>
|
||||
<p class="mt-2 text-xs text-[#999]">
|
||||
{{ example.note }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -303,11 +354,16 @@ const providerExamples = [
|
||||
注意事项
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-4 space-y-3']">
|
||||
<div
|
||||
class="p-4 space-y-3"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<AlertTriangle class="h-5 w-5 text-yellow-500 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">API Key 安全</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
API Key 安全
|
||||
</p>
|
||||
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
端点的 API Key 会被加密存储,但仍建议使用子账号或限定范围的 Key,而非主账号 Key。
|
||||
</p>
|
||||
@@ -317,7 +373,9 @@ const providerExamples = [
|
||||
<div class="flex items-start gap-3">
|
||||
<AlertTriangle class="h-5 w-5 text-yellow-500 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">测试端点</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
测试端点
|
||||
</p>
|
||||
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
添加端点后,可以在健康监控页面测试连通性。如果显示不健康,请检查 URL 和 Key 是否正确。
|
||||
</p>
|
||||
@@ -330,11 +388,16 @@ const providerExamples = [
|
||||
<section class="pt-4">
|
||||
<RouterLink
|
||||
to="/guide/model"
|
||||
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">下一步:模型管理</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">配置模型映射和负载均衡</div>
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
下一步:模型管理
|
||||
</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
配置模型映射和负载均衡
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
|
||||
@@ -65,12 +65,17 @@ const roleComparison = [
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="p-2 rounded-lg bg-blue-500/10">
|
||||
<Users class="h-5 w-5 text-blue-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">普通用户</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
普通用户
|
||||
</h3>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
@@ -88,12 +93,17 @@ const roleComparison = [
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="p-2 rounded-lg bg-orange-500/10">
|
||||
<Shield class="h-5 w-5 text-orange-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">管理员</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
管理员
|
||||
</h3>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
@@ -113,14 +123,23 @@ const roleComparison = [
|
||||
</div>
|
||||
|
||||
<!-- 权限对比表 -->
|
||||
<div :class="[panelClasses.section, 'overflow-hidden']">
|
||||
<div
|
||||
class="overflow-hidden"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">功能</th>
|
||||
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">普通用户</th>
|
||||
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">管理员</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
功能
|
||||
</th>
|
||||
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
普通用户
|
||||
</th>
|
||||
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
管理员
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -133,12 +152,24 @@ const roleComparison = [
|
||||
{{ item.feature }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<Check v-if="item.user" class="h-5 w-5 text-green-500 mx-auto" />
|
||||
<span v-else class="text-[#999]">—</span>
|
||||
<Check
|
||||
v-if="item.user"
|
||||
class="h-5 w-5 text-green-500 mx-auto"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="text-[#999]"
|
||||
>—</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<Check v-if="item.admin" class="h-5 w-5 text-green-500 mx-auto" />
|
||||
<span v-else class="text-[#999]">—</span>
|
||||
<Check
|
||||
v-if="item.admin"
|
||||
class="h-5 w-5 text-green-500 mx-auto"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="text-[#999]"
|
||||
>—</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -153,12 +184,17 @@ const roleComparison = [
|
||||
API Key 管理
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="p-2 rounded-lg bg-orange-500/10">
|
||||
<Key class="h-5 w-5 text-orange-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">什么是 API Key?</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
什么是 API Key?
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<ul class="space-y-3 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
@@ -188,14 +224,23 @@ const roleComparison = [
|
||||
Key 配置选项
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'overflow-hidden']">
|
||||
<div
|
||||
class="overflow-hidden"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">选项</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">说明</th>
|
||||
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">必填</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
选项
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
说明
|
||||
</th>
|
||||
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
必填
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -211,8 +256,14 @@ const roleComparison = [
|
||||
{{ field.description }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<span v-if="field.required" :class="panelClasses.badgeGreen">必填</span>
|
||||
<span v-else class="text-[#999]">可选</span>
|
||||
<span
|
||||
v-if="field.required"
|
||||
:class="panelClasses.badgeGreen"
|
||||
>必填</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-[#999]"
|
||||
>可选</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -227,22 +278,31 @@ const roleComparison = [
|
||||
配额设置
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5 space-y-4']">
|
||||
<div
|
||||
class="p-5 space-y-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<Clock class="h-5 w-5 text-[#cc785c]" />
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">配额类型</h3>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
配额类型
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div class="p-4 rounded-lg bg-[#f5f5f0]/50 dark:bg-[#1f1d1a]/50">
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">请求次数配额</h4>
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
请求次数配额
|
||||
</h4>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
限制每日/每月的 API 调用次数,超过后请求会被拒绝
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 rounded-lg bg-[#f5f5f0]/50 dark:bg-[#1f1d1a]/50">
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">Token 用量配额</h4>
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
Token 用量配额
|
||||
</h4>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
限制每日/每月的 Token 消耗量,适合控制成本
|
||||
</p>
|
||||
@@ -252,7 +312,9 @@ const roleComparison = [
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">配额继承</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
配额继承
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
可以在用户级别设置默认配额,新创建的 Key 会自动继承该配额。
|
||||
也可以在创建 Key 时覆盖默认配额。
|
||||
@@ -268,7 +330,10 @@ const roleComparison = [
|
||||
模型访问控制
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-5']">
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
通过「允许的模型」字段,可以限制 Key 只能访问特定模型:
|
||||
</p>
|
||||
@@ -288,7 +353,9 @@ const roleComparison = [
|
||||
<div class="flex items-start gap-2">
|
||||
<AlertTriangle class="h-5 w-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-yellow-700 dark:text-yellow-300">使用场景</p>
|
||||
<p class="font-medium text-yellow-700 dark:text-yellow-300">
|
||||
使用场景
|
||||
</p>
|
||||
<p class="text-yellow-600 dark:text-yellow-400 mt-1">
|
||||
比如限制免费用户只能使用 gpt-3.5,付费用户可以使用 gpt-4。
|
||||
或者为不同项目创建只能访问特定模型的 Key。
|
||||
@@ -305,11 +372,16 @@ const roleComparison = [
|
||||
安全建议
|
||||
</h2>
|
||||
|
||||
<div :class="[panelClasses.section, 'p-4 space-y-3']">
|
||||
<div
|
||||
class="p-4 space-y-3"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Shield class="h-5 w-5 text-[#cc785c] flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">设置 Key 有效期</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
设置 Key 有效期
|
||||
</p>
|
||||
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
为临时使用的 Key 设置过期时间,避免遗忘造成安全风险。
|
||||
</p>
|
||||
@@ -319,7 +391,9 @@ const roleComparison = [
|
||||
<div class="flex items-start gap-3">
|
||||
<Shield class="h-5 w-5 text-[#cc785c] flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">按场景分配 Key</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
按场景分配 Key
|
||||
</p>
|
||||
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
为开发、测试、生产环境分别创建 Key,便于追踪和管理。
|
||||
</p>
|
||||
@@ -329,7 +403,9 @@ const roleComparison = [
|
||||
<div class="flex items-start gap-3">
|
||||
<Shield class="h-5 w-5 text-[#cc785c] flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">启用 IP 白名单</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
启用 IP 白名单
|
||||
</p>
|
||||
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
对于生产环境的 Key,配置 IP 白名单可以防止 Key 泄露后被滥用。
|
||||
</p>
|
||||
@@ -339,7 +415,9 @@ const roleComparison = [
|
||||
<div class="flex items-start gap-3">
|
||||
<Shield class="h-5 w-5 text-[#cc785c] flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">定期审计</p>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
定期审计
|
||||
</p>
|
||||
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
定期检查用量统计和审计日志,发现异常及时禁用相关 Key。
|
||||
</p>
|
||||
@@ -352,11 +430,16 @@ const roleComparison = [
|
||||
<section class="pt-4">
|
||||
<RouterLink
|
||||
to="/guide/advanced"
|
||||
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">下一步:高级功能</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">格式转换、请求头规则等高级配置</div>
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
下一步:高级功能
|
||||
</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
格式转换、请求头规则等高级配置
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
|
||||
@@ -398,7 +398,10 @@
|
||||
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
统计周期
|
||||
</h3>
|
||||
<TimeRangePicker v-model="dailyTimeRange" :allow-hourly="true" />
|
||||
<TimeRangePicker
|
||||
v-model="dailyTimeRange"
|
||||
:allow-hourly="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 趋势图表区域 -->
|
||||
|
||||
Reference in New Issue
Block a user