| .. | .. |
|---|
| 214 | 214 | final messageId = msg['messageId'] as String?; |
|---|
| 215 | 215 | final content = msg['content'] as String?; |
|---|
| 216 | 216 | if (messageId != null && content != null) { |
|---|
| 217 | | - ref.read(messagesProvider.notifier).updateContent(messageId, content); |
|---|
| 217 | + // Try updating in current session first |
|---|
| 218 | + final currentMessages = ref.read(messagesProvider); |
|---|
| 219 | + final inCurrent = currentMessages.any((m) => m.id == messageId); |
|---|
| 220 | + if (inCurrent) { |
|---|
| 221 | + ref.read(messagesProvider.notifier).updateContent(messageId, content); |
|---|
| 222 | + } else { |
|---|
| 223 | + // Message is in a different session (user switched after recording). |
|---|
| 224 | + // Load that session's messages from disk, update, and save back. |
|---|
| 225 | + _updateTranscriptOnDisk(messageId, content); |
|---|
| 226 | + } |
|---|
| 218 | 227 | } |
|---|
| 219 | 228 | case 'unread': |
|---|
| 220 | 229 | final sessionId = msg['sessionId'] as String?; |
|---|
| .. | .. |
|---|
| 391 | 400 | void _handleIncomingImage(Map<String, dynamic> msg) { |
|---|
| 392 | 401 | final imageData = msg['imageBase64'] as String? ?? msg['data'] as String? ?? msg['image'] as String?; |
|---|
| 393 | 402 | final content = msg['content'] as String? ?? msg['caption'] as String? ?? ''; |
|---|
| 403 | + final sessionId = msg['sessionId'] as String?; |
|---|
| 394 | 404 | |
|---|
| 395 | 405 | if (imageData == null) return; |
|---|
| 396 | 406 | |
|---|
| 397 | | - // Always update the Navigate screen screenshot |
|---|
| 407 | + // Always update the Navigate screen screenshot provider |
|---|
| 398 | 408 | ref.read(latestScreenshotProvider.notifier).state = imageData; |
|---|
| 399 | 409 | |
|---|
| 400 | | - final isScreenshot = content == 'Screenshot' || content == 'Capturing screenshot...'; |
|---|
| 410 | + final isScreenshot = content == 'Screenshot' || |
|---|
| 411 | + content == 'Capturing screenshot...' || |
|---|
| 412 | + (msg['type'] == 'screenshot'); |
|---|
| 401 | 413 | |
|---|
| 402 | 414 | if (isScreenshot) { |
|---|
| 403 | 415 | // Remove any "Capturing screenshot..." placeholder text messages |
|---|
| .. | .. |
|---|
| 405 | 417 | (m) => m.role == MessageRole.assistant && m.content == 'Capturing screenshot...', |
|---|
| 406 | 418 | ); |
|---|
| 407 | 419 | |
|---|
| 408 | | - // Only add to chat if the Screen button requested it |
|---|
| 420 | + // Only add to chat if the Screen button explicitly requested it |
|---|
| 409 | 421 | if (!_screenshotForChat) { |
|---|
| 410 | 422 | ref.read(isTypingProvider.notifier).state = false; |
|---|
| 411 | 423 | return; |
|---|
| .. | .. |
|---|
| 419 | 431 | content: content, |
|---|
| 420 | 432 | status: MessageStatus.sent, |
|---|
| 421 | 433 | ); |
|---|
| 434 | + |
|---|
| 435 | + // Cross-session routing: store for target session if not active |
|---|
| 436 | + final activeId = ref.read(activeSessionIdProvider); |
|---|
| 437 | + if (sessionId != null && sessionId != activeId) { |
|---|
| 438 | + _storeForSession(sessionId, message); |
|---|
| 439 | + _incrementUnread(sessionId); |
|---|
| 440 | + return; |
|---|
| 441 | + } |
|---|
| 422 | 442 | |
|---|
| 423 | 443 | ref.read(messagesProvider.notifier).addMessage(message); |
|---|
| 424 | 444 | ref.read(isTypingProvider.notifier).state = false; |
|---|
| .. | .. |
|---|
| 436 | 456 | _chatLog('storeForSession: verified ${verify.length} messages after save'); |
|---|
| 437 | 457 | } |
|---|
| 438 | 458 | |
|---|
| 459 | + /// Update a transcript for a message stored on disk (not in the active session). |
|---|
| 460 | + /// Scans all session files to find the message by ID, updates content, and saves. |
|---|
| 461 | + Future<void> _updateTranscriptOnDisk(String messageId, String content) async { |
|---|
| 462 | + try { |
|---|
| 463 | + final dir = await getApplicationDocumentsDirectory(); |
|---|
| 464 | + final msgDir = Directory('${dir.path}/messages'); |
|---|
| 465 | + if (!await msgDir.exists()) return; |
|---|
| 466 | + |
|---|
| 467 | + await for (final entity in msgDir.list()) { |
|---|
| 468 | + if (entity is! File || !entity.path.endsWith('.json')) continue; |
|---|
| 469 | + |
|---|
| 470 | + final jsonStr = await entity.readAsString(); |
|---|
| 471 | + final List<dynamic> jsonList = jsonDecode(jsonStr) as List<dynamic>; |
|---|
| 472 | + bool found = false; |
|---|
| 473 | + |
|---|
| 474 | + final updated = jsonList.map((j) { |
|---|
| 475 | + final map = j as Map<String, dynamic>; |
|---|
| 476 | + if (map['id'] == messageId) { |
|---|
| 477 | + found = true; |
|---|
| 478 | + return {...map, 'content': content}; |
|---|
| 479 | + } |
|---|
| 480 | + return map; |
|---|
| 481 | + }).toList(); |
|---|
| 482 | + |
|---|
| 483 | + if (found) { |
|---|
| 484 | + await entity.writeAsString(jsonEncode(updated)); |
|---|
| 485 | + _chatLog('transcript: updated messageId=$messageId on disk in ${entity.path.split('/').last}'); |
|---|
| 486 | + return; |
|---|
| 487 | + } |
|---|
| 488 | + } |
|---|
| 489 | + _chatLog('transcript: messageId=$messageId not found on disk'); |
|---|
| 490 | + } catch (e) { |
|---|
| 491 | + _chatLog('transcript: disk update error=$e'); |
|---|
| 492 | + } |
|---|
| 493 | + } |
|---|
| 494 | + |
|---|
| 439 | 495 | void _incrementUnread(String sessionId) { |
|---|
| 440 | 496 | final counts = Map<String, int>.from(ref.read(unreadCountsProvider)); |
|---|
| 441 | 497 | counts[sessionId] = (counts[sessionId] ?? 0) + 1; |
|---|