docs: audit reports + feature docs + skills + admin-v2 + config sync
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

Update audit tracker, roadmap, architecture docs,
add admin-v2 Roles page + Billing tests,
sync CLAUDE.md, Cargo.toml, docker-compose.yml,
add deep-research / frontend-design / chart-visualization skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-04-02 19:25:00 +08:00
parent 28299807b6
commit 8898bb399e
48 changed files with 7388 additions and 173 deletions

View File

@@ -0,0 +1,649 @@
# ChatStore 结构化重构设计
> 日期: 2026-04-02
> 状态: Draft
> 范围: desktop/src/store/chatStore.ts 及关联文件
## 1. 背景
ChatStore908 行)是 ZCLAW 桌面端聊天的核心状态管理模块承担了消息管理、流式处理、对话管理、Artifact 面板、离线队列、ChatMode 切换等职责。经过多轮功能迭代,存在以下问题:
### 1.1 功能断裂
| 问题 | 影响 |
|------|------|
| `cancelStream()` 是 no-op | 用户无法取消长时间运行的流式响应 |
| GatewayClient 路径不支持 thinking delta | Web 版/远程连接用户无法使用推理模式 |
| 双路径Kernel/Gateway逻辑重复且不一致 | 维护成本高,行为不可预测 |
### 1.2 数据可靠性
| 问题 | 影响 |
|------|------|
| 流式过程中刷新页面丢失未持久化内容 | 用户丢失已生成的回复 |
| 无消息重试机制 | 网络波动后用户需手动重新输入 |
| 对话持久化仅在 `onComplete` 时触发 | 长对话中断后数据丢失 |
### 1.3 架构债务
| 问题 | 影响 |
|------|------|
| ChatStore 908 行、职责过多 | 难以理解和修改 |
| `Message` vs `SessionMessage` 两套类型体系 | 类型转换混乱 |
| 未纳入 Store Coordinator | 不符合项目 store 注入模式 |
| `Conversation.tsx` 有未使用的 Context | 死代码增加认知负担 |
### 1.4 性能风险
| 问题 | 影响 |
|------|------|
| 所有消息全量在内存 | 长对话占用过多内存 |
| 每次 `onDelta` 触发全量 `set()` 映射 | 频繁渲染影响性能 |
## 2. 设计决策
### 2.1 方案选择
选择 **方案 B: 结构化多 Store 重构**,理由:
- 每个拆分后的 Store 可在一个上下文窗口内完整理解
- 统一流式抽象层消除双路径重复
- 定时批量持久化平衡性能与可靠性
- 逐步迁移保证每步可验证
### 2.2 双路径统一策略
统一 KernelClientTauri 事件)和 GatewayClientWebSocket的流式体验使两条路径具备相同能力thinking delta 支持、5 分钟超时、取消机制。
### 2.3 持久化策略
采用定时批量保存(每 3 秒或每 50 条 delta使用 `requestIdleCallback`(不可用时降级为 `setTimeout`)降低对 UI 的性能影响。存储目标为 IndexedDB通过 `idb-keyval` 库),避免 localStorage 的 5-10 MB 大小限制。localStorage 仅保留对话元数据id 列表、当前对话 ID、当前 agent
## 3. 架构设计
### 3.1 Store 拆分
```
desktop/src/store/
├── chat/ # 新建目录
│ ├── conversationStore.ts # 对话列表管理(~200行
│ ├── messageStore.ts # 消息管理 + 检索(~250行
│ ├── streamStore.ts # 统一流式处理(~200行
│ └── artifactStore.ts # Artifact 面板(~80行
├── chatStore.ts # 保留为 facadere-export 统一接口
```
### 3.2 conversationStore
**职责**: 对话生命周期管理
**状态:**
- `conversations: Conversation[]`
- `currentConversationId: string | null`
- `agents: Agent[]`agent 列表,从现有 chatStore 迁入)
- `currentAgent: Agent | null`
- `sessionKey: string | null`
- `currentModel: string`(当前模型名称,持久化)
**Actions:**
- `newConversation()` — 保存当前对话,创建新的空对话
- `switchConversation(id: string)` — 保存当前,加载目标对话
- `deleteConversation(id: string)` — 删除对话
- `upsertActiveConversation()` — 批量保存当前对话的 messages/sessionKey 到 conversations 数组
- `getCurrentConversation()` — 获取当前活跃对话
- `setCurrentAgent(agent: Agent)` — 切换 agent保存/恢复对话
- `syncAgents(profiles: AgentProfileLike[])` — 同步 agent 列表
- `setCurrentModel(model: string)` — 切换模型
**Agent 绑定**: 每个 Conversation 关联一个 agentId切换对话时恢复对应 agent。
**存储**: 对话列表和 agent 信息持久化到 IndexedDB通过 zustand persist 的自定义 storagelocalStorage 仅存元数据。包含存储配额检查:写入前估算数据大小,超过 4 MB 时自动清理最旧的已归档对话。
### 3.3 messageStore
**职责**: 当前对话的消息数据管理
**状态:**
- `messages: ChatMessage[]`
- `totalInputTokens: number`
- `totalOutputTokens: number`
**Actions:**
- `addMessage(message: ChatMessage)` — 追加消息
- `updateMessage(id: string, updates: Partial<ChatMessage>)` — 合并更新
- `getStreamingMessage()` — 获取当前流式消息role=assistant 且 streaming=true
- `updateStreamingContent(id: string, delta: string)` — 高性能增量更新
- `appendThinking(id: string, delta: string)` — 追加 thinking 内容
- `addToolStep(id: string, step: ToolStep)` — 追加工具调用步骤
- `completeMessage(id: string, tokens: TokenUsage)` — 标记消息完成,记录 token
- `failMessage(id: string, error: string)` — 标记消息失败,保存原始内容用于重试
- `retryMessage(id: string)` — 使用 originalContent 创建重试
- `addTokenUsage(input: number, output: number)` — 累计 token
- `resetMessages(messages: ChatMessage[])` — 切换对话时重载消息
- `searchMessages(query: string)` — 消息内文本搜索
### 3.4 streamStore
**职责**: 统一流式处理、离线队列、完成后副作用
**状态:**
- `isStreaming: boolean`
- `isLoading: boolean`
- `streamHandle: StreamHandle | null`
- `chatMode: ChatModeType`
- `suggestions: string[]`
**Actions:**
- `sendMessage(content: string, context?: SendMessageContext)` — 核心发送逻辑
1. **离线检查**:调用 `offlineStore.isOffline()`;若离线,委托 `offlineStore.queueMessage()` 并显示系统消息后返回
2. **流式守卫**:若 `isStreaming === true`,拒绝发送(前端防重复)
3. 选择活跃的 `StreamingAdapter`
4. **原子消息创建**:一次性创建 optimistic 用户消息 + 流式 assistant 占位消息,通过单次 `set()` 写入 messageStore避免部分状态被批量保存
5. 启动流式请求,注册 callbacks
6. 管理 dirty 标志触发批量保存
7. **完成后副作用**onComplete 回调中):
- `conversationStore.upsertActiveConversation()` — 立即保存
- `memoryExtractor.extractFromConversation()` — 异步记忆提取(.catch 静默处理)
- `intelligenceClient.reflection.recordConversation()` — 对话记录(.catch 静默处理)
- `intelligenceClient.reflection.shouldReflect()` — 反射触发检查
- `generateFollowUpSuggestions(content)` — 关键词建议生成 → `setSuggestions()`
- 浏览器 TTS如已启用
- `cancelStream()` — 取消当前流式响应
- `setChatMode(mode: ChatModeType)` — 切换聊天模式
- `getChatModeConfig()` — 获取当前模式配置
- `setSuggestions(suggestions: string[])` — 设置建议列表
**批量保存机制:**
```
流式开始(用户消息 + assistant 占位已原子写入 messageStore
├── dirty 标志管理
│ └── 每次 delta/thinking/tool 更新后设置 dirty = true
├── 每 3 秒检查 dirty 标志
│ └── dirty → conversationStore.upsertActiveConversation()
├── 每累积 50 条 delta 强制保存
├── onComplete → 立即保存 + 触发副作用
└── onError → 立即保存(保留已接收的部分内容)
```
**跨 Store 同步契约**: streamStore 调用 messageStore 和 conversationStore 的方法均为同步 Zustand `set()` 调用。批量保存计时器(`setInterval`)在 Zustand 事务外运行,读取的 `messages` 始终是上一帧的完整快照——不存在部分写入的中间态。
**依赖:** streamStore → messageStore更新流式消息、conversationStore保存对话、offlineStore离线队列、connectionStore选择 adapter
### 3.5 artifactStore
**职责:** Artifact 面板管理(从现有 ChatStore 直接提取,无逻辑变更)
**状态:**
- `artifacts: ArtifactFile[]`
- `selectedArtifactId: string | null`
- `artifactPanelOpen: boolean`
**Actions:**
- `addArtifact(artifact)`, `selectArtifact(id)`, `setArtifactPanelOpen(open)`, `clearArtifacts()`
### 3.6 Facade 兼容层
保留 `chatStore.ts` 作为 re-export facade确保渐进迁移
```typescript
// chatStore.ts (facade)
export { useConversationStore } from './chat/conversationStore'
export { useMessageStore } from './chat/messageStore'
export { useStreamStore } from './chat/streamStore'
export { useArtifactStore } from './chat/artifactStore'
// 兼容层 — 逐步迁移后删除
export { useChatStore } from './chat/chatStoreCompat'
```
`chatStoreCompat` 聚合所有子 Store 的状态和 actions 为统一的 `useChatStore` 接口,使现有组件无需修改即可继续工作。
## 4. 统一流式抽象层
### 4.1 StreamingAdapter 接口
文件: `desktop/src/lib/streaming-adapter.ts`
```typescript
interface StreamCallbacks {
onDelta: (delta: string) => void
onThinkingDelta?: (delta: string) => void
onToolStart?: (name: string, input: unknown) => void
onToolEnd?: (name: string, output: unknown) => void
onHandStart?: (name: string) => void
onHandEnd?: (name: string, result: unknown) => void
onWorkflowStart?: (workflowId: string) => void
onWorkflowEnd?: (workflowId: string, result: unknown) => void
onComplete: (tokens: TokenUsage) => void
onError: (error: string) => void
}
interface TokenUsage {
inputTokens: number
outputTokens: number
}
interface StreamHandle {
cancel(): void
readonly active: boolean
}
interface StreamingAdapter {
start(
agentId: string,
message: string,
sessionId: string,
mode: ChatModeType,
callbacks: StreamCallbacks
): StreamHandle
isAvailable(): boolean
}
```
### 4.2 KernelStreamAdapter
封装现有 `kernel-chat.ts``chatStream` 方法:
- 监听 Tauri `stream:chunk` 事件
- **TokenUsage 适配**: 现有 `kernel-types.ts``onComplete` 使用位置参数 `(inputTokens?: number, outputTokens?: number)`KernelStreamAdapter 内部将其转换为 `TokenUsage` 对象 `{ inputTokens, outputTokens }`
- 映射 `StreamChatEvent``StreamCallbacks`:
| StreamChatEvent | StreamCallbacks |
|----------------|-----------------|
| `delta` | `onDelta(text)` |
| `thinkingDelta` | `onThinkingDelta(thinking)` |
| `tool_start` | `onToolStart(name, input)` |
| `tool_end` | `onToolEnd(name, output)` |
| `handStart` | `onHandStart(name)` |
| `handEnd` | `onHandEnd(name, result)` |
| `complete` | `onComplete({ inputTokens, outputTokens })` |
| `error` | `onError(message)` |
- 5 分钟超时(保持现有行为)
- `cancel()` 调用新增的 Tauri command `cancel_stream(session_id)`
- `iteration_start` 事件内部日志记录,不暴露到 callbacks
### 4.3 GatewayStreamAdapter
封装 GatewayClient 的 WebSocket 流式:
- 使用 GatewayClient 的 `chatStream` 方法
- 映射 `AgentStreamDelta` 事件到 `StreamCallbacks`:
| AgentStreamDelta | StreamCallbacks |
|-----------------|-----------------|
| `stream === 'assistant'` | `onDelta(content)` |
| `stream === 'thinking'` | `onThinkingDelta(content)` |
| `stream === 'tool'` + `step === 'start'` | `onToolStart(name, input)` |
| `stream === 'tool'` + `step === 'end'` | `onToolEnd(name, output)` |
| `stream === 'hand'` + `step === 'start'` | `onHandStart(name)` |
| `stream === 'hand'` + `step === 'end'` | `onHandEnd(name, result)` |
| `stream === 'workflow'` + `step === 'start'` | `onWorkflowStart(workflowId)` |
| `stream === 'workflow'` + `step === 'end'` | `onWorkflowEnd(workflowId, result)` |
| `stream === 'lifecycle'` + `phase === 'end'` | `onComplete(tokens)` |
| `stream === 'error'` | `onError(message)` |
- 新增 5 分钟超时(与 Kernel 统一)
- `cancel()` 关闭 WebSocket 连接并发送取消消息
- 统一 `onComplete` 签名,包含 TokenUsage 参数
### 4.4 适配器选择
`streamStore` 通过 `connectionStore.getClient()` 获取当前客户端实例,判断类型选择 adapter:
```typescript
const client = getClient()
const adapter = client instanceof KernelClient
? kernelStreamAdapter
: gatewayStreamAdapter
```
## 5. 类型统一
### 5.1 统一 ChatMessage 类型
文件: `desktop/src/types/chat.ts`
```typescript
interface ChatMessage {
id: string
role: 'user' | 'assistant' | 'tool' | 'hand' | 'workflow' | 'system'
content: string
timestamp: Date
streaming?: boolean
optimistic?: boolean
// thinking
thinkingContent?: string
// error & retry
error?: string
originalContent?: string
// tool/hand context
toolSteps?: ToolStep[]
handName?: string
handStatus?: string
handResult?: unknown
// workflow
workflowId?: string
workflowStep?: number
workflowStatus?: string
workflowResult?: unknown
// subtasks
subtasks?: Subtask[]
// attachments
files?: MessageFile[]
codeBlocks?: CodeBlock[]
// metadata
metadata?: {
inputTokens?: number
outputTokens?: number
model?: string
runId?: string
}
}
```
### 5.2 辅助类型
```typescript
// ToolStep 替代现有 ToolCallStep统一命名
interface ToolStep {
id: string
name: string
status: 'running' | 'completed' | 'error'
input?: unknown
output?: unknown
startTime: Date
endTime?: Date
}
interface Subtask {
id: string
title: string
status: 'pending' | 'in_progress' | 'completed' | 'failed'
result?: unknown
}
interface SendMessageContext {
files?: MessageFile[]
parentMessageId?: string
}
```
### 5.3 SessionMessage → ChatMessage 映射
Gateway 路径的会话历史使用 `SessionMessage`API 响应格式,字符串日期),需要映射函数:
```typescript
// desktop/src/types/chat.ts
function sessionToChatMessage(sm: SessionMessage): ChatMessage {
return {
id: sm.id,
role: mapSessionRole(sm.role), // 'user'/'assistant'/'system' 直接映射
content: sm.content,
timestamp: new Date(sm.timestamp),
metadata: {
model: sm.metadata?.model,
inputTokens: sm.metadata?.tokens?.input,
outputTokens: sm.metadata?.tokens?.output,
},
}
}
```
`sessionStore` 内部继续使用 API 响应的 `SessionMessage` 类型(它是 API 契约),仅在展示层转换为 `ChatMessage`。不需要修改 `sessionStore` 的内部类型。
### 5.4 类型清理
| 类型 | 文件 | 动作 |
|------|------|------|
| `ChatStore.Message` | `store/chatStore.ts` | 迁移到 `types/chat.ts``ChatMessage` |
| `ConversationContext` | `components/ai/Conversation.tsx` | 仅删除未使用的 Provider/Context/hook保留滚动容器组件 |
| `initStreamListener` | `store/chatStore.ts` | 被 `streamStore` + `StreamingAdapter` 替代 |
## 6. Cancel 机制
### 6.1 前端Phase 5a — 纯前端取消)
前端 cancel 分两步实现先纯前端方案Phase 5a后端配合后完善Phase 5b
```typescript
// streamStore
cancelStream() {
if (this.streamHandle?.active) {
this.streamHandle.cancel()
}
// 标记当前流式消息为已完成
const streamingMsg = messageStore.getStreamingMessage()
if (streamingMsg) {
messageStore.updateMessage(streamingMsg.id, {
streaming: false,
content: streamingMsg.content + '\n\n_响应已取消_'
})
}
set({ isStreaming: false, streamHandle: null })
conversationStore.upsertActiveConversation() // 立即保存
}
```
**KernelStreamAdapter.cancel()**Phase 5a 纯前端):
- 停止监听 Tauri `stream:chunk` 事件(移除 listener
- 不通知后端停止,后端继续运行直到自然完成或 5 分钟超时
- 前端标记消息为已取消,用户可继续操作
**GatewayStreamAdapter.cancel()**Phase 5a 纯前端):
- 发送 WebSocket 取消消息 `{ type: "cancel", sessionId }`(如果 Gateway 服务端已支持则生效)
- 关闭事件监听器
### 6.2 后端配合Phase 5b — 需新基础设施)
Phase 5b 在后端基础设施就绪后实施。需要在 Tauri 端新增:
1. **SessionStreamGuards 状态**: 在 `lib.rs` 中注册 `DashMap<String, Arc<AtomicBool>>` 作为 Tauri managed state
2. **cancel_stream command**: 读取 guards map设置取消标志
3. **流式循环检查**: `tokio::spawn` 内每轮迭代检查 `cancel_flag`
```rust
// chat.rs — 取消标志写入
#[tauri::command]
async fn cancel_stream(
session_id: String,
guards: State<'_, SessionStreamGuards>,
) -> Result<(), String> {
if let Some(pair) = guards.0.get(&session_id) {
pair.value().store(true, Ordering::SeqCst);
}
Ok(())
}
// agent_chat_stream — 在每轮接收循环中检查
let cancelled = cancel_flag.load(Ordering::Relaxed);
if cancelled {
tx.send(LoopEvent::Complete(AgentLoopResult {
response: "...".into(),
input_tokens: 0,
output_tokens: 0,
iterations,
})).ok();
break;
}
```
Phase 5a 和 5b 可独立交付Phase 5a 即可满足用户需求。
## 7. 迁移计划
### 7.1 迁移顺序
| Phase | 内容 | 影响文件数 | 风险 |
|-------|------|-----------|------|
| 0 | 创建 `types/chat.ts` + `store/chat/` 目录 | 新建 | 无 |
| 1 | 提取 `artifactStore` | ~5 | 极低 |
| 2 | 提取 `conversationStore`(含 agents、sessionKey | ~8 | 低 |
| 3 | 提取 `messageStore`(含 token 统计、search | ~10 | 中 |
| 4 | 提取 `streamStore` + `StreamingAdapter`含离线检查、副作用、suggestions | ~12 | 中高 |
| 5a | 前端 cancel纯前端停止监听+标记已取消) | ~3 | 低 |
| 5b | 后端 cancelRust SessionStreamGuards + cancel_stream command | ~4 | 中 |
| 6 | 实现定时批量持久化IndexedDB + 配额检查) | ~4 | 中 |
| 7 | 删除旧代码 + 清理 facade | ~6 | 低 |
| 8 | 删除死代码ConversationContext、旧类型映射 | ~4 | 低 |
### 7.2 每阶段验证
每个 Phase 完成后执行:
1. `pnpm tsc --noEmit` — 类型检查通过
2. `pnpm vitest run` — 现有测试通过
3. 手动验证: 发送消息 → 流式响应 → 切换对话 → 刷新页面数据保持
4. 手动验证Phase 5 后): cancel 流式响应 → 状态正确恢复
### 7.3 关键文件清单
| 文件 | 角色 |
|------|------|
| `desktop/src/store/chatStore.ts` | 重构对象,最终保留为 facade |
| `desktop/src/store/chat/conversationStore.ts` | 新建 |
| `desktop/src/store/chat/messageStore.ts` | 新建 |
| `desktop/src/store/chat/streamStore.ts` | 新建 |
| `desktop/src/store/chat/artifactStore.ts` | 新建 |
| `desktop/src/store/chat/chatStoreCompat.ts` | 新建(兼容层,最终删除) |
| `desktop/src/lib/streaming-adapter.ts` | 新建StreamingAdapter 接口 + 双实现) |
| `desktop/src/lib/kernel-chat.ts` | 修改KernelStreamAdapter 封装) |
| `desktop/src/types/chat.ts` | 新建ChatMessage + ToolStep + Subtask |
| `desktop/src/types/session.ts` | 保留API 契约类型),添加映射函数 |
| `desktop/src/components/ChatArea.tsx` | 逐步迁移 import |
| `desktop/src/components/ai/Conversation.tsx` | 清理死代码(仅 Context/Provider |
| `desktop/src/store/index.ts` | 注册新 Store |
| `desktop/src/store/offlineStore.ts` | 不修改streamStore 调用其 API |
| `desktop/src-tauri/src/kernel_commands/chat.rs` | Phase 5b: 新增 cancel_stream + SessionStreamGuards |
| `desktop/src-tauri/src/lib.rs` | Phase 5b: 注册 cancel_stream + guards state |
## 8. streamStore 完成后副作用
`streamStore.sendMessage``onComplete` 回调在流式响应完成后触发以下副作用。这些副作用在 `streamStore` 内部处理,不属于 `StreamingAdapter` 的职责。
```typescript
// streamStore 内部 onComplete 处理
async function handleComplete(tokens: TokenUsage) {
// 1. 更新消息状态
messageStore.completeMessage(streamingMsgId, tokens)
// 2. 立即持久化
conversationStore.upsertActiveConversation()
// 3. 记忆提取(非阻塞,失败静默)
try {
const extractor = getMemoryExtractor()
if (extractor) {
await extractor.extractFromConversation(
conversationStore.getCurrentConversation()
)
}
} catch (e) {
logger.warn('Memory extraction failed', e)
}
// 4. 对话反思跟踪(非阻塞)
try {
const client = getIntelligenceClient()
if (client?.reflection) {
await client.reflection.recordConversation(...)
const shouldReflect = await client.reflection.shouldReflect(...)
if (shouldReflect) {
// 触发反思流程
}
}
} catch (e) {
logger.warn('Reflection tracking failed', e)
}
// 5. 后续建议生成
const suggestions = generateFollowUpSuggestions(lastAssistantContent)
set({ suggestions })
// 6. 语音朗读(如果用户开启)
if (speechSettings.autoSpeak) {
speechSynth.speak(lastAssistantContent)
}
}
```
## 9. 离线队列集成
`streamStore.sendMessage` 在发起流式请求之前检查离线状态:
```typescript
async sendMessage(content: string, context?: SendMessageContext) {
// 1. 离线检查(优先级最高)
// 注意isOffline 是 boolean 属性不是函数queueMessage 使用位置参数
const { isOffline, queueMessage } = useOfflineStore.getState()
if (isOffline) {
const userMsg = createUserMessage(content, context?.files)
messageStore.addMessage(userMsg)
messageStore.addMessage(createSystemMessage('消息已加入离线队列,网络恢复后将自动发送'))
queueMessage(content, conversationStore.currentAgent?.id, conversationStore.sessionKey ?? undefined)
conversationStore.upsertActiveConversation()
return // 不继续流式请求
}
// 2. 正常流式流程...
}
```
## 10. streamStore 状态补充
`suggestions``chatMode` 归属 streamStore
**状态:**
- `suggestions: string[]`
- `chatMode: ChatModeType`
**Actions:**
- `setSuggestions(suggestions: string[])` — 设置后续建议
- `setChatMode(mode: ChatModeType)` — 切换聊天模式
- `getChatModeConfig()` — 获取当前模式配置
- `searchSkills(query: string)` — 委托给 `getSkillDiscovery().searchSkills()`
`totalInputTokens`/`totalOutputTokens` 为仅会话内累计(不持久化),刷新后重置为 0。
## 11. 兼容层迁移指南
`chatStoreCompat.ts` 聚合子 Store 为统一的 `useChatStore` 接口,确保现有 19 个消费者文件无需修改。
```typescript
// chatStoreCompat.ts — 使用 Zustand subscribe 保持响应式
// 注意:不能在 create() 中直接 .getState(),那样只会读取初始值不会响应变化
import { subscribe } from 'zustand'
// 方案:直接 re-export 子 Store组件按需导入
export { useConversationStore as useConversationStore } from './chat/conversationStore'
export { useMessageStore as useMessageStore } from './chat/messageStore'
export { useStreamStore as useStreamStore } from './chat/streamStore'
export { useArtifactStore as useArtifactStore } from './chat/artifactStore'
// 兼容 hook聚合所有子 store 状态供旧组件使用
// 使用 useSyncExternalStore 或每个子 store 的独立 hook 组合
export function useChatStore<T>(selector: (state: ChatCompatState) => T): T {
// 方案 A推荐组件直接从子 store 导入
// 方案 B过渡期聚合 hook内部使用多个 useSelector
const conv = useConversationStore(selector)
const msg = useMessageStore(selector)
const stream = useStreamStore(selector)
const art = useArtifactStore(selector)
return selector({ ...conv, ...msg, ...stream, ...art })
}
```
> **重要**:兼容层是过渡性代码,仅保证旧组件可编译运行。新代码必须直接使用子 Store。每个 Phase 迁移一部分组件后,兼容层逐步缩小。最终删除。
迁移方式:每 Phase 完成后,逐步将组件的 `import { useChatStore } from './chatStore'` 改为直接从子 Store 导入。最终删除 `chatStoreCompat.ts`
## 12. 不在本次范围内
以下项目明确排除,作为后续迭代考虑:
- **消息分页/懒加载**当前所有消息全量在内存Phase 2 考虑)
- **文件真实上传**(当前附件是伪文本标记,需后端配合)
- **TitleMiddleware 实现**(后端 placeholder需 LLM driver 接入)
- **消息导出增强**(当前仅 Markdown 导出)