5.6 KiB
5.6 KiB
停止生成功能 - 补充修复
问题描述
在之前的修复后,发现两个新问题:
- 按钮文字问题:"确认"应该改为"发送"
- 停止后状态显示错误:点击停止后,消息仍然显示"发送中..."而不是"已停止"
原因分析
问题 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
}
}
测试验证
测试步骤
- 发送消息
- 在 AI 回复时点击"停止"按钮
- 验证点:
- ✅ 消息上方的标签从"发送中..."变为"已停止"(黄色)
- ✅ 不再显示 loading 动画(三个跳动的点)
- ✅ 按钮从"停止"变回"发送"
- ✅ 显示消息操作按钮(复制、重新生成、删除)
预期 UI 变化
发送中:
AI 助手 14:53 [发送中...]
[... ... ...] <- loading 动画
正在生成的文字...
停止后(修复前 ❌):
AI 助手 14:53 [发送中...] <- ❌ 错误:仍显示发送中
[... ... ...] <- ❌ loading 动画还在
已生成的文字...
停止后(修复后 ✅):
AI 助手 14:53 [已停止] <- ✅ 正确:显示已停止
已生成的文字...
[复制] [重新生成] [删除] <- ✅ 显示操作按钮
控制台日志
点击停止后应该看到:
🛑 [handleStopGeneration] 用户请求停止生成
🛑 [makeChatRequestStream] 检测到中止信号,停止读取流
⚠️ [makeChatRequestStream] 请求被中止: 用户中止操作
⏸️ [sendMessageStream] 用户主动停止生成,保留已生成内容
⏸️ [sendMessageStream] 用户中止,更新消息状态 <- ✅ 新增
修改文件清单
- ✅
web/src/components/Chat/ChatLayout.vue- 按钮文字 - ✅
web/src/stores/chatStore.ts- catch 块中更新消息列表
注意事项
为什么要重新加载消息列表?
- 消息状态更新在 Service 层:
chatService.sendMessageStream()中修改了 message.status - Store 层持有的是引用:虽然 service 中修改了对象,但 Vue 的响应式系统可能没有检测到
- 强制触发响应式更新:通过
[...chatService.getMessages()]创建新数组,确保 Vue 检测到变化
为什么在 catch 块中而不是 finally?
- 时机问题:需要在消息状态已被设置为
paused之后再更新 UI - 条件判断:只有 AbortError 才需要这个更新,其他错误不需要
- finally 块作用:只负责清理状态(isSending、abortController),不涉及业务逻辑
相关文档
STOP_GENERATION_SUMMARY.md- 初始修复总结STOP_GENERATION_FIX.md- 详细技术文档STOP_GENERATION_TEST.md- 测试指南
补充修复完成! 🎉
现在点击停止后:
- ✅ 按钮显示"发送"而不是"确认"
- ✅ 消息状态正确显示"已停止"而不是"发送中..."