Merge pull request 'docs(openwebui): the TTS voice rejector is in-browser Kokoro, not frank-tts' (#20) from docs/openwebui-browser-kokoro-tts into main

This commit was merged in pull request #20.
This commit is contained in:
2026-10-18 20:38:50 -07:00
3 changed files with 71 additions and 0 deletions
@@ -201,3 +201,4 @@ harness bundle and measured the day of the flip:
| Endpoint answers, synthesis fails | the engine: `podman healthcheck run frank-tts-engine`, then `systemctl --user status frank-tts-engine.service` |
| First request of the day is ~0.6 s slower than the rest — or every name returns 400 | the warm pass did not run; it carries `-`, so it cannot fail the unit. `journalctl --user -u frank-tts-engine` for the warmer's lines, then `podman exec frank-tts-engine /app/warm-voices.sh` — the registry is memory-only (law 1 of [[Frank TTS Engine]]) |
| The unit never appears after a deploy | the generator dropped it: `frank-tts-engine.network` is missing from `~/.config/containers/systemd/` |
| A consumer rejects `narrator1` by listing 29 names (`af_heart … bm_fable`) and no request ever reaches this endpoint | that consumer is synthesizing **in the browser** with Kokoro.js, whose voice pack is fixed — OpenWebUI 0.11.4 ships it as the `browser-kokoro` TTS engine; see [[OpenWebUI]] |
@@ -292,6 +292,42 @@ carries base64 image data or a URL.
Failure modes: connection refused ⇒ OpenWebUI or ComfyUI is down; timeouts
on large sizes ⇒ check ComfyUI resources; 401 ⇒ the token is bad.
## Text-to-speech (frank-tts)
TTS is the fleet's one OpenAI-compatible speech endpoint —
`http://host.containers.internal:7772/v1` (`audio.tts.openai.api_base_url`
in the config DB, 2026-10-18), model `frank-tts`, voice `narrator1`. That
port is the rootless quadlet `frank-tts` (this repo:
`dankclaw/voiceservice/quadlet/frank-tts.container`,
`scripts/frank-tts.env`), and its shim resolves `voice=<name>` against
`/home/dank/voices/<name>/reference.wav` — a name without a reference is a
400 listing the voices that exist, never a silent fallback.
**The 29-voice `af_* … bm_fable` list is NOT frank-tts.** That enum belongs
to **Kokoro.js running in the browser**: the deployed front end (0.11.4)
ships an in-page Kokoro implementation (chunk carrying a frozen voice table
`{af_heart:{name:"Heart",…}}` and `_validate_voice`, model
`onnx-community/Kokoro-82M-v1.0-ONNX`, plus a personal *Kokoro.js dtype*
setting). Its engine select takes `""` (Default) or `browser-kokoro`; when
`browser-kokoro` is chosen the page synthesizes locally and rejects any
voice outside its fixed pack — so `narrator1` fails with
`Voice: "narrator1" not found. Should be one of: af_heart, …` **without a
single request reaching the server**. Personal audio settings are per
browser: no `user.settings` row and no container env carries an audio
override, yet the toast still appears ⇒ the choice lives in that browser's
own storage. Fix in the UI: Settings → Audio → TTS Engine → **Default**,
which hands synthesis to the admin config (frank-tts); picking `af_heart`
instead keeps Kokoro, and Kokoro cannot clone — fixed pack only.
Verified 2026-10-18 from inside the container with the exact OpenWebUI body
(`{"model":"frank-tts","input":…,"voice":"narrator1",
"response_format":"mp3","speed":1.0}`) → **200, `audio/mpeg`, 7,629 B**,
while `podman logs open-webui` recorded **zero** `audio/speech` requests for
the failing click — that contrast is the instrument: a server-path failure
leaves a log line and a frank-tts error, a `browser-kokoro` failure leaves
neither.
## Retired instances
- The compose `open-webui` service (port 3000) was never run: 3000 is Gitea's HTTP port, so it could not bind.
@@ -349,6 +385,7 @@ on large sizes ⇒ check ComfyUI resources; 401 ⇒ the token is bad.
## See Also
- [[Dankclaw Stack]]
- [[Frank TTS]] — the speech endpoint behind *Text-to-speech (frank-tts)*
- [[Hermes]] — the agent OpenWebUI can talk to
- [[SearXNG]] — the search engine on this host
- [LLM knowledge base](../LLM/index.md) — the vLLM and llama.cpp endpoints
+33
View File
@@ -2754,3 +2754,36 @@ every restart since the cutover.
[[#Naming a voice]] now states that a call naming nothing is the harness's
normal shape, not a client bug — the misconception that produced the 400 in
the first place.
## 2026-10-18 — OpenWebUI "voice narrator1 not found": the rejector is in the browser
- Frank's toast — `Error: Voice "narrator1" not found. Should be one of:
af_heart, …, bm_fable` — looked like frank-tts refusing the name. It is
not: that 29-name enum is the voice pack of **Kokoro.js running in the
page**, and OpenWebUI 0.11.4 ships that implementation in its own front
end (frozen table `{af_heart:{name:"Heart",…}}`, `_validate_voice`, model
`onnx-community/Kokoro-82M-v1.0-ONNX`, a personal *Kokoro.js dtype*
setting, engine values `""`/`browser-kokoro`).
- Instruments that separated the two paths, in order: the admin config DB is
correct (`audio.tts.engine=openai`,
`audio.tts.openai.api_base_url=http://host.containers.internal:7772/v1`,
`model=frank-tts`, `voice=narrator1`, `split_on=punctuation`); from inside
the container the exact OpenWebUI body
(`{"model":"frank-tts","input":…,"voice":"narrator1","response_format":"mp3","speed":1.0}`)
returns **200 `audio/mpeg` 7,629 B**; and `podman logs open-webui` holds
**zero** `audio/speech` requests for the failing click. A server-path
failure leaves a log line and a frank-tts error — a `browser-kokoro`
failure leaves neither. No container env carries an `AUDIO_*`/`TTS`
variable and no `user.settings` row (all four users) carries an audio
override, so the engine choice is per-browser storage, not fleet state.
- Fix is a UI click, not a deploy: Settings → Audio → TTS Engine →
**Default** hands synthesis back to frank-tts and the cloned voices;
choosing `af_heart` keeps in-browser Kokoro, which cannot clone (fixed
pack). Recorded on [[OpenWebUI]] under *Text-to-speech (frank-tts)*;
suite green 384/384 (wall 65.9 s).
- Also in the window: **PR #22 merged** (`0c7fe8b`, `dank/command-center`) so
`agent/extensions/dankvoice.ts` landed on the auto-load path — proven in a
plain `omp` session with no flags: `/say merged build check` → 1.20 s WAV
to the player, temp dir cleaned; the audible pass through the default
`pw-play` detection confirmed by Frank. Sessions open before the merge need
a restart to pick the command up.