refactor(guide): 重构教程页面为管理员视角

- 重新规划教程结构:概览、供应商管理、模型管理、用户与密钥、高级功能、常见问题
- 新增详细的功能解释和配置说明
- 删除旧的 CLI/SDK 用户指南页面
- 修复路由配置,添加 meta.requiresAuth: false
This commit is contained in:
fawney19
2026-01-29 13:58:06 +08:00
parent ec9aea1f0e
commit 297da59448
11 changed files with 2398 additions and 189 deletions

View File

@@ -15,9 +15,46 @@ const routes: RouteRecordRaw[] = [
{
path: '/guide',
name: 'Guide',
component: () => importWithRetry(() => import('@/views/public/Guide.vue')),
meta: { requiresAuth: false }
component: () => importWithRetry(() => import('@/views/public/guide/GuideLayout.vue')),
meta: { requiresAuth: false },
children: [
{
path: '',
name: 'GuideOverview',
component: () => importWithRetry(() => import('@/views/public/guide/Overview.vue')),
meta: { requiresAuth: false }
},
{
path: 'provider',
name: 'GuideProvider',
component: () => importWithRetry(() => import('@/views/public/guide/ProviderGuide.vue')),
meta: { requiresAuth: false }
},
{
path: 'model',
name: 'GuideModel',
component: () => importWithRetry(() => import('@/views/public/guide/ModelGuide.vue')),
meta: { requiresAuth: false }
},
{
path: 'user-key',
name: 'GuideUserKey',
component: () => importWithRetry(() => import('@/views/public/guide/UserKeyGuide.vue')),
meta: { requiresAuth: false }
},
{
path: 'advanced',
name: 'GuideAdvanced',
component: () => importWithRetry(() => import('@/views/public/guide/AdvancedGuide.vue')),
meta: { requiresAuth: false }
},
{
path: 'faq',
name: 'GuideFaq',
component: () => importWithRetry(() => import('@/views/public/guide/GuideFaq.vue')),
meta: { requiresAuth: false }
}
]
},
{
path: '/logo-demo',

View File

@@ -1,185 +0,0 @@
<template>
<div class="min-h-screen literary-grid literary-paper">
<!-- Header -->
<header class="sticky top-0 z-50 border-b border-[#cc785c]/10 dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/90 dark:bg-[#191714]/95 backdrop-blur-xl transition-all">
<div class="h-14 sm:h-16 flex items-center px-3 sm:px-4 md:px-8">
<!-- Left: Logo & Brand -->
<RouterLink
to="/"
class="flex items-center gap-2 sm:gap-3 group/logo cursor-pointer shrink-0"
>
<HeaderLogo
size="h-7 w-7 sm:h-9 sm:w-9"
class-name="text-[#191919] dark:text-white"
/>
<div class="flex flex-col justify-center">
<h1 class="text-base sm:text-lg font-bold text-[#191919] dark:text-white leading-none">
Aether
</h1>
<span class="text-[9px] sm:text-[10px] text-[#91918d] dark:text-muted-foreground leading-none mt-1 sm:mt-1.5 font-medium tracking-wide">AI Gateway</span>
</div>
</RouterLink>
<!-- Spacer -->
<div class="flex-1" />
<!-- Right: Actions -->
<div class="flex items-center gap-1 sm:gap-2 shrink-0">
<!-- Theme Toggle + GitHub Icons -->
<div class="flex items-center gap-0.5 sm:gap-1">
<button
class="flex h-8 w-8 sm:h-9 sm:w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
:title="themeMode === 'system' ? '跟随系统' : themeMode === 'dark' ? '深色模式' : '浅色模式'"
@click="toggleDarkMode"
>
<SunMoon
v-if="themeMode === 'system'"
class="h-3.5 w-3.5 sm:h-4 sm:w-4"
/>
<Sun
v-else-if="themeMode === 'light'"
class="h-3.5 w-3.5 sm:h-4 sm:w-4"
/>
<Moon
v-else
class="h-3.5 w-3.5 sm:h-4 sm:w-4"
/>
</button>
<a
href="https://github.com/fawney19/Aether"
target="_blank"
rel="noopener noreferrer"
class="flex h-8 w-8 sm:h-9 sm:w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
title="GitHub 仓库"
>
<GithubIcon class="h-3.5 w-3.5 sm:h-4 sm:w-4" />
</a>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="relative z-10 px-4 sm:px-8 md:px-16 lg:px-20 py-12 md:py-20">
<div class="max-w-4xl mx-auto">
<!-- Page Title -->
<div class="text-center mb-12">
<div
class="inline-flex items-center gap-1.5 md:gap-2 rounded-full bg-[#cc785c]/10 dark:bg-[#cc785c]/20 border border-[#cc785c]/20 dark:border-[#cc785c]/40 px-3 md:px-4 py-1.5 md:py-2 text-xs md:text-sm font-medium text-[#cc785c] dark:text-[#d4a27f] mb-4 md:mb-6 backdrop-blur-sm"
>
<BookOpen class="h-3.5 w-3.5 md:h-4 md:w-4" />
配置教程
</div>
<h1 class="text-3xl sm:text-4xl md:text-5xl font-bold text-[#191919] dark:text-white mb-4">
配置指南
</h1>
<p class="text-base md:text-lg text-[#666663] dark:text-[#c9c3b4] max-w-2xl mx-auto">
快速上手 Aether AI Gateway 的完整配置教程
</p>
</div>
<!-- Wiki Content Placeholder -->
<div class="bg-white/70 dark:bg-[#262624]/80 backdrop-blur-sm rounded-2xl p-8 md:p-12 border border-[#e5e4df] dark:border-[rgba(227,224,211,0.16)]">
<div class="text-center py-16">
<div class="flex h-16 w-16 items-center justify-center rounded-2xl bg-[#cc785c]/10 dark:bg-[#cc785c]/15 mx-auto mb-6">
<FileText class="h-8 w-8 text-[#cc785c] dark:text-[#d4a27f]" />
</div>
<h2 class="text-xl md:text-2xl font-bold text-[#191919] dark:text-white mb-3">
教程文档即将上线
</h2>
<p class="text-[#666663] dark:text-[#c9c3b4] max-w-md mx-auto mb-8">
我们正在努力编写详细的配置教程包括 Claude CodeCodex CLI Gemini CLI 的完整配置指南
</p>
<div class="flex flex-wrap items-center justify-center gap-3">
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-amber-500/10 text-amber-600 dark:text-amber-400 text-sm font-medium">
<Loader2 class="h-4 w-4 animate-spin" />
文档编写中
</div>
</div>
</div>
<!-- Coming Soon Sections -->
<div class="mt-12 pt-8 border-t border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)]">
<h3 class="text-lg font-semibold text-[#191919] dark:text-white mb-6 text-center">
即将推出的内容
</h3>
<div class="grid md:grid-cols-3 gap-4">
<div class="p-4 rounded-xl bg-[#fafaf7] dark:bg-[#1f1d1a] border border-[#e5e4df]/50 dark:border-[rgba(227,224,211,0.08)]">
<div class="flex items-center gap-3 mb-2">
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-amber-500/10">
<Code2 class="h-4 w-4 text-amber-500" />
</div>
<span class="font-medium text-[#191919] dark:text-white">Claude Code</span>
</div>
<p class="text-sm text-[#666663] dark:text-[#c9c3b4]">
IDE 集成配置教程
</p>
</div>
<div class="p-4 rounded-xl bg-[#fafaf7] dark:bg-[#1f1d1a] border border-[#e5e4df]/50 dark:border-[rgba(227,224,211,0.08)]">
<div class="flex items-center gap-3 mb-2">
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-500/10">
<Terminal class="h-4 w-4 text-emerald-500" />
</div>
<span class="font-medium text-[#191919] dark:text-white">Codex CLI</span>
</div>
<p class="text-sm text-[#666663] dark:text-[#c9c3b4]">
命令行工具配置教程
</p>
</div>
<div class="p-4 rounded-xl bg-[#fafaf7] dark:bg-[#1f1d1a] border border-[#e5e4df]/50 dark:border-[rgba(227,224,211,0.08)]">
<div class="flex items-center gap-3 mb-2">
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10">
<Sparkles class="h-4 w-4 text-primary" />
</div>
<span class="font-medium text-[#191919] dark:text-white">Gemini CLI</span>
</div>
<p class="text-sm text-[#666663] dark:text-[#c9c3b4]">
多模态 AI 配置教程
</p>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</template>
<script setup lang="ts">
import { RouterLink } from 'vue-router'
import {
BookOpen,
Code2,
FileText,
Loader2,
Moon,
Sparkles,
Sun,
SunMoon,
Terminal
} from 'lucide-vue-next'
import GithubIcon from '@/components/icons/GithubIcon.vue'
import HeaderLogo from '@/components/HeaderLogo.vue'
import { useDarkMode } from '@/composables/useDarkMode'
const { themeMode, toggleDarkMode } = useDarkMode()
</script>
<style scoped>
/* Typography */
h1, h2, h3 {
font-family: var(--serif);
letter-spacing: -0.02em;
font-weight: 500;
}
p {
font-family: var(--serif);
letter-spacing: 0.01em;
line-height: 1.7;
}
button, nav, a, .inline-flex {
font-family: var(--sans-serif);
}
</style>

View File

@@ -402,7 +402,7 @@
</div>
<div
class="mt-6 md:mt-12 transition-all duration-700 flex items-center justify-center gap-4"
class="mt-6 md:mt-12 transition-all duration-700 flex items-center justify-center gap-4 relative z-30"
:style="getButtonsStyle(SECTIONS.FEATURES)"
>
<RouterLink

View File

@@ -0,0 +1,367 @@
<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'
const props = withDefaults(
defineProps<{
baseUrl?: string
}>(),
{
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
}
)
// 格式转换示例
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 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 黑名单' }
]
}
]
</script>
<template>
<div class="space-y-8">
<!-- 标题 -->
<div class="space-y-4">
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
高级功能
</h1>
<p class="text-lg text-[#666663] dark:text-[#a3a094]">
Aether 提供了一些高级功能帮助你实现更灵活的 API 管理
</p>
</div>
<!-- 格式转换 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
格式转换
</h2>
<div :class="[panelClasses.section, 'p-5']">
<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="[panelClasses.section, 'p-5 space-y-4']">
<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="[panelClasses.section, 'p-4']">
<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">
格式转换会增加少量延迟通常 &lt;10ms部分特有功能 Claude thinkingOpenAI 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="[panelClasses.section, 'p-5']">
<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="[panelClasses.section, 'overflow-hidden']">
<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="[panelClasses.section, 'p-4']">
<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="[panelClasses.section, 'p-5']">
<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]">
系统设置页面可以配置全局参数
</p>
<div class="grid gap-4 md:grid-cols-3">
<div
v-for="category in systemSettings"
:key="category.category"
:class="[panelClasses.section, 'p-4']"
>
<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>
</section>
<!-- 健康监控 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
健康监控
</h2>
<div :class="[panelClasses.section, 'p-5']">
<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="[panelClasses.section, 'p-4']">
<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>
</section>
<!-- 下一步 -->
<section class="pt-4">
<RouterLink
to="/guide/faq"
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
>
<div class="flex-1">
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">常见问题</div>
<div class="text-sm text-[#666663] dark:text-[#a3a094]">查看使用中的常见问题和解答</div>
</div>
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
</RouterLink>
</section>
</div>
</template>

