From be373466a36f3344890abaf4a3496d3d45563e63 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Sat, 25 Apr 2026 20:34:07 +0200 Subject: [PATCH] fix: klares UI-Feedback fuer Wake-Word-State MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stefan's Verwirrung: Ohr-Button + KEIN Porcupine = Direkt-Aufnahme, nicht passives Lauschen. Wenn er lange wartet, schnappt das Mikro Hintergrundgeraeusche/Sprache auf, sendet ab, Ohr aus. Sah aus wie "Wake-Word triggerte" — war aber stinknormales Recording. Fixes fuer klares Feedback: - Toast bei jedem State-Wechsel: * Direkt-Aufnahme (kein Porcupine): "Wake-Word nicht aktiv — direkte Aufnahme startet (Mikro hoert mit)" * armed: "Lausche auf X..." * Wake erkannt: "Wake-Word X erkannt — sprich jetzt" * endConversation: "Lausche wieder auf X" oder "Mikro aus" - Ohr-Button-Icon zeigt drei States: 🔇 off 👂 armed (Porcupine lauscht passiv) 🎙️ conversing (aktive Aufnahme laeuft) - ChatScreen subscribed wakeWordService.onStateChange fuer Live- Updates des Icons. Co-Authored-By: Claude Opus 4.7 (1M context) --- android/src/screens/ChatScreen.tsx | 12 +++++++++++- android/src/services/wakeword.ts | 11 +++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index 4959cec..faaa4be 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -104,6 +104,8 @@ const ChatScreen: React.FC = () => { const [showCameraUpload, setShowCameraUpload] = useState(false); const [gpsEnabled, setGpsEnabled] = useState(false); const [wakeWordActive, setWakeWordActive] = useState(false); + // Genauer State (off/armed/conversing) fuer UI-Feedback am Button + const [wakeWordState, setWakeWordState] = useState<'off' | 'armed' | 'conversing'>('off'); const [fullscreenImage, setFullscreenImage] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [searchVisible, setSearchVisible] = useState(false); @@ -154,6 +156,11 @@ const ChatScreen: React.FC = () => { // Wake Word: einmalig laden + Porcupine vorbereiten (wenn Access Key gesetzt) useEffect(() => { wakeWordService.loadFromStorage().catch(() => {}); + const unsub = wakeWordService.onStateChange((s) => { + setWakeWordState(s); + setWakeWordActive(s !== 'off'); + }); + return () => unsub(); }, []); // ttsCanPlayRef live aktuell halten — Closure in onMessage unten liest @@ -1009,7 +1016,10 @@ const ChatScreen: React.FC = () => { style={[styles.wakeWordBtn, wakeWordActive && styles.wakeWordBtnActive]} onPress={toggleWakeWord} > - {wakeWordActive ? '👂' : '🔇'} + + {wakeWordState === 'conversing' ? '🎙️' : + wakeWordState === 'armed' ? '👂' : '🔇'} + )} diff --git a/android/src/services/wakeword.ts b/android/src/services/wakeword.ts index c0029f8..bd5fa71 100644 --- a/android/src/services/wakeword.ts +++ b/android/src/services/wakeword.ts @@ -174,14 +174,14 @@ class WakeWordService { } } else { // Kein Porcupine init → User explicit informieren - console.warn('[WakeWord] Porcupine nicht initialisiert — Access Key fehlt? Fallback Direkt-Konversation'); + console.warn('[WakeWord] Porcupine nicht initialisiert — Access Key fehlt? Fallback Direkt-Aufnahme'); ToastAndroid.show( - 'Wake-Word nicht verfuegbar — Access Key in Settings setzen', + 'Wake-Word nicht aktiv — direkte Aufnahme startet (Mikro hoert mit)', ToastAndroid.LONG, ); } - // Fallback: direkt in die Konversation - console.log('[WakeWord] Konversation startet sofort (kein Wake-Word)'); + // Fallback: direkt in die Konversation (Mikro AKTIV, nicht passive) + console.log('[WakeWord] Direkt-Aufnahme startet (kein Wake-Word)'); this.setState('conversing'); setTimeout(() => { if (this.state === 'conversing') { @@ -203,6 +203,7 @@ class WakeWordService { /** Wake-Word getriggert: Porcupine pausieren, Konversation starten. */ private async onWakeDetected(): Promise { console.log('[WakeWord] Wake-Word "%s" erkannt!', this.keyword); + ToastAndroid.show(`Wake-Word "${this.keyword}" erkannt — sprich jetzt`, ToastAndroid.SHORT); if (this.porcupine) { try { await this.porcupine.stop(); } catch {} } @@ -225,6 +226,7 @@ class WakeWordService { try { await this.porcupine.start(); console.log('[WakeWord] Konversation zu Ende — zurueck zu armed'); + ToastAndroid.show(`Lausche wieder auf "${this.keyword}"`, ToastAndroid.SHORT); this.setState('armed'); return; } catch (err) { @@ -232,6 +234,7 @@ class WakeWordService { } } console.log('[WakeWord] Konversation zu Ende — Ohr aus'); + ToastAndroid.show('Mikro aus', ToastAndroid.SHORT); this.setState('off'); }