| .. | .. |
|---|
| 20 | 20 | import '../services/audio_service.dart'; |
|---|
| 21 | 21 | import '../services/message_store.dart'; |
|---|
| 22 | 22 | import '../services/mqtt_service.dart'; |
|---|
| 23 | +import '../services/trace_service.dart'; |
|---|
| 23 | 24 | import '../services/navigate_notifier.dart'; |
|---|
| 24 | 25 | import '../services/push_service.dart'; |
|---|
| 25 | 26 | import '../theme/app_theme.dart'; |
|---|
| .. | .. |
|---|
| 43 | 44 | |
|---|
| 44 | 45 | Future<void> _chatLog(String msg) async { |
|---|
| 45 | 46 | debugPrint('[Chat] $msg'); |
|---|
| 47 | + TraceService.instance.addTrace('Chat', msg); |
|---|
| 46 | 48 | if (!kDebugMode) return; |
|---|
| 47 | 49 | try { |
|---|
| 48 | 50 | final dir = await getApplicationDocumentsDirectory(); |
|---|
| .. | .. |
|---|
| 256 | 258 | |
|---|
| 257 | 259 | void _handleMessage(Map<String, dynamic> msg) { |
|---|
| 258 | 260 | final type = msg['type'] as String?; |
|---|
| 261 | + final msgSessionId = msg['sessionId'] as String?; |
|---|
| 262 | + final msgSeq = msg['seq']; |
|---|
| 263 | + |
|---|
| 264 | + TraceService.instance.addTrace( |
|---|
| 265 | + 'handleMessage processing', |
|---|
| 266 | + 'type=$type sessionId=${msgSessionId?.substring(0, msgSessionId.length.clamp(0, 8))} seq=$msgSeq', |
|---|
| 267 | + ); |
|---|
| 259 | 268 | |
|---|
| 260 | 269 | // Track sequence numbers for catch_up protocol |
|---|
| 261 | 270 | final seq = msg['seq'] as int?; |
|---|
| 262 | 271 | if (seq != null) { |
|---|
| 263 | 272 | // Dedup: skip messages we've already processed |
|---|
| 264 | | - if (_seenSeqs.contains(seq)) return; |
|---|
| 273 | + if (_seenSeqs.contains(seq)) { |
|---|
| 274 | + TraceService.instance.addTrace( |
|---|
| 275 | + 'handleMessage seq deduped', |
|---|
| 276 | + 'seq=$seq type=$type — already seen, dropping', |
|---|
| 277 | + ); |
|---|
| 278 | + return; |
|---|
| 279 | + } |
|---|
| 265 | 280 | _seenSeqs.add(seq); |
|---|
| 266 | 281 | _seenSeqsList.add(seq); |
|---|
| 267 | 282 | // Keep bounded at 500 with O(1) FIFO eviction (drop oldest first) |
|---|
| .. | .. |
|---|
| 491 | 506 | msg['text'] as String? ?? |
|---|
| 492 | 507 | ''; |
|---|
| 493 | 508 | |
|---|
| 509 | + TraceService.instance.addTrace( |
|---|
| 510 | + 'handleMessage processing type=text', |
|---|
| 511 | + 'sessionId=${sessionId?.substring(0, sessionId.length.clamp(0, 8))}', |
|---|
| 512 | + ); |
|---|
| 513 | + |
|---|
| 494 | 514 | final message = Message.text( |
|---|
| 495 | 515 | role: MessageRole.assistant, |
|---|
| 496 | 516 | content: content, |
|---|
| .. | .. |
|---|
| 500 | 520 | final activeId = ref.read(activeSessionIdProvider); |
|---|
| 501 | 521 | if (sessionId != null && sessionId != activeId) { |
|---|
| 502 | 522 | // Store message for the other session so it's there when user switches |
|---|
| 523 | + TraceService.instance.addTrace( |
|---|
| 524 | + 'message stored for session', |
|---|
| 525 | + 'sessionId=${sessionId.substring(0, sessionId.length.clamp(0, 8))}, toast shown', |
|---|
| 526 | + ); |
|---|
| 503 | 527 | await _storeForSession(sessionId, message); |
|---|
| 504 | 528 | _incrementUnread(sessionId); |
|---|
| 505 | 529 | final sessions = ref.read(sessionsProvider); |
|---|
| .. | .. |
|---|
| 516 | 540 | ); |
|---|
| 517 | 541 | } |
|---|
| 518 | 542 | } else { |
|---|
| 543 | + TraceService.instance.addTrace( |
|---|
| 544 | + 'message displayed in chat', |
|---|
| 545 | + 'sessionId=${sessionId?.substring(0, sessionId.length.clamp(0, 8)) ?? "global"} len=${content.length}', |
|---|
| 546 | + ); |
|---|
| 519 | 547 | ref.read(messagesProvider.notifier).addMessage(message); |
|---|
| 520 | 548 | ref.read(isTypingProvider.notifier).state = false; |
|---|
| 521 | 549 | _scrollToBottom(); |
|---|