update at 2025-10-15 15:07:45
This commit is contained in:
208
STOP_GENERATION_PATCH.md
Normal file
208
STOP_GENERATION_PATCH.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# 停止生成功能 - 补充修复
|
||||
|
||||
## 问题描述
|
||||
|
||||
在之前的修复后,发现两个新问题:
|
||||
1. **按钮文字问题**:"确认"应该改为"发送"
|
||||
2. **停止后状态显示错误**:点击停止后,消息仍然显示"发送中..."而不是"已停止"
|
||||
|
||||
## 原因分析
|
||||
|
||||
### 问题 1:按钮文字
|
||||
这是 UI 文案问题,直接修改即可。
|
||||
|
||||
### 问题 2:状态显示错误
|
||||
**根本原因**:在 `chatStore.ts` 的 catch 块中,当捕获到 `AbortError` 时,虽然不抛出错误,但也没有更新 UI 的消息列表,导致消息状态仍然是 `sending`。
|
||||
|
||||
**代码分析**:
|
||||
```typescript
|
||||
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`
|
||||
|
||||
```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`
|
||||
|
||||
```typescript
|
||||
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(问题版本)
|
||||
```typescript
|
||||
catch (error: any) {
|
||||
if (error.name !== 'AbortError') {
|
||||
throw error
|
||||
}
|
||||
// ❌ AbortError 被静默忽略,UI 不更新
|
||||
}
|
||||
```
|
||||
|
||||
### After(修复版本)
|
||||
```typescript
|
||||
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. ✅ 消息状态正确显示"已停止"而不是"发送中..."
|
||||
Reference in New Issue
Block a user