docs: rebuild the learning, model and serving documentation system

Signed-off-by: zhifu gao <[email protected]>
This commit is contained in:
zhifu gao
2026-09-06 14:11:58 +00:00
parent 007668f6e1
commit f958aab5b8
51 changed files with 4126 additions and 1708 deletions
+104 -34
View File
@@ -6,6 +6,7 @@ Output: gh-pages-output/api.html for deployment to gh-pages.
This script is designed to be run by GitHub Actions on every push to main,
automatically regenerating the API docs.
"""
import argparse
import ast
import glob
import html as htmlmod
@@ -17,7 +18,10 @@ from pathlib import Path
REPO_URL = "https://github.com/modelscope/FunASR"
BRANCH = "main"
REPO_ROOT = Path(__file__).resolve().parents[1]
OUTPUT_DIR = REPO_ROOT / "gh-pages-output"
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output-dir", type=Path, default=REPO_ROOT / "gh-pages-output")
OUTPUT_DIR = parser.parse_args().output_dir.resolve()
STYLESHEET = REPO_ROOT / "gh-pages-output" / "api-reference.css"
os.chdir(REPO_ROOT)
@@ -164,22 +168,27 @@ print(f"Extracted: {len(all_data)} modules, {total_entries} entries")
# Generate HTML
sidebar = ""
content = ""
overview_links = ""
eid = 0
for top_level in tree_structure:
for group_index, top_level in enumerate(tree_structure):
sub_modules = tree_structure[top_level]
sidebar += f'<div class="l1"><div class="l1-title" onclick="toggleL1(this)"><span>{esc(top_level)}</span><span class="cnt">{sum(1 for s in sub_modules if f"{top_level}.{s}" in all_data)}</span><span class="arr">▸</span></div><div class="l1-children">\n'
for sub_name in sub_modules:
group_id = f"group-{group_index}"
sidebar += f'<div class="l1"><button type="button" class="l1-title" aria-expanded="true" aria-controls="{group_id}"><span>{esc(top_level)}</span><span class="cnt">{sum(1 for s in sub_modules if f"{top_level}.{s}" in all_data)}</span><span class="arr" aria-hidden="true">▸</span></button><div class="l1-children" id="{group_id}">\n'
for module_index, sub_name in enumerate(sub_modules):
key = f"{top_level}.{sub_name}"
if key not in all_data:
continue
sidebar += f'<div class="l2"><div class="l2-title" onclick="toggleL2(this)"><span>{esc(sub_name)}</span><span class="arr">▸</span></div><div class="l2-children">\n'
module_id = f"{group_id}-module-{module_index}"
sidebar += f'<div class="l2"><button type="button" class="l2-title" aria-expanded="true" aria-controls="{module_id}"><span>{esc(sub_name)}</span><span class="arr" aria-hidden="true">▸</span></button><div class="l2-children" id="{module_id}">\n'
for entry in all_data[key]:
eid += 1
eid_str = f"e{eid}"
filepath = entry.get("filepath", "")
if entry["type"] == "class":
sidebar += f'<a class="l3-item" data-target="{eid_str}" onclick="showEntry(\'{eid_str}\')"><span class="mb badge-c">C</span>{esc(entry["name"])}</a>\n'
if entry["name"] in {"AutoModel", "RegisterTables"}:
overview_links += f'<a href="#{eid_str}" class="entry-link">{esc(entry["name"])} <span aria-hidden="true">→</span></a> '
sidebar += f'<a class="l3-item" href="#{eid_str}" data-target="{eid_str}"><span class="mb badge-c" aria-hidden="true">C</span><span>{esc(entry["name"])}</span></a>\n'
gh_link = github_url(filepath, entry["lineno"])
content += f'<div class="api-detail" id="{eid_str}"><span class="dtag badge-c">class</span><h2>{esc(entry["name"])}</h2><div class="dmod">{key} · <a href="{gh_link}" target="_blank">View on GitHub ↗</a></div><div class="ddoc">{format_doc(entry["doc"])}</div>'
content += f'<details class="src-block"><summary>📄 Source code</summary><pre><code>{esc(entry["source"])}</code></pre>'
@@ -200,7 +209,7 @@ for top_level in tree_structure:
for m in entry["methods"]:
eid += 1
mid = f"e{eid}"
sidebar += f'<a class="l3-item l3m" data-target="{mid}" onclick="showEntry(\'{mid}\')"><span class="mb badge-m">M</span>.{esc(m["name"])}()</a>\n'
sidebar += f'<a class="l3-item l3m" href="#{mid}" data-target="{mid}"><span class="mb badge-m" aria-hidden="true">M</span><span>.{esc(m["name"])}()</span></a>\n'
sig = f'{entry["name"]}.{m["name"]}({", ".join(m["args"])})'
mgh = github_url(filepath, m["lineno"])
content += f'<div class="api-detail" id="{mid}"><span class="dtag badge-m">method</span><h2><code>{esc(sig)}</code></h2><div class="dmod">{key}.{entry["name"]} · <a href="{mgh}" target="_blank">View on GitHub ↗</a></div><div class="ddoc">{format_doc(m["doc"])}</div>'
@@ -211,7 +220,7 @@ for top_level in tree_structure:
else:
args_str = ", ".join(entry.get("args", []))
gh_link = github_url(filepath, entry["lineno"])
sidebar += f'<a class="l3-item" data-target="{eid_str}" onclick="showEntry(\'{eid_str}\')"><span class="mb badge-f">F</span>{esc(entry["name"])}</a>\n'
sidebar += f'<a class="l3-item" href="#{eid_str}" data-target="{eid_str}"><span class="mb badge-f" aria-hidden="true">F</span><span>{esc(entry["name"])}</span></a>\n'
content += f'<div class="api-detail" id="{eid_str}"><span class="dtag badge-f">function</span><h2><code>{esc(entry["name"])}({esc(args_str)})</code></h2><div class="dmod">{key} · <a href="{gh_link}" target="_blank">View on GitHub ↗</a></div><div class="ddoc">{format_doc(entry["doc"])}</div>'
content += f'<details class="src-block"><summary>📄 Source code</summary><pre><code>{esc(entry["source"])}</code></pre>'
if entry["truncated"]:
@@ -220,53 +229,114 @@ for top_level in tree_structure:
sidebar += '</div></div>\n'
sidebar += '</div></div>\n'
# Write the full page (keeping same CSS structure + source styles)
# Preserve extracted content and entry numbering while adding focus targets.
content = content.replace('class="api-detail"', 'class="api-detail" tabindex="-1"')
content = content.replace('target="_blank"', 'target="_blank" rel="noopener noreferrer"')
content = content.replace('<summary>📄 Source', '<summary>Source')
html_page = f'''<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>FunASR API</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="api-reference.css">
</head><body class="api-page">
<nav class="nav"><div class="container">
<a class="skip-link" href="#api-content">Skip to content</a>
<nav class="nav" aria-label="Main navigation"><div class="container">
<a href="index.html" class="nav-logo">FunASR</a>
<div class="nav-links">
<a href="index.html">Home</a>
<a href="tutorial.html">Tutorial</a>
<a href="training.html">Training</a>
<a href="model-registration.html">Develop</a>
<a href="api.html" class="active">API</a>
<a href="api.html" class="active" aria-current="page">API</a>
</div>
<a href="https://github.com/modelscope/FunASR" class="nav-github">GitHub</a>
</div></nav>
<header class="api-header"><h1>API Reference</h1><p>{len(all_data)} modules <span aria-hidden="true">/</span> {total_entries} classes and functions</p></header>
<div class="api-layout">
<div class="api-sidebar">
<input type="text" class="sb-search" placeholder="Search..." oninput="filterSidebar(this.value)">
<aside class="api-sidebar" aria-label="API navigation">
<label class="sr-only" for="api-search">Search API entries</label>
<input id="api-search" type="search" class="sb-search" placeholder="Search API" autocomplete="off" spellcheck="false">
<p id="search-empty" class="search-empty" role="status" hidden>No matching APIs</p>
<nav aria-label="Modules and entries">
{sidebar}
</div>
<div class="api-content">
<div class="api-welcome" id="api-welcome"><h2>API Reference</h2><p>{total_entries} entries · auto-generated from source<br>Click source code to expand. Links point to latest GitHub code.</p></div>
</nav></aside>
<main class="api-content" id="api-content" tabindex="-1">
<div class="api-welcome" id="api-welcome"><h2>FunASR Python API</h2><p class="muted">Models, inference, training, and utilities.</p>{overview_links}</div>
{content}
</div></div>
</main></div>
<script>
function toggleL1(el){{el.closest('.l1').classList.toggle('open')}}
function toggleL2(el){{el.closest('.l2').classList.toggle('open')}}
function showEntry(id){{
document.querySelectorAll('.api-detail').forEach(e=>e.classList.remove('active'));
document.querySelectorAll('.l3-item').forEach(e=>e.classList.remove('active'));
document.getElementById('api-welcome').style.display='none';
var t=document.getElementById(id);if(t)t.classList.add('active');
var l=document.querySelector('[data-target="'+id+'"]');if(l)l.classList.add('active');
document.querySelector('.api-content').scrollTop=0;
const entries = Array.from(document.querySelectorAll('.api-detail'));
const entryLinks = Array.from(document.querySelectorAll('.l3-item'));
const controls = Array.from(document.querySelectorAll('.l1-title, .l2-title'));
const welcome = document.getElementById('api-welcome');
const search = document.getElementById('api-search');
let expandedBeforeSearch = null;
function setExpanded(button, expanded) {{
button.setAttribute('aria-expanded', String(expanded));
document.getElementById(button.getAttribute('aria-controls')).hidden = !expanded;
}}
function filterSidebar(q){{
q=q.toLowerCase();
document.querySelectorAll('.l3-item').forEach(e=>e.classList.toggle('hidden',q&&!e.textContent.toLowerCase().includes(q)));
if(q)document.querySelectorAll('.l1,.l2').forEach(e=>e.classList.add('open'));
function showEntry(id, focus = false) {{
// Resolve only known entries, never interpolate an untrusted hash in a selector.
const target = entries.find(entry => entry.id === id) || null;
entries.forEach(entry => {{ entry.hidden = entry !== target; }});
welcome.hidden = Boolean(target);
entryLinks.forEach(link => {{
if (target && link.dataset.target === target.id) {{
link.setAttribute('aria-current', 'true');
if (link.hidden) {{ search.value = ''; filterSidebar(''); }}
for (const level of ['.l1', '.l2']) {{
const group = link.closest(level);
if (group) setExpanded(group.querySelector('button'), true);
}}
}} else {{
link.removeAttribute('aria-current');
}}
}});
document.title = target ? target.querySelector('h2').textContent + ' | FunASR API' : 'FunASR API';
if (focus && target) {{
target.focus({{preventScroll: true}});
target.scrollIntoView({{block: 'start'}});
}}
}}
if(window.location.hash)showEntry(window.location.hash.slice(1));
function filterSidebar(query) {{
const q = query.trim().toLowerCase();
if (q && !expandedBeforeSearch) {{
expandedBeforeSearch = new Map(controls.map(button => [button, button.getAttribute('aria-expanded') === 'true']));
}}
entryLinks.forEach(link => {{ link.hidden = Boolean(q) && !link.textContent.toLowerCase().includes(q); }});
document.querySelectorAll('.l2, .l1').forEach(group => {{
group.hidden = Boolean(q) && !Array.from(group.querySelectorAll('.l3-item')).some(link => !link.hidden);
}});
if (q) controls.forEach(button => setExpanded(button, true));
else if (expandedBeforeSearch) {{
expandedBeforeSearch.forEach((expanded, button) => setExpanded(button, expanded));
expandedBeforeSearch = null;
}}
document.getElementById('search-empty').hidden = !q || entryLinks.some(link => !link.hidden);
}}
controls.forEach(button => {{
setExpanded(button, false);
button.addEventListener('click', () => setExpanded(button, button.getAttribute('aria-expanded') !== 'true'));
}});
entryLinks.forEach(link => link.addEventListener('click', event => {{
if (!event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey && event.button === 0) {{
showEntry(link.dataset.target, true);
}}
}}));
search.addEventListener('input', () => filterSidebar(search.value));
document.querySelector('.skip-link').addEventListener('click', event => {{
if (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey || event.button !== 0) return;
event.preventDefault();
document.getElementById('api-content').focus();
}});
window.addEventListener('hashchange', () => showEntry(window.location.hash.slice(1)));
showEntry(window.location.hash.slice(1));
</script></body></html>'''
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
if STYLESHEET.resolve() != (OUTPUT_DIR / STYLESHEET.name).resolve():
shutil.copy2(STYLESHEET, OUTPUT_DIR / STYLESHEET.name)
with (OUTPUT_DIR / "api.html").open("w", encoding="utf-8") as f:
f.write(html_page)
@@ -274,4 +344,4 @@ training_src = REPO_ROOT / "training.html"
if training_src.exists():
shutil.copy2(training_src, OUTPUT_DIR / "training.html")
print("Generated gh-pages-output/api.html")
print(f"Generated {OUTPUT_DIR / 'api.html'}")