diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 1420319..df2217d 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -5690,6 +5690,7 @@ "语音 & 音频": "Audio & Speech", "支持模型": "Supported Models", "速率限制": "Rate Limits", - "常见问题": "FAQ" + "常见问题": "FAQ", + "本页目录": "On this page" } } \ No newline at end of file diff --git a/web/src/i18n/locales/zh-CN.json b/web/src/i18n/locales/zh-CN.json index 582b24f..0250766 100644 --- a/web/src/i18n/locales/zh-CN.json +++ b/web/src/i18n/locales/zh-CN.json @@ -5549,6 +5549,7 @@ "语音 & 音频": "语音 & 音频", "支持模型": "支持模型", "速率限制": "速率限制", - "常见问题": "常见问题" + "常见问题": "常见问题", + "本页目录": "本页目录" } } \ No newline at end of file diff --git a/web/src/pages/Docs/index.jsx b/web/src/pages/Docs/index.jsx index 83c9b52..bb2a2a7 100644 --- a/web/src/pages/Docs/index.jsx +++ b/web/src/pages/Docs/index.jsx @@ -38,6 +38,7 @@ import { Music, Video, FileText, + List, } from 'lucide-react'; /* ─── DOCS SECTIONS ─── */ @@ -95,8 +96,8 @@ function SectionTitle({ id, icon: Icon, children }) { } /* ─── SubSection ─── */ -function SubSection({ children, className = '' }) { - return

{children}

; +function SubSection({ id, children, className = '' }) { + return

{children}

; } /* ─── 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 ( + + ); +} + /* ─── MAIN DOCS PAGE ─── */ export default function Docs() { const { t } = useTranslation(); @@ -256,7 +341,7 @@ export default function Docs() { )} {/* ── Content ── */} -
+
{/* ── Section: 概述 ── */} {t('概述')} @@ -321,13 +406,13 @@ export default function Docs() { API 密钥是与 TuringToken 通信的凭证。每个密钥可以设置额度上限、有效期和 IP 白名单。

- 创建密钥 + 创建密钥

登录后进入 控制台 → API Key, 点击"新建 API Key",填写名称后即可生成。请务必妥善保存密钥,关闭页面后将无法再次查看完整密钥。

- 使用密钥 + 使用密钥

在所有 API 请求中,通过 Bearer Token 方式传递密钥:

- 基础请求 + 基础请求 - 流式输出 + 流式输出

设置 stream: true 即可启用 SSE 流式响应:

- Node.js SDK 示例 + Node.js SDK 示例 {t('语音 & 音频')}

支持语音转文字(STT)和文字转语音(TTS),兼容 OpenAI Audio API 格式。

- 语音转文字 (STT) + 语音转文字 (STT) - 文字转语音 (TTS) + 文字转语音 (TTS)
+ + {/* ── Right Page TOC ── */} + );