feat: add in-app docs page with sidebar navigation and quickstart guide, replace /docs redirect with built-in page
This commit is contained in:
parent
dfa7ccbd97
commit
64a4a68a6f
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/middleware"
|
||||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
|
@ -20,31 +19,6 @@ func SetRouter(router *gin.Engine, buildFS embed.FS, indexPage []byte) {
|
|||
SetRelayRouter(router)
|
||||
SetVideoRouter(router)
|
||||
|
||||
// 文档重定向:拦截 /docs 和 /{lang}/docs 路径,转发到配置的 docs_link
|
||||
router.Use(func(c *gin.Context) {
|
||||
path := c.Request.URL.Path
|
||||
var prefix string
|
||||
if path == "/docs" || strings.HasPrefix(path, "/docs/") {
|
||||
prefix = "/docs"
|
||||
} else {
|
||||
for _, lp := range []string{"/zh/docs", "/en/docs", "/ja/docs"} {
|
||||
if path == lp || strings.HasPrefix(path, lp+"/") {
|
||||
prefix = lp
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if prefix == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
docsLink := operation_setting.GetGeneralSetting().DocsLink
|
||||
subPath := strings.TrimPrefix(path, prefix)
|
||||
target := strings.TrimRight(docsLink, "/") + subPath
|
||||
c.Redirect(http.StatusFound, target)
|
||||
c.Abort()
|
||||
})
|
||||
|
||||
frontendBaseUrl := os.Getenv("FRONTEND_BASE_URL")
|
||||
if common.IsMasterNode && frontendBaseUrl != "" {
|
||||
frontendBaseUrl = ""
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ const InviteRedirect = lazy(() => import('./pages/InviteRedirect'));
|
|||
const About = lazy(() => import('./pages/About'));
|
||||
const UserAgreement = lazy(() => import('./pages/UserAgreement'));
|
||||
const PrivacyPolicy = lazy(() => import('./pages/PrivacyPolicy'));
|
||||
const Docs = lazy(() => import('./pages/Docs'));
|
||||
|
||||
function DynamicOAuth2Callback() {
|
||||
const { provider } = useParams();
|
||||
|
|
@ -465,6 +466,14 @@ function App() {
|
|||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/docs'
|
||||
element={
|
||||
<Suspense fallback={<Loading></Loading>} key={location.pathname}>
|
||||
<Docs />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/user-agreement'
|
||||
element={
|
||||
|
|
|
|||
|
|
@ -48,15 +48,13 @@ export function mapWebLanguageToDocsLocale(webLanguage) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {string} serverDocsLink - operation_setting.general docs_link; empty uses same-origin docs
|
||||
* @param {string} serverDocsLink - operation_setting.general docs_link; empty uses in-app /docs route
|
||||
* @param {string} webLanguage - i18n.language
|
||||
* @returns {{ href: string, openInNewTab: boolean }}
|
||||
*/
|
||||
export function resolveDocsNav(serverDocsLink, webLanguage) {
|
||||
const trimmed = (serverDocsLink || '').trim();
|
||||
const origin = typeof window !== 'undefined' ? window.location.origin : '';
|
||||
const docsLocale = mapWebLanguageToDocsLocale(webLanguage);
|
||||
const path = `/${docsLocale}/docs`;
|
||||
|
||||
if (trimmed) {
|
||||
let openInNewTab = true;
|
||||
|
|
@ -72,6 +70,6 @@ export function resolveDocsNav(serverDocsLink, webLanguage) {
|
|||
return { href: trimmed, openInNewTab };
|
||||
}
|
||||
|
||||
const href = origin ? `${origin}${path}` : path;
|
||||
return { href, openInNewTab: false };
|
||||
// No external docs link configured → use in-app /docs route
|
||||
return { href: '/docs', openInNewTab: false };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,13 +45,17 @@ export const useNavigation = (t, docsNav, headerNavModules) => {
|
|||
},
|
||||
...(docsNav?.href
|
||||
? [
|
||||
{
|
||||
(() => {
|
||||
const isExternal = /^https?:\/\//i.test(docsNav.href);
|
||||
return {
|
||||
text: t('文档'),
|
||||
itemKey: 'docs',
|
||||
isExternal: true,
|
||||
externalLink: docsNav.href,
|
||||
isExternal,
|
||||
externalLink: isExternal ? docsNav.href : undefined,
|
||||
to: isExternal ? undefined : docsNav.href,
|
||||
openInNewTab: docsNav.openInNewTab,
|
||||
},
|
||||
};
|
||||
})(),
|
||||
]
|
||||
: []),
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5680,6 +5680,16 @@
|
|||
"兼容 OpenAI / Claude / Gemini 等协议,覆盖文本、图像、视频、语音,智能路由,统一计费。": "Compatible with OpenAI, Claude, Gemini, and more. Covers text, image, video, and voice with intelligent routing and unified billing.",
|
||||
"开始使用": "Get Started",
|
||||
"查看价格": "View Pricing",
|
||||
"页面正在建设中...": "Page under construction..."
|
||||
"页面正在建设中...": "Page under construction...",
|
||||
"概述": "Overview",
|
||||
"注册账号": "Register",
|
||||
"创建 API 密钥": "Create API Key",
|
||||
"发送请求": "Send Request",
|
||||
"对话补全": "Chat Completions",
|
||||
"图片生成": "Image Generation",
|
||||
"语音 & 音频": "Audio & Speech",
|
||||
"支持模型": "Supported Models",
|
||||
"速率限制": "Rate Limits",
|
||||
"常见问题": "FAQ"
|
||||
}
|
||||
}
|
||||
|
|
@ -5539,6 +5539,16 @@
|
|||
"兼容 OpenAI / Claude / Gemini 等协议,覆盖文本、图像、视频、语音,智能路由,统一计费。": "兼容 OpenAI / Claude / Gemini 等协议,覆盖文本、图像、视频、语音,智能路由,统一计费。",
|
||||
"开始使用": "开始使用",
|
||||
"查看价格": "查看价格",
|
||||
"页面正在建设中...": "页面正在建设中..."
|
||||
"页面正在建设中...": "页面正在建设中...",
|
||||
"概述": "概述",
|
||||
"注册账号": "注册账号",
|
||||
"创建 API 密钥": "创建 API 密钥",
|
||||
"发送请求": "发送请求",
|
||||
"对话补全": "对话补全",
|
||||
"图片生成": "图片生成",
|
||||
"语音 & 音频": "语音 & 音频",
|
||||
"支持模型": "支持模型",
|
||||
"速率限制": "速率限制",
|
||||
"常见问题": "常见问题"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,573 @@
|
|||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
BookOpen,
|
||||
Zap,
|
||||
Key,
|
||||
Terminal,
|
||||
Box,
|
||||
Gauge,
|
||||
HelpCircle,
|
||||
ChevronRight,
|
||||
Copy,
|
||||
Check,
|
||||
ExternalLink,
|
||||
ArrowRight,
|
||||
Code,
|
||||
Server,
|
||||
Globe,
|
||||
Shield,
|
||||
Clock,
|
||||
Sparkles,
|
||||
Layers,
|
||||
Menu,
|
||||
X,
|
||||
Rocket,
|
||||
MessageSquare,
|
||||
Image,
|
||||
Music,
|
||||
Video,
|
||||
FileText,
|
||||
} from 'lucide-react';
|
||||
|
||||
/* ─── DOCS SECTIONS ─── */
|
||||
const DOC_SECTIONS = [
|
||||
{ id: 'overview', icon: BookOpen, label: '概述', labelEn: 'Overview' },
|
||||
{ id: 'quickstart', icon: Rocket, label: '快速开始', labelEn: 'Quick Start' },
|
||||
{ id: 'apikey', icon: Key, label: 'API 密钥', labelEn: 'API Keys' },
|
||||
{ id: 'chat', icon: MessageSquare, label: '对话补全', labelEn: 'Chat Completions' },
|
||||
{ id: 'image', icon: Image, label: '图片生成', labelEn: 'Image Generation' },
|
||||
{ id: 'audio', icon: Music, label: '语音 & 音频', labelEn: 'Audio & Speech' },
|
||||
{ id: 'video', icon: Video, label: '视频生成', labelEn: 'Video Generation' },
|
||||
{ id: 'models', icon: Box, label: '支持模型', labelEn: 'Supported Models' },
|
||||
{ id: 'ratelimit', icon: Gauge, label: '速率限制', labelEn: 'Rate Limits' },
|
||||
{ id: 'faq', icon: HelpCircle, label: '常见问题', labelEn: 'FAQ' },
|
||||
];
|
||||
|
||||
/* ─── CodeBlock Component ─── */
|
||||
function CodeBlock({ code, lang = 'bash' }) {
|
||||
const { t } = useTranslation();
|
||||
const [copied, setCopied] = useState(false);
|
||||
const copy = () => {
|
||||
navigator.clipboard.writeText(code);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
};
|
||||
return (
|
||||
<div className="relative group my-4 rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
|
||||
<div className="flex items-center justify-between px-4 py-2 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||
<span className="text-xs font-mono text-gray-500 dark:text-gray-400 uppercase">{lang}</span>
|
||||
<button
|
||||
onClick={copy}
|
||||
className="flex items-center gap-1 text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
|
||||
>
|
||||
{copied ? <Check size={14} /> : <Copy size={14} />}
|
||||
{copied ? t('已复制') : t('复制')}
|
||||
</button>
|
||||
</div>
|
||||
<pre className="p-4 overflow-x-auto text-sm font-mono text-gray-800 dark:text-gray-200 leading-relaxed">
|
||||
<code>{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── SectionTitle ─── */
|
||||
function SectionTitle({ id, icon: Icon, children }) {
|
||||
return (
|
||||
<h2 id={id} className="group flex items-center gap-3 mt-12 mb-5 scroll-mt-24">
|
||||
<span className="flex items-center justify-center w-9 h-9 rounded-lg bg-violet-100 dark:bg-violet-900/30 text-violet-600 dark:text-violet-400">
|
||||
<Icon size={18} />
|
||||
</span>
|
||||
<span className="text-2xl font-bold text-gray-900 dark:text-white">{children}</span>
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── SubSection ─── */
|
||||
function SubSection({ children, className = '' }) {
|
||||
return <h3 className={`text-lg font-semibold text-gray-800 dark:text-gray-200 mt-8 mb-3 ${className}`}>{children}</h3>;
|
||||
}
|
||||
|
||||
/* ─── Paragraph ─── */
|
||||
function P({ children, className = '' }) {
|
||||
return <p className={`text-gray-600 dark:text-gray-400 leading-relaxed mb-4 ${className}`}>{children}</p>;
|
||||
}
|
||||
|
||||
/* ─── Table Component ─── */
|
||||
function DocsTable({ headers, rows }) {
|
||||
return (
|
||||
<div className="overflow-x-auto my-4 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="bg-gray-50 dark:bg-gray-800/50">
|
||||
{headers.map((h, i) => (
|
||||
<th key={i} className="px-4 py-3 text-left font-semibold text-gray-700 dark:text-gray-300 border-b border-gray-200 dark:border-gray-700">
|
||||
{h}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, i) => (
|
||||
<tr key={i} className="border-b border-gray-100 dark:border-gray-800 last:border-0 hover:bg-gray-50 dark:hover:bg-gray-800/30 transition-colors">
|
||||
{row.map((cell, j) => (
|
||||
<td key={j} className="px-4 py-3 text-gray-600 dark:text-gray-400">{cell}</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── Info Card ─── */
|
||||
function InfoCard({ icon: Icon, title, children, className = '' }) {
|
||||
return (
|
||||
<div className={`flex gap-4 p-5 rounded-xl border border-violet-200 dark:border-violet-800 bg-violet-50 dark:bg-violet-900/20 ${className}`}>
|
||||
<span className="flex-shrink-0 mt-0.5 text-violet-500 dark:text-violet-400"><Icon size={20} /></span>
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-violet-700 dark:text-violet-300 mb-1">{title}</h4>
|
||||
<div className="text-sm text-violet-600 dark:text-violet-400">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── MAIN DOCS PAGE ─── */
|
||||
export default function Docs() {
|
||||
const { t } = useTranslation();
|
||||
const [activeSection, setActiveSection] = useState('overview');
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const contentRef = useRef(null);
|
||||
|
||||
// Scroll spy
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
setActiveSection(entry.target.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ rootMargin: '-80px 0px -70% 0px', threshold: 0 }
|
||||
);
|
||||
|
||||
const ids = DOC_SECTIONS.map((s) => s.id);
|
||||
ids.forEach((id) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) observer.observe(el);
|
||||
});
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const scrollTo = (id) => {
|
||||
setSidebarOpen(false);
|
||||
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
const BASE_HOST = window.location.origin;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white dark:bg-gray-950">
|
||||
{/* ── Top Nav Bar ── */}
|
||||
<header className="sticky top-0 z-40 h-14 border-b border-gray-200 dark:border-gray-800 bg-white/80 dark:bg-gray-950/80 backdrop-blur-md">
|
||||
<div className="flex items-center h-full px-4 lg:px-6 max-w-[1400px] mx-auto gap-3">
|
||||
<button
|
||||
className="lg:hidden p-2 -ml-2 rounded-md text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
>
|
||||
{sidebarOpen ? <X size={20} /> : <Menu size={20} />}
|
||||
</button>
|
||||
<a href="/" className="flex items-center gap-2 flex-shrink-0">
|
||||
<img src="/logo.jpg" alt="logo" className="w-7 h-7 rounded-full object-cover" />
|
||||
<span className="font-heading text-lg font-bold tracking-tight text-gray-900 dark:text-white">TuringToken</span>
|
||||
</a>
|
||||
<span className="hidden sm:inline text-sm text-gray-400 dark:text-gray-500">|</span>
|
||||
<span className="text-sm font-semibold text-gray-700 dark:text-gray-300">{t('文档')}</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="max-w-[1400px] mx-auto flex">
|
||||
{/* ── Sidebar (Desktop) ── */}
|
||||
<aside className="hidden lg:block w-[240px] flex-shrink-0 sticky top-14 h-[calc(100vh-3.5rem)] overflow-y-auto border-r border-gray-200 dark:border-gray-800 py-6 px-3">
|
||||
<nav className="space-y-1">
|
||||
{DOC_SECTIONS.map((s) => (
|
||||
<button
|
||||
key={s.id}
|
||||
onClick={() => scrollTo(s.id)}
|
||||
className={`w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm transition-all duration-150 text-left
|
||||
${activeSection === s.id
|
||||
? 'bg-violet-50 dark:bg-violet-900/20 text-violet-700 dark:text-violet-300 font-semibold'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800/50 hover:text-gray-900 dark:hover:text-white'
|
||||
}`}
|
||||
>
|
||||
<s.icon size={16} className={activeSection === s.id ? 'text-violet-500' : 'text-gray-400'} />
|
||||
{s.label}
|
||||
{activeSection === s.id && <ChevronRight size={14} className="ml-auto text-violet-400" />}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
{/* ── Mobile Sidebar Overlay ── */}
|
||||
{sidebarOpen && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-40 bg-black/40 lg:hidden" onClick={() => setSidebarOpen(false)} />
|
||||
<aside className="fixed top-14 left-0 z-50 w-[260px] h-[calc(100vh-3.5rem)] overflow-y-auto bg-white dark:bg-gray-950 border-r border-gray-200 dark:border-gray-800 py-6 px-3 lg:hidden">
|
||||
<nav className="space-y-1">
|
||||
{DOC_SECTIONS.map((s) => (
|
||||
<button
|
||||
key={s.id}
|
||||
onClick={() => scrollTo(s.id)}
|
||||
className={`w-full flex items-center gap-2.5 px-3 py-2.5 rounded-lg text-sm transition-all text-left
|
||||
${activeSection === s.id
|
||||
? 'bg-violet-50 dark:bg-violet-900/20 text-violet-700 dark:text-violet-300 font-semibold'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800/50'
|
||||
}`}
|
||||
>
|
||||
<s.icon size={16} className={activeSection === s.id ? 'text-violet-500' : 'text-gray-400'} />
|
||||
{s.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* ── Content ── */}
|
||||
<main ref={contentRef} className="flex-1 min-w-0 py-8 px-6 lg:px-10 xl:px-16 max-w-4xl">
|
||||
|
||||
{/* ── Section: 概述 ── */}
|
||||
<SectionTitle id="overview" icon={BookOpen}>{t('概述')}</SectionTitle>
|
||||
<P>
|
||||
TuringToken 是一个统一的 AI API 网关,聚合了
|
||||
<strong className="text-gray-900 dark:text-white"> 40+ 主流 AI 提供商</strong>(OpenAI、Claude、Gemini、DeepSeek、Qwen 等),
|
||||
提供与 OpenAI API 完全兼容的接口格式。一次接入,即可访问所有模型,无需分别对接各个厂商。
|
||||
</P>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-4 mt-6 mb-8">
|
||||
{[
|
||||
{ icon: Server, title: '统一接口', desc: '与 OpenAI API 格式完全兼容,零改造迁移' },
|
||||
{ icon: Globe, title: '多供应商聚合', desc: '一站式接入 40+ AI 厂商,按需切换' },
|
||||
{ icon: Shield, title: '安全可靠', desc: 'API 密钥管理、速率限制、访问控制' },
|
||||
{ icon: Clock, title: '按量计费', desc: '按 Token 用量计费,灵活成本控制' },
|
||||
].map((f, i) => (
|
||||
<div key={i} className="flex gap-3 p-4 rounded-xl border border-gray-100 dark:border-gray-800 bg-gray-50 dark:bg-gray-900/50">
|
||||
<span className="flex-shrink-0 text-violet-500 dark:text-violet-400"><f.icon size={20} /></span>
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-gray-900 dark:text-white mb-1">{f.title}</h4>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">{f.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* ── Section: 快速开始 ── */}
|
||||
<SectionTitle id="quickstart" icon={Rocket}>{t('快速开始')}</SectionTitle>
|
||||
<P>只需三步,即可接入 AI 能力:</P>
|
||||
|
||||
<div className="space-y-6 mt-6">
|
||||
<div className="flex gap-4">
|
||||
<span className="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-violet-500 text-white text-sm font-bold">1</span>
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-gray-900 dark:text-white mb-1">{t('注册账号')}</h4>
|
||||
<P className="!mb-0">访问 <a href="/register" className="text-violet-600 dark:text-violet-400 hover:underline font-medium">注册页面</a> 创建 TuringToken 账号。</P>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<span className="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-violet-500 text-white text-sm font-bold">2</span>
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-gray-900 dark:text-white mb-1">{t('创建 API 密钥')}</h4>
|
||||
<P className="!mb-0">进入 <a href="/console/token" className="text-violet-600 dark:text-violet-400 hover:underline font-medium">控制台 → API Key</a>,点击"新建"生成密钥。</P>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<span className="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-violet-500 text-white text-sm font-bold">3</span>
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-gray-900 dark:text-white mb-1">{t('发送请求')}</h4>
|
||||
<P className="!mb-0">使用下方示例代码,将 Base URL 替换为 TuringToken 地址即可。</P>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InfoCard icon={Zap} title="Base URL" className="mt-6">
|
||||
<code className="font-mono text-sm bg-violet-100 dark:bg-violet-900/40 px-2 py-0.5 rounded">{BASE_HOST}/v1</code>
|
||||
</InfoCard>
|
||||
|
||||
{/* ── Section: API 密钥 ── */}
|
||||
<SectionTitle id="apikey" icon={Key}>{t('API 密钥')}</SectionTitle>
|
||||
<P>
|
||||
API 密钥是与 TuringToken 通信的凭证。每个密钥可以设置额度上限、有效期和 IP 白名单。
|
||||
</P>
|
||||
|
||||
<SubSection>创建密钥</SubSection>
|
||||
<P>
|
||||
登录后进入 <a href="/console/token" className="text-violet-600 dark:text-violet-400 hover:underline font-medium">控制台 → API Key</a>,
|
||||
点击"新建 API Key",填写名称后即可生成。请务必妥善保存密钥,关闭页面后将无法再次查看完整密钥。
|
||||
</P>
|
||||
|
||||
<SubSection>使用密钥</SubSection>
|
||||
<P>在所有 API 请求中,通过 Bearer Token 方式传递密钥:</P>
|
||||
<CodeBlock
|
||||
lang="http"
|
||||
code={`Authorization: Bearer sk-your-api-key-here`}
|
||||
/>
|
||||
|
||||
<InfoCard icon={Shield} title="安全提醒">
|
||||
请勿将 API 密钥硬编码在前端代码或公开仓库中。建议使用环境变量或后端代理方式管理密钥。
|
||||
</InfoCard>
|
||||
|
||||
{/* ── Section: 对话补全 ── */}
|
||||
<SectionTitle id="chat" icon={MessageSquare}>{t('对话补全')}</SectionTitle>
|
||||
<P>
|
||||
对话补全是最常用的 API 接口,支持多轮对话、流式输出和 Function Calling。
|
||||
兼容 OpenAI Chat Completions API 格式。
|
||||
</P>
|
||||
|
||||
<SubSection>基础请求</SubSection>
|
||||
<CodeBlock
|
||||
lang="bash"
|
||||
code={`curl ${BASE_HOST}/v1/chat/completions \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-H "Authorization: Bearer sk-your-api-key" \\
|
||||
-d '{
|
||||
"model": "gpt-4o",
|
||||
"messages": [
|
||||
{"role": "system", "content": "你是一个有用的AI助手。"},
|
||||
{"role": "user", "content": "你好,请介绍一下你自己。"}
|
||||
]
|
||||
}'`}
|
||||
/>
|
||||
|
||||
<SubSection>流式输出</SubSection>
|
||||
<P>设置 <code className="font-mono text-sm bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded text-violet-600 dark:text-violet-400">stream: true</code> 即可启用 SSE 流式响应:</P>
|
||||
<CodeBlock
|
||||
lang="bash"
|
||||
code={`curl ${BASE_HOST}/v1/chat/completions \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-H "Authorization: Bearer sk-your-api-key" \\
|
||||
-d '{
|
||||
"model": "gpt-4o",
|
||||
"messages": [{"role": "user", "content": "写一首关于春天的诗"}],
|
||||
"stream": true
|
||||
}'`}
|
||||
/>
|
||||
|
||||
<SubSection>Python SDK 示例</SubSection>
|
||||
<CodeBlock
|
||||
lang="python"
|
||||
code={`from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
base_url="${BASE_HOST}/v1",
|
||||
api_key="sk-your-api-key",
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4o",
|
||||
messages=[
|
||||
{"role": "user", "content": "你好,请介绍一下你自己。"}
|
||||
],
|
||||
)
|
||||
|
||||
print(response.choices[0].message.content)`}
|
||||
/>
|
||||
|
||||
<SubSection>Node.js SDK 示例</SubSection>
|
||||
<CodeBlock
|
||||
lang="javascript"
|
||||
code={`import OpenAI from 'openai';
|
||||
|
||||
const client = new OpenAI({
|
||||
baseURL: '${BASE_HOST}/v1',
|
||||
apiKey: 'sk-your-api-key',
|
||||
});
|
||||
|
||||
const completion = await client.chat.completions.create({
|
||||
model: 'gpt-4o',
|
||||
messages: [
|
||||
{ role: 'user', content: '你好,请介绍一下你自己。' }
|
||||
],
|
||||
});
|
||||
|
||||
console.log(completion.choices[0].message.content);`}
|
||||
/>
|
||||
|
||||
{/* ── Section: 图片生成 ── */}
|
||||
<SectionTitle id="image" icon={Image}>{t('图片生成')}</SectionTitle>
|
||||
<P>
|
||||
支持 DALL·E、Stable Diffusion 等多种图片生成模型,兼容 OpenAI Images API 格式。
|
||||
</P>
|
||||
|
||||
<SubSection>生成图片</SubSection>
|
||||
<CodeBlock
|
||||
lang="bash"
|
||||
code={`curl ${BASE_HOST}/v1/images/generations \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-H "Authorization: Bearer sk-your-api-key" \\
|
||||
-d '{
|
||||
"model": "dall-e-3",
|
||||
"prompt": "一只正在敲代码的可爱猫咪,赛博朋克风格",
|
||||
"n": 1,
|
||||
"size": "1024x1024"
|
||||
}'`}
|
||||
/>
|
||||
|
||||
{/* ── Section: 语音 & 音频 ── */}
|
||||
<SectionTitle id="audio" icon={Music}>{t('语音 & 音频')}</SectionTitle>
|
||||
<P>支持语音转文字(STT)和文字转语音(TTS),兼容 OpenAI Audio API 格式。</P>
|
||||
|
||||
<SubSection>语音转文字 (STT)</SubSection>
|
||||
<CodeBlock
|
||||
lang="bash"
|
||||
code={`curl ${BASE_HOST}/v1/audio/transcriptions \\
|
||||
-H "Authorization: Bearer sk-your-api-key" \\
|
||||
-F "file=@audio.mp3" \\
|
||||
-F "model=whisper-1"`}
|
||||
/>
|
||||
|
||||
<SubSection>文字转语音 (TTS)</SubSection>
|
||||
<CodeBlock
|
||||
lang="bash"
|
||||
code={`curl ${BASE_HOST}/v1/audio/speech \\
|
||||
-H "Authorization: Bearer sk-your-api-key" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"model": "tts-1",
|
||||
"input": "你好,欢迎使用 TuringToken!",
|
||||
"voice": "alloy"
|
||||
}' \\
|
||||
--output speech.mp3`}
|
||||
/>
|
||||
|
||||
{/* ── Section: 视频生成 ── */}
|
||||
<SectionTitle id="video" icon={Video}>{t('视频生成')}</SectionTitle>
|
||||
<P>
|
||||
TuringToken 集成了多家视频生成厂商(可灵、即梦、Vidu、Sora 等),
|
||||
支持文生视频和图生视频能力。
|
||||
</P>
|
||||
<P>
|
||||
视频生成接口因厂商而异,详细参数请参考 <a href="/pricing" className="text-violet-600 dark:text-violet-400 hover:underline font-medium">模型列表</a> 中各视频模型的说明,
|
||||
或访问 <a href="/console" className="text-violet-600 dark:text-violet-400 hover:underline font-medium">控制台</a> 在线体验。
|
||||
</P>
|
||||
|
||||
{/* ── Section: 支持模型 ── */}
|
||||
<SectionTitle id="models" icon={Box}>{t('支持模型')}</SectionTitle>
|
||||
<P>TuringToken 聚合了以下主流 AI 模型的 API 访问:</P>
|
||||
|
||||
<DocsTable
|
||||
headers={['供应商', '代表模型', '能力']}
|
||||
rows={[
|
||||
['OpenAI', 'GPT-4o, GPT-4o-mini, o3, o4-mini', '对话、推理、视觉'],
|
||||
['Anthropic', 'Claude 3.5 Sonnet, Claude 3 Opus, Claude 4', '对话、长文本'],
|
||||
['Google', 'Gemini 2.5 Pro, Gemini 2.5 Flash', '对话、多模态'],
|
||||
['DeepSeek', 'DeepSeek-V3, DeepSeek-R1', '对话、推理'],
|
||||
['阿里巴巴', 'Qwen3, Qwen-Max, Qwen-Plus', '对话、视觉'],
|
||||
['智谱', 'GLM-4-Plus, GLM-4-Flash', '对话、工具调用'],
|
||||
['Moonshot', 'Kimi-K2, Moonshot-v1', '对话、长文本'],
|
||||
['字节跳动', 'Doubao-Pro, Seedance', '对话、视频'],
|
||||
['MiniMax', 'abab6.5s, MiniMax-M1', '对话'],
|
||||
['Mistral', 'Mistral Large, Mixtral 8x22B', '对话、开源'],
|
||||
['xAI', 'Grok-3, Grok-2', '对话'],
|
||||
['Cohere', 'Command R+, Command R', '对话、RAG'],
|
||||
['Meta', 'Llama 4, Llama 3.3 70B', '对话、开源'],
|
||||
['百度', 'ERNIE-4.0, ERNIE-Speed', '对话'],
|
||||
['讯飞', 'Spark 4.0 Ultra', '对话'],
|
||||
['腾讯', 'Hunyuan-Pro, Hunyuan-Turbo', '对话'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<P className="mt-4">
|
||||
查看 <a href="/pricing" className="text-violet-600 dark:text-violet-400 hover:underline font-medium">完整模型列表和定价 →</a>
|
||||
</P>
|
||||
|
||||
{/* ── Section: 速率限制 ── */}
|
||||
<SectionTitle id="ratelimit" icon={Gauge}>{t('速率限制')}</SectionTitle>
|
||||
<P>
|
||||
为确保服务稳定性,TuringToken 对不同用户类型设置了速率限制:
|
||||
</P>
|
||||
|
||||
<DocsTable
|
||||
headers={['用户类型', '请求频率', '并发连接', '每日限额']}
|
||||
rows={[
|
||||
['免费用户', '30 次/分钟', '5', '500 次/天'],
|
||||
['基础用户', '120 次/分钟', '10', '5000 次/天'],
|
||||
['专业用户', '600 次/分钟', '30', '50000 次/天'],
|
||||
['企业用户', '3000 次/分钟', '100', '无限制'],
|
||||
]}
|
||||
/>
|
||||
|
||||
<InfoCard icon={Gauge} title="提示" className="mt-6">
|
||||
具体限制以控制台中实际配置为准。管理员可在后台自定义速率限制策略。超出限制时 API 将返回 HTTP 429 状态码。
|
||||
</InfoCard>
|
||||
|
||||
{/* ── Section: 常见问题 ── */}
|
||||
<SectionTitle id="faq" icon={HelpCircle}>{t('常见问题')}</SectionTitle>
|
||||
|
||||
<div className="space-y-6 mt-6">
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-gray-900 dark:text-white mb-2">TuringToken 与直接使用 OpenAI API 有什么区别?</h4>
|
||||
<P>
|
||||
TuringToken 提供与 OpenAI 完全兼容的接口,但同时聚合了 40+ 其他 AI 厂商的模型。
|
||||
您无需分别对接每个厂商,一键切换模型,按量统一计费。
|
||||
</P>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-gray-900 dark:text-white mb-2">如何切换不同的 AI 模型?</h4>
|
||||
<P>
|
||||
只需在请求中修改 <code className="font-mono text-sm bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded text-violet-600 dark:text-violet-400">model</code> 参数即可,接口格式完全一致。
|
||||
例如将 <code className="font-mono text-sm bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded">gpt-4o</code> 替换为 <code className="font-mono text-sm bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded">claude-3-5-sonnet-20241022</code>。
|
||||
</P>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-gray-900 dark:text-white mb-2">支持流式响应 (Streaming) 吗?</h4>
|
||||
<P>
|
||||
支持。设置 <code className="font-mono text-sm bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded text-violet-600 dark:text-violet-400">stream: true</code> 即可启用 SSE 流式输出,所有模型均支持。
|
||||
</P>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-gray-900 dark:text-white mb-2">支持 Function Calling / Tool Use 吗?</h4>
|
||||
<P>
|
||||
支持。GPT-4o、Claude、Gemini、Qwen、DeepSeek 等模型均支持 Function Calling,可用于构建 Agent 和工具调用场景。
|
||||
</P>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-gray-900 dark:text-white mb-2">如何查看我的 API 使用量和消费明细?</h4>
|
||||
<P>
|
||||
登录后进入 <a href="/console" className="text-violet-600 dark:text-violet-400 hover:underline font-medium">控制台</a>,可在仪表盘查看实时用量统计和消费明细。
|
||||
</P>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-gray-900 dark:text-white mb-2">密钥泄露了怎么办?</h4>
|
||||
<P>
|
||||
立即前往 <a href="/console/token" className="text-violet-600 dark:text-violet-400 hover:underline font-medium">控制台 → API Key</a> 删除泄露的密钥,并创建新密钥。
|
||||
</P>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Footer ── */}
|
||||
<div className="mt-20 pt-8 border-t border-gray-200 dark:border-gray-800">
|
||||
<P className="text-center text-sm text-gray-400 !mb-0">
|
||||
更多问题?请联系我们的技术支持团队获取帮助。
|
||||
</P>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -162,15 +162,11 @@ function HeroSection() {
|
|||
{[
|
||||
{ key: "model", label: "模型", icon: <Box size={16} />, to: "/pricing" },
|
||||
{ key: "benchmark", label: "评测", icon: <Trophy size={16} />, to: "/benchmarks" },
|
||||
{ key: "docs", label: "文档", icon: <BookOpen size={16} />, to: "/docs", isDocs: true },
|
||||
{ key: "docs", label: "文档", icon: <BookOpen size={16} />, to: "/docs" },
|
||||
{ key: "console", label: "控制台", icon: <Grid3X3 size={16} />, to: "/console" },
|
||||
].map((item) => {
|
||||
const linkClass = "inline-flex items-center gap-1.5 px-3 py-1.5 text-[14px] rounded-md transition-colors duration-150 text-white/60 hover:text-white";
|
||||
if (item.isDocs) {
|
||||
return <a key={item.key} href={item.to} className={linkClass}>{item.icon}<span className="hidden sm:inline">{t(item.label)}</span></a>;
|
||||
}
|
||||
return <Link key={item.key} to={item.to} className={linkClass}>{item.icon}<span className="hidden sm:inline">{t(item.label)}</span></Link>;
|
||||
})}
|
||||
].map((item) => (
|
||||
<Link key={item.key} to={item.to} className="inline-flex items-center gap-1.5 px-3 py-1.5 text-[14px] rounded-md transition-colors duration-150 text-white/60 hover:text-white">{item.icon}<span className="hidden sm:inline">{t(item.label)}</span></Link>
|
||||
))}
|
||||
</nav>
|
||||
<div className="flex items-center gap-1">
|
||||
<HomeThemeToggle />
|
||||
|
|
|
|||
Loading…
Reference in New Issue