feat: restructure docs page to left-center-right 3-column layout with page TOC sidebar

This commit is contained in:
xiezhouwei 2026-06-22 10:08:09 +08:00
parent e028f6f93a
commit 0ab319143b
3 changed files with 102 additions and 12 deletions

View File

@ -5690,6 +5690,7 @@
"语音 & 音频": "Audio & Speech", "语音 & 音频": "Audio & Speech",
"支持模型": "Supported Models", "支持模型": "Supported Models",
"速率限制": "Rate Limits", "速率限制": "Rate Limits",
"常见问题": "FAQ" "常见问题": "FAQ",
"本页目录": "On this page"
} }
} }

View File

@ -5549,6 +5549,7 @@
"语音 & 音频": "语音 & 音频", "语音 & 音频": "语音 & 音频",
"支持模型": "支持模型", "支持模型": "支持模型",
"速率限制": "速率限制", "速率限制": "速率限制",
"常见问题": "常见问题" "常见问题": "常见问题",
"本页目录": "本页目录"
} }
} }

View File

@ -38,6 +38,7 @@ import {
Music, Music,
Video, Video,
FileText, FileText,
List,
} from 'lucide-react'; } from 'lucide-react';
/* ─── DOCS SECTIONS ─── */ /* ─── DOCS SECTIONS ─── */
@ -95,8 +96,8 @@ function SectionTitle({ id, icon: Icon, children }) {
} }
/* ─── SubSection ─── */ /* ─── SubSection ─── */
function SubSection({ children, className = '' }) { function SubSection({ id, children, className = '' }) {
return <h3 className={`text-lg font-semibold text-gray-800 dark:text-gray-200 mt-8 mb-3 ${className}`}>{children}</h3>; return <h3 id={id} className={`text-lg font-semibold text-gray-800 dark:text-gray-200 mt-8 mb-3 scroll-mt-[72px] ${className}`}>{children}</h3>;
} }
/* ─── Paragraph ─── */ /* ─── Paragraph ─── */
@ -145,6 +146,90 @@ function InfoCard({ icon: Icon, title, children, className = '' }) {
); );
} }
/* ─── Right Page TOC ─── */
function RightToc({ contentRef, HEADER_OFFSET, activeSection }) {
const { t } = useTranslation();
const [tocItems, setTocItems] = useState([]);
const [activeTocId, setActiveTocId] = useState('');
// Build TOC from all h2/h3 headings in the content area
useEffect(() => {
if (!contentRef.current) return;
const timer = setTimeout(() => {
const headings = contentRef.current.querySelectorAll('h2[id], h3[id]');
const items = [];
headings.forEach((h) => {
items.push({
id: h.id,
text: h.textContent.trim(),
level: h.tagName === 'H2' ? 2 : 3,
});
});
setTocItems(items);
}, 100);
return () => clearTimeout(timer);
}, [contentRef, activeSection]);
// TOC scroll-spy
useEffect(() => {
if (tocItems.length === 0) return;
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
setActiveTocId(entry.target.id);
break;
}
}
},
{ rootMargin: `-${HEADER_OFFSET}px 0px -80% 0px`, threshold: 0 }
);
tocItems.forEach((item) => {
const el = document.getElementById(item.id);
if (el) observer.observe(el);
});
return () => observer.disconnect();
}, [tocItems, HEADER_OFFSET]);
const scrollToHeading = (id) => {
const el = document.getElementById(id);
if (!el) return;
const top = el.getBoundingClientRect().top + window.scrollY - HEADER_OFFSET;
window.scrollTo({ top, behavior: 'smooth' });
};
if (tocItems.length === 0) return null;
return (
<aside className="hidden xl:block w-[200px] flex-shrink-0">
<div className="sticky top-[72px] max-h-[calc(100vh-5rem)] overflow-y-auto py-8">
<h4 className="flex items-center gap-2 text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider mb-3 px-1">
<List size={13} />
{t('本页目录')}
</h4>
<nav className="space-y-0.5 border-l border-gray-200 dark:border-gray-700">
{tocItems.map((item) => (
<button
key={item.id}
onClick={() => scrollToHeading(item.id)}
className={`block w-full text-left text-sm py-1.5 truncate transition-all duration-150
${item.level === 3 ? 'pl-5' : 'pl-3'}
${activeTocId === item.id
? 'text-violet-600 dark:text-violet-400 font-medium border-l-2 -ml-px border-violet-500'
: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 border-l-2 -ml-px border-transparent'
}`}
>
{item.text}
</button>
))}
</nav>
</div>
</aside>
);
}
/* ─── MAIN DOCS PAGE ─── */ /* ─── MAIN DOCS PAGE ─── */
export default function Docs() { export default function Docs() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -256,7 +341,7 @@ export default function Docs() {
)} )}
{/* ── Content ── */} {/* ── Content ── */}
<main ref={contentRef} className="flex-1 min-w-0 py-8 px-6 lg:px-10 xl:px-16 max-w-4xl"> <main ref={contentRef} className="flex-1 min-w-0 py-8 px-6 lg:px-10 xl:px-16 max-w-3xl">
{/* ── Section: 概述 ── */} {/* ── Section: 概述 ── */}
<SectionTitle id="overview" icon={BookOpen}>{t('概述')}</SectionTitle> <SectionTitle id="overview" icon={BookOpen}>{t('概述')}</SectionTitle>
@ -321,13 +406,13 @@ export default function Docs() {
API 密钥是与 TuringToken 通信的凭证每个密钥可以设置额度上限有效期和 IP 白名单 API 密钥是与 TuringToken 通信的凭证每个密钥可以设置额度上限有效期和 IP 白名单
</P> </P>
<SubSection>创建密钥</SubSection> <SubSection id="apikey-create">创建密钥</SubSection>
<P> <P>
登录后进入 <a href="/console/token" className="text-violet-600 dark:text-violet-400 hover:underline font-medium">控制台 API Key</a> 登录后进入 <a href="/console/token" className="text-violet-600 dark:text-violet-400 hover:underline font-medium">控制台 API Key</a>
点击"新建 API Key"填写名称后即可生成请务必妥善保存密钥关闭页面后将无法再次查看完整密钥 点击"新建 API Key"填写名称后即可生成请务必妥善保存密钥关闭页面后将无法再次查看完整密钥
</P> </P>
<SubSection>使用密钥</SubSection> <SubSection id="apikey-usage">使用密钥</SubSection>
<P>在所有 API 请求中通过 Bearer Token 方式传递密钥</P> <P>在所有 API 请求中通过 Bearer Token 方式传递密钥</P>
<CodeBlock <CodeBlock
lang="http" lang="http"
@ -345,7 +430,7 @@ export default function Docs() {
兼容 OpenAI Chat Completions API 格式 兼容 OpenAI Chat Completions API 格式
</P> </P>
<SubSection>基础请求</SubSection> <SubSection id="chat-basic">基础请求</SubSection>
<CodeBlock <CodeBlock
lang="bash" lang="bash"
code={`curl ${BASE_HOST}/v1/chat/completions \\ code={`curl ${BASE_HOST}/v1/chat/completions \\
@ -360,7 +445,7 @@ export default function Docs() {
}'`} }'`}
/> />
<SubSection>流式输出</SubSection> <SubSection id="chat-stream">流式输出</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> <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 <CodeBlock
lang="bash" lang="bash"
@ -394,7 +479,7 @@ response = client.chat.completions.create(
print(response.choices[0].message.content)`} print(response.choices[0].message.content)`}
/> />
<SubSection>Node.js SDK 示例</SubSection> <SubSection id="chat-nodejs">Node.js SDK 示例</SubSection>
<CodeBlock <CodeBlock
lang="javascript" lang="javascript"
code={`import OpenAI from 'openai'; code={`import OpenAI from 'openai';
@ -438,7 +523,7 @@ console.log(completion.choices[0].message.content);`}
<SectionTitle id="audio" icon={Music}>{t('语音 & 音频')}</SectionTitle> <SectionTitle id="audio" icon={Music}>{t('语音 & 音频')}</SectionTitle>
<P>支持语音转文字STT和文字转语音TTS兼容 OpenAI Audio API 格式</P> <P>支持语音转文字STT和文字转语音TTS兼容 OpenAI Audio API 格式</P>
<SubSection>语音转文字 (STT)</SubSection> <SubSection id="audio-stt">语音转文字 (STT)</SubSection>
<CodeBlock <CodeBlock
lang="bash" lang="bash"
code={`curl ${BASE_HOST}/v1/audio/transcriptions \\ code={`curl ${BASE_HOST}/v1/audio/transcriptions \\
@ -447,7 +532,7 @@ console.log(completion.choices[0].message.content);`}
-F "model=whisper-1"`} -F "model=whisper-1"`}
/> />
<SubSection>文字转语音 (TTS)</SubSection> <SubSection id="audio-tts">文字转语音 (TTS)</SubSection>
<CodeBlock <CodeBlock
lang="bash" lang="bash"
code={`curl ${BASE_HOST}/v1/audio/speech \\ code={`curl ${BASE_HOST}/v1/audio/speech \\
@ -573,6 +658,9 @@ console.log(completion.choices[0].message.content);`}
</P> </P>
</div> </div>
</main> </main>
{/* ── Right Page TOC ── */}
<RightToc contentRef={contentRef} HEADER_OFFSET={HEADER_OFFSET} activeSection={activeSection} />
</div> </div>
</div> </div>
); );