2.8 KiB
2.8 KiB
name, description
| name | description |
|---|---|
| openwebui-tts-chain-audit | Audit/repair OpenWebUI TTS on Dankclaw when cloned voices get rejected by a 29-name Kokoro enum or synthesis is silent; rootless DB + endpoint probes |
Audit OpenWebUI's TTS/STT wiring on Dankclaw when a voice is rejected, synthesis is silent, or someone asks "is the cloned voice wired correctly". Not for model-picker/ACL issues (openwebui-picker-acl-repair owns those).
Shape of the stack
- Container
open-webuiruns rootless asdankon Dankclaw:ssh dankclaw 'podman exec open-webui ...'(NO sudo:sudo podmansees an empty world). Host port 3005. - Server-side config =
webui.dbconfigtable (admin), per-user overrides =user.settingsJSON column. Settings changed via UI persist per-user; admin page persists toconfig. - Cloned voices live behind the frank-tts façade
:7772(/v1/audio/speech,/v1/audio/voices); voices resolve to/home/dank/voices/<name>/reference.wav.
The trap (2026-10-18 law)
Error: Voice "narrator1" not found. Should be one of: af_heart, ... (29 names) is OpenWebUI's bundled in-browser Kokoro (browser-kokoro) rejecting client-side — frank-tts never sees the request. Signature: container logs show zero audio/speech hits. Fix = the personal dropdown Settings -> Audio -> TTS Engine -> Default (empty string), not any deploy.
Probe sequence (read-only, safe)
- Admin truth:
podman exec open-webui python -c "import sqlite3; c=sqlite3.connect('/app/backend/data/webui.db'); [print(k,'=',v) for k,v in sorted(dict(c.execute('SELECT key,value FROM config')).items()) if 'audio' in k or 'tts' in k.lower()]"Expect:audio.tts.engine=openai,audio.tts.openai.api_base_url=http://host.containers.internal:7772/v1,audio.tts.model=frank-tts,audio.tts.voice=<cloned name>. - User overrides — a user pinned to
browser-kokorobeats admin config. CAUTION:user.settingscan be SQL NULL or the JSON string"null":... for uid,s in c.execute('SELECT id,settings FROM user'): d=json.loads(s) if s else None; d=d if isinstance(d,dict) else {}; hits={k:v for k,v in d.items() if 'audio' in k or 'tts' in k.lower()} ... - Endpoint truth:
curl -s http://127.0.0.1:7772/v1/audio/voices(roster), then POST the exact OpenWebUI body{"model":"frank-tts","input":"probe","voice":"narrator1","response_format":"mp3","speed":1.0}to/v1/audio/speech— expect200 audio/mpeg. - Contrast logic: server-path failure leaves a container log line AND a façade error; browser-kokoro failure leaves neither.
Law
- Full contract + diagnosis live in serverAppConfigs docs (
Dankclaw/OpenWebUI.md§ Text-to-speech,Dankclaw/Frank TTS.mdtroubleshooting). Docs are the source; this skill is just the probe route. - Rootless gotcha generalizes: any Dankclaw service probe through podman goes WITHOUT sudo unless the unit is a system quadlet.