From ebe0e8065f7ccfd8c23a35e7dcb9987053efb142 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sun, 16 Aug 2026 01:46:26 +0200 Subject: [PATCH] =?UTF-8?q?feat(voice):=2030s-Passiv-Fenster=20raus=20?= =?UTF-8?q?=E2=80=94=20Stille-Toleranz=20regiert=20alles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stefans Modell: es braucht keinen separaten 30s-Wert. Nach ARIAs Antwort geht das Mikro auf; fängst du nicht innerhalb der Stille-Toleranz (z.B. 5s) an zu reden, ist Schluss → zurück aufs Wake-Word. Derselbe Wert wie die Pause-Toleranz beim Reden. Ein Befehl ohne "fortführen" = Ende; nach einer normalen Antwort = ein Stille- Fenster zum Weiterreden. - Alle Aufnahme-Pfade (wake/barge/passiv): noSpeechTimeoutMs = endpointMs = loadSttEndpointMs() statt loadConvWindowMs()/loadPassiveListenMs(). Ein Wert. - Passiv-Hardcap: loadMaxRecordingMs() statt fix 35s (schnitt langes Reden ab). - Leeres Endpoint im listening-State: KEIN Re-Arm mehr → exitPassiveListening (ein 5s-Fenster, dann Ende). Der 30s-Master-Timer in wakeword.ts wird dadurch nie mehr scharf (harmloser Backstop). Redest du weiter → Antwort → wieder ein Stille-Fenster (Multi-Turn bleibt, nur ohne 30s-Leerlauf). Die Settings "Konversations-Fenster"/"Passiv-Lauschen" sind damit obsolet (UI-Cleanup später). Deploy: neue APK. Co-Authored-By: Claude Opus 4.8 --- android/src/screens/ChatScreen.tsx | 38 ++++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index 5ba3462..766154b 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -1679,7 +1679,11 @@ const ChatScreen: React.FC = () => { rememberMyRequest(audioRequestId); const wasInterrupted = interruptAriaIfBusy(); const location = await getCurrentLocation(); - const windowMs = await loadConvWindowMs(); + // EIN Wert regiert: die Stille-Toleranz. Sie gilt sowohl als Pause WÄHREND + // des Redens (endpointMs) ALS AUCH als "wenn du nicht anfängst zu reden, + // ist Schluss" (noSpeechTimeoutMs). Kein separates 30s-Konversationsfenster + // mehr — Stefans Modell: sagst du nichts, greift der Stille-Wert. + const sttEndpointMs = await loadSttEndpointMs(); const userMsg: ChatMessage = { id: nextId(), @@ -1697,8 +1701,8 @@ const ChatScreen: React.FC = () => { speed: ttsSpeedRef.current, interrupted: wasInterrupted, location: location || null, - noSpeechTimeoutMs: windowMs, - endpointMs: await loadSttEndpointMs(), + noSpeechTimeoutMs: sttEndpointMs, + endpointMs: sttEndpointMs, // Notbremse 5 min (nicht 1 min) — der Stille-Endpoint beendet normale // Turns eh sofort; der Cap darf lange Diktate nicht mitten drin kappen. hardCapMs: await loadMaxRecordingMs(), @@ -1756,12 +1760,12 @@ const ChatScreen: React.FC = () => { !(m.audioRequestId === ev.audioRequestId && m.text.includes('Spracheingabe wird verarbeitet')))); } - // Bei Passive-Listen + speaker_mismatch oder no-speech: erneut passiv - // lauschen (Timer im wakeword-service laeuft weiter, regelt das Ende). - // Sonst endConversation wie bisher. + // Kein Re-Arm mehr: nach ARIAs Antwort gab es EIN Stille-Fenster (= + // Stille-Toleranz). Kam nichts, ist Schluss → zurück aufs Wake-Word. + // Kein 30s-Nachlauschen. (speaker_mismatch/no-speech landen beide hier.) if (wakeWordService.getState() === 'listening') { - console.log('[Chat] Passive-Listen: leeres Endpoint — naechste passive Aufnahme'); - startPassiveStreamingRecording(); + console.log('[Chat] Passive-Listen: leeres Endpoint — Ende, zurueck aufs Wake-Word'); + wakeWordService.exitPassiveListening('timeout').catch(() => {}); } else { wakeWordService.endConversation(); if (!wakeWordService.isActive()) setWakeWordActive(false); @@ -1791,7 +1795,7 @@ const ChatScreen: React.FC = () => { const audioRequestId = `audio_${Date.now()}_${Math.floor(Math.random() * 100000)}`; rememberMyRequest(audioRequestId); const location = await getCurrentLocation(); - const windowMs = await loadConvWindowMs(); + const sttEndpointMs = await loadSttEndpointMs(); // ein Wert für Pause + No-Speech const userMsg: ChatMessage = { id: nextId(), @@ -1809,8 +1813,8 @@ const ChatScreen: React.FC = () => { speed: ttsSpeedRef.current, interrupted: true, // Barge-In → Brain weiss "User hat unterbrochen" location: location || null, - noSpeechTimeoutMs: windowMs, - endpointMs: await loadSttEndpointMs(), + noSpeechTimeoutMs: sttEndpointMs, + endpointMs: sttEndpointMs, // Notbremse 5 min (s.o.) — lange Diktate nicht bei 1 min abschneiden. hardCapMs: await loadMaxRecordingMs(), projectId: focusedProjectIdRef.current, @@ -1871,16 +1875,20 @@ const ChatScreen: React.FC = () => { const audioRequestId = `audio_passive_${Date.now()}_${Math.floor(Math.random() * 100000)}`; rememberMyRequest(audioRequestId); const location = await getCurrentLocation(); - const passiveMs = await loadPassiveListenMs(); + // Kein 30s-Passiv-Fenster mehr: nach ARIAs Antwort geht das Mikro auf, und + // fängst du nicht innerhalb der Stille-Toleranz an zu reden, ist Schluss → + // zurück aufs Wake-Word. Derselbe Wert wie die Pause-Toleranz beim Reden. + const sttEndpointMs = await loadSttEndpointMs(); const { ok } = await audioService.startStreamingRecording({ audioRequestId, voice: localXttsVoiceRef.current, speed: ttsSpeedRef.current, interrupted: false, location: location || null, - noSpeechTimeoutMs: Math.min(passiveMs, 30000), - endpointMs: await loadSttEndpointMs(), - hardCapMs: Math.max(passiveMs + 5000, 35000), + noSpeechTimeoutMs: sttEndpointMs, + endpointMs: sttEndpointMs, + // Lange Antworten nicht kappen (früher 35s → schnitt langes Reden ab). + hardCapMs: await loadMaxRecordingMs(), projectId: focusedProjectIdRef.current, }); if (!ok) {