Skip to content

Commit 3fd39dc

Browse files
patrickjchenCodingNinja2020kigner
authored
Add WebUi (Adopted from https://github.com/kigner/audio.cpp-webui, add support for Linux) (#87)
* webui: add the Gradio WebUI, cross-platform and English-first The WebUI comes from kigner's fork and is the bulk of the logic here: https://github.com/kigner/audio.cpp-webui.git (merged at 8924917). It is a Gradio UI over audiocpp_server that loads one model at a time on demand, with model management, background downloads, per-family request profiles, VRAM budgeting and adaptive chunking for TTS/ASR/VC, and a dialogue mode that chains diarization into ASR. All of that is kigner's work and is carried over as-is. That fork targets Windows only and its interface is Chinese only. What this commit adds is the cross-platform and localization work on top: Cross-platform: - Resolve the server binary by OS convention (.exe only on Windows) instead of assuming audiocpp_server.exe, and name run_server.sh/.bat per platform in the messages that mention it. - Find from-source builds in either layout: build/<os>-<backend>-<type>/bin, where the backend is in the directory name, and a plain `cmake -B build` -> build/bin, which says nothing about the backend and is classified from its CMakeCache (GGML_CUDA:BOOL=ON). Without the second, a stock Linux dev tree found no binary at all and SERVER_EXE pointed at a path that did not exist. - Add run_webui.sh as the POSIX counterpart of run_webui.bat. Localization, English first: - Strings move to webui/locales/<lang>.json, chosen by AUDIOCPP_LANG (default en) or the language picker in the UI. en.json is the per-key fallback, so a partial translation degrades to English rather than showing raw keys, and an unknown or malformed locale warns and falls back instead of taking the UI down. Adding a language is dropping in a file. zh.json keeps the fork's original Chinese. - Catalog entries hold English display_name/input_hint; a locale overrides any of them via catalog.<id>.<field>, which is how zh.json restores the Chinese model names. Without this the catalog's own strings leaked into the English UI. - t() applies str.format only when given arguments, so a translation can reorder placeholders, and a bad one falls back rather than raising. The picker switches language in place. Gradio bakes each label in when it builds the Blocks tree and cannot rebuild it in another language, so switching means re-labelling every component: a registry maps each component property back to the locale key that produced it, and the handler turns that into one update each. The registry is derived rather than declared. Every localized string already goes through t(), so a component's English text identifies its key by reverse lookup, keeping ~120 call sites free of registration boilerplate. Two cases reverse lookup cannot see, and how they are handled: - Interpolated labels never match their template ("Model list (task=tts)" vs "Model list (task={task})"), so they register explicitly via i18n_register(). - Several keys sharing one English string are ambiguous; _english_key_index() checks whether any locale translates them differently and drops those, leaving the component on its current label rather than risking the wrong one. Switching is process-wide, not per session: Gradio serves one Blocks object to all connections, which suits a local single-user tool. Values the server parses stay English while their labels localize, and the TTS/Voice Design sample texts are only re-localized while untouched, so a switch cannot discard typed input. Gradio's own widget text ("Click to Upload", ...) is compiled into its frontend bundle and picked from the browser locale, so it follows the browser rather than this picker and is left alone; the README says so. Fixed while localizing: - Two loops named their variable `t`, shadowing the translator for the rest of their scope. - The ASR language list built labels as "<localized> <English>", which read as a stutter once the localized name was itself English ("Chinese Chinese"). Comments and docstrings in webui.py are still Chinese and are left for a later pass. model_manager: fetch HF files via huggingface_hub, kigner's fix. Xet-backed repos redirect to a CDN that rejects plain GETs against the resolve URL, so only the hub client can pull their weights. Co-Authored-By: kigner <aidiscovery2045@proton.me> * webui: rebase on upstream 3-language UI, keep Linux support The upstream author (kigner/audio.cpp-webui) rewrote the i18n layer and added ~1200 lines of features since our fork point. Their scheme is inline _t(zh, en) calls with Traditional Chinese generated from the Simplified source by OpenCC, which is mutually exclusive with our locales/*.json key lookup. Take theirs as the base and re-apply our Linux work on top, rather than hand- porting every new string into a locale file. i18n: - Adopt webui/ui_i18n.py; drop webui/locales/{en,zh}.json and the t()/registry code they fed. English/Simplified/Traditional all now come from one source. - Default to English (was Chinese) and reorder the picker to match. - AUDIOCPP_LANG supplies the default only when nothing has been picked in the UI yet; a saved pick wins, so a stale env var can't defeat the picker. Accepts the spellings people actually write (zh_TW, zh-CN, english, ...). Linux: - Re-apply EXE_SUFFIX / SERVER_EXE_NAME / SERVER_LAUNCHER and the from-source build discovery (_cmake_cache_backend, _discover_dev_bin_dirs), which also fixes _find_gguf_exe: upstream hardcoded build/windows-*-release, so it could never find a Linux converter build. - ffmpeg lookup and the "gguf converter not found" messages no longer name .exe unconditionally. - requirements.txt: mark pywin32 and the pythonnet/pywebview stack sys_platform == "win32" so the file installs on Linux. model_manager: the two branches changed the same download path for different reasons, so keep both. Upstream's resumable .part/Range downloads and Windows rename-retry stay; snapshot fetches route through hf_hub_download again, which is the only way to pull Xet-backed repos (upstream already ships hf-xet but never wired it in). Added prune_hf_local_dir_cache so the hub client's .cache/huggingface bookkeeping isn't promoted into installed model dirs. Tests: 20 pass (test_ui_i18n updated for the English default, plus new cases for the env-var precedence rule and alias normalization). test_realtime_pipeline.py is untouched and still needs torch to run. * webui: don't mask a missing opencc behind the ui_i18n import fallback The try/except that falls back to `webui.ui_i18n` for the test harness also swallowed ImportErrors raised *inside* ui_i18n — most notably a missing opencc — and reported them as "No module named 'webui.ui_i18n'; 'webui' is not a package", which points at the wrong problem. Only fall back when the unresolved name is ui_i18n/webui itself; re-raise anything else so a missing dependency names itself. * webui: move on-demand server to port 8088 to avoid an 8080 clash The WebUI's on-demand audiocpp_server defaulted to 8080, which on this setup collides with a Windows-side listener (5KPlayer's Airplay.exe). Set the catalog port to 8088. Code fallbacks and the standalone run_server default stay at 8080 by design; override lives in the config. * webui: restore the live elapsed-seconds counter during offline TTS Adopting upstream's streaming support routed the offline TTS button through the generator do_tts_or_stream, whose immediate first yield replaces Gradio's native "processing X.Xs" indicator with a static "⏳ Generating…" message — so the seconds that used to tick during synthesis showed nothing until the final "用时 X.Xs" status. Run the blocking do_tts in a daemon thread and have the generator yield "⏳ 生成中…已用时 Xs" every ~0.5s until it finishes, restoring the live count (and now also covering the model-reload wait). do_tts already returns (out, msg) and never raises, so no extra error handling is needed. * webui: localize console/log messages so they follow the UI language The 53 _ui_log(...) event lines (model load, TTS/ASR/music start+done, downloads, GGUF conversion, ...) were hardcoded Chinese f-strings, so the console and webui log stayed Chinese even with the English UI default. Route each through _t(zh, en, ...), converting inline f-string expressions into named format kwargs. Also localize the helper fragments they interpolate (resample note, threads note, per-segment note, audio-duration hint) and the remaining non-_ui_log log paths: the _pump_server_output duplicate-line collapser and the required_files read-failure print. Now logs default to English and switch to Chinese with the picker, matching the rest of the UI. Verified: all 258 _t() calls have matching zh/en placeholder sets with every placeholder covered by a kwarg; 20 tests pass. * make it work on Windows * restore model_manager.py, and introduce a new one model_manager_webui.py --------- Co-authored-by: Jilong Chen <jilong.chen@gmail.com> Co-authored-by: kigner <aidiscovery2045@proton.me>
1 parent 9a0ccb3 commit 3fd39dc

