Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec9530f17f | |||
| 97cb7be313 | |||
| 77e927ffcd |
@@ -79,8 +79,8 @@ android {
|
|||||||
applicationId "com.ariacockpit"
|
applicationId "com.ariacockpit"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 705
|
versionCode 706
|
||||||
versionName "0.0.7.5"
|
versionName "0.0.7.6"
|
||||||
// Fallback fuer Libraries mit Product Flavors
|
// Fallback fuer Libraries mit Product Flavors
|
||||||
missingDimensionStrategy 'react-native-camera', 'general'
|
missingDimensionStrategy 'react-native-camera', 'general'
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aria-cockpit",
|
"name": "aria-cockpit",
|
||||||
"version": "0.0.7.5",
|
"version": "0.0.7.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
Binary file not shown.
@@ -26,6 +26,7 @@ import rvs, { RVSMessage, ConnectionState } from '../services/rvs';
|
|||||||
import audioService from '../services/audio';
|
import audioService from '../services/audio';
|
||||||
import wakeWordService from '../services/wakeword';
|
import wakeWordService from '../services/wakeword';
|
||||||
import phoneCallService from '../services/phoneCall';
|
import phoneCallService from '../services/phoneCall';
|
||||||
|
import { playWakeReadySound } from '../services/wakeReadySound';
|
||||||
import updateService from '../services/updater';
|
import updateService from '../services/updater';
|
||||||
import VoiceButton from '../components/VoiceButton';
|
import VoiceButton from '../components/VoiceButton';
|
||||||
import FileUpload, { FileData } from '../components/FileUpload';
|
import FileUpload, { FileData } from '../components/FileUpload';
|
||||||
@@ -55,6 +56,10 @@ interface ChatMessage {
|
|||||||
messageId?: string;
|
messageId?: string;
|
||||||
/** Lokaler Pfad zur gecachten TTS-Audio-Datei (file://...) */
|
/** Lokaler Pfad zur gecachten TTS-Audio-Datei (file://...) */
|
||||||
audioPath?: string;
|
audioPath?: string;
|
||||||
|
/** Korrelations-ID fuer Sprachnachrichten — wird mit dem STT-Result zurueck-
|
||||||
|
* gespiegelt damit wir die EXAKT richtige Placeholder-Bubble ersetzen,
|
||||||
|
* auch wenn mehrere Aufnahmen parallel offen sind. */
|
||||||
|
audioRequestId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Konstanten ---
|
// --- Konstanten ---
|
||||||
@@ -292,46 +297,42 @@ const ChatScreen: React.FC = () => {
|
|||||||
// den gleichen Text bekommen (Bug: zweite Antwort ueberschreibt erste).
|
// den gleichen Text bekommen (Bug: zweite Antwort ueberschreibt erste).
|
||||||
if (sender === 'stt') {
|
if (sender === 'stt') {
|
||||||
const sttText = (message.payload.text as string) || '';
|
const sttText = (message.payload.text as string) || '';
|
||||||
// Debug-Toast: visualisiert dass das STT-Event in der App angekommen ist.
|
const sttAudioReqId = (message.payload.audioRequestId as string) || '';
|
||||||
// Wenn dieser Toast NICHT erscheint, kommt das Event nicht durch (Bridge
|
if (!sttText) {
|
||||||
// oder RVS broadcastet es nicht), und der Bug liegt server-side.
|
return;
|
||||||
ToastAndroid.show(`STT empfangen: "${sttText.slice(0, 40)}"`, ToastAndroid.SHORT);
|
}
|
||||||
if (sttText) {
|
setMessages(prev => {
|
||||||
setMessages(prev => {
|
const newText = `\uD83C\uDFA4 ${sttText}`;
|
||||||
const idx = prev.findIndex(m =>
|
// Primaer: matche per audioRequestId (eindeutig pro Aufnahme).
|
||||||
m.sender === 'user' && m.text.includes('Spracheingabe wird verarbeitet')
|
// So gibt's keine Verwechslung wenn zwei Audios kurz hintereinander
|
||||||
);
|
// gesendet wurden und ihre STT-Results ueberlappen.
|
||||||
const placeholderCount = prev.filter(m =>
|
if (sttAudioReqId) {
|
||||||
m.sender === 'user' && m.text.includes('Spracheingabe wird verarbeitet')
|
const idxById = prev.findIndex(m => m.audioRequestId === sttAudioReqId);
|
||||||
).length;
|
if (idxById >= 0) {
|
||||||
console.log('[Chat] STT-Result: idx=%d text="%s" placeholders=%d',
|
const next = prev.slice();
|
||||||
idx, sttText.slice(0, 60), placeholderCount);
|
next[idxById] = { ...next[idxById], text: newText };
|
||||||
// Zweiter Toast: zeigt ob die Placeholder gefunden wurde.
|
return next;
|
||||||
ToastAndroid.show(
|
|
||||||
idx < 0
|
|
||||||
? `STT: keine Placeholder (${placeholderCount}) \u2192 neue Bubble`
|
|
||||||
: `STT: Bubble #${idx} ersetzt`,
|
|
||||||
ToastAndroid.SHORT,
|
|
||||||
);
|
|
||||||
const newText = `\uD83C\uDFA4 ${sttText}`;
|
|
||||||
if (idx < 0) {
|
|
||||||
// Defensiv: wenn keine Placeholder im State (z.B. weil sie nie
|
|
||||||
// hinzugefuegt wurde oder schon durch ein anderes Update verloren
|
|
||||||
// ging), die Sprachnachricht trotzdem als neue Bubble einfuegen.
|
|
||||||
// Sonst kommt ARIAs Antwort ohne sichtbare User-Nachricht.
|
|
||||||
return capMessages([...prev, {
|
|
||||||
id: nextId(),
|
|
||||||
sender: 'user',
|
|
||||||
text: newText,
|
|
||||||
timestamp: message.timestamp,
|
|
||||||
attachments: [{ type: 'audio', name: 'Sprachaufnahme' }],
|
|
||||||
}]);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Fallback: alte Bridge-Version ohne audioRequestId \u2014 match per Substring,
|
||||||
|
// nimmt die ERSTE noch unaufgeloeste Placeholder.
|
||||||
|
const idx = prev.findIndex(m =>
|
||||||
|
m.sender === 'user' && m.text.includes('Spracheingabe wird verarbeitet')
|
||||||
|
);
|
||||||
|
if (idx >= 0) {
|
||||||
const next = prev.slice();
|
const next = prev.slice();
|
||||||
next[idx] = { ...next[idx], text: newText };
|
next[idx] = { ...next[idx], text: newText };
|
||||||
return next;
|
return next;
|
||||||
});
|
}
|
||||||
}
|
// Letzter Fallback: gar keine Placeholder \u2192 neue Bubble einfuegen
|
||||||
|
return capMessages([...prev, {
|
||||||
|
id: nextId(),
|
||||||
|
sender: 'user',
|
||||||
|
text: newText,
|
||||||
|
timestamp: message.timestamp,
|
||||||
|
attachments: [{ type: 'audio', name: 'Sprachaufnahme' }],
|
||||||
|
}]);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,7 +494,14 @@ const ChatScreen: React.FC = () => {
|
|||||||
// Conversation-Window: User hat X Sekunden um anzufangen, sonst Konversation aus
|
// Conversation-Window: User hat X Sekunden um anzufangen, sonst Konversation aus
|
||||||
const windowMs = await loadConvWindowMs();
|
const windowMs = await loadConvWindowMs();
|
||||||
const started = await audioService.startRecording(true, windowMs);
|
const started = await audioService.startRecording(true, windowMs);
|
||||||
if (!started) {
|
if (started) {
|
||||||
|
// Erst JETZT signalisieren dass das Mikro wirklich offen ist —
|
||||||
|
// vorher war's noch in der Init-Phase. So weiss der User exakt
|
||||||
|
// ab wann er reden kann. "Bereit"-Sound (Ding-Dong) ist optional
|
||||||
|
// ueber Settings → Wake-Word abschaltbar.
|
||||||
|
ToastAndroid.show('🎤 Mikro offen — sprich jetzt', ToastAndroid.SHORT);
|
||||||
|
playWakeReadySound().catch(() => {});
|
||||||
|
} else {
|
||||||
// Mikrofon nicht verfuegbar, naechsten Versuch
|
// Mikrofon nicht verfuegbar, naechsten Versuch
|
||||||
wakeWordService.resume();
|
wakeWordService.resume();
|
||||||
}
|
}
|
||||||
@@ -507,12 +515,14 @@ const ChatScreen: React.FC = () => {
|
|||||||
// Barge-In: laufende ARIA-Aktivitaet abbrechen wenn welche da ist.
|
// Barge-In: laufende ARIA-Aktivitaet abbrechen wenn welche da ist.
|
||||||
const wasInterrupted = interruptAriaIfBusy();
|
const wasInterrupted = interruptAriaIfBusy();
|
||||||
const location = await getCurrentLocation();
|
const location = await getCurrentLocation();
|
||||||
|
const audioRequestId = `audio_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
|
||||||
const userMsg: ChatMessage = {
|
const userMsg: ChatMessage = {
|
||||||
id: nextId(),
|
id: nextId(),
|
||||||
sender: 'user',
|
sender: 'user',
|
||||||
text: '🎙 Spracheingabe wird verarbeitet...',
|
text: '🎙 Spracheingabe wird verarbeitet...',
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
attachments: [{ type: 'audio', name: 'Sprachaufnahme' }],
|
attachments: [{ type: 'audio', name: 'Sprachaufnahme' }],
|
||||||
|
audioRequestId,
|
||||||
};
|
};
|
||||||
setMessages(prev => capMessages([...prev, userMsg]));
|
setMessages(prev => capMessages([...prev, userMsg]));
|
||||||
rvs.send('audio', {
|
rvs.send('audio', {
|
||||||
@@ -522,6 +532,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
voice: localXttsVoiceRef.current,
|
voice: localXttsVoiceRef.current,
|
||||||
speed: ttsSpeedRef.current,
|
speed: ttsSpeedRef.current,
|
||||||
interrupted: wasInterrupted,
|
interrupted: wasInterrupted,
|
||||||
|
audioRequestId,
|
||||||
...(location && { location }),
|
...(location && { location }),
|
||||||
});
|
});
|
||||||
// resume() wird durch onPlaybackFinished nach ARIAs Antwort getriggert.
|
// resume() wird durch onPlaybackFinished nach ARIAs Antwort getriggert.
|
||||||
@@ -677,12 +688,14 @@ const ChatScreen: React.FC = () => {
|
|||||||
// Barge-In: laufende ARIA-Aktivitaet abbrechen falls aktiv.
|
// Barge-In: laufende ARIA-Aktivitaet abbrechen falls aktiv.
|
||||||
const wasInterrupted = interruptAriaIfBusy();
|
const wasInterrupted = interruptAriaIfBusy();
|
||||||
const location = await getCurrentLocation();
|
const location = await getCurrentLocation();
|
||||||
|
const audioRequestId = `audio_${Date.now()}_${Math.floor(Math.random() * 100000)}`;
|
||||||
|
|
||||||
const userMsg: ChatMessage = {
|
const userMsg: ChatMessage = {
|
||||||
id: nextId(),
|
id: nextId(),
|
||||||
sender: 'user',
|
sender: 'user',
|
||||||
text: '🎙 Spracheingabe wird verarbeitet...',
|
text: '🎙 Spracheingabe wird verarbeitet...',
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
|
audioRequestId,
|
||||||
};
|
};
|
||||||
setMessages(prev => capMessages([...prev, userMsg]));
|
setMessages(prev => capMessages([...prev, userMsg]));
|
||||||
|
|
||||||
@@ -693,6 +706,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
voice: localXttsVoiceRef.current,
|
voice: localXttsVoiceRef.current,
|
||||||
speed: ttsSpeedRef.current,
|
speed: ttsSpeedRef.current,
|
||||||
interrupted: wasInterrupted,
|
interrupted: wasInterrupted,
|
||||||
|
audioRequestId,
|
||||||
...(location && { location }),
|
...(location && { location }),
|
||||||
});
|
});
|
||||||
}, [getCurrentLocation, interruptAriaIfBusy]);
|
}, [getCurrentLocation, interruptAriaIfBusy]);
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ import {
|
|||||||
TTS_SPEED_MAX,
|
TTS_SPEED_MAX,
|
||||||
TTS_SPEED_STORAGE_KEY,
|
TTS_SPEED_STORAGE_KEY,
|
||||||
} from '../services/audio';
|
} from '../services/audio';
|
||||||
|
import {
|
||||||
|
isWakeReadySoundEnabled,
|
||||||
|
setWakeReadySoundEnabled,
|
||||||
|
playWakeReadySound,
|
||||||
|
} from '../services/wakeReadySound';
|
||||||
import wakeWordService, {
|
import wakeWordService, {
|
||||||
WAKE_KEYWORDS,
|
WAKE_KEYWORDS,
|
||||||
KEYWORD_LABELS,
|
KEYWORD_LABELS,
|
||||||
@@ -122,6 +127,7 @@ const SettingsScreen: React.FC = () => {
|
|||||||
const [ttsSpeed, setTtsSpeed] = useState<number>(TTS_SPEED_DEFAULT);
|
const [ttsSpeed, setTtsSpeed] = useState<number>(TTS_SPEED_DEFAULT);
|
||||||
const [wakeKeyword, setWakeKeyword] = useState<string>(DEFAULT_KEYWORD);
|
const [wakeKeyword, setWakeKeyword] = useState<string>(DEFAULT_KEYWORD);
|
||||||
const [wakeStatus, setWakeStatus] = useState<string>('');
|
const [wakeStatus, setWakeStatus] = useState<string>('');
|
||||||
|
const [wakeReadySound, setWakeReadySound] = useState<boolean>(true);
|
||||||
const [editingPath, setEditingPath] = useState(false);
|
const [editingPath, setEditingPath] = useState(false);
|
||||||
const [xttsVoice, setXttsVoice] = useState('');
|
const [xttsVoice, setXttsVoice] = useState('');
|
||||||
const [loadingVoice, setLoadingVoice] = useState<string | null>(null);
|
const [loadingVoice, setLoadingVoice] = useState<string | null>(null);
|
||||||
@@ -194,6 +200,7 @@ const SettingsScreen: React.FC = () => {
|
|||||||
AsyncStorage.getItem(WAKE_KEYWORD_STORAGE).then(saved => {
|
AsyncStorage.getItem(WAKE_KEYWORD_STORAGE).then(saved => {
|
||||||
if (saved && (WAKE_KEYWORDS as readonly string[]).includes(saved)) setWakeKeyword(saved);
|
if (saved && (WAKE_KEYWORDS as readonly string[]).includes(saved)) setWakeKeyword(saved);
|
||||||
});
|
});
|
||||||
|
isWakeReadySoundEnabled().then(setWakeReadySound);
|
||||||
AsyncStorage.getItem('aria_xtts_voice').then(saved => {
|
AsyncStorage.getItem('aria_xtts_voice').then(saved => {
|
||||||
if (saved) setXttsVoice(saved);
|
if (saved) setXttsVoice(saved);
|
||||||
});
|
});
|
||||||
@@ -828,6 +835,31 @@ const SettingsScreen: React.FC = () => {
|
|||||||
{!!wakeStatus && (
|
{!!wakeStatus && (
|
||||||
<Text style={{marginTop: 8, fontSize: 12, color: '#8888AA'}}>{wakeStatus}</Text>
|
<Text style={{marginTop: 8, fontSize: 12, color: '#8888AA'}}>{wakeStatus}</Text>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<View style={[styles.toggleRow, {marginTop: 20, borderTopWidth: 1, borderTopColor: '#1E1E2E', paddingTop: 16}]}>
|
||||||
|
<View style={styles.toggleInfo}>
|
||||||
|
<Text style={styles.toggleLabel}>Bereit-Sound abspielen</Text>
|
||||||
|
<Text style={styles.toggleHint}>
|
||||||
|
Kurzer Ding-Dong wenn das Mikro nach Wake-Word offen ist —
|
||||||
|
akustische Bestaetigung dass du jetzt sprechen darfst.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Switch
|
||||||
|
value={wakeReadySound}
|
||||||
|
onValueChange={async (val) => {
|
||||||
|
setWakeReadySound(val);
|
||||||
|
await setWakeReadySoundEnabled(val);
|
||||||
|
if (val) {
|
||||||
|
// Direkt eine Vorschau abspielen damit der User weiss wie's klingt.
|
||||||
|
// playWakeReadySound checked das gerade gesetzte Flag — wenn val=true,
|
||||||
|
// wird abgespielt; bei false bleibt es still.
|
||||||
|
setTimeout(() => playWakeReadySound().catch(() => {}), 150);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
trackColor={{ false: '#2A2A3E', true: '#0096FF' }}
|
||||||
|
thumbColor={wakeReadySound ? '#FFFFFF' : '#666680'}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</>)}
|
</>)}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Spielt einen kurzen "Bereit"-Sound (Airplane Ding-Dong) wenn das Mikrofon
|
||||||
|
* nach Wake-Word-Erkennung wirklich offen ist. Datei liegt in
|
||||||
|
* android/app/src/main/res/raw/wake_ready_sound.mp3 — wird ueber Android's
|
||||||
|
* Resource-System per react-native-sound abgespielt.
|
||||||
|
*
|
||||||
|
* Toggle: AsyncStorage-Key 'aria_wake_ready_sound_enabled' (default true).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Sound from 'react-native-sound';
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
|
||||||
|
export const WAKE_READY_SOUND_STORAGE_KEY = 'aria_wake_ready_sound_enabled';
|
||||||
|
|
||||||
|
Sound.setCategory('Playback', false);
|
||||||
|
|
||||||
|
let cachedSound: Sound | null = null;
|
||||||
|
let cachedFailed = false;
|
||||||
|
|
||||||
|
function getSound(): Promise<Sound | null> {
|
||||||
|
if (cachedFailed) return Promise.resolve(null);
|
||||||
|
if (cachedSound) return Promise.resolve(cachedSound);
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const s = new Sound('wake_ready_sound', Sound.MAIN_BUNDLE, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.warn('[WakeReadySound] Konnte nicht geladen werden:', err);
|
||||||
|
cachedFailed = true;
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cachedSound = s;
|
||||||
|
resolve(s);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** True wenn der User den "Bereit"-Sound aktiviert hat. Default: true. */
|
||||||
|
export async function isWakeReadySoundEnabled(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const raw = await AsyncStorage.getItem(WAKE_READY_SOUND_STORAGE_KEY);
|
||||||
|
if (raw === null) return true; // Default an
|
||||||
|
return raw === 'true';
|
||||||
|
} catch {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setWakeReadySoundEnabled(enabled: boolean): Promise<void> {
|
||||||
|
try {
|
||||||
|
await AsyncStorage.setItem(WAKE_READY_SOUND_STORAGE_KEY, String(enabled));
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Spielt den Bereit-Sound einmal ab — non-blocking. Wenn der User ihn
|
||||||
|
* in den Settings deaktiviert hat oder die Datei nicht ladbar ist,
|
||||||
|
* passiert einfach nichts. */
|
||||||
|
export async function playWakeReadySound(): Promise<void> {
|
||||||
|
if (!(await isWakeReadySoundEnabled())) return;
|
||||||
|
const s = await getSound();
|
||||||
|
if (!s) return;
|
||||||
|
try {
|
||||||
|
s.stop(() => {
|
||||||
|
s.setCurrentTime(0);
|
||||||
|
s.play((success) => {
|
||||||
|
if (!success) console.warn('[WakeReadySound] Wiedergabe fehlgeschlagen');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[WakeReadySound] play() Exception:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -197,7 +197,9 @@ class WakeWordService {
|
|||||||
/** Wake-Word getriggert: Native-Modul pausieren, Konversation starten. */
|
/** Wake-Word getriggert: Native-Modul 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 "${KEYWORD_LABELS[this.keyword]}" erkannt — sprich jetzt`, ToastAndroid.SHORT);
|
// KEIN Toast hier — der Toast "sprich jetzt" kommt erst wenn das Mikro
|
||||||
|
// wirklich offen ist (audioService meldet 'recording'-State). So weiss
|
||||||
|
// der User exakt ab wann er reden darf.
|
||||||
if (this.nativeReady && OpenWakeWord) {
|
if (this.nativeReady && OpenWakeWord) {
|
||||||
try { await OpenWakeWord.stop(); } catch {}
|
try { await OpenWakeWord.stop(); } catch {}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-9
@@ -1510,10 +1510,12 @@ class ARIABridge:
|
|||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
self._next_speed_override = None
|
self._next_speed_override = None
|
||||||
interrupted = bool(payload.get("interrupted", False))
|
interrupted = bool(payload.get("interrupted", False))
|
||||||
logger.info("[rvs] Audio empfangen: %s, %dms, %dKB%s",
|
audio_request_id = payload.get("audioRequestId", "") or ""
|
||||||
|
logger.info("[rvs] Audio empfangen: %s, %dms, %dKB%s%s",
|
||||||
mime_type, duration_ms, len(audio_b64) // 1365,
|
mime_type, duration_ms, len(audio_b64) // 1365,
|
||||||
" [BARGE-IN]" if interrupted else "")
|
" [BARGE-IN]" if interrupted else "",
|
||||||
asyncio.create_task(self._process_app_audio(audio_b64, mime_type, interrupted))
|
f" reqId={audio_request_id[:16]}" if audio_request_id else "")
|
||||||
|
asyncio.create_task(self._process_app_audio(audio_b64, mime_type, interrupted, audio_request_id))
|
||||||
|
|
||||||
elif msg_type == "stt_response":
|
elif msg_type == "stt_response":
|
||||||
# Antwort der whisper-bridge auf unseren stt_request
|
# Antwort der whisper-bridge auf unseren stt_request
|
||||||
@@ -1569,13 +1571,19 @@ class ARIABridge:
|
|||||||
_STT_REMOTE_TIMEOUT_READY_S = 45.0
|
_STT_REMOTE_TIMEOUT_READY_S = 45.0
|
||||||
_STT_REMOTE_TIMEOUT_LOADING_S = 300.0
|
_STT_REMOTE_TIMEOUT_LOADING_S = 300.0
|
||||||
|
|
||||||
async def _process_app_audio(self, audio_b64: str, mime_type: str, interrupted: bool = False) -> None:
|
async def _process_app_audio(self, audio_b64: str, mime_type: str,
|
||||||
|
interrupted: bool = False,
|
||||||
|
audio_request_id: str = "") -> None:
|
||||||
"""App-Audio → STT → aria-core. Primaer via whisper-bridge (RVS), Fallback lokal.
|
"""App-Audio → STT → aria-core. Primaer via whisper-bridge (RVS), Fallback lokal.
|
||||||
|
|
||||||
interrupted=True wenn der User waehrend ARIA noch sprach/dachte aufgenommen hat
|
interrupted=True wenn der User waehrend ARIA noch sprach/dachte aufgenommen hat
|
||||||
(Barge-In). Wird als Hinweis-Praefix an aria-core mitgegeben damit ARIA die
|
(Barge-In). Wird als Hinweis-Praefix an aria-core mitgegeben damit ARIA die
|
||||||
Korrektur/Unterbrechung in den Kontext einordnen kann statt als reine
|
Korrektur/Unterbrechung in den Kontext einordnen kann statt als reine
|
||||||
Folgefrage zu behandeln."""
|
Folgefrage zu behandeln.
|
||||||
|
|
||||||
|
audio_request_id: Korrelations-ID die die App im audio-Event mitschickt — wird
|
||||||
|
unveraendert ans STT-Result zurueckgegeben damit die App die EXAKT richtige
|
||||||
|
'wird verarbeitet'-Bubble ersetzen kann (auch bei mehreren parallelen Aufnahmen)."""
|
||||||
# Erst Remote versuchen
|
# Erst Remote versuchen
|
||||||
text = await self._stt_remote(audio_b64, mime_type)
|
text = await self._stt_remote(audio_b64, mime_type)
|
||||||
if text is None:
|
if text is None:
|
||||||
@@ -1601,12 +1609,15 @@ class ARIABridge:
|
|||||||
# STT-Text an RVS senden (fuer Anzeige in App + Diagnostic)
|
# STT-Text an RVS senden (fuer Anzeige in App + Diagnostic)
|
||||||
# sender="stt" damit Bridge es ignoriert (kein Loop)
|
# sender="stt" damit Bridge es ignoriert (kein Loop)
|
||||||
try:
|
try:
|
||||||
|
stt_payload = {
|
||||||
|
"text": text,
|
||||||
|
"sender": "stt",
|
||||||
|
}
|
||||||
|
if audio_request_id:
|
||||||
|
stt_payload["audioRequestId"] = audio_request_id
|
||||||
ok = await self._send_to_rvs({
|
ok = await self._send_to_rvs({
|
||||||
"type": "chat",
|
"type": "chat",
|
||||||
"payload": {
|
"payload": stt_payload,
|
||||||
"text": text,
|
|
||||||
"sender": "stt",
|
|
||||||
},
|
|
||||||
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
"timestamp": int(asyncio.get_event_loop().time() * 1000),
|
||||||
})
|
})
|
||||||
if ok:
|
if ok:
|
||||||
|
|||||||
Reference in New Issue
Block a user