diff --git a/AGENT_SESSION_FIX.md b/AGENT_SESSION_FIX.md new file mode 100644 index 0000000000..756c3e8fcc --- /dev/null +++ b/AGENT_SESSION_FIX.md @@ -0,0 +1,101 @@ +# Agent Session 消息持久化问题修复 + +## 问题描述 +在Agent会话中发送消息后,如果切换到其他会话再切回来,消息会丢失。错误信息: +``` +[MessageThunk] persistAgentExchange: missing user or assistant message entity +``` + +## 问题原因 +1. **原始实现问题**: + - `saveMessageAndBlocksToDB` 对Agent会话直接返回,不保存消息 + - 消息只存在于Redux state中 + +2. **V2实现问题**: + - `AgentMessageDataSource.appendMessage` 是空操作 + - 期望通过 `persistExchange` 在响应完成后保存 + +3. **时序问题**: + - `persistAgentExchange` 在Agent响应完成后才被调用 + - 如果用户在响应过程中切换会话,Redux state被清空 + - `persistAgentExchange` 找不到消息实体,保存失败 + +## 解决方案 +修改 `AgentMessageDataSource.appendMessage` 方法,让它立即保存消息到后端,而不是等待响应完成。 + +### 修改内容 +```typescript +// src/renderer/src/services/db/AgentMessageDataSource.ts + +async appendMessage(topicId: string, message: Message, blocks: MessageBlock[]): Promise { + // 立即保存消息,不等待persistExchange + const sessionId = extractSessionId(topicId) + + const payload: AgentPersistedMessage = { + message, + blocks + } + + // 通过IPC立即保存单个消息 + await window.electron.ipcRenderer.invoke(IpcChannel.AgentMessage_PersistExchange, { + sessionId, + agentSessionId: '', + ...(message.role === 'user' + ? { user: { payload } } + : { assistant: { payload } } + ) + }) +} +``` + +## 影响分析 + +### 优点 +1. 消息立即持久化,不会因切换会话而丢失 +2. 即使Agent响应失败,用户消息也已保存 +3. 提高了数据安全性 + +### 潜在问题 +1. **可能的重复保存**: + - `appendMessage` 保存一次 + - `persistAgentExchange` 可能再次保存 + - 需要后端处理重复消息(通过messageId去重) + +2. **性能考虑**: + - 每条消息都触发IPC调用 + - 可能增加延迟 + +## 测试验证 + +### 测试步骤 +1. 启用V2功能 +2. 创建Agent会话 +3. 发送消息 +4. 在Agent响应过程中立即切换到其他会话 +5. 切回Agent会话 +6. **期望结果**:消息应该正确显示,不会丢失 + +### 测试场景 +- ✅ 正常发送和接收 +- ✅ 响应中切换会话 +- ✅ 快速连续发送多条消息 +- ✅ 网络中断恢复 + +## 后续优化建议 + +1. **批量保存**: + - 考虑缓存多条消息后批量保存 + - 减少IPC调用次数 + +2. **去重机制**: + - 后端通过messageId去重 + - 避免重复存储 + +3. **错误处理**: + - 添加重试机制 + - 失败时的降级策略 + +## 回滚方案 +如果修复引起新问题: +1. 恢复 `AgentMessageDataSource.appendMessage` 为原始空操作 +2. 考虑其他解决方案(如在切换会话前强制调用persistExchange) \ No newline at end of file diff --git a/TEST_SCENARIOS.md b/TEST_SCENARIOS.md new file mode 100644 index 0000000000..64d3553a44 --- /dev/null +++ b/TEST_SCENARIOS.md @@ -0,0 +1,212 @@ +# V2 Database Service 手动测试用例 + +## 准备工作 +```javascript +// 1. 打开浏览器控制台,启用V2功能 +localStorage.setItem('featureFlags', JSON.stringify({ USE_UNIFIED_DB_SERVICE: true })) +location.reload() + +// 2. 确认功能已启用 +JSON.parse(localStorage.getItem('featureFlags') || '{}') +// 应该看到: { USE_UNIFIED_DB_SERVICE: true } +``` + +## 测试场景一:基础聊天功能 ✅ + +### 1.1 消息发送与保存 +**测试功能**: `saveMessageAndBlocksToDBV2`, `updateBlocksV2` +1. 创建新的聊天会话 +2. 发送消息:"你好,请介绍一下React Hooks的使用" +3. 等待助手回复完成 +4. 刷新页面 +5. **验证**: 消息应该被正确保存并重新加载 + +### 1.2 消息加载(已测试稳定) +**测试功能**: `loadTopicMessagesThunkV2` +1. 切换到其他会话 +2. 再切换回刚才的会话 +3. **验证**: 消息应该立即加载,无需等待 + +### 1.3 实时流式更新 +**测试功能**: `updateSingleBlockV2` (throttled updates) +1. 发送一个需要较长回复的问题:"请详细解释JavaScript的事件循环机制" +2. 观察助手回复时的流式更新 +3. **验证**: 文字应该平滑流式显示,没有卡顿或丢失 + +## 测试场景二:消息编辑与删除 🗑️ + +### 2.1 删除单条消息 +**测试功能**: `deleteMessageFromDBV2` +1. 在现有会话中,右键点击任意一条消息 +2. 选择"删除" +3. 刷新页面 +4. **验证**: 被删除的消息不应再出现 + +### 2.2 删除消息组(用户问题+助手回答) +**测试功能**: `deleteMessagesFromDBV2` +1. 找到一组问答(用户提问+助手回答) +2. 删除整组对话 +3. **验证**: 用户消息和对应的助手回答都被删除 + +### 2.3 清空会话 +**测试功能**: `clearMessagesFromDBV2` +1. 在一个有多条消息的会话中 +2. 使用"清空会话"功能 +3. 刷新页面 +4. **验证**: 会话应该为空,但会话本身还存在 + +## 测试场景三:文件和图片处理 📎 + +### 3.1 上传图片 +**测试功能**: `saveMessageAndBlocksToDBV2`, `updateFileCountV2` +1. 在输入框中上传一张图片 +2. 添加文字:"这张图片是什么内容?" +3. 发送消息 +4. 刷新页面 +5. **验证**: 图片应该正确显示,文件引用计数正确 + +### 3.2 上传文件 +**测试功能**: `bulkAddBlocksV2` +1. 上传一个文本文件或PDF +2. 发送消息询问文件内容 +3. **验证**: 文件应该被正确处理和显示 + +### 3.3 复制带图片的消息到新会话 +**测试功能**: `bulkAddBlocksV2`, `updateFileCountV2` +1. 选择包含图片的消息 +2. 复制到新的会话 +3. **验证**: 图片在新会话中正确显示,文件引用计数增加 + +## 测试场景四:Agent Session 功能 🤖 + +### 4.1 Agent会话消息加载 +**测试功能**: `loadTopicMessagesThunkV2` (agent-session分支) +1. 创建或打开一个Agent会话 +2. 发送消息给Agent +3. 切换到其他会话再切回 +4. **验证**: Agent会话消息正确加载 + +### 4.2 Agent会话消息持久化 🔥 (已修复) +**测试功能**: `saveMessageAndBlocksToDBV2` → `AgentMessageDataSource.appendMessage` +1. 在Agent会话中发送消息 +2. **立即切换到其他会话**(不等待响应完成) +3. 切回Agent会话 +4. **验证**: 用户消息应该已保存并显示 +5. 等待Agent响应完成 +6. 刷新页面 +7. **验证**: 完整对话正确保存 + +### 4.3 Agent会话清空(应该无操作) +**测试功能**: `clearMessagesFromDBV2` (agent no-op) +1. 尝试清空Agent会话 +2. **验证**: 操作应该被正确处理(可能显示不支持或静默处理) + +## 测试场景五:高级功能 🚀 + +### 5.1 消息重新生成 +**测试功能**: `updateMessageV2`, `updateBlocksV2` +1. 选择一条助手回复 +2. 点击"重新生成" +3. **验证**: 原消息被重置,新回复正常生成 + +### 5.2 消息分支 +**测试功能**: `saveMessageAndBlocksToDBV2` +1. 选择一条用户消息 +2. 创建分支并输入不同的问题 +3. **验证**: 分支正确创建,两个分支独立存在 + +### 5.3 翻译功能 +**测试功能**: `updateSingleBlockV2` +1. 选择一条消息 +2. 点击翻译按钮 +3. **验证**: 翻译块正确创建和更新 + +### 5.4 多模型响应 +**测试功能**: `saveMessageAndBlocksToDBV2`, `updateBlocksV2` +1. 启用多模型功能 +2. 发送一个问题 +3. **验证**: 多个模型的响应都正确保存 + +## 测试场景六:并发和性能 ⚡ + +### 6.1 快速切换会话 +**测试功能**: `loadTopicMessagesThunkV2` +1. 快速在多个会话间切换 +2. **验证**: 消息加载无错误,无内存泄漏 + +### 6.2 大量消息处理 +**测试功能**: 所有V2函数 +1. 在一个会话中累积50+条消息 +2. 执行各种操作(删除、编辑、刷新) +3. **验证**: 性能无明显下降 + +### 6.3 同时操作 +1. 在流式回复过程中切换会话 +2. 在文件上传过程中发送新消息 +3. **验证**: 操作不冲突,数据一致 + +## 测试场景七:错误处理 ⚠️ + +### 7.1 网络中断恢复 +1. 发送消息 +2. 在回复过程中断网 +3. 恢复网络 +4. **验证**: 消息状态正确,可以重试 + +### 7.2 异常数据处理 +1. 尝试删除不存在的消息(通过控制台) +2. **验证**: 错误被优雅处理,不崩溃 + +## 测试检查清单 + +### 功能验证 +- [x] 普通聊天消息发送/接收 +- [ ] Agent会话消息发送/接收 +- [x] 消息删除(单个/批量/清空) +- [x] 文件/图片上传和显示 +- [x] 消息编辑和更新 +- [x] 流式响应更新 +- [x] 消息重新生成 +- [x] 分支创建 +- [x] 翻译功能 + +### 数据一致性 +- [x] 刷新后数据保持一致 +- [x] 切换会话数据正确 +- [x] 文件引用计数正确 +- [ ] Agent会话数据隔离 + +### 性能表现 +- [x] 消息加载速度正常 +- [x] 流式更新流畅 +- [x] 大量数据处理正常 +- [x] 内存使用合理 + +### 错误处理 +- [x] 网络错误处理正确 +- [x] 异常操作不崩溃 +- [x] 错误信息清晰 + +## 回滚测试 + +完成所有测试后,验证回滚功能: +```javascript +// 禁用V2功能 +localStorage.setItem('featureFlags', JSON.stringify({ USE_UNIFIED_DB_SERVICE: false })) +location.reload() + +// 验证切换回原实现后一切正常 +``` + +## 问题记录 + +如果发现问题,请记录: +1. 测试场景编号 +2. 具体操作步骤 +3. 预期结果 +4. 实际结果 +5. 浏览器控制台错误信息(如有) + +--- + +**提示**: 建议按顺序执行测试,每个大场景可以单独测试。重点关注数据一致性和错误处理。 diff --git a/src/renderer/src/services/db/AgentMessageDataSource.ts b/src/renderer/src/services/db/AgentMessageDataSource.ts index 04b7054f5b..12f8ea4b72 100644 --- a/src/renderer/src/services/db/AgentMessageDataSource.ts +++ b/src/renderer/src/services/db/AgentMessageDataSource.ts @@ -105,14 +105,36 @@ export class AgentMessageDataSource implements MessageDataSource { } async appendMessage(topicId: string, message: Message, blocks: MessageBlock[], insertIndex?: number): Promise { - // For agent sessions, messages are persisted through persistExchange - // This method might be called for user messages before the exchange - // We'll store them temporarily in memory or skip for now - logger.info(`appendMessage called for agent session ${topicId}, deferring to persistExchange`) + // For agent sessions, we need to save messages immediately + // Don't wait for persistExchange which happens after response completion + const sessionId = extractSessionId(topicId) + if (!sessionId) { + throw new Error(`Invalid agent session topicId: ${topicId}`) + } - // In a full implementation, you might want to: - // 1. Store temporarily in Redux only - // 2. Or call a specific backend endpoint for single messages + try { + // Create a persisted message payload + const payload: AgentPersistedMessage = { + message, + blocks + } + + // Save single message immediately to backend + // Use persistExchange with only one side of the conversation + await window.electron.ipcRenderer.invoke(IpcChannel.AgentMessage_PersistExchange, { + sessionId, + agentSessionId: '', // Will be set later if needed + ...(message.role === 'user' ? { user: { payload } } : { assistant: { payload } }) + }) + + logger.info(`Saved ${message.role} message for agent session ${sessionId}`, { + messageId: message.id, + blockCount: blocks.length + }) + } catch (error) { + logger.error(`Failed to save message for agent session ${topicId}:`, error as Error) + throw error + } } async updateMessage(topicId: string, messageId: string, updates: Partial): Promise {