Matthias Nott
2026-03-22 fee149c1fbc63f52aa66148018e569e30cca6161
fix: voice transcript display, audio file persistence, debug logging

- Read 'transcript' field from MQTT voice payloads (server uses this, not 'content')
- Save audio to file for cross-session voice messages (survives toJsonLight strip)
- Keep audioUri in toJsonLight when it's a file path (starts with '/')
- Await _storeForSession before showing toast
- Add debug logging to voice handler and store operations
2 files modified
changed files
lib/models/message.dart patch | view | blame | history
lib/screens/chat_screen.dart patch | view | blame | history
lib/models/message.dart
....@@ -114,18 +114,21 @@
114114 };
115115 }
116116
117
- /// Lightweight JSON for persistence (strips temp audio paths, keeps images).
117
+ /// Lightweight JSON for persistence (strips base64 audio, keeps file paths and images).
118118 Map<String, dynamic> toJsonLight() {
119
+ // Keep audioUri if it's a file path (starts with '/') — these are saved audio files.
120
+ // Strip base64 audio data (large, temp) — those won't survive restart.
121
+ final keepAudio = audioUri != null && audioUri!.startsWith('/');
119122 return {
120123 'id': id,
121124 'role': role.name,
122125 'type': type.name,
123126 'content': content,
127
+ if (keepAudio) 'audioUri': audioUri,
124128 'timestamp': timestamp,
125129 if (status != null) 'status': status!.name,
126130 if (duration != null) 'duration': duration,
127131 // Keep imageBase64 — images are typically 50-200 KB and must survive restart.
128
- // audioUri is intentionally omitted: it is a temp file path that won't survive restart.
129132 if (imageBase64 != null) 'imageBase64': imageBase64,
130133 };
131134 }
lib/screens/chat_screen.dart
....@@ -34,6 +34,15 @@
3434 ConsumerState<ChatScreen> createState() => _ChatScreenState();
3535 }
3636
37
+Future<void> _chatLog(String msg) async {
38
+ try {
39
+ final dir = await getApplicationDocumentsDirectory();
40
+ final file = File('${dir.path}/mqtt_debug.log');
41
+ final ts = DateTime.now().toIso8601String().substring(11, 19);
42
+ await file.writeAsString('[$ts] $msg\n', mode: FileMode.append);
43
+ } catch (_) {}
44
+}
45
+
3746 class _ChatScreenState extends ConsumerState<ChatScreen>
3847 with WidgetsBindingObserver {
3948 MqttService? _ws;
....@@ -307,7 +316,7 @@
307316 Future<void> _handleIncomingVoice(Map<String, dynamic> msg) async {
308317 final sessionId = msg['sessionId'] as String?;
309318 final audioData = msg['audioBase64'] as String? ?? msg['audio'] as String? ?? msg['data'] as String?;
310
- final content = msg['content'] as String? ?? msg['text'] as String? ?? '';
319
+ final content = msg['content'] as String? ?? msg['transcript'] as String? ?? msg['text'] as String? ?? '';
311320 final duration = msg['duration'] as int?;
312321
313322 final message = Message(
....@@ -346,8 +355,11 @@
346355 );
347356
348357 final activeId = ref.read(activeSessionIdProvider);
358
+ _chatLog('voice: sessionId=$sessionId activeId=$activeId audioPath=$savedAudioPath content="${content.substring(0, content.length.clamp(0, 30))}"');
349359 if (sessionId != null && sessionId != activeId) {
360
+ _chatLog('voice: cross-session, storing for $sessionId');
350361 await _storeForSession(sessionId, storedMessage);
362
+ _chatLog('voice: stored, incrementing unread');
351363 _incrementUnread(sessionId);
352364 final sessions = ref.read(sessionsProvider);
353365 final session = sessions.firstWhere(
....@@ -414,8 +426,12 @@
414426 /// Store a message for a non-active session so it persists when the user switches to it.
415427 Future<void> _storeForSession(String sessionId, Message message) async {
416428 final existing = await MessageStore.loadAll(sessionId);
429
+ _chatLog('storeForSession: $sessionId existing=${existing.length} adding type=${message.type.name} content="${message.content.substring(0, message.content.length.clamp(0, 30))}" audioUri=${message.audioUri != null ? "set(${message.audioUri!.length})" : "null"}');
417430 MessageStore.save(sessionId, [...existing, message]);
418431 await MessageStore.flush();
432
+ // Verify
433
+ final verify = await MessageStore.loadAll(sessionId);
434
+ _chatLog('storeForSession: verified ${verify.length} messages after save');
419435 }
420436
421437 void _incrementUnread(String sessionId) {