fix: 第四轮调试 — 回复去重/消息时序/UI布局/自主思考深度优化 + 文档重整
后端修复: - main.go: 恢复 /api/v1/chat 路由中丢失的 handleChat 调用 (空响应回归) - orchestrator.go: splitChatByLines 改为双换行分割, 避免单换行误拆 - chat_handler.go: multi_message 增加 !hasReview 守卫, 消息延迟 200→800ms - thinker.go: RecordUserMessage 追踪活跃会话ID, 推送主动消息到正确会话 - thinker.go: 增强思考提示词 — 禁止在用户休息/离开时发送主动消息 前端修复: - useWebSocket.ts: stream_segments 不再创建消息气泡, 消除重复回复 - MessageBubble.tsx: 动作消息居左对齐无头像, 时间戳移至气泡外侧 hover 显示 - ChatInput.tsx: 昔涟输入提示移至输入框上方, 波点动画效果 - MessageList/TypingIndicator/ChatContainer: 清理冗余 isTyping 传递 - MemoryPanel.tsx: 新增记忆面板组件 文档重整: - docs/debug/ → docs/debug_log/ 重命名统一 - 新增 debug_log/README.md 索引 - .gitignore: 新增 android/ 排除规则 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
# Round 2 深度调试报告
|
||||
|
||||
**日期**: 2026-05-21
|
||||
**类型**: 综合性深度调试 + 即时修复
|
||||
**状态**: ✅ 完成
|
||||
|
||||
---
|
||||
|
||||
## 1. 执行摘要
|
||||
|
||||
本次 Round 2 深度调试通过 Chromium CDP 进行前端 E2E 测试 + 后端全面 API 验证,发现并修复了 **3 个问题**(1 个 P0 崩溃、1 个 P0 后端 PANIC、1 个测试工具缺陷),最终 **14/14 全部通过**。
|
||||
|
||||
---
|
||||
|
||||
## 2. 发现的问题与修复
|
||||
|
||||
### 2.1 P0 — `useSpeechSynthesis.ts` `cancel()` 未守卫调用
|
||||
|
||||
**文件**: [`frontend/web/src/hooks/useSpeechSynthesis.ts`](../../frontend/web/src/hooks/useSpeechSynthesis.ts)
|
||||
|
||||
**问题**: 参考 `docs/debug/2026-05-21-crash-cancel.md` 的详细分析。
|
||||
|
||||
- `stop()` 回调 (原 L223):仅当 `utteranceRef.current` 存在时才调用 `cancel()`,但浏览器 Web Speech API 可能在任意时刻有活跃 utterance,导致 `cancel()` 被跳过
|
||||
- Cleanup effect (原 L261):同样的问题,组件卸载时可能跳过 `cancel()`
|
||||
|
||||
**修复**: 将守卫条件从 `utteranceRef.current` 改为 `isSupported`:
|
||||
|
||||
```typescript
|
||||
// stop() — 当 isSupported 为 true 时始终调用 cancel()
|
||||
const stop = useCallback(() => {
|
||||
if (!isSupported) {
|
||||
console.warn('[useSpeechSynthesis] stop: speechSynthesis not supported');
|
||||
return;
|
||||
}
|
||||
window.speechSynthesis.cancel();
|
||||
// ... 重置状态
|
||||
}, [isSupported]);
|
||||
|
||||
// cleanup effect — 同样始终调用 cancel(),并清理 resumeIntervalRef
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (isSupported) {
|
||||
window.speechSynthesis.cancel();
|
||||
}
|
||||
if (resumeIntervalRef.current) {
|
||||
clearInterval(resumeIntervalRef.current);
|
||||
resumeIntervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [isSupported]);
|
||||
```
|
||||
|
||||
**状态**: ✅ 已修复
|
||||
|
||||
---
|
||||
|
||||
### 2.2 P0 — IoT 子会话 `nil pointer dereference` PANIC
|
||||
|
||||
**文件**: [`backend/ai-core/internal/subsession/iot_provider.go`](../../backend/ai-core/internal/subsession/iot_provider.go)
|
||||
|
||||
**根因**:
|
||||
1. [`iot_provider.go:116`](../../backend/ai-core/internal/subsession/iot_provider.go:116): `persona.NewLoader("")` 传入空字符串
|
||||
2. [`persona/loader.go:24-27`](../../backend/ai-core/internal/persona/loader.go:24): `os.ReadDir("")` 失败,返回 `nil, error`
|
||||
3. [`iot_provider.go:118`](../../backend/ai-core/internal/subsession/iot_provider.go:118): 仅 `log.Printf` 错误,未检查 `loader` 是否为 nil
|
||||
4. [`iot_provider.go:120`](../../backend/ai-core/internal/subsession/iot_provider.go:120): `loader.Get("cyrene")` 对 nil 解引用 → **PANIC**
|
||||
|
||||
**崩溃日志**:
|
||||
```
|
||||
[iot-provider] 加载人格配置失败: 读取人格目录失败: open : no such file or directory
|
||||
[subsession] dispatch goroutine panic 恢复 (type=iot): runtime error: invalid memory address or nil pointer dereference
|
||||
```
|
||||
|
||||
**修复**: 添加 `loader != nil` 守卫,优雅降级使用默认值:
|
||||
|
||||
```go
|
||||
// 加载人格配置
|
||||
trueName := "昔涟"
|
||||
loader, err := persona.NewLoader("")
|
||||
if err != nil {
|
||||
log.Printf("[iot-provider] 加载人格配置失败: %v", err)
|
||||
}
|
||||
if loader != nil {
|
||||
if personaConfig, err := loader.Get("cyrene"); err == nil && personaConfig != nil {
|
||||
trueName = personaConfig.Identity.TrueName
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**编译与部署**: 重新编译 `ai-core` (`go build`),kill 旧进程 (PID 12870),启动新进程 (PID 22942)。
|
||||
|
||||
**状态**: ✅ 已修复并验证
|
||||
|
||||
---
|
||||
|
||||
### 2.3 测试工具 — CDP 脚本按钮匹配错误
|
||||
|
||||
**文件**: [`debug/cache/test_cdp_e2e_v4.py`](../../debug/cache/test_cdp_e2e_v4.py)
|
||||
|
||||
**问题**: 测试脚本查找 `btn.textContent.includes('登录')` 的按钮,但页面上有两个包含"登录"的按钮:
|
||||
- 模式切换按钮 `"登录"` → 调用 `switchMode('login')`(不触发表单提交)
|
||||
- 真正的提交按钮 `"进入昔涟的世界 ♪"` → 调用 `handleLogin()`
|
||||
|
||||
**修复**: 改为匹配提交按钮的特征文字 `"进入"` 或 `"昔涟"`。
|
||||
|
||||
**状态**: ✅ 已修复并验证
|
||||
|
||||
---
|
||||
|
||||
## 3. 验证结果汇总
|
||||
|
||||
### 3.1 CDP E2E v4 测试 (14/14 全部通过)
|
||||
|
||||
| # | 检查项 | 状态 |
|
||||
|---|--------|------|
|
||||
| 1 | 页面无 JS 异常 | ✅ |
|
||||
| 2 | 无硬编码 `test-token-cyrene` | ✅ |
|
||||
| 3 | 找到用户名输入框 | ✅ |
|
||||
| 4 | 找到密码输入框 | ✅ |
|
||||
| 5 | 找到登录按钮 (`"进入昔涟的世界 ♪"`) | ✅ |
|
||||
| 6 | 无认证错误 (API 调用 8 次) | ✅ |
|
||||
| 7 | localStorage 有 token | ✅ |
|
||||
| 8 | 侧边栏或聊天区域存在 | ✅ |
|
||||
| 9 | 会话列表 | ✅ |
|
||||
| 10 | 创建新会话 | ✅ |
|
||||
| 11 | 获取消息历史 | ✅ |
|
||||
| 12 | 记忆列表 | ✅ |
|
||||
| 13 | WebSocket 连接 | ✅ |
|
||||
| 14 | 收到 IoT 响应 | ✅ |
|
||||
|
||||
### 3.2 后端 API 深度验证
|
||||
|
||||
| API | 方法 | 状态 | 备注 |
|
||||
|-----|------|------|------|
|
||||
| `/api/v1/health` | GET | ✅ 200 | 5 服务全部健康 |
|
||||
| `/api/v1/auth/login` | POST | ✅ 200 | 返回 `token`, `user_id`, `expires` |
|
||||
| `/api/v1/auth/refresh` | POST | ✅ 200 | 返回新 token |
|
||||
| `/api/v1/sessions?user_id=admin` | GET | ✅ 200 | `{"sessions": [...]}` |
|
||||
| `/api/v1/sessions` | POST | ✅ 201 | 创建会话成功 |
|
||||
| `/api/v1/sessions/{id}/messages` | GET | ✅ 200 | 历史消息正常 |
|
||||
| `/api/v1/memory` | GET | ✅ 200 | 记忆列表正常 |
|
||||
| `/api/v1/memory/search?query=IoT` | GET | ⚠️ 400 | 参数格式问题(非阻塞) |
|
||||
| `/ws/chat` | WS | ✅ 101 | 消息流正常 |
|
||||
|
||||
### 3.3 WebSocket + IoT 流验证
|
||||
|
||||
- **WebSocket 连接**: ✅ 正常建立
|
||||
- **设备状态广播**: ✅ 每 10 秒推送 8 个设备状态
|
||||
- **IoT 查询消息流**: ✅ 完整流程:`device_update` → `stream_chunk` → `response`
|
||||
- **IoT 子会话**: ✅ 不再 PANIC,优雅降级
|
||||
- **后台思考**: ✅ `post_chat` 触发正常
|
||||
|
||||
### 3.4 服务运行时状态
|
||||
|
||||
| 服务 | 端口 | PID | 状态 |
|
||||
|------|------|-----|------|
|
||||
| gateway | 8080 | 12874 | ✅ |
|
||||
| ai-core | 8081 | 22942 | ✅ (已更新) |
|
||||
| memory-service | 8091 | 12864 | ✅ |
|
||||
| tool-engine | 8092 | 12868 | ✅ |
|
||||
| iot-debug-service | 8083 | 12866 | ✅ |
|
||||
| vite preview | 5199 | 1266 | ✅ |
|
||||
| chromium CDP | 9225 | 4741 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 4. 已知遗留问题
|
||||
|
||||
### 4.1 低优先级 — Memory Search 400
|
||||
|
||||
`GET /api/v1/memory/search?query=IoT` 返回 400。需要在 `memory_handler.go` 中确认查询参数名是否正确(可能是 `q` 而非 `query`)。
|
||||
|
||||
### 4.2 低优先级 — `MessageBubble.tsx` `setInterval` 无清理
|
||||
|
||||
[`MessageBubble.tsx:105-110`](../../frontend/web/src/components/chat/MessageBubble.tsx) 中 `AIMessageActions` 的 `checkEnd` interval 没有在组件卸载时清理,可能导致内存泄漏。
|
||||
|
||||
### 4.3 低优先级 — IoT 子会话未匹配到"列出所有设备"
|
||||
|
||||
`iot_provider.go` 的 `matchIotOperation()` 函数未匹配 `"列出所有IoT设备"` 关键词。该消息被降级到 General 子会话处理,虽不影响功能但无法触发 IoT 专用响应。
|
||||
|
||||
### 4.4 信息 — 登录响应无 `refresh_token` 字段
|
||||
|
||||
后端 [`auth_handler.go:208`](../../backend/gateway/internal/handler/auth_handler.go:208) 登录响应仅返回 `token`, `user_id`, `expires`,前端 [`client.ts:80`](../../frontend/web/src/api/client.ts:80) `getRefreshToken()` 读取 `localStorage.getItem('refresh_token')` 始终为 null。
|
||||
|
||||
---
|
||||
|
||||
## 5. 修改文件清单
|
||||
|
||||
| 文件 | 变更类型 | 描述 |
|
||||
|------|----------|------|
|
||||
| `frontend/web/src/hooks/useSpeechSynthesis.ts` | 🐛 修复 | P0: cancel() 守卫条件改为 isSupported |
|
||||
| `backend/ai-core/internal/subsession/iot_provider.go` | 🐛 修复 | P0: 添加 loader nil 检查,防止 PANIC |
|
||||
| `backend/ai-core/cmd/ai-core` | 🔄 重新编译 | 包含上述修复 |
|
||||
| `debug/cache/test_cdp_e2e_v4.py` | 🐛 修复 | 按钮匹配逻辑改为匹配提交按钮 |
|
||||
| `debug/cache/test_cdp_token_investigation.py` | ➕ 新增 | test-token-cyrene 来源诊断脚本 |
|
||||
| `debug/cache/test_cdp_e2e_v3.py` | ➕ 新增 | E2E v3 测试(发现 API 格式差异) |
|
||||
|
||||
---
|
||||
|
||||
## 6. 结论
|
||||
|
||||
Round 2 深度调试成功完成。发现了 **2 个 P0 级别缺陷**(useSpeechSynthesis cancel 守卫缺失、IoT 子会话 nil pointer PANIC)和 **1 个测试工具缺陷**(按钮匹配错误),全部已修复并验证通过。系统各组件(gateway、ai-core、memory-service、tool-engine、iot-debug-service)协同工作正常,WebSocket 消息流、IoT 设备广播、记忆存储链路完整。
|
||||
Reference in New Issue
Block a user