38 files changed

Lines changed: 13739 additions & 0 deletions

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,18 @@ __pycache__/
2828
/*.wav
2929
/flutter/
3030
/.cache/
31+
32+
# --- portable bundle: distributed manually, never committed ---
33+
/audiocpp-portable/
34+
35+
# --- WebUI runtime artifacts: keep the source + empty dirs, drop generated files ---
36+
/webui/output/*
37+
!/webui/output/.gitkeep
38+
/webui/logs/*
39+
!/webui/logs/.gitkeep
40+
/webui/third_party/
41+
/webui/llm_api_key.txt
42+
# written by the in-UI language picker; per-machine, not a project setting
43+
/webui/configs/ui_language.json
44+
# personal voice recording — stays local, repo is public
45+
/webui/voice/my-record.wav

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,13 @@ target_link_libraries(engine_runtime PUBLIC ggml)
568568
target_link_libraries(engine_runtime PRIVATE sentencepiece cjson_vendor yaml_vendor)
569569
if (ENGINE_ENABLE_OPENMP)
570570
target_link_libraries(engine_runtime PRIVATE OpenMP::OpenMP_CXX)
571+
if (MSVC)
572+
# MSVC's default /openmp implements only OpenMP 2.0 and rejects the
573+
# '#pragma omp simd' directives in longformer_attention.cpp (error C7660).
574+
# /openmp:experimental enables the OpenMP 4.0 SIMD support; it overrides the
575+
# /openmp added by OpenMP::OpenMP_CXX above (harmless D9025 override notice).
576+
target_compile_options(engine_runtime PRIVATE /openmp:experimental)
577+
endif()
571578
endif()
572579
573580
if (ENGINE_ENABLE_CUDA)

_env.bat

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
@echo off
2+
REM _env.bat -- shared environment detection for the audio.cpp .bat launchers.
3+
REM Called (not run) by run_webui.bat / run_server.bat / run_cli_tts.bat. It sets
4+
REM common variables and deliberately does NOT use setlocal, so they propagate back
5+
REM to the caller. Change detection logic here only.
6+
REM
7+
REM Exports: ROOT BUNDLE WEBUI_DIR PY HAS_CUDA BACKEND SERVER_EXE CLI_EXE GGUF_EXE
8+
9+
REM --- ROOT = this script's directory, without the trailing backslash ---
10+
set "ROOT=%~dp0"
11+
if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%"
12+
13+
REM Dev tree: the repo root doubles as the bundle (models\ live under it; the
14+
REM binaries live under build\). webui.py's own _find_bundle_root handles this.
15+
set "BUNDLE=%ROOT%"
16+
set "WEBUI_DIR=%ROOT%\webui"
17+
18+
REM --- Python with the deps (gradio/requests/torch/safetensors/opencc/...) ---
19+
REM Order: explicit override, project venv (Scripts\ on Windows), then a bundle venv.
20+
set "PY="
21+
if defined AUDIOCPP_PYTHON if exist "%AUDIOCPP_PYTHON%" set "PY=%AUDIOCPP_PYTHON%"
22+
if not defined PY if exist "%ROOT%\venv\Scripts\python.exe" set "PY=%ROOT%\venv\Scripts\python.exe"
23+
if not defined PY if exist "%ROOT%\venv\python.exe" set "PY=%ROOT%\venv\python.exe"
24+
if not defined PY if exist "%BUNDLE%\venv\Scripts\python.exe" set "PY=%BUNDLE%\venv\Scripts\python.exe"
25+
if not defined PY if exist "%BUNDLE%\venv\python.exe" set "PY=%BUNDLE%\venv\python.exe"
26+
27+
REM --- CUDA present? (NVIDIA driver installs nvcuda.dll in System32) ---
28+
set "HAS_CUDA="
29+
if exist "%SystemRoot%\System32\nvcuda.dll" set "HAS_CUDA=1"
30+
31+
REM --- Locate the from-source binaries. The default Visual Studio generator nests
32+
REM them in build\bin\Release (multi-config); Ninja/Makefiles use build\bin. ---
33+
set "BIN="
34+
if exist "%ROOT%\build\bin\Release\audiocpp_server.exe" set "BIN=%ROOT%\build\bin\Release"
35+
if not defined BIN if exist "%ROOT%\build\bin\audiocpp_server.exe" set "BIN=%ROOT%\build\bin"
36+
if defined BIN set "SERVER_EXE=%BIN%\audiocpp_server.exe"
37+
if defined BIN set "CLI_EXE=%BIN%\audiocpp_cli.exe"
38+
if defined BIN set "GGUF_EXE=%BIN%\audiocpp_gguf.exe"
39+
40+
REM --- BACKEND: read the actual build's GGML_CUDA flag from its CMakeCache, so we
41+
REM never advertise a GPU backend a CPU-only build can't serve. cuda when ON, else cpu. ---
42+
set "BACKEND=cpu"
43+
if exist "%ROOT%\build\CMakeCache.txt" (
44+
for /f "tokens=2 delims==" %%A in ('findstr /b /c:"GGML_CUDA:BOOL" "%ROOT%\build\CMakeCache.txt" 2^>nul') do (
45+
if /I "%%A"=="ON" set "BACKEND=cuda"
46+
)
47+
)

requirements.txt

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# audio.cpp -- unified Python requirements (webui + SpeakType + tools)
2+
# Frozen from the project venv on 2026-07-15.
3+
# One env covers every Python layer of the project:
4+
# webui/ gradio UI + realtime pipeline (gradio, numpy, requests; torch for silero VAD)
5+
# SpeakType/ voice dictation demo (sounddevice, pywebview, pywin32, pyperclip; torch for silero VAD)
6+
# tools/ model_manager etc. (requests, huggingface-hub, safetensors, tqdm)
7+
# The portable bundle ships this same env pre-installed as audiocpp-portable\venv.
8+
# Windows-only wheels (pywin32 and the pythonnet/pywebview desktop stack, used by
9+
# SpeakType) carry a sys_platform marker so this file also installs cleanly on Linux.
10+
# Regenerate: venv\Scripts\python.exe -m pip freeze > requirements.txt
11+
#annotated-doc==0.0.4
12+
annotated-types==0.7.0
13+
anyio==4.14.1
14+
bottle==0.13.4
15+
brotli==1.2.0
16+
certifi==2026.6.17
17+
cffi==2.1.0
18+
charset-normalizer==3.4.7
19+
click==8.4.2
20+
clr_loader==0.3.1; sys_platform == "win32"
21+
colorama==0.4.6
22+
fastapi==0.138.2
23+
filelock==3.29.4
24+
fsspec==2026.6.0
25+
gradio==6.19.0
26+
gradio_client==2.5.0
27+
groovy==0.1.2
28+
h11==0.16.0
29+
hf-gradio==0.4.1
30+
hf-xet==1.5.1
31+
httpcore==1.0.9
32+
httptools==0.8.0
33+
httpx==0.28.1
34+
huggingface_hub==1.21.0
35+
idna==3.18
36+
iniconfig==2.3.0
37+
Jinja2==3.1.6
38+
markdown-it-py==4.2.0
39+
MarkupSafe==3.0.3
40+
mdurl==0.1.2
41+
mpmath==1.3.0
42+
networkx==3.6.1
43+
numpy==2.4.6
44+
opencc-python-reimplemented==0.1.7
45+
orjson==3.11.9
46+
packaging==26.2
47+
pandas==3.0.3
48+
pillow==12.2.0
49+
pluggy==1.6.0
50+
proxy_tools==0.1.0; sys_platform == "win32"
51+
pycparser==3.0
52+
pydantic==2.13.4
53+
pydantic_core==2.46.4
54+
pydub==0.25.1
55+
Pygments==2.20.0
56+
pyperclip==1.11.0
57+
pytest==9.1.1
58+
python-dateutil==2.9.0.post0
59+
python-dotenv==1.2.2
60+
python-multipart==0.0.32
61+
pythonnet==3.1.0; sys_platform == "win32"
62+
pytz==2026.2
63+
pywebview==6.2.1; sys_platform == "win32"
64+
pywin32==312; sys_platform == "win32"
65+
PyYAML==6.0.3
66+
requests==2.34.2
67+
rich==15.0.0
68+
safehttpx==0.1.7
69+
safetensors==0.8.0
70+
semantic-version==2.10.0
71+
shellingham==1.5.4
72+
six==1.17.0
73+
sounddevice==0.5.5
74+
starlette==1.3.1
75+
sympy==1.14.0
76+
tomlkit==0.14.0
77+
torch==2.12.1
78+
torchaudio==2.11.0
79+
tqdm==4.68.3
80+
typer==0.25.1
81+
typing-inspection==0.4.2
82+
typing_extensions==4.15.0
83+
tzdata==2026.2
84+
urllib3==2.7.0
85+
uvicorn==0.49.0
86+
watchfiles==1.2.0
87+
websockets==16.0

run_webui.bat

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
@echo off
2+
setlocal
3+
chcp 65001 >nul
4+
cd /d "%~dp0"
5+
call "%~dp0_env.bat"
6+
7+
REM _env.bat auto-detected BACKEND (cuda|cpu) from the NVIDIA driver + bundled exes;
8+
REM hand it to webui.py unless the user already chose via AUDIOCPP_BACKEND.
9+
if not defined AUDIOCPP_BACKEND (
10+
if /I "%BACKEND%"=="cuda" ( set "AUDIOCPP_BACKEND=gpu" ) else ( set "AUDIOCPP_BACKEND=cpu" )
11+
)
12+
13+
REM Python (with gradio/requests/torch/safetensors/...) is located by _env.bat (PY).
14+
if not exist "%PY%" (
15+
echo [run_webui] no Python with deps found. Looked for:
16+
echo %BUNDLE%\venv\python.exe ^(bundle venv^)
17+
echo %ROOT%\venv\python.exe ^(root venv^)
18+
echo %ROOT%\venv\Scripts\python.exe ^(project venv^)
19+
echo Install into one of them: gradio requests torch safetensors pyyaml huggingface_hub
20+
pause
21+
exit /b 1
22+
)
23+
echo [run_webui] python: %PY%
24+
25+
echo [run_webui] the WebUI starts/switches audiocpp_server on demand
26+
echo [run_webui] pick a model in the UI and click "load" (no need to run run_server.bat)
27+
echo [run_webui] backend: %AUDIOCPP_BACKEND% (auto-detected; override with AUDIOCPP_BACKEND=gpu or cpu)
28+
echo [run_webui] UI -^> http://127.0.0.1:7860
29+
"%PY%" "%WEBUI_DIR%\webui.py"
30+
31+
endlocal
32+
pause

run_webui.sh

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env bash
2+
# Launch the audio.cpp WebUI on Linux/macOS (POSIX counterpart of run_webui.bat).
3+
#
4+
# The WebUI starts/switches audiocpp_server on demand — pick a model in the UI and
5+
# click load; no need to start a server separately. Backend (cuda|cpu) is auto-detected
6+
# by webui.py from nvidia-smi and the available build; override with AUDIOCPP_BACKEND=gpu|cpu.
7+
# UI language: English by default, with 中文 / 中文繁體 selectable from the picker in the
8+
# UI. That pick is saved to webui/configs/ui_language.json and wins on later runs, so
9+
# AUDIOCPP_LANG (en|zh|zh-Hant) only sets the default before anything has been picked.
10+
set -euo pipefail
11+
12+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13+
WEBUI_DIR="$ROOT/webui"
14+
15+
# Locate a Python that has the deps (gradio/requests/torch/safetensors/opencc/...).
16+
PY=""
17+
for cand in \
18+
"${AUDIOCPP_PYTHON:-}" \
19+
"$ROOT/venv/bin/python" \
20+
"$ROOT/.venv/bin/python" \
21+
"$(command -v python3 || true)" \
22+
"$(command -v python || true)"; do
23+
if [ -n "$cand" ] && [ -x "$cand" ]; then PY="$cand"; break; fi
24+
done
25+
26+
if [ -z "$PY" ]; then
27+
echo "[run_webui] no Python found. Create a venv and install deps:" >&2
28+
echo " python3 -m venv venv && ./venv/bin/pip install -r requirements.txt" >&2
29+
exit 1
30+
fi
31+
32+
echo "[run_webui] python: $PY"
33+
echo "[run_webui] backend: ${AUDIOCPP_BACKEND:-auto} language: ${AUDIOCPP_LANG:-en (unless already picked in the UI)}"
34+
echo "[run_webui] UI -> http://127.0.0.1:7860"
35+
exec "$PY" "$WEBUI_DIR/webui.py"

0 commit comments

Comments
 (0)