fix: klares UI-Feedback fuer Wake-Word-State
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) <noreply@anthropic.com>
This commit is contained in:
parent
bbf9aed3ba
commit
be373466a3
|
|
@ -104,6 +104,8 @@ const ChatScreen: React.FC = () => {
|
||||||
const [showCameraUpload, setShowCameraUpload] = useState(false);
|
const [showCameraUpload, setShowCameraUpload] = useState(false);
|
||||||
const [gpsEnabled, setGpsEnabled] = useState(false);
|
const [gpsEnabled, setGpsEnabled] = useState(false);
|
||||||
const [wakeWordActive, setWakeWordActive] = 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<string | null>(null);
|
const [fullscreenImage, setFullscreenImage] = useState<string | null>(null);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [searchVisible, setSearchVisible] = useState(false);
|
const [searchVisible, setSearchVisible] = useState(false);
|
||||||
|
|
@ -154,6 +156,11 @@ const ChatScreen: React.FC = () => {
|
||||||
// Wake Word: einmalig laden + Porcupine vorbereiten (wenn Access Key gesetzt)
|
// Wake Word: einmalig laden + Porcupine vorbereiten (wenn Access Key gesetzt)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
wakeWordService.loadFromStorage().catch(() => {});
|
wakeWordService.loadFromStorage().catch(() => {});
|
||||||
|
const unsub = wakeWordService.onStateChange((s) => {
|
||||||
|
setWakeWordState(s);
|
||||||
|
setWakeWordActive(s !== 'off');
|
||||||
|
});
|
||||||
|
return () => unsub();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// ttsCanPlayRef live aktuell halten — Closure in onMessage unten liest
|
// ttsCanPlayRef live aktuell halten — Closure in onMessage unten liest
|
||||||
|
|
@ -1009,7 +1016,10 @@ const ChatScreen: React.FC = () => {
|
||||||
style={[styles.wakeWordBtn, wakeWordActive && styles.wakeWordBtnActive]}
|
style={[styles.wakeWordBtn, wakeWordActive && styles.wakeWordBtnActive]}
|
||||||
onPress={toggleWakeWord}
|
onPress={toggleWakeWord}
|
||||||
>
|
>
|
||||||
<Text style={styles.wakeWordIcon}>{wakeWordActive ? '👂' : '🔇'}</Text>
|
<Text style={styles.wakeWordIcon}>
|
||||||
|
{wakeWordState === 'conversing' ? '🎙️' :
|
||||||
|
wakeWordState === 'armed' ? '👂' : '🔇'}
|
||||||
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -174,14 +174,14 @@ class WakeWordService {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Kein Porcupine init → User explicit informieren
|
// 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(
|
ToastAndroid.show(
|
||||||
'Wake-Word nicht verfuegbar — Access Key in Settings setzen',
|
'Wake-Word nicht aktiv — direkte Aufnahme startet (Mikro hoert mit)',
|
||||||
ToastAndroid.LONG,
|
ToastAndroid.LONG,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Fallback: direkt in die Konversation
|
// Fallback: direkt in die Konversation (Mikro AKTIV, nicht passive)
|
||||||
console.log('[WakeWord] Konversation startet sofort (kein Wake-Word)');
|
console.log('[WakeWord] Direkt-Aufnahme startet (kein Wake-Word)');
|
||||||
this.setState('conversing');
|
this.setState('conversing');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.state === 'conversing') {
|
if (this.state === 'conversing') {
|
||||||
|
|
@ -203,6 +203,7 @@ class WakeWordService {
|
||||||
/** Wake-Word getriggert: Porcupine pausieren, Konversation starten. */
|
/** Wake-Word getriggert: Porcupine pausieren, Konversation starten. */
|
||||||
private async onWakeDetected(): Promise<void> {
|
private async onWakeDetected(): Promise<void> {
|
||||||
console.log('[WakeWord] Wake-Word "%s" erkannt!', this.keyword);
|
console.log('[WakeWord] Wake-Word "%s" erkannt!', this.keyword);
|
||||||
|
ToastAndroid.show(`Wake-Word "${this.keyword}" erkannt — sprich jetzt`, ToastAndroid.SHORT);
|
||||||
if (this.porcupine) {
|
if (this.porcupine) {
|
||||||
try { await this.porcupine.stop(); } catch {}
|
try { await this.porcupine.stop(); } catch {}
|
||||||
}
|
}
|
||||||
|
|
@ -225,6 +226,7 @@ class WakeWordService {
|
||||||
try {
|
try {
|
||||||
await this.porcupine.start();
|
await this.porcupine.start();
|
||||||
console.log('[WakeWord] Konversation zu Ende — zurueck zu armed');
|
console.log('[WakeWord] Konversation zu Ende — zurueck zu armed');
|
||||||
|
ToastAndroid.show(`Lausche wieder auf "${this.keyword}"`, ToastAndroid.SHORT);
|
||||||
this.setState('armed');
|
this.setState('armed');
|
||||||
return;
|
return;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -232,6 +234,7 @@ class WakeWordService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('[WakeWord] Konversation zu Ende — Ohr aus');
|
console.log('[WakeWord] Konversation zu Ende — Ohr aus');
|
||||||
|
ToastAndroid.show('Mikro aus', ToastAndroid.SHORT);
|
||||||
this.setState('off');
|
this.setState('off');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue