docs: redesign product site and unify documentation entry points

Signed-off-by: zhifu gao <[email protected]>
This commit is contained in:
zhifu gao
2026-09-06 10:48:22 +00:00
parent 483ca541ea
commit a1e5445c31
36 changed files with 1125 additions and 863 deletions
+4
View File
@@ -3,6 +3,8 @@ name: Product site
on:
pull_request:
paths:
- 'docs/**/*.md'
- 'gh-pages-output/**'
- 'web-pages/product-site/**'
- 'web-pages/scripts/**'
- 'web-pages/nginx/**'
@@ -10,6 +12,8 @@ on:
push:
branches: [main]
paths:
- 'docs/**/*.md'
- 'gh-pages-output/**'
- 'web-pages/product-site/**'
- 'web-pages/scripts/**'
- 'web-pages/nginx/**'
+1 -1
View File
@@ -22,7 +22,7 @@
</p>
<p align="center">
<a href="#quick-start">Quick Start</a> · <a href="./examples/colab/">Colab</a> · <a href="#benchmark">Benchmark</a> · <a href="./docs/model_selection.md">Model selection</a> · <a href="./docs/migration_from_whisper.md">Migration guide</a> · <a href="./docs/use_case_showcase.md">Use cases</a> · <a href="./docs/community_projects.md">Community integrations</a> · <a href="./docs/deployment_matrix.md">Deployment matrix</a> · <a href="https://www.funasr.com/">Deployment hub</a> · <a href="./docs/troubleshooting.md">Troubleshooting</a> · <a href="#model-zoo">Models</a> · <a href="https://modelscope.github.io/FunASR/agent.html">Agent Integration</a> · <a href="./integrations/openclaw/">OpenClaw</a> · <a href="https://modelscope.github.io/FunASR/">Docs</a> · <a href="./CONTRIBUTING.md">Contribute</a>
<a href="#quick-start">Quick Start</a> · <a href="./docs/model_selection.md">Model selection</a> · <a href="#model-zoo">Models</a> · <a href="./docs/deployment_matrix.md">Deployment matrix</a> · <a href="https://www.funasr.com/">Deployment hub</a> · <a href="https://www.funasr.com/en/docs/">Docs</a> · <a href="#benchmark">Benchmark</a> · <a href="./CONTRIBUTING.md">Contribute</a>
</p>
---
+1 -1
View File
@@ -21,7 +21,7 @@
</p>
<p align="center">
<a href="#クイックスタート">クイックスタート</a> · <a href="./examples/colab/README_ja.md">Colab</a> · <a href="./docs/model_selection_ja.md">モデル選択</a> · <a href="#ベンチマーク">ベンチマーク</a> · <a href="./docs/migration_from_whisper.md">Migration guide</a> · <a href="./docs/use_case_showcase.md">Use cases</a> · <a href="./docs/deployment_matrix_ja.md">Deployment matrix</a> · <a href="#モデル一覧">モデル一覧</a> · <a href="https://modelscope.github.io/FunASR/agent.html">Agent連携</a> · <a href="https://modelscope.github.io/FunASR/">ドキュメント</a>
<a href="#クイックスタート">クイックスタート</a> · <a href="./docs/model_selection_ja.md">モデル選択</a> · <a href="#モデル一覧">モデル一覧</a> · <a href="./docs/deployment_matrix_ja.md">デプロイ方式</a> · <a href="https://www.funasr.com/en/">デプロイセンター</a> · <a href="https://www.funasr.com/en/docs/">ドキュメント</a> · <a href="#ベンチマーク">ベンチマーク</a>
</p>
---
+1 -1
View File
@@ -21,7 +21,7 @@
</p>
<p align="center">
<a href="#빠른-시작">빠른 시작</a> · <a href="./examples/colab/README_ko.md">Colab</a> · <a href="./docs/model_selection_ko.md">모델 선택</a> · <a href="#벤치마크">벤치마크</a> · <a href="./docs/migration_from_whisper.md">Migration guide</a> · <a href="./docs/use_case_showcase.md">Use cases</a> · <a href="./docs/deployment_matrix_ko.md">Deployment matrix</a> · <a href="#모델-목록">모델 목록</a> · <a href="https://modelscope.github.io/FunASR/agent.html">Agent 연동</a> · <a href="https://modelscope.github.io/FunASR/">문서</a>
<a href="#빠른-시작">빠른 시작</a> · <a href="./docs/model_selection_ko.md">모델 선택</a> · <a href="#모델-목록">모델 목록</a> · <a href="./docs/deployment_matrix_ko.md">배포 방식</a> · <a href="https://www.funasr.com/en/">배포 센터</a> · <a href="https://www.funasr.com/en/docs/">문서</a> · <a href="#벤치마크">벤치마크</a>
</p>
---
+1 -1
View File
@@ -21,7 +21,7 @@
</p>
<p align="center">
<a href="#快速开始">快速开始</a> · <a href="./examples/colab/README_zh.md">Colab</a> · <a href="#性能评测">性能评测</a> · <a href="./docs/model_selection_zh.md">模型选择</a> · <a href="./docs/migration_from_whisper_zh.md">迁移指南</a> · <a href="./docs/use_case_showcase_zh.md">场景速览</a> · <a href="./docs/community_projects_zh.md">社区集成</a> · <a href="./docs/deployment_matrix_zh.md">部署选型</a> · <a href="https://www.funasr.com/">部署中心</a> · <a href="./docs/troubleshooting_zh.md">排障 FAQ</a> · <a href="#模型列表">模型列表</a> · <a href="https://modelscope.github.io/FunASR/agent.html">Agent 集成</a> · <a href="./integrations/openclaw/">OpenClaw</a> · <a href="https://modelscope.github.io/FunASR/zh/">文档</a> · <a href="./CONTRIBUTING.md">贡献</a>
<a href="#快速开始">快速开始</a> · <a href="./docs/model_selection_zh.md">模型选择</a> · <a href="#模型列表">模型列表</a> · <a href="./docs/deployment_matrix_zh.md">部署选型</a> · <a href="https://www.funasr.com/">部署中心</a> · <a href="https://www.funasr.com/docs/">文档中心</a> · <a href="#性能评测">性能评测</a> · <a href="./CONTRIBUTING.md">贡献</a>
</p>
---
+49 -3
View File
@@ -1,6 +1,52 @@
# FunASR document generation
# FunASR Documentation
[中文文档](https://www.funasr.com/docs/) |
[English documentation](https://www.funasr.com/en/docs/) |
[Model Zoo](../model_zoo/modelscope_models.md) |
[Releases](https://github.com/modelscope/FunASR/releases)
## Start Here
| Your task | Guide |
| --- | --- |
| Choose a model, language, or timestamp capability | [Model selection](model_selection.md) / [中文](model_selection_zh.md) |
| Run the first transcription | [Quickstart](https://www.funasr.com/en/quickstart.html) / [CLI](cli.md) |
| Move from Whisper or a cloud API | [Migration](migration_from_whisper.md) / [中文](migration_from_whisper_zh.md) |
## Models and Deployment
| Your task | Guide |
| --- | --- |
| Compare CPU, GPU, realtime and API runtimes | [Deployment matrix](deployment_matrix.md) / [中文](deployment_matrix_zh.md) |
| Transcribe and diarize with third-party MOSS | [MOSS](moss_transcribe_diarize.md) / [中文](moss_transcribe_diarize_zh.md) |
| Accelerate with the FunASR vLLM split engine | [vLLM](vllm_guide.md) / [中文](vllm_guide_zh.md) |
| Evaluate native vLLM serving | [Validation record](vllm_native_funasr_validation.md) |
| Deploy llama.cpp, TensorRT, Docker or Kubernetes | [Deployment manuals](https://www.funasr.com/en/deploy/) |
## Evaluate and Operate
- [Performance methodology](benchmark/rtf_reproducibility.md) and [realtime WebSocket benchmarks](benchmark/realtime_ws_benchmark.md)
- [Troubleshooting](troubleshooting.md) / [中文](troubleshooting_zh.md)
- [Use cases](use_case_showcase.md), [community integrations](community_projects.md), and [repository responsibilities](repository_roles.md)
The product-site documentation is rendered from these Markdown sources. Edit the
source once; the site build updates article content, navigation and local search.
The catalogue lives in [documentation.json](../web-pages/product-site/data/documentation.json).
Model weights retain their individual licenses; the FunASR toolkit is MIT licensed.
## Build the Product Documentation
From the repository root:
```sh
python -m pip install -r web-pages/product-site/requirements-site.txt
python web-pages/product-site/build.py --output /tmp/funasr-docs
python web-pages/product-site/validate.py /tmp/funasr-docs
python -m http.server --directory /tmp/funasr-docs 8000
```
## Build the Sphinx Reference
## Generate HTML
For convenience, we provide users with the ability to generate local HTML manually.
First, you should install the following packages, which is required for building HTML:
@@ -16,4 +62,4 @@ cd docs
make html
```
The generated files are all contained in the "FunASR/docs/_build" directory. You can access the FunASR documentation by simply opening the "html/index.html" file in your browser from this directory.
The generated files are all contained in the "FunASR/docs/_build" directory. You can access the FunASR documentation by simply opening the "html/index.html" file in your browser from this directory.
+46 -105
View File
@@ -1,135 +1,76 @@
.. Funasr documentation master file, created by
sphinx-quickstart on Tues Dec 6 19:05:00 2022.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
FunASR Documentation
====================
FunASR: A Fundamental End-to-End Speech Recognition Toolkit
============================================================
.. image:: ./images/funasr_logo.jpg
Build speech applications with the right model, a reproducible inference path,
and explicit deployment boundaries.
FunASR hopes to build a bridge between academic research and industrial applications on speech recognition. By supporting the training & finetuning of the industrial-grade speech recognition model released on `ModelScope <https://www.modelscope.cn/models?page=1&tasks=auto-speech-recognition>`_, researchers and developers can conduct research and production of speech recognition models more conveniently, and promote the development of speech recognition ecology. ASR for Fun!
Overview
============================================================
.. image:: ./images/funasr_overview.png
The `product documentation <https://www.funasr.com/en/docs/>`_ and
`Chinese documentation <https://www.funasr.com/docs/>`_ provide a searchable
entry to these guides. Source Markdown remains in this repository.
.. toctree::
:maxdepth: 1
:caption: Installation
./installation/installation.md
./installation/docker.md
.. toctree::
:maxdepth: 5
:caption: Quick Start
./funasr/quick_start.md
:caption: Get Started
installation/installation
installation/docker
model_selection
model_selection_zh
cli
migration_from_whisper
migration_from_whisper_zh
.. toctree::
:maxdepth: 1
:caption: Academic Egs
:caption: Models and Deployment
./academic_recipe/asr_recipe.md
./academic_recipe/punc_recipe.md
./academic_recipe/vad_recipe.md
./academic_recipe/sv_recipe.md
./academic_recipe/sd_recipe.md
.. toctree::
:maxdepth: 1
:caption: ModelScope Egs
./modelscope_pipeline/quick_start.md
./egs_modelscope/asr/TEMPLATE/README.md
./egs_modelscope/vad/TEMPLATE/README.md
./egs_modelscope/punctuation/TEMPLATE/README.md
./egs_modelscope/tp/TEMPLATE/README.md
./modelscope_pipeline/sv_pipeline.md
./modelscope_pipeline/sd_pipeline.md
./modelscope_pipeline/itn_pipeline.md
.. toctree::
:maxdepth: 1
:caption: Huggingface Egs
Undo
.. toctree::
:maxdepth: 1
:caption: Model Zoo
./model_zoo/modelscope_models.md
./model_zoo/huggingface_models.md
.. toctree::
:maxdepth: 1
:caption: Runtime and Service
./deployment_matrix.md
./deployment_matrix_zh.md
deployment_matrix
deployment_matrix_zh
./moss_transcribe_diarize.md
./moss_transcribe_diarize_zh.md
./runtime/readme.md
./runtime/docs/SDK_tutorial_online.md
./runtime/docs/SDK_tutorial.md
./runtime/html5/readme.md
vllm_guide
vllm_guide_zh
vllm_native_funasr_validation
.. toctree::
:maxdepth: 1
:caption: Benchmark and Leaderboard
./benchmark/rtf_reproducibility.md
./benchmark/realtime_ws_benchmark.md
./benchmark/benchmark_onnx.md
./benchmark/benchmark_onnx_cpp.md
./benchmark/benchmark_libtorch.md
./benchmark/benchmark_pipeline_cer.md
:caption: Evaluate and Operate
benchmark/rtf_reproducibility
benchmark/realtime_ws_benchmark
troubleshooting
troubleshooting_zh
.. toctree::
:maxdepth: 1
:caption: Funasr Library
:caption: Applications and Ecosystem
./reference/build_task.md
use_case_showcase
use_case_showcase_zh
community_projects
community_projects_zh
repository_roles
repository_roles_zh
.. toctree::
:maxdepth: 1
:caption: Papers
:caption: Reference
./reference/papers.md
reference/build_task
reference/papers
reference/application
reference/FQA
.. toctree::
:maxdepth: 1
:caption: Application
Model weights and runtime choices
--------------------------------
./model_selection.md
./model_selection_zh.md
./migration_from_whisper.md
./migration_from_whisper_zh.md
./cli.md
./use_case_showcase.md
./use_case_showcase_zh.md
./community_projects.md
./reference/application.md
- `Model Zoo <https://github.com/modelscope/FunASR/tree/main/model_zoo>`_
- `Production deployment manuals <https://www.funasr.com/en/deploy/>`_
- `Training examples <https://github.com/modelscope/FunASR/tree/main/examples>`_
- `Release assets <https://github.com/modelscope/FunASR/releases>`_
.. toctree::
:maxdepth: 1
:caption: FAQ and Troubleshooting
./reference/FQA.md
Indices and tables
==================
Indices
-------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
@@ -0,0 +1,31 @@
# Documentation and Product Website Implementation Plan
**Goal:** Modernize the whole FunASR documentation experience without losing existing content.
**Architecture:** Existing Jinja generator plus source-backed Markdown catalogue and shared shell.
**Tech Stack:** Python, Jinja2, BeautifulSoup, Python-Markdown, plain CSS/JS, Playwright.
**Spec:** ../specs/2026-09-06-docs-product-redesign.md
## Sequence
- [x] Inventory current website, build/deploy contract and documentation sources.
- [x] Add failing tests for source-backed docs, search, shared shell and license boundaries.
- [x] Implement catalogue/renderer, link mapping, bilingual docs and search.
- [x] Rework homepage, shared visual system, navigation and legacy reading surfaces.
- [x] Reorganize source docs index, README entry points and GitHub Pages entry points.
- [x] Run static/browser regressions and visually inspect desktop/mobile screenshots.
- [ ] Preserve rollback bundle, sign/DCO PR, verify exact-head checks, merge and deploy.
- [ ] Verify public routes, record release evidence and remaining content gaps.
## Test Commands
Run on ind-gpu8 from the isolated repository:
```sh
python -m pytest web-pages/product-site/tests -q
python web-pages/product-site/build.py --output /tmp/funasr-redesign-build
python web-pages/product-site/validate.py /tmp/funasr-redesign-build
```
Use the repository browser suite and an added redesign suite to verify desktop/mobile
navigation, source-backed MOSS docs, search/no-results, copy, language peer, all old
routes, media loading and layout overflow. Retain logs/screenshots with the release.
@@ -0,0 +1,49 @@
# FunASR Documentation and Product Website
## Intent
Rebuild the public documentation experience around model selection, first inference,
deployment, and operation. The user authorized implementation and reversible
deployment without repeated approval. Preserve existing URLs and technical scope.
## Architecture
Keep the existing Jinja/static site generator and deployment registry. Add a
documentation catalogue whose entries point at existing Markdown sources, not
copied articles. Render those sources into bilingual `/docs/` routes with a
shared sidebar, section navigation, source links, and a local search index.
Unlisted repository-relative links continue to their source on GitHub.
The source documentation README and Sphinx index follow the same task order.
The homepage is a concise product/workflow entry. Models, deployments, documentation,
ecosystem, benchmarks, blog and donors share typography, navigation and mobile
behavior. Donors stays last. Legacy article bodies, anchors, demos and media are
preserved while their surrounding shell is normalized during generation.
## Visual Direction
White/light-gray reading surfaces, near-black text and code surfaces, emerald
commands and small coral accents. System sans-serif with a CJK sans fallback.
No stock server hero, artificial performance promises, gradient decoration or
floating section cards. Fixed-size text, restrained 8px-or-less corners,
visible focus, reduced-motion support, readable tables and code on narrow screens.
Use existing licensed Lucide icons and real project/audio assets.
## Technical Integrity
Keep MOSS visibly attributed to OpenMOSS, offline transcription/diarization with
anonymous speakers, not identity recognition. Do not conflate native vLLM,
split-engine wrappers, realtime ASR, or CPU/GPU validation coverage. Fix discovered
stale copy from primary evidence; never expand test claims just to fit the design.
Toolkit license comes from the current repository LICENSE, model licenses vary.
## Acceptance
1. Bilingual home, deployment hub/details, model list, docs, blog and donors work.
2. Docs originate from repository Markdown; links, images, fenced code and tables render.
3. Search works locally, handles no results, and needs no account or remote analytics.
4. Mobile navigation, language peers, anchor links and copy controls work by keyboard.
5. All existing public routes and article content survive; no broken internal links.
6. Static tests, full site validator and browser checks at 320/390/768/1440/1920 pass.
7. Record source commit, build checksums, previous deployment and rollback procedure
before promotion. Verify public routes after deployment.
+33
View File
@@ -0,0 +1,33 @@
:root {color-scheme:light;font-family:Inter,Arial,"PingFang SC","Microsoft YaHei","WenQuanYi Zen Hei",sans-serif;color:#1b2723;background:#fff;line-height:1.65;letter-spacing:0}
* {box-sizing:border-box}
body {margin:0}
a {color:inherit;text-decoration:none;overflow-wrap:anywhere}
a:hover {color:#087a5b;text-decoration:underline;text-underline-offset:4px}
a:focus-visible,button:focus-visible,input:focus-visible {outline:3px solid #cb5e48;outline-offset:4px}
header,footer {display:flex;align-items:center;justify-content:space-between;gap:20px;max-width:1200px;margin:auto;padding:24px 32px}
header {border-bottom:1px solid #dce3df}
nav {display:flex;gap:24px;flex-wrap:wrap;font-size:14px}
.brand {font-size:22px;font-weight:700}
main {max-width:1120px;margin:auto;padding:56px 32px}
.eyebrow {font-size:12px;color:#087a5b;margin:0 0 12px}
h1 {font-size:56px;line-height:1.1;font-weight:600;margin:0}
.lead {font-size:22px;color:#52645b;margin:20px 0 32px}
form {max-width:680px;margin-bottom:48px}
label {display:block;font-size:13px;margin-bottom:8px}
.search {display:flex;border:1px solid #becdc5;border-radius:4px;overflow:hidden}
input {min-width:0;flex:1;border:0;padding:14px;font:inherit;background:transparent}
button {border:0;border-left:1px solid #becdc5;background:#eaf6f0;color:#145f49;padding:0 20px;font:inherit;cursor:pointer}
.chapters {display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:40px 48px}
.chapters section {border-top:2px solid #2d9272;padding-top:20px;min-width:0}
.chapters section:nth-child(2) {border-color:#cd7965}
.chapters section:nth-child(3) {border-color:#8a7cad}
.chapters section:nth-child(4) {border-color:#a5a05d}
.number {font-size:12px;color:#748b80}
h2 {font-size:22px;font-weight:600;margin:8px 0 16px}
.chapters a {display:block;padding:7px 0}
.reference {border-top:1px solid #dce3df;margin-top:48px;padding-top:24px}
.reference p {color:#52645b}
footer {border-top:1px solid #dce3df;font-size:13px;color:#52645b;flex-wrap:wrap}
.skip {position:absolute;top:-100px;left:20px;padding:12px;background:white;z-index:10}
.skip:focus {top:8px}
@media(max-width:600px) {header,footer {padding:20px}header {align-items:flex-start;flex-wrap:wrap}nav {gap:16px}main {padding:36px 20px}h1 {font-size:42px}.lead {font-size:19px}.chapters {grid-template-columns:1fr;gap:32px}h2 {font-size:20px}button {padding:0 14px}}
+23 -353
View File
@@ -1,359 +1,29 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="FunASR: Industrial-grade speech recognition toolkit. 170x realtime on GPU, 50+ languages, speaker diarization, emotion detection, OpenAI-compatible API, vLLM acceleration, and agent integration.">
<meta property="og:title" content="FunASR - Open-source speech understanding toolkit">
<meta property="og:description" content="170x realtime, 50+ languages, speaker diarization, vLLM acceleration, and an OpenAI-compatible local speech API.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://modelscope.github.io/FunASR/">
<link rel="canonical" href="https://modelscope.github.io/FunASR/">
<link rel="alternate" hreflang="en" href="https://modelscope.github.io/FunASR/">
<link rel="alternate" hreflang="zh" href="https://modelscope.github.io/FunASR/zh/">
<link rel="alternate" hreflang="ja" href="https://modelscope.github.io/FunASR/ja/">
<link rel="alternate" hreflang="ko" href="https://modelscope.github.io/FunASR/ko/">
<link rel="alternate" hreflang="x-default" href="https://modelscope.github.io/FunASR/">
<meta property="og:site_name" content="FunASR">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="FunASR - Open-source speech understanding toolkit">
<meta name="twitter:description" content="170x realtime, 50+ languages, speaker diarization, vLLM acceleration, and an OpenAI-compatible local speech API.">
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"SoftwareSourceCode","name":"FunASR","description":"FunASR is an open-source speech recognition and speech understanding toolkit with ASR, VAD, punctuation, speaker diarization, emotion detection, vLLM acceleration, and OpenAI-compatible API examples.","url":"https://modelscope.github.io/FunASR/","codeRepository":"https://github.com/modelscope/FunASR","programmingLanguage":"Python","license":"https://github.com/modelscope/FunASR/blob/main/LICENSE","applicationCategory":"Speech recognition toolkit","operatingSystem":["Linux","macOS","Windows"]}
</script>
<title>FunASR - End-to-End Speech Recognition Toolkit</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>FunASR Documentation</title>
<meta name="description" content="FunASR model selection, speech transcription, MOSS diarization, vLLM and llama.cpp deployment, and API reference.">
<link rel="canonical" href="https://www.funasr.com/en/docs/">
<link rel="stylesheet" href="docs-hub.css">
</head>
<body>
<nav class="nav">
<div class="container">
<a href="index.html" class="nav-logo">FunASR</a>
<div class="nav-links">
<a href="index.html" class="active">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">API</a>
<a href="vllm.html">vLLM</a>
<a href="agent.html">Agent</a>
<a href="benchmark.html">Benchmark</a>
<a href="donors.html">Donors</a>
</div>
<div class="lang-dropdown"><button class="lang-btn">English</button><div class="lang-menu"><a href="index.html" class="current">English</a><a href="zh/index.html">中文</a><a href="ja/index.html">日本語</a><a href="ko/index.html">한국어</a></div></div>
<a href="https://github.com/modelscope/FunASR" class="nav-github">GitHub</a>
</div>
</nav>
<div class="hero">
<div class="container hero-content">
<div class="hero-copy">
<p class="eyebrow">Open-source speech understanding toolkit</p>
<h1>FunASR</h1>
<p>Production-ready ASR, VAD, punctuation, speaker diarization, emotion detection, and audio event recognition with one unified Python interface.</p>
<div class="hero-badges">
<a href="https://pypi.org/project/funasr/"><img src="https://img.shields.io/pypi/v/funasr?style=flat-square&color=2563eb" alt="PyPI"></a>
<a href="https://github.com/modelscope/FunASR/stargazers"><img src="https://img.shields.io/github/stars/modelscope/FunASR?style=flat-square&color=b45309" alt="Stars"></a>
<a href="https://github.com/modelscope/FunASR/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-059669?style=flat-square" alt="License"></a>
</div>
<div class="hero-actions">
<a href="tutorial.html" class="btn btn-white">Get started</a>
<a href="training.html" class="btn btn-outline-white">Training guide</a>
<a href="api.html" class="btn btn-outline-white">API reference</a>
</div>
</div>
<div class="hero-panel" aria-label="FunASR recognition preview">
<div class="terminal-bar">
<span class="terminal-dots"><span></span><span></span><span></span></span>
<span>funasr-pipeline.py</span>
</div>
<pre>from funasr import AutoModel
model = AutoModel(
model="paraformer-zh",
vad_model="fsmn-vad",
punc_model="ct-punc",
spk_model="cam++",
)
res = model.generate(input="meeting.wav")
print(res[0]["sentence_info"])</pre>
<div class="hero-metrics">
<div><strong>50+</strong><span>languages</span></div>
<div><strong>170x</strong><span>realtime</span></div>
<div><strong>1 API</strong><span>pipeline</span></div>
</div>
</div>
</div>
</div>
<section class="alt">
<div class="container">
<h2 class="section-title">What's New</h2>
<p class="section-subtitle">The public docs now track the latest README and main-branch capabilities.</p>
<div class="grid-3">
<div class="card"><h3>llama.cpp / GGUF Runtime</h3><p>runtime-llamacpp-v0.1.8 adds a Linux Vulkan package for SenseVoiceSmall GGUF. Run the self-contained binary with <code>--backend vulkan</code> on supported GPUs.</p><p><a href="deployment-matrix.html#runtime-llamacpp-v0.1.8">Choose the GGUF runtime</a></p></div>
<div class="card"><h3>MOSS-Transcribe-Diarize</h3><p>Use the third-party OpenMOSS model for offline multi-speaker meetings: one request returns text, timestamps, and anonymous speaker labels without an external VAD pipeline.</p><p><a href="moss-transcribe-diarize.html">Choose the MOSS deployment path</a></p></div>
<div class="card"><h3>vLLM Inference Engine</h3><p>2-3x faster LLM decoding for Fun-ASR-Nano, with tensor parallel batch inference and real-time WebSocket service.</p><p><a href="vllm.html">Read the vLLM guide</a></p></div>
<div class="card"><h3>Agent Infrastructure</h3><p><code>funasr-server</code> exposes OpenAI-compatible transcription APIs; MCP and voice-input examples connect local ASR to AI tools.</p><p><a href="agent.html">Set up Agent integration</a></p></div>
<div class="card"><h3>Benchmark Report</h3><p>Long-form benchmark results cover SenseVoice, Paraformer, Fun-ASR-Nano, GLM-ASR, and Whisper variants on GPU and CPU.</p><p><a href="benchmark.html">View benchmark</a></p></div>
</div>
</div>
</section>
<section class="alt">
<div class="container">
<h2 class="section-title">Private Speech API in Minutes</h2>
<p class="section-subtitle">Run an OpenAI-compatible transcription endpoint locally, then plug it into agents, apps, and batch pipelines without sending audio to a cloud ASR provider.</p>
<div class="grid-2">
<div class="card">
<h3>Start the server</h3>
<p>Use SenseVoice for a fast first test, or switch models after the endpoint is running.</p>
<pre>pip install funasr fastapi uvicorn python-multipart
funasr-server --model sensevoice --device cuda</pre>
</div>
<div class="card">
<h3>Verify with curl</h3>
<p>Download a public sample and call the same route used by OpenAI-compatible clients.</p>
<pre>curl -L https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/BAC009S0764W0121.wav -o sample.wav
curl http://localhost:8000/v1/audio/transcriptions \
-F [email protected] \
-F model=sensevoice \
-F response_format=verbose_json</pre>
</div>
</div>
<div class="section-cta">
<a href="https://colab.research.google.com/github/modelscope/FunASR/blob/main/examples/colab/funasr_quickstart.ipynb" class="btn btn-primary">Open in Colab</a>
<a href="https://github.com/modelscope/FunASR/tree/main/examples/openai_api" class="btn btn-primary">OpenAI API example</a>
<a href="https://github.com/modelscope/FunASR/blob/main/examples/openai_api/WORKFLOWS.md" class="btn btn-primary">Workflow recipes</a>
<a href="https://github.com/modelscope/FunASR/blob/main/examples/openai_api/GRADIO.md" class="btn btn-primary">Gradio demo</a>
<a href="https://github.com/modelscope/FunASR/blob/main/examples/openai_api/SECURITY.md" class="btn btn-primary">Security guide</a>
<a href="agent.html" class="btn btn-primary">Agent integration</a>
</div>
</div>
</section>
<section class="docs-band">
<div class="container">
<h2 class="section-title">Documentation</h2>
<p class="section-subtitle">Start with examples, tune on your own data, extend the registry, or jump into source-linked API docs.</p>
<div class="doc-grid">
<a class="doc-card" href="model-selection.html">
<span class="doc-kicker">Choose</span>
<h3>Model Selection</h3>
<p>Pick SenseVoice, Paraformer, Fun-ASR-Nano, streaming runtime, or OpenAI API aliases for your workload.</p>
</a>
<a class="doc-card" href="deployment-matrix.html">
<span class="doc-kicker">Deploy</span>
<h3>Deployment Matrix</h3>
<p>Choose between Python API, OpenAI API, Docker Compose, Kubernetes, WebSocket runtime, vLLM, MCP, batch, subtitles, and Triton.</p>
</a>
<a class="doc-card" href="migration.html">
<span class="doc-kicker">Compare</span>
<h3>Migration Guide</h3>
<p>Evaluate FunASR against Whisper or cloud ASR with feature mapping, representative benchmarks, and rollout checks.</p>
</a>
<a class="doc-card" href="use-cases.html">
<span class="doc-kicker">Choose</span>
<h3>Use Cases</h3>
<p>Find the fastest path for private APIs, agents, streaming, vLLM, subtitles, batch jobs, and benchmarks.</p>
</a>
<a class="doc-card" href="tutorial.html">
<span class="doc-kicker">Learn</span>
<h3>Tutorial</h3>
<p>Install FunASR, choose a model, and run common ASR, VAD, diarization, and export flows.</p>
</a>
<a class="doc-card" href="training.html">
<span class="doc-kicker">Tune</span>
<h3>Training</h3>
<p>Prepare JSONL data, fine-tune Paraformer, SenseVoice, and Fun-ASR-Nano, then monitor runs.</p>
</a>
<a class="doc-card" href="model-registration.html">
<span class="doc-kicker">Extend</span>
<h3>Developer Guide</h3>
<p>Understand the registry, add a model, package remote code, and avoid integration pitfalls.</p>
</a>
<a class="doc-card" href="vllm.html">
<span class="doc-kicker">Accelerate</span>
<h3>vLLM Inference</h3>
<p>Run LLM-based ASR with vLLM, tensor parallel batch decoding, streaming SDK, and WebSocket service.</p>
</a>
<a class="doc-card" href="agent.html">
<span class="doc-kicker">Integrate</span>
<h3>Agent & API</h3>
<p>Expose FunASR as an OpenAI-compatible endpoint, low-code workflow node, MCP tool, voice input, or subtitle generator.</p>
</a>
<a class="doc-card" href="benchmark.html">
<span class="doc-kicker">Measure</span>
<h3>Benchmark</h3>
<p>Compare FunASR and Whisper on long-form audio, including GPU and CPU speed/CER numbers.</p>
</a>
<a class="doc-card" href="api.html">
<span class="doc-kicker">Reference</span>
<h3>API</h3>
<p>Browse generated classes, methods, source previews, and GitHub line links.</p>
</a>
</div>
</div>
</section>
<section>
<div class="container">
<h2 class="section-title">Capabilities</h2>
<p class="section-subtitle">Everything needed for speech understanding, from raw audio segmentation to speaker-aware transcripts.</p>
<div class="grid-3">
<div class="card">
<div class="card-icon">ASR</div>
<h3>Speech Recognition</h3>
<p>Streaming and offline ASR with VAD segmentation. Process long-form audio with a single API call.</p>
</div>
<div class="card">
<div class="card-icon">50+</div>
<h3>Multilingual Models</h3>
<p>Fun-ASR-Nano covers 31 languages and Qwen3-ASR covers 52 languages with language detection.</p>
</div>
<div class="card">
<div class="card-icon">SPK</div>
<h3>Speaker Diarization</h3>
<p>Identify who spoke when, then attach speaker labels to sentence-level ASR output.</p>
</div>
<div class="card">
<div class="card-icon">SFX</div>
<h3>Emotion & Events</h3>
<p>SenseVoice detects emotion and audio events including background music, applause, laughter, and crying.</p>
</div>
<div class="card">
<div class="card-icon">RTF</div>
<h3>Low Latency</h3>
<p>Non-autoregressive models support fast batch and realtime workloads across common deployment targets.</p>
</div>
<div class="card">
<div class="card-icon">OPS</div>
<h3>Train & Deploy</h3>
<p>Fine-tune with DeepSpeed, export to ONNX, and deploy through Docker runtime or the Python SDK.</p>
</div>
</div>
</div>
</section>
<section class="alt">
<div class="container">
<h2 class="section-title">Models</h2>
<p class="section-subtitle">Pre-trained industrial models ready for recognition, segmentation, and speech understanding workflows.</p>
<div class="grid-2">
<div class="card">
<h3>Fun-ASR-Nano</h3>
<p>End-to-end ASR trained on tens of millions of hours. 31 languages, dialects, accents, lyrics, timestamps, and hotwords.</p>
<div class="card-tags">
<span class="tag">800M</span>
<span class="tag">31 Languages</span>
<span class="tag">Timestamps</span>
<span class="tag">Speaker Diarization</span>
<span class="tag">Hotwords</span>
</div>
</div>
<div class="card">
<h3>Paraformer</h3>
<p>Non-autoregressive Chinese and English ASR with streaming and offline variants for production systems.</p>
<div class="card-tags">
<span class="tag">Streaming</span>
<span class="tag">Offline</span>
<span class="tag">Hotwords</span>
<span class="tag">ONNX</span>
</div>
</div>
<div class="card">
<h3>SenseVoice</h3>
<p>Multi-task speech understanding for ASR, language ID, emotion, and audio events across five languages.</p>
<div class="card-tags">
<span class="tag">Emotion</span>
<span class="tag">Audio Events</span>
<span class="tag">Speaker Diarization</span>
<span class="tag">70ms/10s</span>
</div>
</div>
<div class="card">
<h3>Qwen3-ASR</h3>
<p>LLM-based ASR with 52 languages, contextual understanding, and automatic language detection.</p>
<div class="card-tags">
<span class="tag">52 Languages</span>
<span class="tag">0.6B / 1.7B</span>
<span class="tag">Context-aware</span>
</div>
</div>
</div>
</div>
</section>
<section>
<div class="container narrow">
<h2 class="section-title">Quick Start</h2>
<p class="section-subtitle">Install locally, or run the <a href="https://github.com/modelscope/FunASR/tree/main/examples/colab">Colab quickstart</a> first to transcribe a sample in your browser.</p>
<pre>pip install funasr
# Or latest: pip install git+https://github.com/modelscope/FunASR.git</pre>
<pre>from funasr import AutoModel
model = AutoModel(
model="paraformer-zh",
vad_model="fsmn-vad",
punc_model="ct-punc",
spk_model="cam++",
)
res = model.generate(input="meeting.wav", batch_size_s=300)
for sent in res[0]["sentence_info"]:
print(f"[Speaker {sent['spk']}] {sent['text']}")</pre>
<div class="section-cta">
<a href="https://colab.research.google.com/github/modelscope/FunASR/blob/main/examples/colab/funasr_quickstart.ipynb" class="btn btn-primary">Open in Colab</a>
<a href="tutorial.html" class="btn btn-primary">Full tutorial</a>
</div>
</div>
</section>
<section class="alt">
<div class="container">
<h2 class="section-title">Ecosystem</h2>
<p class="section-subtitle">Related projects around ASR, speech understanding, video clipping, and voice generation.</p>
<div class="grid-2">
<div class="card">
<h3><a class="card-title-link" href="https://github.com/QwenAudio/Fun-ASR">Fun-ASR-Nano</a></h3>
<p>The latest ASR large model with multilingual recognition, timestamps, speaker diarization, and hotwords.</p>
</div>
<div class="card">
<h3><a class="card-title-link" href="https://github.com/QwenAudio/SenseVoice">SenseVoice</a></h3>
<p>Multi-task speech understanding for ASR, emotion detection, and audio event recognition.</p>
</div>
<div class="card">
<h3><a class="card-title-link" href="https://github.com/modelscope/FunClip">FunClip</a></h3>
<p>AI video clipping powered by FunASR and LLM-assisted editing workflows.</p>
</div>
<div class="card">
<h3><a class="card-title-link" href="https://github.com/QwenAudio/CosyVoice">CosyVoice</a></h3>
<p>Natural speech generation with multi-language, timbre, and emotion control.</p>
</div>
</div>
</div>
</section>
<footer>
<p>FunASR &middot; Tongyi Lab, Alibaba Group</p>
<p>
<a href="https://github.com/modelscope/FunASR">GitHub</a> &middot;
<a href="training.html">Training</a> &middot;
<a href="use-cases.html">Use Cases</a> &middot;
<a href="deployment-matrix.html">Deployment</a> &middot;
<a href="api.html">API</a> &middot;
<a href="vllm.html">vLLM</a> &middot;
<a href="agent.html">Agent</a> &middot;
<a href="benchmark.html">Benchmark</a> &middot;
<a href="https://github.com/modelscope/FunASR/blob/main/MODEL_LICENSE">License</a> &middot;
<a href="https://pypi.org/project/funasr/">PyPI</a> &middot;
<a href="https://github.com/modelscope/FunASR/issues">Issues</a>
</p>
</footer>
<a class="skip" href="#main">Skip to content</a>
<header><a class="brand" href="https://www.funasr.com/en/">FunASR</a><nav aria-label="Primary"><a href="https://www.funasr.com/en/deploy/">Deploy</a><a href="api.html">API</a><a href="zh/">中文</a><a href="https://github.com/modelscope/FunASR">GitHub</a></nav></header>
<main id="main">
<p class="eyebrow">DEVELOPER DOCUMENTATION</p>
<h1>FunASR</h1>
<p class="lead">From the first transcript to a service you can operate.</p>
<form action="https://www.funasr.com/en/docs/" method="get" role="search"><label for="query">Search documentation</label><div class="search"><input id="query" name="q" type="search" placeholder="Models, timestamps, MOSS, vLLM..."><button type="submit">Search</button></div></form>
<div class="chapters">
<section><span class="number">01</span><h2>Get started</h2><a href="https://www.funasr.com/en/docs/model-selection.html">Choose a model</a><a href="tutorial.html">Python quickstart</a><a href="https://www.funasr.com/en/docs/command-line.html">Command-line inference</a><a href="https://www.funasr.com/en/docs/migration.html">Migrate from Whisper</a></section>
<section><span class="number">02</span><h2>Models and capabilities</h2><a href="https://www.funasr.com/en/models.html">Model catalogue</a><a href="https://www.funasr.com/en/docs/moss-transcribe-diarize.html">MOSS-Transcribe-Diarize · Third-party model by OpenMOSS</a><a href="https://github.com/modelscope/FunASR/tree/main/model_zoo">Model Zoo</a><a href="model-registration.html">Register a model</a></section>
<section><span class="number">03</span><h2>Deploy a service</h2><a href="https://www.funasr.com/en/deploy/">Deployment matrix</a><a href="https://www.funasr.com/en/deploy/vllm.html">Native vLLM</a><a href="https://www.funasr.com/en/docs/vllm.html">FunASR split-engine vLLM</a><a href="https://www.funasr.com/en/deploy/llama-cpp.html">llama.cpp and GGUF</a></section>
<section><span class="number">04</span><h2>Operate and extend</h2><a href="https://www.funasr.com/en/docs/troubleshooting.html">Troubleshooting</a><a href="https://www.funasr.com/en/docs/benchmark-method.html">Reproducible benchmarks</a><a href="training.html">Training and fine-tuning</a><a href="api.html">Python API</a></section>
</div>
<section class="reference"><h2>Guides and reference</h2><p>The documentation hub uses repository Markdown as its source. Existing tutorials and API addresses remain available here.</p><nav aria-label="Reference"><a href="agent.html">Agent integrations</a><a href="use-cases.html">Use cases</a><a href="moss-transcribe-diarize.html">MOSS reference</a><a href="https://github.com/modelscope/FunASR/releases">Release assets</a><a href="ja/" lang="ja">日本語</a><a href="ko/" lang="ko">한국어</a></nav></section>
</main>
<footer><span>FunASR</span><a href="https://github.com/modelscope/FunASR/tree/main/docs">Edit documentation</a><a href="https://www.funasr.com/en/donors.html">Donors</a></footer>
</body>
</html>
+23 -353
View File
@@ -1,359 +1,29 @@
<!DOCTYPE html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="FunASR 是工业级开源语音识别工具包,支持 GPU 170x 实时、50+ 语言、说话人分离、情感检测、OpenAI 兼容 API、vLLM 加速和 Agent 集成。">
<meta property="og:title" content="FunASR - 开源语音理解工具包">
<meta property="og:description" content="GPU 170x 实时、50+ 语言、说话人分离、vLLM 加速和本地 OpenAI 兼容语音 API。">
<meta property="og:type" content="website">
<meta property="og:url" content="https://modelscope.github.io/FunASR/zh/">
<link rel="canonical" href="https://modelscope.github.io/FunASR/zh/">
<link rel="alternate" hreflang="en" href="https://modelscope.github.io/FunASR/">
<link rel="alternate" hreflang="zh" href="https://modelscope.github.io/FunASR/zh/">
<link rel="alternate" hreflang="ja" href="https://modelscope.github.io/FunASR/ja/">
<link rel="alternate" hreflang="ko" href="https://modelscope.github.io/FunASR/ko/">
<link rel="alternate" hreflang="x-default" href="https://modelscope.github.io/FunASR/">
<meta property="og:site_name" content="FunASR">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="FunASR - 开源语音理解工具包">
<meta name="twitter:description" content="GPU 170x 实时、50+ 语言、说话人分离、vLLM 加速和本地 OpenAI 兼容语音 API。">
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"SoftwareSourceCode","name":"FunASR","description":"FunASR 是开源语音识别与语音理解工具包,支持 ASR、VAD、标点恢复、说话人分离、情感检测、vLLM 加速和 OpenAI 兼容 API 示例。","url":"https://modelscope.github.io/FunASR/zh/","codeRepository":"https://github.com/modelscope/FunASR","programmingLanguage":"Python","license":"https://github.com/modelscope/FunASR/blob/main/LICENSE","applicationCategory":"Speech recognition toolkit","operatingSystem":["Linux","macOS","Windows"]}
</script>
<title>FunASR - 端到端语音识别工具包</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../style.css">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>FunASR 文档中心</title>
<meta name="description" content="FunASR 模型选择、语音转写、MOSS 说话人分离、vLLM 与 llama.cpp 部署及 API 参考。">
<link rel="canonical" href="https://www.funasr.com/docs/">
<link rel="stylesheet" href="../docs-hub.css">
</head>
<body>
<nav class="nav">
<div class="container">
<a href="index.html" class="nav-logo">FunASR</a>
<div class="nav-links">
<a href="index.html" class="active">首页</a>
<a href="tutorial.html">教程</a>
<a href="training.html">训练</a>
<a href="model-registration.html">开发</a>
<a href="../api.html">API</a>
<a href="vllm.html">vLLM</a>
<a href="agent.html">Agent</a>
<a href="benchmark.html">Benchmark</a>
<a href="../donors.html">功德榜</a>
</div>
<div class="lang-dropdown"><button class="lang-btn">中文</button><div class="lang-menu"><a href="../index.html">English</a><a href="index.html" class="current">中文</a><a href="../ja/index.html">日本語</a><a href="../ko/index.html">한국어</a></div></div>
<a href="https://github.com/modelscope/FunASR" class="nav-github">GitHub</a>
</div>
</nav>
<div class="hero">
<div class="container hero-content">
<div class="hero-copy">
<p class="eyebrow">开源语音理解工具包</p>
<h1>FunASR</h1>
<p>生产级语音识别、语音检测、标点恢复、说话人分离、情感检测、音频事件识别——统一的 Python 接口。</p>
<div class="hero-badges">
<a href="https://pypi.org/project/funasr/"><img src="https://img.shields.io/pypi/v/funasr?style=flat-square&color=2563eb" alt="PyPI"></a>
<a href="https://github.com/modelscope/FunASR/stargazers"><img src="https://img.shields.io/github/stars/modelscope/FunASR?style=flat-square&color=b45309" alt="Stars"></a>
<a href="https://github.com/modelscope/FunASR/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-059669?style=flat-square" alt="License"></a>
</div>
<div class="hero-actions">
<a href="tutorial.html" class="btn btn-white">快速开始</a>
<a href="training.html" class="btn btn-outline-white">训练指南</a>
<a href="../api.html" class="btn btn-outline-white">API 文档</a>
</div>
</div>
<div class="hero-panel" aria-label="FunASR 识别预览">
<div class="terminal-bar">
<span class="terminal-dots"><span></span><span></span><span></span></span>
<span>funasr-pipeline.py</span>
</div>
<pre>from funasr import AutoModel
model = AutoModel(
model="paraformer-zh",
vad_model="fsmn-vad",
punc_model="ct-punc",
spk_model="cam++",
)
res = model.generate(input="meeting.wav")
print(res[0]["sentence_info"])</pre>
<div class="hero-metrics">
<div><strong>50+</strong><span>语言</span></div>
<div><strong>170x</strong><span>实时</span></div>
<div><strong>1个API</strong><span>全流程</span></div>
</div>
</div>
</div>
</div>
<section class="alt">
<div class="container">
<h2 class="section-title">最新动态</h2>
<p class="section-subtitle">网站文档已对齐 README 和 main 分支最新能力。</p>
<div class="grid-3">
<div class="card"><h3>llama.cpp / GGUF 运行时</h3><p>runtime-llamacpp-v0.1.8 新增 SenseVoiceSmall GGUF 的 Linux Vulkan 包。支持 Vulkan 的 GPU 可用自包含二进制和 <code>--backend vulkan</code> 运行。</p><p><a href="deployment-matrix.html#runtime-llamacpp-v0.1.8">选择 GGUF 运行时</a></p></div>
<div class="card"><h3>MOSS-Transcribe-Diarize</h3><p>使用 OpenMOSS 的第三方离线模型处理多人会议:一次请求返回文本、时间戳和匿名说话人标签,不需要外挂 VAD 流水线。</p><p><a href="moss-transcribe-diarize.html">选择 MOSS 部署路径</a></p></div>
<div class="card"><h3>vLLM 推理引擎</h3><p>Fun-ASR-Nano LLM 解码加速 2-3 倍,支持 tensor parallel 批量推理和实时 WebSocket 服务。</p><p><a href="vllm.html">阅读 vLLM 文档</a></p></div>
<div class="card"><h3>Agent 基础设施</h3><p><code>funasr-server</code> 提供 OpenAI 兼容转写 API;MCP 和语音输入示例可把本地 ASR 接入 AI 工具。</p><p><a href="agent.html">配置 Agent 集成</a></p></div>
<div class="card"><h3>性能评测</h3><p>长音频评测覆盖 SenseVoice、Paraformer、Fun-ASR-Nano、GLM-ASR 和 Whisper 变体,包含 GPU/CPU 结果。</p><p><a href="benchmark.html">查看评测</a></p></div>
</div>
</div>
</section>
<section class="alt">
<div class="container">
<h2 class="section-title">几分钟部署私有语音 API</h2>
<p class="section-subtitle">在本地启动 OpenAI 兼容的转写接口,将语音能力接入 Agent、应用和批处理流水线,无需把音频发送到云端 ASR 服务。</p>
<div class="grid-2">
<div class="card">
<h3>启动服务</h3>
<p>先用 SenseVoice 快速验证,服务启动后也可以切换到其他模型。</p>
<pre>pip install funasr fastapi uvicorn python-multipart
funasr-server --model sensevoice --device cuda</pre>
</div>
<div class="card">
<h3>用 curl 验证</h3>
<p>下载公开测试音频,调用与 OpenAI 兼容客户端一致的接口路径。</p>
<pre>curl -L https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/BAC009S0764W0121.wav -o sample.wav
curl http://localhost:8000/v1/audio/transcriptions \
-F [email protected] \
-F model=sensevoice \
-F response_format=verbose_json</pre>
</div>
</div>
<div class="section-cta">
<a href="https://colab.research.google.com/github/modelscope/FunASR/blob/main/examples/colab/funasr_quickstart.ipynb" class="btn btn-primary">打开 Colab</a>
<a href="https://github.com/modelscope/FunASR/blob/main/examples/openai_api/README_zh.md" class="btn btn-primary">OpenAI API 示例</a>
<a href="https://github.com/modelscope/FunASR/blob/main/examples/openai_api/WORKFLOWS_zh.md" class="btn btn-primary">工作流配方</a>
<a href="https://github.com/modelscope/FunASR/blob/main/examples/openai_api/GRADIO_zh.md" class="btn btn-primary">Gradio 演示</a>
<a href="https://github.com/modelscope/FunASR/blob/main/examples/openai_api/SECURITY_zh.md" class="btn btn-primary">安全指南</a>
<a href="agent.html" class="btn btn-primary">Agent 集成</a>
</div>
</div>
</section>
<section class="docs-band">
<div class="container">
<h2 class="section-title">文档中心</h2>
<p class="section-subtitle">从示例开始,在自己的数据上微调,扩展模型注册表,或查阅带源码链接的 API 文档。</p>
<div class="doc-grid">
<a class="doc-card" href="model-selection.html">
<span class="doc-kicker">选型</span>
<h3>模型选择</h3>
<p>为你的场景选择 SenseVoice、Paraformer、Fun-ASR-Nano、流式服务或 OpenAI API alias。</p>
</a>
<a class="doc-card" href="deployment-matrix.html">
<span class="doc-kicker">部署</span>
<h3>部署选型</h3>
<p>对比 Python API、OpenAI API、Docker Compose、Kubernetes、WebSocket、vLLM、MCP、批处理、字幕和 Triton。</p>
</a>
<a class="doc-card" href="migration.html">
<span class="doc-kicker">对比</span>
<h3>迁移指南</h3>
<p>用功能映射、代表性评测和上线检查,评估是否从 Whisper 或云端 ASR 切到 FunASR。</p>
</a>
<a class="doc-card" href="use-cases.html">
<span class="doc-kicker">选型</span>
<h3>场景速览</h3>
<p>快速找到私有 API、Agent、流式、vLLM、字幕、批处理和评测的最佳入口。</p>
</a>
<a class="doc-card" href="tutorial.html">
<span class="doc-kicker">学习</span>
<h3>使用教程</h3>
<p>安装 FunASR,选择模型,运行语音识别、VAD、说话人分离和导出流程。</p>
</a>
<a class="doc-card" href="training.html">
<span class="doc-kicker">微调</span>
<h3>模型训练</h3>
<p>准备 JSONL 数据,微调 Paraformer、SenseVoice、Fun-ASR-Nano,监控训练过程。</p>
</a>
<a class="doc-card" href="model-registration.html">
<span class="doc-kicker">扩展</span>
<h3>开发指南</h3>
<p>理解注册表机制,添加新模型,打包独立仓库代码,避免集成陷阱。</p>
</a>
<a class="doc-card" href="vllm.html">
<span class="doc-kicker">加速</span>
<h3>vLLM 推理</h3>
<p>使用 vLLM 加速 LLM-based ASR,支持多卡批量解码、流式 SDK 和 WebSocket 服务。</p>
</a>
<a class="doc-card" href="agent.html">
<span class="doc-kicker">集成</span>
<h3>Agent 与 API</h3>
<p>将 FunASR 暴露为 OpenAI 兼容接口、低代码工作流节点、MCP 工具、语音输入法或字幕生成器。</p>
</a>
<a class="doc-card" href="benchmark.html">
<span class="doc-kicker">评测</span>
<h3>性能评测</h3>
<p>对比 FunASR 与 Whisper 在长音频上的 GPU/CPU 速度和 CER。</p>
</a>
<a class="doc-card" href="../api.html">
<span class="doc-kicker">参考</span>
<h3>API</h3>
<p>浏览自动生成的类、方法文档,查看源码预览和 GitHub 行号链接。</p>
</a>
</div>
</div>
</section>
<section>
<div class="container">
<h2 class="section-title">核心能力</h2>
<p class="section-subtitle">语音理解所需的一切——从音频分段到带说话人标注的转写结果。</p>
<div class="grid-3">
<div class="card">
<div class="card-icon">ASR</div>
<h3>语音识别</h3>
<p>支持流式和离线 ASR,内置 VAD 分段。单次 API 调用即可处理数小时的长音频。</p>
</div>
<div class="card">
<div class="card-icon">50+</div>
<h3>多语言模型</h3>
<p>Fun-ASR-Nano 覆盖 31 种语言含中文方言,Qwen3-ASR 覆盖 52 种语言并自动检测语种。</p>
</div>
<div class="card">
<div class="card-icon">SPK</div>
<h3>说话人分离</h3>
<p>识别"谁在什么时候说了什么",将说话人标签附加到句子级 ASR 输出。</p>
</div>
<div class="card">
<div class="card-icon">SFX</div>
<h3>情感与音频事件</h3>
<p>SenseVoice 可检测语音情感和音频事件——背景音乐、掌声、笑声、哭声。</p>
</div>
<div class="card">
<div class="card-icon">RTF</div>
<h3>低延迟</h3>
<p>非自回归模型支持高效批处理和实时场景,可部署在各种硬件上。</p>
</div>
<div class="card">
<div class="card-icon">OPS</div>
<h3>训练与部署</h3>
<p>支持 DeepSpeed 微调、ONNX 导出、Docker 运行时和 Python SDK 部署。</p>
</div>
</div>
</div>
</section>
<section class="alt">
<div class="container">
<h2 class="section-title">模型</h2>
<p class="section-subtitle">预训练工业级模型,用于语音识别、音频分段和语音理解流水线。</p>
<div class="grid-2">
<div class="card">
<h3>Fun-ASR-Nano</h3>
<p>基于数千万小时数据训练的端到端 ASR。支持 31 种语言、方言、歌词、时间戳和热词。</p>
<div class="card-tags">
<span class="tag">8亿参数</span>
<span class="tag">31种语言</span>
<span class="tag">时间戳</span>
<span class="tag">说话人分离</span>
<span class="tag">热词</span>
</div>
</div>
<div class="card">
<h3>Paraformer</h3>
<p>非自回归中英文语音识别,提供流式和离线版本,适用于生产系统。</p>
<div class="card-tags">
<span class="tag">流式</span>
<span class="tag">离线</span>
<span class="tag">热词</span>
<span class="tag">ONNX</span>
</div>
</div>
<div class="card">
<h3>SenseVoice</h3>
<p>多任务语音理解:语音识别、语种识别、情感检测、音频事件检测,支持 5 种语言。</p>
<div class="card-tags">
<span class="tag">情感</span>
<span class="tag">音频事件</span>
<span class="tag">说话人分离</span>
<span class="tag">70ms/10s</span>
</div>
</div>
<div class="card">
<h3>Qwen3-ASR</h3>
<p>基于大语言模型的语音识别,支持 52 种语言,具备上下文理解和自动语种检测能力。</p>
<div class="card-tags">
<span class="tag">52种语言</span>
<span class="tag">0.6B / 1.7B</span>
<span class="tag">上下文感知</span>
</div>
</div>
</div>
</div>
</section>
<section>
<div class="container narrow">
<h2 class="section-title">快速开始</h2>
<p class="section-subtitle">可以本地安装,也可以先运行 <a href="https://github.com/modelscope/FunASR/blob/main/examples/colab/README_zh.md">Colab 快速体验</a>,在浏览器里转写样例音频。</p>
<pre>pip install funasr
# 或安装最新版:pip install git+https://github.com/modelscope/FunASR.git</pre>
<pre>from funasr import AutoModel
model = AutoModel(
model="paraformer-zh",
vad_model="fsmn-vad",
punc_model="ct-punc",
spk_model="cam++",
)
res = model.generate(input="meeting.wav", batch_size_s=300)
for sent in res[0]["sentence_info"]:
print(f"[说话人 {sent['spk']}] {sent['text']}")</pre>
<div class="section-cta">
<a href="https://colab.research.google.com/github/modelscope/FunASR/blob/main/examples/colab/funasr_quickstart.ipynb" class="btn btn-primary">打开 Colab</a>
<a href="tutorial.html" class="btn btn-primary">完整教程</a>
</div>
</div>
</section>
<section class="alt">
<div class="container">
<h2 class="section-title">生态系统</h2>
<p class="section-subtitle">围绕语音识别、语音理解、视频剪辑、语音合成的相关项目。</p>
<div class="grid-2">
<div class="card">
<h3><a class="card-title-link" href="https://github.com/QwenAudio/Fun-ASR">Fun-ASR-Nano</a></h3>
<p>最新的语音识别大模型,支持多语言识别、时间戳、说话人分离和热词定制。</p>
</div>
<div class="card">
<h3><a class="card-title-link" href="https://github.com/QwenAudio/SenseVoice">SenseVoice</a></h3>
<p>多任务语音理解:语音识别、情感检测、音频事件识别。</p>
</div>
<div class="card">
<h3><a class="card-title-link" href="https://github.com/modelscope/FunClip">FunClip</a></h3>
<p>基于 FunASR 的 AI 视频剪辑,支持大模型辅助的智能编辑流程。</p>
</div>
<div class="card">
<h3><a class="card-title-link" href="https://github.com/QwenAudio/CosyVoice">CosyVoice</a></h3>
<p>自然语音生成,支持多语言、音色和情感控制。</p>
</div>
</div>
</div>
</section>
<footer>
<p>FunASR &middot; 通义实验室,阿里巴巴集团</p>
<p>
<a href="https://github.com/modelscope/FunASR">GitHub</a> &middot;
<a href="training.html">训练</a> &middot;
<a href="use-cases.html">场景速览</a> &middot;
<a href="deployment-matrix.html">部署选型</a> &middot;
<a href="../api.html">API</a> &middot;
<a href="vllm.html">vLLM</a> &middot;
<a href="agent.html">Agent</a> &middot;
<a href="benchmark.html">Benchmark</a> &middot;
<a href="https://github.com/modelscope/FunASR/blob/main/MODEL_LICENSE">许可证</a> &middot;
<a href="https://pypi.org/project/funasr/">PyPI</a> &middot;
<a href="https://github.com/modelscope/FunASR/issues">问题反馈</a>
</p>
</footer>
<a class="skip" href="#main">跳到正文</a>
<header><a class="brand" href="https://www.funasr.com/">FunASR</a><nav aria-label="主导航"><a href="https://www.funasr.com/deploy/">部署</a><a href="../api.html">API</a><a href="../">EN</a><a href="https://github.com/modelscope/FunASR">GitHub</a></nav></header>
<main id="main">
<p class="eyebrow">开发者文档</p>
<h1>FunASR</h1>
<p class="lead">从第一次转写,到稳定运行的语音服务。</p>
<form action="https://www.funasr.com/docs/" method="get" role="search"><label for="query">搜索文档</label><div class="search"><input id="query" name="q" type="search" placeholder="模型、时间戳、MOSS、vLLM..."><button type="submit">搜索</button></div></form>
<div class="chapters">
<section><span class="number">01</span><h2>开始使用</h2><a href="https://www.funasr.com/docs/model-selection.html">选择模型</a><a href="tutorial.html">Python 快速开始</a><a href="https://www.funasr.com/docs/command-line.html">命令行推理</a><a href="https://www.funasr.com/docs/migration.html">从 Whisper 迁移</a></section>
<section><span class="number">02</span><h2>模型与能力</h2><a href="https://www.funasr.com/models.html">模型目录</a><a href="https://www.funasr.com/docs/moss-transcribe-diarize.html">MOSS-Transcribe-Diarize · OpenMOSS 第三方模型</a><a href="https://github.com/modelscope/FunASR/tree/main/model_zoo">Model Zoo</a><a href="../model-registration.html">注册模型</a></section>
<section><span class="number">03</span><h2>部署服务</h2><a href="https://www.funasr.com/deploy/">部署矩阵</a><a href="https://www.funasr.com/deploy/vllm.html">原生 vLLM</a><a href="https://www.funasr.com/docs/vllm.html">FunASR split-engine vLLM</a><a href="https://www.funasr.com/deploy/llama-cpp.html">llama.cpp 与 GGUF</a></section>
<section><span class="number">04</span><h2>运维与扩展</h2><a href="https://www.funasr.com/docs/troubleshooting.html">常见问题与排障</a><a href="https://www.funasr.com/docs/benchmark-method.html">可复现性能评测</a><a href="../training.html">训练与微调</a><a href="../api.html">Python API</a></section>
</div>
<section class="reference"><h2>教程与参考</h2><p>统一文档中心从仓库 Markdown 生成。本站原有教程和 API 地址继续保留。</p><nav aria-label="参考资料"><a href="../agent.html">Agent 集成</a><a href="use-cases.html">应用场景</a><a href="moss-transcribe-diarize.html">MOSS 参考</a><a href="https://github.com/modelscope/FunASR/releases">发布产物</a><a href="../ja/" lang="ja">日本語</a><a href="../ko/" lang="ko">한국어</a></nav></section>
</main>
<footer><span>FunASR</span><a href="https://github.com/modelscope/FunASR/tree/main/docs">编辑文档</a><a href="https://www.funasr.com/donors.html">功德榜</a></footer>
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
# FunASR product website and documentation
This is the source for www.funasr.com. It uses Jinja templates and a static
Python build; no browser framework or runtime application server is required.
## Content ownership
- `data/documentation.json`: task groups, bilingual titles and Markdown sources.
- Repository `docs/*.md`: documentation body, shared with GitHub readers.
- `data/deployments.json`: versions, hardware, evidence and operational limits.
- `templates/`: generated homepage, deployment manuals and documentation shell.
- `assets/css/experience.css`: shared product and legacy reading experience.
- `legacy/`: preserved public routes, content and demo assets. Do not delete or
replace this snapshot with a partial output tree.
- Repository `gh-pages-output/index.html` and `zh/index.html`: GitHub Pages entry
points. Their existing API/tutorial routes remain available.
The build emits search indexes, maps source-relative document links, preserves
Unicode heading anchors and fingerprints all product assets. Historical
benchmark evidence must retain its exact tested model/runtime/hardware; new
upstream support is not evidence that old tests covered a new checkpoint.
## Validate
```sh
python -m pip install -r web-pages/product-site/requirements-site.txt
python -m pytest web-pages/product-site/tests -q
python web-pages/product-site/build.py --output /tmp/funasr-site
python web-pages/product-site/validate.py /tmp/funasr-site
```
Browser tests live in `tests/browser/`. Install their pinned npm dependencies
and Chromium, then run `npm test` from that directory. The suite owns its local
HTTP server and rejects reuse of an occupied port.
Production releases use `web-pages/scripts/deploy-product-site.sh`; see
`docs/operations/funasr-com-site-release.md` for backup, atomic switching and
rollback. Only publish a tested artifact, never a partially built output.
@@ -0,0 +1,10 @@
# Homepage speech sample
The audio is the public Chinese sample used by FunASR quickstart examples:
https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav
- Duration: 5.5466875 seconds.
- SHA256: a1bd32dc78493c123f9625a66deee562aed2895f53fbc39f2cca3be7e6f4f20f
- The homepage waveform is plotted from the actual sample, not generated speech.
- Model and sample rights remain with their respective owners; the repository's
software license is not a separate license grant for third-party media.
@@ -0,0 +1,261 @@
/* Shared reading and navigation system for generated guides and legacy articles. */
:root {
--ink: #191d1c;
--muted: #626b68;
--line: #e0e6e3;
--soft: #f5f7f6;
--paper: #fff;
--blue: #087f5b;
--blue-dark: #066347;
--green: #087f5b;
--primary: #087f5b;
--primary-dark: #066347;
--text: #191d1c;
--text-soft: #626b68;
--text-muted: #626b68;
--border: #e0e6e3;
--radius: 8px;
--font-reading: -apple-system, BlinkMacSystemFont, "Inter", "Noto Sans CJK SC", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", "WenQuanYi Zen Hei", Arial, sans-serif;
font-family: var(--font-reading);
}
body, button, input, select, textarea { font-family: var(--font-reading); letter-spacing: 0; }
body { -webkit-font-smoothing: antialiased; }
h1, h2, h3, h4 { font-family: var(--font-reading); text-wrap: balance; letter-spacing: 0; }
body h1 { font-size: 42px; line-height: 1.2; font-weight: 740; }
body h2 { font-size: 28px; line-height: 1.35; }
p { text-wrap: pretty; }
pre, code { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; }
button, a { -webkit-tap-highlight-color: transparent; }
.site-header { height: 72px; min-height: 72px; padding: 0 max(24px, calc((100% - 1328px) / 2)); background: #fffffff5; }
.brand { font-size: 20px; font-weight: 760; }
.brand-mark { width: 28px; height: 28px; background: #191d1c; color: #7eebbb; border-radius: 7px; }
.primary-navigation a { font-size: 13px; padding: 10px 9px; }
.primary-navigation a[aria-current=page] { color: var(--blue); border-bottom-color: var(--blue); }
.section { max-width: 1200px; padding: 48px 0; margin: 0 auto; border-bottom: 1px solid var(--line); }
.section-heading { margin-bottom: 26px; max-width: 760px; }
.section-heading h2 { margin: 8px 0 12px; }
.section-heading p:not(.eyebrow) { font-size: 15px; line-height: 1.8; }
.eyebrow { color: var(--blue); font-size: 11px; font-weight: 700; letter-spacing: 0; }
.button { border-radius: 6px; font-size: 14px; min-height: 44px; padding: 10px 18px; }
.button-primary { background: var(--blue); border-color: var(--blue); color: white; }
.button-primary:hover { background: #066347; border-color: #066347; }
.hero { min-height: 0; height: 560px; background: #171d1b; color: #fff; overflow: hidden; }
.hero-image { position: absolute; inset: auto 0 0; width: 100%; height: 120px; object-fit: cover; opacity: 1; }
.hero-content { position: relative; width: min(1200px, calc(100% - 48px)); max-width: none; padding: 48px 0 0; margin: 0 auto; }
.hero .eyebrow { color: #89dbb6; margin: 0 0 10px; }
.hero h1 { font-size: 72px; line-height: 1.12; margin: 0 0 12px; color: white; }
.hero .hero-copy { max-width: 900px; font-size: 27px; line-height: 1.45; font-weight: 530; margin: 0 0 10px; color: #f8fbf9; }
.hero-description { max-width: 660px; font-size: 16px; line-height: 1.7; color: #afbdb7; margin: 0 0 24px; }
.hero .button-primary { background: #8fefc2; border-color: #8fefc2; color: #103328; }
.hero .button-primary img { filter: none; }
.hero .button-secondary { color: #fff; background: #ffffff08; border-color: #55625c; }
.hero .button-secondary img { filter: invert(1); }
.hero-content > .audio-proof { max-width: none; }
.audio-proof { position: relative; display: flex; justify-content: space-between; gap: 24px; align-items: center; padding: 18px 0; margin-top: 24px; border-top: 1px solid #506359; }
.sample-label { color: #8fefc2; font: 11px monospace; }
.audio-proof p { margin: 8px 0 0; color: #f8fbf9; font-size: 15px; }
.audio-proof audio { width: 280px; height: 40px; flex-shrink: 0; }
.repository-router { padding-top: 40px; }
.repository-router-links { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 20px; }
.repository-router-links a { min-height: 112px; padding: 18px 0; border-top: 2px solid #087f5b; }
.repository-router-links a:nth-child(2) { border-color: #715dba; }
.repository-router-links a:nth-child(3) { border-color: #c55d51; }
.repository-router-links a:nth-child(4) { border-color: #b48125; }
.repository-router-links span { font-size: 15px; color: #191d1c; font-weight: 700; }
.repository-router-links small { font-size: 10px; font-weight: 400; color: var(--muted); }
.repository-router-links strong { font-size: 13px; font-weight: 450; color: var(--muted); }
.knowledge-entry { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 64px; align-items: start; }
.knowledge-entry .learning-paths { grid-column: 1 / -1; }
.learning-paths { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 24px; margin-top: 18px; }
.learning-paths a { text-decoration: none; display: grid; gap: 8px; padding: 18px 0; border-top: 1px solid var(--line); }
.learning-paths a span { color: #87938d; font: 12px monospace; }
.learning-paths strong { font-size: 17px; color: var(--ink); }
.learning-paths small { font-size: 12px; color: var(--muted); }
.learning-paths a:hover strong, .docs-groups a:hover { color: var(--blue); }
.doc-search { position: relative; margin: 0; width: 100%; min-width: 0; }
.doc-search label { display: block; color: var(--muted); font-size: 12px; margin-bottom: 8px; }
.search-field { display: flex; padding: 5px 6px 5px 16px; border: 1px solid #c9d5ce; border-radius: 8px; background: white; align-items: center; }
.search-field:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px #087f5b14; }
.search-field input { border: 0; outline: 0; background: transparent; min-width: 0; flex: 1; font-size: 14px; height: 38px; color: var(--ink); }
.search-field input:focus-visible { outline: none; }
.search-results { position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 35; background: white; border: 1px solid var(--line); border-radius: 8px; max-height: 420px; overflow-y: auto; box-shadow: 0 12px 30px #192e2118; }
.search-results[hidden] { display: none; }
.search-results a { display: block; text-decoration: none; padding: 13px 16px; border-bottom: 1px solid var(--line); }
.search-results a:hover, .search-results a:focus-visible { background: #eef7f1; }
.search-results strong { display: block; font-size: 14px; color: var(--ink); }
.search-results small { display: block; font-size: 12px; color: var(--muted); margin-top: 4px; overflow-wrap: anywhere; }
.search-results p { margin: 0; padding: 18px; font-size: 13px; }
.selector { gap: 24px; }
.segments { border-radius: 6px; }
.segments button { font-size: 13px; min-height: 40px; }
.segments button[aria-pressed=true] { background: #1b2922; color: #e6f7ec; }
.selector-result { border-radius: 8px; background: #f4f8f5; padding: 24px; margin-top: 20px; }
.selector-result h3 { font-size: 20px; }
.selector-result p { font-size: 13px; }
.limitation { border-left-color: #c5864d; }
.matrix-section { padding-top: 30px; }
.matrix-section .section-heading { display: none; }
.matrix-disclosure summary { cursor: pointer; font-size: 15px; font-weight: 600; padding: 12px 0; }
.matrix-disclosure summary span { color: var(--muted); font: 12px monospace; margin-left: 10px; }
.table-wrap { width: 100%; max-width: 100%; overflow-x: auto; border-radius: 0; }
table { font-size: 13px; }
th { background: #f6f8f7; }
td, th { padding: 14px; }
.terminal, .command-block, .docs-article pre { background: #1c2521; border-color: #35443b; border-radius: 8px; color: #e6eee9; }
.terminal-bar { background: #26362d; border-radius: 8px 8px 0 0; }
.command-block pre { font-size: 13px; line-height: 1.8; }
.api-section { align-items: center; }
.responsibility-grid > div, .ecosystem-repositories > article { border-radius: 0; background: transparent; }
.responsibility-grid h3, .ecosystem-repositories h3 { font-size: 18px; }
.responsibility-grid p, .ecosystem-repositories p { font-size: 14px; }
.final-cta h2 { max-width: 620px; font-size: 25px; }
.site-footer { padding: 42px max(24px, calc((100% - 1200px) / 2)); background: #17211c; color: #c1d0c7; font-size: 13px; }
.site-footer a { color: #ceded4; }
.site-footer p { color: #9eb3a6; font-size: 12px; }
.page-intro, .detail-hero { max-width: 1200px; padding: 48px 0 36px; margin: 0 auto; background: none; border-bottom: 1px solid var(--line); }
.page-intro h1, .detail-heading h1 { font-size: 38px; }
.intro-metrics dd { font-size: 24px; }
.detail-meta { border-radius: 6px; background: #f4f7f5; }
.detail-meta dd { font-size: 13px; }
.deploy-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
.deployment-card { padding: 24px; border: 1px solid var(--line); border-radius: 8px; box-shadow: none; background: #fff; }
.deployment-card h2 { font-size: 22px; }
.deployment-card:hover { border-color: #8ab7a0; }
.deployment-card p, .deployment-card dl { font-size: 13px; }
.card-limitation { border-radius: 0; background: transparent; border-top: 1px solid var(--line); padding: 12px 0; }
.content-section { max-width: 1040px; }
.content-section h2 { font-size: 26px; }
.runtime-path { border-radius: 0; border: 0; border-top: 1px solid var(--line); background: none; padding: 20px 0; }
.limitation-callout { border-radius: 0; background: #fbf7ee; padding: 24px; }
.limitation-callout h2 { font-size: 20px; }
.status { border-radius: 4px; font-size: 11px; }
.docs-layout { display: grid; grid-template-columns: 230px minmax(0, 790px) 190px; max-width: 1328px; gap: 36px; padding: 30px 24px 70px; margin: 0 auto; align-items: start; }
.docs-sidebar { position: sticky; top: 100px; max-height: calc(100vh - 124px); overflow-y: auto; font-size: 13px; padding-right: 18px; border-right: 1px solid var(--line); }
.docs-sidebar a { text-decoration: none; display: block; padding: 7px 10px; margin: 2px 0; border-radius: 5px; color: #5a665f; }
.docs-sidebar a:hover { color: var(--blue); background: var(--soft); }
.docs-sidebar a[aria-current=page] { color: #086a47; background: #e9f5ed; font-weight: 600; }
.docs-home { font-weight: 700; color: var(--ink) !important; }
.docs-sidebar details { margin: 22px 0; }
.docs-sidebar summary { font-size: 12px; font-weight: 700; padding: 0 10px 6px; cursor: pointer; }
.docs-body { min-width: 0; }
.breadcrumbs { display: flex; flex-wrap: wrap; gap: 8px; color: var(--muted); font-size: 12px; margin-bottom: 32px; }
.breadcrumbs a { text-decoration: none; }
.docs-title h1 { font-size: 36px; line-height: 1.4; margin: 0 0 16px; }
.docs-title p { color: var(--muted); font-size: 15px; margin-bottom: 26px; }
.source-link { font-size: 12px; color: var(--blue); }
.source-note { font-size: 12px; color: var(--muted); padding: 12px 0; border-bottom: 1px solid var(--line); }
.source-title-anchor { height: 0; }
.docs-article { font-size: 15px; line-height: 1.9; margin-top: 34px; min-width: 0; overflow-wrap: anywhere; }
.docs-article h2 { font-size: 25px; margin: 42px 0 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.docs-article h3 { font-size: 19px; margin: 28px 0 12px; }
.docs-article a { color: var(--blue); }
.docs-article p { margin: 12px 0; }
.docs-article ul, .docs-article ol { padding-left: 24px; }
.docs-article li { margin: 6px 0; }
.docs-article pre { position: relative; overflow-x: auto; padding: 24px 50px 24px 20px; margin: 20px 0; max-width: 100%; font-size: 13px; line-height: 1.8; }
.docs-article pre code { background: none; color: inherit; white-space: pre; padding: 0; }
.docs-article :not(pre) > code { padding: 2px 5px; border-radius: 4px; background: #f0f4f1; font-size: .88em; }
.docs-article table { display: block; overflow-x: auto; max-width: 100%; width: 100%; }
.docs-article blockquote { border-left: 3px solid #7ea98e; background: #f3f8f4; margin: 20px 0; padding: 8px 20px; color: #52655a; }
.docs-toc { position: sticky; top: 104px; max-height: calc(100vh - 130px); overflow-y: auto; border-left: 1px solid var(--line); padding-left: 18px; font-size: 12px; }
.docs-toc strong { color: var(--ink); }
.docs-toc ul { list-style: none; padding-left: 0; }
.docs-toc ul ul { padding-left: 10px; }
.docs-toc a { color: var(--muted); display: block; text-decoration: none; padding: 5px 0; line-height: 1.5; }
.docs-toc a:hover { color: var(--blue); }
.docs-groups { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px; margin-top: 36px; }
.docs-groups section { border-top: 1px solid var(--line); padding-top: 20px; }
.chapter-number { color: #89988e; font: 12px monospace; }
.docs-groups h2 { font-size: 20px; margin: 8px 0 12px; }
.docs-groups a { display: flex; justify-content: space-between; gap: 16px; padding: 8px 0; font-size: 14px; text-decoration: none; }
.docs-next { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 16px; border-top: 1px solid var(--line); padding-top: 24px; margin-top: 48px; font-size: 13px; }
.legacy-page { color: var(--ink); background: white; }
.legacy-page .site-header a { text-decoration: none; }
.legacy-page article { padding: 48px 0 40px; }
.legacy-page article > .container, .legacy-page main > .container { max-width: 1040px; }
.legacy-page article h1 { font-size: 38px; }
.legacy-page article h2 { font-size: 25px; margin-top: 42px; border-top: 1px solid var(--line); }
.legacy-page article h3 { font-size: 19px; }
.legacy-page article p, .legacy-page article li { font-size: 15px; line-height: 1.9; }
.legacy-page .model-card { box-shadow: none; border: 0; border-top: 1px solid var(--line); border-radius: 0; padding: 26px 0; margin: 0; }
.legacy-page .model-name { font-size: 23px; }
.legacy-page .card, .legacy-page .blog-card { border-radius: 8px; box-shadow: none; }
.legacy-page pre { background: #1c2521; color: #e6eee9; border-radius: 8px; font-size: 13px; position: relative; }
.legacy-page .highlight, .legacy-page .callout { border-radius: 4px; background: #fbf7ee; }
.legacy-page .badge { border-radius: 4px; }
.legacy-page .site-footer { margin-top: 30px; text-align: left; }
.legacy-page .site-footer p { font-size: 12px; }
.legacy-page footer strong { color: white; }
.legacy-page .hero, .legacy-page .page-header { height: auto; min-height: 0; padding: 50px 24px; background: #f5f8f6; color: var(--ink); }
.legacy-page .hero h1 { color: var(--ink); font-size: 38px; }
.legacy-page .hero p { color: var(--muted); }
.legacy-page .post-card { min-width: 0; overflow-wrap: anywhere; }
.legacy-page .launch-feature { margin-top: 36px; }
.legacy-page .launch-feature h2 { font-size: 22px; }
[id] { scroll-margin-top: 100px; }
@media (max-width: 1280px) {
.section, .page-intro, .detail-hero { width: calc(100% - 48px); }
.docs-layout { grid-template-columns: 210px minmax(0, 1fr); gap: 28px; }
.docs-toc { display: none; }
}
@media (max-width: 1050px) {
.site-header { grid-template-columns: auto 1fr auto auto; padding: 0 24px; height: 64px; min-height: 64px; }
.menu-button { display: inline-flex; grid-column: 3; grid-row: 1; }
.header-actions { grid-column: 4; grid-row: 1; }
.primary-navigation { display: none; position: absolute; top: 64px; left: 0; right: 0; background: white; border-bottom: 1px solid var(--line); padding: 12px 24px; box-shadow: 0 10px 16px #172b2110; }
.primary-navigation[data-open=true] { display: grid; grid-template-columns: repeat(4, 1fr); }
.primary-navigation a { white-space: normal; }
.docs-layout { grid-template-columns: 180px minmax(0, 1fr); gap: 24px; }
.repository-router-links { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
body { font-size: 15px; }
body h1, .page-intro h1, .detail-heading h1, .legacy-page article h1 { font-size: 30px; }
body h2, .section-heading h2 { font-size: 24px; }
.site-header { padding: 0 16px; }
.primary-navigation[data-open=true] { grid-template-columns: repeat(2, 1fr); padding: 12px 16px; }
.hero { height: 610px; }
.hero-content { width: calc(100% - 40px); padding-top: 36px; }
.hero h1 { font-size: 56px; }
.hero .hero-copy { font-size: 24px; }
.hero-description { font-size: 14px; max-width: 440px; }
.hero-actions { gap: 10px; }
.hero-actions .button { padding: 10px 13px; font-size: 13px; }
.audio-proof { flex-direction: column; align-items: flex-start; gap: 16px; margin-top: 30px; padding-top: 18px; }
.audio-proof p { font-size: 13px; }
.audio-proof audio { width: min(280px, 100%); }
.hero-image { height: 100px; }
.section, .page-intro, .detail-hero { width: calc(100% - 40px); padding: 34px 0; }
.knowledge-entry { display: block; }
.learning-paths { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.learning-paths strong { font-size: 16px; }
.repository-router-links { gap: 14px; }
.repository-router-links a { min-height: 124px; }
.repository-router-links span { font-size: 14px; }
.repository-router-links strong { font-size: 12px; }
.selector { gap: 18px; }
.selector-result { padding: 18px; }
.selector-result h3 { font-size: 18px; }
.deploy-grid { grid-template-columns: 1fr; }
.deployment-card { padding: 20px; }
.docs-layout { display: block; padding: 24px 20px 50px; }
.docs-sidebar { position: static; max-height: none; border-right: 0; padding: 0 0 16px; margin-bottom: 24px; border-bottom: 1px solid var(--line); }
.docs-sidebar details { display: inline-block; vertical-align: top; width: calc(50% - 8px); margin: 6px 4px 6px 0; }
.docs-sidebar details:not([open]) nav { display: none; }
.docs-sidebar details[open] { width: 100%; }
.docs-title h1 { font-size: 29px; }
.breadcrumbs { margin-bottom: 20px; }
.docs-groups { gap: 24px; grid-template-columns: 1fr; }
.docs-article { font-size: 14px; }
.docs-article h2 { font-size: 23px; }
.docs-article pre { font-size: 12px; }
.legacy-page .container { padding: 0 20px; }
.legacy-page table { white-space: normal; min-width: 620px; font-size: 12px; }
.legacy-page article { padding-top: 30px; }
.legacy-page .hero h1 { font-size: 30px; }
.site-footer { padding: 36px 20px; }
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
*, *::before, *::after { animation: none !important; transition: none !important; }
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

@@ -0,0 +1,107 @@
(() => {
const language = document.body.dataset.language || 'zh';
const chinese = language === 'zh';
let searchData;
const getSearchData = () => {
if (!searchData) {
searchData = fetch(`/search-${language}.json`).then((response) => {
if (!response.ok) throw new Error('search_unavailable');
return response.json();
}).catch((error) => { searchData = null; throw error; });
}
return searchData;
};
document.querySelectorAll('[data-doc-search]').forEach((form) => {
const input = form.querySelector('input');
const results = form.querySelector('.search-results');
let timer;
const show = async () => {
const query = input.value.trim().toLocaleLowerCase();
if (!query) { results.hidden = true; results.replaceChildren(); return; }
try {
const entries = await getSearchData();
if (query !== input.value.trim().toLocaleLowerCase()) return;
const terms = query.split(/\s+/);
const ranked = entries.map((entry) => {
const title = entry.title.toLocaleLowerCase();
const text = entry.text.toLocaleLowerCase();
return { ...entry, score: terms.every((term) => (title + text).includes(term))
? terms.reduce((score, term) => score + (title.includes(term) ? 10 : 1), 0) : 0 };
}).filter((entry) => entry.score).sort((a, b) => b.score - a.score).slice(0, 10);
results.replaceChildren();
for (const entry of ranked) {
const link = document.createElement('a');
link.href = entry.url;
const title = document.createElement('strong');
title.textContent = entry.title;
const snippet = document.createElement('small');
const offset = Math.max(0, entry.text.toLocaleLowerCase().indexOf(terms[0]) - 35);
snippet.textContent = entry.text.slice(offset, offset + 130);
link.append(title, snippet);
results.append(link);
}
if (!ranked.length) {
const message = document.createElement('p');
message.textContent = chinese ? '没有找到相关文档。试试模型名称或部署方式。' : 'No matching documents. Try a model or runtime name.';
results.append(message);
}
results.hidden = false;
} catch {
results.replaceChildren();
const message = document.createElement('p');
message.textContent = chinese ? '搜索暂时不可用,请从文档目录浏览。' : 'Search is unavailable. Browse the documentation index instead.';
results.append(message);
results.hidden = false;
}
};
input.addEventListener('input', () => { clearTimeout(timer); timer = setTimeout(show, 120); });
form.addEventListener('submit', (event) => { event.preventDefault(); clearTimeout(timer); show(); });
form.addEventListener('keydown', (event) => {
if (event.key === 'Escape') { results.hidden = true; input.focus(); }
if (event.key === 'ArrowDown' && event.target === input) { event.preventDefault(); results.querySelector('a')?.focus(); }
});
document.addEventListener('click', (event) => { if (!form.contains(event.target)) results.hidden = true; });
const query = new URLSearchParams(location.search).get('q');
if (query) { input.value = query; show(); }
});
if (matchMedia('(max-width: 720px)').matches) {
document.querySelectorAll('.docs-sidebar details').forEach((group) => { group.open = false; });
}
document.querySelectorAll('.docs-article pre, .legacy-page article pre').forEach((pre) => {
if (pre.querySelector('button')) return;
const text = pre.textContent.trim();
const button = document.createElement('button');
button.className = 'icon-button command-copy';
button.type = 'button';
const original = chinese ? '复制代码' : 'Copy code';
button.title = original;
button.setAttribute('aria-label', original);
const icon = document.createElement('img');
icon.src = document.body.dataset.copyIcon;
icon.alt = '';
button.append(icon);
button.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(text);
button.title = chinese ? '已复制' : 'Copied';
button.dataset.copied = 'true';
} catch {
button.title = chinese ? '复制失败,请选择代码' : 'Copy failed; select the code';
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(pre.querySelector('code') || pre);
selection.removeAllRanges(); selection.addRange(range);
}
button.setAttribute('aria-label', button.title);
setTimeout(() => { button.title = original; button.setAttribute('aria-label', original); delete button.dataset.copied; }, 2000);
});
pre.append(button);
});
const menu = document.querySelector('[data-menu-toggle]');
const navigation = document.querySelector('[data-primary-nav]');
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && menu?.getAttribute('aria-expanded') === 'true') {
menu.setAttribute('aria-expanded', 'false'); navigation.dataset.open = 'false'; menu.focus();
}
});
})();
+10 -3
View File
@@ -64,9 +64,16 @@
button.addEventListener('click', async () => {
const target = document.querySelector(button.dataset.copyTarget);
if (!target) return;
await navigator.clipboard.writeText(target.textContent.trim());
button.dataset.copied = 'true';
window.setTimeout(() => delete button.dataset.copied, 1400);
const original = button.title;
try {
await navigator.clipboard.writeText(target.textContent.trim());
button.dataset.copied = 'true';
button.title = document.body.dataset.language === 'zh' ? '已复制' : 'Copied';
} catch {
button.title = document.body.dataset.language === 'zh' ? '复制失败,请选择代码' : 'Copy failed; select the code';
}
button.setAttribute('aria-label', button.title);
window.setTimeout(() => { delete button.dataset.copied; button.title = original; button.setAttribute('aria-label', original); }, 1400);
});
});
})();
+57 -1
View File
@@ -17,6 +17,7 @@ from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoes
from legacy import normalize_document
from registry import load_registry, validate_registry
from selector import MATCH_WEIGHTS
from documentation import load_catalogue, doc_route, render_source, write_search_indexes
SITE_ROOT = Path(__file__).resolve().parent
@@ -139,7 +140,7 @@ def _page_context(
'assets': assets,
'github_repository_url': GITHUB_REPOSITORY_URL,
'github_url': '/go/github',
'docs_url': '/go/docs',
'docs_url': '/docs/' if language == 'zh' else '/en/docs/',
'releases_url': '/go/releases',
}
@@ -377,6 +378,61 @@ def build(output_dir: Path) -> dict[str, Any]:
'hreflang': context['peer_canonical'],
})
catalogue = load_catalogue()
for language in ('zh', 'en'):
prefix = '/en' if language == 'en' else ''
peer_prefix = '' if language == 'en' else '/en'
for index, entry in enumerate([None, *catalogue['pages']]):
route = f'{prefix}/docs/' if entry is None else doc_route(entry['slug'], language)
peer = f'{peer_prefix}/docs/' if entry is None else doc_route(entry['slug'], 'en' if language == 'zh' else 'zh')
page_title = entry[language] if entry else ('文档中心' if language == 'zh' else 'Documentation')
context = _page_context(
language=language, route=route, peer_route=peer,
title=f'{page_title} - FunASR', description=page_title,
date_modified=registry['verified'], navigation=navigation, assets=assets,
)
context.update({'catalogue': catalogue, 'doc_entry': entry})
if entry:
context.update(render_source(entry, language, catalogue))
context['previous'] = catalogue['pages'][index - 2] if index > 1 else None
context['following'] = catalogue['pages'][index] if index < len(catalogue['pages']) else None
_render_page(environment, 'docs.html', route_path(stage, route), context)
pages.append({'route': route, 'language': language, 'canonical': context['canonical'], 'hreflang': context['peer_canonical']})
# Reskin old article shells without maintaining another copy of their content.
for path in sorted(stage.rglob('*.html')):
soup = BeautifulSoup(path.read_text(encoding='utf-8'), 'html.parser')
old_nav = soup.select_one('nav[data-product-navigation]')
if old_nav is None:
continue
relative = path.relative_to(stage)
language = 'en' if relative.parts[0] == 'en' else 'zh'
route = legacy_route(relative)
peer = route[3:] if language == 'en' else f'/en{route}'
context = _page_context(language=language, route=route, peer_route=peer,
title='', description='', date_modified=registry['verified'], navigation=navigation, assets=assets)
shell = BeautifulSoup(environment.get_template('base.html').render(**context), 'html.parser')
old_nav.replace_with(shell.select_one('.site-header'))
soup.body['data-language'] = language
soup.body['data-route'] = route
soup.body['data-copy-icon'] = assets['lucide/copy.svg']
soup.body['class'] = list(soup.body.get('class', [])) + ['legacy-page']
for href in (assets['css/site.css'], assets['css/experience.css']):
link = soup.new_tag('link', rel='stylesheet', href=href)
soup.head.append(link)
for src in (assets['js/site.js'], assets['js/experience.js']):
script = soup.new_tag('script', src=src, defer=True)
soup.body.append(script)
footer = soup.find('footer')
if footer:
footer.replace_with(shell.select_one('.site-footer'))
# Article content and ids are untouched; tables gain a scroll container.
for table in soup.select('article table'):
wrapper = soup.new_tag('div', attrs={'class': 'table-wrap'})
table.wrap(wrapper)
path.write_text(str(soup), encoding='utf-8')
write_search_indexes(stage)
last_modified_by_route = {
entry['routes'][language]: entry['tested']['verified']
for entry in registry['deployments']
+6 -4
View File
@@ -216,6 +216,8 @@
},
"evidence": [
{"label": "H100 native FunASR validation record", "url": "https://github.com/modelscope/FunASR/blob/main/docs/vllm_native_funasr_validation.md"},
{"label": "official checkpoint registered upstream (merged PR #54944)", "url": "https://github.com/vllm-project/vllm/pull/54944"},
{"label": "official checkpoint at a4362c94 (separate from historical tests below)", "url": "https://huggingface.co/FunAudioLLM/Fun-ASR-Nano-2512-vllm/tree/a4362c943d48951f98ca2a62181cc028970270c5"},
{"label": "vLLM 0.27.1 release and CUDA 12.9 wheel", "url": "https://github.com/vllm-project/vllm/releases/tag/v0.27.1"},
{"label": "native FunASR model integration", "url": "https://github.com/vllm-project/vllm/pull/33247"},
{"label": "native FunASR hotword support", "url": "https://github.com/vllm-project/vllm/pull/39674"},
@@ -244,9 +246,9 @@
"name": "vLLM 原生 FunASR 服务",
"summary": "通过 vLLM 内置的 FunASR 架构,在 NVIDIA GPU 上提供兼容 OpenAI 的多语言转写与热词接口。",
"fit": ["需要标准 /v1/audio/transcriptions 接口", "Fun-ASR-Nano 批量或并发转写", "已有 NVIDIA GPU 和 Linux 服务环境"],
"not_fit": ["只接受官方发布权重、不能使用社区转换 checkpoint", "纯 CPU 或边缘设备", "尚未完成显存、并发与业务音频压测的公网服务"],
"not_fit": ["未经重新验证就直接替换下方固定版本 checkpoint", "纯 CPU 或边缘设备", "尚未完成显存、并发与业务音频压测的公网服务"],
"selection_reason": "vLLM 已原生注册 FunASR 架构,并提供 OpenAI 兼容转写、显存调度、并发和热词参数。",
"primary_limitation": "当前原生 vLLM 路径依赖社区转换 checkpoint;要求官方权重链路时使用官方 FunASR split-engine。实测仅覆盖单张 H100,必须按目标 GPU、音频、语言、热词与流量复测。",
"primary_limitation": "下方历史实测与命令使用社区转换 checkpoint。官方 FunAudioLLM/Fun-ASR-Nano-2512-vllm 已被 vLLM 上游登记(PR #54944);这不等于本页旧测试已覆盖新权重,切换时须重新验证。历史数据仅覆盖单张 H100,目标 GPU、音频、语言、热词与流量均须复测。",
"status_label": "社区验证",
"operations": ["固定 vLLM wheel、Torch、CUDA、模型 revision 与音频依赖", "等待 /health 就绪并完成预热后再采集容量数据", "记录队列等待、最终结果延迟、显存和热词命中率"],
"security": ["在 API 网关实现认证、TLS、限流和音频大小/时长限制", "worker 仅绑定内网地址,不直接暴露公网", "校验 wheel、模型 revision 和示例音频摘要,隔离模型缓存与上传临时目录"],
@@ -256,9 +258,9 @@
"name": "Native FunASR on vLLM",
"summary": "Use vLLM's built-in FunASR architecture for multilingual and hotword-aware transcription through an OpenAI-compatible API on NVIDIA GPUs.",
"fit": ["A standard /v1/audio/transcriptions endpoint", "Concurrent or batch Fun-ASR-Nano transcription", "An NVIDIA GPU Linux serving environment"],
"not_fit": ["Deployments that accept only officially published weights and cannot use a community conversion", "CPU-only or edge devices", "Internet-facing service before memory, concurrency, and workload tests"],
"not_fit": ["Replacing the pinned checkpoint below without revalidation", "CPU-only or edge devices", "Internet-facing service before memory, concurrency, and workload tests"],
"selection_reason": "vLLM natively registers the FunASR architecture and provides OpenAI-compatible transcription, memory scheduling, concurrency, and hotword parameters.",
"primary_limitation": "The native vLLM path currently depends on a community-converted checkpoint; use the official FunASR split-engine when an official-weight chain is required. Evidence covers one H100 only, so retest the target GPU, audio, languages, hotwords, and traffic.",
"primary_limitation": "The historical tests and commands below use a community-converted checkpoint. The official FunAudioLLM/Fun-ASR-Nano-2512-vllm checkpoint is now registered upstream in vLLM PR #54944; this does not extend the old test evidence to new weights. Revalidate any switch and the target GPU, audio, languages, hotwords, and traffic; historical measurements cover one H100 only.",
"status_label": "Community verified",
"operations": ["Pin the vLLM wheel, Torch, CUDA, model revision, and audio dependencies", "Wait for /health and warm the model before measuring capacity", "Measure queue time, final-result latency, memory, and hotword hit rate"],
"security": ["Put authentication, TLS, rate limits, and audio size/duration limits at the gateway", "Bind workers to a private address instead of exposing them directly", "Verify wheel, model revision, and sample digests; isolate model cache and temporary uploads"],
@@ -0,0 +1,24 @@
{
"groups": [
{"id": "start", "zh": "开始使用", "en": "Get started"},
{"id": "models", "zh": "模型与能力", "en": "Models & capabilities"},
{"id": "deploy", "zh": "部署与接口", "en": "Deploy & integrate"},
{"id": "operate", "zh": "评测与运维", "en": "Evaluate & operate"},
{"id": "ecosystem", "zh": "应用与生态", "en": "Applications & ecosystem"}
],
"pages": [
{"slug": "model-selection", "group": "start", "zh": "选择第一个模型", "en": "Choose your first model", "source_zh": "docs/model_selection_zh.md", "source_en": "docs/model_selection.md"},
{"slug": "command-line", "group": "start", "zh": "命令行转写", "en": "Command-line transcription", "source_zh": "docs/cli.md", "source_en": "docs/cli.md"},
{"slug": "migration", "group": "start", "zh": "从 Whisper 与云 API 迁移", "en": "Migrate from Whisper & cloud APIs", "source_zh": "docs/migration_from_whisper_zh.md", "source_en": "docs/migration_from_whisper.md"},
{"slug": "moss-transcribe-diarize", "group": "models", "zh": "MOSS 转写与说话人分离", "en": "MOSS transcription & diarization", "source_zh": "docs/moss_transcribe_diarize_zh.md", "source_en": "docs/moss_transcribe_diarize.md"},
{"slug": "deployment-matrix", "group": "deploy", "zh": "运行时与部署矩阵", "en": "Runtime & deployment matrix", "source_zh": "docs/deployment_matrix_zh.md", "source_en": "docs/deployment_matrix.md"},
{"slug": "vllm", "group": "deploy", "zh": "vLLM split-engine 推理", "en": "vLLM split-engine inference", "source_zh": "docs/vllm_guide_zh.md", "source_en": "docs/vllm_guide.md"},
{"slug": "native-vllm", "group": "deploy", "zh": "原生 vLLM 验证记录", "en": "Native vLLM validation record", "source_zh": "docs/vllm_native_funasr_validation.md", "source_en": "docs/vllm_native_funasr_validation.md"},
{"slug": "troubleshooting", "group": "operate", "zh": "安装与推理故障排查", "en": "Troubleshooting", "source_zh": "docs/troubleshooting_zh.md", "source_en": "docs/troubleshooting.md"},
{"slug": "benchmark-method", "group": "operate", "zh": "可复现性能评测", "en": "Reproducible performance tests", "source_zh": "docs/benchmark/rtf_reproducibility.md", "source_en": "docs/benchmark/rtf_reproducibility.md"},
{"slug": "realtime-benchmark", "group": "operate", "zh": "实时 WebSocket 压测", "en": "Realtime WebSocket benchmarking", "source_zh": "docs/benchmark/realtime_ws_benchmark.md", "source_en": "docs/benchmark/realtime_ws_benchmark.md"},
{"slug": "use-cases", "group": "ecosystem", "zh": "应用场景与示例", "en": "Use cases & examples", "source_zh": "docs/use_case_showcase_zh.md", "source_en": "docs/use_case_showcase.md"},
{"slug": "community", "group": "ecosystem", "zh": "社区集成", "en": "Community integrations", "source_zh": "docs/community_projects_zh.md", "source_en": "docs/community_projects.md"},
{"slug": "repositories", "group": "ecosystem", "zh": "四仓职责与贡献入口", "en": "Repositories & contribution paths", "source_zh": "docs/repository_roles_zh.md", "source_en": "docs/repository_roles.md"}
]
}
+1 -1
View File
@@ -6,7 +6,7 @@
{"id": "benchmarks", "zh": "性能", "en": "Benchmarks", "href_zh": "/benchmarks.html", "href_en": "/en/benchmarks.html"},
{"id": "ecosystem", "zh": "生态", "en": "Ecosystem", "href_zh": "/ecosystem.html", "href_en": "/en/ecosystem.html"},
{"id": "blog", "zh": "博客", "en": "Blog", "href_zh": "/blog/", "href_en": "/en/blog/"},
{"id": "docs", "zh": "文档", "en": "Docs", "href_zh": "https://github.com/modelscope/FunASR/tree/main/docs", "href_en": "https://github.com/modelscope/FunASR/tree/main/docs"},
{"id": "docs", "zh": "文档", "en": "Docs", "href_zh": "/docs/", "href_en": "/en/docs/"},
{"id": "donors", "zh": "功德榜", "en": "Donors", "href_zh": "/donors.html", "href_en": "/en/donors.html"}
]
}
+101
View File
@@ -0,0 +1,101 @@
"""Render the documentation catalogue from repository-owned Markdown sources."""
from __future__ import annotations
import json
import posixpath
import re
from pathlib import Path
from urllib.parse import quote, unquote, urlsplit, urlunsplit
from bs4 import BeautifulSoup
from markdown import Markdown
SITE_ROOT = Path(__file__).resolve().parent
REPO_ROOT = SITE_ROOT.parents[1]
GITHUB = 'https://github.com/modelscope/FunASR/blob/main/'
RAW = 'https://raw.githubusercontent.com/modelscope/FunASR/main/'
def source_slug(value: str, separator: str) -> str:
"""Preserve Unicode and repeated spaces used by source Markdown fragments."""
return re.sub(r'[^\w\- ]', '', value.lower()).replace(' ', separator)
def load_catalogue() -> dict:
catalogue = json.loads((SITE_ROOT / 'data/documentation.json').read_text())
for entry in catalogue['pages']:
for language in ('zh', 'en'):
path = (REPO_ROOT / entry[f'source_{language}']).resolve()
if not path.is_relative_to(REPO_ROOT) or not path.is_file():
raise ValueError(f'Missing documentation source: {path}')
return catalogue
def doc_route(slug: str, language: str) -> str:
return f'/{"en/" if language == "en" else ""}docs/{slug}.html'
def render_source(entry: dict, language: str, catalogue: dict) -> dict:
relative = entry[f'source_{language}']
parser = Markdown(extensions=['extra', 'toc', 'sane_lists'],
extension_configs={'toc': {'slugify': source_slug}})
soup = BeautifulSoup(parser.convert((REPO_ROOT / relative).read_text()), 'html.parser')
local_routes = {
page[f'source_{language}']: doc_route(page['slug'], language)
for page in catalogue['pages']
}
for element, attr in [('a', 'href'), ('img', 'src')]:
for node in soup.select(f'{element}[{attr}]'):
value = str(node[attr])
parsed = urlsplit(value)
if parsed.scheme or parsed.netloc or not parsed.path or parsed.path.startswith('/'):
continue
resolved = posixpath.normpath(posixpath.join(posixpath.dirname(relative), unquote(parsed.path)))
if resolved.startswith('../'):
raise ValueError(f'Documentation link escapes repository: {relative}: {value}')
destination = local_routes.get(resolved) if element == 'a' else None
if destination is None:
destination = (RAW if element == 'img' else GITHUB) + quote(resolved, safe='/')
node[attr] = urlunsplit((*urlsplit(destination)[:3], parsed.query, parsed.fragment))
heading = soup.find('h1')
if heading:
# Keep source anchor names when the document title is moved into the shell.
heading.name = 'div'
heading['class'] = ['source-title-anchor']
heading.clear()
for index, code in enumerate(soup.select('pre')):
code['id'] = code.get('id', f'code-{index}')
return {
'content_html': str(soup),
'toc_html': parser.toc,
'source_url': GITHUB + quote(relative, safe='/'),
'source_english_only': language == 'zh' and relative == entry['source_en'],
}
def write_search_indexes(stage: Path) -> None:
indexes: dict[str, list] = {'zh': [], 'en': []}
for path in sorted(stage.rglob('*.html')):
if path.name == '404.html':
continue
soup = BeautifulSoup(path.read_text(), 'html.parser')
heading = soup.select_one('h1')
canonical = soup.select_one('link[rel="canonical"]')
if heading is None or canonical is None:
continue
url = urlsplit(canonical['href']).path
if url in {'/', '/en/'} or '/static/' in url or '/voice/' in url:
continue
content = soup.select_one('.docs-article') or soup.select_one('main') or soup.select_one('article') or soup.body
if content is None:
continue
for node in content.select('script, style, nav, .site-header, .site-footer'):
node.decompose()
indexes['en' if url.startswith('/en/') else 'zh'].append({
'title': heading.get_text(' ', strip=True),
'url': url,
'text': content.get_text(' ', strip=True)[:24000],
})
for language, entries in indexes.items():
(stage / f'search-{language}.json').write_text(json.dumps(entries, ensure_ascii=False))
@@ -1,3 +1,4 @@
Jinja2==3.1.6
beautifulsoup4==4.13.4
pytest==8.4.1
Markdown==3.8.2
+3 -1
View File
@@ -14,6 +14,7 @@
<meta property="og:description" content="{{ description }}">
<meta property="og:url" content="{{ canonical }}">
<link rel="stylesheet" href="{{ assets['css/site.css'] }}">
<link rel="stylesheet" href="{{ assets['css/experience.css'] }}">
<script type="application/ld+json">{{ {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
@@ -26,7 +27,7 @@
'license': 'https://github.com/modelscope/FunASR/blob/main/LICENSE'
} | tojson }}</script>
</head>
<body data-language="{{ language }}" data-route="{{ route }}">
<body data-language="{{ language }}" data-route="{{ route }}" data-copy-icon="{{ assets['lucide/copy.svg'] }}">
<a class="skip-link" href="#main">{{ '跳到主要内容' if language == 'zh' else 'Skip to main content' }}</a>
<header class="site-header">
<a class="brand" href="{{ '/' if language == 'zh' else '/en/' }}" aria-label="FunASR {{ '首页' if language == 'zh' else 'home' }}">
@@ -75,5 +76,6 @@
</div>
</footer>
<script src="{{ assets['js/site.js'] }}" defer></script>
<script src="{{ assets['js/experience.js'] }}" defer></script>
</body>
</html>
@@ -0,0 +1,42 @@
{% extends "base.html" %}
{% block content %}
<div class="docs-layout">
<aside class="docs-sidebar">
<a class="docs-home" href="{{ '/docs/' if language == 'zh' else '/en/docs/' }}">{{ '文档中心' if language == 'zh' else 'Documentation' }}</a>
{% for group in catalogue.groups %}
<details open>
<summary>{{ group[language] }}</summary>
<nav aria-label="{{ group[language] }}">
{% for page in catalogue.pages if page.group == group.id %}
<a href="{{ ('/docs/' if language == 'zh' else '/en/docs/') ~ page.slug ~ '.html' }}"{% if doc_entry and doc_entry.slug == page.slug %} aria-current="page"{% endif %}>{{ page[language] }}</a>
{% endfor %}
</nav>
</details>
{% endfor %}
<a href="{{ '/deploy/' if language == 'zh' else '/en/deploy/' }}">{{ '部署手册' if language == 'zh' else 'Deployment guides' }} &rarr;</a>
</aside>
<div class="docs-body">
<nav class="breadcrumbs" aria-label="{{ '面包屑' if language == 'zh' else 'Breadcrumbs' }}"><a href="{{ '/' if language == 'zh' else '/en/' }}">FunASR</a><span>/</span><a href="{{ '/docs/' if language == 'zh' else '/en/docs/' }}">{{ '文档' if language == 'zh' else 'Docs' }}</a>{% if doc_entry %}<span>/</span><span>{{ doc_entry[language] }}</span>{% endif %}</nav>
{% if doc_entry %}
<header class="docs-title"><h1>{{ doc_entry[language] }}</h1><a class="source-link" data-source-link href="{{ source_url }}">{{ '查看 / 编辑源文档' if language == 'zh' else 'View / edit source' }} &nearr;</a></header>
{% if source_english_only %}<p class="source-note">{{ '本篇目前使用英文源文档;导航保留中文。' }}</p>{% endif %}
<article class="docs-article">{{ content_html | safe }}</article>
<nav class="docs-next" aria-label="{{ '相邻文档' if language == 'zh' else 'Adjacent documents' }}">
{% if previous %}<a href="{{ ('/docs/' if language == 'zh' else '/en/docs/') ~ previous.slug ~ '.html' }}">&larr; {{ previous[language] }}</a>{% endif %}
{% if following %}<a href="{{ ('/docs/' if language == 'zh' else '/en/docs/') ~ following.slug ~ '.html' }}">{{ following[language] }} &rarr;</a>{% endif %}
</nav>
{% else %}
<header class="docs-title"><p class="eyebrow">FUNASR DOCUMENTATION</p><h1>{{ '从第一次转写,到生产部署。' if language == 'zh' else 'From first transcript to production.' }}</h1><p>{{ '模型能力、可运行示例和部署边界,在同一个地方。' if language == 'zh' else 'Model capabilities, runnable examples, and deployment boundaries in one place.' }}</p></header>
{% include 'search.html' %}
<div class="docs-groups">
{% for group in catalogue.groups %}
<section><span class="chapter-number">0{{ loop.index }}</span><h2>{{ group[language] }}</h2>
{% for page in catalogue.pages if page.group == group.id %}<a href="{{ ('/docs/' if language == 'zh' else '/en/docs/') ~ page.slug ~ '.html' }}">{{ page[language] }} <span aria-hidden="true">&rarr;</span></a>{% endfor %}
</section>
{% endfor %}
</div>
{% endif %}
</div>
{% if doc_entry %}<aside class="docs-toc"><strong>{{ '本页目录' if language == 'zh' else 'On this page' }}</strong>{{ toc_html | safe }}</aside>{% endif %}
</div>
{% endblock %}
+37 -24
View File
@@ -1,37 +1,32 @@
{% extends "base.html" %}
{% block content %}
<section class="hero" data-section="hero">
<img class="hero-image" src="{{ assets['images/deployment-topology.png'] }}" alt="{{ '连接 GPU、CPU、边缘设备和 API 客户端的私有部署拓扑' if language == 'zh' else 'Private deployment topology connecting GPU, CPU, edge devices, and API clients' }}">
<div class="hero-shade" aria-hidden="true"></div>
<img class="hero-image" src="{{ assets['images/speech-waveform.png'] }}" alt="{{ '公开中文语音样本的实际波形' if language == 'zh' else 'Waveform of the public Chinese speech sample' }}">
<div class="hero-content">
<p class="eyebrow">{{ '开源语音基础设施' if language == 'zh' else 'Open speech infrastructure' }}</p>
<p class="eyebrow">OPEN SOURCE / SPEECH INTELLIGENCE</p>
<h1>FunASR</h1>
<p class="hero-copy">
{{ '可私有化部署的语音智能基础设施。覆盖 GPU 高吞吐、实时流式服务、OpenAI 兼容 API,以及 CPU 与边缘独立运行。' if language == 'zh' else 'Private-deployment speech infrastructure for high-throughput GPU inference, realtime streaming, OpenAI-compatible APIs, and standalone CPU or edge use.' }}
{{ '让语音,成为可用的信息。' if language == 'zh' else 'Turn speech into useful information.' }}
</p>
<p class="hero-description">{{ '语音识别、时间戳、说话人分离。从一行 Python,到你自己的生产服务。' if language == 'zh' else 'Recognition, timestamps, and diarization. From a line of Python to your own production service.' }}</p>
<div class="hero-actions">
<a class="button button-primary" href="#deployment-selector">
{{ '选择部署方案' if language == 'zh' else 'Choose a deployment' }}
<a class="button button-primary" href="{{ '/quickstart.html' if language == 'zh' else '/en/quickstart.html' }}">
{{ '开始构建' if language == 'zh' else 'Start building' }}
<img src="{{ assets['lucide/arrow-right.svg'] }}" alt="">
</a>
<a class="button button-secondary" href="{{ '/quickstart.html' if language == 'zh' else '/en/quickstart.html' }}">
<a class="button button-secondary" href="#deployment-selector">
<img src="{{ assets['lucide/terminal.svg'] }}" alt="">
{{ '5 分钟验证' if language == 'zh' else 'Verify in five minutes' }}
{{ '选择部署方案' if language == 'zh' else 'Choose a deployment' }}
</a>
</div>
<dl class="hero-proof">
<div><dt>{{ '许可' if language == 'zh' else 'License' }}</dt><dd>Apache-2.0</dd></div>
<div><dt>{{ '接口' if language == 'zh' else 'API' }}</dt><dd>OpenAI compatible</dd></div>
<div><dt>{{ '验证日期' if language == 'zh' else 'Verified' }}</dt><dd>{{ verified }}</dd></div>
</dl>
<div class="audio-proof"><div><span class="sample-label">{{ '公开样本 · 中文' if language == 'zh' else 'PUBLIC SAMPLE · CHINESE' }}</span><p>欢迎大家来体验达摩院推出的语音识别模型。</p></div><audio controls preload="metadata" src="{{ assets['audio/speech-sample.wav'] }}" aria-label="{{ '播放公开中文样本' if language == 'zh' else 'Play public Chinese sample' }}"></audio></div>
</div>
</section>
<section class="section repository-router" data-section="repository-router">
<div class="section-heading compact-heading">
<p class="eyebrow">{{ '生态路由' if language == 'zh' else 'Ecosystem routing' }}</p>
<h2>{{ '按工作流进入专用仓库' if language == 'zh' else 'Choose the focused repository' }}</h2>
<p>{{ '部署路径适用于 FunASR 运行时;当需求由模型能力或视频工作流定义时,直接进入对应仓库。' if language == 'zh' else 'Deployment paths serve the FunASR runtime. When the requirement is defined by model capability or a video workflow, start in the focused repository.' }}</p>
<p class="eyebrow">{{ '一个工具箱,多种可能' if language == 'zh' else 'ONE TOOLKIT. MANY POSSIBILITIES.' }}</p>
<h2>{{ '为你的应用,选对模型。' if language == 'zh' else 'The right model for your application.' }}</h2>
</div>
<div class="repository-router-links">
<a href="/go/fun-asr">
@@ -49,14 +44,30 @@
<strong>{{ '从语音定位到视频剪辑' if language == 'zh' else 'From speech localization to video clipping' }}</strong>
<img src="{{ assets['lucide/arrow-right.svg'] }}" alt="">
</a>
<a href="{{ '/docs/moss-transcribe-diarize.html' if language == 'zh' else '/en/docs/moss-transcribe-diarize.html' }}">
<span>MOSS <small>OpenMOSS</small></span>
<strong>{{ '长音频转写与匿名说话人' if language == 'zh' else 'Long-form transcription & anonymous speakers' }}</strong>
<img src="{{ assets['lucide/arrow-right.svg'] }}" alt="">
</a>
</div>
</section>
<section class="section knowledge-entry" data-section="documentation">
<div class="section-heading compact-heading"><p class="eyebrow">{{ '开发者文档' if language == 'zh' else 'DEVELOPER DOCUMENTATION' }}</p><h2>{{ '从问题,直接到答案。' if language == 'zh' else 'Find your next step.' }}</h2></div>
{% include 'search.html' %}
<div class="learning-paths">
<a href="{{ '/docs/model-selection.html' if language == 'zh' else '/en/docs/model-selection.html' }}"><span>01</span><strong>{{ '选模型' if language == 'zh' else 'Choose a model' }}</strong><small>{{ '能力、语种与时间戳' if language == 'zh' else 'Capabilities, languages & timestamps' }}</small></a>
<a href="{{ '/quickstart.html' if language == 'zh' else '/en/quickstart.html' }}"><span>02</span><strong>{{ '跑通推理' if language == 'zh' else 'Run inference' }}</strong><small>Python / CLI / API</small></a>
<a href="{{ '/deploy/' if language == 'zh' else '/en/deploy/' }}"><span>03</span><strong>{{ '部署服务' if language == 'zh' else 'Deploy a service' }}</strong><small>vLLM / llama.cpp / Docker</small></a>
<a href="{{ '/docs/troubleshooting.html' if language == 'zh' else '/en/docs/troubleshooting.html' }}"><span>04</span><strong>{{ '稳定运行' if language == 'zh' else 'Operate reliably' }}</strong><small>{{ '评测、监控与故障排查' if language == 'zh' else 'Evaluation, monitoring & troubleshooting' }}</small></a>
</div>
</section>
<section id="deployment-selector" class="section selector-section" data-section="deployment-selector">
<div class="section-heading">
<p class="eyebrow">{{ '部署选择器' if language == 'zh' else 'Deployment selector' }}</p>
<h2>{{ '从工作负载开始,不从框架名开始' if language == 'zh' else 'Start with the workload, not a framework name' }}</h2>
<p>{{ '选择业务形态、硬件和首要目标,得到一条有明确限制的推荐路径。' if language == 'zh' else 'Choose the workload, hardware, and primary goal to get one recommendation with an explicit limitation.' }}</p>
<h2>{{ '在你的硬件上,找到合适的运行时。' if language == 'zh' else 'A runtime that fits your hardware.' }}</h2>
<p>{{ '从本地 CPU 到 GPU 集群,每条路径都有安装步骤、测试记录和已知限制。' if language == 'zh' else 'From a local CPU to GPU infrastructure, with setup steps, test records, and known limitations.' }}</p>
</div>
<form class="selector" data-deployment-selector>
<fieldset>
@@ -108,8 +119,9 @@
<section class="section matrix-section" data-section="deployment-matrix">
<div class="section-heading compact-heading">
<p class="eyebrow">{{ '完整矩阵' if language == 'zh' else 'Full matrix' }}</p>
<h2>{{ '无 JavaScript 也能完成选择' if language == 'zh' else 'The full decision path works without JavaScript' }}</h2>
<h2>{{ '运行时一览' if language == 'zh' else 'Explore the runtimes' }}</h2>
</div>
<details class="matrix-disclosure"><summary>{{ '查看全部部署路径' if language == 'zh' else 'Compare all deployment paths' }} <span>{{ deployments | length }}</span></summary>
<div class="table-wrap">
<table>
<thead><tr>
@@ -132,6 +144,7 @@
</tbody>
</table>
</div>
</details>
</section>
<section class="section api-section" data-section="api-contract">
@@ -142,13 +155,13 @@
</div>
<div class="terminal" aria-label="{{ 'OpenAI 兼容 API 验证命令' if language == 'zh' else 'OpenAI-compatible API verification commands' }}">
<div class="terminal-bar"><span></span><span></span><span></span><strong>localhost:8000</strong></div>
<pre><code><span class="prompt">$</span> curl -fsS http://localhost:8000/health
{"status":"ok"}
<pre><code>curl -fsS http://localhost:8000/health
# {"status":"ok"}
<span class="prompt">$</span> curl -fsS http://localhost:8000/v1/models
{"data":[{"id":"sensevoice"}]}
curl -fsS http://localhost:8000/v1/models
# {"data":[{"id":"sensevoice"}]}
<span class="prompt">$</span> curl http://localhost:8000/v1/audio/transcriptions \
curl -fsS http://localhost:8000/v1/audio/transcriptions \
-F [email protected] -F model=sensevoice</code></pre>
<button class="icon-button copy-button" type="button" data-copy-target=".terminal pre" title="{{ '复制命令' if language == 'zh' else 'Copy commands' }}">
<img src="{{ assets['lucide/copy.svg'] }}" alt="">
@@ -0,0 +1,8 @@
<form class="doc-search" data-doc-search role="search" action="{{ '/docs/' if language == 'zh' else '/en/docs/' }}">
<label for="docs-query">{{ '搜索文档' if language == 'zh' else 'Search documentation' }}</label>
<div class="search-field">
<input id="docs-query" name="q" type="search" autocomplete="off" placeholder="{{ '模型、部署、时间戳、MOSS…' if language == 'zh' else 'Models, deployment, timestamps, MOSS…' }}" aria-controls="search-results">
<button class="icon-button" type="submit" title="{{ '搜索' if language == 'zh' else 'Search' }}"><img src="{{ assets['lucide/arrow-right.svg'] }}" alt=""></button>
</div>
<div id="search-results" class="search-results" hidden aria-live="polite"></div>
</form>
@@ -0,0 +1,55 @@
import { expect, test } from '@playwright/test';
for (const width of [320, 390, 768, 1440, 1920]) {
test(`documentation and legacy layouts fit ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 900 });
const errors: string[] = [];
page.on('pageerror', error => errors.push(error.message));
for (const route of ['/docs/', '/en/docs/', '/docs/moss-transcribe-diarize.html', '/models.html', '/blog/', '/donors.html']) {
await page.goto(route);
await expect(page.locator('[data-primary-nav]')).toHaveCount(1);
expect(await page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(1);
}
expect(errors).toEqual([]);
});
}
test('search is bilingual, keyboard accessible, and handles missing results', async ({ page }) => {
for (const prefix of ['', '/en']) {
await page.goto(`${prefix}/docs/`);
const query = page.locator('[data-doc-search] input');
await query.fill('MOSS');
const first = page.locator('.search-results a').first();
await expect(first).toBeVisible();
await query.press('ArrowDown');
await expect(first).toBeFocused();
await expect(first).toHaveAttribute('href', new RegExp(`^${prefix}/`));
await query.fill('zzzz-no-document-match-48291');
await expect(page.locator('.search-results')).toContainText(prefix ? 'No matching documents' : '没有找到');
}
});
test('source-derived code copies cleanly and legacy menu closes with Escape', async ({ page, context }) => {
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
await page.goto('/docs/moss-transcribe-diarize.html');
const block = page.locator('.docs-article pre').first();
const expected = await block.locator('code').innerText();
await block.locator('button').click();
expect((await page.evaluate(() => navigator.clipboard.readText())).trim()).toBe(expected.trim());
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('/models.html');
const menu = page.locator('[data-menu-toggle]');
await menu.press('Enter');
await expect(menu).toHaveAttribute('aria-expanded', 'true');
await page.keyboard.press('Escape');
await expect(menu).toHaveAttribute('aria-expanded', 'false');
});
test('public speech sample is playable and waveform has actual pixels', async ({ page }) => {
await page.goto('/');
await expect.poll(() => page.locator('audio').evaluate((audio: HTMLAudioElement) => audio.duration)).toBeGreaterThan(5);
await page.locator('audio').evaluate((audio: HTMLAudioElement) => audio.play());
await expect.poll(() => page.locator('audio').evaluate((audio: HTMLAudioElement) => audio.currentTime)).toBeGreaterThan(0);
await page.locator('audio').evaluate((audio: HTMLAudioElement) => audio.pause());
expect(await page.locator('.hero-image').evaluate((image: HTMLImageElement) => image.naturalWidth)).toBeGreaterThan(1000);
});
@@ -6,7 +6,7 @@ const python = process.env.SITE_PYTHON || 'python3';
export default defineConfig({
testDir: '.',
testMatch: 'product-site.spec.ts',
testMatch: '*.spec.ts',
timeout: 45_000,
workers: 1,
reporter: 'line',
@@ -438,7 +438,7 @@ test('llama.cpp blog heading clears fixed navigation on mobile', async ({ page }
await page.goto('/blog/funasr-llama-cpp-whisper-cpp-alternative.html');
const layout = await page.evaluate(() => {
const navigation = document.querySelector<HTMLElement>('nav.nav');
const navigation = document.querySelector<HTMLElement>('.site-header');
const heading = document.querySelector<HTMLElement>('h1');
return {
overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
@@ -463,7 +463,7 @@ test('legacy comparison pages keep accurate claims and fit mobile', async ({ pag
const audit = await page.evaluate(() => ({
overflow: document.documentElement.scrollWidth - document.documentElement.clientWidth,
text: document.body.innerText,
trackedGitHub: document.querySelector('.nav-btn')?.getAttribute('href'),
trackedGitHub: document.querySelector('.site-header a[aria-label="GitHub"]')?.getAttribute('href'),
}));
expect(audit.overflow).toBeLessThanOrEqual(1);
+2 -1
View File
@@ -90,7 +90,8 @@ def test_visible_repository_links_use_fixed_conversion_routes(tmp_path):
json_ld = json.loads(soup.select_one('script[type="application/ld+json"]').string)
assert github_hrefs == {'/go/github'}
assert soup.select_one('.site-footer a[href="/go/docs"]')
docs_route = '/en/docs/' if relative.startswith('en/') else '/docs/'
assert soup.select_one(f'.site-footer a[href="{docs_route}"]')
assert soup.select_one('.site-footer a[href="/go/releases"]')
assert json_ld['codeRepository'] == 'https://github.com/modelscope/FunASR'
@@ -0,0 +1,88 @@
from pathlib import Path
import json
import sys
from urllib.parse import unquote
from bs4 import BeautifulSoup
import pytest
SITE_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(SITE_ROOT))
from build import build
@pytest.fixture(scope='module')
def output(tmp_path_factory):
path = tmp_path_factory.mktemp('documentation')
build(path)
return path
def test_bilingual_docs_have_navigation_and_real_source_content(output):
for prefix in ('', 'en/'):
index = BeautifulSoup((output / prefix / 'docs/index.html').read_text(), 'html.parser')
assert index.select_one('[data-doc-search]')
assert index.select_one('a[href$="/docs/moss-transcribe-diarize.html"]')
moss = BeautifulSoup((output / prefix / 'docs/moss-transcribe-diarize.html').read_text(), 'html.parser')
assert moss.select_one('.docs-sidebar')
assert moss.select_one('.docs-toc a[href^="#"]')
assert 'OpenMOSS' in moss.select_one('.docs-article').get_text()
assert moss.select_one('.docs-article pre code')
assert moss.select_one('[data-source-link]')['href'].endswith('.md')
def test_search_index_is_bilingual_and_links_to_emitted_pages(output):
for language in ('zh', 'en'):
rows = json.loads((output / f'search-{language}.json').read_text())
assert len(rows) >= 12
assert any('MOSS' in row['title'] for row in rows)
for row in rows:
path = row['url'].split('#')[0].lstrip('/')
if path.endswith('/'):
path += 'index.html'
assert (output / path).is_file(), row['url']
def test_legacy_articles_and_new_pages_share_versioned_design(output):
for relative in ('index.html', 'models.html', 'blog/index.html', 'donors.html', 'deploy/vllm.html'):
soup = BeautifulSoup((output / relative).read_text(), 'html.parser')
assert soup.select_one('link[href*="experience."][href$=".css"]'), relative
assert soup.select_one('[data-primary-nav]'), relative
links = soup.select('[data-primary-nav] a')
assert links[-1]['href'].endswith('donors.html')
home = (output / 'index.html').read_text()
assert 'data-doc-search' in home
assert 'Apache-2.0</dd>' not in home
def test_documentation_links_are_mapped_to_local_pages_or_repository(output):
soup = BeautifulSoup((output / 'docs/model-selection.html').read_text(), 'html.parser')
for link in soup.select('.docs-article a[href]'):
href = link['href']
assert href.startswith(('/', '#', 'https://', 'http://', 'mailto:')), href
def test_source_fragment_links_keep_unicode_and_punctuation_boundaries(output):
for prefix in ('', 'en/'):
for path in (output / prefix / 'docs').glob('*.html'):
soup = BeautifulSoup(path.read_text(), 'html.parser')
ids = {node['id'] for node in soup.select('[id]')}
for link in soup.select('.docs-article a[href^="#"]'):
assert unquote(link['href'][1:]) in ids, (path, link['href'])
def test_section_based_quickstarts_are_searchable(output):
for language, route in (('zh', '/quickstart.html'), ('en', '/en/quickstart.html')):
rows = json.loads((output / f'search-{language}.json').read_text())
assert any(row['url'] == route for row in rows)
def test_github_pages_hubs_have_task_navigation_and_search_handoff():
root = SITE_ROOT.parents[1]
for relative, destination in (('index.html', '/en/docs/'), ('zh/index.html', '/docs/')):
soup = BeautifulSoup((root / 'gh-pages-output' / relative).read_text(), 'html.parser')
assert len(soup.select('.chapters section')) == 4
assert soup.select_one('form[role="search"]')['action'] == 'https://www.funasr.com' + destination
assert soup.select_one('a[href$="moss-transcribe-diarize.html"]')
assert soup.select_one('a[href$="api.html"]')
assert '170x' not in soup.get_text()
+6 -6
View File
@@ -74,13 +74,13 @@ def test_homepage_routes_each_project_by_workload(built_site, relative, markers)
(
(
'index.html',
'按工作流进入专用仓库',
('/go/fun-asr', '/go/sensevoice', '/go/funclip'),
'为你的应用,选对模型。',
('/go/fun-asr', '/go/sensevoice', '/go/funclip', '/docs/moss-transcribe-diarize.html'),
),
(
'en/index.html',
'Choose the focused repository',
('/go/fun-asr', '/go/sensevoice', '/go/funclip'),
'The right model for your application.',
('/go/fun-asr', '/go/sensevoice', '/go/funclip', '/en/docs/moss-transcribe-diarize.html'),
),
),
)
@@ -349,8 +349,8 @@ def test_old_llama_routes_point_to_product_pages(built_site):
):
soup = read_soup(built_site / relative)
assert soup.select_one('link[rel="canonical"]')['href'].endswith(expected)
assert soup.select_one('.nav-links a[href$="/deploy/"]') or soup.select_one(
'.nav-links a[href$="/en/deploy/"]'
assert soup.select_one('[data-primary-nav] a[href$="/deploy/"]') or soup.select_one(
'[data-primary-nav] a[href$="/en/deploy/"]'
)
@@ -93,7 +93,9 @@ def test_vllm_contract_tracks_native_funasr_release_and_h100_validation(valid_re
)
limitation = entry['translations']['en']['primary_limitation'].lower()
assert 'community-converted checkpoint' in limitation
assert 'official funasr split-engine' in limitation
assert 'official funaudiollm/fun-asr-nano-2512-vllm' in limitation
assert 'does not extend the old test evidence' in limitation
assert 'https://github.com/vllm-project/vllm/pull/54944' in evidence_urls
def test_moss_transcribe_diarize_contract_tracks_third_party_upstream(valid_registry):