View File

@@ -0,0 +1,200 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { Search, ChevronDown, ExternalLink, HelpCircle } from 'lucide-vue-next'
import { faqItems, panelClasses } from './guide-config'
const props = withDefaults(
defineProps<{
baseUrl?: string
}>(),
{
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
}
)
// 搜索关键词
const searchQuery = ref('')
// 展开的 FAQ
const expandedIds = ref<Set<string>>(new Set())
// 分类列表
const categories = computed(() => {
const cats = new Set(faqItems.map(item => item.category))
return Array.from(cats)
})
// 过滤后的 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))
}
}
</script>
<template>
<div class="space-y-8">
<!-- 标题 -->
<div class="space-y-4">
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
常见问题
</h1>
<p class="text-lg text-[#666663] dark:text-[#a3a094]">
关于 Aether 使用和配置的常见问题解答
</p>
</div>
<!-- 搜索栏 -->
<div :class="[panelClasses.section, 'p-4']">
<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]"
/>
<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="[panelClasses.section, 'overflow-hidden']"
>
<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"
>
{{ faq.answer }}
</div>
</div>
</div>
</div>
</div>
<!-- 无结果 -->
<div
v-else
:class="[panelClasses.section, 'p-8 text-center']"
>
<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="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
>
<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="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
>
<div class="p-2 rounded-lg bg-blue-500/10">
<svg class="h-5 w-5 text-blue-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
</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>

View File

