# 停止生成功能修复文档 ## 问题描述 1. **按钮点击无效**:确认/停止按钮点击没有响应 2. **停止逻辑不生效**:即使调用了 `stopGeneration()`,流式输出仍在继续 ## 参考实现 参考了 Cherry Studio 中的 **PAUSED** 状态设计理念。 ## 修复内容 ### 1. 修复按钮点击事件绑定 **问题**:原代码使用三元表达式直接绑定函数引用 ```vue @click="store.state.isSending ? handleStopGeneration : handleSendMessage" ``` **修复**:改为调用统一的处理函数 ```vue @click="handleButtonClick" ``` ```typescript const handleButtonClick = () => { if (store.state.isSending) { handleStopGeneration() } else { handleSendMessage() } } ``` ### 2. 添加 PAUSED 消息状态 **文件**:`web/src/types/chat.ts` ```typescript // 添加 'paused' 状态 export type MessageStatus = 'pending' | 'sending' | 'success' | 'error' | 'paused' // 添加 'paused' 事件类型 export interface StreamEvent { type: 'start' | 'delta' | 'end' | 'error' | 'paused' // ... } ``` ### 3. 优化错误处理逻辑 **文件**:`web/src/services/chatService.ts` ```typescript catch (error) { const isAborted = error instanceof Error && error.name === 'AbortError' if (isAborted) { // 用户主动停止,保留已生成的内容 assistantMessage.status = 'paused' assistantMessage.error = undefined onChunk({ type: 'paused', messageId: assistantMessage.id }) } else { // 其他错误 assistantMessage.status = 'error' assistantMessage.error = error instanceof Error ? error.message : '发送失败' onChunk({ type: 'error', error: assistantMessage.error, messageId: assistantMessage.id }) } } ``` ### 4. 在流式读取中检查中止信号 **文件**:`web/src/services/modelServiceManager.ts` ```typescript while (true) { // 检查是否被中止 if (signal?.aborted) { console.log('🛑 [makeChatRequestStream] 检测到中止信号,停止读取流') reader.cancel() throw new DOMException('用户中止操作', 'AbortError') } const { done, value } = await reader.read() if (done) break // ... 处理数据 } ``` ### 5. UI 显示优化 **文件**:`web/src/components/Chat/ChatLayout.vue` ```vue 已停止
``` ## 工作流程 ### 用户点击停止按钮时: 1. `handleButtonClick()` 检测到 `isSending = true` 2. 调用 `handleStopGeneration()` 3. `store.stopGeneration()` 执行 `abortController.abort()` 4. 中止信号传递到 `chatService.sendMessageStream()` 5. 信号继续传递到 `modelServiceManager.makeChatRequestStream()` 6. 流式读取循环检测到 `signal.aborted` 7. 调用 `reader.cancel()` 并抛出 `AbortError` 8. 错误向上冒泡,在 `chatService` 中被识别为用户中止 9. 消息状态设置为 `'paused'`,保留已生成内容 10. UI 更新显示"已停止"标签 ### 正常完成时: 1. 流式读取完成,消息状态设置为 `'success'` 2. UI 显示完整消息和操作按钮 ## 关键改进点 ### 1. 按钮事件绑定 - ✅ 使用函数调用而非三元表达式 - ✅ 运行时动态判断状态 ### 2. 状态管理 - ✅ 新增 `paused` 状态区分用户中止和错误 - ✅ 保留用户中止前的已生成内容 ### 3. 中止信号传递 - ✅ 完整的信号链:UI → Store → Service → API - ✅ 在流读取循环中实时检查中止状态 ### 4. 用户体验 - ✅ 立即响应停止操作 - ✅ 保留部分生成的内容可查看 - ✅ 可以对停止的消息进行复制、重新生成等操作 ## 测试验证 ### 手动测试步骤: 1. **启动应用**并创建新对话 2. **发送消息**并立即点击"停止"按钮 3. **验证**: - ✅ 流式输出立即停止 - ✅ 消息显示"已停止"标签 - ✅ 已生成的内容被保留 - ✅ 可以对停止的消息进行操作(复制、重新生成、删除) - ✅ `isSending` 状态恢复为 `false` - ✅ 可以继续发送新消息 ### 预期行为: - **立即响应**:点击停止后 100ms 内停止输出 - **状态正确**:消息标记为 "已停止" 而非 "发送失败" - **内容保留**:显示停止前生成的所有文本 - **可继续操作**:可以立即发送下一条消息 ## 参考资源 - Cherry Studio PAUSED 状态设计 - AbortController Web API - Fetch API with abort signals - ReadableStream reader.cancel() method ## 修改文件清单 1. `web/src/components/Chat/ChatLayout.vue` - 按钮事件和UI显示 2. `web/src/types/chat.ts` - 类型定义 3. `web/src/services/chatService.ts` - 错误处理逻辑 4. `web/src/services/modelServiceManager.ts` - 流式读取中止检查 5. `web/src/stores/chatStore.ts` - 已有正确的中止逻辑(无需修改) ## 注意事项 - 确保在所有流式读取循环中检查 `signal.aborted` - 区分用户中止 (`AbortError`) 和其他错误 - 保持状态一致性:`isSending` 必须在 finally 块中重置