feat(fillers): Beruhigungssätze auch im Geräte-TTS-Modus sprechen

Bisher wurde der Filler im "Im Gerät"-Modus (Web Speech API) nur angezeigt,
aber nicht gesprochen (Server-Audio ist dort aus). Jetzt eigener Kanal:

- Orchestrator: chat_stream bekommt on_filler; _emit_filler bevorzugt den
  dedizierten Filler-Kanal (sonst Fallback on_token). Ephemer wie bisher.
- ws.py: on_filler -> {type:"filler", text}.
- Frontend (app.js): {type:"filler"} -> transient in der STATUS-Zeile (nicht
  in der Antwort-Bubble) und im Geräte-Modus sofort speechSynthesis.speak()
  OHNE cancel (mehrere Filler -> Queue; die Antwort cancelt spaeter und
  uebernimmt). Abgesichert ueber deviceVoicesReady()/deviceVoiceReady(lang) —
  keine Verschaerfung der bekannten deviceVoiceReady-Regression.

Server-TTS-Modus unveraendert (Filler zusaetzlich als "Satz null" gesprochen),
zeigt den Filler nun ebenfalls in der Status-Zeile statt in der Bubble.

Tests: 312 gruen; JS-Syntax geprueft. Doc §5.4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-29 23:08:48 +02:00
commit fdf65e8623
4 changed files with 36 additions and 7 deletions

View file

@ -679,6 +679,16 @@ function runTurn(path, onopen) {
case "ack":
routeLang = (msg.route && msg.route.language) || null;
break;
case "filler":
// Beruhigungs-/Geduldssatz: transient in der Status-Zeile (nicht in der
// Antwort-Bubble) und im Geräte-Modus sofort sprechen — OHNE cancel, damit
// mehrere Filler in die SpeechSynthesis-Queue laufen; die Antwort (semantic)
// canceled später und übernimmt.
if (msg.text) statusEl.textContent = msg.text;
if (msg.text && isDeviceMode() && deviceVoicesReady() && deviceVoiceReady(turnLang())) {
try { speechSynthesis.speak(_makeUtterance(msg.text, turnLang())); } catch (e) {}
}
break;
case "audio":
// Ankuendigung: der naechste Binaer-Frame ist ein satzweiser TTS-Chunk.
expectChunk = true;