@@ -0,0 +1,258 @@
<template>
<div class="min-h-screen literary-grid literary-paper">
<!-- Header -->
<header class="sticky top-0 z-50 border-b border-[#cc785c]/10 dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/90 dark:bg-[#191714]/95 backdrop-blur-xl transition-all">
<div class="h-14 sm:h-16 flex items-center px-3 sm:px-4 md:px-8">
<!-- Left: Logo & Brand -->
<RouterLink
to="/"
class="flex items-center gap-2 sm:gap-3 group/logo cursor-pointer shrink-0"
>
<HeaderLogo
size="h-7 w-7 sm:h-9 sm:w-9"
class-name="text-[#191919] dark:text-white"
/>
<div class="flex flex-col justify-center">
<h1 class="text-base sm:text-lg font-bold text-[#191919] dark:text-white leading-none">
Aether
</h1>
<span class="text-[9px] sm:text-[10px] text-[#91918d] dark:text-muted-foreground leading-none mt-1 sm:mt-1.5 font-medium tracking-wide">AI Gateway</span>
</div>
</RouterLink>
<!-- Center: Breadcrumb -->
<div class="hidden md:flex items-center gap-2 ml-8">
<RouterLink
to="/guide"
class="text-sm text-[#666663] dark:text-muted-foreground hover:text-[#191919] dark:hover:text-white transition"
>
教程文档
</RouterLink>
<ChevronRight
v-if="currentNavItem && currentNavItem.id !== 'overview'"
class="h-4 w-4 text-[#91918d]"
/>
<span
v-if="currentNavItem && currentNavItem.id !== 'overview'"
class="text-sm font-medium text-[#191919] dark:text-white"
>
{{ currentNavItem.name }}
</span>
</div>
<!-- Spacer -->
<div class="flex-1" />
<!-- Right: Actions -->
<div class="flex items-center gap-1 sm:gap-2 shrink-0">
<!-- Mobile menu button -->
<button
class="md:hidden flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
@click="showMobileNav = !showMobileNav"
>
<Menu class="h-4 w-4" />
</button>
<!-- Theme Toggle + GitHub Icons -->
<div class="flex items-center gap-0.5 sm:gap-1">
<button
class="flex h-8 w-8 sm:h-9 sm:w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
:title="themeMode === 'system' ? '跟随系统' : themeMode === 'dark' ? '深色模式' : '浅色模式'"
@click="toggleDarkMode"
>
<SunMoon
v-if="themeMode === 'system'"
class="h-3.5 w-3.5 sm:h-4 sm:w-4"
/>
<Sun
v-else-if="themeMode === 'light'"
class="h-3.5 w-3.5 sm:h-4 sm:w-4"
/>
<Moon
v-else
class="h-3.5 w-3.5 sm:h-4 sm:w-4"
/>
</button>
<a
href="https://github.com/fawney19/Aether"
target="_blank"
rel="noopener noreferrer"
class="flex h-8 w-8 sm:h-9 sm:w-9 items-center justify-center rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/50 transition"
title="GitHub 仓库"
>
<GithubIcon class="h-3.5 w-3.5 sm:h-4 sm:w-4" />
</a>
</div>
</div>
</div>
</header>
<!-- Mobile Nav Overlay -->
<Transition name="fade">
<div
v-if="showMobileNav"
class="fixed inset-0 z-40 bg-black/50 md:hidden"
@click="showMobileNav = false"
/>
</Transition>
<!-- Mobile Nav Drawer -->
<Transition name="slide-left">
<div
v-if="showMobileNav"
class="fixed left-0 top-14 bottom-0 z-50 w-64 bg-[#fafaf7] dark:bg-[#191714] border-r border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] md:hidden overflow-y-auto"
>
<nav class="p-4 space-y-1">
<RouterLink
v-for="item in guideNavItems"
:key="item.id"
:to="item.path"
class="flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors"
:class="isActive(item.path)
? 'bg-[#cc785c]/10 text-[#cc785c] dark:text-[#d4a27f]'
: 'text-[#666663] dark:text-muted-foreground hover:bg-[#f0f0eb] dark:hover:bg-[#262624]'"
@click="showMobileNav = false"
>
<component
:is="item.icon"
class="h-4 w-4 shrink-0"
/>
<span class="text-sm font-medium">{{ item.name }}</span>
</RouterLink>
</nav>
</div>
</Transition>
<!-- Main Content -->
<div class="flex">
<!-- Desktop Sidebar -->
<aside class="hidden md:block w-64 shrink-0 border-r border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#191714]/50">
<div class="sticky top-16 h-[calc(100vh-4rem)] overflow-y-auto">
<nav class="p-4 space-y-1">
<RouterLink
v-for="item in guideNavItems"
:key="item.id"
:to="item.path"
class="flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors group"
:class="isActive(item.path)
? 'bg-[#cc785c]/10 text-[#cc785c] dark:text-[#d4a27f]'
: 'text-[#666663] dark:text-muted-foreground hover:bg-[#f0f0eb] dark:hover:bg-[#262624]'"
>
<component
:is="item.icon"
class="h-4 w-4 shrink-0"
/>
<div class="flex flex-col">
<span class="text-sm font-medium">{{ item.name }}</span>
<span
v-if="item.description"
class="text-xs text-[#91918d] dark:text-muted-foreground/70"
>
{{ item.description }}
</span>
</div>
</RouterLink>
</nav>
<!-- Base URL Input -->
<div class="p-4 border-t border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)]">
<label class="block text-xs font-medium text-[#666663] dark:text-muted-foreground mb-2">
Aether Base URL
</label>
<input
v-model="baseUrl"
type="text"
class="w-full px-3 py-2 text-sm rounded-lg border border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-white dark:bg-[#1f1d1a] text-[#191919] dark:text-white placeholder-[#91918d] focus:outline-none focus:ring-2 focus:ring-[#cc785c]/30"
placeholder="https://your-aether.com"
/>
<p class="mt-1.5 text-xs text-[#91918d]">
代码示例将使用此 URL
</p>
</div>
</div>
</aside>
<!-- Page Content -->
<main class="flex-1 min-w-0">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8 md:py-12">
<RouterView v-slot="{ Component }">
<component :is="Component" :base-url="baseUrl" />
</RouterView>
</div>
</main>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { RouterLink, RouterView, useRoute } from 'vue-router'
import {
ChevronRight,
Menu,
Moon,
Sun,
SunMoon
} from 'lucide-vue-next'
import GithubIcon from '@/components/icons/GithubIcon.vue'
import HeaderLogo from '@/components/HeaderLogo.vue'
import { useDarkMode } from '@/composables/useDarkMode'
import { guideNavItems } from './guide-config'
const route = useRoute()
const { themeMode, toggleDarkMode } = useDarkMode()
const showMobileNav = ref(false)
const baseUrl = ref(typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com')
const currentNavItem = computed(() => {
return guideNavItems.find(item => item.path === route.path)
})
function isActive(path: string): boolean {
if (path === '/guide') {
return route.path === '/guide'
}
return route.path.startsWith(path)
}
</script>
<style scoped>
/* Typography */
h1, h2, h3 {
font-family: var(--serif);
letter-spacing: -0.02em;
font-weight: 500;
}
p {
font-family: var(--serif);
letter-spacing: 0.01em;
line-height: 1.7;
}
button, nav, a, .inline-flex, input, label {
font-family: var(--sans-serif);
}
/* Transitions */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.slide-left-enter-active,
.slide-left-leave-active {
transition: transform 0.2s ease;
}
.slide-left-enter-from,
.slide-left-leave-to {
transform: translateX(-100%);
}
</style>

View File

@@ -0,0 +1,317 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router'
import { ArrowRight, Layers, Check, Info, Shuffle, TrendingUp, Gauge, Clock } from 'lucide-vue-next'
import { loadBalanceModes, panelClasses } from './guide-config'
const props = withDefaults(
defineProps<{
baseUrl?: string
}>(),
{
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
}
)
// 模型配置字段
const modelFields = [
{ name: '模型名称', description: '用户调用时使用的模型名,如 gpt-4、claude-3', required: true },
{ name: '别名', description: '模型的其他名称,用户可以用别名调用', required: false },
{ name: '关联端点', description: '该模型使用的端点列表,可多选', required: true },
{ name: '负载均衡模式', description: '多端点时的请求分发策略', required: false },
{ name: '目标模型名', description: '发送给端点的实际模型名(如端点模型名不同)', required: false },
{ name: '是否启用', description: '禁用后用户无法使用该模型', required: false }
]
// 负载均衡图标
const lbIcons = {
priority: TrendingUp,
random: Shuffle,
round_robin: Clock,
weighted: Gauge,
latency: Clock
}
</script>
<template>
<div class="space-y-8">
<!-- 标题 -->
<div class="space-y-4">
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
模型管理
</h1>
<p class="text-lg text-[#666663] dark:text-[#a3a094]">
模型是用户实际调用的对象通过模型配置你可以定义模型名称关联端点配置负载均衡策略
</p>
</div>
<!-- 模型概念 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
什么是模型
</h2>
<div :class="[panelClasses.section, 'p-5']">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 rounded-lg bg-purple-500/10">
<Layers class="h-5 w-5 text-purple-500" />
</div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">模型 (Model)</h3>
</div>
<ul class="space-y-3 text-sm text-[#666663] dark:text-[#a3a094]">
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>模型是用户在请求中指定的 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">model</code> 参数值</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>一个模型可以关联多个端点实现负载均衡和故障转移</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>可以设置别名让用户用多个名称访问同一个模型</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>支持模型名映射请求中的模型名可以与发给端点的不同</span>
</li>
</ul>
</div>
<div :class="[panelClasses.section, 'p-4']">
<div class="flex items-start gap-3">
<Info class="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">示例场景</p>
<p class="mt-1">
用户请求模型 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">gpt-4</code>
你可以配置它同时使用 OpenAI 官方端点和某个代理端点
系统会根据负载均衡策略选择一个端点如果失败则自动尝试其他端点
</p>
</div>
</div>
</div>
</section>
<!-- 创建模型 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
创建模型
</h2>
<div :class="[panelClasses.section, 'p-5 space-y-4']">
<div class="flex items-start gap-4">
<div class="w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
1
</div>
<div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">进入模型管理页面</h3>
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
在管理后台左侧菜单点击模型管理
</p>
</div>
</div>
<div class="flex items-start gap-4">
<div class="w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
2
</div>
<div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">添加模型</h3>
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
点击添加模型填写模型名称这是用户调用时使用的名称
</p>
</div>
</div>
<div class="flex items-start gap-4">
<div class="w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
3
</div>
<div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">关联端点</h3>
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
选择该模型要使用的端点可以选择多个如果端点的模型名与你定义的不同需要设置目标模型名映射
</p>
</div>
</div>
<div class="flex items-start gap-4">
<div class="w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
4
</div>
<div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">配置负载均衡</h3>
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
如果选择了多个端点可以配置负载均衡策略
</p>
</div>
</div>
</div>
</section>
<!-- 模型配置字段 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
配置字段说明
</h2>
<div :class="[panelClasses.section, 'overflow-hidden']">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">字段</th>
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">说明</th>
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">必填</th>
</tr>
</thead>
<tbody>
<tr
v-for="field in modelFields"
:key="field.name"
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
>
<td class="px-4 py-3 font-medium text-[#262624] dark:text-[#f1ead8]">
{{ field.name }}
</td>
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
{{ field.description }}
</td>
<td class="px-4 py-3 text-center">
<span v-if="field.required" :class="panelClasses.badgeGreen">必填</span>
<span v-else class="text-[#999]">可选</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- 负载均衡模式 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
负载均衡模式
</h2>
<p class="text-[#666663] dark:text-[#a3a094]">
当模型关联多个端点时系统会根据负载均衡模式选择端点
</p>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<div
v-for="lb in loadBalanceModes"
:key="lb.mode"
:class="[panelClasses.section, 'p-4']"
>
<div class="flex items-center gap-3 mb-2">
<component
:is="lbIcons[lb.mode as keyof typeof lbIcons] || Shuffle"
class="h-5 w-5 text-[#cc785c]"
/>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">{{ lb.name }}</h3>
</div>
<p class="text-sm text-[#666663] dark:text-[#a3a094]">{{ lb.description }}</p>
</div>
</div>
<div :class="[panelClasses.section, 'p-4']">
<div class="flex items-start gap-3">
<Info class="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">推荐配置</p>
<p class="mt-1">
<strong>主备切换场景</strong>使用优先级模式将官方端点设为高优先级代理端点设为低优先级<br />
<strong>分摊负载场景</strong>使用轮询加权模式将多个同质端点平均分配请求
</p>
</div>
</div>
</div>
</section>
<!-- 模型名映射 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
模型名映射
</h2>
<div :class="[panelClasses.section, 'p-5']">
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
有时候你希望用户调用的模型名与实际发给端点的模型名不同例如
</p>
<div class="space-y-3">
<div class="flex items-center gap-4 text-sm">
<div class="px-3 py-2 rounded-lg bg-purple-500/10 border border-purple-500/20">
<span class="text-purple-600 dark:text-purple-400">用户请求</span>
<code class="ml-2 text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">gpt-4</code>
</div>
<ArrowRight class="h-4 w-4 text-[#999]" />
<div class="px-3 py-2 rounded-lg bg-green-500/10 border border-green-500/20">
<span class="text-green-600 dark:text-green-400">发给端点</span>
<code class="ml-2 text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">gpt-4-turbo-preview</code>
</div>
</div>
<div class="flex items-center gap-4 text-sm">
<div class="px-3 py-2 rounded-lg bg-purple-500/10 border border-purple-500/20">
<span class="text-purple-600 dark:text-purple-400">用户请求</span>
<code class="ml-2 text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">claude-3</code>
</div>
<ArrowRight class="h-4 w-4 text-[#999]" />
<div class="px-3 py-2 rounded-lg bg-green-500/10 border border-green-500/20">
<span class="text-green-600 dark:text-green-400">发给端点</span>
<code class="ml-2 text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">claude-3-opus-20240229</code>
</div>
</div>
</div>
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-4">
在模型的端点关联配置中可以为每个端点设置目标模型名来实现这种映射
</p>
</div>
</section>
<!-- 别名功能 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
模型别名
</h2>
<div :class="[panelClasses.section, 'p-5']">
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
别名让用户可以用多个名称访问同一个模型适用于
</p>
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>兼容旧的模型名 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">gpt-4-turbo</code> <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">gpt-4-turbo-preview</code> 指向同一个模型</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>提供简短易记的名称如用 <code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">claude</code> 代替完整版本号</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>版本迁移时保持向后兼容</span>
</li>
</ul>
</div>
</section>
<!-- 下一步 -->
<section class="pt-4">
<RouterLink
to="/guide/user-key"
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
>
<div class="flex-1">
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">下一步用户与密钥</div>
<div class="text-sm text-[#666663] dark:text-[#a3a094]">管理用户和 API Key</div>
</div>
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
</RouterLink>
</section>
</div>
</template>

View File

@@ -0,0 +1,243 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router'
import { ArrowRight, Server, Layers, Key, Box, ChevronRight } from 'lucide-vue-next'
import { coreConcepts, apiFormats, configSteps, panelClasses } from './guide-config'
const props = withDefaults(
defineProps<{
baseUrl?: string
}>(),
{
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
}
)
// 概念图标映射
const conceptIcons = {
blue: Server,
green: Box,
purple: Layers,
orange: Key
}
// 概念颜色类
const conceptColors = {
blue: 'border-blue-500/30 bg-blue-500/5',
green: 'border-green-500/30 bg-green-500/5',
purple: 'border-purple-500/30 bg-purple-500/5',
orange: 'border-orange-500/30 bg-orange-500/5'
}
const conceptIconColors = {
blue: 'text-blue-500',
green: 'text-green-500',
purple: 'text-purple-500',
orange: 'text-orange-500'
}
</script>
<template>
<div class="space-y-8">
<!-- 标题区域 -->
<div class="space-y-4">
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
欢迎使用 Aether
</h1>
<p class="text-lg text-[#666663] dark:text-[#a3a094]">
Aether 是一个 AI API 网关帮助你统一管理多个 AI 服务供应商实现负载均衡访问控制和用量统计
</p>
</div>
<!-- 核心概念 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
核心概念
</h2>
<p class="text-[#666663] dark:text-[#a3a094]">
理解这四个核心概念是配置 Aether 的基础
</p>
<div class="grid gap-4 sm:grid-cols-2">
<div
v-for="concept in coreConcepts"
:key="concept.name"
:class="[
'p-4 rounded-xl border-2 transition-all',
conceptColors[concept.color as keyof typeof conceptColors]
]"
>
<div class="flex items-start gap-3">
<component
:is="conceptIcons[concept.color as keyof typeof conceptIcons]"
:class="[
'h-6 w-6 flex-shrink-0 mt-0.5',
conceptIconColors[concept.color as keyof typeof conceptIconColors]
]"
/>
<div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
{{ concept.name }}
</h3>
<p class="mt-1 text-sm text-[#666663] dark:text-[#a3a094]">
{{ concept.description }}
</p>
</div>
</div>
</div>
</div>
<!-- 关系图 -->
<div :class="[panelClasses.section, 'p-6 mt-6']">
<h3 class="text-sm font-medium text-[#666663] dark:text-[#a3a094] mb-4">
它们之间的关系
</h3>
<div class="flex items-center justify-center gap-3 flex-wrap">
<div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-blue-500/10 border border-blue-500/20">
<Server class="h-4 w-4 text-blue-500" />
<span class="text-sm font-medium text-blue-600 dark:text-blue-400">供应商</span>
</div>
<ChevronRight class="h-5 w-5 text-[#999] hidden sm:block" />
<ArrowRight class="h-5 w-5 text-[#999] sm:hidden" />
<div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-green-500/10 border border-green-500/20">
<Box class="h-4 w-4 text-green-500" />
<span class="text-sm font-medium text-green-600 dark:text-green-400">端点</span>
</div>
<ChevronRight class="h-5 w-5 text-[#999] hidden sm:block" />
<ArrowRight class="h-5 w-5 text-[#999] sm:hidden" />
<div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-purple-500/10 border border-purple-500/20">
<Layers class="h-4 w-4 text-purple-500" />
<span class="text-sm font-medium text-purple-600 dark:text-purple-400">模型</span>
</div>
<ChevronRight class="h-5 w-5 text-[#999] hidden sm:block" />
<ArrowRight class="h-5 w-5 text-[#999] sm:hidden" />
<div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-orange-500/10 border border-orange-500/20">
<Key class="h-4 w-4 text-orange-500" />
<span class="text-sm font-medium text-orange-600 dark:text-orange-400">API Key</span>
</div>
</div>
<p class="text-center text-sm text-[#666663] dark:text-[#a3a094] mt-4">
一个供应商可以有多个端点一个模型可以关联多个端点一个 API Key 可以访问多个模型
</p>
</div>
</section>
<!-- 配置流程 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
配置流程
</h2>
<p class="text-[#666663] dark:text-[#a3a094]">
按照以下步骤完成初始配置
</p>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<div
v-for="step in configSteps"
:key="step.step"
:class="[panelClasses.section, 'p-4 relative']"
>
<div class="absolute -top-3 -left-2 w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm">
{{ step.step }}
</div>
<div class="pt-2">
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
{{ step.title }}
</h3>
<p class="mt-1 text-sm text-[#666663] dark:text-[#a3a094]">
{{ step.description }}
</p>
</div>
</div>
</div>
</section>
<!-- 支持的 API 格式 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
支持的 API 格式
</h2>
<p class="text-[#666663] dark:text-[#a3a094]">
Aether 支持多种 API 格式可以作为不同客户端的统一入口
</p>
<div :class="[panelClasses.section, 'overflow-hidden']">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">格式</th>
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">端点</th>
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">认证方式</th>
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">常用客户端</th>
</tr>
</thead>
<tbody>
<tr
v-for="format in apiFormats"
:key="format.name"
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
>
<td class="px-4 py-3 font-medium text-[#262624] dark:text-[#f1ead8]">
{{ format.name }}
</td>
<td class="px-4 py-3 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
{{ format.endpoint }}
</td>
<td class="px-4 py-3 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
{{ format.auth }}
</td>
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
{{ format.clients.join(', ') }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- 快速导航 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
开始配置
</h2>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<RouterLink
to="/guide/provider"
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
>
<Server class="h-5 w-5 text-[#cc785c]" />
<div class="flex-1">
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">供应商管理</div>
<div class="text-sm text-[#666663] dark:text-[#a3a094]">添加和配置 API 供应商</div>
</div>
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
</RouterLink>
<RouterLink
to="/guide/model"
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
>
<Layers class="h-5 w-5 text-[#cc785c]" />
<div class="flex-1">
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">模型管理</div>
<div class="text-sm text-[#666663] dark:text-[#a3a094]">配置模型和负载均衡</div>
</div>
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
</RouterLink>
<RouterLink
to="/guide/user-key"
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
>
<Key class="h-5 w-5 text-[#cc785c]" />
<div class="flex-1">
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">用户与密钥</div>
<div class="text-sm text-[#666663] dark:text-[#a3a094]">管理用户和 API Key</div>
</div>
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
</RouterLink>
</div>
</section>
</div>
</template>

View File

@@ -0,0 +1,343 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router'
import { ArrowRight, Server, Plus, Settings, Check, AlertTriangle, Info } from 'lucide-vue-next'
import { apiFormats, panelClasses } from './guide-config'
const props = withDefaults(
defineProps<{
baseUrl?: string
}>(),
{
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
}
)
// 端点配置字段说明
const endpointFields = [
{ name: '名称', description: '端点的显示名称,用于区分不同的端点', required: true },
{ name: 'URL', description: 'API 的基础 URL如 https://api.openai.com', required: true },
{ name: 'API Key', description: '调用该 API 需要的密钥', required: true },
{ name: 'API 格式', description: '该端点支持的 API 格式OpenAI/Claude/Gemini 等)', required: true },
{ name: '优先级', description: '数字越大优先级越高,用于负载均衡', required: false },
{ name: '权重', description: '加权负载均衡时使用的权重值', required: false },
{ name: '代理', description: '如需通过代理访问,填写代理地址', required: false },
{ name: '超时', description: '请求超时时间(秒)', required: false }
]
// 常见供应商配置示例
const providerExamples = [
{
name: 'OpenAI',
url: 'https://api.openai.com',
format: 'OpenAI',
note: '官方 API需要国际信用卡或通过代理访问'
},
{
name: 'Anthropic',
url: 'https://api.anthropic.com',
format: 'Claude',
note: '官方 Claude API'
},
{
name: 'Google AI',
url: 'https://generativelanguage.googleapis.com',
format: 'Gemini',
note: '官方 Gemini API'
},
{
name: 'Azure OpenAI',
url: 'https://{resource}.openai.azure.com',
format: 'OpenAI',
note: '需要替换 {resource} 为你的资源名'
},
{
name: 'OpenRouter',
url: 'https://openrouter.ai/api',
format: 'OpenAI',
note: '聚合多家供应商的 API 代理'
},
{
name: '自托管 / 其他',
url: 'https://your-api.com',
format: 'OpenAI',
note: '大多数 OpenAI 兼容服务选择 OpenAI 格式'
}
]
</script>
<template>
<div class="space-y-8">
<!-- 标题 -->
<div class="space-y-4">
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
供应商管理
</h1>
<p class="text-lg text-[#666663] dark:text-[#a3a094]">
供应商和端点是 Aether 的基础配置决定了系统可以调用哪些 AI 服务
</p>
</div>
<!-- 概念说明 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
供应商 vs 端点
</h2>
<div class="grid gap-4 md:grid-cols-2">
<div :class="[panelClasses.section, 'p-5']">
<div class="flex items-center gap-3 mb-3">
<div class="p-2 rounded-lg bg-blue-500/10">
<Server class="h-5 w-5 text-blue-500" />
</div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">供应商 (Provider)</h3>
</div>
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>逻辑分组用于组织管理多个端点</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>一个供应商可以有多个端点</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>例如OpenAI 供应商下可以有官方端点和多个代理端点</span>
</li>
</ul>
</div>
<div :class="[panelClasses.section, 'p-5']">
<div class="flex items-center gap-3 mb-3">
<div class="p-2 rounded-lg bg-green-500/10">
<Settings class="h-5 w-5 text-green-500" />
</div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">端点 (Endpoint)</h3>
</div>
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>实际的 API 配置单元</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>包含 URL密钥API 格式等信息</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>系统实际调用的是端点而非供应商</span>
</li>
</ul>
</div>
</div>
</section>
<!-- 添加供应商步骤 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
添加供应商
</h2>
<div :class="[panelClasses.section, 'p-5 space-y-4']">
<div class="flex items-start gap-4">
<div class="w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
1
</div>
<div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">进入供应商管理页面</h3>
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
登录管理后台在左侧菜单点击供应商管理
</p>
</div>
</div>
<div class="flex items-start gap-4">
<div class="w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
2
</div>
<div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">创建供应商</h3>
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
点击添加供应商按钮填写供应商名称 OpenAIAnthropic
</p>
</div>
</div>
<div class="flex items-start gap-4">
<div class="w-8 h-8 rounded-full bg-[#cc785c] flex items-center justify-center text-white font-bold text-sm flex-shrink-0">
3
</div>
<div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">添加端点</h3>
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
在供应商下点击添加端点填写 API URL密钥等配置
</p>
</div>
</div>
</div>
</section>
<!-- 端点配置字段 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
端点配置字段
</h2>
<div :class="[panelClasses.section, 'overflow-hidden']">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">字段</th>
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">说明</th>
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">必填</th>
</tr>
</thead>
<tbody>
<tr
v-for="field in endpointFields"
:key="field.name"
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
>
<td class="px-4 py-3 font-medium text-[#262624] dark:text-[#f1ead8]">
{{ field.name }}
</td>
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
{{ field.description }}
</td>
<td class="px-4 py-3 text-center">
<span v-if="field.required" :class="panelClasses.badgeGreen">必填</span>
<span v-else class="text-[#999]">可选</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- API 格式选择 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
API 格式选择
</h2>
<div :class="[panelClasses.section, 'p-4']">
<div class="flex items-start gap-3">
<Info class="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">如何选择正确的 API 格式</p>
<p class="mt-1">
根据目标 API 服务的实际格式选择大多数第三方 API 代理 OpenRouter都兼容 OpenAI 格式
如果不确定可以先尝试 OpenAI 格式
</p>
</div>
</div>
</div>
<div :class="[panelClasses.section, 'overflow-hidden']">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">格式</th>
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">端点路径</th>
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">认证方式</th>
</tr>
</thead>
<tbody>
<tr
v-for="format in apiFormats"
:key="format.name"
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
>
<td class="px-4 py-3 font-medium text-[#262624] dark:text-[#f1ead8]">
{{ format.name }}
</td>
<td class="px-4 py-3 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
{{ format.endpoint }}
</td>
<td class="px-4 py-3 font-mono text-xs text-[#666663] dark:text-[#a3a094]">
{{ format.auth }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- 常见供应商示例 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
常见供应商配置
</h2>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<div
v-for="example in providerExamples"
:key="example.name"
:class="[panelClasses.section, 'p-4']"
>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">
{{ example.name }}
</h3>
<div class="mt-2 space-y-1 text-sm">
<div>
<span class="text-[#666663] dark:text-[#a3a094]">URL: </span>
<code class="text-xs bg-[#f5f5f0] dark:bg-[#1f1d1a] px-1.5 py-0.5 rounded">{{ example.url }}</code>
</div>
<div>
<span class="text-[#666663] dark:text-[#a3a094]">格式: </span>
<span :class="panelClasses.badgeBlue">{{ example.format }}</span>
</div>
</div>
<p class="mt-2 text-xs text-[#999]">{{ example.note }}</p>
</div>
</div>
</section>
<!-- 注意事项 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
注意事项
</h2>
<div :class="[panelClasses.section, 'p-4 space-y-3']">
<div class="flex items-start gap-3">
<AlertTriangle class="h-5 w-5 text-yellow-500 flex-shrink-0 mt-0.5" />
<div class="text-sm">
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">API Key 安全</p>
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
端点的 API Key 会被加密存储但仍建议使用子账号或限定范围的 Key而非主账号 Key
</p>
</div>
</div>
<div class="flex items-start gap-3">
<AlertTriangle class="h-5 w-5 text-yellow-500 flex-shrink-0 mt-0.5" />
<div class="text-sm">
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">测试端点</p>
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
添加端点后可以在健康监控页面测试连通性如果显示不健康请检查 URL Key 是否正确
</p>
</div>
</div>
</div>
</section>
<!-- 下一步 -->
<section class="pt-4">
<RouterLink
to="/guide/model"
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
>
<div class="flex-1">
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">下一步模型管理</div>
<div class="text-sm text-[#666663] dark:text-[#a3a094]">配置模型映射和负载均衡</div>
</div>
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
</RouterLink>
</section>
</div>
</template>

View File

@@ -0,0 +1,365 @@
<script setup lang="ts">
import { RouterLink } from 'vue-router'
import { ArrowRight, Users, Key, Shield, Check, Info, AlertTriangle, Clock } from 'lucide-vue-next'
import { panelClasses } from './guide-config'
const props = withDefaults(
defineProps<{
baseUrl?: string
}>(),
{
baseUrl: typeof window !== 'undefined' ? window.location.origin : 'https://your-aether.com'
}
)
// 用户字段说明
const userFields = [
{ name: '用户名/邮箱', description: '用户的登录凭证', required: true },
{ name: '角色', description: '普通用户或管理员', required: true },
{ name: '状态', description: '启用/禁用用户', required: false },
{ name: '默认配额', description: '该用户创建的 Key 默认继承的配额设置', required: false }
]
// API Key 字段说明
const keyFields = [
{ name: '名称', description: 'Key 的描述性名称,方便识别', required: true },
{ name: '所属用户', description: 'Key 归属的用户', required: true },
{ name: '有效期', description: '过期时间,不设置则永久有效', required: false },
{ name: '允许的模型', description: '限制该 Key 可访问的模型列表,不设置则可访问所有模型', required: false },
{ name: '请求配额', description: '每日/每月的请求次数限制', required: false },
{ name: 'Token 配额', description: '每日/每月的 Token 用量限制', required: false },
{ name: 'IP 白名单', description: '限制只有特定 IP 可以使用该 Key', required: false },
{ name: '状态', description: '启用/禁用该 Key', required: false }
]
// 权限对比
const roleComparison = [
{ feature: '查看仪表盘', user: true, admin: true },
{ feature: '使用 API', user: true, admin: true },
{ feature: '管理自己的 API Key', user: true, admin: true },
{ feature: '查看用量统计', user: true, admin: true },
{ feature: '管理供应商/端点', user: false, admin: true },
{ feature: '管理模型', user: false, admin: true },
{ feature: '管理其他用户', user: false, admin: true },
{ feature: '管理其他用户的 Key', user: false, admin: true },
{ feature: '系统设置', user: false, admin: true }
]
</script>
<template>
<div class="space-y-8">
<!-- 标题 -->
<div class="space-y-4">
<h1 class="text-3xl font-bold text-[#262624] dark:text-[#f1ead8]">
用户与密钥
</h1>
<p class="text-lg text-[#666663] dark:text-[#a3a094]">
用户和 API Key Aether 的访问控制核心通过用户管理分配角色权限通过 Key 管理控制 API 访问
</p>
</div>
<!-- 用户管理 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
用户管理
</h2>
<div class="grid gap-4 md:grid-cols-2">
<div :class="[panelClasses.section, 'p-5']">
<div class="flex items-center gap-3 mb-3">
<div class="p-2 rounded-lg bg-blue-500/10">
<Users class="h-5 w-5 text-blue-500" />
</div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">普通用户</h3>
</div>
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>可以使用 API 调用模型</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>管理自己的 API Key</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>查看自己的用量统计</span>
</li>
</ul>
</div>
<div :class="[panelClasses.section, 'p-5']">
<div class="flex items-center gap-3 mb-3">
<div class="p-2 rounded-lg bg-orange-500/10">
<Shield class="h-5 w-5 text-orange-500" />
</div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">管理员</h3>
</div>
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>管理供应商端点模型</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>管理所有用户和 Key</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>系统设置和监控</span>
</li>
</ul>
</div>
</div>
<!-- 权限对比表 -->
<div :class="[panelClasses.section, 'overflow-hidden']">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">功能</th>
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">普通用户</th>
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">管理员</th>
</tr>
</thead>
<tbody>
<tr
v-for="item in roleComparison"
:key="item.feature"
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
>
<td class="px-4 py-3 text-[#262624] dark:text-[#f1ead8]">
{{ item.feature }}
</td>
<td class="px-4 py-3 text-center">
<Check v-if="item.user" class="h-5 w-5 text-green-500 mx-auto" />
<span v-else class="text-[#999]"></span>
</td>
<td class="px-4 py-3 text-center">
<Check v-if="item.admin" class="h-5 w-5 text-green-500 mx-auto" />
<span v-else class="text-[#999]"></span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- API Key 管理 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
API Key 管理
</h2>
<div :class="[panelClasses.section, 'p-5']">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 rounded-lg bg-orange-500/10">
<Key class="h-5 w-5 text-orange-500" />
</div>
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">什么是 API Key</h3>
</div>
<ul class="space-y-3 text-sm text-[#666663] dark:text-[#a3a094]">
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>API Key 是用户调用 Aether API 的凭证</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>一个用户可以有多个 Key用于不同场景如开发生产测试</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>可以为 Key 设置细粒度的权限和配额限制</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span>Key 的用量会记录到所属用户</span>
</li>
</ul>
</div>
</section>
<!-- Key 配置字段 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
Key 配置选项
</h2>
<div :class="[panelClasses.section, 'overflow-hidden']">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)] bg-[#fafaf7]/50 dark:bg-[#1f1d1a]/50">
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">选项</th>
<th class="px-4 py-3 text-left font-medium text-[#666663] dark:text-[#a3a094]">说明</th>
<th class="px-4 py-3 text-center font-medium text-[#666663] dark:text-[#a3a094]">必填</th>
</tr>
</thead>
<tbody>
<tr
v-for="field in keyFields"
:key="field.name"
class="border-b border-[#e5e4df] dark:border-[rgba(227,224,211,0.08)] last:border-0"
>
<td class="px-4 py-3 font-medium text-[#262624] dark:text-[#f1ead8]">
{{ field.name }}
</td>
<td class="px-4 py-3 text-[#666663] dark:text-[#a3a094]">
{{ field.description }}
</td>
<td class="px-4 py-3 text-center">
<span v-if="field.required" :class="panelClasses.badgeGreen">必填</span>
<span v-else class="text-[#999]">可选</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- 配额设置 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
配额设置
</h2>
<div :class="[panelClasses.section, 'p-5 space-y-4']">
<div class="flex items-center gap-3">
<Clock class="h-5 w-5 text-[#cc785c]" />
<h3 class="font-semibold text-[#262624] dark:text-[#f1ead8]">配额类型</h3>
</div>
<div class="grid gap-4 md:grid-cols-2">
<div class="p-4 rounded-lg bg-[#f5f5f0]/50 dark:bg-[#1f1d1a]/50">
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">请求次数配额</h4>
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
限制每日/每月的 API 调用次数超过后请求会被拒绝
</p>
</div>
<div class="p-4 rounded-lg bg-[#f5f5f0]/50 dark:bg-[#1f1d1a]/50">
<h4 class="font-medium text-[#262624] dark:text-[#f1ead8]">Token 用量配额</h4>
<p class="text-sm text-[#666663] dark:text-[#a3a094] mt-1">
限制每日/每月的 Token 消耗量适合控制成本
</p>
</div>
</div>
<div class="flex items-start gap-3">
<Info class="h-5 w-5 text-blue-500 flex-shrink-0 mt-0.5" />
<div class="text-sm text-[#666663] dark:text-[#a3a094]">
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">配额继承</p>
<p class="mt-1">
可以在用户级别设置默认配额新创建的 Key 会自动继承该配额
也可以在创建 Key 时覆盖默认配额
</p>
</div>
</div>
</div>
</section>
<!-- 模型访问控制 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
模型访问控制
</h2>
<div :class="[panelClasses.section, 'p-5']">
<p class="text-sm text-[#666663] dark:text-[#a3a094] mb-4">
通过允许的模型字段可以限制 Key 只能访问特定模型
</p>
<ul class="space-y-2 text-sm text-[#666663] dark:text-[#a3a094]">
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span><strong>不设置</strong>Key 可以访问所有启用的模型</span>
</li>
<li class="flex items-start gap-2">
<Check class="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
<span><strong>设置模型列表</strong>Key 只能访问列表中的模型</span>
</li>
</ul>
<div class="mt-4 p-4 rounded-lg bg-yellow-500/10 border border-yellow-500/20">
<div class="flex items-start gap-2">
<AlertTriangle class="h-5 w-5 text-yellow-600 dark:text-yellow-400 flex-shrink-0 mt-0.5" />
<div class="text-sm">
<p class="font-medium text-yellow-700 dark:text-yellow-300">使用场景</p>
<p class="text-yellow-600 dark:text-yellow-400 mt-1">
比如限制免费用户只能使用 gpt-3.5付费用户可以使用 gpt-4
或者为不同项目创建只能访问特定模型的 Key
</p>
</div>
</div>
</div>
</div>
</section>
<!-- 安全建议 -->
<section class="space-y-4">
<h2 class="text-xl font-semibold text-[#262624] dark:text-[#f1ead8]">
安全建议
</h2>
<div :class="[panelClasses.section, 'p-4 space-y-3']">
<div class="flex items-start gap-3">
<Shield class="h-5 w-5 text-[#cc785c] flex-shrink-0 mt-0.5" />
<div class="text-sm">
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">设置 Key 有效期</p>
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
为临时使用的 Key 设置过期时间避免遗忘造成安全风险
</p>
</div>
</div>
<div class="flex items-start gap-3">
<Shield class="h-5 w-5 text-[#cc785c] flex-shrink-0 mt-0.5" />
<div class="text-sm">
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">按场景分配 Key</p>
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
为开发测试生产环境分别创建 Key便于追踪和管理
</p>
</div>
</div>
<div class="flex items-start gap-3">
<Shield class="h-5 w-5 text-[#cc785c] flex-shrink-0 mt-0.5" />
<div class="text-sm">
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">启用 IP 白名单</p>
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
对于生产环境的 Key配置 IP 白名单可以防止 Key 泄露后被滥用
</p>
</div>
</div>
<div class="flex items-start gap-3">
<Shield class="h-5 w-5 text-[#cc785c] flex-shrink-0 mt-0.5" />
<div class="text-sm">
<p class="font-medium text-[#262624] dark:text-[#f1ead8]">定期审计</p>
<p class="text-[#666663] dark:text-[#a3a094] mt-1">
定期检查用量统计和审计日志发现异常及时禁用相关 Key
</p>
</div>
</div>
</div>
</section>
<!-- 下一步 -->
<section class="pt-4">
<RouterLink
to="/guide/advanced"
:class="[panelClasses.section, panelClasses.cardHover, 'p-4 flex items-center gap-3 group']"
>
<div class="flex-1">
<div class="font-medium text-[#262624] dark:text-[#f1ead8]">下一步高级功能</div>
<div class="text-sm text-[#666663] dark:text-[#a3a094]">格式转换请求头规则等高级配置</div>
</div>
<ArrowRight class="h-5 w-5 text-[#999] group-hover:text-[#cc785c] transition-colors" />
</RouterLink>
</section>
</div>
</template>

View File

@@ -0,0 +1,264 @@
import type { Component } from 'vue'
import {
BookOpen,
Server,
Layers,
Users,
Settings,
HelpCircle
} from 'lucide-vue-next'
// 导航配置
export interface GuideNavItem {
id: string
name: string
path: string
icon: Component
description?: string
}
export const guideNavItems: GuideNavItem[] = [
{
id: 'overview',
name: '概览',
path: '/guide',
icon: BookOpen,
description: '系统架构与核心概念'
},
{
id: 'provider',
name: '供应商管理',
path: '/guide/provider',
icon: Server,
description: '添加供应商与端点配置'
},
{
id: 'model',
name: '模型管理',
path: '/guide/model',
icon: Layers,
description: '模型映射与负载均衡'
},
{
id: 'user-key',
name: '用户与密钥',
path: '/guide/user-key',
icon: Users,
description: 'API Key 与权限管理'
},
{
id: 'advanced',
name: '高级功能',
path: '/guide/advanced',
icon: Settings,
description: '格式转换、请求头规则等'
},
{
id: 'faq',
name: '常见问题',
path: '/guide/faq',
icon: HelpCircle,
description: '使用中的常见问题'
}
]
// 样式类常量
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',
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]',
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',
iconButtonSmall: [
'flex items-center justify-center rounded-lg border h-7 w-7',
'border-[#e5e4df] dark:border-[rgba(227,224,211,0.12)]',
'bg-transparent',
'text-[#666663] dark:text-[#f1ead8]',
'transition hover:bg-[#f0f0eb] dark:hover:bg-[#3a3731]'
].join(' ')
} as const
// 核心概念数据
export const coreConcepts = [
{
name: '供应商 (Provider)',
description: '代表一个 AI 服务提供商,如 OpenAI、Anthropic、Google 等',
color: 'blue'
},
{
name: '端点 (Endpoint)',
description: '供应商下的具体 API 端点,包含 URL、密钥、API 格式等配置',
color: 'green'
},
{
name: '模型 (Model)',
description: '可供用户使用的模型,可关联多个端点实现负载均衡',
color: 'purple'
},
{
name: 'API Key',
description: '用户访问系统的凭证,可设置权限、配额、允许的模型等',
color: 'orange'
}
]
// API 格式说明
export const apiFormats = [
{
name: 'OpenAI',
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: 'Claude',
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',
endpoint: '/v1beta/models/{model}:generateContent',
auth: 'x-goog-api-key: xxx',
clients: ['Gemini SDK', 'Gemini CLI']
}
]
// 负载均衡模式
export const loadBalanceModes = [
{
mode: 'priority',
name: '优先级',
description: '按端点优先级顺序调用,高优先级的先用,失败后降级到低优先级'
},
{
mode: 'random',
name: '随机',
description: '随机选择一个可用端点,适合多个同质端点'
},
{
mode: 'round_robin',
name: '轮询',
description: '依次轮流使用各个端点,分摊负载'
},
{
mode: 'weighted',
name: '加权',
description: '按权重比例分配请求,权重高的端点处理更多请求'
},
{
mode: 'latency',
name: '最低延迟',
description: '优先使用历史延迟最低的端点'
}
]
// 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 官方选 OpenAIAnthropic 官方选 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 用 BearerClaude 用 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) 网络连接是否正常。可以在健康监控页面查看端点状态。'
}
]
// 配置流程步骤
export const configSteps = [
{
step: 1,
title: '添加供应商',
description: '创建一个供应商来组织你的 API 端点',
path: '/admin/providers'
},
{
step: 2,
title: '配置端点',
description: '在供应商下添加 API 端点,填写 URL、密钥等',
path: '/admin/providers'
},
{
step: 3,
title: '创建模型',
description: '创建用户可用的模型,关联到端点',
path: '/admin/models'
},
{
step: 4,
title: '发放密钥',
description: '为用户创建 API Key设置权限和配额',
path: '/admin/keys'
}
]