diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx
index d6d1c84..d46222b 100644
--- a/android/src/screens/ChatScreen.tsx
+++ b/android/src/screens/ChatScreen.tsx
@@ -50,7 +50,7 @@ import VoiceButton from '../components/VoiceButton';
import FileUpload, { FileData } from '../components/FileUpload';
import CameraUpload, { PhotoData } from '../components/CameraUpload';
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';
// --- Typen ---
@@ -93,6 +93,9 @@ interface ChatMessage {
* gespiegelt damit wir die EXAKT richtige Placeholder-Bubble ersetzen,
* auch wenn mehrere Aufnahmen parallel offen sind. */
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 */
skillCreated?: {
name: string;
@@ -184,6 +187,14 @@ function stripSystemHints(text: string): string {
}
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 STORAGE_PATH_KEY = 'aria_attachment_storage_path';
@@ -1668,7 +1679,9 @@ const ChatScreen: React.FC = () => {
location: location || null,
noSpeechTimeoutMs: windowMs,
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,
});
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()) {
console.log('[Chat] STT-Endpoint: %r (reason=%s, %dms, %.1fs Audio)',
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
// zu 'conversing' damit der normale Flow greift (TTS, resume, etc.)
if (wakeWordService.getState() === 'listening') {
@@ -1771,7 +1791,8 @@ const ChatScreen: React.FC = () => {
location: location || null,
noSpeechTimeoutMs: windowMs,
endpointMs: await loadSttEndpointMs(),
- hardCapMs: 60000,
+ // Notbremse 5 min (s.o.) — lange Diktate nicht bei 1 min abschneiden.
+ hardCapMs: await loadMaxRecordingMs(),
projectId: focusedProjectIdRef.current,
});
if (ok) {
@@ -2255,7 +2276,7 @@ const ChatScreen: React.FC = () => {
// die Session auch app-seitig haben wir +2s Toleranz.
noSpeechTimeoutMs: 0,
endpointMs: await loadSttEndpointMs(),
- hardCapMs: 300000,
+ hardCapMs: await loadMaxRecordingMs(),
projectId: focusedProjectIdRef.current,
});
if (!ok) {
@@ -2647,6 +2668,16 @@ const ChatScreen: React.FC = () => {
{att.serverPath ? '(tippen zum Laden)' : '(nicht verfuegbar)'}
+ ) : att.type === 'audio' ? (
+
+ {'🎙'}
+
+ {att.name || 'Sprachaufnahme'}
+
+ {typeof item.durationS === 'number' && item.durationS > 0 ? (
+ {formatDur(item.durationS)}
+ ) : null}
+
) : (