fix(voice): Hard-Cap an 'Max. Aufnahmedauer'-Setting + Dauer an Bubble
Wake-Word/Barge-In waren hart auf 60s gecappt (schnitt lange Diktate bei 1 min ab). Jetzt lesen sie loadMaxRecordingMs() — der bestehende, aber vom Streaming-Pfad abgeklemmte 'Maximale Aufnahmedauer'-Regler (1-30 min) steuert nun wirklich. Voice-Bubbles zeigen die Aufnahmedauer (durationS aus stt_endpoint) als 'M:SS'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -50,7 +50,7 @@ import VoiceButton from '../components/VoiceButton';
|
|||||||
import FileUpload, { FileData } from '../components/FileUpload';
|
import FileUpload, { FileData } from '../components/FileUpload';
|
||||||
import CameraUpload, { PhotoData } from '../components/CameraUpload';
|
import CameraUpload, { PhotoData } from '../components/CameraUpload';
|
||||||
import MessageText from '../components/MessageText';
|
import MessageText from '../components/MessageText';
|
||||||
import { loadConvWindowMs, loadTtsSpeed, TTS_SPEED_DEFAULT, loadSttEndpointMs } from '../services/audio';
|
import { loadConvWindowMs, loadTtsSpeed, TTS_SPEED_DEFAULT, loadSttEndpointMs, loadMaxRecordingMs } from '../services/audio';
|
||||||
import Geolocation from '@react-native-community/geolocation';
|
import Geolocation from '@react-native-community/geolocation';
|
||||||
|
|
||||||
// --- Typen ---
|
// --- Typen ---
|
||||||
@@ -93,6 +93,9 @@ interface ChatMessage {
|
|||||||
* gespiegelt damit wir die EXAKT richtige Placeholder-Bubble ersetzen,
|
* gespiegelt damit wir die EXAKT richtige Placeholder-Bubble ersetzen,
|
||||||
* auch wenn mehrere Aufnahmen parallel offen sind. */
|
* auch wenn mehrere Aufnahmen parallel offen sind. */
|
||||||
audioRequestId?: string;
|
audioRequestId?: string;
|
||||||
|
/** Laenge der Sprachaufnahme in Sekunden (aus dem stt_endpoint) — fuer die
|
||||||
|
* Dauer-Anzeige an der Voice-Bubble. */
|
||||||
|
durationS?: number;
|
||||||
/** Skill-Created-Bubble: ARIA hat einen neuen Skill angelegt */
|
/** Skill-Created-Bubble: ARIA hat einen neuen Skill angelegt */
|
||||||
skillCreated?: {
|
skillCreated?: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -184,6 +187,14 @@ function stripSystemHints(text: string): string {
|
|||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
/** Sekunden → "M:SS" fuer die Sprachnachricht-Dauer. */
|
||||||
|
function formatDur(sec: number): string {
|
||||||
|
const s = Math.max(0, Math.round(sec));
|
||||||
|
const m = Math.floor(s / 60);
|
||||||
|
const r = s % 60;
|
||||||
|
return `${m}:${r.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
const DEFAULT_ATTACHMENT_DIR = `${RNFS.DocumentDirectoryPath}/chat_attachments`;
|
const DEFAULT_ATTACHMENT_DIR = `${RNFS.DocumentDirectoryPath}/chat_attachments`;
|
||||||
const STORAGE_PATH_KEY = 'aria_attachment_storage_path';
|
const STORAGE_PATH_KEY = 'aria_attachment_storage_path';
|
||||||
|
|
||||||
@@ -1668,7 +1679,9 @@ const ChatScreen: React.FC = () => {
|
|||||||
location: location || null,
|
location: location || null,
|
||||||
noSpeechTimeoutMs: windowMs,
|
noSpeechTimeoutMs: windowMs,
|
||||||
endpointMs: await loadSttEndpointMs(),
|
endpointMs: await loadSttEndpointMs(),
|
||||||
hardCapMs: 60000,
|
// 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(),
|
||||||
projectId: focusedProjectIdRef.current,
|
projectId: focusedProjectIdRef.current,
|
||||||
});
|
});
|
||||||
import('../services/logger').then(m => m.reportAppDebug('wake.cb', `startStreamingRecording returned ok=${ok}`)).catch(()=>{});
|
import('../services/logger').then(m => m.reportAppDebug('wake.cb', `startStreamingRecording returned ok=${ok}`)).catch(()=>{});
|
||||||
@@ -1696,6 +1709,13 @@ const ChatScreen: React.FC = () => {
|
|||||||
if (ev.text && ev.text.trim()) {
|
if (ev.text && ev.text.trim()) {
|
||||||
console.log('[Chat] STT-Endpoint: %r (reason=%s, %dms, %.1fs Audio)',
|
console.log('[Chat] STT-Endpoint: %r (reason=%s, %dms, %.1fs Audio)',
|
||||||
ev.text.slice(0, 80), ev.reason, ev.sttMs, ev.durationS);
|
ev.text.slice(0, 80), ev.reason, ev.sttMs, ev.durationS);
|
||||||
|
// Aufnahme-Dauer an die passende Voice-Bubble haengen (Anzeige). Der
|
||||||
|
// spaetere STT-Text-Update spreadet die Message, die Dauer bleibt.
|
||||||
|
if (ev.audioRequestId && typeof ev.durationS === 'number' && ev.durationS > 0) {
|
||||||
|
const dur = ev.durationS;
|
||||||
|
setMessages(prev => prev.map(m =>
|
||||||
|
m.audioRequestId === ev.audioRequestId ? { ...m, durationS: dur } : m));
|
||||||
|
}
|
||||||
// Wenn passive lauschend: User hat tatsaechlich was gesagt → uebergang
|
// Wenn passive lauschend: User hat tatsaechlich was gesagt → uebergang
|
||||||
// zu 'conversing' damit der normale Flow greift (TTS, resume, etc.)
|
// zu 'conversing' damit der normale Flow greift (TTS, resume, etc.)
|
||||||
if (wakeWordService.getState() === 'listening') {
|
if (wakeWordService.getState() === 'listening') {
|
||||||
@@ -1771,7 +1791,8 @@ const ChatScreen: React.FC = () => {
|
|||||||
location: location || null,
|
location: location || null,
|
||||||
noSpeechTimeoutMs: windowMs,
|
noSpeechTimeoutMs: windowMs,
|
||||||
endpointMs: await loadSttEndpointMs(),
|
endpointMs: await loadSttEndpointMs(),
|
||||||
hardCapMs: 60000,
|
// Notbremse 5 min (s.o.) — lange Diktate nicht bei 1 min abschneiden.
|
||||||
|
hardCapMs: await loadMaxRecordingMs(),
|
||||||
projectId: focusedProjectIdRef.current,
|
projectId: focusedProjectIdRef.current,
|
||||||
});
|
});
|
||||||
if (ok) {
|
if (ok) {
|
||||||
@@ -2255,7 +2276,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
// die Session auch app-seitig haben wir +2s Toleranz.
|
// die Session auch app-seitig haben wir +2s Toleranz.
|
||||||
noSpeechTimeoutMs: 0,
|
noSpeechTimeoutMs: 0,
|
||||||
endpointMs: await loadSttEndpointMs(),
|
endpointMs: await loadSttEndpointMs(),
|
||||||
hardCapMs: 300000,
|
hardCapMs: await loadMaxRecordingMs(),
|
||||||
projectId: focusedProjectIdRef.current,
|
projectId: focusedProjectIdRef.current,
|
||||||
});
|
});
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
@@ -2647,6 +2668,16 @@ const ChatScreen: React.FC = () => {
|
|||||||
{att.serverPath ? '(tippen zum Laden)' : '(nicht verfuegbar)'}
|
{att.serverPath ? '(tippen zum Laden)' : '(nicht verfuegbar)'}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
) : att.type === 'audio' ? (
|
||||||
|
<View style={styles.attachmentFile}>
|
||||||
|
<Text style={styles.attachmentFileIcon}>{'🎙'}</Text>
|
||||||
|
<Text style={styles.attachmentFileName} numberOfLines={1}>
|
||||||
|
{att.name || 'Sprachaufnahme'}
|
||||||
|
</Text>
|
||||||
|
{typeof item.durationS === 'number' && item.durationS > 0 ? (
|
||||||
|
<Text style={styles.attachmentFileSize}>{formatDur(item.durationS)}</Text>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.attachmentFile}
|
style={styles.attachmentFile}
|
||||||
|
|||||||
Reference in New Issue
Block a user