From 2b48e5cac6fb4acccd8ea173c1636783a795665f Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 11 Jul 2026 20:40:57 +0200 Subject: [PATCH] =?UTF-8?q?fix(voice):=20Ohr=20re-armt=20nach=20Fast-Path-?= =?UTF-8?q?Befehl=20(kein=20TTS=20=E2=86=92=20kein=20Re-Arm-Trigger)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fast-Path-Antworten sind speak=False → kein TTS → onPlaybackFinished feuert nie. Das Wake-Word-Re-Arm haengt aber genau daran → nach "nächster Titel" blieb das Ohr grau in 'conversing' stecken (Stefan im Auto, 2 Min gewartet, kein Re-Arm). Log bestaetigt: nach stream.final kam kein wake.end/wake.start. - Bridge: chat-Payload traegt jetzt 'speak' (war nur answeredBy). - App: bei stiller Antwort (speak=false ODER answeredBy=fast-path) und laufender Konversation dieselbe Re-Arm-Logik wie bei TTS-Ende anstossen (aktiv → resume/Konversationsfenster, sonst → endConversation). Co-Authored-By: Claude Opus 4.8 --- android/src/screens/ChatScreen.tsx | 15 +++++++++++++++ bridge/aria_bridge.py | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index 5a9c162..d43da84 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -1203,6 +1203,21 @@ const ChatScreen: React.FC = () => { clearAckTimer(cmid); pendingPayloads.current.delete(cmid); } + // Stille Antwort (speak=false, z.B. Fast-Path „nächster Titel") → es + // kommt KEIN TTS, also feuert onPlaybackFinished nie. Genau daran haengt + // aber das Wake-Word-Re-Arm — sonst bleibt das Ohr nach dem Steuerbefehl + // grau haengen (Reproduktion Stefan im Auto). Hier dieselbe Logik wie + // bei TTS-Ende anstossen: aktiv → Konversations-Fenster (resume), + // sonst → Konversation beenden (re-arm). + const _isSilent = (message.payload as any).speak === false + || ((message.payload as any).answeredBy === 'fast-path'); + if (_isSilent && wakeWordService.isConversing()) { + if (AppState.currentState === 'active') { + wakeWordService.resume(); + } else { + wakeWordService.endConversation().catch(() => {}); + } + } } // TTS-Audio abspielen wenn vorhanden — respektiert geraetelokalen Mute/Disable diff --git a/bridge/aria_bridge.py b/bridge/aria_bridge.py index 22f7cbd..f723676 100644 --- a/bridge/aria_bridge.py +++ b/bridge/aria_bridge.py @@ -1366,6 +1366,11 @@ class ARIABridge: "projectId": (payload.get("projectId") or "") if isinstance(payload, dict) else "", # Quell-Backend (local/claude/fast-path) fuer den Diagnostic-Badge. "answeredBy": (payload.get("answeredBy") or "") if isinstance(payload, dict) else "", + # Wird diese Antwort vorgelesen? Fast-Path/Steuerbefehl = False. + # Die App braucht das: ohne TTS feuert onPlaybackFinished nie, + # und daran haengt das Wake-Word-Re-Arm — sonst bleibt das Ohr + # nach einem Fast-Path-Befehl grau haengen. + "speak": bool(payload.get("speak", True)) if isinstance(payload, dict) else True, }, "timestamp": int(asyncio.get_event_loop().time() * 1000), })