@@ -626,24 +626,16 @@ class ChatNotifier extends StateNotifier<ChatState> {
626626 return ;
627627 }
628628
629+ // ─── 防御性清理:上一轮被中断后 AgentLoop generator 可能在后续微任务
630+ // 中继续往 _agentMessages 追加了 tool/assistant(tool_calls) 消息,
631+ // 导致消息链不合法。在新一轮起步前再做一次 sanitize 确保干净。
632+ _sanitizeAgentMessages ();
633+
629634 // 快照附件并立即清空 UI 中的附件列表
630635 final pendingAttachments = List <FileAttachment >.from (state.attachments);
631636
632- // 如果当前没有会话,创建一个
633- int ? conversationId = state.currentConversationId;
634- if (conversationId == null ) {
635- final title = input.length > 20 ? '${input .substring (0 , 20 )}...' : input;
636- final conversation = await _conversationsDao.create (
637- title: title,
638- modelCardId: modelCard.id,
639- );
640- conversationId = conversation.id;
641- state = state.copyWith (currentConversationId: conversationId);
642- _ref.read (conversationsProvider.notifier).refresh ();
643- }
644-
637+ // ─── 立即更新 UI 状态 → 让按钮点击有即时视觉反馈,消除卡顿感 ───
645638 final userMsg = ChatMessage .user (input, attachments: pendingAttachments);
646- // 新一轮开始前清理可能残留的流式缓冲/计时器
647639 _flushTimer? .cancel ();
648640 _flushTimer = null ;
649641 _streamBuffer.clear ();
@@ -659,6 +651,24 @@ class ChatNotifier extends StateNotifier<ChatState> {
659651 preview: input.length > 30 ? '${input .substring (0 , 30 )}...' : input,
660652 );
661653
654+ // ─── 让出一帧给 UI 渲染 loading 状态,然后再执行重活 ───
655+ // 这保证了上面 setState 的新状态能先被 Flutter 引擎处理并绘制,
656+ // 用户看到的是"发送后立即进入 loading 动画",而不是"按钮卡住不动"。
657+ await Future <void >.delayed (Duration .zero);
658+
659+ // 如果当前没有会话,创建一个
660+ int ? conversationId = state.currentConversationId;
661+ if (conversationId == null ) {
662+ final title = input.length > 20 ? '${input .substring (0 , 20 )}...' : input;
663+ final conversation = await _conversationsDao.create (
664+ title: title,
665+ modelCardId: modelCard.id,
666+ );
667+ conversationId = conversation.id;
668+ state = state.copyWith (currentConversationId: conversationId);
669+ _ref.read (conversationsProvider.notifier).refresh ();
670+ }
671+
662672 await _conversationsDao.saveMessage (conversationId, userMsg);
663673
664674 // 处理附件为 content parts
@@ -1042,6 +1052,15 @@ class ChatNotifier extends StateNotifier<ChatState> {
10421052 activeToolCalls: [],
10431053 );
10441054 }
1055+
1056+ // ─── 延迟防御清理 ───
1057+ // _subscription?.cancel() 只是调度了取消,AgentLoop 的 async* generator
1058+ // 在后续微任务中可能仍会往 _agentMessages 追加消息(比如 tool result 或
1059+ // assistant(tool_calls)),导致消息链再次进入不合法状态。
1060+ // 调度一个延迟微任务,在 generator 最终停止后做最后一遍 sanitize。
1061+ Future <void >.delayed (const Duration (milliseconds: 50 )).then ((_) {
1062+ if (mounted) _sanitizeAgentMessages ();
1063+ });
10451064 }
10461065
10471066 /// 删除指定索引的消息
0 commit comments