mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
refactor(guide): 重构指南页面为 Markdown 内容驱动架构
- 将各指南页面的内联硬编码内容抽取为独立 Markdown 文件 - 新增 MarkdownViewer 组件统一渲染 Markdown 内容 - 新增 ArchitectureDiagram 组件和架构演示组件 - 新增模块指南页面 (ModulesGuide) - GuideLayout 支持子导航、页面过渡动画和图片 Lightbox - guide-config 重构为支持 subItems 的导航结构 - 添加指南相关截图资源
This commit is contained in:
@@ -59,6 +59,12 @@ const routes: RouteRecordRaw[] = [
|
||||
name: 'GuideFaq',
|
||||
component: () => importWithRetry(() => import('@/views/public/guide/GuideFaq.vue')),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: 'modules',
|
||||
name: 'GuideModules',
|
||||
component: () => importWithRetry(() => import('@/views/public/guide/ModulesGuide.vue')),
|
||||
meta: { requiresAuth: false }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1,786 +1,136 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { ArrowRight, Shuffle, FileCode, Globe, Shield, Check, Info, AlertTriangle, Settings } 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 conversionExamples = [
|
||||
{
|
||||
from: 'OpenAI SDK',
|
||||
to: 'Claude API',
|
||||
description: '用 OpenAI SDK 调用 Claude 模型',
|
||||
icon: 'openai'
|
||||
},
|
||||
{
|
||||
from: 'Anthropic SDK',
|
||||
to: 'OpenAI API',
|
||||
description: '用 Claude SDK 调用 GPT 模型',
|
||||
icon: 'claude'
|
||||
},
|
||||
{
|
||||
from: 'Claude Code',
|
||||
to: 'OpenAI API',
|
||||
description: 'Claude Code 使用 GPT 模型',
|
||||
icon: 'cli'
|
||||
}
|
||||
]
|
||||
|
||||
// 请求头规则类型
|
||||
const headerRuleTypes = [
|
||||
{ type: 'add', name: '添加', description: '添加新的请求头,如果已存在则跳过' },
|
||||
{ type: 'set', name: '设置', description: '设置请求头的值,会覆盖已有值' },
|
||||
{ type: 'remove', name: '删除', description: '删除指定的请求头' }
|
||||
]
|
||||
|
||||
// 请求体规则类型
|
||||
const bodyRuleTypes = [
|
||||
{ action: 'set', name: '覆写', description: '设置或覆盖指定路径的字段值' },
|
||||
{ action: 'drop', name: '删除', description: '删除指定路径的字段' },
|
||||
{ action: 'rename', name: '重命名', description: '将字段从一个路径移动到另一个路径' },
|
||||
{ action: 'insert', name: '插入', description: '在数组的指定位置插入元素,位置留空则追加到末尾' },
|
||||
{ action: 'regex_replace', name: '正则替换', description: '对字符串字段执行正则表达式替换' },
|
||||
]
|
||||
|
||||
// 请求体规则示例
|
||||
const bodyRuleExamples = [
|
||||
{
|
||||
title: '注入系统提示词',
|
||||
description: '在 messages 数组开头插入一条 system 消息(index: 0)',
|
||||
rule: `{
|
||||
"action": "insert",
|
||||
"path": "messages",
|
||||
"index": 0,
|
||||
"value": {
|
||||
"role": "system",
|
||||
"content": "你是一个专业助手"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: '追加消息到末尾',
|
||||
description: '不指定 index,自动追加到数组末尾',
|
||||
rule: `{
|
||||
"action": "insert",
|
||||
"path": "messages",
|
||||
"value": {
|
||||
"role": "user",
|
||||
"content": "请用中文回答"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: '设置自定义元数据',
|
||||
description: '覆写嵌套字段,不存在时自动创建中间层级',
|
||||
rule: `{
|
||||
"action": "set",
|
||||
"path": "metadata.source",
|
||||
"value": "internal-app"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: '删除不需要的字段',
|
||||
description: '移除请求体中的敏感或多余字段',
|
||||
rule: `{
|
||||
"action": "drop",
|
||||
"path": "user_info.ip_address"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: '内容脱敏',
|
||||
description: '用正则替换 messages 中的手机号',
|
||||
rule: `{
|
||||
"action": "regex_replace",
|
||||
"path": "messages[-1].content",
|
||||
"pattern": "1[3-9]\\\\d{9}",
|
||||
"replacement": "[手机号已隐藏]",
|
||||
"flags": ""
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: '重命名字段',
|
||||
description: '将字段从旧路径移动到新路径',
|
||||
rule: `{
|
||||
"action": "rename",
|
||||
"from": "extra.custom_id",
|
||||
"to": "metadata.trace_id"
|
||||
}`,
|
||||
},
|
||||
]
|
||||
|
||||
// 路径语法示例
|
||||
const pathSyntaxExamples = [
|
||||
{ path: 'metadata.user', desc: '嵌套 dict 字段' },
|
||||
{ path: 'messages[0].content', desc: '数组第一个元素的 content 字段' },
|
||||
{ path: 'messages[-1]', desc: '数组最后一个元素' },
|
||||
{ path: 'matrix[0][1]', desc: '多维数组访问' },
|
||||
{ path: 'config\\.v1.enabled', desc: '\\. 转义为字面量点号 → key "config.v1"' },
|
||||
]
|
||||
|
||||
// 系统设置分类
|
||||
const systemSettings = [
|
||||
{
|
||||
category: '基础设置',
|
||||
items: [
|
||||
{ name: '系统名称', description: '显示在页面标题和 Logo 旁边的名称' },
|
||||
{ name: '注册开关', description: '是否允许新用户注册' },
|
||||
{ name: '默认用户角色', description: '新注册用户的默认角色' }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'API 设置',
|
||||
items: [
|
||||
{ name: '格式转换', description: '是否启用跨格式 API 调用' },
|
||||
{ name: '请求超时', description: '默认的 API 请求超时时间' },
|
||||
{ name: '流式响应', description: '是否默认启用流式响应' }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: '安全设置',
|
||||
items: [
|
||||
{ name: 'IP 限流', description: '单 IP 的请求频率限制' },
|
||||
{ name: 'Key 限流', description: '单 Key 的请求频率限制' },
|
||||
{ name: 'IP 黑名单', description: '全局 IP 黑名单' }
|
||||
]
|
||||
}
|
||||
]
|
||||
import { Settings } from 'lucide-vue-next'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 标题 -->
|
||||
<div class="space-y-12 pb-12">
|
||||
<!-- Hero 区域 -->
|
||||
<div class="space-y-4">
|
||||
<div class="inline-flex items-center gap-1.5 rounded-full bg-[#cc785c]/10 dark:bg-[#cc785c]/20 border border-[#cc785c]/20 dark:border-[#cc785c]/40 px-3 py-1 text-xs font-medium text-[#cc785c] dark:text-[#d4a27f]">
|
||||
<Settings class="h-3 w-3" />
|
||||
高级使用
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
高级功能
|
||||
</h1>
|
||||
<p class="text-lg text-[#666663] dark:text-[#a3a094]">
|
||||
{{ siteName }} 提供了一些高级功能,帮助你实现更灵活的 API 管理。
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094] max-w-2xl">
|
||||
解锁 Aether 的自定义请求修改、智能转换机制以及进阶配置。
|
||||
</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">
|
||||
<Shuffle class="h-5 w-5 text-purple-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
什么是格式转换?
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
格式转换允许你用一种 SDK 调用另一种格式的 API。例如用 OpenAI SDK 调用 Claude 模型,
|
||||
系统会自动转换请求格式和响应格式。
|
||||
</p>
|
||||
|
||||
<div class="grid gap-3 md:grid-cols-3">
|
||||
<div
|
||||
v-for="example in conversionExamples"
|
||||
:key="example.from"
|
||||
class="p-4 rounded-lg bg-[#f5f5f0]/50 dark:bg-[#1f1d1a]/50"
|
||||
>
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span :class="panelClasses.badgeBlue">{{ example.from }}</span>
|
||||
<ArrowRight class="h-4 w-4 text-[#999]" />
|
||||
<span :class="panelClasses.badgeGreen">{{ example.to }}</span>
|
||||
</div>
|
||||
<p class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
{{ example.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 如何启用 -->
|
||||
<div
|
||||
class="p-5 space-y-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
如何启用格式转换
|
||||
</h3>
|
||||
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
|
||||
1
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
开启系统设置
|
||||
</p>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
在「系统设置」中开启 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">ENABLE_API_FORMAT_CONVERSION</code>
|
||||
</p>
|
||||
</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>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
配置端点
|
||||
</p>
|
||||
<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>
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
使用
|
||||
</p>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
用户现在可以用 OpenAI SDK 调用配置在 Claude 格式端点上的模型
|
||||
</p>
|
||||
</div>
|
||||
</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">
|
||||
格式转换会增加少量延迟(通常 <10ms)。部分特有功能(如 Claude 的 thinking、OpenAI 的 function calling)
|
||||
可能无法完美转换,建议在实际场景中测试。
|
||||
</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]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="p-2 rounded-lg bg-green-500/10">
|
||||
<FileCode class="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
什么是请求头规则?
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
请求头规则允许你在转发请求时修改 HTTP 请求头。可以添加、修改或删除特定的请求头。
|
||||
</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-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="rule in headerRuleTypes"
|
||||
:key="rule.type"
|
||||
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]">
|
||||
<span :class="panelClasses.badgeBlue">{{ rule.name }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
|
||||
{{ rule.description }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</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>
|
||||
<ul class="mt-1 space-y-1">
|
||||
<li>• 添加额外的认证信息(如 API 版本号)</li>
|
||||
<li>• 添加跟踪标记(如请求 ID、来源标识)</li>
|
||||
<li>• 删除敏感信息(如用户原始 IP)</li>
|
||||
</ul>
|
||||
</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]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="p-2 rounded-lg bg-orange-500/10">
|
||||
<FileCode class="h-5 w-5 text-orange-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
什么是请求体规则?
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
请求体规则允许你在转发请求时修改请求体(JSON Body)的内容。可以覆写字段、删除字段、向数组追加/插入元素,甚至用正则替换字符串值。
|
||||
规则按顺序依次执行,受保护的顶层字段(<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">model</code>、<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">stream</code>)不可修改。
|
||||
</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-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="rule in bodyRuleTypes"
|
||||
:key="rule.action"
|
||||
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]">
|
||||
<span :class="panelClasses.badge">{{ rule.name }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
|
||||
{{ rule.description }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 路径语法 -->
|
||||
<div
|
||||
class="p-5 space-y-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
路径语法
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
使用点号(<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">.</code>)分隔层级,方括号(<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">[N]</code>)访问数组元素。
|
||||
</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-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="ex in pathSyntaxExamples"
|
||||
:key="ex.path"
|
||||
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
|
||||
>
|
||||
<td class="px-4 py-3 font-mono text-xs text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ ex.path }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
|
||||
{{ ex.desc }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实战示例 -->
|
||||
<div
|
||||
class="p-5 space-y-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
实战示例
|
||||
</h3>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div
|
||||
v-for="example in bodyRuleExamples"
|
||||
:key="example.title"
|
||||
class="rounded-lg border border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] overflow-hidden"
|
||||
>
|
||||
<div class="px-4 py-2.5 border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
|
||||
<p class="font-medium text-sm text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ example.title }}
|
||||
</p>
|
||||
<p class="text-xs text-[#666663] dark:text-[#a3a094] mt-0.5">
|
||||
{{ example.description }}
|
||||
</p>
|
||||
</div>
|
||||
<pre class="p-4 text-xs font-mono text-[#262624] dark:text-[#f1ead8] overflow-x-auto bg-[#fafaf7]/30 dark:bg-[#1a1816]/30"><code>{{ example.rule }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 正则替换说明 -->
|
||||
<div
|
||||
class="p-5 space-y-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
正则替换详解
|
||||
</h3>
|
||||
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">regex_replace</code> 对指定路径的字符串值执行正则表达式替换。
|
||||
</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-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 class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)]">
|
||||
<td class="px-4 py-3 font-mono text-xs text-[#262624] dark:text-[#f1ead8]">
|
||||
path
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<Check class="h-4 w-4 text-green-500" />
|
||||
</td>
|
||||
<td class="px-4 py-3 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-3 font-mono text-xs text-[#262624] dark:text-[#f1ead8]">
|
||||
pattern
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<Check class="h-4 w-4 text-green-500" />
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
|
||||
正则表达式(Python re 语法),保存时会校验合法性
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)]">
|
||||
<td class="px-4 py-3 font-mono text-xs text-[#262624] dark:text-[#f1ead8]">
|
||||
replacement
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<Check class="h-4 w-4 text-green-500" />
|
||||
</td>
|
||||
<td class="px-4 py-3 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-3 font-mono text-xs text-[#262624] dark:text-[#f1ead8]">
|
||||
flags
|
||||
</td>
|
||||
<td class="px-4 py-3 text-xs text-[#999]">
|
||||
可选
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
|
||||
<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">i</code> 忽略大小写 /
|
||||
<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">m</code> 多行模式 /
|
||||
<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1 py-0.5 rounded">s</code> dotall(. 匹配换行)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="last:border-0">
|
||||
<td class="px-4 py-3 font-mono text-xs text-[#262624] dark:text-[#f1ead8]">
|
||||
count
|
||||
</td>
|
||||
<td class="px-4 py-3 text-xs text-[#999]">
|
||||
可选
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
|
||||
替换次数,默认 0 = 全部替换
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</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>
|
||||
<ul class="mt-1 space-y-1">
|
||||
<li>
|
||||
<span class="font-medium text-[#262624] dark:text-[#f1ead8]">注入 System Prompt</span> — 在所有请求前插入统一的系统提示词
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-medium text-[#262624] dark:text-[#f1ead8]">请求增强</span> — 自动追加上下文、metadata 等字段
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-medium text-[#262624] dark:text-[#f1ead8]">内容过滤</span> — 用正则替换脱敏敏感信息(手机号、邮箱等)
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-medium text-[#262624] dark:text-[#f1ead8]">字段清理</span> — 删除不需要的自定义字段,避免上游报错
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-medium text-[#262624] dark:text-[#f1ead8]">字段适配</span> — 将客户端的字段名重命名为上游期望的格式
|
||||
</li>
|
||||
</ul>
|
||||
</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]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="p-2 rounded-lg bg-blue-500/10">
|
||||
<Globe class="h-5 w-5 text-blue-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
HTTP 代理
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
如果你的服务器无法直接访问某些 API(如在国内访问 OpenAI),可以在端点配置中设置代理。
|
||||
</p>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="p-4 rounded-lg bg-[#f5f5f0]/50 dark:bg-[#1f1d1a]/50">
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
端点级代理
|
||||
</h4>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
在端点配置中填写代理地址,格式如 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">http://proxy:8080</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 rounded-lg bg-[#f5f5f0]/50 dark:bg-[#1f1d1a]/50">
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
全局代理
|
||||
</h4>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
也可以通过环境变量 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">HTTP_PROXY</code> 设置全局代理
|
||||
</p>
|
||||
</div>
|
||||
</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]">
|
||||
在「系统设置」页面可以配置全局参数:
|
||||
<!-- 1. 格式转换 -->
|
||||
<section
|
||||
id="format-conversion"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>1. 格式转换</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
格式转换机制允许非原生接口的请求通过 Gateway 转为下游需要的格式,这一转换发生在多个层级:
|
||||
</p>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<div
|
||||
v-for="category in systemSettings"
|
||||
:key="category.category"
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<Settings class="h-4 w-4 text-[#cc785c]" />
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
{{ category.category }}
|
||||
</h3>
|
||||
</div>
|
||||
<ul class="space-y-2">
|
||||
<li
|
||||
v-for="item in category.items"
|
||||
:key="item.name"
|
||||
class="text-sm"
|
||||
>
|
||||
<span class="font-medium text-[#262624] dark:text-[#f1ead8]">{{ item.name }}</span>
|
||||
<span class="text-[#666663] dark:text-[#a3a094]"> - {{ item.description }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-decimal pl-5 space-y-2 mt-2 text-[#666663] dark:text-[#a3a094] text-sm">
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">全局格式转换:</strong> 在系统层面全局开启或关闭的默认转换规则。</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">提供商级转换:</strong> 针对某个特定提供商的所有请求进行特定的格式转换配置。</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">端点级转换:</strong> 针对某个具体端点的细粒度转换支持。</li>
|
||||
</ul>
|
||||
</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]"
|
||||
>
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="p-2 rounded-lg bg-green-500/10">
|
||||
<Shield class="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
端点健康检查
|
||||
</h3>
|
||||
</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>系统会定期检测端点的可用性</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">
|
||||
<AlertTriangle class="h-5 w-5 text-yellow-500 flex-shrink-0 mt-0.5" />
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
端点显示不健康?
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
检查:1) API URL 是否正确;2) API Key 是否有效;3) 网络是否可达;4) 是否需要配置代理。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 2. 请求上游固定非流/流式 -->
|
||||
<section
|
||||
id="stream-policy"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>2. 请求上游固定非流/流式</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
部分提供商在流式或非流式表现上可能存在差异,此功能可强制在请求上游时使用指定的流式/非流式格式(并在前端按用户请求还原)。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 下一步 -->
|
||||
<section class="pt-4">
|
||||
<RouterLink
|
||||
to="/guide/faq"
|
||||
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>
|
||||
<!-- 3. 请求头/体编辑 -->
|
||||
<section
|
||||
id="header-body-edit"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>3. 请求头/体编辑</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
可在端点设置中添加规则(Rules),使用条件匹配与动作(如 Delete, Set, Insert)来动态修改发往上游的 Headers 或 JSON Payload。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 4. 模型映射 -->
|
||||
<section
|
||||
id="model-mapping"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>4. 模型映射</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
解决提供商特定模型名称与平台标准名称不一致的问题。将请求中的统一模型名动态转换为上游真实需要的模型名。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 5. 正则映射 -->
|
||||
<section
|
||||
id="regex-mapping"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>5. 正则映射</h2>
|
||||
<ul class="list-decimal pl-5 space-y-2 mt-4 text-[#666663] dark:text-[#a3a094] text-sm">
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">模型权限:</strong> 使用正则表达式来匹配一类模型,给予批量授权。</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">自动获取上游模型:</strong> 在自动获取的模型列表中,应用正则过滤与清洗,从而快速导入符合规则的标准模型列表。</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mt-12 pt-8 border-t border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)]">
|
||||
<!-- 6. 能力标签 -->
|
||||
<section
|
||||
id="capabilities"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h3 class="mt-0 text-xl text-[#262624] dark:text-[#f1ead8]">
|
||||
6. 能力标签
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-2">
|
||||
为特定的 Key 或模型添加自定义标签(如 Vision, Function Calling, Long Context),通过标签约束路由,只选择具备该能力的可用通道。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 7. 余额监控 -->
|
||||
<section
|
||||
id="balance-monitor"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h3 class="mt-0 text-xl text-[#262624] dark:text-[#f1ead8]">
|
||||
7. 余额监控
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-2">
|
||||
针对各大提供商的官方接口或常见聚合平台,自动抓取并记录剩余额度,在余额低于阈值时触发报警或禁用策略。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 8. 配置导入/出 -->
|
||||
<section
|
||||
id="config-export"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h3 class="mt-0 text-xl text-[#262624] dark:text-[#f1ead8]">
|
||||
8. 配置导入/出
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-2">
|
||||
支持将统一模型配置、提供商端点及网关路由策略一键导出为 JSON,并在其他部署实例中迁移导入。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 9. 锁定用户密钥 -->
|
||||
<section
|
||||
id="lock-key"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h3 class="mt-0 text-xl text-[#262624] dark:text-[#f1ead8]">
|
||||
9. 锁定用户密钥
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-2">
|
||||
若监控发现恶意使用、异常调用或高频报错,管理员可以临时或永久锁定特定密钥,以阻断攻击源头。
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,466 +1,159 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import {
|
||||
ArrowRight,
|
||||
ChevronRight,
|
||||
Monitor,
|
||||
Shield,
|
||||
Zap,
|
||||
Database,
|
||||
Info,
|
||||
Shuffle
|
||||
} from 'lucide-vue-next'
|
||||
import { Network, ArrowRight } 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)']
|
||||
}
|
||||
]
|
||||
import ArchitectureDiagram from './components/ArchitectureDiagram.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 标题 -->
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-12">
|
||||
<!-- Hero 区域 -->
|
||||
<div class="space-y-4">
|
||||
<div class="inline-flex items-center gap-1.5 rounded-full bg-[#cc785c]/10 dark:bg-[#cc785c]/20 border border-[#cc785c]/20 dark:border-[#cc785c]/40 px-3 py-1 text-xs font-medium text-[#cc785c] dark:text-[#d4a27f]">
|
||||
<Network class="h-3 w-3" />
|
||||
系统架构
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
架构说明
|
||||
</h1>
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094]">
|
||||
{{ siteName }} 的系统架构、请求处理流程和数据流向。
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094] max-w-2xl">
|
||||
Aether 作为一个高性能的 AI API Gateway,其核心在于统一的接入层与灵活的路由调度。我们在下方提供了一个架构的数据流向动画演示,帮助您直观地理解它的工作原理。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 系统概览 -->
|
||||
<section class="space-y-3">
|
||||
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
|
||||
系统概览
|
||||
</h2>
|
||||
<!-- 动态架构图 -->
|
||||
<div class="mt-10 mb-6">
|
||||
<ArchitectureDiagram />
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<div
|
||||
class="p-5"
|
||||
:class="[panelClasses.section]"
|
||||
:class="[panelClasses.card]"
|
||||
class="p-8 mt-6"
|
||||
>
|
||||
<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]">
|
||||
客户端
|
||||
<ul class="space-y-6">
|
||||
<li class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 rounded-full bg-[#cc785c]/10 text-[#cc785c] flex items-center justify-center shrink-0 mt-1 shadow-sm">
|
||||
<span class="font-bold font-mono">1</span>
|
||||
</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 class="text-lg font-medium text-[#262624] dark:text-[#f1ead8] m-0 mb-1">
|
||||
多维度支持
|
||||
</h3>
|
||||
<span class="text-xs text-[#666663] dark:text-[#a3a094]">{{ store.role }}</span>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] leading-relaxed">
|
||||
全面兼容并支持多种 API 格式、端点配置及不同的认证方式,保障不同客户端的平滑接入。
|
||||
</p>
|
||||
</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>
|
||||
</li>
|
||||
|
||||
<li class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 rounded-full bg-[#cc785c]/10 text-[#cc785c] flex items-center justify-center shrink-0 mt-1 shadow-sm">
|
||||
<span class="font-bold font-mono">2</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-medium text-[#262624] dark:text-[#f1ead8] m-0 mb-1">
|
||||
统一的模型入口
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] leading-relaxed">
|
||||
对外暴露统一的模型名称,对内自动映射到各提供商可能不同的内部模型名及其变体,支持多变体优先级选择和基于亲和性的稳定路由,结合负载均衡、故障转移等智能调度策略。
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 rounded-full bg-[#cc785c]/10 text-[#cc785c] flex items-center justify-center shrink-0 mt-1 shadow-sm">
|
||||
<span class="font-bold font-mono">3</span>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<h3 class="text-lg font-medium text-[#262624] dark:text-[#f1ead8] m-0 mb-3">
|
||||
格式转换流程 (跨平台转发)
|
||||
</h3>
|
||||
<div class="flex flex-col md:flex-row items-stretch md:items-center gap-3 bg-[#f5f5f0]/50 dark:bg-[rgba(227,224,211,0.05)] p-4 rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.06)]">
|
||||
<div class="flex-1 bg-white dark:bg-[#191714] border border-[#e5e4df] dark:border-white/10 rounded-lg p-3 text-center text-sm font-medium text-[#262624] dark:text-white shadow-sm">
|
||||
多API格式兼容入口
|
||||
</div>
|
||||
<ArrowRight class="hidden md:block w-5 h-5 text-[#cc785c] mx-1 shrink-0" />
|
||||
<div class="md:hidden flex justify-center py-1">
|
||||
<ArrowRight class="w-5 h-5 text-[#cc785c] rotate-90" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 bg-[#cc785c]/10 dark:bg-[#cc785c]/20 border border-[#cc785c]/20 rounded-lg p-3 text-center text-sm font-medium text-[#cc785c] dark:text-[#d4a27f]">
|
||||
格式转换
|
||||
</div>
|
||||
<ArrowRight class="hidden md:block w-5 h-5 text-[#91918d] dark:text-white/30 mx-1 shrink-0" />
|
||||
<div class="md:hidden flex justify-center py-1">
|
||||
<ArrowRight class="w-5 h-5 text-[#91918d] dark:text-white/30 rotate-90" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 bg-white dark:bg-[#191714] border border-[#e5e4df] dark:border-white/10 rounded-lg p-3 text-center text-sm font-medium text-[#262624] dark:text-white shadow-sm">
|
||||
上游提供商
|
||||
</div>
|
||||
<ArrowRight class="hidden md:block w-5 h-5 text-[#91918d] dark:text-white/30 mx-1 shrink-0" />
|
||||
<div class="md:hidden flex justify-center py-1">
|
||||
<ArrowRight class="w-5 h-5 text-[#91918d] dark:text-white/30 rotate-90" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 bg-[#cc785c]/10 dark:bg-[#cc785c]/20 border border-[#cc785c]/20 rounded-lg p-3 text-center text-sm font-medium text-[#cc785c] dark:text-[#d4a27f]">
|
||||
格式转换
|
||||
</div>
|
||||
<ArrowRight class="hidden md:block w-5 h-5 text-[#cc785c] mx-1 shrink-0" />
|
||||
<div class="md:hidden flex justify-center py-1">
|
||||
<ArrowRight class="w-5 h-5 text-[#cc785c] rotate-90" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 bg-white dark:bg-[#191714] border border-[#e5e4df] dark:border-white/10 rounded-lg p-3 text-center text-sm font-medium text-[#262624] dark:text-white shadow-sm">
|
||||
兼容出口响应
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 rounded-full bg-[#cc785c]/10 text-[#cc785c] flex items-center justify-center shrink-0 mt-1 shadow-sm">
|
||||
<span class="font-bold font-mono">4</span>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<h3 class="text-lg font-medium text-[#262624] dark:text-[#f1ead8] m-0 mb-3">
|
||||
透传流程 (原生同生态)
|
||||
</h3>
|
||||
<div class="flex flex-col md:flex-row items-stretch md:items-center gap-3 bg-[#f5f5f0]/50 dark:bg-[rgba(227,224,211,0.05)] p-4 rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.06)]">
|
||||
<div class="flex-1 bg-white dark:bg-[#191714] border border-[#e5e4df] dark:border-white/10 rounded-lg p-3 text-center text-sm font-medium text-[#262624] dark:text-white shadow-sm">
|
||||
原生格式 API 入口
|
||||
</div>
|
||||
<ArrowRight class="hidden md:block w-5 h-5 text-[#cc785c] mx-1 shrink-0" />
|
||||
<div class="md:hidden flex justify-center py-1">
|
||||
<ArrowRight class="w-5 h-5 text-[#cc785c] rotate-90" />
|
||||
</div>
|
||||
|
||||
<div class="flex-[2] bg-blue-500/5 dark:bg-blue-500/10 border border-blue-500/20 rounded-lg p-3 text-center text-sm font-medium text-blue-600 dark:text-blue-400">
|
||||
同格式请求原生透传 → 上游提供商 → 原生响应透传
|
||||
</div>
|
||||
<ArrowRight class="hidden md:block w-5 h-5 text-[#cc785c] mx-1 shrink-0" />
|
||||
<div class="md:hidden flex justify-center py-1">
|
||||
<ArrowRight class="w-5 h-5 text-[#cc785c] rotate-90" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 bg-white dark:bg-[#191714] border border-[#e5e4df] dark:border-white/10 rounded-lg p-3 text-center text-sm font-medium text-[#262624] dark:text-white shadow-sm">
|
||||
原生 API 出口
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 flex items-center justify-center shrink-0 mt-1 shadow-sm">
|
||||
<span class="font-bold font-mono">5</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-medium text-[#262624] dark:text-[#f1ead8] m-0 mb-1">
|
||||
响应回路
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] leading-relaxed">
|
||||
上游提供商返回的响应会经过逆向格式转换(跨平台场景)或直接透传(同生态场景),同时将提供商内部的模型名还原为客户端请求时使用的统一模型名,确保客户端收到格式和字段完全兼容的响应。
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</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>
|
||||
|
||||
@@ -1,408 +1,398 @@
|
||||
<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: []
|
||||
}
|
||||
]
|
||||
import { BookOpen } from 'lucide-vue-next'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 标题 -->
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-12">
|
||||
<!-- Hero 区域 -->
|
||||
<div class="space-y-4">
|
||||
<div class="inline-flex items-center gap-1.5 rounded-full bg-[#cc785c]/10 dark:bg-[#cc785c]/20 border border-[#cc785c]/20 dark:border-[#cc785c]/40 px-3 py-1 text-xs font-medium text-[#cc785c] dark:text-[#d4a27f]">
|
||||
<BookOpen class="h-3 w-3" />
|
||||
文档核心
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
相关概念
|
||||
</h1>
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094]">
|
||||
深入理解 {{ siteName }} 中的核心概念及其关系。
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094] max-w-2xl">
|
||||
深入理解 Aether 运行时的核心模块和层级结构。
|
||||
</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>
|
||||
<!-- 1. 创建统一模型 -->
|
||||
<section
|
||||
id="create-model"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>1. 创建统一模型</h2>
|
||||
<div class="space-y-4 mt-4">
|
||||
<div>
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
1. 左侧模型选择、搜索区域
|
||||
</h4>
|
||||
<ul class="list-disc pl-5 mt-2 space-y-1 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li>模型来源:opencode 维护的 https://models.dev</li>
|
||||
<li>仅显示几个官方模型提供商,可以通过搜索获取更多模型。</li>
|
||||
</ul>
|
||||
</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>
|
||||
<div>
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">
|
||||
2. 右侧参数配置区域
|
||||
</h4>
|
||||
<ul class="list-disc pl-5 mt-2 space-y-1 text-sm text-[#666663] dark:text-[#a3a094]">
|
||||
<li>手动添加模型 或 选择模型后自动填写。</li>
|
||||
<li>模型偏好:标定这个模型是否支持这些能力,一般情况下可不选。</li>
|
||||
<li>
|
||||
价格配置:
|
||||
<ul class="list-[circle] pl-5 mt-1 space-y-1">
|
||||
<li>计费方式:Token价格 + 按次价格 + 视频计费 = 最终计费</li>
|
||||
<li>价格阶梯:以总Token数落在哪个区间为准,并非按阶梯溢出补全式计费</li>
|
||||
</ul>
|
||||
</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]"
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-create-model.webp"
|
||||
alt="创建统一模型"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm mt-4 w-full"
|
||||
>
|
||||
<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]"
|
||||
<!-- 2. 添加提供商 -->
|
||||
<section
|
||||
id="add-provider"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>2. 添加提供商</h2>
|
||||
<div class="space-y-4 mt-4 text-[#666663] dark:text-[#a3a094] text-sm">
|
||||
<ul class="list-decimal pl-5 space-y-2">
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">提供商类型:</strong>自定义或反代;一般自定义即可,反代请进入反代章节。</li>
|
||||
<li>
|
||||
<strong class="text-[#262624] dark:text-[#f1ead8] font-medium">计费类型:</strong>
|
||||
<ul class="list-disc pl-5 mt-1 space-y-1">
|
||||
<li>按量付费:持续使用</li>
|
||||
<li>月卡额度:按周期(天)限额</li>
|
||||
<li>免费套餐:不计入成本即倍率为0</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">最大重试次数:</strong>在缓存亲和调度模式下,首次请求失败后的重试次数。</li>
|
||||
<li>
|
||||
<strong class="text-[#262624] dark:text-[#f1ead8] font-medium">超时时间:</strong>
|
||||
<ul class="list-disc pl-5 mt-1 space-y-1">
|
||||
<li>流式首字超时时间:流式请求收到首字前的超时时间</li>
|
||||
<li>非流请求超时时间:非流请求的总超时时间</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">保持优先级:</strong>通过格式转换的请求,是否保持当前优先级。</li>
|
||||
</ul>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-add-provider.webp"
|
||||
alt="添加提供商"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm mt-4 w-full"
|
||||
>
|
||||
<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"
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. 添加端点 -->
|
||||
<section
|
||||
id="add-endpoint"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>3. 添加端点</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
添加端点是添加上游支持的端点,并非你需要使用的端点。比如 Anyrouter 只支持 Claude Code 接入,那么就应该仅添加 Claude CLI 端点。
|
||||
</p>
|
||||
<ul class="list-decimal pl-5 space-y-2 text-[#666663] dark:text-[#a3a094] text-sm">
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">选择格式:</strong>选择上游支持的端点格式。</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">自定义 Base URL、Path:</strong>根据上游提供的信息填写。为空则使用提示的默认值。</li>
|
||||
</ul>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-add-endpoint-1.webp"
|
||||
alt="添加端点选择格式"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-add-endpoint-2.webp"
|
||||
alt="端点自定义"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. 添加密钥 -->
|
||||
<section
|
||||
id="add-key"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>4. 添加密钥</h2>
|
||||
<ul class="list-decimal pl-5 space-y-3 mt-4 text-[#666663] dark:text-[#a3a094] text-sm">
|
||||
<li>
|
||||
<strong class="text-[#262624] dark:text-[#f1ead8] font-medium">认证类型:</strong>
|
||||
<ul class="list-disc pl-5 mt-1">
|
||||
<li>API Key: 传统密钥</li>
|
||||
<li>Vertex AI: 用于 Google Cloud</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">支持的API格式:</strong>勾选Key可以使用的API格式,同时可以设置key访问该端点时的倍率。</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">优先级:</strong>在提供商优先的调度模式下,不同优先级的Key按顺序故障转移,同优先级的Key进行负载均衡。</li>
|
||||
<li>
|
||||
<strong class="text-[#262624] dark:text-[#f1ead8] font-medium">RPM限制:</strong>Key的使用速率限制,会预留10%给已有用户进行提高缓存命中率。
|
||||
<ul class="list-disc pl-5 mt-1">
|
||||
<li>填写固定数值。</li>
|
||||
<li>不填为空,采用一定策略自适应学习。</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="text-[#262624] dark:text-[#f1ead8] font-medium">缓存TTL:</strong>
|
||||
<ul class="list-disc pl-5 mt-1">
|
||||
<li>0: 不使用缓存优先</li>
|
||||
<li>N: 同一个用户Key在N分钟内,优先使用之前使用的提供商、Key响应请求。</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<strong class="text-[#262624] dark:text-[#f1ead8] font-medium">熔断探测:</strong><br>
|
||||
当同一个提供商Key字连续若干次请求失败后,会进入熔断状态,之后每间N分钟进行探测请求,若请求成功解除熔断后续正常请求,否则按以指数级增长探测时间以待下次探测,最大探测间隔不会增长超过32分钟。
|
||||
</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">能力标签:</strong>定义该Key可以使用的能力。</li>
|
||||
<li>
|
||||
<strong class="text-[#262624] dark:text-[#f1ead8] font-medium">自动获取上游模型:</strong><br>
|
||||
在上游获取模型端点支持的情况下,从接口自动获取可以用模型列表。且按一定时间自动刷新,不开启则默认任意模型可用,或在后续模型权限中手动添加。
|
||||
</li>
|
||||
</ul>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-add-key-1.webp"
|
||||
alt="添加密钥认证"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-add-key-2.webp"
|
||||
alt="密钥倍率与优先级"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. 模型权限 -->
|
||||
<section
|
||||
id="model-permission"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>5. 模型权限</h2>
|
||||
<ul class="list-decimal pl-5 space-y-2 mt-4 text-[#666663] dark:text-[#a3a094] text-sm">
|
||||
<li>在编辑密钥中开启自动获取/刷新上游全部模型。</li>
|
||||
<li>手动限制/创建该Key的可用模型。</li>
|
||||
<li>不开启自动获取且勾选任意模型,即模型权限为空,则认为可以使用提供商的全部关联模型。</li>
|
||||
<li>只有在模型权限不为空的情况下,才可以配合正则映射模型。</li>
|
||||
</ul>
|
||||
<div class="grid grid-cols-1 gap-4 mt-6">
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-model-perms-1.webp"
|
||||
alt="模型权限 1"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-model-perms-2.webp"
|
||||
alt="模型权限 2"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-model-perms-3.webp"
|
||||
alt="模型权限 3"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
<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]">
|
||||
下一步:关键策略
|
||||
<!-- 6. 关联模型 -->
|
||||
<section
|
||||
id="link-model"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>6. 关联模型</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
从全局模型关联提供商模型,即设置该提供商可以访问的模型,同时可以在这里设置提供商自己的模型价格。默认继承全局模型的价格。
|
||||
</p>
|
||||
<div class="grid grid-cols-1 gap-4 mt-6">
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-link-model-1.webp"
|
||||
alt="关联模型 1"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-link-model-2.webp"
|
||||
alt="关联模型 2"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-link-model-3.webp"
|
||||
alt="关联模型 3"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 7. 模型映射 -->
|
||||
<section
|
||||
id="model-mapping"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>7. 模型映射</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
如果该提供商的请求名称并非标准名称,即可通过映射改变在实际请求提供时的模型 id。
|
||||
</p>
|
||||
<div class="bg-[#f5f5f0]/80 dark:bg-[rgba(227,224,211,0.05)] p-4 rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.06)] text-sm mb-6">
|
||||
<p class="mb-2">
|
||||
<span class="text-[#cc785c] font-medium mr-2">名称修正示例:</span>官方标准名称为 <code class="bg-black/5 dark:bg-white/10 px-1 py-0.5 rounded text-xs">claude-opus-4-6</code> ,实际提供商叫 <code class="bg-black/5 dark:bg-white/10 px-1 py-0.5 rounded text-xs">claude-opus-4-6-last</code>。
|
||||
</p>
|
||||
<p><span class="text-[#cc785c] font-medium mr-2">降/升级请求示例:</span>官方标准名称为 <code class="bg-black/5 dark:bg-white/10 px-1 py-0.5 rounded text-xs">claude-opus-4-6</code> ,实际请求提供商用 <code class="bg-black/5 dark:bg-white/10 px-1 py-0.5 rounded text-xs">claude-sonnet-4-6</code>。</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-model-mapping-1.webp"
|
||||
alt="模型映射 1"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-model-mapping-2.webp"
|
||||
alt="模型映射 2"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 8. 反向代理 -->
|
||||
<section
|
||||
id="reverse-proxy"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>8. 反向代理</h2>
|
||||
<div class="space-y-6 mt-6">
|
||||
<div>
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8] mb-3">
|
||||
1. Codex
|
||||
</h4>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-reverse-proxy-codex.webp"
|
||||
alt="Codex 反向代理"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full max-w-2xl"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8] mb-3">
|
||||
2. Krio
|
||||
</h4>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-reverse-proxy-kiro.webp"
|
||||
alt="Krio 反向代理"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full max-w-2xl"
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8] mb-3">
|
||||
3. Antigravity
|
||||
</h4>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-reverse-proxy-antigravity.webp"
|
||||
alt="Antigravity 反向代理"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full max-w-2xl"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 9. 优先级管理 -->
|
||||
<section
|
||||
id="priority-management"
|
||||
class="scroll-mt-24 lg:scroll-mt-20 pb-8"
|
||||
>
|
||||
<h2>9. 优先级管理</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
允许拖动、或者直接点击数字输入调整优先级。
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
|
||||
<div class="space-y-4">
|
||||
<div class="bg-white/50 dark:bg-white/5 p-4 rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.06)] shadow-sm">
|
||||
<h4 class="font-bold text-[#262624] dark:text-[#f1ead8] flex items-center gap-2">
|
||||
1. 提供商优先
|
||||
</h4>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
按提供商顺序调度,同优先级负载均衡。
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-xs text-[#666663] dark:text-[#a3a094]">
|
||||
了解调度、缓存亲和性、故障转移等核心策略
|
||||
<div class="bg-white/50 dark:bg-white/5 p-4 rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.06)] shadow-sm">
|
||||
<h4 class="font-bold text-[#262624] dark:text-[#f1ead8] flex items-center gap-2">
|
||||
2. Key优先
|
||||
</h4>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
全局Key统一调度,同优先级负载均衡。
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-white/50 dark:bg-white/5 p-4 rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.06)] shadow-sm">
|
||||
<h4 class="font-bold text-[#262624] dark:text-[#f1ead8] flex items-center gap-2">
|
||||
3. 缓存亲和模式
|
||||
</h4>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
在Key TTL时间的约束下,优先使用上一次请求使用的Key。
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-white/50 dark:bg-white/5 p-4 rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.06)] shadow-sm">
|
||||
<h4 class="font-bold text-[#262624] dark:text-[#f1ead8] flex items-center gap-2">
|
||||
4. 负载均衡模式
|
||||
</h4>
|
||||
<ul class="list-disc pl-5 mt-1 text-sm text-[#666663] dark:text-[#a3a094] space-y-1">
|
||||
<li>取消全局提供商优先级,提供商内部Key依然保持优先级。</li>
|
||||
<li>取消全局Key优先级,乱序使用。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-white/50 dark:bg-white/5 p-4 rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.06)] shadow-sm">
|
||||
<h4 class="font-bold text-[#262624] dark:text-[#f1ead8] flex items-center gap-2">
|
||||
5. 固定顺序模式
|
||||
</h4>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
|
||||
取消缓存亲和,始终使用固定顺序Key请求。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight class="h-4 w-4 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</RouterLink>
|
||||
|
||||
<div class="space-y-4">
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-priority-1.webp"
|
||||
alt="优先级管理 1"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/concepts-priority-2.webp"
|
||||
alt="优先级管理 2"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm w-full"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,217 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Search, ChevronDown, ExternalLink, HelpCircle } from 'lucide-vue-next'
|
||||
import { faqItems, panelClasses } from './guide-config'
|
||||
import { useSiteInfo } from '@/composables/useSiteInfo'
|
||||
import { reactive } from 'vue'
|
||||
import { HelpCircle, ChevronRight } from 'lucide-vue-next'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
baseUrl?: string
|
||||
}>(),
|
||||
const faqs = reactive([
|
||||
{
|
||||
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
|
||||
id: 1,
|
||||
question: '1. 为什么发生错误没有进行故障转移?',
|
||||
answer: '故障转移机制(Failover)依赖于配置的重试策略和上游返回的错误码类型。通常只有在遇到 429 (Too Many Requests) 或 5xx (Server Errors) 时,并且在”最大重试次数”允许的范围内,才会触发故障转移。对于 400 (Bad Request) 或 401 (Unauthorized) 这种客户端确切错误,为避免持续无效重试,系统可能直接中断请求并返回错误。同时也要检查调度模式是否支持故障转移(例如固定顺序模式或缓存亲和性强绑定时可能会影响行为)。',
|
||||
isOpen: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
question: '2. 如何排查请求不通的问题?',
|
||||
answer: '建议首先检查【系统设置】中的请求体记录等级是否设为 `Full`,以便抓取完整的上下行数据。然后检查对应的【提供商端点】的 Base URL 和 API 格式是否正确匹配。如果开启了带来,排查【代理配置】优先级。',
|
||||
isOpen: false
|
||||
}
|
||||
)
|
||||
])
|
||||
|
||||
const { siteName } = useSiteInfo()
|
||||
|
||||
// 搜索关键词
|
||||
const searchQuery = ref('')
|
||||
|
||||
// 展开的 FAQ
|
||||
const expandedIds = ref<Set<string>>(new Set())
|
||||
|
||||
// 过滤后的 FAQ
|
||||
const filteredFaqs = computed(() => {
|
||||
if (!searchQuery.value.trim()) {
|
||||
return faqItems
|
||||
}
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
return faqItems.filter(
|
||||
item =>
|
||||
item.question.toLowerCase().includes(query) ||
|
||||
item.answer.toLowerCase().includes(query)
|
||||
)
|
||||
})
|
||||
|
||||
// 按分类分组
|
||||
const faqsByCategory = computed(() => {
|
||||
const grouped: Record<string, typeof faqItems> = {}
|
||||
for (const item of filteredFaqs.value) {
|
||||
if (!grouped[item.category]) {
|
||||
grouped[item.category] = []
|
||||
}
|
||||
grouped[item.category].push(item)
|
||||
}
|
||||
return grouped
|
||||
})
|
||||
|
||||
// 切换展开状态
|
||||
function toggleExpand(id: string) {
|
||||
if (expandedIds.value.has(id)) {
|
||||
expandedIds.value.delete(id)
|
||||
} else {
|
||||
expandedIds.value.add(id)
|
||||
}
|
||||
}
|
||||
|
||||
// 全部展开/收起
|
||||
function toggleAll() {
|
||||
if (expandedIds.value.size === filteredFaqs.value.length) {
|
||||
expandedIds.value.clear()
|
||||
} else {
|
||||
expandedIds.value = new Set(filteredFaqs.value.map(item => item.id))
|
||||
}
|
||||
const toggleFaq = (index: number) => {
|
||||
faqs[index].isOpen = !faqs[index].isOpen
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 标题 -->
|
||||
<div class="space-y-12 pb-12">
|
||||
<!-- Hero 区域 -->
|
||||
<div class="space-y-4">
|
||||
<div class="inline-flex items-center gap-1.5 rounded-full bg-[#cc785c]/10 dark:bg-[#cc785c]/20 border border-[#cc785c]/20 dark:border-[#cc785c]/40 px-3 py-1 text-xs font-medium text-[#cc785c] dark:text-[#d4a27f]">
|
||||
<HelpCircle class="h-3 w-3" />
|
||||
答疑解惑
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
常见问题
|
||||
</h1>
|
||||
<p class="text-lg text-[#666663] dark:text-[#a3a094]">
|
||||
关于 {{ siteName }} 使用和配置的常见问题解答。
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094] max-w-2xl">
|
||||
在使用 Aether 过程中遇到的常见问题与排错指南。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<Search class="h-5 w-5 text-[#999]" />
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="搜索问题..."
|
||||
class="flex-1 bg-transparent border-none outline-none text-[#262624] dark:text-[#f1ead8] placeholder:text-[#999]"
|
||||
<section class="scroll-mt-24 lg:scroll-mt-20">
|
||||
<div class="space-y-4 mt-8">
|
||||
<div
|
||||
v-for="(faq, index) in faqs"
|
||||
:key="faq.id"
|
||||
class="bg-white/50 dark:bg-white/5 border border-[#e5e4df] dark:border-[rgba(227,224,211,0.06)] rounded-xl overflow-hidden shadow-sm transition-all"
|
||||
>
|
||||
<button
|
||||
v-if="filteredFaqs.length > 0"
|
||||
class="text-sm text-[#cc785c] hover:underline"
|
||||
@click="toggleAll"
|
||||
>
|
||||
{{ expandedIds.size === filteredFaqs.length ? '全部收起' : '全部展开' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FAQ 列表 -->
|
||||
<div
|
||||
v-if="filteredFaqs.length > 0"
|
||||
class="space-y-6"
|
||||
>
|
||||
<div
|
||||
v-for="category in Object.keys(faqsByCategory)"
|
||||
:key="category"
|
||||
class="space-y-3"
|
||||
>
|
||||
<h2 class="text-lg font-semibold text-[#262624] dark:text-[#f1ead8] flex items-center gap-2">
|
||||
<HelpCircle class="h-5 w-5 text-[#cc785c]" />
|
||||
{{ category }}
|
||||
</h2>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="faq in faqsByCategory[category]"
|
||||
:key="faq.id"
|
||||
class="overflow-hidden"
|
||||
:class="[panelClasses.section]"
|
||||
<button
|
||||
class="w-full flex items-center justify-between p-5 text-left hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
|
||||
@click="toggleFaq(index)"
|
||||
>
|
||||
<button
|
||||
class="w-full px-4 py-3 flex items-center justify-between text-left hover:bg-[#f5f5f0]/50 dark:hover:bg-[#1f1d1a]/50 transition-colors"
|
||||
@click="toggleExpand(faq.id)"
|
||||
>
|
||||
<span class="font-medium text-[#262624] dark:text-[#f1ead8] pr-4">
|
||||
{{ faq.question }}
|
||||
</span>
|
||||
<ChevronDown
|
||||
class="h-5 w-5 text-[#999] flex-shrink-0 transition-transform"
|
||||
:class="{ 'rotate-180': expandedIds.has(faq.id) }"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
v-show="expandedIds.has(faq.id)"
|
||||
class="px-4 pb-4 text-sm text-[#666663] dark:text-[#a3a094] whitespace-pre-line"
|
||||
>
|
||||
<h3 class="text-lg font-medium text-[#262624] dark:text-[#f1ead8] m-0">
|
||||
{{ faq.question }}
|
||||
</h3>
|
||||
<ChevronRight
|
||||
class="w-5 h-5 text-[#91918d] dark:text-[#a3a094] transition-transform duration-200"
|
||||
:class="{ 'rotate-90 text-[#cc785c]': faq.isOpen }"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div
|
||||
v-show="faq.isOpen"
|
||||
class="px-5 pb-5 pt-0 text-sm text-[#666663] dark:text-[#a3a094] leading-relaxed border-t border-[#e5e4df]/50 dark:border-[rgba(227,224,211,0.06)] mt-2"
|
||||
>
|
||||
<div class="pt-4">
|
||||
{{ faq.answer }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无结果 -->
|
||||
<div
|
||||
v-else
|
||||
class="p-8 text-center"
|
||||
:class="[panelClasses.section]"
|
||||
>
|
||||
<HelpCircle class="h-12 w-12 text-[#999] mx-auto mb-4" />
|
||||
<p class="text-[#666663] dark:text-[#a3a094]">
|
||||
没有找到匹配的问题,请尝试其他关键词
|
||||
</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">
|
||||
<a
|
||||
href="https://github.com/your-repo/aether"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<div class="p-2 rounded-lg bg-gray-500/10">
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">GitHub</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">查看源码、提交 Issue</div>
|
||||
</div>
|
||||
<ExternalLink class="h-4 w-4 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://github.com/your-repo/aether/discussions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="p-4 flex items-center gap-3 group"
|
||||
:class="[panelClasses.section, panelClasses.cardHover]"
|
||||
>
|
||||
<div class="p-2 rounded-lg bg-blue-500/10">
|
||||
<svg
|
||||
class="h-5 w-5 text-blue-500"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">Discussions</div>
|
||||
<div class="text-sm text-[#666663] dark:text-[#a3a094]">社区讨论、功能建议</div>
|
||||
</div>
|
||||
<ExternalLink class="h-4 w-4 text-[#999] group-hover:text-[#cc785c] transition-colors" />
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -62,13 +62,13 @@
|
||||
/>
|
||||
</RouterLink>
|
||||
|
||||
<!-- 快速开始子导航 -->
|
||||
<!-- 子导航 -->
|
||||
<div
|
||||
v-if="item.id === 'overview' && isNavActive(item.path)"
|
||||
class="ml-7 space-y-0.5 mt-0.5"
|
||||
v-if="item.subItems && isNavActive(item.path)"
|
||||
class="ml-7 space-y-0.5 mt-0.5 mb-2"
|
||||
>
|
||||
<a
|
||||
v-for="sub in overviewSubItems"
|
||||
v-for="sub in item.subItems"
|
||||
:key="sub.hash"
|
||||
:href="sub.hash"
|
||||
class="flex items-center gap-2 px-2.5 py-1.5 rounded-md text-[12px] transition-colors"
|
||||
@@ -273,21 +273,55 @@
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<article
|
||||
class="max-w-4xl mx-auto pb-24"
|
||||
@click="onArticleClick"
|
||||
>
|
||||
<RouterView
|
||||
v-slot="{ Component }"
|
||||
>
|
||||
<component
|
||||
:is="Component"
|
||||
:base-url="baseUrl"
|
||||
/>
|
||||
<transition
|
||||
name="fade"
|
||||
mode="out-in"
|
||||
>
|
||||
<component
|
||||
:is="Component"
|
||||
:base-url="baseUrl"
|
||||
class="literary-content"
|
||||
/>
|
||||
</transition>
|
||||
</RouterView>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Image Lightbox -->
|
||||
<Teleport to="body">
|
||||
<Transition
|
||||
enter-active-class="transition duration-200 ease-out"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition duration-150 ease-in"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div
|
||||
v-if="lightboxSrc"
|
||||
class="fixed inset-0 z-[100] flex items-center justify-center bg-black/80 backdrop-blur-sm cursor-zoom-out"
|
||||
@click="lightboxSrc = ''"
|
||||
>
|
||||
<img
|
||||
:src="lightboxSrc"
|
||||
:alt="lightboxAlt"
|
||||
class="max-w-[90vw] max-h-[90vh] object-contain rounded-xl shadow-2xl"
|
||||
@click.stop
|
||||
>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</AppShell>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { RouterLink, RouterView, useRoute } from 'vue-router'
|
||||
import {
|
||||
Menu,
|
||||
@@ -310,27 +344,92 @@ const { siteName, siteSubtitle } = useSiteInfo()
|
||||
|
||||
const mobileMenuOpen = ref(false)
|
||||
const baseUrl = ref(typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com')
|
||||
const activeHash = ref('#production')
|
||||
const activeHash = ref('')
|
||||
const lightboxSrc = ref('')
|
||||
const lightboxAlt = ref('')
|
||||
|
||||
// 快速开始子导航
|
||||
const overviewSubItems = [
|
||||
{ name: '部署', hash: '#production' },
|
||||
{ name: '配置流程', hash: '#config-steps' },
|
||||
{ name: 'API 格式', hash: '#api-formats' },
|
||||
{ name: '推荐帖子', hash: '#recommended-posts' }
|
||||
]
|
||||
function onArticleClick(e: MouseEvent) {
|
||||
const target = e.target as HTMLElement
|
||||
if (target.tagName === 'IMG' && target.closest('.literary-content')) {
|
||||
const img = target as HTMLImageElement
|
||||
lightboxSrc.value = img.src
|
||||
lightboxAlt.value = img.alt || ''
|
||||
}
|
||||
}
|
||||
|
||||
let observer: IntersectionObserver | null = null
|
||||
|
||||
function getScrollContainer(): Element | null {
|
||||
return document.querySelector('.app-shell__content')
|
||||
}
|
||||
|
||||
function setupIntersectionObserver() {
|
||||
if (observer) {
|
||||
observer.disconnect()
|
||||
}
|
||||
|
||||
const scrollRoot = getScrollContainer()
|
||||
|
||||
observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const visibleEntries = entries.filter((entry) => entry.isIntersecting)
|
||||
if (visibleEntries.length > 0) {
|
||||
const topEntry = visibleEntries.reduce((prev, current) => {
|
||||
return (current.boundingClientRect.top < prev.boundingClientRect.top) ? current : prev
|
||||
})
|
||||
activeHash.value = `#${topEntry.target.id}`
|
||||
}
|
||||
},
|
||||
{
|
||||
root: scrollRoot,
|
||||
rootMargin: '-80px 0px -70% 0px',
|
||||
threshold: 0
|
||||
}
|
||||
)
|
||||
|
||||
const sections = document.querySelectorAll('article section[id]')
|
||||
sections.forEach((section) => observer?.observe(section))
|
||||
}
|
||||
|
||||
function scrollToHash(hash: string) {
|
||||
activeHash.value = hash
|
||||
const el = document.querySelector(hash)
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
const container = getScrollContainer()
|
||||
if (el && container) {
|
||||
const elTop = el.getBoundingClientRect().top
|
||||
const containerTop = container.getBoundingClientRect().top
|
||||
const offset = elTop - containerTop + container.scrollTop - 80
|
||||
container.scrollTo({ top: offset, behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
// 路由变化时关闭移动端菜单
|
||||
watch(() => route.path, () => {
|
||||
mobileMenuOpen.value = false
|
||||
// 路由变化时管理状态和观察者
|
||||
watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
mobileMenuOpen.value = false
|
||||
activeHash.value = ''
|
||||
nextTick(() => {
|
||||
setupIntersectionObserver()
|
||||
const firstSection = document.querySelector('article section[id]')
|
||||
if (!activeHash.value && firstSection) {
|
||||
activeHash.value = `#${firstSection.id}`
|
||||
}
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
setupIntersectionObserver()
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (observer) {
|
||||
observer.disconnect()
|
||||
}
|
||||
})
|
||||
|
||||
const currentNavItem = computed(() => {
|
||||
@@ -347,6 +446,7 @@ function isNavActive(href: string) {
|
||||
// 移动端菜单用的导航数据
|
||||
const navigation = computed(() => [
|
||||
{
|
||||
title: '',
|
||||
items: guideNavItems.map(item => ({
|
||||
name: item.name,
|
||||
href: item.path,
|
||||
@@ -365,11 +465,64 @@ const contentClasses = computed(() => {
|
||||
})
|
||||
|
||||
const mainClasses = computed(() => {
|
||||
return 'pt-24 lg:pt-6'
|
||||
return 'pt-24 lg:pt-8'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scrollbar-none::-webkit-scrollbar { display: none; }
|
||||
.scrollbar-none { -ms-overflow-style: none; scrollbar-width: none; }
|
||||
|
||||
/* Literary Tech Typography Overrides for Guide Content */
|
||||
:deep(.literary-content) h2 {
|
||||
@apply text-2xl mb-8 mt-12 flex items-center gap-3 transition-colors;
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.015em;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
:deep(.literary-content) h3 {
|
||||
@apply text-xl mb-6 mt-10 transition-colors;
|
||||
font-family: var(--serif);
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
:deep(.literary-content) p:not([class*="text-sm"]):not([class*="text-xs"]) {
|
||||
font-family: var(--serif);
|
||||
font-weight: 400;
|
||||
@apply leading-relaxed text-[1.05rem] mb-4;
|
||||
color: var(--color-text);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
:deep(.literary-content) li:not([class*="text-sm"]):not([class*="text-xs"]) {
|
||||
font-family: var(--serif);
|
||||
font-weight: 400;
|
||||
@apply leading-relaxed text-[1.05rem] mb-2;
|
||||
color: var(--color-text);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* UI Elements inside content should remain sans-serif */
|
||||
:deep(.literary-content) button,
|
||||
:deep(.literary-content) input,
|
||||
:deep(.literary-content) select,
|
||||
:deep(.literary-content) label,
|
||||
:deep(.literary-content) table,
|
||||
:deep(.literary-content) .font-mono,
|
||||
:deep(.literary-content) [class*="font-mono"] {
|
||||
font-family: var(--sans-serif);
|
||||
}
|
||||
|
||||
:deep(.literary-content) pre,
|
||||
:deep(.literary-content) code {
|
||||
font-family: var(--monospace) !important;
|
||||
}
|
||||
|
||||
:deep(.literary-content) img {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
</style>
|
||||
|
||||
151
frontend/src/views/public/guide/ModulesGuide.vue
Normal file
151
frontend/src/views/public/guide/ModulesGuide.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<script setup lang="ts">
|
||||
import { Blocks } from 'lucide-vue-next'
|
||||
import { panelClasses } from './guide-config'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-12 pb-12">
|
||||
<!-- Hero 区域 -->
|
||||
<div class="space-y-4">
|
||||
<div class="inline-flex items-center gap-1.5 rounded-full bg-[#cc785c]/10 dark:bg-[#cc785c]/20 border border-[#cc785c]/20 dark:border-[#cc785c]/40 px-3 py-1 text-xs font-medium text-[#cc785c] dark:text-[#d4a27f]">
|
||||
<Blocks class="h-3 w-3" />
|
||||
系统扩展
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
模块管理
|
||||
</h1>
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094] max-w-2xl">
|
||||
Aether 提供高可插入的模块化管理机制,帮助连接外部服务和授权系统。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-8">
|
||||
<!-- 1. 访问令牌 -->
|
||||
<section
|
||||
id="management-tokens"
|
||||
:class="[panelClasses.card]"
|
||||
class="p-6 scroll-mt-24 lg:scroll-mt-20 flex flex-col"
|
||||
>
|
||||
<div class="w-12 h-12 rounded-xl bg-purple-500/10 text-purple-600 dark:text-purple-400 flex items-center justify-center mb-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-key-round"
|
||||
><path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z" /><circle
|
||||
cx="16.5"
|
||||
cy="7.5"
|
||||
r=".5"
|
||||
fill="currentColor"
|
||||
/></svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-[#262624] dark:text-[#f1ead8] mb-2 m-0 mt-0 pt-0 border-0">
|
||||
1. 访问令牌
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] leading-relaxed">
|
||||
允许通过 Management Token 授权使用全部或部分后端敏感接口(如自动化运维脚本接入)。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 2. 邮件配置 -->
|
||||
<section
|
||||
id="email-config"
|
||||
:class="[panelClasses.card]"
|
||||
class="p-6 scroll-mt-24 lg:scroll-mt-20 flex flex-col"
|
||||
>
|
||||
<div class="w-12 h-12 rounded-xl bg-blue-500/10 text-blue-600 dark:text-blue-400 flex items-center justify-center mb-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-mail"
|
||||
><rect
|
||||
width="20"
|
||||
height="16"
|
||||
x="2"
|
||||
y="4"
|
||||
rx="2"
|
||||
/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" /></svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-[#262624] dark:text-[#f1ead8] mb-2 m-0 mt-0 pt-0 border-0">
|
||||
2. 邮件配置
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] leading-relaxed">
|
||||
用于注册验证及账户通知服务。管理员可以自定义 SMTP 服务器并高度定制系统发送的各种 HTML 邮件模板。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 3. OAuth 登录 -->
|
||||
<section
|
||||
id="oauth-login"
|
||||
:class="[panelClasses.card]"
|
||||
class="p-6 scroll-mt-24 lg:scroll-mt-20 flex flex-col"
|
||||
>
|
||||
<div class="w-12 h-12 rounded-xl bg-orange-500/10 text-orange-600 dark:text-orange-400 flex items-center justify-center mb-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-user-check"
|
||||
><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle
|
||||
cx="9"
|
||||
cy="7"
|
||||
r="4"
|
||||
/><polyline points="16 11 18 13 22 9" /></svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-[#262624] dark:text-[#f1ead8] mb-2 m-0 mt-0 pt-0 border-0">
|
||||
3. OAuth 登录
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] leading-relaxed">
|
||||
支持包括 Linux Do OAuth 等多种第三方授权验证登录,方便统一认证渠道并提升用户体验。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- 4. LDAP 认证 -->
|
||||
<section
|
||||
id="ldap-auth"
|
||||
:class="[panelClasses.card]"
|
||||
class="p-6 scroll-mt-24 lg:scroll-mt-20 flex flex-col"
|
||||
>
|
||||
<div class="w-12 h-12 rounded-xl bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 flex items-center justify-center mb-4">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-building-2"
|
||||
><path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z" /><path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2" /><path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2" /><path d="M10 6h4" /><path d="M10 10h4" /><path d="M10 14h4" /><path d="M10 18h4" /></svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-[#262624] dark:text-[#f1ead8] mb-2 m-0 mt-0 pt-0 border-0">
|
||||
4. LDAP 认证
|
||||
</h3>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] leading-relaxed">
|
||||
为企业管理构建。直接对接企业现有的 LDAP 或 Active Directory 目录以映射内网员工账户和组织架构。
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,610 +1,130 @@
|
||||
<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 级'
|
||||
}
|
||||
]
|
||||
import { Target } from 'lucide-vue-next'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<!-- 标题 -->
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-12 pb-12">
|
||||
<!-- Hero 区域 -->
|
||||
<div class="space-y-4">
|
||||
<div class="inline-flex items-center gap-1.5 rounded-full bg-[#cc785c]/10 dark:bg-[#cc785c]/20 border border-[#cc785c]/20 dark:border-[#cc785c]/40 px-3 py-1 text-xs font-medium text-[#cc785c] dark:text-[#d4a27f]">
|
||||
<Target class="h-3 w-3" />
|
||||
调度与策略
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
|
||||
关键策略
|
||||
</h1>
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094]">
|
||||
{{ siteName }} 的调度、缓存亲和性、故障转移、并发控制等核心策略机制。
|
||||
<p class="text-base text-[#666663] dark:text-[#a3a094] max-w-2xl">
|
||||
了解 Aether 内部的日志记录、智能调度模式以及服务限制策略。
|
||||
</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 组合。调度模式决定了候选的排序方式:
|
||||
<section
|
||||
id="request-logging"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>1. 请求体记录</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
在系统设置中,您可以修改请求体记录详情等级,以便于调试和审计。
|
||||
</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"
|
||||
|
||||
<div class="overflow-hidden rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-white dark:bg-[#191714] shadow-sm max-w-2xl">
|
||||
<table class="w-full text-sm text-left">
|
||||
<thead class="bg-[#f5f5f0] dark:bg-[rgba(227,224,211,0.05)] border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)]">
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-6 py-3 font-medium text-[#262624] dark:text-[#f1ead8]"
|
||||
>
|
||||
<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"
|
||||
日志等级
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-6 py-3 font-medium text-[#262624] dark:text-[#f1ead8]"
|
||||
>
|
||||
<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>
|
||||
记录内容
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-[#e5e4df] dark:divide-[rgba(227,224,211,0.06)]">
|
||||
<tr class="hover:bg-black/5 dark:hover:bg-white/5 transition-colors">
|
||||
<td class="px-6 py-4 font-mono font-medium text-[#cc785c] dark:text-[#d4a27f]">
|
||||
Base
|
||||
</td>
|
||||
<td class="px-6 py-4 text-[#666663] dark:text-[#a3a094]">
|
||||
基本请求信息(IP, 模型, 耗时, Token 等)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-black/5 dark:hover:bg-white/5 transition-colors">
|
||||
<td class="px-6 py-4 font-mono font-medium text-[#cc785c] dark:text-[#d4a27f]">
|
||||
Headers
|
||||
</td>
|
||||
<td class="px-6 py-4 text-[#666663] dark:text-[#a3a094]">
|
||||
Base + 请求头 (Headers)
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover:bg-black/5 dark:hover:bg-white/5 transition-colors border-b-0">
|
||||
<td class="px-6 py-4 font-mono font-medium text-[#cc785c] dark:text-[#d4a27f]">
|
||||
Full
|
||||
</td>
|
||||
<td class="px-6 py-4 text-[#666663] dark:text-[#a3a094]">
|
||||
Headers + 完整的请求体与响应体 (Payloads)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="p-4"
|
||||
:class="[panelClasses.section]"
|
||||
|
||||
<img
|
||||
loading="lazy"
|
||||
src="/guide/strategy-request-logging.webp"
|
||||
alt="请求体记录设置"
|
||||
class="rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] shadow-sm mt-6 w-full max-w-3xl"
|
||||
>
|
||||
<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
|
||||
id="scheduling"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>2. 调度模式</h2>
|
||||
<ul class="list-decimal pl-5 space-y-2 mt-4 text-[#666663] dark:text-[#a3a094] text-sm">
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">提供商优先:</strong> 优先根据提供商设置的顺序进行调度。</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">Key优先:</strong> 无视提供商层级,直接在所有可用的 Key 之间根据优先级进行调度。</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">缓存亲和:</strong> 尽量将相同用户的请求路由到之前处理过该用户请求的提供商/节点,以最大化利用上游缓存。</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">负载均衡:</strong> 在相同优先级的节点之间均匀分配流量。</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">固定顺序:</strong> 取消随机性与动态调整,严格按照固定的顺序遍历尝试。</li>
|
||||
<li><strong class="text-[#262624] dark:text-[#f1ead8] font-medium">故障转移:</strong> 当请求失败时,根据策略自动切换到下一个可用的备用节点进行重试。</li>
|
||||
</ul>
|
||||
</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
|
||||
id="rate-limit"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>3. 访问限制</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
系统支持多种维度的访问频率限制(Rate Limit),有效防止恶意请求或滥用,保障服务稳定性。
|
||||
</p>
|
||||
</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
|
||||
id="payload-cleanup"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>4. 请求体压缩清理</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
为节省数据库空间与提高查询性能,系统提供自动请求体清理与压缩策略,将历史请求详情定期冷热分离并清理。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="cron-tasks"
|
||||
class="scroll-mt-24 lg:scroll-mt-20"
|
||||
>
|
||||
<h2>5. 定时任务</h2>
|
||||
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
平台内置多个定时任务,用于模型列表同步、缓存清理、余额监控及统计数据聚合等周期性操作。
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,706 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useDarkMode } from '@/composables/useDarkMode'
|
||||
|
||||
const { isDark } = useDarkMode()
|
||||
|
||||
// Ultra-premium floating aesthetic palette
|
||||
const colors = computed(() => {
|
||||
const brandVal = isDark.value ? '#d4a27f' : '#cc785c'
|
||||
return {
|
||||
// Pure, infinite canvas
|
||||
bg: isDark.value ? '#080808' : '#fafafa',
|
||||
grid: isDark.value ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)',
|
||||
|
||||
// Floating Cards - completely solid, crisp edges
|
||||
cardBg: isDark.value ? '#121212' : '#ffffff',
|
||||
cardBorder: isDark.value ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.08)',
|
||||
|
||||
// Core Card
|
||||
coreBg: isDark.value ? '#171615' : '#ffffff',
|
||||
coreBorder: isDark.value ? 'rgba(212, 162, 127, 0.3)' : 'rgba(204, 120, 92, 0.25)',
|
||||
|
||||
// Text
|
||||
textMain: isDark.value ? '#f5f5f5' : '#111111',
|
||||
textMuted: isDark.value ? '#888888' : '#777777',
|
||||
|
||||
// Thematic Flow Colors
|
||||
brand: brandVal, // Ingress
|
||||
convertAccent: '#a855f7', // Conversion
|
||||
passAccent: '#3b82f6', // Passthrough
|
||||
returnAccent: '#10b981', // Loop
|
||||
proxyAccent: '#f59e0b', // Proxies
|
||||
|
||||
// The fluid river tracks
|
||||
trackMain: isDark.value ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.05)'
|
||||
}
|
||||
})
|
||||
|
||||
// Clean, precise floating shadow
|
||||
const shadows = computed(() => {
|
||||
return isDark.value
|
||||
? {
|
||||
node: '0 8px 30px rgba(0,0,0,0.6)',
|
||||
coreGlow: '0 0 40px rgba(212, 162, 127, 0.03)',
|
||||
}
|
||||
: {
|
||||
node: '0 8px 30px rgba(0,0,0,0.06)',
|
||||
coreGlow: '0 0 40px rgba(204, 120, 92, 0.03)',
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative w-full overflow-hidden rounded-[32px] border diagram-container"
|
||||
:style="{
|
||||
backgroundColor: colors.bg,
|
||||
borderColor: colors.cardBorder,
|
||||
/* Hyper-clean infinite Dot Grid Background */
|
||||
backgroundImage: `radial-gradient(${colors.grid} 1px, transparent 1px)`,
|
||||
backgroundSize: '24px 24px'
|
||||
}"
|
||||
>
|
||||
<!-- Horizontal scroll wrapper for the dynamic wide canvas -->
|
||||
<div class="w-full overflow-x-auto pb-4 custom-scrollbar">
|
||||
<!-- Absolute, unconstrained canvas simulating an infinite whiteboard -->
|
||||
<!-- Width extends far enough to fit all spaced-out floating nodes -->
|
||||
<div class="relative min-w-[1300px] h-[680px] mx-auto overflow-visible py-16 px-10">
|
||||
<!-- ==================== FLUID SVG "RIVERS" (The glowing tracks) ==================== -->
|
||||
<!-- Placed perfectly behind the HTML nodes. Z-0 -->
|
||||
<svg
|
||||
class="absolute inset-0 w-full h-full pointer-events-none z-0 overflow-visible"
|
||||
viewBox="0 0 1300 680"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
|
||||
<!-- Defs for beautiful organic gradients -->
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="grad-convert"
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="100%"
|
||||
y2="0%"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
:stop-color="colors.brand"
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
:stop-color="colors.convertAccent"
|
||||
/>
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="grad-pass"
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="100%"
|
||||
y2="100%"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
:stop-color="colors.brand"
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
:stop-color="colors.passAccent"
|
||||
/>
|
||||
</linearGradient>
|
||||
<!-- Soft glow filter for the tracks themselves -->
|
||||
<filter
|
||||
id="glow-track"
|
||||
x="-20%"
|
||||
y="-20%"
|
||||
width="140%"
|
||||
height="140%"
|
||||
>
|
||||
<feGaussianBlur
|
||||
stdDeviation="3"
|
||||
result="blur"
|
||||
/>
|
||||
<feComposite
|
||||
in="SourceGraphic"
|
||||
in2="blur"
|
||||
operator="over"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- The Base Tracks (Ghost Rivers) -->
|
||||
<g
|
||||
:stroke="colors.trackMain"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
>
|
||||
|
||||
<!-- Ingress Tributaries: 3 Clients -> 1 Core -->
|
||||
<!-- Starting X: 220 (clients), Ending X: 380 (core) -->
|
||||
<path
|
||||
id="flow-in-1"
|
||||
d="M 220 180 C 300 180, 270 300, 380 300"
|
||||
/>
|
||||
<path
|
||||
id="flow-in-2"
|
||||
d="M 220 300 L 380 300"
|
||||
/>
|
||||
<path
|
||||
id="flow-in-3"
|
||||
d="M 220 420 C 300 420, 270 300, 380 300"
|
||||
/>
|
||||
|
||||
<!-- Egress Delta: Core -> 2 Flow Engines -->
|
||||
<!-- Starting X: 580 (core), Ending X: 750 (engines) -->
|
||||
<!-- Beautiful fluid Bezier splits -->
|
||||
<path
|
||||
id="flow-out-top"
|
||||
d="M 580 300 C 650 300, 650 200, 750 200"
|
||||
/>
|
||||
<path
|
||||
id="flow-out-bot"
|
||||
d="M 580 300 C 650 300, 650 400, 750 400"
|
||||
/>
|
||||
|
||||
<!-- To the Cloud Delta: Processors -> Destinations -->
|
||||
<!-- Starting X: 950 (engines), Ending X: 1100 (destinations) -->
|
||||
|
||||
<!-- From Purple Mapping Engine -->
|
||||
<path
|
||||
id="flow-cloud-1"
|
||||
d="M 950 200 C 1030 200, 1030 140, 1100 140"
|
||||
/>
|
||||
<path
|
||||
id="flow-cloud-2"
|
||||
d="M 950 200 C 1020 200, 1020 200, 1100 200"
|
||||
/>
|
||||
<path
|
||||
id="flow-cloud-3"
|
||||
d="M 950 200 C 1030 200, 1030 260, 1100 260"
|
||||
/>
|
||||
|
||||
<!-- From Blue Passthrough Engine -->
|
||||
<path
|
||||
id="flow-proxy-1"
|
||||
d="M 950 400 C 1030 400, 1030 360, 1100 360"
|
||||
/>
|
||||
<path
|
||||
id="flow-proxy-2"
|
||||
d="M 950 400 C 1030 400, 1030 420, 1100 420"
|
||||
/>
|
||||
<path
|
||||
id="flow-proxy-3"
|
||||
d="M 950 400 C 1030 400, 1030 480, 1100 480"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<!-- The Majestic Return Arch (Response Loop) -->
|
||||
<g
|
||||
:stroke="colors.returnAccent"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
opacity="0.25"
|
||||
>
|
||||
<path
|
||||
id="flow-return"
|
||||
d="M 1150 510 C 1150 630, 950 640, 600 640 C 250 640, 120 600, 120 480"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<!-- ==================== GLOWING DATA PACKET ANIMATIONS ==================== -->
|
||||
<!-- We use slightly larger, softer glowing circles to emphasize the "fluid" nature -->
|
||||
|
||||
<g style="filter: drop-shadow(0 0 8px currentColor)">
|
||||
<!-- Ingress Flow -->
|
||||
<circle
|
||||
r="4"
|
||||
:fill="colors.brand"
|
||||
>
|
||||
<animateMotion
|
||||
dur="2.2s"
|
||||
repeatCount="indefinite"
|
||||
keyPoints="0;1"
|
||||
keyTimes="0;1"
|
||||
calcMode="spline"
|
||||
keySplines="0.4 0 0.2 1"
|
||||
><mpath href="#flow-in-1" /></animateMotion>
|
||||
</circle>
|
||||
<circle
|
||||
r="4"
|
||||
:fill="colors.brand"
|
||||
>
|
||||
<animateMotion
|
||||
dur="1.8s"
|
||||
begin="0.5s"
|
||||
repeatCount="indefinite"
|
||||
keyPoints="0;1"
|
||||
keyTimes="0;1"
|
||||
calcMode="spline"
|
||||
keySplines="0.4 0 0.2 1"
|
||||
><mpath href="#flow-in-2" /></animateMotion>
|
||||
</circle>
|
||||
<circle
|
||||
r="4"
|
||||
:fill="colors.brand"
|
||||
>
|
||||
<animateMotion
|
||||
dur="2.4s"
|
||||
begin="0.2s"
|
||||
repeatCount="indefinite"
|
||||
keyPoints="0;1"
|
||||
keyTimes="0;1"
|
||||
calcMode="spline"
|
||||
keySplines="0.4 0 0.2 1"
|
||||
><mpath href="#flow-in-3" /></animateMotion>
|
||||
</circle>
|
||||
|
||||
<!-- Delta Splits (Using gradients) -->
|
||||
<circle
|
||||
r="4.5"
|
||||
:fill="colors.convertAccent"
|
||||
>
|
||||
<animateMotion
|
||||
dur="2s"
|
||||
repeatCount="indefinite"
|
||||
keyPoints="0;1"
|
||||
keyTimes="0;1"
|
||||
calcMode="spline"
|
||||
keySplines="0.4 0 0.2 1"
|
||||
><mpath href="#flow-out-top" /></animateMotion>
|
||||
</circle>
|
||||
<circle
|
||||
r="4.5"
|
||||
:fill="colors.passAccent"
|
||||
>
|
||||
<!-- Make this packet slightly pulsing -->
|
||||
<animate
|
||||
attributeName="r"
|
||||
values="4;5;4"
|
||||
dur="1s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animateMotion
|
||||
dur="2.1s"
|
||||
begin="0.8s"
|
||||
repeatCount="indefinite"
|
||||
keyPoints="0;1"
|
||||
keyTimes="0;1"
|
||||
calcMode="spline"
|
||||
keySplines="0.4 0 0.2 1"
|
||||
><mpath href="#flow-out-bot" /></animateMotion>
|
||||
</circle>
|
||||
|
||||
<!-- Cloud Destiny -->
|
||||
<circle
|
||||
r="3.5"
|
||||
:fill="colors.convertAccent"
|
||||
>
|
||||
<animateMotion
|
||||
dur="1.5s"
|
||||
repeatCount="indefinite"
|
||||
keyPoints="0;1"
|
||||
keyTimes="0;1"
|
||||
calcMode="spline"
|
||||
keySplines="0.4 0 0.2 1"
|
||||
><mpath href="#flow-cloud-1" /></animateMotion>
|
||||
</circle>
|
||||
<circle
|
||||
r="3.5"
|
||||
:fill="colors.convertAccent"
|
||||
>
|
||||
<animateMotion
|
||||
dur="1.4s"
|
||||
begin="0.4s"
|
||||
repeatCount="indefinite"
|
||||
keyPoints="0;1"
|
||||
keyTimes="0;1"
|
||||
calcMode="spline"
|
||||
keySplines="0.4 0 0.2 1"
|
||||
><mpath href="#flow-cloud-3" /></animateMotion>
|
||||
</circle>
|
||||
<circle
|
||||
r="3.5"
|
||||
:fill="colors.passAccent"
|
||||
>
|
||||
<animateMotion
|
||||
dur="1.6s"
|
||||
repeatCount="indefinite"
|
||||
keyPoints="0;1"
|
||||
keyTimes="0;1"
|
||||
calcMode="spline"
|
||||
keySplines="0.4 0 0.2 1"
|
||||
><mpath href="#flow-proxy-1" /></animateMotion>
|
||||
</circle>
|
||||
<circle
|
||||
r="3.5"
|
||||
:fill="colors.passAccent"
|
||||
>
|
||||
<animateMotion
|
||||
dur="1.5s"
|
||||
begin="0.7s"
|
||||
repeatCount="indefinite"
|
||||
keyPoints="0;1"
|
||||
keyTimes="0;1"
|
||||
calcMode="spline"
|
||||
keySplines="0.4 0 0.2 1"
|
||||
><mpath href="#flow-proxy-3" /></animateMotion>
|
||||
</circle>
|
||||
</g>
|
||||
|
||||
<!-- Return Path Slow Comets -->
|
||||
<circle
|
||||
r="5"
|
||||
:fill="colors.returnAccent"
|
||||
style="filter: drop-shadow(0 0 10px currentColor)"
|
||||
>
|
||||
<animateMotion
|
||||
dur="6s"
|
||||
repeatCount="indefinite"
|
||||
><mpath href="#flow-return" /></animateMotion>
|
||||
</circle>
|
||||
<circle
|
||||
r="5"
|
||||
:fill="colors.returnAccent"
|
||||
style="filter: drop-shadow(0 0 10px currentColor)"
|
||||
>
|
||||
<animateMotion
|
||||
dur="6s"
|
||||
begin="3s"
|
||||
repeatCount="indefinite"
|
||||
><mpath href="#flow-return" /></animateMotion>
|
||||
</circle>
|
||||
|
||||
</svg>
|
||||
|
||||
<!-- ========================================================================= -->
|
||||
<!-- FREE FLOATING HTML NODES -->
|
||||
<!-- Beautifully positioned to align exactly with the ends of the SVG rivers -->
|
||||
<!-- ========================================================================= -->
|
||||
|
||||
<!-- 1. INGRESS CLIENTS -->
|
||||
<div class="absolute left-10 top-[160px] flex flex-col gap-[80px] z-10 w-[140px]">
|
||||
<div class="absolute -top-[45px] font-sans text-[11px] font-bold tracking-[0.2em] uppercase opacity-40 ml-4 flex gap-2 items-center">
|
||||
<div
|
||||
class="w-1.5 h-1.5 rounded-full"
|
||||
:style="{ backgroundColor: colors.textMuted }"
|
||||
/>Sources
|
||||
</div>
|
||||
<!-- Nodes -->
|
||||
<div
|
||||
class="h-[40px] rounded-xl flex items-center px-4 gap-3 bg-white dark:bg-[#121212] transition-transform hover:-translate-y-1 cursor-pointer"
|
||||
:style="{ border: `1px solid ${colors.cardBorder}`, boxShadow: shadows.node }"
|
||||
>
|
||||
<div
|
||||
class="w-2 h-2 rounded-full"
|
||||
:style="{ backgroundColor: colors.brand }"
|
||||
/>
|
||||
<span
|
||||
class="font-sans text-[12px] font-semibold tracking-wide"
|
||||
:style="{ color: colors.textMain }"
|
||||
>Claude</span>
|
||||
</div>
|
||||
<div
|
||||
class="h-[40px] rounded-xl flex items-center px-4 gap-3 bg-white dark:bg-[#121212] transition-transform hover:-translate-y-1 cursor-pointer"
|
||||
:style="{ border: `1px solid ${colors.cardBorder}`, boxShadow: shadows.node }"
|
||||
>
|
||||
<div
|
||||
class="w-2 h-2 rounded-full"
|
||||
:style="{ backgroundColor: colors.brand }"
|
||||
/>
|
||||
<span
|
||||
class="font-sans text-[12px] font-semibold tracking-wide"
|
||||
:style="{ color: colors.textMain }"
|
||||
>OpenAI</span>
|
||||
</div>
|
||||
<div
|
||||
class="h-[40px] rounded-xl flex items-center px-4 gap-3 bg-white dark:bg-[#121212] transition-transform hover:-translate-y-1 cursor-pointer"
|
||||
:style="{ border: `1px solid ${colors.cardBorder}`, boxShadow: shadows.node }"
|
||||
>
|
||||
<div
|
||||
class="w-2 h-2 rounded-full"
|
||||
:style="{ backgroundColor: colors.brand }"
|
||||
/>
|
||||
<span
|
||||
class="font-sans text-[12px] font-semibold tracking-wide"
|
||||
:style="{ color: colors.textMain }"
|
||||
>Gemini</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 2. THE AETHER NEXUS (Core Gateway) -->
|
||||
<!-- Perfectly centered vertically. Y anchors to 300px -->
|
||||
<div
|
||||
class="absolute left-[380px] top-[140px] w-[200px] rounded-[24px] py-8 px-5 z-20 flex flex-col items-center transition-transform hover:scale-[1.02]"
|
||||
:style="{ backgroundColor: colors.coreBg, border: `1px solid ${colors.coreBorder}`, boxShadow: `${shadows.node}, ${shadows.coreGlow}` }"
|
||||
>
|
||||
<div
|
||||
class="absolute -top-3.5 px-3 py-0.5 rounded-full shadow-sm bg-white dark:bg-[#111] text-[10px] font-bold tracking-[0.2em] font-sans"
|
||||
:style="{ color: colors.brand, border: `1px solid ${colors.cardBorder}` }"
|
||||
>
|
||||
AETHER
|
||||
</div>
|
||||
|
||||
<h3 class="text-xl font-bold font-sans tracking-[0.1em] mb-8 text-transparent bg-clip-text bg-gradient-to-r from-neutral-800 to-neutral-400 dark:from-white dark:to-neutral-500">
|
||||
GATEWAY
|
||||
</h3>
|
||||
|
||||
<!-- Internal Micro-Pills -->
|
||||
<div class="flex flex-col gap-2.5 w-full">
|
||||
<div
|
||||
class="h-[32px] rounded-lg border-b flex items-center justify-center font-sans text-[10px] font-medium opacity-70"
|
||||
:style="{ borderColor: colors.cardBorder, color: colors.textMain }"
|
||||
>
|
||||
统一模型名 / 格式聚合
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<div
|
||||
class="flex-1 rounded-lg flex items-center justify-center py-[7px] font-sans font-bold text-[10px] bg-black/[0.04] dark:bg-white/[0.04]"
|
||||
:style="{ color: colors.textMain }"
|
||||
>
|
||||
鉴定
|
||||
</div>
|
||||
<div
|
||||
class="flex-1 rounded-lg flex items-center justify-center py-[7px] font-sans font-bold text-[10px] bg-black/[0.04] dark:bg-white/[0.04]"
|
||||
:style="{ color: colors.textMain }"
|
||||
>
|
||||
并发
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-[36px] rounded-lg border flex items-center justify-center font-sans text-[10px] font-bold relative mt-2"
|
||||
:style="{ borderColor: colors.coreBorder, color: colors.textMain, backgroundColor: isDark ? 'rgba(212, 162, 127, 0.05)' : 'rgba(204, 120, 92, 0.03)' }"
|
||||
>
|
||||
智能分发引擎
|
||||
<div
|
||||
class="absolute -right-[5px] w-2 h-2 rounded-full bg-current"
|
||||
:style="{ color: colors.brand }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 3. EGRESS DELTA ENGINES -->
|
||||
<!-- Y perfectly aligned to the flow-out-top (y=200) and flow-out-bot (y=400) -->
|
||||
|
||||
<!-- Format Engine (Purple) -->
|
||||
<div
|
||||
class="absolute left-[750px] top-[165px] w-[200px] h-[70px] rounded-2xl flex flex-col justify-center px-6 z-10 transition-transform hover:-translate-y-1 cursor-pointer"
|
||||
:style="{ backgroundColor: colors.cardBg, border: `1px solid ${colors.cardBorder}`, boxShadow: shadows.node }"
|
||||
>
|
||||
<div
|
||||
class="absolute -left-1 w-2 h-6 rounded-full"
|
||||
:style="{ backgroundColor: colors.convertAccent }"
|
||||
/>
|
||||
<span
|
||||
class="font-sans text-[14px] font-bold tracking-wide flex items-center gap-2 mb-1"
|
||||
:style="{ color: colors.textMain }"
|
||||
>格式转换流 <div
|
||||
class="w-1.5 h-1.5 rounded-full animate-pulse"
|
||||
:style="{ backgroundColor: colors.convertAccent }"
|
||||
/></span>
|
||||
<span
|
||||
class="font-sans text-[9px] opacity-50"
|
||||
:style="{ color: colors.textMain }"
|
||||
>双向翻译协议与模型还原</span>
|
||||
</div>
|
||||
|
||||
<!-- Passthrough Engine (Blue) -->
|
||||
<div
|
||||
class="absolute left-[750px] top-[365px] w-[200px] h-[70px] rounded-2xl flex flex-col justify-center px-6 z-10 transition-transform hover:-translate-y-1 cursor-pointer border-dashed"
|
||||
:style="{ backgroundColor: colors.cardBg, borderColor: colors.cardBorder, borderWidth: '2px', boxShadow: shadows.node }"
|
||||
>
|
||||
<div
|
||||
class="absolute -left-1.5 w-2.5 h-6 rounded-full border-2 bg-transparent"
|
||||
:style="{ borderColor: colors.passAccent }"
|
||||
/>
|
||||
<span
|
||||
class="font-sans text-[14px] font-bold tracking-wide flex items-center gap-2 mb-1"
|
||||
:style="{ color: colors.passAccent }"
|
||||
>原生直通管道</span>
|
||||
<span
|
||||
class="font-sans text-[9px] opacity-60"
|
||||
:style="{ color: colors.textMain }"
|
||||
>同源生态双向超低延迟透传</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 4. UPSTREAM CLOUDS & PROXIES -->
|
||||
|
||||
<!-- Standard Cloud Targets (Top Flow: Y=140, 200, 260) -->
|
||||
<div class="absolute left-[1100px] top-[120px] flex flex-col gap-[20px] w-[140px] z-10">
|
||||
<div
|
||||
class="absolute -top-[30px] font-sans text-[11px] font-bold tracking-[0.2em] uppercase"
|
||||
:style="{ color: colors.convertAccent }"
|
||||
>
|
||||
Providers
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-[40px] rounded-xl flex items-center justify-between px-4 bg-white dark:bg-[#121212] transition-all hover:ring-2 hover:ring-purple-500/30"
|
||||
:style="{ border: `1px solid ${colors.cardBorder}`, boxShadow: shadows.node }"
|
||||
>
|
||||
<span
|
||||
class="font-sans text-[12px] font-semibold tracking-wide"
|
||||
:style="{ color: colors.textMain }"
|
||||
>Claude</span>
|
||||
<div
|
||||
class="w-1.5 h-1.5 rounded-full"
|
||||
:style="{ backgroundColor: colors.textMuted }"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="h-[40px] rounded-xl flex items-center justify-between px-4 bg-white dark:bg-[#121212] transition-all hover:ring-2 hover:ring-purple-500/30"
|
||||
:style="{ border: `1px solid ${colors.cardBorder}`, boxShadow: shadows.node }"
|
||||
>
|
||||
<span
|
||||
class="font-sans text-[12px] font-semibold tracking-wide"
|
||||
:style="{ color: colors.textMain }"
|
||||
>OpenAI</span>
|
||||
<div
|
||||
class="w-1.5 h-1.5 rounded-full"
|
||||
:style="{ backgroundColor: colors.textMuted }"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="h-[40px] rounded-xl flex items-center justify-between px-4 bg-white dark:bg-[#121212] transition-all hover:ring-2 hover:ring-purple-500/30"
|
||||
:style="{ border: `1px solid ${colors.cardBorder}`, boxShadow: shadows.node }"
|
||||
>
|
||||
<span
|
||||
class="font-sans text-[12px] font-semibold tracking-wide"
|
||||
:style="{ color: colors.textMain }"
|
||||
>Gemini</span>
|
||||
<div
|
||||
class="w-1.5 h-1.5 rounded-full"
|
||||
:style="{ backgroundColor: colors.textMuted }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Proxy Targets (Bottom Flow: Y=360, 420, 480) -->
|
||||
<div class="absolute left-[1100px] top-[340px] flex flex-col gap-[20px] w-[140px] z-10">
|
||||
<div
|
||||
class="absolute -top-[30px] font-sans text-[11px] font-bold tracking-[0.2em] uppercase"
|
||||
:style="{ color: colors.passAccent }"
|
||||
>
|
||||
Proxies
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="h-[40px] rounded-xl flex items-center justify-between px-4 bg-white dark:bg-[#121212] transition-all hover:ring-2 hover:ring-blue-500/30"
|
||||
:style="{ border: `1px dashed ${colors.cardBorder}`, boxShadow: shadows.node }"
|
||||
>
|
||||
<span
|
||||
class="font-sans text-[12px] font-semibold tracking-wide"
|
||||
:style="{ color: colors.textMain }"
|
||||
>Codex</span>
|
||||
</div>
|
||||
<div
|
||||
class="h-[40px] rounded-xl flex items-center justify-between px-4 bg-white dark:bg-[#121212] transition-all hover:ring-2 hover:ring-blue-500/30"
|
||||
:style="{ border: `1px dashed ${colors.cardBorder}`, boxShadow: shadows.node }"
|
||||
>
|
||||
<span
|
||||
class="font-sans text-[12px] font-semibold tracking-wide"
|
||||
:style="{ color: colors.textMain }"
|
||||
>Kiro</span>
|
||||
</div>
|
||||
<div
|
||||
class="h-[40px] rounded-xl flex items-center justify-between px-4 bg-white dark:bg-[#121212] transition-all hover:ring-2 hover:ring-blue-500/30"
|
||||
:style="{ border: `1px dashed ${colors.cardBorder}`, boxShadow: shadows.node }"
|
||||
>
|
||||
<span
|
||||
class="font-sans text-[12px] font-semibold tracking-wide"
|
||||
:style="{ color: colors.textMain }"
|
||||
>Antigrav</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 5. THE ORGANIC RETURN LOOP OVERLAYS -->
|
||||
<!-- Floating labels sitting precisely on the huge bottom elliptic sweep -->
|
||||
|
||||
<!-- Left anchor label -->
|
||||
<div
|
||||
class="absolute left-[120px] bottom-[20px] bg-white/80 dark:bg-black/60 backdrop-blur-md px-4 py-2 rounded-2xl border"
|
||||
:style="{ borderColor: colors.cardBorder, color: colors.returnAccent, boxShadow: shadows.node }"
|
||||
>
|
||||
<div class="font-sans text-[10px] font-bold tracking-wide flex items-center gap-2">
|
||||
<svg
|
||||
class="w-3 h-3"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
><polyline points="15 18 9 12 15 6" /></svg>返回兼容格式响应
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Center massive anchor -->
|
||||
<div
|
||||
class="absolute left-[540px] bottom-[15px] bg-white dark:bg-[#121212] px-6 py-2.5 rounded-full border-2 flex items-center gap-4 z-20 cursor-default"
|
||||
:style="{ borderColor: colors.returnAccent, boxShadow: shadows.node }"
|
||||
>
|
||||
<div
|
||||
class="w-2.5 h-2.5 rounded-full animate-ping absolute"
|
||||
:style="{ backgroundColor: colors.returnAccent, opacity: 0.4 }"
|
||||
/>
|
||||
<div
|
||||
class="w-2.5 h-2.5 rounded-full relative"
|
||||
:style="{ backgroundColor: colors.returnAccent }"
|
||||
/>
|
||||
<div class="flex flex-col">
|
||||
<span
|
||||
class="font-sans text-[13px] font-bold tracking-widest uppercase"
|
||||
:style="{ color: colors.returnAccent }"
|
||||
>Response Loop</span>
|
||||
<span
|
||||
class="font-sans text-[10px] font-semibold opacity-60 mt-0.5"
|
||||
:style="{ color: colors.textMain }"
|
||||
>响应逆向格式转换 / 上游模型实体还原</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right anchor label -->
|
||||
<div
|
||||
class="absolute right-[120px] bottom-[90px] bg-white/80 dark:bg-black/60 backdrop-blur-md px-4 py-2 rounded-2xl border"
|
||||
:style="{ borderColor: colors.cardBorder, color: colors.returnAccent, boxShadow: shadows.node }"
|
||||
>
|
||||
<div class="font-sans text-[10px] font-bold tracking-wide flex items-center gap-2">
|
||||
拉取上游原始流端<svg
|
||||
class="w-3 h-3"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
><polyline points="9 18 15 12 9 6" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.font-sans {
|
||||
font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
|
||||
/* Custom horizontal scrollbar for tight spaces */
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(150, 150, 150, 0.2);
|
||||
border-radius: 20px;
|
||||
}
|
||||
.custom-scrollbar:hover::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(150, 150, 150, 0.4);
|
||||
}
|
||||
</style>
|
||||
169
frontend/src/views/public/guide/components/MarkdownViewer.vue
Normal file
169
frontend/src/views/public/guide/components/MarkdownViewer.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="markdown-viewer-container">
|
||||
<div
|
||||
class="markdown-body"
|
||||
v-html="renderedHtml"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { marked, type Renderer } from 'marked'
|
||||
import DOMPurify from 'dompurify'
|
||||
import hljs from 'highlight.js'
|
||||
import 'highlight.js/styles/github-dark.css'
|
||||
|
||||
const props = defineProps<{
|
||||
content: string
|
||||
}>()
|
||||
|
||||
const renderedHtml = ref('')
|
||||
|
||||
// marked v16+ 需要通过自定义 renderer 实现代码高亮
|
||||
const renderer: Partial<Renderer> = {
|
||||
code({ text, lang }) {
|
||||
const language = lang && hljs.getLanguage(lang) ? lang : 'plaintext'
|
||||
const highlighted = hljs.highlight(text, { language }).value
|
||||
return `<pre><code class="hljs language-${language}">${highlighted}</code></pre>`
|
||||
}
|
||||
}
|
||||
|
||||
marked.use({ renderer, gfm: true, breaks: true })
|
||||
|
||||
const renderMarkdown = () => {
|
||||
if (!props.content) {
|
||||
renderedHtml.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const rawHtml = marked.parse(props.content) as string
|
||||
renderedHtml.value = DOMPurify.sanitize(rawHtml)
|
||||
} catch {
|
||||
renderedHtml.value = '<p class="text-red-500">Failed to render content</p>'
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.content, () => {
|
||||
renderMarkdown()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
renderMarkdown()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Literary Tech Markdown Styles */
|
||||
.markdown-viewer-container {
|
||||
@apply w-full max-w-none;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
@apply text-[var(--color-text)] font-serif leading-relaxed;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
@apply text-4xl mb-8 mt-12 font-medium tracking-tight text-[var(--color-text)];
|
||||
font-family: var(--serif);
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
@apply text-2xl mb-6 mt-12 font-medium tracking-tight text-[var(--color-text)] border-b pb-2;
|
||||
border-color: var(--color-border-soft);
|
||||
font-family: var(--serif);
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
@apply text-xl mb-4 mt-8 font-medium tracking-tight text-[var(--color-text)];
|
||||
font-family: var(--serif);
|
||||
}
|
||||
|
||||
.markdown-body p {
|
||||
@apply mb-4 text-[1.05rem] opacity-90;
|
||||
font-family: var(--serif);
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
@apply pl-6 mb-6 opacity-90 space-y-2 text-[1.05rem];
|
||||
font-family: var(--serif);
|
||||
}
|
||||
|
||||
.markdown-body ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.markdown-body ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
@apply border-l-4 pl-4 italic opacity-80 mb-6;
|
||||
border-color: var(--book-cloth);
|
||||
background: var(--color-background-soft);
|
||||
@apply py-2 rounded-r-lg;
|
||||
}
|
||||
|
||||
/* Code Blocks & Inline Code */
|
||||
.markdown-body pre {
|
||||
@apply p-4 rounded-xl overflow-x-auto mb-6 text-sm backdrop-blur-md;
|
||||
background: var(--color-background-soft);
|
||||
border: 1px solid var(--color-border-soft);
|
||||
font-family: var(--monospace) !important;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.markdown-body code {
|
||||
font-family: var(--monospace) !important;
|
||||
}
|
||||
|
||||
.markdown-body :not(pre) > code {
|
||||
@apply px-1.5 py-0.5 rounded text-sm;
|
||||
background: var(--color-background-soft);
|
||||
color: var(--book-cloth);
|
||||
font-family: var(--monospace) !important;
|
||||
border: 1px solid var(--color-border-soft);
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.markdown-body table {
|
||||
@apply w-full mb-6 border-collapse text-left;
|
||||
font-family: var(--sans-serif);
|
||||
}
|
||||
|
||||
.markdown-body th {
|
||||
@apply px-4 py-3 font-medium bg-[var(--color-background-soft)] border;
|
||||
border-color: var(--color-border-soft);
|
||||
}
|
||||
|
||||
.markdown-body td {
|
||||
@apply px-4 py-3 border opacity-90;
|
||||
border-color: var(--color-border-soft);
|
||||
}
|
||||
|
||||
.markdown-body tr:nth-child(even) {
|
||||
@apply bg-[var(--color-background)]/50;
|
||||
}
|
||||
|
||||
/* Links */
|
||||
.markdown-body a {
|
||||
@apply text-[var(--book-cloth)] underline decoration-dashed underline-offset-4 transition-all;
|
||||
}
|
||||
|
||||
.markdown-body a:hover {
|
||||
@apply decoration-solid opacity-80;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
.markdown-body img {
|
||||
@apply max-w-full rounded-xl border object-contain mx-auto mb-6 shadow-sm;
|
||||
border-color: var(--color-border-soft);
|
||||
max-height: 600px;
|
||||
}
|
||||
|
||||
.markdown-body p:has(img) {
|
||||
@apply text-center;
|
||||
}
|
||||
</style>
|
||||
33
frontend/src/views/public/guide/content/advanced.md
Normal file
33
frontend/src/views/public/guide/content/advanced.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 高级功能
|
||||
|
||||
## 1. 格式转换
|
||||
|
||||
1. **全局格式转换**
|
||||
2. **提供商级转换**
|
||||
3. **端点级转换**
|
||||
|
||||
## 2. 请求上游固定非流/流式
|
||||
您可以根据提供商的特性,强制将请求转换为流式或非流式输出。
|
||||
|
||||
## 3. 请求头/体编辑
|
||||
支持在请求发往上游前,动态对 Headers 或 Body 结构进行剔除、修改、正则替换等操作。
|
||||
|
||||
## 4. 模型映射
|
||||
将 Aether 统一模型名称映射至上游真实的非标模型名称。
|
||||
|
||||
## 5. 正则映射
|
||||
|
||||
1. **模型权限**:通过正则快速匹配一类模型权限
|
||||
2. **自动获取上游模型**:针对不支持标准模型获取接口的上游,通过抓取/正则自定义获取模型列表
|
||||
|
||||
## 6. 能力标签
|
||||
为模型或 Key 赋予 `vision`、`function_call`、`1m_context` 等细分能力标签。
|
||||
|
||||
## 7. 余额监控
|
||||
自动抓取配置了余额查询接口的提供商账户余额。
|
||||
|
||||
## 8. 配置导入/出
|
||||
支持将完整的配置环境(除去核心凭证)导出并在不同实例中导入。
|
||||
|
||||
## 9. 锁定用户密钥
|
||||
发现滥用时,可一键锁定用户请求密钥。
|
||||
35
frontend/src/views/public/guide/content/architecture.md
Normal file
35
frontend/src/views/public/guide/content/architecture.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 架构说明
|
||||
|
||||
Aether的系统架构、请求处理流程和数据流向。
|
||||
|
||||
## 1. 系统概览
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Client[客户端<br>SDK / CLI / Web]
|
||||
Aether[Aether<br>认证 / 路由 / 编排]
|
||||
PostgreSQL[(PostgreSQL)]
|
||||
Redis[(Redis)]
|
||||
Upstream[上游供应商<br>Claude / OpenAI / Gemini]
|
||||
|
||||
Client -->|API Request| Aether
|
||||
Aether -.->|Auth / Config| PostgreSQL
|
||||
Aether <.-.>|Cache / Quota / Lock| Redis
|
||||
Aether -->|Proxy Request| Upstream
|
||||
```
|
||||
|
||||
## 2. 核心原则
|
||||
|
||||
1. **API格式、端点、认证方式说明**
|
||||
提供商支持不同的格式(如 OpenAI Chat, Claude Chat),Aether在接收请求后,将统一管理路由。
|
||||
|
||||
2. **统一的入口模型名称**
|
||||
在内部完成多提供商、多模型名称风格的聚合映射管理。客户端只需知道统一的模型名(例如 `claude-3-opus`),Aether将根据映射规则自动寻找真正对应的上游模型名称。
|
||||
|
||||
3. **请求流转:格式转换**
|
||||
`多API格式兼容入口` → `格式转换` → `上游提供商` → `格式转换` → `多API格式兼容出口`。
|
||||
Aether 会尝试分析客户端的请求体格式,再将其转换为对应上游供应商要求的真正格式。
|
||||
|
||||
4. **请求流转:透传模式**
|
||||
`多API格式入口` → `同格式请求透传` → `上游提供商` → `同格式响应透传` → `多API格式出口`。
|
||||
如果客户端请求格式与上游目标格式一致,Aether 会直接透传(Pass-through),不进行数据解包和重整,最大化性能。
|
||||
120
frontend/src/views/public/guide/content/concepts.md
Normal file
120
frontend/src/views/public/guide/content/concepts.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# 相关概念
|
||||
|
||||
## 1. 创建统一模型
|
||||
1. **左侧模型选择、搜索区域**
|
||||
- 模型来源: opencode维护的 [models.dev](https://models.dev)
|
||||
- 仅显示几个官方模型提供商, 可以通过搜索获取更多模型
|
||||
2. **右侧参数配置区域**
|
||||
- 手动添加模型 或 选择模型后自动填写
|
||||
- 模型偏好: 标定这个模型是否支持这些能力, 一般情况下可不选
|
||||
- 价格配置
|
||||
- 计费方式: Token价格 + 按次价格 + 视频计费 = 最终计费
|
||||
- 价格阶梯: 以总Token数落在哪个区间为准, 并非按阶梯溢出补全式计费
|
||||
|
||||

|
||||
|
||||
## 2. 添加提供商
|
||||
1. **提供商类型**: 自定义或反代; 一般自定义即可, 反代请进入反代章节
|
||||
2. **计费类型**:
|
||||
- 按量付费: 持续使用
|
||||
- 月卡额度: 按周期(天)限额
|
||||
- 免费套餐: 不计入成本即倍率为0
|
||||
3. **最大重试次数**
|
||||
- 在缓存亲和调度模式下, 首次请求失败后的重试次数。
|
||||
4. **超时时间**
|
||||
- 流式首字超时时间: 流式请求收到首字前的超时时间
|
||||
- 非流请求超时时间: 非流请求的总超时时间
|
||||
5. **保持优先级**
|
||||
- 通过格式转换的请求, 是否保持当前优先级。
|
||||
|
||||

|
||||
|
||||
## 3. 添加端点
|
||||
|
||||
添加端点是添加上游支持的端点, 并非你需要使用的端点. 比如Anyrouter只支持Claude Code接入, 那么就应该仅添加Claude CLI端点。
|
||||
|
||||
1. **选择格式**: 选择上游支持的端点
|
||||
2. **自定义Base URL、Path**: 根据上游提供的信息填写。为空则使用提示的默认值。
|
||||
|
||||

|
||||

|
||||
|
||||
## 4. 添加密钥
|
||||
1. **认证类型**
|
||||
- API Key: 传统密钥
|
||||
- Vertex AI: 用于Google Cloud
|
||||
2. **支持的API格式**: 勾选Key可以使用的API格式, 同时可以设置key访问该端点时的倍率
|
||||
3. **优先级**: 在提供商优先的调度模式下, 不同优先级的Key按顺序故障转移, 同优先级的Key进行负载均衡
|
||||
4. **RPM限制**:
|
||||
Key的使用速率限制, 会预留10%给已有用户进行提高缓存命中率
|
||||
- 填写固定数值
|
||||
- 不填为空, 采用一定策略自适应学习。
|
||||
5. **缓存TTL**
|
||||
- 0: 不使用缓存优先
|
||||
- N: 同一个用户Key在N分钟内, 优先使用之前使用的提供商、Key响应请求。
|
||||
6. **熔断探测**
|
||||
当同一个提供商Key自动连续若干次请求失败后, 会进入熔断状态, 之后每间N分钟进行探测请求, 若请求成功解除熔断后续正常请求, 否则按以指数级增长探测时间以待下次探测, 最大探测间隔不会增长超过32分钟
|
||||
7. **能力标签**
|
||||
定义该Key可以使用的能力
|
||||
8. **自动获取上游模型**
|
||||
在上游获取模型端点支持的情况下, 从接口自动获取可以用模型列表。且按一定时间自动刷新, 不开启则默认任意模型可用, 或在后续模型权限中手动添加。
|
||||
|
||||

|
||||

|
||||
|
||||
## 5. 模型权限
|
||||
1. 在编辑密钥中开启自动获取/刷新上游全部模型
|
||||
2. 手动限制/创建该Key的可用模型
|
||||
3. 不开启自动获取且勾选任意模型, 即模型权限为空, 则认为可以使用提供商的全部关联模型
|
||||
4. 只有在模型权限不为空的情况下, 才可以配合正则映射模型
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## 6. 关联模型
|
||||
|
||||
从全局模型关联提供商模型, 即设置该提供商可以访问的模型, 同时可以在这里设置提供商自己的模型价格。默认继承全局模型的价格
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## 7. 模型映射
|
||||
|
||||
如果该提供的请求名称并非标准名称, 即可通过映射改变在实际请求提供时的模型id, 比如:
|
||||
官方标准名称为: `claude-opus-4-6` 实际提供商叫 `claude-opus-4-6-last`
|
||||
|
||||
又或者可以降/升级请求模型:
|
||||
官方标准名称为: `claude-opus-4-6` 实际请求提供商用 `claude-sonnet-4-6`
|
||||
|
||||

|
||||

|
||||
|
||||
## 8. 反向代理
|
||||
|
||||
1. **Codex**
|
||||

|
||||
2. **Krio**
|
||||

|
||||
3. **Antigravity**
|
||||

|
||||
|
||||
## 9. 优先级管理
|
||||
|
||||
允许拖动、或者直接点击数字输入调整优先级
|
||||
|
||||
1. **提供商优先**
|
||||
按提供商顺序调度, 同优先级负载均衡
|
||||
2. **Key优先**
|
||||
全局Key统一调度, 同优先级负载均衡
|
||||
3. **缓存亲和模式**
|
||||
在KeyTTL时间的约束下, 优先使用上一次请求使用的Key
|
||||
4. **负载均衡模式**
|
||||
- 取消全局提供商优先级, 提供商内部Key依然保持优先级
|
||||
- 取消全局Key优先级, 乱序使用
|
||||
5. **固定顺序模式**
|
||||
取消缓存亲和, 始终使用固定顺序Key请求
|
||||
|
||||

|
||||

|
||||
10
frontend/src/views/public/guide/content/faq.md
Normal file
10
frontend/src/views/public/guide/content/faq.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# 常见问题
|
||||
|
||||
## 1. 为什么发生错误没有进行故障转移?
|
||||
故障转移只在满足安全的重试条件下发生。主要检查:
|
||||
1. 错误是否属于可重试类型(如 429, 5xx 或网络超时),400/401/404 等请求本身的错误一般不重试。
|
||||
2. 是否已经达到提供商或全局配置中的「最大重试次数」。
|
||||
3. 在某些特定的流式响应中,如果内容已经开始返回中途断开,可能为了防止数据混乱而不进行转移。
|
||||
|
||||
## 2. 其它问题
|
||||
请提交 Issue 寻求帮助或参与社区讨论。
|
||||
6
frontend/src/views/public/guide/content/modules.md
Normal file
6
frontend/src/views/public/guide/content/modules.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# 模块管理
|
||||
|
||||
1. **访问令牌**: 允许通过令牌授权使用全部后端接口
|
||||
2. **邮件配置**: 注册、通知服务, 可自定义邮件模版
|
||||
3. **OAuth登录**: 支持 Linux Do OAuth 授权登录
|
||||
4. **LDAP认证**: 提供企业级统合身份与权限管理
|
||||
131
frontend/src/views/public/guide/content/overview.md
Normal file
131
frontend/src/views/public/guide/content/overview.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# 快速开始
|
||||
## 部署
|
||||
|
||||
选择适合你的部署方式开始
|
||||
|
||||
### 1. 预构建镜像 (Docker Compose)
|
||||
```markdown
|
||||
# 1. 克隆代码
|
||||
git clone https://github.com/fawney19/Aether.git
|
||||
cd Aether
|
||||
|
||||
# 2. 配置环境变量
|
||||
cp .env.example .env
|
||||
python generate_keys.py # 生成密钥, 并将生成的密钥填入 .env
|
||||
|
||||
# 3. 部署 / 更新(自动执行数据库迁移)
|
||||
docker compose pull && docker compose up -d
|
||||
|
||||
# 4. 升级前备份
|
||||
docker compose exec postgres pg_dump -U postgres aether | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
|
||||
```
|
||||
|
||||
### 2. 本地代码构建镜像 (Docker Compose)
|
||||
```markdown
|
||||
# 1. 克隆代码
|
||||
git clone https://github.com/fawney19/Aether.git
|
||||
cd Aether
|
||||
|
||||
# 2. 配置环境变量
|
||||
cp .env.example .env
|
||||
python generate_keys.py # 生成密钥, 并将生成的密钥填入 .env
|
||||
|
||||
# 3. 构建(自动构建、启动、迁移)
|
||||
./deploy.sh
|
||||
|
||||
# 4. 更新需要拉取最新代码
|
||||
git pull origin master
|
||||
```
|
||||
|
||||
### 3. 本地开发
|
||||
依赖 Docker、uv、nodejs
|
||||
```markdown
|
||||
# 启动数据库
|
||||
docker compose -f docker-compose.build.yml up -d postgres redis
|
||||
|
||||
# 后端
|
||||
uv sync
|
||||
./dev.sh
|
||||
|
||||
# 前端
|
||||
cd frontend && npm install && npm run dev
|
||||
```
|
||||
|
||||
## 配置流程
|
||||
|
||||
1. **创建统一模型**
|
||||
以Opus4.6为例, 其他模型同样添加即可, 非必要建议只添加官方支持的模型ID
|
||||

|
||||
|
||||
2. **添加提供商**
|
||||

|
||||
|
||||
3. **添加端点**
|
||||

|
||||

|
||||
|
||||
4. **添加密钥**
|
||||

|
||||
|
||||
5. **关联全局模型**
|
||||

|
||||

|
||||
|
||||
6. **模型映射**
|
||||

|
||||
|
||||
## 反向代理
|
||||
|
||||
添加提供商时, 提供商类型选择对应类型即可, 反向代理默认开启提供商级格式转换。
|
||||
|
||||
1. **Codex**
|
||||
- OAuth授权登录
|
||||
- 导入RefreshToken, 支持批量导入
|
||||
2. **Kiro**
|
||||
- Build ID
|
||||
- Identity Center
|
||||
- Start URL
|
||||
- Region
|
||||
- 导入 RefreshToken, 支持批量导入
|
||||
- Social 格式要求
|
||||
```json
|
||||
{
|
||||
"refresh_token": ""
|
||||
}
|
||||
```
|
||||
- IDC 格式要求
|
||||
```json
|
||||
{
|
||||
"refresh_token": "",
|
||||
"client_id": "",
|
||||
"client_secret": "",
|
||||
"machine_id": ""
|
||||
}
|
||||
```
|
||||
3. **Antigravity**
|
||||
- OAuth授权登录
|
||||
- 导入RefreshToken, 支持批量导入
|
||||
|
||||

|
||||
|
||||
## 异步任务
|
||||
|
||||
需要有提供商端点支持
|
||||
|
||||
1. Veo
|
||||
2. Sora
|
||||
|
||||
## 代理配置
|
||||
|
||||
1. **Aether-Proxy**
|
||||
Rust实现, 超小资源占有, 适合性能低的vps直接使用。
|
||||
[https://github.com/fawney19/Aether/tree/master/aether-proxy](https://github.com/fawney19/Aether/tree/master/aether-proxy)
|
||||
|
||||
2. **代理节点**
|
||||
在模块管理中, 开启代理模块后可以添加和使用代理功能, 包括手动添加和Aether-Proxy自动连接。
|
||||
|
||||
3. **多级代理**
|
||||
优先级: Key代理 > 提供商代理 > 全局代理
|
||||
- 全局代理 - 系统配置
|
||||
- 提供商代理 - 提供商配置
|
||||
- Key代理 - Key配置
|
||||
31
frontend/src/views/public/guide/content/strategy.md
Normal file
31
frontend/src/views/public/guide/content/strategy.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 关键策略
|
||||
|
||||
## 1. 请求体记录
|
||||
|
||||
在系统设置中, 可以修改请求体记录详情等级
|
||||
|
||||
| 日志等级 | 记录内容 |
|
||||
| --- | --- |
|
||||
| **Base** | 基本请求信息 |
|
||||
| **Headers** | Base + 请求头 |
|
||||
| **Full** | Headers + 请求体 |
|
||||
|
||||

|
||||
|
||||
## 2. 调度模式
|
||||
|
||||
1. 提供商优先
|
||||
2. Key优先
|
||||
3. 缓存亲和
|
||||
4. 负载均衡
|
||||
5. 固定顺序
|
||||
6. 故障转移
|
||||
|
||||
## 3. 访问限制
|
||||
*全局或 Key 级别的访问频率及配额限制*
|
||||
|
||||
## 4. 请求体压缩清理
|
||||
*去除多余空格与不可见字符以节省 Token 操作*
|
||||
|
||||
## 5. 定时任务
|
||||
*自动刷新模型列表及定时检查配额状态*
|
||||
292
frontend/src/views/public/guide/demos/GuideArchitectureDemos.vue
Normal file
292
frontend/src/views/public/guide/demos/GuideArchitectureDemos.vue
Normal file
@@ -0,0 +1,292 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ArrowLeft } from 'lucide-vue-next'
|
||||
|
||||
const activeDemo = ref<number>(1)
|
||||
|
||||
const demos = [
|
||||
{ id: 1, name: '垂直管道流 (Vertical Pipeline)' },
|
||||
{ id: 2, name: '中心辐射 (Central Star Hub)' },
|
||||
{ id: 3, name: '2.5D 层级结构 (Isometric Layered)' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fixed inset-0 z-50 bg-[#faf9f5] dark:bg-[#141311] overflow-y-auto">
|
||||
<div class="sticky top-0 z-10 flex items-center justify-between px-6 py-4 bg-white/80 dark:bg-[#1a1815]/80 backdrop-blur border-b border-[#e5e4df] dark:border-white/10">
|
||||
<div class="flex items-center gap-4">
|
||||
<router-link
|
||||
to="/guide/architecture"
|
||||
class="flex items-center gap-2 text-sm font-medium text-[#666663] hover:text-[#cc785c] dark:text-[#a3a094] transition-colors"
|
||||
>
|
||||
<ArrowLeft class="w-4 h-4" /> 返回文档
|
||||
</router-link>
|
||||
<h1 class="text-xl font-bold text-[#262624] dark:text-[#f1ead8] m-0 border-l border-[#e5e4df] dark:border-white/10 pl-4">
|
||||
架构布局草案演示
|
||||
</h1>
|
||||
</div>
|
||||
<div class="flex gap-2 bg-[#f5f5f0] dark:bg-[#1e1c19] p-1 rounded-xl border border-[#e5e4df] dark:border-white/5">
|
||||
<button
|
||||
v-for="demo in demos"
|
||||
:key="demo.id"
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200"
|
||||
:class="[
|
||||
activeDemo === demo.id
|
||||
? 'bg-white dark:bg-[#2a2825] text-[#cc785c] shadow-sm'
|
||||
: 'text-[#666663] dark:text-[#a3a094] hover:bg-black/5 dark:hover:bg-white/5'
|
||||
]"
|
||||
@click="activeDemo = demo.id"
|
||||
>
|
||||
{{ demo.name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Canvas Area -->
|
||||
<div class="p-8 w-full max-w-7xl mx-auto flex justify-center min-h-[800px]">
|
||||
<!-- Variant 1: Vertical -->
|
||||
<div
|
||||
v-if="activeDemo === 1"
|
||||
class="w-full h-[800px] border border-dashed border-[#cc785c]/30 rounded-3xl relative flex flex-col items-center justify-between p-12"
|
||||
>
|
||||
<div class="absolute top-4 left-6 text-sm font-mono text-[#cc785c]/50">
|
||||
Layout Variant: Vertical Pipeline
|
||||
</div>
|
||||
<!-- Ingress row -->
|
||||
<div class="flex gap-12 w-full justify-center">
|
||||
<div class="w-48 h-16 bg-white dark:bg-[#1e1c19] border border-[#e5e4df] dark:border-white/10 rounded-xl flex items-center justify-center font-mono font-bold">
|
||||
Claude API Request
|
||||
</div>
|
||||
<div class="w-48 h-16 bg-white dark:bg-[#1e1c19] border border-[#e5e4df] dark:border-white/10 rounded-xl flex items-center justify-center font-mono font-bold">
|
||||
OpenAI API Request
|
||||
</div>
|
||||
<div class="w-48 h-16 bg-white dark:bg-[#1e1c19] border border-[#e5e4df] dark:border-white/10 rounded-xl flex items-center justify-center font-mono font-bold">
|
||||
Gemini API Request
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Down Arrows -->
|
||||
<div class="flex gap-12 w-full justify-center my-4 opacity-50">
|
||||
<div class="w-1 h-12 bg-[#cc785c] mx-auto relative">
|
||||
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2 border-l-4 border-r-4 border-t-8 border-transparent border-t-[#cc785c]" />
|
||||
</div>
|
||||
<div class="w-1 h-12 bg-[#cc785c] mx-auto relative">
|
||||
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2 border-l-4 border-r-4 border-t-8 border-transparent border-t-[#cc785c]" />
|
||||
</div>
|
||||
<div class="w-1 h-12 bg-[#cc785c] mx-auto relative">
|
||||
<div class="absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2 border-l-4 border-r-4 border-t-8 border-transparent border-t-[#cc785c]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aether Core -->
|
||||
<div class="w-full max-w-3xl h-48 bg-[#cc785c]/10 border-2 border-[#cc785c] rounded-2xl flex flex-col items-center justify-center">
|
||||
<h2 class="text-3xl font-black tracking-widest text-[#cc785c] mb-2">
|
||||
AETHER GATEWAY
|
||||
</h2>
|
||||
<div class="text-sm font-medium w-full px-12 text-center text-[#262624] dark:text-[#f1ead8]">
|
||||
<span class="px-3">多源聚合</span>|<span class="px-3">鉴权 / 配额管控</span>|<span class="px-3">负载均衡 / 故障转移</span>|<span class="px-3">基于亲和性路由</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Split flow down -->
|
||||
<div class="w-full max-w-2xl h-16 relative flex justify-between px-24 my-4 opacity-50">
|
||||
<!-- Left line to convert -->
|
||||
<div class="absolute top-0 left-[25%] w-[1px] h-full bg-[#a855f7]" />
|
||||
<!-- Right line to passthrough -->
|
||||
<div class="absolute top-0 right-[25%] w-[1px] h-full bg-[#3b82f6]" />
|
||||
</div>
|
||||
|
||||
<!-- Egress Processing -->
|
||||
<div class="flex gap-24 w-full justify-center">
|
||||
<div class="w-72 h-32 bg-[#a855f7]/10 border-2 border-[#a855f7] rounded-xl flex flex-col items-center justify-center text-[#a855f7]">
|
||||
<h3 class="font-bold text-lg mb-2">
|
||||
3. 格式转换引擎
|
||||
</h3>
|
||||
<span class="text-xs font-mono">Protocols Translate</span>
|
||||
</div>
|
||||
<div class="w-72 h-32 bg-[#3b82f6]/10 border-2 border-[#3b82f6] rounded-xl flex flex-col items-center justify-center text-[#3b82f6]">
|
||||
<h3 class="font-bold text-lg mb-2">
|
||||
4. 原生双向透传
|
||||
</h3>
|
||||
<span class="text-xs font-mono">Direct Passthrough</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Final hop down -->
|
||||
<div class="w-1 h-12 bg-[#e5e4df] dark:bg-white/20 mx-auto my-4" />
|
||||
|
||||
<!-- Upstream -->
|
||||
<div class="w-full max-w-4xl h-24 border-2 border-dashed border-[#e5e4df] dark:border-white/20 rounded-xl flex items-center justify-around">
|
||||
<div class="font-bold text-[#666663] dark:text-[#a3a094]">
|
||||
Upstream:
|
||||
</div>
|
||||
<div class="px-6 py-2 bg-emerald-500/10 text-emerald-600 rounded-lg font-bold">
|
||||
Claude
|
||||
</div>
|
||||
<div class="px-6 py-2 bg-emerald-500/10 text-emerald-600 rounded-lg font-bold">
|
||||
OpenAI
|
||||
</div>
|
||||
<div class="px-6 py-2 bg-emerald-500/10 text-emerald-600 rounded-lg font-bold">
|
||||
Gemini
|
||||
</div>
|
||||
<div class="px-6 py-2 bg-amber-500/10 text-amber-600 rounded-lg font-bold">
|
||||
Custom Reverse Proxies (Codex, Kiro...)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Variant 2: Central Hub -->
|
||||
<div
|
||||
v-if="activeDemo === 2"
|
||||
class="w-full h-[800px] border border-dashed border-[#cc785c]/30 rounded-3xl relative flex items-center justify-center p-12"
|
||||
>
|
||||
<div class="absolute top-4 left-6 text-sm font-mono text-[#cc785c]/50">
|
||||
Layout Variant: Central Star/Hub
|
||||
</div>
|
||||
|
||||
<!-- Aether Core / Center -->
|
||||
<div class="w-72 h-72 rounded-full border-4 border-[#cc785c] bg-[#cc785c]/5 flex flex-col items-center justify-center z-10 relative">
|
||||
<h2 class="text-xl font-black text-[#cc785c] text-center px-4 leading-tight">
|
||||
AETHER<br>GATEWAY
|
||||
</h2>
|
||||
<div class="mt-4 text-xs font-mono text-center leading-relaxed">
|
||||
Auth<br>Quota<br>Load Balance
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ingress Left Arc -->
|
||||
<div class="absolute left-24 top-1/2 -translate-y-1/2 flex flex-col gap-6">
|
||||
<div class="w-40 h-10 bg-white dark:bg-[#1e1c19] border border-[#e5e4df] dark:border-white/10 rounded pl-4 flex items-center relative">
|
||||
<span class="font-mono text-sm font-bold">Claude API</span>
|
||||
<div class="absolute right-[-40px] top-1/2 h-[1px] w-10 bg-[#cc785c]" />
|
||||
</div>
|
||||
<div class="w-40 h-10 bg-white dark:bg-[#1e1c19] border border-[#e5e4df] dark:border-white/10 rounded pl-4 flex items-center relative">
|
||||
<span class="font-mono text-sm font-bold">OpenAI API</span>
|
||||
<div class="absolute right-[-40px] top-1/2 h-[1px] w-10 bg-[#cc785c]" />
|
||||
</div>
|
||||
<div class="w-40 h-10 bg-white dark:bg-[#1e1c19] border border-[#e5e4df] dark:border-white/10 rounded pl-4 flex items-center relative">
|
||||
<span class="font-mono text-sm font-bold">Gemini API</span>
|
||||
<div class="absolute right-[-40px] top-1/2 h-[1px] w-10 bg-[#cc785c]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Engines Top/Bottom -->
|
||||
<div class="absolute top-24 left-1/2 -translate-x-1/2 flex flex-col items-center">
|
||||
<div class="w-48 h-16 bg-[#a855f7]/10 border-2 border-[#a855f7] rounded-xl flex items-center justify-center text-sm font-bold text-[#a855f7]">
|
||||
格式转换引擎
|
||||
</div>
|
||||
<div class="w-[1px] h-12 bg-[#a855f7]" />
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-24 left-1/2 -translate-x-1/2 flex flex-col items-center">
|
||||
<div class="w-[1px] h-12 bg-[#3b82f6]" />
|
||||
<div class="w-48 h-16 bg-[#3b82f6]/10 border-2 border-[#3b82f6] rounded-xl flex items-center justify-center text-sm font-bold text-[#3b82f6]">
|
||||
原生直通管道
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Egress Right Arc -->
|
||||
<div class="absolute right-24 top-1/2 -translate-y-1/2 flex flex-col gap-4">
|
||||
<div class="w-48 h-12 border border-dashed border-emerald-500 rounded flex items-center justify-center relative bg-emerald-500/5 text-emerald-600 font-bold">
|
||||
<div class="absolute left-[-60px] top-1/2 h-[1px] w-14 bg-emerald-500/50" />
|
||||
Standard Providers
|
||||
</div>
|
||||
<div class="w-48 h-12 border border-dashed border-amber-500 rounded flex items-center justify-center relative bg-amber-500/5 text-amber-600 font-bold">
|
||||
<div class="absolute left-[-60px] top-1/2 h-[1px] w-14 bg-amber-500/50" />
|
||||
Reverse Proxies
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Variant 3: 2.5D Stacked -->
|
||||
<div
|
||||
v-if="activeDemo === 3"
|
||||
class="w-full h-[800px] border border-dashed border-[#cc785c]/30 rounded-3xl relative pt-32 pb-12 flex justify-center perspective-[1000px]"
|
||||
>
|
||||
<div class="absolute top-4 left-6 text-sm font-mono text-[#cc785c]/50">
|
||||
Layout Variant: 2.5D Stacked Layers (AWS Style)
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-[600px] h-[600px] relative transition-transform duration-500 preserve-3d"
|
||||
style="transform: rotateX(55deg) rotateZ(-45deg);"
|
||||
>
|
||||
<!-- Layer 1: Ingress (Top) -->
|
||||
<div class="absolute inset-x-0 top-0 h-40 bg-white/40 dark:bg-black/40 backdrop-blur-md border border-white/50 dark:border-white/10 rounded-tr-3xl rounded-bl-3xl shadow-2xl flex flex-col justify-center px-12 transform translate-z-[120px]">
|
||||
<h3 class="text-sm font-bold text-[#cc785c] absolute top-4 left-4 tracking-widest">
|
||||
LAYER 1: INGRESS CLIENTS
|
||||
</h3>
|
||||
<div class="flex gap-4 w-full">
|
||||
<div class="flex-1 h-12 bg-white dark:bg-[#2a2a2a] rounded flex items-center justify-center font-mono font-bold shadow-sm">
|
||||
Claude
|
||||
</div>
|
||||
<div class="flex-1 h-12 bg-white dark:bg-[#2a2a2a] rounded flex items-center justify-center font-mono font-bold shadow-sm">
|
||||
OpenAI
|
||||
</div>
|
||||
<div class="flex-1 h-12 bg-white dark:bg-[#2a2a2a] rounded flex items-center justify-center font-mono font-bold shadow-sm">
|
||||
Gemini
|
||||
</div>
|
||||
</div>
|
||||
<!-- Data Flow drops to L2 -->
|
||||
<div class="absolute bottom-[-60px] left-1/2 w-2 h-16 bg-[#cc785c] opacity-50 blur-sm rounded-full transform -rotate-x-90 translate-y-8" />
|
||||
</div>
|
||||
|
||||
<!-- Layer 2: Aether Gateway Core (Middle) -->
|
||||
<div class="absolute inset-x-0 top-[200px] h-48 bg-[#cc785c]/20 backdrop-blur-xl border-2 border-[#cc785c] rounded-lg shadow-2xl flex items-center justify-center relative transform translate-z-[60px]">
|
||||
<h3 class="text-sm font-bold text-[#cc785c] absolute top-4 left-4 tracking-widest">
|
||||
LAYER 2: AETHER GATEWAY (CORE)
|
||||
</h3>
|
||||
<div class="text-center">
|
||||
<h2 class="text-3xl font-black text-[#cc785c] tracking-[0.3em] mb-4">
|
||||
AETHER
|
||||
</h2>
|
||||
<div class="flex gap-2">
|
||||
<span class="px-2 py-1 text-[10px] bg-white/50 dark:bg-black/50 rounded ring-1 ring-[#cc785c]">Auth</span>
|
||||
<span class="px-2 py-1 text-[10px] bg-white/50 dark:bg-black/50 rounded ring-1 ring-[#cc785c]">Rate Limit</span>
|
||||
<span class="px-2 py-1 text-[10px] bg-white/50 dark:bg-black/50 rounded ring-1 ring-[#cc785c]">Load Balance</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer 3: Egress & Providers (Bottom) -->
|
||||
<div class="absolute inset-x-0 bottom-[-50px] h-60 bg-[#f5f5f0]/50 dark:bg-[#1a1815]/50 backdrop-blur border border-[#e5e4df] dark:border-white/10 rounded-br-3xl rounded-tl-3xl shadow-xl flex flex-col p-8 transform translate-z-[0px]">
|
||||
<h3 class="text-sm font-bold text-[#666663] dark:text-[#a3a094] mb-4">
|
||||
LAYER 3: EGRESS & UPSTREAM
|
||||
</h3>
|
||||
|
||||
<div class="flex gap-8 mb-6">
|
||||
<div class="flex-1 h-16 bg-[#a855f7]/20 border border-[#a855f7] rounded flex items-center justify-center text-[#a855f7] font-bold">
|
||||
Format Convert
|
||||
</div>
|
||||
<div class="flex-1 h-16 bg-[#3b82f6]/20 border border-[#3b82f6] rounded flex items-center justify-center text-[#3b82f6] font-bold">
|
||||
Direct Passthrough
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full flex gap-4 h-16">
|
||||
<div class="w-full border border-dashed border-emerald-500 rounded bg-emerald-500/10 flex items-center justify-center text-emerald-600 font-bold">
|
||||
Providers (Claude, OpenAI)
|
||||
</div>
|
||||
<div class="w-full border border-dashed border-amber-500 rounded bg-amber-500/10 flex items-center justify-center text-amber-600 font-bold">
|
||||
Reverse Proxies
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.perspective-\[1000px\] {
|
||||
perspective: 1000px;
|
||||
}
|
||||
.preserve-3d {
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
.translate-z-\[120px\] { transform: translateZ(120px); }
|
||||
.translate-z-\[60px\] { transform: translateZ(60px); }
|
||||
.translate-z-\[0px\] { transform: translateZ(0px); }
|
||||
.-rotate-x-90 { transform: rotateX(-90deg); }
|
||||
</style>
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
BookOpen,
|
||||
Target,
|
||||
Settings,
|
||||
Blocks,
|
||||
HelpCircle
|
||||
} from 'lucide-vue-next'
|
||||
|
||||
@@ -15,6 +16,7 @@ export interface GuideNavItem {
|
||||
path: string
|
||||
icon: Component
|
||||
description?: string
|
||||
subItems?: { name: string; hash: string }[]
|
||||
}
|
||||
|
||||
export const guideNavItems: GuideNavItem[] = [
|
||||
@@ -23,309 +25,113 @@ export const guideNavItems: GuideNavItem[] = [
|
||||
name: '快速开始',
|
||||
path: '/guide',
|
||||
icon: Rocket,
|
||||
description: '部署后的配置指南'
|
||||
description: '部署后的配置指南',
|
||||
subItems: [
|
||||
{ name: '部署', hash: '#production' },
|
||||
{ name: '配置流程', hash: '#config-steps' },
|
||||
{ name: '反向代理', hash: '#reverse-proxy' },
|
||||
{ name: '异步任务', hash: '#async-tasks' },
|
||||
{ name: '代理配置', hash: '#proxy-config' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'architecture',
|
||||
name: '架构说明',
|
||||
path: '/guide/architecture',
|
||||
icon: Network,
|
||||
description: '系统架构与请求流程'
|
||||
description: '系统架构'
|
||||
},
|
||||
{
|
||||
id: 'concepts',
|
||||
name: '相关概念',
|
||||
path: '/guide/concepts',
|
||||
icon: BookOpen,
|
||||
description: '核心概念深入解释'
|
||||
description: '核心概念',
|
||||
subItems: [
|
||||
{ name: '创建统一模型', hash: '#create-model' },
|
||||
{ name: '添加提供商', hash: '#add-provider' },
|
||||
{ name: '添加端点', hash: '#add-endpoint' },
|
||||
{ name: '添加密钥', hash: '#add-key' },
|
||||
{ name: '模型权限', hash: '#model-permission' },
|
||||
{ name: '关联模型', hash: '#link-model' },
|
||||
{ name: '模型映射', hash: '#model-mapping' },
|
||||
{ name: '反向代理', hash: '#reverse-proxy' },
|
||||
{ name: '优先级管理', hash: '#priority-management' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'strategy',
|
||||
name: '关键策略',
|
||||
path: '/guide/strategy',
|
||||
icon: Target,
|
||||
description: '调度、缓存与故障转移'
|
||||
description: '关键策略',
|
||||
subItems: [
|
||||
{ name: '请求体记录', hash: '#request-logging' },
|
||||
{ name: '调度模式', hash: '#scheduling' },
|
||||
{ name: '访问限制', hash: '#rate-limit' },
|
||||
{ name: '请求体清理', hash: '#payload-cleanup' },
|
||||
{ name: '定时任务', hash: '#cron-tasks' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'advanced',
|
||||
name: '高级功能',
|
||||
path: '/guide/advanced',
|
||||
icon: Settings,
|
||||
description: '格式转换、请求规则等'
|
||||
description: '高级功能',
|
||||
subItems: [
|
||||
{ name: '格式转换', hash: '#format-conversion' },
|
||||
{ name: '流式/非流式', hash: '#stream-policy' },
|
||||
{ name: '请求头/体编辑', hash: '#header-body-edit' },
|
||||
{ name: '模型映射', hash: '#model-mapping' },
|
||||
{ name: '正则映射', hash: '#regex-mapping' },
|
||||
{ name: '能力标签', hash: '#capabilities' },
|
||||
{ name: '余额监控', hash: '#balance-monitor' },
|
||||
{ name: '配置导入/出', hash: '#config-export' },
|
||||
{ name: '锁定用户密钥', hash: '#lock-key' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'modules',
|
||||
name: '模块管理',
|
||||
path: '/guide/modules',
|
||||
icon: Blocks,
|
||||
description: '模块管理',
|
||||
subItems: [
|
||||
{ name: '访问令牌', hash: '#management-tokens' },
|
||||
{ name: '邮件配置', hash: '#email-config' },
|
||||
{ name: 'OAuth登录', hash: '#oauth-login' },
|
||||
{ name: 'LDAP认证', hash: '#ldap-auth' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'faq',
|
||||
name: '常见问题',
|
||||
path: '/guide/faq',
|
||||
icon: HelpCircle,
|
||||
description: '使用中的常见问题'
|
||||
description: '常见问题'
|
||||
}
|
||||
]
|
||||
|
||||
// 样式类常量
|
||||
// 样式类常量 - 使用 Literary Tech 主题
|
||||
export const panelClasses = {
|
||||
card: 'bg-white/70 dark:bg-[#262624]/80 backdrop-blur-sm rounded-2xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.16)]',
|
||||
cardHover: 'hover:border-[#cc785c]/30 dark:hover:border-[#d4a27f]/30 transition-colors',
|
||||
section: 'bg-white/50 dark:bg-[#262624]/60 backdrop-blur-sm rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)]',
|
||||
commandPanel: 'rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-white/50 dark:bg-[#1f1d1a]/50',
|
||||
configPanel: 'rounded-xl border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] overflow-hidden',
|
||||
panelHeader: 'px-4 py-2 border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50',
|
||||
card: 'literary-card rounded-2xl backdrop-blur-sm transition-all duration-300',
|
||||
cardHover: 'hover:-translate-y-1 hover:shadow-lg dark:hover:shadow-[var(--book-cloth)]/10 shadow-[var(--book-cloth)]/10',
|
||||
section: 'literary-surface-inset bg-white/40 dark:bg-black/20 backdrop-blur-md rounded-xl md:rounded-2xl p-5 md:p-8 transition-colors',
|
||||
commandPanel: 'literary-surface-elevated rounded-xl overflow-hidden shadow-sm backdrop-blur-md',
|
||||
configPanel: 'literary-surface-elevated rounded-xl overflow-hidden',
|
||||
panelHeader: 'px-4 py-3 border-b literary-border bg-[var(--color-background-soft)]/50',
|
||||
codeBody: 'p-0',
|
||||
badge: 'inline-flex items-center gap-1.5 rounded-full bg-[#cc785c]/10 dark:bg-[#cc785c]/20 border border-[#cc785c]/20 dark:border-[#cc785c]/40 px-3 py-1.5 text-xs font-medium text-[#cc785c] dark:text-[#d4a27f]',
|
||||
badge: 'literary-badge bg-[var(--color-background)] rounded-full px-3 py-1.5',
|
||||
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)]',
|
||||
'flex items-center justify-center rounded-lg border h-8 w-8',
|
||||
'literary-border',
|
||||
'bg-transparent',
|
||||
'text-[#666663] dark:text-[#f1ead8]',
|
||||
'transition hover:bg-[#f0f0eb] dark:hover:bg-[#3a3731]'
|
||||
'text-[var(--color-text)]',
|
||||
'transition hover:bg-[var(--color-background-soft)]'
|
||||
].join(' ')
|
||||
} as const
|
||||
|
||||
// API 格式说明
|
||||
export const apiFormats = [
|
||||
{
|
||||
name: 'OpenAI Chat',
|
||||
endpoint: '/v1/chat/completions',
|
||||
auth: 'Authorization: Bearer xxx',
|
||||
clients: ['OpenAI SDK', 'Cursor', 'LangChain', '大部分开源工具']
|
||||
},
|
||||
{
|
||||
name: 'OpenAI CLI',
|
||||
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',
|
||||
auth: 'x-api-key: xxx',
|
||||
clients: ['Anthropic SDK']
|
||||
},
|
||||
{
|
||||
name: 'Claude CLI',
|
||||
endpoint: '/v1/messages',
|
||||
auth: 'Authorization: Bearer xxx',
|
||||
clients: ['Claude Code']
|
||||
},
|
||||
{
|
||||
name: 'Gemini Chat',
|
||||
endpoint: '/v1beta/models/{model}:generateContent',
|
||||
auth: 'x-goog-api-key: xxx',
|
||||
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 configSteps = [
|
||||
{
|
||||
step: 1,
|
||||
title: '添加供应商',
|
||||
description: '创建供应商并配置端点(URL、API Key、API 格式)',
|
||||
path: '/admin/providers'
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
title: '创建模型',
|
||||
description: '定义用户可用的模型名,关联到端点',
|
||||
path: '/admin/models'
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
title: '发放密钥',
|
||||
description: '为用户创建 API Key,设置权限和配额',
|
||||
path: '/admin/keys'
|
||||
},
|
||||
{
|
||||
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)'
|
||||
},
|
||||
{
|
||||
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 格式'
|
||||
}
|
||||
]
|
||||
|
||||
// FAQ 数据
|
||||
export const faqItems = [
|
||||
{
|
||||
id: 'concept-provider-endpoint',
|
||||
category: '概念理解',
|
||||
question: '供应商和端点有什么区别?',
|
||||
answer: '供应商是逻辑分组,用于组织管理多个端点。一个供应商可以有多个端点(比如不同区域、不同账号的 API)。端点才是实际调用 API 的配置单元,包含 URL、密钥等信息。'
|
||||
},
|
||||
{
|
||||
id: 'concept-model-mapping',
|
||||
category: '概念理解',
|
||||
question: '模型映射是什么意思?',
|
||||
answer: '模型映射让你可以用自定义的模型名(如 gpt-4)来访问实际的模型(如某端点的 gpt-4-turbo)。一个模型可以映射到多个端点,实现负载均衡和故障转移。'
|
||||
},
|
||||
{
|
||||
id: 'config-format',
|
||||
category: '配置问题',
|
||||
question: '端点的 API 格式怎么选择?',
|
||||
answer: '根据实际服务商的 API 格式选择。比如 OpenAI 官方选 OpenAI,Anthropic 官方选 Claude。如果用的是 OpenAI 兼容的第三方服务,通常选 OpenAI 格式。'
|
||||
},
|
||||
{
|
||||
id: 'config-priority',
|
||||
category: '配置问题',
|
||||
question: '端点优先级有什么用?',
|
||||
answer: '当模型关联多个端点且使用优先级负载均衡模式时,系统会先调用高优先级端点。如果失败(超时、错误等),会自动降级到低优先级端点。适合主备切换场景。'
|
||||
},
|
||||
{
|
||||
id: 'config-quota',
|
||||
category: '配置问题',
|
||||
question: '如何限制用户的使用量?',
|
||||
answer: '在 API Key 配置中设置配额:可以限制每日/每月的请求次数或 Token 用量。也可以在用户层面设置默认配额,新建的 Key 会继承用户配额。'
|
||||
},
|
||||
{
|
||||
id: 'advanced-conversion',
|
||||
category: '高级功能',
|
||||
question: '什么是格式转换?',
|
||||
answer: '格式转换允许用 OpenAI SDK 调用 Claude 模型,或用 Anthropic SDK 调用 OpenAI 模型。系统会自动转换请求和响应格式。需要在系统设置开启,并在端点配置中启用。'
|
||||
},
|
||||
{
|
||||
id: 'advanced-header-rules',
|
||||
category: '高级功能',
|
||||
question: '请求头规则有什么用?',
|
||||
answer: '可以在转发请求时添加、修改或删除 HTTP 头。常用于:添加认证信息、设置特定的 API 版本、添加跟踪标记等。'
|
||||
},
|
||||
{
|
||||
id: 'error-401',
|
||||
category: '常见错误',
|
||||
question: '返回 401 Unauthorized 错误?',
|
||||
answer: '检查:1) API Key 是否正确;2) Key 是否已过期或被禁用;3) 请求头格式是否正确(OpenAI 用 Bearer,Claude 用 x-api-key)。'
|
||||
},
|
||||
{
|
||||
id: 'error-404',
|
||||
category: '常见错误',
|
||||
question: '返回 404 Not Found 错误?',
|
||||
answer: '检查:1) 模型名称是否正确;2) 该模型是否已在系统中配置;3) API Key 是否有权限访问该模型;4) 端点 URL 是否正确。'
|
||||
},
|
||||
{
|
||||
id: 'error-502',
|
||||
category: '常见错误',
|
||||
question: '返回 502/503 错误?',
|
||||
answer: '表示上游服务不可用。检查:1) 端点健康状态;2) 供应商 API 是否正常;3) 网络连接是否正常。可以在健康监控页面查看端点状态。'
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user