mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
refactor(guide): 重构教程文档页面结构和布局
- 将 ProviderGuide/ModelGuide/UserKeyGuide 替换为 ArchitectureGuide/ConceptsGuide/StrategyGuide - GuideLayout 迁移至 AppShell 组件,统一侧边栏导航和移动端菜单 - Overview 页面重写内容结构 - 更新路由配置和导航配置项 - homeGuard 添加 /guide 路径支持
This commit is contained in:
@@ -20,7 +20,7 @@ export function resolveHomeRedirect(
|
||||
|
||||
// 已登录用户如果是从dashboard返回首页、刷新首页、或者有returnTo参数,允许访问首页
|
||||
const isFromApp =
|
||||
from.path.startsWith('/dashboard') || from.path.startsWith('/admin') || from.path === '/'
|
||||
from.path.startsWith('/dashboard') || from.path.startsWith('/admin') || from.path.startsWith('/guide') || from.path === '/'
|
||||
if (to.query.returnTo || isFromApp) {
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -31,21 +31,21 @@ const routes: RouteRecordRaw[] = [
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: 'provider',
|
||||
name: 'GuideProvider',
|
||||
component: () => importWithRetry(() => import('@/views/public/guide/ProviderGuide.vue')),
|
||||
path: 'architecture',
|
||||
name: 'GuideArchitecture',
|
||||
component: () => importWithRetry(() => import('@/views/public/guide/ArchitectureGuide.vue')),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: 'model',
|
||||
name: 'GuideModel',
|
||||
component: () => importWithRetry(() => import('@/views/public/guide/ModelGuide.vue')),
|
||||
path: 'concepts',
|
||||
name: 'GuideConcepts',
|
||||
component: () => importWithRetry(() => import('@/views/public/guide/ConceptsGuide.vue')),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: 'user-key',
|
||||
name: 'GuideUserKey',
|
||||
component: () => importWithRetry(() => import('@/views/public/guide/UserKeyGuide.vue')),
|
||||
path: 'strategy',
|
||||
name: 'GuideStrategy',
|
||||
component: () => importWithRetry(() => import('@/views/public/guide/StrategyGuide.vue')),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
|
||||
466
frontend/src/views/public/guide/ArchitectureGuide.vue
Normal file
466
frontend/src/views/public/guide/ArchitectureGuide.vue
Normal file
@@ -0,0 +1,466 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import {
|
||||
ArrowRight,
|
||||
ChevronRight,
|
||||
Monitor,
|
||||
Shield,
|
||||
Zap,
|
||||
Database,
|
||||
Info,
|
||||
Shuffle
|
||||
} from 'lucide-vue-next'
|
||||
import { panelClasses } from './guide-config'
|
||||
import { useSiteInfo } from '@/composables/useSiteInfo'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
baseUrl?: string
|
||||
}>(),
|
||||
{
|
||||
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
|
||||
}
|
||||
)
|
||||
|
||||
const { siteName } = useSiteInfo()
|
||||
|
||||
// 系统分层
|
||||
const layers = [
|
||||
{
|
||||
name: 'API 层',
|
||||
path: 'api/ + middleware/',
|
||||
description: '协议路由、格式适配、认证鉴权、请求管道'
|
||||
},
|
||||
{
|
||||
name: '编排层',
|
||||
path: 'services/orchestration/',
|
||||
description: '候选排序、请求分发、错误分类、故障转移'
|
||||
},
|
||||
{
|
||||
name: '调度层',
|
||||
path: 'services/scheduling/',
|
||||
description: '候选构建、优先级排序、缓存亲和性、并发检查'
|
||||
},
|
||||
{
|
||||
name: '供应商适配层',
|
||||
path: 'services/provider/',
|
||||
description: '上游 HTTP 调用、认证管理、流式传输'
|
||||
}
|
||||
]
|
||||
|
||||
// 请求处理流程
|
||||
const requestFlow = [
|
||||
{ step: '请求进入', detail: 'ASGI 中间件处理限流、监控、DB Session 管理' },
|
||||
{ step: '路由匹配', detail: '根据请求路径选择对应的格式适配器(Claude/OpenAI/Gemini)' },
|
||||
{ step: '认证鉴权', detail: 'API Key / JWT / Management Token 认证,配额与权限检查' },
|
||||
{ step: '候选构建', detail: '查询可用的 Provider + Endpoint + Key 组合,构建候选列表' },
|
||||
{ step: '候选排序', detail: '按调度模式排序(优先级/全局Key),结合缓存亲和性调整顺序' },
|
||||
{ step: '故障转移执行', detail: '按序尝试候选,失败时根据错误分类决定重试或放弃' },
|
||||
{ step: '上游调用', detail: '构建上游请求,处理流式/非流式响应' },
|
||||
{ step: '响应后处理', detail: '用量统计、计费、缓存亲和性更新、审计记录' }
|
||||
]
|
||||
|
||||
// 数据存储
|
||||
const dataStores = [
|
||||
{
|
||||
name: 'PostgreSQL',
|
||||
role: '主存储',
|
||||
items: ['用户与认证', '供应商/端点/密钥', '模型与路由', '用量与配额', '统计聚合', '审计日志']
|
||||
},
|
||||
{
|
||||
name: 'Redis',
|
||||
role: '缓存与协调',
|
||||
items: ['缓存亲和性 (affinity)', 'RPM 限流计数', '任务协调锁', 'Usage 队列 (Streams)', '通用缓存 (Provider/Model/User)']
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 标题 -->
|
||||
<div class="space-y-3">
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
架构说明
|
||||
</h1>
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094]">
|
||||
{{ siteName }} 的系统架构、请求处理流程和数据流向。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 系统概览 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
系统概览
|
||||
</h2>
|
||||
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center justify-center gap-3 sm:gap-6 flex-wrap text-sm">
|
||||
<div class="text-center">
|
||||
<Monitor class="h-4 w-4 text-[#cc785c] mx-auto mb-1.5" />
|
||||
<div class="font-medium text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
客户端
|
||||
</div>
|
||||
<div class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
SDK / CLI / Web
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChevronRight class="h-4 w-4 text-[#999]" />
|
||||
|
||||
<div class="text-center">
|
||||
<Shield class="h-4 w-4 text-[#cc785c] mx-auto mb-1.5" />
|
||||
<div class="font-medium text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ siteName }}
|
||||
</div>
|
||||
<div class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
认证 / 路由 / 编排
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChevronRight class="h-4 w-4 text-[#999]" />
|
||||
|
||||
<div class="text-center">
|
||||
<Zap class="h-4 w-4 text-[#cc785c] mx-auto mb-1.5" />
|
||||
<div class="font-medium text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
上游供应商
|
||||
</div>
|
||||
<div class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
Claude / OpenAI / Gemini
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center justify-center gap-6 text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Database class="h-3 w-3" />
|
||||
<span>PostgreSQL</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Zap class="h-3 w-3" />
|
||||
<span>Redis</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 分层架构 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
分层架构
|
||||
</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
系统采用严格的分层架构,依赖方向从上到下,禁止反向依赖:
|
||||
</p>
|
||||
|
||||
<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-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094] w-10">
|
||||
#
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
层级
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
路径
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
职责
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(layer, index) in layers"
|
||||
:key="layer.name"
|
||||
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
|
||||
>
|
||||
<td class="px-4 py-2.5 text-[#cc785c] font-bold">
|
||||
{{ index + 1 }}
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
{{ layer.name }}
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
{{ layer.path }}
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
{{ layer.description }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-4 w-4 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>
|
||||
<ul class="mt-1 space-y-1">
|
||||
<li><code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">api/</code> 可以依赖 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">services/</code> 和 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">core/</code></li>
|
||||
<li><code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">services/</code> 可以依赖 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">core/</code>、<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">models/</code>、<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">clients/</code>,禁止反向依赖 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">api/</code></li>
|
||||
<li><code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">core/</code> 保持纯工具与协议,尽量无副作用</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 请求处理流程 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
请求处理流程
|
||||
</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
一个 API 请求从进入到响应的完整处理链路:
|
||||
</p>
|
||||
|
||||
<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-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094] w-10">
|
||||
#
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
步骤
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
说明
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(item, index) in requestFlow"
|
||||
:key="item.step"
|
||||
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
|
||||
>
|
||||
<td class="px-4 py-2.5 text-[#cc785c] font-bold">
|
||||
{{ index + 1 }}
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
{{ item.step }}
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
{{ item.detail }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 格式转换流程 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
格式转换原理
|
||||
</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
当客户端使用的 API 格式与上游端点不同时,系统会自动执行格式转换:
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center justify-center gap-3 sm:gap-6 flex-wrap text-sm">
|
||||
<div class="text-center">
|
||||
<div class="font-medium text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
入站格式
|
||||
</div>
|
||||
<div class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
OpenAI / Claude / Gemini
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChevronRight class="h-4 w-4 text-[#999]" />
|
||||
|
||||
<div class="text-center">
|
||||
<Shuffle class="h-4 w-4 text-[#cc785c] mx-auto mb-1" />
|
||||
<div class="font-medium text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
归一化
|
||||
</div>
|
||||
<div class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
统一内部格式
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChevronRight class="h-4 w-4 text-[#999]" />
|
||||
|
||||
<div class="text-center">
|
||||
<div class="font-medium text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
目标格式
|
||||
</div>
|
||||
<div class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
匹配上游端点
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center text-xs text-[#666663] dark:text-[#a3a094] mt-3">
|
||||
例如:用 OpenAI SDK 发送请求 -> 归一化为内部格式 -> 转换为 Claude API 格式发送给上游
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 数据存储 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
数据存储
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<div
|
||||
v-for="store in dataStores"
|
||||
:key="store.name"
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-2.5 mb-2.5">
|
||||
<Database class="h-4 w-4 text-[#cc785c]" />
|
||||
<div>
|
||||
<h3 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ store.name }}
|
||||
</h3>
|
||||
<span class="text-xs text-[#666663] dark:text-[#a3a094]">{{ store.role }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="space-y-1">
|
||||
<li
|
||||
v-for="item in store.items"
|
||||
:key="item"
|
||||
class="flex items-start gap-2 text-sm text-[#666663] dark:text-[#a3a094]"
|
||||
>
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>{{ item }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-4 w-4 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]">
|
||||
Redis 降级
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
Redis 客户端内置熔断机制:连续 3 次失败触发熔断,60 秒后尝试恢复。开发模式下可完全降级为内存模式运行。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 适配器体系 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
适配器体系
|
||||
</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
{{ siteName }} 通过适配器模式支持多种 API 格式,每种格式有独立的请求构建和响应解析逻辑:
|
||||
</p>
|
||||
|
||||
<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-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
类型
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
基类
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
实现
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)]">
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
聊天 API
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
ChatAdapterBase
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
Claude / OpenAI / Gemini
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)]">
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
CLI 透传
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
CliAdapterBase
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
Claude CLI / OpenAI CLI (Codex) / Gemini CLI
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="last:border-0">
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
视频/图片
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
VideoAdapterBase
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
OpenAI Video / Gemini Video / Gemini Image
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 下一步 -->
|
||||
<section class="pt-2">
|
||||
<RouterLink
|
||||
to="/guide/concepts"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
下一步:相关概念
|
||||
</div>
|
||||
<div class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
深入了解供应商、端点、模型等核心概念
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight class="h-4 w-4 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
408
frontend/src/views/public/guide/ConceptsGuide.vue
Normal file
408
frontend/src/views/public/guide/ConceptsGuide.vue
Normal file
@@ -0,0 +1,408 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import {
|
||||
ArrowRight,
|
||||
Server,
|
||||
Box,
|
||||
Key,
|
||||
Link,
|
||||
Layers,
|
||||
Users,
|
||||
Info,
|
||||
Tag
|
||||
} from 'lucide-vue-next'
|
||||
import { providerExamples, panelClasses } from './guide-config'
|
||||
import { useSiteInfo } from '@/composables/useSiteInfo'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
baseUrl?: string
|
||||
}>(),
|
||||
{
|
||||
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
|
||||
}
|
||||
)
|
||||
|
||||
const { siteName } = useSiteInfo()
|
||||
|
||||
// 核心概念列表
|
||||
const concepts = [
|
||||
{
|
||||
name: '供应商 (Provider)',
|
||||
icon: Server,
|
||||
description: '上游 AI 服务商的逻辑抽象,用于组织和管理多个端点。',
|
||||
details: [
|
||||
'一个供应商代表一个 AI 服务提供商(如 OpenAI、Anthropic)',
|
||||
'供应商下可以有多个端点(不同区域、不同账号)',
|
||||
'供应商级别可以配置格式转换开关',
|
||||
'供应商的优先级影响候选排序'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '端点 (ProviderEndpoint)',
|
||||
icon: Box,
|
||||
description: '实际的 API 配置单元,包含调用上游所需的全部信息。',
|
||||
details: [
|
||||
'包含 URL、API 格式(api_family:endpoint_kind 签名)',
|
||||
'可配置代理、超时、请求头/请求体规则',
|
||||
'端点下挂载一个或多个 API Key(ProviderAPIKey)',
|
||||
'端点是系统实际发起调用的最小单元'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '供应商密钥 (ProviderAPIKey)',
|
||||
icon: Key,
|
||||
description: '端点的鉴权凭据,是调度系统操作的核心对象。',
|
||||
details: [
|
||||
'包含 internal_priority(内部优先级),影响候选排序',
|
||||
'可设置能力标签(如 1M 上下文、1H 缓存支持)',
|
||||
'可限定模型白名单,只允许特定模型使用此 Key',
|
||||
'包含上游配额元信息(RPM 等)'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '候选 (ProviderCandidate)',
|
||||
icon: Link,
|
||||
description: 'Provider + Endpoint + Key 的组合,是调度和故障转移的基本单位。',
|
||||
details: [
|
||||
'每次请求会构建一组候选列表',
|
||||
'候选按调度策略排序后依次尝试',
|
||||
'失败时自动切换到下一个候选(故障转移)',
|
||||
'候选上附带并发检查、缓存亲和性等元数据'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
// 模型相关概念
|
||||
const modelConcepts = [
|
||||
{
|
||||
name: '全局模型 (GlobalModel)',
|
||||
description: '用户请求中使用的模型名定义。用户在 API 调用中指定的 model 参数值必须是已注册的 GlobalModel。',
|
||||
example: 'claude-sonnet-4-20250514、gpt-4o、gemini-2.5-pro'
|
||||
},
|
||||
{
|
||||
name: '模型 (Model)',
|
||||
description: '供应商侧的模型实现。一个 GlobalModel 可以关联到多个 Provider 的 Model,每个 Model 可以映射不同的上游模型名。',
|
||||
example: '用户请求 gpt-4 -> 端点 A 发送 gpt-4-turbo,端点 B 发送 gpt-4o'
|
||||
},
|
||||
{
|
||||
name: '模型别名',
|
||||
description: '一个 GlobalModel 可以设置多个别名,用户可以用任意别名调用。适合版本迁移时保持向后兼容。',
|
||||
example: 'claude-3-5-sonnet -> claude-3.5-sonnet (别名)'
|
||||
}
|
||||
]
|
||||
|
||||
// 用户与密钥
|
||||
const userConcepts = [
|
||||
{
|
||||
name: 'API Key(用户密钥)',
|
||||
description: '用户访问系统的凭证,与上游 ProviderAPIKey 不同。每个 API Key 归属一个用户,可以设置:',
|
||||
features: ['允许访问的模型列表', '请求/Token 配额(日/月)', 'IP 白名单', '有效期', '1H 缓存策略']
|
||||
},
|
||||
{
|
||||
name: '亲和性键 (affinity_key)',
|
||||
description: '缓存亲和性的维度标识,通常取值为用户 API Key ID。系统会尝试为同一个 affinity_key 稳定选择相同的 ProviderAPIKey,以最大化利用上游的 Prompt Caching。',
|
||||
features: []
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 标题 -->
|
||||
<div class="space-y-3">
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
相关概念
|
||||
</h1>
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094]">
|
||||
深入理解 {{ siteName }} 中的核心概念及其关系。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 概念关系 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
概念关系
|
||||
</h2>
|
||||
|
||||
<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-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
关系
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
说明
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)]">
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
供应商 → 端点 <span class="text-[#999] font-normal text-xs ml-1">1:N</span>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
一个供应商包含多个端点(不同区域、不同账号等)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)]">
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
端点 → 供应商密钥 <span class="text-[#999] font-normal text-xs ml-1">1:N</span>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
一个端点下挂载多个 API Key
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)]">
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
全局模型 → 端点 <span class="text-[#999] font-normal text-xs ml-1">N:M</span>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
全局模型与端点是多对多关系,通过 Model 关联
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="last:border-0">
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
用户 API Key → 全局模型 <span class="text-[#999] font-normal text-xs ml-1">N:M</span>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
用户 API Key 可限制可访问的模型列表
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 供应商侧核心概念 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
供应商侧概念
|
||||
</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="concept in concepts"
|
||||
:key="concept.name"
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-2.5 mb-2">
|
||||
<component
|
||||
:is="concept.icon"
|
||||
class="h-4 w-4 text-[#cc785c]"
|
||||
/>
|
||||
<h3 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ concept.name }}
|
||||
</h3>
|
||||
</div>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-2.5">
|
||||
{{ concept.description }}
|
||||
</p>
|
||||
<ul class="space-y-1">
|
||||
<li
|
||||
v-for="detail in concept.details"
|
||||
:key="detail"
|
||||
class="flex items-start gap-2 text-sm text-[#666663] dark:text-[#a3a094]"
|
||||
>
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>{{ detail }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 常见供应商配置 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
常见供应商配置
|
||||
</h2>
|
||||
|
||||
<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-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
供应商
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
URL
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
格式
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
备注
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="example in providerExamples"
|
||||
:key="example.name"
|
||||
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
|
||||
>
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
{{ example.name }}
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
{{ example.url }}
|
||||
</td>
|
||||
<td class="px-4 py-2.5 whitespace-nowrap">
|
||||
<span :class="panelClasses.badgeBlue">{{ example.format }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-xs text-[#999]">
|
||||
{{ example.note }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 模型概念 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
模型体系
|
||||
</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="model in modelConcepts"
|
||||
:key="model.name"
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-2.5 mb-2">
|
||||
<Layers class="h-4 w-4 text-[#cc785c]" />
|
||||
<h3 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ model.name }}
|
||||
</h3>
|
||||
</div>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
{{ model.description }}
|
||||
</p>
|
||||
<div class="mt-2 flex items-center gap-2 text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
<Tag class="h-3 w-3 text-[#999]" />
|
||||
<span>示例: </span>
|
||||
<code class="text-[#262624] dark:text-[#f1ead8] bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">{{ model.example }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型名映射示例 -->
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h3 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8] mb-3">
|
||||
模型名映射示例
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center gap-3 text-sm flex-wrap">
|
||||
<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-2 py-1 rounded text-[#262624] dark:text-[#f1ead8]">用户请求 gpt-4</code>
|
||||
<ArrowRight class="h-3.5 w-3.5 text-[#999]" />
|
||||
<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-2 py-1 rounded text-[#262624] dark:text-[#f1ead8]">端点 A: gpt-4-turbo</code>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 text-sm flex-wrap">
|
||||
<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-2 py-1 rounded text-[#262624] dark:text-[#f1ead8]">用户请求 gpt-4</code>
|
||||
<ArrowRight class="h-3.5 w-3.5 text-[#999]" />
|
||||
<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-2 py-1 rounded text-[#262624] dark:text-[#f1ead8]">端点 B: gpt-4o</code>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-2.5">
|
||||
同一个模型名可以映射到不同端点的不同上游模型名,系统根据调度策略选择端点后使用对应的映射。
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 用户与密钥 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
用户与 API Key
|
||||
</h2>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="concept in userConcepts"
|
||||
:key="concept.name"
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-2.5 mb-2">
|
||||
<Users class="h-4 w-4 text-[#cc785c]" />
|
||||
<h3 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ concept.name }}
|
||||
</h3>
|
||||
</div>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
{{ concept.description }}
|
||||
</p>
|
||||
<ul
|
||||
v-if="concept.features.length > 0"
|
||||
class="mt-2 space-y-1"
|
||||
>
|
||||
<li
|
||||
v-for="feature in concept.features"
|
||||
:key="feature"
|
||||
class="flex items-start gap-2 text-sm text-[#666663] dark:text-[#a3a094]"
|
||||
>
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>{{ feature }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-4 w-4 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]">
|
||||
两种 Key 的区别
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
<strong>用户 API Key</strong>:用户用来访问 {{ siteName }} 的凭证(外部面向用户)。<br>
|
||||
<strong>ProviderAPIKey</strong>:{{ siteName }} 用来调用上游服务的凭证(内部面向供应商)。
|
||||
两者是独立的概念,用户 API Key 的请求会被路由到合适的 ProviderAPIKey。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 下一步 -->
|
||||
<section class="pt-2">
|
||||
<RouterLink
|
||||
to="/guide/strategy"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
下一步:关键策略
|
||||
</div>
|
||||
<div class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
了解调度、缓存亲和性、故障转移等核心策略
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight class="h-4 w-4 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,204 +1,308 @@
|
||||
<template>
|
||||
<div class="min-h-screen literary-grid literary-paper">
|
||||
<!-- Header -->
|
||||
<header class="sticky top-0 z-50 border-b border-[#cc785c]/10 dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/90 dark:bg-[#191714]/95 backdrop-blur-xl transition-all">
|
||||
<div class="h-14 sm:h-16 flex items-center px-3 sm:px-4 md:px-8">
|
||||
<!-- Left: Logo & Brand -->
|
||||
<AppShell
|
||||
:main-class="mainClasses"
|
||||
:sidebar-class="sidebarClasses"
|
||||
:content-class="contentClasses"
|
||||
>
|
||||
<!-- GLOBAL TEXTURE (Paper Noise) -->
|
||||
<div
|
||||
class="absolute inset-0 pointer-events-none z-0 opacity-[0.03] mix-blend-multiply fixed"
|
||||
:style="{ backgroundImage: `url(\"data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E\")` }"
|
||||
/>
|
||||
|
||||
<template #sidebar>
|
||||
<!-- HEADER (Brand) -->
|
||||
<div class="shrink-0 flex items-center px-6 h-20">
|
||||
<RouterLink
|
||||
to="/"
|
||||
class="flex items-center gap-2 sm:gap-3 group/logo cursor-pointer shrink-0"
|
||||
class="flex items-center gap-3 group transition-opacity hover:opacity-80"
|
||||
>
|
||||
<HeaderLogo
|
||||
size="h-7 w-7 sm:h-9 sm:w-9"
|
||||
size="h-9 w-9"
|
||||
class-name="text-[#191919] dark:text-white"
|
||||
/>
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 class="text-base sm:text-lg font-bold text-[#191919] dark:text-white leading-none">
|
||||
<h1 class="text-lg font-bold text-[#191919] dark:text-white leading-none">
|
||||
{{ siteName }}
|
||||
</h1>
|
||||
<span class="text-[9px] sm:text-[10px] text-[#91918d] dark:text-muted-foreground leading-none mt-1 sm:mt-1.5 font-medium tracking-wide">{{ siteSubtitle }}</span>
|
||||
<span class="text-[10px] text-[#91918d] dark:text-muted-foreground leading-none mt-1.5 font-medium tracking-wide">{{ siteSubtitle }}</span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
<!-- Center: Breadcrumb -->
|
||||
<div class="hidden md:flex items-center gap-2 ml-8">
|
||||
<RouterLink
|
||||
to="/guide"
|
||||
class="text-sm text-[#666663] dark:text-muted-foreground hover:text-[#191919] dark:hover:text-white transition"
|
||||
>
|
||||
教程文档
|
||||
</RouterLink>
|
||||
<ChevronRight
|
||||
v-if="currentNavItem && currentNavItem.id !== 'overview'"
|
||||
class="h-4 w-4 text-[#91918d]"
|
||||
/>
|
||||
<span
|
||||
v-if="currentNavItem && currentNavItem.id !== 'overview'"
|
||||
class="text-sm font-medium text-[#191919] dark:text-white"
|
||||
>
|
||||
{{ currentNavItem.name }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Spacer -->
|
||||
<div class="flex-1" />
|
||||
|
||||
<!-- Right: Actions -->
|
||||
<div class="flex items-center gap-1 sm:gap-2 shrink-0">
|
||||
<!-- Mobile menu button -->
|
||||
<button
|
||||
class="md:hidden flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
|
||||
@click="showMobileNav = !showMobileNav"
|
||||
>
|
||||
<Menu class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<!-- Theme Toggle + GitHub Icons -->
|
||||
<div class="flex items-center gap-0.5 sm:gap-1">
|
||||
<button
|
||||
class="flex h-8 w-8 sm:h-9 sm:w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
|
||||
:title="themeMode === 'system' ? '跟随系统' : themeMode === 'dark' ? '深色模式' : '浅色模式'"
|
||||
@click="toggleDarkMode"
|
||||
>
|
||||
<SunMoon
|
||||
v-if="themeMode === 'system'"
|
||||
class="h-3.5 w-3.5 sm:h-4 sm:w-4"
|
||||
/>
|
||||
<Sun
|
||||
v-else-if="themeMode === 'light'"
|
||||
class="h-3.5 w-3.5 sm:h-4 sm:w-4"
|
||||
/>
|
||||
<Moon
|
||||
v-else
|
||||
class="h-3.5 w-3.5 sm:h-4 sm:w-4"
|
||||
/>
|
||||
</button>
|
||||
<a
|
||||
href="https://github.com/fawney19/Aether"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex h-8 w-8 sm:h-9 sm:w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
|
||||
title="GitHub 仓库"
|
||||
>
|
||||
<GithubIcon class="h-3.5 w-3.5 sm:h-4 sm:w-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Mobile Nav Overlay -->
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="showMobileNav"
|
||||
class="fixed inset-0 z-40 bg-black/50 md:hidden"
|
||||
@click="showMobileNav = false"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<!-- Mobile Nav Drawer -->
|
||||
<Transition name="slide-left">
|
||||
<div
|
||||
v-if="showMobileNav"
|
||||
class="fixed left-0 top-14 bottom-0 z-50 w-64 bg-[#fafaf7] dark:bg-[#191714] border-r border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] md:hidden overflow-y-auto"
|
||||
>
|
||||
<nav class="p-4 space-y-1">
|
||||
<RouterLink
|
||||
v-for="item in guideNavItems"
|
||||
:key="item.id"
|
||||
:to="item.path"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors"
|
||||
:class="isActive(item.path)
|
||||
? 'bg-[#cc785c]/10 text-[#cc785c] dark:text-[#d4a27f]'
|
||||
: 'text-[#666663] dark:text-muted-foreground hover:bg-[#f0f0eb] dark:hover:bg-[#262624]'"
|
||||
@click="showMobileNav = false"
|
||||
>
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="h-4 w-4 shrink-0"
|
||||
/>
|
||||
<span class="text-sm font-medium">{{ item.name }}</span>
|
||||
</RouterLink>
|
||||
</nav>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex">
|
||||
<!-- Desktop Sidebar -->
|
||||
<aside class="hidden md:block w-64 shrink-0 border-r border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#191714]/50">
|
||||
<div class="sticky top-16 h-[calc(100vh-4rem)] overflow-y-auto">
|
||||
<nav class="p-4 space-y-1">
|
||||
<RouterLink
|
||||
<!-- NAVIGATION -->
|
||||
<div class="flex-1 overflow-y-auto py-2 scrollbar-none">
|
||||
<nav class="w-full px-3">
|
||||
<div class="space-y-0.5">
|
||||
<template
|
||||
v-for="item in guideNavItems"
|
||||
:key="item.id"
|
||||
:to="item.path"
|
||||
class="flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors group"
|
||||
:class="isActive(item.path)
|
||||
? 'bg-[#cc785c]/10 text-[#cc785c] dark:text-[#d4a27f]'
|
||||
: 'text-[#666663] dark:text-muted-foreground hover:bg-[#f0f0eb] dark:hover:bg-[#262624]'"
|
||||
>
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="h-4 w-4 shrink-0"
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm font-medium">{{ item.name }}</span>
|
||||
<span
|
||||
v-if="item.description"
|
||||
class="text-xs text-[#91918d] dark:text-muted-foreground/70"
|
||||
<RouterLink
|
||||
:to="item.path"
|
||||
class="group relative flex items-center justify-between px-2.5 py-2 rounded-lg transition-all duration-200"
|
||||
:class="[
|
||||
isNavActive(item.path)
|
||||
? 'bg-primary/10 text-primary font-medium'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center gap-2.5">
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="h-4 w-4 transition-colors duration-200"
|
||||
:class="isNavActive(item.path) ? 'text-primary' : 'text-muted-foreground/70 group-hover:text-foreground'"
|
||||
:stroke-width="isNavActive(item.path) ? 2 : 1.75"
|
||||
/>
|
||||
<span class="text-[13px] tracking-tight">{{ item.name }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="isNavActive(item.path)"
|
||||
class="w-1 h-1 rounded-full bg-primary"
|
||||
/>
|
||||
</RouterLink>
|
||||
|
||||
<!-- 快速开始子导航 -->
|
||||
<div
|
||||
v-if="item.id === 'overview' && isNavActive(item.path)"
|
||||
class="ml-7 space-y-0.5 mt-0.5"
|
||||
>
|
||||
<a
|
||||
v-for="sub in overviewSubItems"
|
||||
:key="sub.hash"
|
||||
:href="sub.hash"
|
||||
class="flex items-center gap-2 px-2.5 py-1.5 rounded-md text-[12px] transition-colors"
|
||||
:class="activeHash === sub.hash
|
||||
? 'text-primary font-medium'
|
||||
: 'text-muted-foreground/70 hover:text-foreground hover:bg-muted/30'"
|
||||
@click.prevent="scrollToHash(sub.hash)"
|
||||
>
|
||||
{{ item.description }}
|
||||
</span>
|
||||
<span
|
||||
class="w-1 h-1 rounded-full flex-shrink-0"
|
||||
:class="activeHash === sub.hash ? 'bg-primary' : 'bg-muted-foreground/30'"
|
||||
/>
|
||||
{{ sub.name }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER (Base URL) -->
|
||||
<div class="p-4 border-t border-[#3d3929]/5 dark:border-white/5">
|
||||
<label class="block text-[10px] font-semibold text-muted-foreground/70 uppercase tracking-[0.1em] mb-2">
|
||||
Base URL
|
||||
</label>
|
||||
<input
|
||||
v-model="baseUrl"
|
||||
type="text"
|
||||
class="w-full px-3 py-2 text-sm rounded-lg border border-[#3d3929]/5 dark:border-white/5 bg-white/50 dark:bg-white/5 text-[#191919] dark:text-white placeholder-[#91918d] focus:outline-none focus:ring-2 focus:ring-[#cc785c]/30 transition"
|
||||
placeholder="https://your-aether.com"
|
||||
>
|
||||
<p class="mt-1.5 text-[10px] text-muted-foreground/50">
|
||||
代码示例将使用此 URL
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #header>
|
||||
<!-- Mobile Header -->
|
||||
<header class="lg:hidden fixed top-0 left-0 right-0 z-50 border-b border-[var(--shell-border)] bg-[var(--shell-glass)] backdrop-blur-xl transition-all">
|
||||
<div class="mx-auto max-w-7xl px-6 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<RouterLink
|
||||
to="/"
|
||||
class="flex items-center gap-3 group"
|
||||
>
|
||||
<HeaderLogo
|
||||
size="h-9 w-9"
|
||||
class-name="text-[#191919] dark:text-white"
|
||||
/>
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 class="text-lg font-bold text-[#191919] dark:text-white leading-none">
|
||||
{{ siteName }}
|
||||
</h1>
|
||||
<span class="text-[10px] text-[#91918d] dark:text-muted-foreground leading-none mt-1.5 font-medium tracking-wide">{{ siteSubtitle }}</span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</nav>
|
||||
|
||||
<!-- Base URL Input -->
|
||||
<div class="p-4 border-t border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)]">
|
||||
<label class="block text-xs font-medium text-[#666663] dark:text-muted-foreground mb-2">
|
||||
{{ siteName }} Base URL
|
||||
</label>
|
||||
<input
|
||||
v-model="baseUrl"
|
||||
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>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
class="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
|
||||
:title="themeMode === 'system' ? '跟随系统' : themeMode === 'dark' ? '深色模式' : '浅色模式'"
|
||||
@click="toggleDarkMode"
|
||||
>
|
||||
<SunMoon
|
||||
v-if="themeMode === 'system'"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
<Sun
|
||||
v-else-if="themeMode === 'light'"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
<Moon
|
||||
v-else
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
class="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
|
||||
@click="mobileMenuOpen = !mobileMenuOpen"
|
||||
>
|
||||
<div class="relative w-5 h-5">
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0 rotate-90 scale-75"
|
||||
enter-to-class="opacity-100 rotate-0 scale-100"
|
||||
leave-active-class="transition-all duration-150 ease-in absolute inset-0"
|
||||
leave-from-class="opacity-100 rotate-0 scale-100"
|
||||
leave-to-class="opacity-0 -rotate-90 scale-75"
|
||||
mode="out-in"
|
||||
>
|
||||
<Menu
|
||||
v-if="!mobileMenuOpen"
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
<X
|
||||
v-else
|
||||
class="h-5 w-5"
|
||||
/>
|
||||
</Transition>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Page Content -->
|
||||
<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"
|
||||
/>
|
||||
</RouterView>
|
||||
<!-- Mobile Dropdown Menu -->
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-300 ease-out overflow-hidden"
|
||||
enter-from-class="opacity-0 max-h-0"
|
||||
enter-to-class="opacity-100 max-h-[500px]"
|
||||
leave-active-class="transition-all duration-200 ease-in overflow-hidden"
|
||||
leave-from-class="opacity-100 max-h-[500px]"
|
||||
leave-to-class="opacity-0 max-h-0"
|
||||
>
|
||||
<div
|
||||
v-if="mobileMenuOpen"
|
||||
class="border-t border-[var(--shell-border)] bg-[var(--shell-glass)] backdrop-blur-xl"
|
||||
>
|
||||
<div class="mx-auto max-w-7xl px-6 py-4">
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="group in navigation"
|
||||
:key="group.title"
|
||||
>
|
||||
<div
|
||||
v-if="group.title"
|
||||
class="text-[10px] font-semibold text-[#91918d] dark:text-muted-foreground uppercase tracking-wider mb-2"
|
||||
>
|
||||
{{ group.title }}
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<RouterLink
|
||||
v-for="item in group.items"
|
||||
:key="item.href"
|
||||
:to="item.href"
|
||||
class="flex items-center gap-2.5 px-3 py-2.5 rounded-xl text-sm font-medium transition-all"
|
||||
:class="isNavActive(item.href)
|
||||
? 'bg-[#cc785c]/10 dark:bg-[#cc785c]/20 text-[#cc785c] dark:text-[#d4a27f]'
|
||||
: 'text-[#666663] dark:text-muted-foreground hover:bg-black/5 dark:hover:bg-white/5 hover:text-[#191919] dark:hover:text-white'"
|
||||
@click="mobileMenuOpen = false"
|
||||
>
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="h-4 w-4 shrink-0"
|
||||
/>
|
||||
<span class="truncate">{{ item.name }}</span>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</header>
|
||||
|
||||
<!-- Desktop Page Header -->
|
||||
<header class="hidden lg:flex h-16 px-8 items-center justify-between shrink-0 border-b border-[#3d3929]/5 dark:border-white/5 sticky top-0 z-40 backdrop-blur-md bg-[#faf9f5]/90 dark:bg-[#191714]/90">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<RouterLink
|
||||
to="/guide"
|
||||
class="hover:text-foreground transition-colors"
|
||||
>
|
||||
教程文档
|
||||
</RouterLink>
|
||||
<template v-if="currentNavItem && currentNavItem.id !== 'overview'">
|
||||
<ChevronRight class="w-3 h-3 opacity-50" />
|
||||
<span class="text-foreground font-medium">
|
||||
{{ currentNavItem.name }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
|
||||
:title="themeMode === 'system' ? '跟随系统' : themeMode === 'dark' ? '深色模式' : '浅色模式'"
|
||||
@click="toggleDarkMode"
|
||||
>
|
||||
<SunMoon
|
||||
v-if="themeMode === 'system'"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
<Sun
|
||||
v-else-if="themeMode === 'light'"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
<Moon
|
||||
v-else
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
</button>
|
||||
<a
|
||||
href="https://github.com/fawney19/Aether"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex h-9 w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
|
||||
title="GitHub 仓库"
|
||||
>
|
||||
<GithubIcon class="h-4 w-4" />
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<RouterView
|
||||
v-slot="{ Component }"
|
||||
>
|
||||
<component
|
||||
:is="Component"
|
||||
:base-url="baseUrl"
|
||||
/>
|
||||
</RouterView>
|
||||
</div>
|
||||
</div>
|
||||
</AppShell>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { RouterLink, RouterView, useRoute } from 'vue-router'
|
||||
import {
|
||||
ChevronRight,
|
||||
Menu,
|
||||
Moon,
|
||||
Sun,
|
||||
SunMoon
|
||||
SunMoon,
|
||||
ChevronRight,
|
||||
X
|
||||
} from 'lucide-vue-next'
|
||||
import GithubIcon from '@/components/icons/GithubIcon.vue'
|
||||
import HeaderLogo from '@/components/HeaderLogo.vue'
|
||||
import AppShell from '@/components/layout/AppShell.vue'
|
||||
import { useDarkMode } from '@/composables/useDarkMode'
|
||||
import { useSiteInfo } from '@/composables/useSiteInfo'
|
||||
import { guideNavItems } from './guide-config'
|
||||
@@ -207,57 +311,68 @@ const route = useRoute()
|
||||
const { themeMode, toggleDarkMode } = useDarkMode()
|
||||
const { siteName, siteSubtitle } = useSiteInfo()
|
||||
|
||||
const showMobileNav = ref(false)
|
||||
const mobileMenuOpen = ref(false)
|
||||
const baseUrl = ref(typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com')
|
||||
const activeHash = ref('#production')
|
||||
|
||||
// 快速开始子导航
|
||||
const overviewSubItems = [
|
||||
{ name: '部署', hash: '#production' },
|
||||
{ name: '配置流程', hash: '#config-steps' },
|
||||
{ name: 'API 格式', hash: '#api-formats' },
|
||||
{ name: '推荐帖子', hash: '#recommended-posts' }
|
||||
]
|
||||
|
||||
function scrollToHash(hash: string) {
|
||||
activeHash.value = hash
|
||||
const el = document.querySelector(hash)
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
}
|
||||
|
||||
// 路由变化时关闭移动端菜单
|
||||
watch(() => route.path, () => {
|
||||
mobileMenuOpen.value = false
|
||||
})
|
||||
|
||||
const currentNavItem = computed(() => {
|
||||
return guideNavItems.find(item => item.path === route.path)
|
||||
})
|
||||
|
||||
function isActive(path: string): boolean {
|
||||
if (path === '/guide') {
|
||||
function isNavActive(href: string) {
|
||||
if (href === '/guide') {
|
||||
return route.path === '/guide'
|
||||
}
|
||||
return route.path.startsWith(path)
|
||||
return route.path === href || route.path.startsWith(`${href}/`)
|
||||
}
|
||||
|
||||
// 移动端菜单用的导航数据
|
||||
const navigation = computed(() => [
|
||||
{
|
||||
items: guideNavItems.map(item => ({
|
||||
name: item.name,
|
||||
href: item.path,
|
||||
icon: item.icon
|
||||
}))
|
||||
}
|
||||
])
|
||||
|
||||
// 样式类 - 与 MainLayout 保持一致
|
||||
const sidebarClasses = computed(() => {
|
||||
return 'w-[260px] flex flex-col hidden lg:flex border-r border-[#3d3929]/5 dark:border-white/5 bg-[#faf9f5] dark:bg-[#1e1c19] h-screen sticky top-0'
|
||||
})
|
||||
|
||||
const contentClasses = computed(() => {
|
||||
return 'flex-1 min-w-0 bg-[#faf9f5] dark:bg-[#191714] text-[#3d3929] dark:text-[#d4a27f]'
|
||||
})
|
||||
|
||||
const mainClasses = computed(() => {
|
||||
return 'pt-24 lg:pt-6'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Typography */
|
||||
h1, h2, h3 {
|
||||
font-family: var(--serif);
|
||||
letter-spacing: -0.02em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: var(--serif);
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
button, nav, a, .inline-flex, input, label {
|
||||
font-family: var(--sans-serif);
|
||||
}
|
||||
|
||||
/* Transitions */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.slide-left-enter-active,
|
||||
.slide-left-leave-active {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.slide-left-enter-from,
|
||||
.slide-left-leave-to {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
.scrollbar-none::-webkit-scrollbar { display: none; }
|
||||
.scrollbar-none { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
</style>
|
||||
|
||||
@@ -1,374 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { ArrowRight, Layers, Check, Info, Shuffle, TrendingUp, Gauge, Clock } from 'lucide-vue-next'
|
||||
import { loadBalanceModes, panelClasses } from './guide-config'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
baseUrl?: string
|
||||
}>(),
|
||||
{
|
||||
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
|
||||
}
|
||||
)
|
||||
|
||||
// 模型配置字段
|
||||
const modelFields = [
|
||||
{ name: '模型名称', description: '用户调用时使用的模型名,如 gpt-4、claude-3', required: true },
|
||||
{ name: '别名', description: '模型的其他名称,用户可以用别名调用', required: false },
|
||||
{ name: '关联端点', description: '该模型使用的端点列表,可多选', required: true },
|
||||
{ name: '负载均衡模式', description: '多端点时的请求分发策略', required: false },
|
||||
{ name: '目标模型名', description: '发送给端点的实际模型名(如端点模型名不同)', required: false },
|
||||
{ name: '是否启用', description: '禁用后用户无法使用该模型', required: false }
|
||||
]
|
||||
|
||||
// 负载均衡图标
|
||||
const lbIcons = {
|
||||
priority: TrendingUp,
|
||||
random: Shuffle,
|
||||
round_robin: Clock,
|
||||
weighted: Gauge,
|
||||
latency: Clock
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 标题 -->
|
||||
<div class="space-y-4">
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
模型管理
|
||||
</h1>
|
||||
<p class="text-lg text-[#666663] dark:text-[#a3a094]">
|
||||
模型是用户实际调用的对象。通过模型配置,你可以定义模型名称、关联端点、配置负载均衡策略。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 模型概念 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
什么是模型?
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<ul class="space-y-3 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>模型是用户在请求中指定的 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">model</code> 参数值</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>一个模型可以关联多个端点,实现负载均衡和故障转移</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>可以设置别名,让用户用多个名称访问同一个模型</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>支持模型名映射,请求中的模型名可以与发给端点的不同</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<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="mt-1">
|
||||
用户请求模型 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">gpt-4</code>,
|
||||
你可以配置它同时使用 OpenAI 官方端点和某个代理端点。
|
||||
系统会根据负载均衡策略选择一个端点,如果失败则自动尝试其他端点。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 创建模型 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
创建模型
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
在管理后台左侧菜单点击「模型管理」
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
2
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
添加模型
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
点击「添加模型」,填写模型名称(这是用户调用时使用的名称)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
3
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
关联端点
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
选择该模型要使用的端点,可以选择多个。如果端点的模型名与你定义的不同,需要设置目标模型名映射。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
4
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
配置负载均衡
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
如果选择了多个端点,可以配置负载均衡策略
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 模型配置字段 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
配置字段说明
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="field in modelFields"
|
||||
:key="field.name"
|
||||
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
|
||||
>
|
||||
<td class="px-4 py-3 font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ field.name }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
|
||||
{{ 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>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 负载均衡模式 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
负载均衡模式
|
||||
</h2>
|
||||
<p class="text-[#666663] dark:text-[#a3a094]">
|
||||
当模型关联多个端点时,系统会根据负载均衡模式选择端点:
|
||||
</p>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div
|
||||
v-for="lb in loadBalanceModes"
|
||||
:key="lb.mode"
|
||||
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>
|
||||
</div>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
{{ lb.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="mt-1">
|
||||
<strong>主备切换场景</strong>:使用「优先级」模式,将官方端点设为高优先级,代理端点设为低优先级。<br>
|
||||
<strong>分摊负载场景</strong>:使用「轮询」或「加权」模式,将多个同质端点平均分配请求。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 模型名映射 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
模型名映射
|
||||
</h2>
|
||||
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
有时候你希望用户调用的模型名与实际发给端点的模型名不同。例如:
|
||||
</p>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center gap-4 text-sm">
|
||||
<div class="px-3 py-2 rounded-lg bg-purple-500/10 border border-purple-500/20">
|
||||
<span class="text-purple-600 dark:text-purple-400">用户请求</span>
|
||||
<code class="ml-2 text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">gpt-4</code>
|
||||
</div>
|
||||
<ArrowRight class="h-4 w-4 text-[#999]" />
|
||||
<div class="px-3 py-2 rounded-lg bg-green-500/10 border border-green-500/20">
|
||||
<span class="text-green-600 dark:text-green-400">发给端点</span>
|
||||
<code class="ml-2 text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">gpt-4-turbo-preview</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 text-sm">
|
||||
<div class="px-3 py-2 rounded-lg bg-purple-500/10 border border-purple-500/20">
|
||||
<span class="text-purple-600 dark:text-purple-400">用户请求</span>
|
||||
<code class="ml-2 text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">claude-3</code>
|
||||
</div>
|
||||
<ArrowRight class="h-4 w-4 text-[#999]" />
|
||||
<div class="px-3 py-2 rounded-lg bg-green-500/10 border border-green-500/20">
|
||||
<span class="text-green-600 dark:text-green-400">发给端点</span>
|
||||
<code class="ml-2 text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">claude-3-opus-20240229</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-4">
|
||||
在模型的端点关联配置中,可以为每个端点设置「目标模型名」来实现这种映射。
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 别名功能 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
模型别名
|
||||
</h2>
|
||||
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
别名让用户可以用多个名称访问同一个模型。适用于:
|
||||
</p>
|
||||
|
||||
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>兼容旧的模型名(如 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">gpt-4-turbo</code> 和 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">gpt-4-turbo-preview</code> 指向同一个模型)</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>提供简短易记的名称(如用 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">claude</code> 代替完整版本号)</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>版本迁移时保持向后兼容</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 下一步 -->
|
||||
<section class="pt-4">
|
||||
<RouterLink
|
||||
to="/guide/user-key"
|
||||
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>
|
||||
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,409 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { ArrowRight, Server, Settings, Check, AlertTriangle, Info } from 'lucide-vue-next'
|
||||
import { apiFormats, panelClasses } from './guide-config'
|
||||
import { useSiteInfo } from '@/composables/useSiteInfo'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
baseUrl?: string
|
||||
}>(),
|
||||
{
|
||||
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
|
||||
}
|
||||
)
|
||||
|
||||
const { siteName } = useSiteInfo()
|
||||
|
||||
// 端点配置字段说明
|
||||
const endpointFields = [
|
||||
{ name: '名称', description: '端点的显示名称,用于区分不同的端点', required: true },
|
||||
{ name: 'URL', description: 'API 的基础 URL,如 https://api.openai.com', required: true },
|
||||
{ name: 'API Key', description: '调用该 API 需要的密钥', required: true },
|
||||
{ name: 'API 格式', description: '该端点支持的 API 格式(OpenAI/Claude/Gemini 等)', required: true },
|
||||
{ name: '优先级', description: '数字越大优先级越高,用于负载均衡', required: false },
|
||||
{ name: '权重', description: '加权负载均衡时使用的权重值', required: false },
|
||||
{ name: '代理', description: '如需通过代理访问,填写代理地址', required: false },
|
||||
{ name: '超时', description: '请求超时时间(秒)', required: false }
|
||||
]
|
||||
|
||||
// 常见供应商配置示例
|
||||
const providerExamples = [
|
||||
{
|
||||
name: 'OpenAI',
|
||||
url: 'https://api.openai.com',
|
||||
format: 'OpenAI Chat',
|
||||
note: '官方 API,需要国际信用卡或通过代理访问'
|
||||
},
|
||||
{
|
||||
name: 'Anthropic',
|
||||
url: 'https://api.anthropic.com',
|
||||
format: 'Claude Chat',
|
||||
note: '官方 Claude API'
|
||||
},
|
||||
{
|
||||
name: 'Google AI',
|
||||
url: 'https://generativelanguage.googleapis.com',
|
||||
format: 'Gemini Chat',
|
||||
note: '官方 Gemini API'
|
||||
},
|
||||
{
|
||||
name: 'Azure OpenAI',
|
||||
url: 'https://{resource}.openai.azure.com',
|
||||
format: 'OpenAI Chat',
|
||||
note: '需要替换 {resource} 为你的资源名'
|
||||
},
|
||||
{
|
||||
name: 'OpenRouter',
|
||||
url: 'https://openrouter.ai/api',
|
||||
format: 'OpenAI Chat',
|
||||
note: '聚合多家供应商的 API 代理'
|
||||
},
|
||||
{
|
||||
name: '自托管 / 其他',
|
||||
url: 'https://your-api.com',
|
||||
format: 'OpenAI Chat',
|
||||
note: '大多数 OpenAI 兼容服务选择 OpenAI Chat 格式'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 标题 -->
|
||||
<div class="space-y-4">
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
供应商管理
|
||||
</h1>
|
||||
<p class="text-lg text-[#666663] dark:text-[#a3a094]">
|
||||
供应商和端点是 {{ siteName }} 的基础配置,决定了系统可以调用哪些 AI 服务。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 概念说明 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
供应商 vs 端点
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<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>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>逻辑分组,用于组织管理多个端点</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>一个供应商可以有多个端点</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>例如:OpenAI 供应商下可以有官方端点和多个代理端点</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>实际的 API 配置单元</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>包含 URL、密钥、API 格式等信息</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>系统实际调用的是端点,而非供应商</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 添加供应商步骤 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
添加供应商
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
登录管理后台,在左侧菜单点击「供应商管理」
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
2
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
创建供应商
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
点击「添加供应商」按钮,填写供应商名称(如 OpenAI、Anthropic)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
3
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
添加端点
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
在供应商下点击「添加端点」,填写 API URL、密钥等配置
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 端点配置字段 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
端点配置字段
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="field in endpointFields"
|
||||
:key="field.name"
|
||||
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
|
||||
>
|
||||
<td class="px-4 py-3 font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ field.name }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
|
||||
{{ 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>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- API 格式选择 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
API 格式选择
|
||||
</h2>
|
||||
|
||||
<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="mt-1">
|
||||
根据目标 API 服务的实际格式选择。大多数第三方 API 代理(如 OpenRouter)都兼容 OpenAI 格式。
|
||||
如果不确定,可以先尝试 OpenAI 格式。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="format in apiFormats"
|
||||
:key="format.name"
|
||||
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
|
||||
>
|
||||
<td class="px-4 py-3 font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ format.name }}
|
||||
</td>
|
||||
<td class="px-4 py-3 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
{{ format.endpoint }}
|
||||
</td>
|
||||
<td class="px-4 py-3 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
{{ format.auth }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 常见供应商示例 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
常见供应商配置
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div
|
||||
v-for="example in providerExamples"
|
||||
:key="example.name"
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ example.name }}
|
||||
</h3>
|
||||
<div class="mt-2 space-y-1 text-sm">
|
||||
<div>
|
||||
<span class="text-[#666663] dark:text-[#a3a094]">URL: </span>
|
||||
<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">{{ example.url }}</code>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-[#666663] dark:text-[#a3a094]">格式: </span>
|
||||
<span :class="panelClasses.badgeBlue">{{ example.format }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-[#999]">
|
||||
{{ example.note }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 注意事项 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
注意事项
|
||||
</h2>
|
||||
|
||||
<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="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
端点的 API Key 会被加密存储,但仍建议使用子账号或限定范围的 Key,而非主账号 Key。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
添加端点后,可以在健康监控页面测试连通性。如果显示不健康,请检查 URL 和 Key 是否正确。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 下一步 -->
|
||||
<section class="pt-4">
|
||||
<RouterLink
|
||||
to="/guide/model"
|
||||
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>
|
||||
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
610
frontend/src/views/public/guide/StrategyGuide.vue
Normal file
610
frontend/src/views/public/guide/StrategyGuide.vue
Normal file
@@ -0,0 +1,610 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import {
|
||||
ArrowRight,
|
||||
TrendingUp,
|
||||
Hash,
|
||||
Info,
|
||||
Activity
|
||||
} from 'lucide-vue-next'
|
||||
import { panelClasses } from './guide-config'
|
||||
import { useSiteInfo } from '@/composables/useSiteInfo'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
baseUrl?: string
|
||||
}>(),
|
||||
{
|
||||
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
|
||||
}
|
||||
)
|
||||
|
||||
const { siteName } = useSiteInfo()
|
||||
|
||||
// 调度模式
|
||||
const schedulingModes = [
|
||||
{
|
||||
name: '提供商优先',
|
||||
description: '按 Provider 优先级排序,同优先级内按 Key 优先级排序,相同优先级哈希分散。适合优先使用特定供应商。',
|
||||
icon: TrendingUp,
|
||||
badge: '默认'
|
||||
},
|
||||
{
|
||||
name: '全局 Key 优先',
|
||||
description: '忽略 Provider 层级,所有 Key 按全局优先级统一排序,相同优先级哈希分散。适合跨 Provider 统一调度,最大化利用所有 Key。',
|
||||
icon: Hash,
|
||||
badge: ''
|
||||
}
|
||||
]
|
||||
|
||||
// 错误分类
|
||||
const errorCategories = [
|
||||
{
|
||||
type: 'RETRYABLE',
|
||||
name: '可重试',
|
||||
description: '超时、限流 (429)、5xx 服务端错误',
|
||||
action: 'FailoverEngine 尝试下一个候选'
|
||||
},
|
||||
{
|
||||
type: 'FATAL',
|
||||
name: '致命',
|
||||
description: '认证失败、模型不存在',
|
||||
action: '立即返回错误,不再重试'
|
||||
},
|
||||
{
|
||||
type: 'CLIENT',
|
||||
name: '客户端',
|
||||
description: '400 系列客户端错误',
|
||||
action: '透传原始错误给调用方'
|
||||
}
|
||||
]
|
||||
|
||||
// 错误处理副作用
|
||||
const errorSideEffects = [
|
||||
{ name: '缓存失效', description: '通过 CacheAwareScheduler 失效亲和性缓存' },
|
||||
{ name: '健康记录', description: '通过 health_monitor 记录失败事件' },
|
||||
{ name: 'RPM 调整', description: '429 错误时自适应调整 RPM 限制' },
|
||||
{ name: 'OAuth 标记', description: '403 VALIDATION_REQUIRED 时标记 Key 为账号封禁' }
|
||||
]
|
||||
|
||||
// 格式转换三层开关
|
||||
const conversionLayers = [
|
||||
{
|
||||
level: '全局',
|
||||
setting: 'enable_format_conversion',
|
||||
description: '系统设置中的全局开关。开启后允许所有跨格式路由。'
|
||||
},
|
||||
{
|
||||
level: 'Provider',
|
||||
setting: 'Provider.enable_format_conversion',
|
||||
description: '全局关闭时,可在 Provider 级别单独开启格式转换。'
|
||||
},
|
||||
{
|
||||
level: 'Endpoint',
|
||||
setting: 'format_acceptance_config',
|
||||
description: '以上两级都关闭时,通过端点的 accept/reject 规则精细控制接受哪些入站格式。'
|
||||
}
|
||||
]
|
||||
|
||||
// 配额类型
|
||||
const quotaTypes = [
|
||||
{
|
||||
name: '请求次数配额',
|
||||
description: '限制每日/每月的 API 调用次数,超过后请求会被拒绝',
|
||||
scope: '用户级 / Key 级'
|
||||
},
|
||||
{
|
||||
name: 'Token 用量配额',
|
||||
description: '限制每日/每月的 Token 消耗量,适合控制成本',
|
||||
scope: '用户级 / Key 级'
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 标题 -->
|
||||
<div class="space-y-3">
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
关键策略
|
||||
</h1>
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094]">
|
||||
{{ siteName }} 的调度、缓存亲和性、故障转移、并发控制等核心策略机制。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 调度策略 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
调度策略
|
||||
</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
当一个模型关联了多个端点和密钥时,系统需要决定使用哪个 Provider + Endpoint + Key 组合。调度模式决定了候选的排序方式:
|
||||
</p>
|
||||
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<div
|
||||
v-for="mode in schedulingModes"
|
||||
:key="mode.name"
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-2.5 mb-2">
|
||||
<component
|
||||
:is="mode.icon"
|
||||
class="h-4 w-4 text-[#cc785c]"
|
||||
/>
|
||||
<h3 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ mode.name }}
|
||||
</h3>
|
||||
<span
|
||||
v-if="mode.badge"
|
||||
:class="panelClasses.badge"
|
||||
>{{ mode.badge }}</span>
|
||||
</div>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
{{ mode.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-4 w-4 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="mt-1">
|
||||
当多个候选具有相同优先级时,系统使用哈希算法将请求均匀分散到各个候选上,避免所有请求集中到同一个 Key。
|
||||
哈希因子结合了 affinity_key(通常为用户 API Key ID),使得同一用户的请求倾向于命中相同的候选。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 缓存亲和性 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
缓存亲和性
|
||||
</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
缓存亲和性是 {{ siteName }} 的核心优化策略,用于最大化利用上游供应商的 Prompt Caching 机制。
|
||||
</p>
|
||||
|
||||
<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-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094] w-10">
|
||||
#
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
步骤
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
说明
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)]">
|
||||
<td class="px-4 py-2.5 text-[#cc785c] font-bold">
|
||||
1
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
提取亲和性键
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
用户发起请求时,系统提取 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">affinity_key</code>(通常为用户 API Key ID)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)]">
|
||||
<td class="px-4 py-2.5 text-[#cc785c] font-bold">
|
||||
2
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
查询缓存
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
查找该 affinity_key 上次成功使用的 ProviderAPIKey(从 Redis 缓存读取)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)]">
|
||||
<td class="px-4 py-2.5 text-[#cc785c] font-bold">
|
||||
3
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
候选置顶
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
如果找到且该 Key 仍可用,将其置顶到候选列表最前面
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="last:border-0">
|
||||
<td class="px-4 py-2.5 text-[#cc785c] font-bold">
|
||||
4
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
更新记录
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
请求成功后,更新缓存记录。请求失败时,失效该缓存条目
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h4 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8] mb-2">
|
||||
为什么重要?
|
||||
</h4>
|
||||
<ul class="space-y-1 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>Claude/OpenAI 等供应商支持 Prompt Caching,重复前缀可免费/降价</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>同一用户的连续请求通常有大量重复上下文</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>稳定路由到同一个 Key 可以最大化缓存命中率</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h4 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8] mb-2">
|
||||
缓存层级
|
||||
</h4>
|
||||
<ul class="space-y-1 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span><strong>L1 内存缓存</strong>:3 秒 TTL,减少 Redis 访问</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span><strong>L2 Redis 缓存</strong>:持久化亲和性映射</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span><strong>降级</strong>:Redis 不可用时禁用亲和性,使用普通排序</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 故障转移 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
故障转移
|
||||
</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
当某个候选失败时,系统会根据错误类型决定是否尝试下一个候选。错误分类由 ErrorClassifier 负责:
|
||||
</p>
|
||||
|
||||
<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-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
类型
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
触发条件
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
处理方式
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="error in errorCategories"
|
||||
:key="error.type"
|
||||
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
|
||||
>
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">{{ error.type }}</code>
|
||||
<span class="ml-1.5 text-[#666663] dark:text-[#a3a094] font-normal text-xs">{{ error.name }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
{{ error.description }}
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
{{ error.action }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 错误处理副作用 -->
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h3 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8] mb-2">
|
||||
错误处理副作用
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-2.5">
|
||||
错误分类后,ErrorHandlerService 负责执行副作用操作:
|
||||
</p>
|
||||
<div class="grid gap-2 sm:grid-cols-2">
|
||||
<div
|
||||
v-for="effect in errorSideEffects"
|
||||
:key="effect.name"
|
||||
class="p-3 rounded-lg bg-[#f5f5f0]/50 dark:bg-[#1f1d1a]/50"
|
||||
>
|
||||
<h4 class="font-medium text-xs text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ effect.name }}
|
||||
</h4>
|
||||
<p class="text-xs text-[#666663] dark:text-[#a3a094] mt-0.5">
|
||||
{{ effect.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 并发控制 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
并发控制
|
||||
</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
系统通过多层并发控制保护上游服务不被过载:
|
||||
</p>
|
||||
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-2.5 mb-2">
|
||||
<Activity class="h-4 w-4 text-[#cc785c]" />
|
||||
<h3 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
RPM 限流
|
||||
</h3>
|
||||
</div>
|
||||
<ul class="space-y-1 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>按 ProviderAPIKey 维度限制每分钟请求数</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>超过 RPM 限制的候选在构建阶段被跳过</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>429 错误时自适应降低 RPM 限制</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-2.5 mb-2">
|
||||
<Activity class="h-4 w-4 text-[#cc785c]" />
|
||||
<h3 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
动态预留
|
||||
</h3>
|
||||
</div>
|
||||
<ul class="space-y-1 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>为缓存亲和性用户预留一定的 RPM 额度</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>避免非缓存用户占满配额导致缓存用户无法命中</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>预留比例根据负载情况自适应调整</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 格式转换开关 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
格式转换控制
|
||||
</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
格式转换(跨格式路由)由三层开关从高到低控制:
|
||||
</p>
|
||||
|
||||
<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-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094] w-10">
|
||||
#
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
层级
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
配置项
|
||||
</th>
|
||||
<th class="px-4 py-2.5 text-left font-medium text-[#666663] dark:text-[#a3a094]">
|
||||
说明
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(layer, index) in conversionLayers"
|
||||
:key="layer.level"
|
||||
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
|
||||
>
|
||||
<td class="px-4 py-2.5 text-[#cc785c] font-bold">
|
||||
{{ index + 1 }}
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-medium text-[#262624] dark:text-[#f1ead8] whitespace-nowrap">
|
||||
{{ layer.level }}
|
||||
</td>
|
||||
<td class="px-4 py-2.5 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
{{ layer.setting }}
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-[#666663] dark:text-[#a3a094]">
|
||||
{{ layer.description }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-4 w-4 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="mt-1">
|
||||
需要格式转换的候选默认会被排到列表后面(降级排序)。通过全局 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">keep_priority_on_conversion</code>
|
||||
或 Provider 级别的同名配置,可以保持原有优先级不降级。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 配额管理 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
配额管理
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-3 md:grid-cols-2">
|
||||
<div
|
||||
v-for="quota in quotaTypes"
|
||||
:key="quota.name"
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h3 class="font-semibold text-sm text-[#262624] dark:text-[#f1ead8] mb-1.5">
|
||||
{{ quota.name }}
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
{{ quota.description }}
|
||||
</p>
|
||||
<div class="mt-2">
|
||||
<span :class="panelClasses.badgeBlue">{{ quota.scope }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<Info class="h-4 w-4 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="mt-1">
|
||||
可以在用户级别设置默认配额,新创建的 API Key 会自动继承。也可以在创建 Key 时覆盖默认配额。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 健康监控 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
健康监控
|
||||
</h2>
|
||||
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<ul class="space-y-1.5 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>系统持续记录每个端点的成功/失败请求</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>连续失败超过阈值的端点会被标记为不健康</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>不健康的端点在候选构建时被跳过,请求自动路由到其他端点</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>定期对不健康端点进行探测恢复</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<span class="text-[#cc785c] mt-1.5 flex-shrink-0 text-[6px]">●</span>
|
||||
<span>可以在管理后台的「健康监控」页面查看实时状态并手动触发检查</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 下一步 -->
|
||||
<section class="pt-2">
|
||||
<RouterLink
|
||||
to="/guide/advanced"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
下一步:高级功能
|
||||
</div>
|
||||
<div class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
格式转换操作、请求头/请求体规则、系统设置
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight class="h-4 w-4 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,443 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { ArrowRight, Users, Key, Shield, Check, Info, AlertTriangle, Clock } from 'lucide-vue-next'
|
||||
import { panelClasses } from './guide-config'
|
||||
import { useSiteInfo } from '@/composables/useSiteInfo'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
baseUrl?: string
|
||||
}>(),
|
||||
{
|
||||
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
|
||||
}
|
||||
)
|
||||
|
||||
const { siteName } = useSiteInfo()
|
||||
|
||||
// API Key 字段说明
|
||||
const keyFields = [
|
||||
{ name: '名称', description: 'Key 的描述性名称,方便识别', required: true },
|
||||
{ name: '所属用户', description: 'Key 归属的用户', required: true },
|
||||
{ name: '有效期', description: '过期时间,不设置则永久有效', required: false },
|
||||
{ name: '允许的模型', description: '限制该 Key 可访问的模型列表,不设置则可访问所有模型', required: false },
|
||||
{ name: '请求配额', description: '每日/每月的请求次数限制', required: false },
|
||||
{ name: 'Token 配额', description: '每日/每月的 Token 用量限制', required: false },
|
||||
{ name: 'IP 白名单', description: '限制只有特定 IP 可以使用该 Key', required: false },
|
||||
{ name: '状态', description: '启用/禁用该 Key', required: false }
|
||||
]
|
||||
|
||||
// 权限对比
|
||||
const roleComparison = [
|
||||
{ feature: '查看仪表盘', user: true, admin: true },
|
||||
{ feature: '使用 API', user: true, admin: true },
|
||||
{ feature: '管理自己的 API Key', user: true, admin: true },
|
||||
{ feature: '查看用量统计', user: true, admin: true },
|
||||
{ feature: '管理供应商/端点', user: false, admin: true },
|
||||
{ feature: '管理模型', user: false, admin: true },
|
||||
{ feature: '管理其他用户', user: false, admin: true },
|
||||
{ feature: '管理其他用户的 Key', user: false, admin: true },
|
||||
{ feature: '系统设置', user: false, admin: true }
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 标题 -->
|
||||
<div class="space-y-4">
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
用户与密钥
|
||||
</h1>
|
||||
<p class="text-lg text-[#666663] dark:text-[#a3a094]">
|
||||
用户和 API Key 是 {{ siteName }} 的访问控制核心。通过用户管理分配角色权限,通过 Key 管理控制 API 访问。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 用户管理 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
用户管理
|
||||
</h2>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<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>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>可以使用 API 调用模型</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>管理自己的 API Key</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>查看自己的用量统计</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>管理供应商、端点、模型</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>管理所有用户和 Key</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>系统设置和监控</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 权限对比表 -->
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="item in roleComparison"
|
||||
:key="item.feature"
|
||||
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
|
||||
>
|
||||
<td class="px-4 py-3 text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ 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>
|
||||
</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>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- API Key 管理 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
API Key 管理
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<ul class="space-y-3 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>API Key 是用户调用 {{ siteName }} API 的凭证</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>一个用户可以有多个 Key,用于不同场景(如开发、生产、测试)</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>可以为 Key 设置细粒度的权限和配额限制</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span>Key 的用量会记录到所属用户</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Key 配置字段 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
Key 配置选项
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="field in keyFields"
|
||||
:key="field.name"
|
||||
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
|
||||
>
|
||||
<td class="px-4 py-3 font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ field.name }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
|
||||
{{ 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>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 配额设置 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
配额设置
|
||||
</h2>
|
||||
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
限制每日/每月的 Token 消耗量,适合控制成本
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="mt-1">
|
||||
可以在用户级别设置默认配额,新创建的 Key 会自动继承该配额。
|
||||
也可以在创建 Key 时覆盖默认配额。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 模型访问控制 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
模型访问控制
|
||||
</h2>
|
||||
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
通过「允许的模型」字段,可以限制 Key 只能访问特定模型:
|
||||
</p>
|
||||
|
||||
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span><strong>不设置</strong>:Key 可以访问所有启用的模型</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-2">
|
||||
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span><strong>设置模型列表</strong>:Key 只能访问列表中的模型</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="mt-4 p-4 rounded-lg bg-yellow-500/10 border border-yellow-500/20">
|
||||
<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="text-yellow-600 dark:text-yellow-400 mt-1">
|
||||
比如限制免费用户只能使用 gpt-3.5,付费用户可以使用 gpt-4。
|
||||
或者为不同项目创建只能访问特定模型的 Key。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 安全建议 -->
|
||||
<section class="space-y-4">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
安全建议
|
||||
</h2>
|
||||
|
||||
<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="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
为临时使用的 Key 设置过期时间,避免遗忘造成安全风险。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
为开发、测试、生产环境分别创建 Key,便于追踪和管理。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
对于生产环境的 Key,配置 IP 白名单可以防止 Key 泄露后被滥用。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
定期检查用量统计和审计日志,发现异常及时禁用相关 Key。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 下一步 -->
|
||||
<section class="pt-4">
|
||||
<RouterLink
|
||||
to="/guide/advanced"
|
||||
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>
|
||||
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Component } from 'vue'
|
||||
import {
|
||||
Rocket,
|
||||
Network,
|
||||
BookOpen,
|
||||
Server,
|
||||
Layers,
|
||||
Users,
|
||||
Target,
|
||||
Settings,
|
||||
HelpCircle
|
||||
} from 'lucide-vue-next'
|
||||
@@ -20,38 +20,38 @@ export interface GuideNavItem {
|
||||
export const guideNavItems: GuideNavItem[] = [
|
||||
{
|
||||
id: 'overview',
|
||||
name: '概览',
|
||||
name: '快速开始',
|
||||
path: '/guide',
|
||||
icon: Rocket,
|
||||
description: '部署后的配置指南'
|
||||
},
|
||||
{
|
||||
id: 'architecture',
|
||||
name: '架构说明',
|
||||
path: '/guide/architecture',
|
||||
icon: Network,
|
||||
description: '系统架构与请求流程'
|
||||
},
|
||||
{
|
||||
id: 'concepts',
|
||||
name: '相关概念',
|
||||
path: '/guide/concepts',
|
||||
icon: BookOpen,
|
||||
description: '系统架构与核心概念'
|
||||
description: '核心概念深入解释'
|
||||
},
|
||||
{
|
||||
id: 'provider',
|
||||
name: '供应商管理',
|
||||
path: '/guide/provider',
|
||||
icon: Server,
|
||||
description: '添加供应商与端点配置'
|
||||
},
|
||||
{
|
||||
id: 'model',
|
||||
name: '模型管理',
|
||||
path: '/guide/model',
|
||||
icon: Layers,
|
||||
description: '模型映射与负载均衡'
|
||||
},
|
||||
{
|
||||
id: 'user-key',
|
||||
name: '用户与密钥',
|
||||
path: '/guide/user-key',
|
||||
icon: Users,
|
||||
description: 'API Key 与权限管理'
|
||||
id: 'strategy',
|
||||
name: '关键策略',
|
||||
path: '/guide/strategy',
|
||||
icon: Target,
|
||||
description: '调度、缓存与故障转移'
|
||||
},
|
||||
{
|
||||
id: 'advanced',
|
||||
name: '高级功能',
|
||||
path: '/guide/advanced',
|
||||
icon: Settings,
|
||||
description: '格式转换、请求头规则等'
|
||||
description: '格式转换、请求规则等'
|
||||
},
|
||||
{
|
||||
id: 'faq',
|
||||
@@ -75,6 +75,7 @@ export const panelClasses = {
|
||||
badgeBlue: 'inline-flex items-center gap-1.5 rounded-full bg-blue-500/10 dark:bg-blue-500/20 border border-blue-500/20 dark:border-blue-500/40 px-2 py-0.5 text-xs font-medium text-blue-600 dark:text-blue-400',
|
||||
badgeGreen: 'inline-flex items-center gap-1.5 rounded-full bg-green-500/10 dark:bg-green-500/20 border border-green-500/20 dark:border-green-500/40 px-2 py-0.5 text-xs font-medium text-green-600 dark:text-green-400',
|
||||
badgeYellow: 'inline-flex items-center gap-1.5 rounded-full bg-yellow-500/10 dark:bg-yellow-500/20 border border-yellow-500/20 dark:border-yellow-500/40 px-2 py-0.5 text-xs font-medium text-yellow-600 dark:text-yellow-400',
|
||||
badgePurple: 'inline-flex items-center gap-1.5 rounded-full bg-purple-500/10 dark:bg-purple-500/20 border border-purple-500/20 dark:border-purple-500/40 px-2 py-0.5 text-xs font-medium text-purple-600 dark:text-purple-400',
|
||||
iconButtonSmall: [
|
||||
'flex items-center justify-center rounded-lg border h-7 w-7',
|
||||
'border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)]',
|
||||
@@ -84,30 +85,6 @@ export const panelClasses = {
|
||||
].join(' ')
|
||||
} as const
|
||||
|
||||
// 核心概念数据
|
||||
export const coreConcepts = [
|
||||
{
|
||||
name: '供应商 (Provider)',
|
||||
description: '代表一个 AI 服务提供商,如 OpenAI、Anthropic、Google 等',
|
||||
color: 'blue'
|
||||
},
|
||||
{
|
||||
name: '端点 (Endpoint)',
|
||||
description: '供应商下的具体 API 端点,包含 URL、密钥、API 格式等配置',
|
||||
color: 'green'
|
||||
},
|
||||
{
|
||||
name: '模型 (Model)',
|
||||
description: '可供用户使用的模型,可关联多个端点实现负载均衡',
|
||||
color: 'purple'
|
||||
},
|
||||
{
|
||||
name: 'API Key',
|
||||
description: '用户访问系统的凭证,可设置权限、配额、允许的模型等',
|
||||
color: 'orange'
|
||||
}
|
||||
]
|
||||
|
||||
// API 格式说明
|
||||
export const apiFormats = [
|
||||
{
|
||||
@@ -118,10 +95,16 @@ export const apiFormats = [
|
||||
},
|
||||
{
|
||||
name: 'OpenAI CLI',
|
||||
endpoint: '/v1/responses (Codex: /responses)',
|
||||
endpoint: '/v1/responses',
|
||||
auth: 'Authorization: Bearer xxx',
|
||||
clients: ['Codex CLI']
|
||||
},
|
||||
{
|
||||
name: 'OpenAI Video',
|
||||
endpoint: '/v1/videos',
|
||||
auth: 'Authorization: Bearer xxx',
|
||||
clients: ['Sora']
|
||||
},
|
||||
{
|
||||
name: 'Claude Chat',
|
||||
endpoint: '/v1/messages',
|
||||
@@ -138,36 +121,148 @@ export const apiFormats = [
|
||||
name: 'Gemini Chat',
|
||||
endpoint: '/v1beta/models/{model}:generateContent',
|
||||
auth: 'x-goog-api-key: xxx',
|
||||
clients: ['Gemini SDK', 'Gemini CLI']
|
||||
clients: ['Gemini SDK']
|
||||
},
|
||||
{
|
||||
name: 'Gemini CLI',
|
||||
endpoint: '/v1beta/models/{model}:generateContent',
|
||||
auth: 'x-goog-api-key: xxx',
|
||||
clients: ['Gemini CLI']
|
||||
},
|
||||
{
|
||||
name: 'Gemini Video',
|
||||
endpoint: '/v1beta/models/{model}:predictLongRunning',
|
||||
auth: 'x-goog-api-key: xxx',
|
||||
clients: ['Veo']
|
||||
}
|
||||
]
|
||||
|
||||
// 负载均衡模式
|
||||
export const loadBalanceModes = [
|
||||
// 配置流程步骤
|
||||
export const configSteps = [
|
||||
{
|
||||
mode: 'priority',
|
||||
name: '优先级',
|
||||
description: '按端点优先级顺序调用,高优先级的先用,失败后降级到低优先级'
|
||||
step: 1,
|
||||
title: '添加供应商',
|
||||
description: '创建供应商并配置端点(URL、API Key、API 格式)',
|
||||
path: '/admin/providers'
|
||||
},
|
||||
{
|
||||
mode: 'random',
|
||||
name: '随机',
|
||||
description: '随机选择一个可用端点,适合多个同质端点'
|
||||
step: 2,
|
||||
title: '创建模型',
|
||||
description: '定义用户可用的模型名,关联到端点',
|
||||
path: '/admin/models'
|
||||
},
|
||||
{
|
||||
mode: 'round_robin',
|
||||
name: '轮询',
|
||||
description: '依次轮流使用各个端点,分摊负载'
|
||||
step: 3,
|
||||
title: '发放密钥',
|
||||
description: '为用户创建 API Key,设置权限和配额',
|
||||
path: '/admin/keys'
|
||||
},
|
||||
{
|
||||
mode: 'weighted',
|
||||
name: '加权',
|
||||
description: '按权重比例分配请求,权重高的端点处理更多请求'
|
||||
step: 4,
|
||||
title: '开始使用',
|
||||
description: '配置客户端,开始调用 API',
|
||||
path: '/guide'
|
||||
}
|
||||
]
|
||||
|
||||
// 客户端配置示例
|
||||
export const clientExamples = [
|
||||
{
|
||||
name: 'Claude Code',
|
||||
configKey: 'ANTHROPIC_BASE_URL',
|
||||
code: (baseUrl: string) => `# 设置环境变量
|
||||
export ANTHROPIC_BASE_URL="${baseUrl}"
|
||||
export ANTHROPIC_API_KEY="your-api-key"
|
||||
|
||||
# 启动 Claude Code
|
||||
claude`,
|
||||
note: '使用 Claude CLI 格式 (Authorization: Bearer)'
|
||||
},
|
||||
{
|
||||
mode: 'latency',
|
||||
name: '最低延迟',
|
||||
description: '优先使用历史延迟最低的端点'
|
||||
name: 'Codex CLI',
|
||||
configKey: 'OPENAI_BASE_URL',
|
||||
code: (baseUrl: string) => `# 设置环境变量
|
||||
export OPENAI_BASE_URL="${baseUrl}"
|
||||
export OPENAI_API_KEY="your-api-key"
|
||||
|
||||
# 启动 Codex
|
||||
codex`,
|
||||
note: '使用 OpenAI CLI 格式 (Responses API)'
|
||||
},
|
||||
{
|
||||
name: 'Cursor',
|
||||
configKey: 'Base URL',
|
||||
code: (baseUrl: string) => `# Cursor Settings > Models > OpenAI API Key
|
||||
Base URL: ${baseUrl}/v1
|
||||
API Key: your-api-key`,
|
||||
note: '使用 OpenAI Chat 格式'
|
||||
},
|
||||
{
|
||||
name: 'OpenAI SDK (Python)',
|
||||
configKey: 'base_url',
|
||||
code: (baseUrl: string) => `from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
base_url="${baseUrl}/v1",
|
||||
api_key="your-api-key"
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="claude-sonnet-4-20250514",
|
||||
messages=[{"role": "user", "content": "Hello"}]
|
||||
)`,
|
||||
note: '使用 OpenAI Chat 格式,支持格式转换调用 Claude/Gemini'
|
||||
},
|
||||
{
|
||||
name: 'Gemini CLI',
|
||||
configKey: 'GEMINI_API_BASE',
|
||||
code: (baseUrl: string) => `# 设置环境变量
|
||||
export GEMINI_API_BASE="${baseUrl}"
|
||||
export GEMINI_API_KEY="your-api-key"
|
||||
|
||||
# 启动 Gemini CLI
|
||||
gemini`,
|
||||
note: '使用 Gemini Chat 格式'
|
||||
}
|
||||
]
|
||||
|
||||
// 常见供应商配置
|
||||
export const providerExamples = [
|
||||
{
|
||||
name: 'OpenAI',
|
||||
url: 'https://api.openai.com',
|
||||
format: 'OpenAI Chat',
|
||||
note: '官方 API'
|
||||
},
|
||||
{
|
||||
name: 'Anthropic',
|
||||
url: 'https://api.anthropic.com',
|
||||
format: 'Claude Chat',
|
||||
note: '官方 Claude API'
|
||||
},
|
||||
{
|
||||
name: 'Google AI',
|
||||
url: 'https://generativelanguage.googleapis.com',
|
||||
format: 'Gemini Chat',
|
||||
note: '官方 Gemini API'
|
||||
},
|
||||
{
|
||||
name: 'Azure OpenAI',
|
||||
url: 'https://{resource}.openai.azure.com',
|
||||
format: 'OpenAI Chat',
|
||||
note: '替换 {resource} 为你的资源名'
|
||||
},
|
||||
{
|
||||
name: 'OpenRouter',
|
||||
url: 'https://openrouter.ai/api',
|
||||
format: 'OpenAI Chat',
|
||||
note: '聚合多家供应商的 API 代理'
|
||||
},
|
||||
{
|
||||
name: '自托管/其他',
|
||||
url: 'https://your-api.com',
|
||||
format: 'OpenAI Chat',
|
||||
note: '大多数兼容服务选择 OpenAI Chat 格式'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -234,31 +329,3 @@ export const faqItems = [
|
||||
answer: '表示上游服务不可用。检查:1) 端点健康状态;2) 供应商 API 是否正常;3) 网络连接是否正常。可以在健康监控页面查看端点状态。'
|
||||
}
|
||||
]
|
||||
|
||||
// 配置流程步骤
|
||||
export const configSteps = [
|
||||
{
|
||||
step: 1,
|
||||
title: '添加供应商',
|
||||
description: '创建一个供应商来组织你的 API 端点',
|
||||
path: '/admin/providers'
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: '配置端点',
|
||||
description: '在供应商下添加 API 端点,填写 URL、密钥等',
|
||||
path: '/admin/providers'
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: '创建模型',
|
||||
description: '创建用户可用的模型,关联到端点',
|
||||
path: '/admin/models'
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
title: '发放密钥',
|
||||
description: '为用户创建 API Key,设置权限和配额',
|
||||
path: '/admin/keys'
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user