Files
zclaw_openfang/wiki/chat.md
iven c10e50d58e docs(wiki): Phase D完成 — 6模块页重构(routing/chat/butler/hands-skills/pipeline/data-model)
- routing.md: 移除Store/lib列表+5节模板 (330→131行)
- chat.md: 添加集成契约+不变量 (180→134行)
- butler.md: 移除重复→引用memory/hands-skills (215→150行)
- hands-skills.md: 5节模板+契约+不变量 (281→170行)
- pipeline.md: 添加契约+重组 (157→154行)
- data-model.md: 添加契约+双库架构图 (181→153行)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:53:17 +08:00

135 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: 聊天系统
updated: 2026-04-22
status: active
tags: [module, chat, stream]
---
# 聊天系统
> 从 [[index]] 导航。关联模块: [[routing]] [[saas]] [[butler]]
## 1. 设计决策
| 决策 | 原因 |
|------|------|
| 3 种 ChatStream 实现 | 覆盖 3 种运行环境: KernelClient(Tauri) / SaaSRelay(浏览器) / GatewayClient(外部进程) |
| 5 Store 拆分 | 原 908 行 ChatStore → stream/conversation/message/chat/artifact单一职责 |
| 5 分钟超时守护 | 防止流挂起: kernel-chat.ts:76超时自动 cancelStream |
| 统一回调接口 | 3 种实现共享 `{ onDelta, onThinkingDelta, onTool, onHand, onComplete, onError }` |
### ChatStream 实现
| 环境 | 实现 | 传输 |
|------|------|------|
| Tauri + SaaS (主路径) | KernelClient + relay | Tauri Event → SaaS Token Pool → LLM |
| Tauri 本地 | KernelClient | Tauri Event → Kernel → LLM 直连 |
| 浏览器端 | SaaSRelayGatewayClient | HTTP SSE → SaaS → LLM |
| 外部 Gateway | GatewayClient | WebSocket |
## 2. 关键文件 + 数据流
### 核心文件
| 文件 | 职责 |
|------|------|
| `desktop/src/store/chat/streamStore.ts` | 流式消息编排、发送、取消 |
| `desktop/src/store/chat/conversationStore.ts` | 会话管理、当前模型、sessionKey |
| `desktop/src/store/chat/messageStore.ts` | 消息持久化 (IndexedDB) |
| `desktop/src/lib/kernel-chat.ts` | KernelClient ChatStream (Tauri) |
| `desktop/src/components/ChatArea.tsx` | 聊天区域 UI |
| `crates/zclaw-runtime/src/loop_runner.rs` | Rust 主聊天循环 + 中间件链 |
### 发送消息流
```
用户输入 → ChatPanel.tsx
→ streamStore.sendMessage(content)
→ effectiveSessionKey = conversationStore.sessionKey || uuid()
→ effectiveAgentId = resolveGatewayAgentId(currentAgent)
→ getClient().chatStream(content, callbacks, { sessionKey, agentId, chatMode })
→ [KernelClient] Tauri invoke('agent_chat_stream')
→ Kernel → loop_runner → 14层中间件 → LLM Driver
→ Tauri Event emit('chat-response-delta')
→ onDelta → streamStore 追加 delta → UI 渲染
→ onComplete → conversationStore 持久化 → messageStore 写 IndexedDB
→ [SaaSRelay] POST /api/v1/relay/chat/completions → SSE
→ [GatewayClient] WebSocket send → onmessage
→ 5 分钟超时守护 (kernel-chat.ts:76)
```
### 集成契约
| 方向 | 模块 | 接口 | 说明 |
|------|------|------|------|
| Calls -> | routing | `getClient()` | 确定走哪条 ChatStream |
| Calls -> | middleware | Through loop_runner | 14 层 runtime 中间件链 |
| Called by <- | UI | ChatPanel.tsx | 用户发送消息取消流式 |
| Emits -> | streamStore | Tauri Event `chat-response-delta` | 流式增量更新 |
## 3. 代码逻辑
### Store 拆分 (5 Store)
| Store | 文件 | 职责 |
|-------|------|------|
| streamStore | `store/chat/streamStore.ts` | 流式编排、发送、取消 |
| conversationStore | `store/chat/conversationStore.ts` | 会话管理、当前模型 |
| messageStore | `store/chat/messageStore.ts` | 消息持久化 (IndexedDB) |
| chatStore | `store/chat/chatStore.ts` | 聊天通用状态 |
| artifactStore | `store/chat/artifactStore.ts` | 聊天产物/附件 |
### 流式事件类型
`agent_chat_stream` Tauri Event emit 的 tagged union:
`Delta` / `ThinkingDelta` / `ToolStart` / `ToolEnd` / `HandStart` / `HandEnd` / `SubtaskStatus` / `IterationStart` / `Complete` / `Error`
### 模型切换
```
UI 选择模型 → conversationStore.currentModel = newModel
→ 下次 sendMessage → getClient() 读取 currentModel
→ SaaS 模式: relay 白名单验证 → 可用则切换
→ 本地模式: 直接使用用户配置的模型
```
### 不变量
- sessionKey 在会话内必须一致 (UUID 生成一次,持久化 IndexedDB)
- cancelStream 设置原子标志位,与 onDelta 回调无竞态
- 3 种 ChatStream 共享同一套回调接口,上层代码无需感知实现差异
- 消息持久化走 messageStore → IndexedDB与流式渲染解耦
### Tauri 命令
| 命令 | 说明 |
|------|------|
| `agent_chat_stream` | 流式聊天 (主路径) |
| `agent_chat` | 非流式聊天 |
| `cancel_stream` | 取消当前流式响应 |
| `classroom_chat` | 课堂场景对话 |
## 4. 活跃问题 + 注意事项
| 问题 | 状态 | 说明 |
|------|------|------|
| B-CHAT-07 混合域截断 | P2 Open | 跨域消息时可能截断上下文 |
| SSE Token 统计为 0 | ✅ 已修复 | SseUsageCapture stream_done flag |
| Tauri invoke 参数名 | ✅ 已修复 (f6c5dd2) | camelCase 格式 |
| Provider Key 解密 | ✅ 已修复 (b69dc61) | warn+skip + heal |
**注意事项:**
- 辅助 LLM 调用 (记忆摘要/提取、管家路由) 复用 `kernel_init` 的 model+base_url与聊天同链路
- 课堂聊天是独立 Tauri 命令 (`classroom_chat`),不走 `agent_chat_stream`
## 5. 变更日志
| 日期 | 变更 |
|------|------|
| 04-22 | Wiki 重写: 5 节模板,增加集成契约和不变量 |
| 04-21 | 上一轮更新 |
| 04-17 | ChatStore 拆分为 5 Store (stream/conversation/message/chat/artifact) |
| 04-16 | Provider Key 解密修复 (b69dc61) |
| 04-16 | Tauri invoke 参数名修复 (f6c5dd2) |