feat: restructure docs page to left-center-right 3-column layout with page TOC sidebar
This commit is contained in:
parent
e028f6f93a
commit
0ab319143b
|
|
@ -5690,6 +5690,7 @@
|
||||||
"语音 & 音频": "Audio & Speech",
|
"语音 & 音频": "Audio & Speech",
|
||||||
"支持模型": "Supported Models",
|
"支持模型": "Supported Models",
|
||||||
"速率限制": "Rate Limits",
|
"速率限制": "Rate Limits",
|
||||||
"常见问题": "FAQ"
|
"常见问题": "FAQ",
|
||||||
|
"本页目录": "On this page"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5549,6 +5549,7 @@
|
||||||
"语音 & 音频": "语音 & 音频",
|
"语音 & 音频": "语音 & 音频",
|
||||||
"支持模型": "支持模型",
|
"支持模型": "支持模型",
|
||||||
"速率限制": "速率限制",
|
"速率限制": "速率限制",
|
||||||
"常见问题": "常见问题"
|
"常见问题": "常见问题",
|
||||||
|
"本页目录": "本页目录"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue