docs(openwebui): the browser-kokoro switch is the user's settings row, not browser storage
`wilwheaton` kept failing in OpenWebUI with the 29-name Kokoro enum even after the dropdown was set to Default. The page's 2026-10-18 conclusion — "no user.settings row carries an audio override, so the choice lives in that browser's own storage" — was a probe-depth bug: the personal audio settings live in table `user`, and the 0.12.x front end nests them under `ui` (`settings.ui.audio.tts.engine`). The row did carry `browser-kokoro`, and the SPA re-applies the server copy on every load, so the UI fix never stuck. Corrected facts on the owner page: - where the override really lives, and the version-dependent nesting; - repair BOTH engine and voice: engine "" still sends the stored voice, so a Kokoro-era `voice: "alloy"` returns as frank-tts's own 400; - the one-line tells: an `/wasm/ort-wasm-simd-threaded.jsep.wasm` fetch means Kokoro.js is live in that browser, and uvicorn logs every request so a missing `/api/v1/audio/speech` line is evidence; - deployed build is 0.12.5 (pin `0b399a8b`, bumped 2026-10-30) — the page still claimed 0.11.4 — and the 401 paragraph's duplicated sentence is gone. Verified 2026-11-04: with the row repaired, `POST /api/v1/audio/speech` as a real user over the vhost returns 200 audio/mpeg and frank-tts logs `speech voice=wilwheaton`.
This commit is contained in:
1 parent
7e9bfbc2be
commit
64b06b9426
2 files changed
+55
-28
No files matched your search
@@ -222,5 +222,5 @@ 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 `wilwheaton` 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]] |
|
||||
| A consumer rejects `wilwheaton` 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. In OpenWebUI the `browser-kokoro` choice is that user's own `user.settings` row (`ui.audio.tts.engine`) and the SPA re-serves it on every load, so flipping the dropdown does not stick; see [[OpenWebUI]] |
|
||||
---
|
||||
@@ -21,7 +21,7 @@ https://github.com/open-webui/open-webui
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Image | `ghcr.io/open-webui/open-webui:main` (verified from the live quadlet unit) |
|
||||
| Deployed build | `0.11.4` — `GET http://127.0.0.1:3005/api/version` → `{"version":"0.11.4"}` (2026-10-18). `:main` is the rolling image, not a stable pin, so the running build lags upstream: releases `0.12.0` (2026-10-10) through `0.12.3` (2026-10-16) exist and are not deployed (https://github.com/open-webui/open-webui/releases). The 2026-09-21 `0.11.4` security/access-control advisory is what pinned this era of the page. Imagegen endpoint is `POST /api/v1/images/generations` (the `comfyui` engine is still upstream). |
|
||||
| Deployed build | `0.12.5` — `GET http://127.0.0.1:3005/api/version` → `{"version":"0.12.5"}` (re-measured 2026-11-04). The repo pin `@sha256:0b399a8b…` (`dankclaw/open-webui/quadlet/open-webui.container`, bumped 2026-10-30) is what makes that build reproducible; `:main` is a rolling tag, so re-measure `/api/version` instead of quoting a build from this page. Imagegen endpoint is `POST /api/v1/images/generations` (the `comfyui` engine is still upstream). |
|
||||
| Port | 3005 (host) → 8080 (container). Port 3000 is Gitea's HTTP, so it is not usable. |
|
||||
| Data volume | `/home/dank/Containers/open-webui/data` → `/app/backend/data` |
|
||||
| Backends | The picker's content is `webui.db` state, not repo state: as recorded on 2026-10-07 it exposes **one** model, `frank-zealot`, with `frank-dragoon`/`frank-archon` configured but disabled — *unverified 2026-10-18* (the DB is the authority and is not readable from the repo). What the repo does fix is the container's own OpenAI target: `OPENAI_API_BASE_URL=http://192.168.0.143:7770` (`dankclaw/open-webui/quadlet/open-webui.container:32`, same value in the live container env) — see [The LLM backends](#the-llm-backends) |
|
||||
@@ -163,14 +163,11 @@ account under Profile → API Keys). Anonymous web visitors get the `pending`
|
||||
role (`ui.default_user_role=pending`), which cannot chat. Verified
|
||||
2026-10-07 from loopback `:3005` and over the public vhost:
|
||||
`GET /api/v1/models/list`, `GET /api/v1/models` and
|
||||
`POST /api/chat/completions` all return **401** with no key (also with a
|
||||
bogus bearer token); only `/api/config`, `/api/version` and `/health`
|
||||
`POST /api/chat/completions` all return **401** with no key (also with a bogus bearer
|
||||
token); only `/api/config`, `/api/version` and `/health` answer publicly, and none of
|
||||
them expose the model list. An external program needs an account + API key — there is
|
||||
no keyless access. Re-verified 2026-10-18 from loopback: `/health` → `{"status":true}`
|
||||
`/api/version` → 0.11.4, `/api/v1/models/list`, `/api/v1/models` and
|
||||
`POST /api/chat/completions` → 401; `/api/config` additionally publishes
|
||||
no keyless access. Re-measured 2026-11-04 from loopback: `/api/version` → `0.12.5`,
|
||||
and `/api/config` publishes
|
||||
`features.enable_login_form=false`, `features.enable_signup=false` and
|
||||
`oauth.providers.oidc="authentik"` — the SSO-only posture, visible from outside.
|
||||
|
||||
@@ -305,28 +302,58 @@ port is the rootless quadlet `frank-tts` (this repo:
|
||||
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 `wilwheaton` fails with
|
||||
`Voice: "wilwheaton" not found. Should be one of: af_heart, …` **without a
|
||||
single request reaching the server**. Personal audio settings are
|
||||
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.
|
||||
to **Kokoro.js running in the browser**: the deployed front end ships an
|
||||
in-page Kokoro implementation (frozen voice table plus `_validate_voice`,
|
||||
model `onnx-community/Kokoro-82M-v1.0-ONNX`, and a personal *Kokoro.js dtype*
|
||||
setting). Its engine select takes `""` (Default) or `browser-kokoro`; with
|
||||
`browser-kokoro` the page synthesizes locally and rejects any voice outside
|
||||
its fixed pack, so `wilwheaton` fails with `Voice: "wilwheaton" not found.
|
||||
Should be one of: af_heart, …` **without a single request leaving the
|
||||
browser**. 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.
|
||||
**The switch is a DB row, not browser storage.** Personal audio settings are
|
||||
the `settings` JSON column of table `user`, and in 0.12.x they nest under
|
||||
`ui`: `ui.audio.tts.engine`, `.voice`, `.defaultVoice`. In 0.11.x the same
|
||||
keys sat at the top level (`audio.tts.*`), and the 2026-10-18 probe listed
|
||||
only top-level keys, found no audio key, and concluded the choice lived in
|
||||
that browser's own storage — a probe-depth bug. The row carried the override
|
||||
the whole way; the 2026-10-30 pin bump to 0.12.5 moved the keys, not the bug.
|
||||
The SPA reads that row at load (`GET /api/v1/users/user/settings`) and posts
|
||||
the **whole** blob back whenever any preference changes
|
||||
(`POST …/settings/update`, body `{...settings}`). So the dropdown can fix it —
|
||||
but a second tab still holding the old copy writes `browser-kokoro` straight
|
||||
back, which is the loop that makes the toast return after it was "fixed". The
|
||||
user row is the authority here; the admin `config` table was never the problem
|
||||
(its `audio.tts.engine` has been `openai` throughout). Repair
|
||||
`settings.ui.audio.tts` with the image's stdlib `sqlite3` (`mode=ro`; quote
|
||||
identifiers, `user` and `group` are reserved words — there is no `sqlite3` CLI
|
||||
in the image or on the host), then reload every tab of that account.
|
||||
|
||||
**Repair the engine *and* the voice.** With engine `""` the front end still
|
||||
sends its stored voice in the request body, so a Kokoro-era `voice`/
|
||||
`defaultVoice` survives the engine fix as frank-tts's own rejection —
|
||||
measured 2026-11-04: `voice: "alloy"` → **400 `unknown voice 'alloy';
|
||||
available voices: dr.kern, garrickhagon, jeffhays, vaati, wilwheaton`**. Set
|
||||
`ui.audio.tts` to `{"engine": "", "voice": "wilwheaton", "defaultVoice":
|
||||
"wilwheaton"}` and reload. Engine `""` means "the admin config decides", and
|
||||
the handler fills `voice` from `audio.tts.voice` only when the body omits it.
|
||||
|
||||
**Two tells settle the case in one line:**
|
||||
|
||||
- a session that fetched `/wasm/ort-wasm-simd-threaded.jsep.wasm` loaded
|
||||
onnxruntime, so Kokoro.js is running in that browser — the server path
|
||||
never asks for that file;
|
||||
- a server-path failure leaves a uvicorn request line for
|
||||
`/api/v1/audio/speech` **and** a frank-tts log line; a `browser-kokoro`
|
||||
failure leaves neither. uvicorn logs every request, so silence is evidence.
|
||||
|
||||
Verified 2026-11-04: the handler's own outbound shape
|
||||
(`{"model":"frank-tts","input":…,"voice":"wilwheaton"}` with
|
||||
`Authorization: Bearer none`, the configured `audio.tts.openai.api_key`) →
|
||||
**200 `audio/wav`** — a body without `response_format` comes back WAV and
|
||||
either MIME is browser-playable, so OpenWebUI accepts it. The full route as a
|
||||
real user over the vhost, `POST /api/v1/audio/speech` → **200 `audio/mpeg`**,
|
||||
with frank-tts logging `speech voice=wilwheaton`.
|
||||
|
||||
## Retired instances
|
||||
|
||||
|
||||
Reference in new issue
Block a user