fix(voice-router): Voice folgt App-Focus + „hauptmenü" als back-to-main
Zwei Bugs aus dem ersten Live-Test des Multi-Threading-Designs. Bug 1 — Voice ignorierte App-Focus: Stefan hat in Projekt X reingeguckt und was reingesagt — Message landete im Hauptchat statt in X. Der Voice-Router auf der Bridge kannte den sichtbaren Kontext der App nicht. Fix: - audio.ts.startStreamingRecording nimmt neuen opts.projectId und schickt es im stt_stream_start-Payload mit. - ChatScreen.tsx: alle 4 startStreamingRecording-Callsites (wake, barge-in, passive, manuell) uebergeben focusedProjectIdRef.current. Neuer useRef-Spiegel damit die Focus-ID auch in useCallbacks/ useEffects mit alten Closures aktuell bleibt. - aria_bridge.py: neuer Handler fuer stt_stream_start speichert die projectId in self._stt_stream_projects[requestId], stt_stream_end loescht wieder. Beim stt_endpoint wird sie an _process_endpoint_text weitergereicht und dort als default_project_id in den Voice-Router. - _apply_voice_router bekommt neuen Prio-Rank 4: „App-Focus als Default" — greift wenn kein Meta, kein Prefix und kein aktiver Sticky. So folgt Voice ohne extra Marker dem sichtbaren Kontext. Bug 2 — Back-to-Main-Regex zu eng: „zurück ins hauptmenü" wurde nicht als Meta erkannt (Regex matchte nur „zurück zum hauptchat") und landete deshalb im aktiven Sticky-Projekt. Fix: Regex akzeptiert jetzt auch hauptmenü, menü, haupt, main mit Praepositionen „zum/zur/ins/in den". Bonus — Burger-Button heller: Stefan konnte den ☰-Toggle im Header kaum sehen. Farbe von Default (dunkelgrau) auf #E0E0F0 (hell) mit fontWeight 700 gesetzt. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -499,8 +499,14 @@ const ChatScreen: React.FC = () => {
|
||||
// fuer den Auto-Fall angenehm.
|
||||
useEffect(() => {
|
||||
AsyncStorage.setItem('aria_focused_project_id', focusedProjectId).catch(() => {});
|
||||
focusedProjectIdRef.current = focusedProjectId;
|
||||
}, [focusedProjectId]);
|
||||
|
||||
// Ref-Spiegel damit useCallback-Handler die aktuelle Focus-ID lesen
|
||||
// ohne dass wir die Deps in jedes Callback muessen (sonst re-createn
|
||||
// die sich bei jedem Wechsel).
|
||||
const focusedProjectIdRef = useRef<string>('');
|
||||
|
||||
// Queue-Status alle 2s pollen — fuers Status-Dot im Focus-Header und
|
||||
// fuer die Drawer-Anzeige. Nur wenn RVS verbunden ist (sonst 30s Timeout).
|
||||
useEffect(() => {
|
||||
@@ -1414,6 +1420,7 @@ const ChatScreen: React.FC = () => {
|
||||
noSpeechTimeoutMs: windowMs,
|
||||
endpointMs: 1500,
|
||||
hardCapMs: 60000,
|
||||
projectId: focusedProjectIdRef.current,
|
||||
});
|
||||
import('../services/logger').then(m => m.reportAppDebug('wake.cb', `startStreamingRecording returned ok=${ok}`)).catch(()=>{});
|
||||
if (ok) {
|
||||
@@ -1509,6 +1516,7 @@ const ChatScreen: React.FC = () => {
|
||||
noSpeechTimeoutMs: windowMs,
|
||||
endpointMs: 1500,
|
||||
hardCapMs: 60000,
|
||||
projectId: focusedProjectIdRef.current,
|
||||
});
|
||||
if (ok) {
|
||||
ToastAndroid.show('🎤 Mikro offen — sprich jetzt', ToastAndroid.SHORT);
|
||||
@@ -1565,6 +1573,7 @@ const ChatScreen: React.FC = () => {
|
||||
noSpeechTimeoutMs: Math.min(passiveMs, 30000),
|
||||
endpointMs: 1500,
|
||||
hardCapMs: Math.max(passiveMs + 5000, 35000),
|
||||
projectId: focusedProjectIdRef.current,
|
||||
});
|
||||
if (!ok) {
|
||||
console.warn('[Chat] passive streaming start failed — exit passive listening');
|
||||
@@ -1880,7 +1889,7 @@ const ChatScreen: React.FC = () => {
|
||||
const location = await getCurrentLocation();
|
||||
|
||||
const cmid = nextClientMsgId();
|
||||
const activePid = focusedProjectId;
|
||||
const activePid = focusedProjectIdRef.current;
|
||||
const userMsg: ChatMessage = {
|
||||
id: nextId(),
|
||||
sender: 'user',
|
||||
@@ -1963,6 +1972,7 @@ const ChatScreen: React.FC = () => {
|
||||
noSpeechTimeoutMs: 0,
|
||||
endpointMs: 1500,
|
||||
hardCapMs: 300000,
|
||||
projectId: focusedProjectIdRef.current,
|
||||
});
|
||||
if (!ok) {
|
||||
// Mikro nicht verfuegbar (Anruf? OpenWakeWord blockiert?) — Bubble weg.
|
||||
@@ -2584,7 +2594,7 @@ const ChatScreen: React.FC = () => {
|
||||
style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}
|
||||
hitSlop={{top:6,bottom:6,left:6,right:6}}
|
||||
>
|
||||
<Text style={{ fontSize: 20 }}>☰</Text>
|
||||
<Text style={{ fontSize: 22, color: '#E0E0F0', fontWeight: '700' }}>☰</Text>
|
||||
{otherActive > 0 && (
|
||||
<View style={{ backgroundColor: '#FF6E6E', borderRadius: 8, minWidth: 16, height: 16, paddingHorizontal: 4, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text style={{ color: '#fff', fontSize: 10, fontWeight: '700' }}>{otherActive}</Text>
|
||||
|
||||
@@ -982,6 +982,10 @@ class AudioService {
|
||||
noSpeechTimeoutMs?: number;
|
||||
endpointMs?: number;
|
||||
hardCapMs?: number;
|
||||
/** Focused projectId — Bridge nutzt das als Default fuer den Voice-Router.
|
||||
* Leer = Hauptchat. Ohne Prefix / Sticky landet die STT-Nachricht damit
|
||||
* automatisch in dem Kontext den Stefan gerade sieht. */
|
||||
projectId?: string;
|
||||
}): Promise<{ requestId: string; ok: boolean }> {
|
||||
if (this.recordingState !== 'idle') {
|
||||
console.warn('[Audio] startStreamingRecording: bereits aktiv (state=%s)', this.recordingState);
|
||||
@@ -1055,6 +1059,7 @@ class AudioService {
|
||||
endpointMs: typeof opts.endpointMs === 'number' ? opts.endpointMs : 1500,
|
||||
hardCapMs: typeof opts.hardCapMs === 'number' ? opts.hardCapMs : 60000,
|
||||
sampleRate: 16000,
|
||||
projectId: opts.projectId || '',
|
||||
});
|
||||
|
||||
// No-Speech-Watchdog — ersetzt den alten VAD-noSpeechTimer.
|
||||
|
||||
Reference in New Issue
Block a user