Files
map-client-vue/STOP_GENERATION_PATCH.md
2025-10-15 15:07:45 +08:00

5.6 KiB
Raw Permalink Blame History

停止生成功能 - 补充修复

问题描述

在之前的修复后,发现两个新问题:

  1. 按钮文字问题"确认"应该改为"发送"
  2. 停止后状态显示错误:点击停止后,消息仍然显示"发送中..."而不是"已停止"

原因分析

问题 1按钮文字

这是 UI 文案问题,直接修改即可。

问题 2状态显示错误

根本原因:在 chatStore.ts 的 catch 块中,当捕获到 AbortError 时,虽然不抛出错误,但也没有更新 UI 的消息列表,导致消息状态仍然是 sending

代码分析

catch (error: any) {
  // 如果是用户主动取消,不显示错误
  if (error.name !== 'AbortError') {
    throw error
  }
  // ❌ 问题:这里什么都不做,消息状态没有更新
}
finally {
  state.isSending = false  // 只重置了发送状态
  state.abortController = null
}

虽然 chatService 中已经将消息状态设置为 paused,但 UI 层面的 state.messages 没有重新加载,所以还显示旧的 sending 状态。

修复方案

1. 修改按钮文字

文件web/src/components/Chat/ChatLayout.vue

<!-- 发送/停止按钮 -->
<n-button
  size="small"
  :type="store.state.isSending ? 'error' : 'primary'"
  :disabled="!store.state.isSending && !inputText.trim()"
  @click="handleButtonClick"
>
  {{ store.state.isSending ? '停止' : '发送' }}
</n-button>

2. 在 AbortError 时更新消息状态

文件web/src/stores/chatStore.ts

catch (error: any) {
  // 如果是用户主动取消,也要更新消息列表(显示 paused 状态)
  if (error.name === 'AbortError') {
    console.log('⏸️ [sendMessageStream] 用户中止,更新消息状态')
    if (state.currentTopicId === currentTopicId) {
      state.messages = [...chatService.getMessages(currentTopicId)]
    }
    loadTopics()
  } else {
    throw error
  }
}
finally {
  state.isSending = false
  state.abortController = null
}

工作流程(更新后)

用户点击停止
    ↓
handleStopGeneration() → store.stopGeneration()
    ↓
abortController.abort() → 触发 AbortError
    ↓
chatService 捕获 AbortError
    ↓
设置 assistantMessage.status = 'paused'
    ↓
保存到 conversation
    ↓
抛出 AbortError 到 chatStore
    ↓
chatStore catch 块捕获 AbortError
    ↓
✅ 重新加载消息列表state.messages = [...chatService.getMessages()]
    ↓
✅ Vue 响应式系统检测到 messages 变化
    ↓
✅ UI 重新渲染,显示 "已停止" 标签
    ↓
finally 块state.isSending = false
    ↓
✅ 按钮文字变回 "发送"

关键改进

Before问题版本

catch (error: any) {
  if (error.name !== 'AbortError') {
    throw error
  }
  // ❌ AbortError 被静默忽略UI 不更新
}

After修复版本

catch (error: any) {
  if (error.name === 'AbortError') {
    // ✅ 更新消息列表,触发 UI 重新渲染
    if (state.currentTopicId === currentTopicId) {
      state.messages = [...chatService.getMessages(currentTopicId)]
    }
    loadTopics()
  } else {
    throw error
  }
}

测试验证

测试步骤

  1. 发送消息
  2. 在 AI 回复时点击"停止"按钮
  3. 验证点
    • 消息上方的标签从"发送中..."变为"已停止"(黄色)
    • 不再显示 loading 动画(三个跳动的点)
    • 按钮从"停止"变回"发送"
    • 显示消息操作按钮(复制、重新生成、删除)

预期 UI 变化

发送中:

AI 助手  14:53  [发送中...]
[... ... ...]  <- loading 动画
正在生成的文字...

停止后(修复前

AI 助手  14:53  [发送中...]  <- ❌ 错误:仍显示发送中
[... ... ...]  <- ❌ loading 动画还在
已生成的文字...

停止后(修复后

AI 助手  14:53  [已停止]  <- ✅ 正确:显示已停止
已生成的文字...
[复制] [重新生成] [删除]  <- ✅ 显示操作按钮

控制台日志

点击停止后应该看到:

🛑 [handleStopGeneration] 用户请求停止生成
🛑 [makeChatRequestStream] 检测到中止信号,停止读取流
⚠️ [makeChatRequestStream] 请求被中止: 用户中止操作
⏸️ [sendMessageStream] 用户主动停止生成,保留已生成内容
⏸️ [sendMessageStream] 用户中止,更新消息状态  <- ✅ 新增

修改文件清单

  1. web/src/components/Chat/ChatLayout.vue - 按钮文字
  2. web/src/stores/chatStore.ts - catch 块中更新消息列表

注意事项

为什么要重新加载消息列表?

  1. 消息状态更新在 Service 层chatService.sendMessageStream() 中修改了 message.status
  2. Store 层持有的是引用:虽然 service 中修改了对象,但 Vue 的响应式系统可能没有检测到
  3. 强制触发响应式更新:通过 [...chatService.getMessages()] 创建新数组,确保 Vue 检测到变化

为什么在 catch 块中而不是 finally

  • 时机问题:需要在消息状态已被设置为 paused 之后再更新 UI
  • 条件判断:只有 AbortError 才需要这个更新,其他错误不需要
  • finally 块作用只负责清理状态isSending、abortController不涉及业务逻辑

相关文档

  • STOP_GENERATION_SUMMARY.md - 初始修复总结
  • STOP_GENERATION_FIX.md - 详细技术文档
  • STOP_GENERATION_TEST.md - 测试指南

补充修复完成! 🎉

现在点击停止后:

  1. 按钮显示"发送"而不是"确认"
  2. 消息状态正确显示"已停止"而不是"发送中..."