From 64b06b9426d0fe798d2d8eb7231a0710a861f6ef Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 4 Nov 2026 12:08:17 -0800 Subject: [PATCH] docs(openwebui): the browser-kokoro switch is the user's settings row, not browser storage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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`. --- serverAppConfigs-docs/Dankclaw/Frank TTS.md | 2 +- serverAppConfigs-docs/Dankclaw/OpenWebUI.md | 81 ++++++++++++++------- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/serverAppConfigs-docs/Dankclaw/Frank TTS.md b/serverAppConfigs-docs/Dankclaw/Frank TTS.md index be84125..6033c1d 100644 --- a/serverAppConfigs-docs/Dankclaw/Frank TTS.md +++ b/serverAppConfigs-docs/Dankclaw/Frank TTS.md @@ -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]] | --- diff --git a/serverAppConfigs-docs/Dankclaw/OpenWebUI.md b/serverAppConfigs-docs/Dankclaw/OpenWebUI.md index f60ef1e..a8f8346 100644 --- a/serverAppConfigs-docs/Dankclaw/OpenWebUI.md +++ b/serverAppConfigs-docs/Dankclaw/OpenWebUI.md @@ -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