From d3e1cc4a410522c508156ae0582560d7b3f30ebe Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Mon, 17 Aug 2026 21:49:04 -0700 Subject: [PATCH] docs(lobe-chat): TTS works with zero client-side settings now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit infra-ops aliased tts-1, tts-1-hd and gpt-4o-mini-tts onto ext-tts's upstream and extended the lobe-chat-esh key allow-list 20 -> 23 models, so the manual "set the TTS model to ext-tts, per browser" step this file described a few hours ago is obsolete. Lobe's stock three-field payload now returns 200 audio/mpeg — verified from the host with this stack's own .env. Adds the coupling that the fix introduces: the three new names are independent LiteLLM DB rows carrying their own copy of the upstream URL, so a future repoint of ext-tts must move all four or stock clients land on a dead engine without any error on the gateway side. Comment/doc only — no functional change, no redeploy. --- stacks/lobe-chat/README.md | 28 ++++++++++++++++++---------- stacks/lobe-chat/compose.yaml | 15 +++++++++------ 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/stacks/lobe-chat/README.md b/stacks/lobe-chat/README.md index 69322fd..65371db 100644 --- a/stacks/lobe-chat/README.md +++ b/stacks/lobe-chat/README.md @@ -50,12 +50,16 @@ model are a **one-time UI setup per browser**, not a maintenance surface. That i > 1. **"An unknown `model` routes to the gateway default" — false on this path.** > True direct-to-`:8198`, which accepts-and-ignores `model`. But requests go > through **LiteLLM**, which resolves the model name *first*. Lobe's default is -> `tts-1`, and `POST $OPENAI_PROXY_URL/audio/speech {"model":"tts-1",...}` returns -> **403 `key not allowed to access model`** (the scoped key's allow-list holds -> `ext-tts` alone; on an unscoped key it is a 400 `Invalid model name`). The same -> call with `"model":"ext-tts"` returns 200 + audio. **So TTS was dead on arrival -> from deploy until the model field was set** — the endpoint being wired is -> necessary, not sufficient. +> `tts-1`, and `POST $OPENAI_PROXY_URL/audio/speech {"model":"tts-1",...}` returned +> **403 `key not allowed to access model`** (the scoped key's allow-list held +> `ext-tts` alone; on an unscoped key it is a 400 `Invalid model name`). **So TTS +> was dead on arrival from deploy until 2026-08-17** — the endpoint being wired is +> necessary, not sufficient. **Fixed the same day**: infra-ops aliased `tts-1`, +> `tts-1-hd` and `gpt-4o-mini-tts` to ext-tts's upstream and extended the +> `lobe-chat-esh` key allow-list 20 → 23 models, so the stock payload now works +> with **zero client-side settings** (verified from this host with this `.env`: +> 200, `audio/mpeg`, real MP3). Setting the UI model field to `ext-tts` remains +> valid and harmless. > 2. **"`response_format: mp3` … set it in the UI" — not possible.** Lobe's OpenAI > TTS client sends exactly three fields (server bundle `chunks/29685.js`): > `{ input, model: options?.model || 'tts-1', voice }`. `response_format` is never @@ -65,10 +69,14 @@ model are a **one-time UI setup per browser**, not a maintenance surface. That i > regardless of the bytes. Chrome sniffs and plays it. Fixing the size is > tts-dev's side of the fence, not a setting here. > -> **To make TTS work in a browser today:** Settings → TTS → *OpenAI TTS model* → -> `ext-tts`. Per browser, because the settings store is client-side. Removing that -> manual step means aliasing `tts-1` → `ext-tts` in LiteLLM **and** adding it to the -> `lobe-chat-esh` key's allow-list — both master-key operations (infra-ops). +> **Nothing to do in the browser any more.** The alias + allow-list pair above +> removed the manual step entirely; a fresh browser, a phone, or any stock OpenAI +> client works out of the box. +> +> ⚠️ **Coupling worth knowing:** `tts-1`, `tts-1-hd` and `gpt-4o-mini-tts` are +> independent LiteLLM DB rows pointing at ext-tts's upstream, not references to +> `ext-tts`. If that upstream ever repoints, all four move together or stock +> clients quietly land on a dead engine. Trade recorded for the record: Open WebUI has dedicated `AUDIO_TTS_*` env vars (fully scriptable) at 1,825 MB; Lobe is endpoint-env + UI-cosmetic at 143 MB. Operator chose diff --git a/stacks/lobe-chat/compose.yaml b/stacks/lobe-chat/compose.yaml index a738571..a11ea2a 100644 --- a/stacks/lobe-chat/compose.yaml +++ b/stacks/lobe-chat/compose.yaml @@ -35,12 +35,15 @@ # ALIASED, not rejected (echo/alloy/onyx/ash->donut, nova->miranda, # shimmer/coral->emmie, fable/sage->glados), so a UI mis-click is NOT the # hazard I first recorded. Only `ballad` and `verse` 404 -- both aliased since. -# ⚠️ REQUIRED UI STEP (tts-dev 2026-08-17): Settings -> TTS -> OpenAI TTS model -# -> `ext-tts`. There is no env var for it. Lobe's default is `tts-1`, and while -# :8198 ignores `model`, LITELLM IN FRONT OF IT DOES NOT -- it resolves the name -# first, so `tts-1` returns 403 (`key not allowed to access model`; 400 on an -# unscoped key) and TTS does nothing. Verified from this host with this .env. -# Per browser -- the TTS settings store is client-side. +# MODEL NAME -- no longer a manual step (2026-08-17). Lobe defaults to `tts-1`, +# and while :8198 ignores `model`, LITELLM IN FRONT OF IT DOES NOT -- it resolves +# the name first, so `tts-1` used to 403 (`key not allowed to access model`) and +# TTS did nothing from deploy until it was found. infra-ops then aliased `tts-1`, +# `tts-1-hd` and `gpt-4o-mini-tts` to ext-tts's upstream AND added them to the +# `lobe-chat-esh` key allow-list, so the stock payload works with ZERO settings. +# Verified from this host with this .env: {"input":...,"model":"tts-1", +# "voice":"alloy"} -> 200 audio/mpeg. Setting the UI field to `ext-tts` also +# works and does no harm. # SIZE: Lobe sends only {input, model, voice} -- no `response_format`, no # `speed`, and no UI field for either -- so you get the gateway's default (WAV, # ~23.5 MB for a 245 s turn), relabelled `audio/mpeg` by LiteLLM. Not fixable