Files
zclaw_openfang/docs/knowledge-base/openmaic-analysis.md
iven 0d4fa96b82
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
refactor: 统一项目名称从OpenFang到ZCLAW
重构所有代码和文档中的项目名称,将OpenFang统一更新为ZCLAW。包括:
- 配置文件中的项目名称
- 代码注释和文档引用
- 环境变量和路径
- 类型定义和接口名称
- 测试用例和模拟数据

同时优化部分代码结构,移除未使用的模块,并更新相关依赖项。
2026-03-27 07:36:03 +08:00

30 KiB
Raw Permalink Blame History

OpenMAIC 深度分析报告

来源: https://github.com/THU-MAIC/OpenMAIC 本地路径: G:\edu\OpenMAIC 分析日期: 2026-03-22 (初版) / 2026-03-26 (深度分析) 许可证: AGPL-3.0

1. 项目概述

1.1 项目定位

OpenMAIC (Open Multi-Agent Interactive Classroom) 是由清华大学 MAIC 团队开发的开源 AI 互动课堂平台。它能够将任何主题或文档转化为丰富的互动学习体验,核心特点是多智能体协作驱动的教育场景生成。

  • 在线演示: https://open.maic.chat/
  • 学术论文: 发表于 JCST'26 (Journal of Computer Science and Technology)

1.2 主要功能和特性

功能模块 描述
一键课堂生成 输入主题或上传文档,自动生成完整课堂
多智能体课堂 AI 老师和 AI 同学实时授课、讨论、互动
丰富场景类型 幻灯片、测验、HTML 交互式模拟、项目制学习 (PBL)
白板 & 语音 智能体实时绘制图表、书写公式、语音讲解
导出功能 支持导出 .pptx 幻灯片或交互式 .html 网页
ZCLAW 集成 可从飞书、Slack、Telegram 等聊天应用中直接生成课堂

1.3 目标用户群体

  • 教育工作者: 快速创建互动课程内容
  • 学生: 获得沉浸式、个性化的学习体验
  • 企业培训: 自动化培训材料生成
  • 内容创作者: 将文档转化为互动演示

2. 技术架构

2.1 项目结构

OpenMAIC/
├── app/                        # Next.js App Router
│   ├── api/                    # 服务端 API 路由 (~18 个端点)
│   │   ├── generate/           # 场景生成流水线
│   │   ├── generate-classroom/ # 异步课堂生成提交与轮询
│   │   ├── chat/               # 多智能体讨论 (SSE 流式传输)
│   │   ├── pbl/                # 项目制学习端点
│   │   └── ...                 # quiz-grade, parse-pdf, web-search 等
│   ├── classroom/[id]/         # 课堂回放页面
│   └── page.tsx                # 首页
├── lib/                        # 核心业务逻辑
│   ├── generation/             # 两阶段课堂生成流水线
│   ├── orchestration/          # LangGraph 多智能体编排
│   ├── playback/               # 回放状态机
│   ├── action/                 # 动作执行引擎
│   ├── ai/                     # LLM 服务商抽象层
│   ├── api/                    # Stage API 门面
│   ├── store/                  # Zustand 状态管理
│   └── types/                  # TypeScript 类型定义
├── components/                 # React UI 组件
│   ├── slide-renderer/         # Canvas 幻灯片编辑器
│   ├── scene-renderers/        # Quiz/Interactive/PBL 场景渲染器
│   ├── generation/             # 课堂生成工具栏
│   ├── chat/                   # 聊天区域和会话管理
│   ├── settings/               # 设置面板
│   ├── whiteboard/             # SVG 白板绘图
│   ├── agent/                  # 智能体头像、配置
│   └── ui/                     # 基础 UI 组件 (shadcn/ui)
├── packages/                   # 工作区子包
│   ├── pptxgenjs/              # 定制化 PowerPoint 生成
│   └── mathml2omml/            # MathML → Office Math 转换
└── skills/openmaic/            # ZCLAW Skill 定义

2.2 技术栈

层级 技术
前端框架 Next.js 16 + React 19
状态管理 Zustand 5
样式方案 Tailwind CSS 4
LLM SDK Vercel AI SDK + LangGraph
类型系统 TypeScript 5
Canvas 渲染 @napi-rs/canvas
幻灯片渲染 基于 PPTist 的 Canvas 引擎
存储 IndexedDB (Dexie)
富文本编辑 ProseMirror

2.3 核心模块和组件

A. 生成流水线 (lib/generation/)

两阶段生成架构:

  1. 大纲生成 (Stage 1): 分析用户输入,生成结构化课堂大纲
  2. 场景生成 (Stage 2): 每个大纲条目生成为丰富的场景
用户输入 → 大纲生成器 → 场景生成器 → 完整课堂
              ↓              ↓
         SceneOutline[]  Scene[] (含 Actions)

B. 多智能体编排 (lib/orchestration/)

LangGraph 状态机拓扑:

START → director ──(end)──→ END
           │
           └─(next)→ agent_generate ──→ director (loop)

Director 策略:

  • 单智能体: 纯代码逻辑,无 LLM 调用
  • 多智能体: LLM 决定下一个发言的智能体

C. 回放引擎 (lib/playback/engine.ts)

状态机:

            start()                  pause()
idle ──────────────────→ playing ──────────────→ paused
  ▲                         ▲                       │
  │                         │  resume()             │
  │                         └───────────────────────┘
  │
  │  handleEndDiscussion()
  │                         confirmDiscussion()
  │                         / handleUserInterrupt()
  │                              │
  │                              ▼         pause()
  └──────────────────────── live ──────────────→ paused

D. 动作引擎 (lib/action/engine.ts)

支持 28+ 种动作类型:

类别 动作
视觉特效 (Fire-and-forget) spotlight, laser
语音 speech (带 TTS)
白板 wb_open, wb_close, wb_draw_text, wb_draw_shape, wb_draw_chart, wb_draw_latex, wb_draw_table, wb_draw_line, wb_clear, wb_delete
视频 play_video
讨论 discussion

2.4 数据流和通信机制

核心数据流:

用户操作 → React UI → Zustand Store → Next.js API → LangGraph → LLM
                ↓                              ↓
           SSE Stream ← StatelessEvent ← Agent Response

SSE 事件类型 (StatelessEvent):

  • agent_start / agent_end: 智能体开始/结束
  • text_delta: 文本增量
  • action: 动作执行
  • thinking: 思考状态
  • cue_user: 提示用户发言
  • done / error: 完成/错误

3. 核心能力

3.1 Agent 架构设计

智能体配置结构 (AgentConfig):

interface AgentConfig {
  id: string;              // 唯一 ID
  name: string;            // 显示名称
  role: string;            // 角色: teacher, assistant, student
  persona: string;         // 完整系统提示词
  avatar: string;          // 头像 URL 或 emoji
  color: string;           // UI 主题色
  allowedActions: string[]; // 允许的动作类型
  priority: number;        // Director 选择优先级 (1-10)
  isDefault: boolean;      // 是否默认模板
  isGenerated?: boolean;   // 是否由 LLM 生成
}

默认智能体:

ID 名称 角色 优先级
default-1 AI teacher teacher 10
default-2 AI助教 assistant 7
default-3 显眼包 student 4
default-4 好奇宝宝 student 5
default-5 笔记员 student 5
default-6 思考者 student 6

角色-动作映射:

const ROLE_ACTIONS = {
  teacher: [...SLIDE_ACTIONS, ...WHITEBOARD_ACTIONS],  // 全部能力
  assistant: [...WHITEBOARD_ACTIONS],                   // 仅白板
  student: [...WHITEBOARD_ACTIONS],                     // 仅白板
};

3.2 工具/能力系统

动作执行架构:

class ActionEngine {
  async execute(action: Action): Promise<void> {
    // 1. 自动打开白板 (如果需要)
    // 2. 根据动作类型执行
    switch (action.type) {
      case 'spotlight': // Fire-and-forget
      case 'laser':
      case 'speech':    // 同步等待 TTS
      case 'wb_*':      // 同步等待渲染
    }
  }
}

结构化输出格式 (LLM 生成):

[
  {"type": "action", "name": "spotlight", "params": {"elementId": "img_1"}},
  {"type": "text", "content": "Hello students..."},
  {"type": "action", "name": "wb_draw_text", "params": {...}}
]

3.3 记忆/上下文管理

无状态架构设计:

  • 后端完全无状态,所有状态由客户端维护
  • 每次请求携带完整上下文 (StatelessChatRequest)

DirectorState (跨轮次传递):

interface DirectorState {
  turnCount: number;                    // 当前轮次
  agentResponses: AgentTurnSummary[];   // 智能体响应历史
  whiteboardLedger: WhiteboardActionRecord[]; // 白板操作记录
}

存储层:

  • IndexedDB (Dexie): 课堂数据、大纲、生成的智能体
  • localStorage: 智能体注册表、用户配置
  • 持久化策略: Zustand persist middleware + debounce 保存

3.4 多模态支持

模态 实现
文本 流式生成 + SSE
语音 Azure TTS / 浏览器 TTS
图像 多服务商 (Kling, Qwen, Seedance 等)
视频 Kling, Veo, Seedance
LaTeX KaTeX 渲染
图表 ECharts

4. 代码质量评估

4.1 代码组织方式

优点:

  • 清晰的模块划分
  • 类型集中管理 (lib/types/)
  • API 门面模式 (lib/api/stage-api.ts)
  • 关注点分离 (生成/播放/动作)

文件规模:

  • 核心文件 200-800 行
  • 最大文件 director-graph.ts 约 450 行

4.2 测试覆盖

未发现测试文件 - 这是项目的明显短板。建议添加:

  • 单元测试: 生成流水线、动作解析
  • 集成测试: API 端点
  • E2E 测试: 课堂生成流程

4.3 文档完善度

优点:

  • 详细的 README (中英双语)
  • 内联注释丰富
  • SKILL.md 示例展示了 Skill 系统用法

不足:

  • 缺少 API 文档
  • 缺少架构图 (除 README 中的文字描述)
  • 无贡献指南细节

4.4 可扩展性设计

良好实践:

  • Provider 抽象: 统一的 LLM 服务商接口
  • Action 插件化: 易于添加新动作类型
  • Scene 类型扩展: 支持 slide/quiz/interactive/pbl
  • Agent 注册表: 支持动态添加智能体

扩展点:

// 添加新 Provider
PROVIDERS['new-provider'] = { ... };

// 添加新 Action 类型
type Action = ... | NewAction;

// 添加新 Scene 类型
type SceneContent = ... | NewContent;

5. 与 ZCLAW 的整合分析

5.1 可复用的组件

组件 来源路径 ZCLAW 适用场景
LLM Provider 抽象 lib/ai/providers.ts 统一多模型支持
结构化输出解析 lib/orchestration/stateless-generate.ts Tool Call 解析
Action 系统 lib/types/action.ts + lib/action/engine.ts Agent 能力定义
智能体注册表 lib/orchestration/registry/ Agent 配置管理
Zustand Store 模式 lib/store/ 状态管理参考
SKILL.md 格式 skills/openmaic/SKILL.md Skill 系统设计

5.2 架构参考价值

A. 无状态后端设计

OpenMAIC 的无状态架构非常适合 ZCLAW 参考:

// StatelessChatRequest - 所有状态由客户端传递
interface StatelessChatRequest {
  messages: UIMessage[];      // 对话历史
  storeState: { ... };        // 应用状态
  config: { agentIds, ... };  // 智能体配置
  directorState?: DirectorState; // 跨轮次状态
}

ZCLAW 可采用类似模式,避免服务端状态管理复杂性。

B. LangGraph 多智能体编排

// Director Graph - 智能体调度状态机
const graph = new StateGraph(OrchestratorState)
  .addNode('director', directorNode)
  .addNode('agent_generate', agentGenerateNode)
  .addEdge(START, 'director')
  .addConditionalEdges('director', directorCondition, {...})
  .addEdge('agent_generate', 'director');

ZCLAW 的多 Agent 协作可参考此模式。

C. Action 执行引擎

// 统一的动作执行入口
class ActionEngine {
  async execute(action: Action): Promise<void> {
    // Fire-and-forget vs Synchronous
  }
}

ZCLAW 的 Hands 系统可采用类似架构。

5.3 潜在的整合方式

方式 1: 作为 ZCLAW 的 Skill

OpenMAIC 可作为 ZCLAW 的一个 Skill 集成:

# skills/openmaic/SKILL.md
---
name: openmaic
description: 生成互动课堂
---

用户可通过 ZCLAW 调用 OpenMAIC 的课堂生成能力。

方式 2: 共享组件库

抽取共享组件:

  • zclaw-shared-types: Action 类型、Provider 接口
  • zclaw-action-engine: 通用动作执行引擎
  • zclaw-llm-adapter: LLM 服务商适配器

方式 3: 架构借鉴

OpenMAIC 特性 ZCLAW 对应
Director Graph zclaw-kernel 调度器
Agent Registry Agent 分身管理
Action Engine Hands 能力系统
Stage/Scene 会话/任务管理

5.4 需要适配的部分

差异点 OpenMAIC ZCLAW 适配建议
运行时 Next.js (服务端) Tauri (桌面端) 重构为 Rust 调用
状态存储 IndexedDB SQLite 保持数据结构,换存储后端
通信协议 SSE over HTTP gRPC / Tauri Commands 适配流式响应
UI 框架 React + Next.js React + Tauri 组件可复用
部署模式 Web / Vercel 桌面应用 需本地 LLM 支持

6. 总结与建议

6.1 OpenMAIC 的优势

  1. 成熟的多智能体编排: LangGraph 状态机设计精良
  2. 丰富的场景类型: 幻灯片、测验、交互、PBL 全覆盖
  3. 完善的多模态支持: 文本、语音、图像、视频、白板
  4. 无状态架构: 易于扩展和维护
  5. 学术论文支撑: 有理论基础

6.2 OpenMAIC 的不足

  1. 缺少测试: 无单元测试、集成测试
  2. Web-only: 无桌面端支持
  3. 依赖外部服务: 需要多个 API Key
  4. 文档分散: 缺少集中式 API 文档

6.3 对 ZCLAW 的建议

  1. 借鉴无状态设计: 将状态管理收敛到客户端
  2. 采用 Action 系统模式: 统一 Hands 能力接口
  3. 参考 LangGraph 编排: 实现多 Agent 协作
  4. 复用 Provider 抽象: 统一 LLM 服务商管理
  5. 保持桌面端优势: OpenMAIC 的 Web 限制是 ZCLAW 的机会

7. 关键代码参考

7.1 Provider 抽象接口

// lib/ai/providers.ts
export type ProviderId = 'openai' | 'anthropic' | 'google' | ...;

export const PROVIDERS: Record<ProviderId, ProviderConfig> = {
  openai: {
    name: 'OpenAI',
    models: ['gpt-4o', 'gpt-4o-mini', ...],
    defaultModel: 'gpt-4o-mini',
  },
  // ...
};

7.2 Action 类型定义

// lib/types/action.ts
export type Action =
  | SpotlightAction
  | LaserAction
  | SpeechAction
  | WhiteboardAction
  | VideoAction
  | DiscussionAction;

export interface ActionBase {
  type: string;
  id?: string;
}

7.3 Agent 配置结构

// lib/types/agent.ts
export interface AgentConfig {
  id: string;
  name: string;
  role: 'teacher' | 'assistant' | 'student';
  persona: string;
  avatar: string;
  color: string;
  allowedActions: string[];
  priority: number;
  isDefault: boolean;
}

8. AGPL-3.0 许可证风险分析

8.1 风险评估

风险点 影响 严重程度
Copyleft 传染 整合代码可能要求 ZCLAW 也开源 🔴
网络条款 AGPL-3.0 的网络使用条款比 GPL 更严格 🔴
商业影响 可能影响 ZCLAW 的商业化能力 🔴

8.2 决策

不直接整合 OpenMAIC 代码 仅借鉴架构思想和设计模式


9. 基于 ZCLAW 现有能力的实现方案

9.1 ZCLAW 已有能力对照

OpenMAIC 功能 ZCLAW 对应 成熟度
多 Agent 编排 (Director Graph) A2A 协议 + Kernel Registry 框架完成
Agent 角色配置 Skills + Agent 分身 完成
动作执行引擎 (28+ Actions) Hands 能力系统 完成
工作流编排 Trigger + EventBus 基础完成
状态管理 MemoryStore (SQLite) 完成
外部集成 Channels 框架完成

9.2 实现路径

  1. 完善 A2A 通信 - 实现 crates/zclaw-protocols/src/a2a.rs 中的 TODO
  2. 扩展 Hands - 添加 whiteboard/slideshow/speech/quiz 能力
  3. 创建 Skill - classroom-generator 课堂生成技能
  4. 工作流增强 - DAG 编排、条件分支、并行执行

9.3 需要新增的文件

hands/whiteboard.HAND.toml   # 白板能力
hands/slideshow.HAND.toml    # 幻灯片能力
hands/speech.HAND.toml       # 语音能力
hands/quiz.HAND.toml         # 测验能力
skills/classroom-generator/SKILL.md  # 课堂生成

10. 后续行动项

  • 完善 A2A 协议实现(消息路由、能力发现)
  • 创建教育类 Handswhiteboard、slideshow、speech、quiz
  • 开发 classroom-generator Skill
  • 增强工作流编排能力DAG、条件分支

11. 深度架构分析 (2026-03-26 补充)

11.1 Director Graph 核心实现

文件: lib/orchestration/director-graph.ts

11.1.1 状态定义

const OrchestratorState = Annotation.Root({
  // 输入 (图入口时设置一次)
  messages: Annotation<UIMessage<ChatMessageMetadata>[]>,
  storeState: Annotation<{
    stage: Stage | null;
    scenes: Scene[];
    currentSceneId: string | null;
    mode: StageMode;
    whiteboardOpen: boolean;
  }>,
  availableAgentIds: Annotation<string[]>,
  maxTurns: Annotation<number>,
  languageModel: Annotation<LanguageModel>,
  triggerAgentId: Annotation<string | null>,
  agentConfigOverrides: Annotation<Record<string, AgentConfig>>,

  // 可变 (节点更新)
  currentAgentId: Annotation<string | null>,
  turnCount: Annotation<number>,
  agentResponses: Annotation<AgentTurnSummary[]>({
    reducer: (prev, update) => [...prev, ...update],
    default: () => [],
  }),
  whiteboardLedger: Annotation<WhiteboardActionRecord[]>({
    reducer: (prev, update) => [...prev, ...update],
    default: () => [],
  }),
  shouldEnd: Annotation<boolean>,
  totalActions: Annotation<number>,
});

11.1.2 Director 节点核心逻辑

async function directorNode(state, config) {
  const write = config.writer as (chunk: StatelessEvent) => void;
  const isSingleAgent = state.availableAgentIds.length <= 1;

  // Turn limit check
  if (state.turnCount >= state.maxTurns) {
    return { shouldEnd: true };
  }

  // 单 Agent: 纯代码逻辑,无 LLM 调用
  if (isSingleAgent) {
    const agentId = state.availableAgentIds[0] || 'default-1';
    if (state.turnCount === 0) {
      write({ type: 'thinking', data: { stage: 'agent_loading', agentId } });
      return { currentAgentId: agentId, shouldEnd: false };
    }
    write({ type: 'cue_user', data: { fromAgentId: agentId } });
    return { shouldEnd: true };
  }

  // 多 Agent: 快速路径 - 触发 Agent
  if (state.turnCount === 0 && state.triggerAgentId) {
    const triggerId = state.triggerAgentId;
    if (state.availableAgentIds.includes(triggerId)) {
      write({ type: 'thinking', data: { stage: 'agent_loading', agentId: triggerId } });
      return { currentAgentId: triggerId, shouldEnd: false };
    }
  }

  // 多 Agent: LLM 决策
  write({ type: 'thinking', data: { stage: 'director' } });
  const prompt = buildDirectorPrompt(agents, conversationSummary, ...);
  const result = await adapter._generate([new SystemMessage(prompt), ...]);
  const decision = parseDirectorDecision(result.generations[0]?.text || '');

  if (decision.nextAgentId === 'USER') {
    write({ type: 'cue_user', data: { fromAgentId: state.currentAgentId } });
    return { shouldEnd: true };
  }

  write({ type: 'thinking', data: { stage: 'agent_loading', agentId: decision.nextAgentId } });
  return { currentAgentId: decision.nextAgentId, shouldEnd: false };
}

11.2 StreamBuffer 节奏控制

文件: lib/buffer/stream-buffer.ts

11.2.1 设计目的

位于 SSE 流和 React 状态之间的统一内容展示节奏控制层:

  • 固定速率 tick 循环逐字显示文本
  • 按顺序触发 Action 回调
  • 避免 LLM 流式输出和前端打字机的双重效果

11.2.2 缓冲项类型

type BufferItem =
  | { kind: 'agent_start'; messageId: string; agentId: string; agentName: string; avatar?: string; color?: string }
  | { kind: 'agent_end'; messageId: string; agentId: string }
  | { kind: 'text'; messageId: string; agentId: string; partId: string; text: string; sealed: boolean }
  | { kind: 'action'; messageId: string; actionId: string; actionName: string; params: Record<string, unknown>; agentId: string }
  | { kind: 'thinking'; stage: string; agentId?: string }
  | { kind: 'cue_user'; fromAgentId?: string; prompt?: string }
  | { kind: 'done'; totalActions: number; totalAgents: number; directorState?: DirectorState }
  | { kind: 'error'; message: string };

11.2.3 回调接口

interface StreamBufferCallbacks {
  onAgentStart(data: AgentStartItem): void;
  onAgentEnd(data: AgentEndItem): void;
  onTextReveal(messageId: string, partId: string, revealedText: string, isComplete: boolean): void;
  onActionReady(messageId: string, data: ActionItem): void;
  onLiveSpeech(text: string | null, agentId: string | null): void;  // Roundtable 实时语音
  onSpeechProgress(ratio: number | null): void;  // 播放进度
  onThinking(data: { stage: string; agentId?: string } | null): void;
  onCueUser(fromAgentId?: string, prompt?: string): void;
  onDone(data: { totalActions: number; totalAgents: number; directorState?: DirectorState }): void;
  onError(message: string): void;
}

11.2.4 Tick 循环核心逻辑

private tick(): void {
  if (this._paused || this._disposed) return;

  const item = this.items[this.readIndex];
  if (!item) return;

  switch (item.kind) {
    case 'text': {
      // 逐字显示
      this.charCursor = Math.min(this.charCursor + this.charsPerTick, item.text.length);
      const revealed = item.text.slice(0, this.charCursor);
      const fullyRevealed = this.charCursor >= item.text.length;
      const isComplete = fullyRevealed && item.sealed;

      this.cb.onTextReveal(item.messageId, item.partId, revealed, isComplete);
      this.cb.onLiveSpeech(revealed, this.currentAgentId);
      this.cb.onSpeechProgress(item.text.length > 0 ? this.charCursor / item.text.length : 1);

      if (isComplete) {
        this.readIndex++;
        this.charCursor = 0;
        this.advanceNonText();  // 处理后续非文本项
      }
      break;
    }
    case 'action': {
      this.cb.onActionReady(item.messageId, item);
      this.readIndex++;
      // Action 后延迟,让动画有时间播放
      if (this.actionDelayTicks > 0) {
        this._dwellTicksRemaining = this.actionDelayTicks;
      }
      break;
    }
    // ... 其他类型
  }
}

11.2.5 配置选项

interface StreamBufferOptions {
  tickMs?: number;           // Tick 间隔,默认 30ms
  charsPerTick?: number;     // 每 tick 显示字符数,默认 1
  postTextDelayMs?: number;  // 文本完成后延迟
  actionDelayMs?: number;    // Action 后延迟
}

11.3 Action 引擎详细实现

文件: lib/action/engine.ts

11.3.1 执行模式

模式 动作 行为
Fire-and-forget spotlight, laser 立即返回,不等待
Synchronous speech, wb_*, play_video, discussion 返回 Promise等待完成

11.3.2 核心执行流程

export class ActionEngine {
  private stageStore: StageStore;
  private audioPlayer: AudioPlayer | null;
  private effectTimer: ReturnType<typeof setTimeout> | null = null;

  async execute(action: Action): Promise<void> {
    // 自动打开白板
    if (action.type.startsWith('wb_') && action.type !== 'wb_open' && action.type !== 'wb_close') {
      await this.ensureWhiteboardOpen();
    }

    switch (action.type) {
      case 'spotlight':
        this.executeSpotlight(action);
        return;  // Fire-and-forget
      case 'speech':
        return this.executeSpeech(action);  // Synchronous
      // ... 其他
    }
  }

  // 视觉特效自动清除
  private scheduleEffectClear(): void {
    if (this.effectTimer) clearTimeout(this.effectTimer);
    this.effectTimer = setTimeout(() => {
      useCanvasStore.getState().clearAllEffects();
    }, 5000);  // 5 秒后自动清除
  }
}

11.3.3 白板动作实现

private async executeWbDrawText(action: WbDrawTextAction): Promise<void> {
  const wb = this.stageAPI.whiteboard.get();
  if (!wb.success || !wb.data) return;

  this.stageAPI.whiteboard.addElement({
    id: action.elementId || '',
    type: 'text',
    content: action.content,
    left: action.x,
    top: action.y,
    width: action.width ?? 400,
    height: action.height ?? 100,
    defaultColor: action.color ?? '#333333',
  }, wb.data.id);

  await delay(800);  // 等待淡入动画
}

11.4 设置状态管理

文件: lib/store/settings.ts

11.4.1 状态结构

interface SettingsState {
  // 模型选择
  providerId: ProviderId;
  modelId: string;
  providersConfig: ProvidersConfig;

  // TTS/ASR 设置
  ttsProviderId: TTSProviderId;
  ttsVoice: string;
  ttsSpeed: number;
  asrProviderId: ASRProviderId;
  asrLanguage: string;
  ttsProvidersConfig: Record<TTSProviderId, {...}>;
  asrProvidersConfig: Record<ASRProviderId, {...}>;

  // 媒体生成
  imageProviderId: ImageProviderId;
  imageModelId: string;
  videoProviderId: VideoProviderId;
  videoModelId: string;
  imageGenerationEnabled: boolean;
  videoGenerationEnabled: boolean;

  // Web Search
  webSearchProviderId: WebSearchProviderId;
  webSearchProvidersConfig: Record<WebSearchProviderId, {...}>;

  // Agent 设置
  selectedAgentIds: string[];
  maxTurns: string;
  agentMode: 'preset' | 'auto';
  autoAgentCount: number;

  // 播放控制
  ttsMuted: boolean;
  ttsVolume: number;
  autoPlayLecture: boolean;
  playbackSpeed: PlaybackSpeed;

  // 布局
  sidebarCollapsed: boolean;
  chatAreaCollapsed: boolean;
  chatAreaWidth: number;
}

11.4.2 持久化与迁移

export const useSettingsStore = create<SettingsState>()(
  persist(
    (set) => ({ /* state and actions */ }),
    {
      name: 'settings-storage',
      version: 2,
      migrate: (persistedState, version) => {
        // 版本迁移逻辑
        if (version === 0) { /* ... */ }
        if (version < 2) { /* ... */ }
        return state;
      },
      merge: (persistedState, currentState) => {
        // 合并内置 Provider 配置
        const merged = { ...currentState, ...persistedState };
        ensureBuiltInProviders(merged);
        return merged;
      },
    },
  ),
);

11.4.3 服务器配置合并

fetchServerProviders: async () => {
  const res = await fetch('/api/server-providers');
  const data = await res.json();

  set((state) => {
    // 重置所有服务器标记
    // 合并服务器配置
    // 自动选择/启用 (仅首次)
    return { /* updated state */ };
  });
}

11.5 无状态请求设计

文件: lib/types/chat.ts

interface StatelessChatRequest {
  // 对话历史 (客户端维护)
  messages: UIMessage<ChatMessageMetadata>[];

  // 当前应用状态
  storeState: {
    stage: Stage | null;
    scenes: Scene[];
    currentSceneId: string | null;
    mode: StageMode;
    whiteboardOpen: boolean;
  };

  // Agent 配置
  config: {
    agentIds: string[];
    sessionType?: 'qa' | 'discussion';
    discussionTopic?: string;
    triggerAgentId?: string;
    agentConfigs?: Array<{...}>;  // 动态生成的 Agent
  };

  // 跨轮次状态 (Director)
  directorState?: DirectorState;

  // 用户配置
  userProfile?: { nickname?: string; bio?: string };

  // API 凭证
  apiKey: string;
  baseUrl?: string;
  model?: string;
}

11.6 SSE 事件类型完整定义

type StatelessEvent =
  | { type: 'agent_start'; data: { messageId, agentId, agentName, agentAvatar?, agentColor? } }
  | { type: 'agent_end'; data: { messageId, agentId } }
  | { type: 'text_delta'; data: { content, messageId? } }
  | { type: 'action'; data: { actionId, actionName, params, agentId, messageId? } }
  | { type: 'thinking'; data: { stage: 'director' | 'agent_loading'; agentId? } }
  | { type: 'cue_user'; data: { fromAgentId?, prompt? } }
  | { type: 'done'; data: { totalActions, totalAgents, agentHadContent?, directorState? } }
  | { type: 'error'; data: { message } };

12. 对 ZCLAW 的关键借鉴点

12.1 StreamBuffer 节奏控制

问题: ZCLAW 目前可能存在 LLM 流式输出和前端打字机的双重效果

解决方案:

  1. 引入类似 StreamBuffer 的中间层
  2. 统一 Chat 和 Agent 回复的内容展示节奏
  3. 支持暂停/恢复/刷新

12.2 Director 快速路径优化

问题: 每次都需要 LLM 决策下一个 Agent

解决方案:

  1. 单 Agent 场景跳过 LLM 调用
  2. 触发 Agent 场景直接调度
  3. 仅多 Agent 复杂场景使用 LLM 决策

12.3 无状态设计

问题: ZCLAW 服务端 Session 管理复杂

解决方案:

  1. 考虑将部分状态迁移到客户端
  2. 服务端只做生成,不维护会话状态
  3. 每次请求携带完整上下文

12.4 Action 引擎统一执行

问题: ZCLAW Hands 系统执行逻辑分散

解决方案:

  1. 创建统一的 ActionEngine 类
  2. 区分 Fire-and-forget 和 Synchronous 模式
  3. 自动处理前置条件 (如白板自动打开)

12.5 设置版本迁移

问题: ZCLAW 配置更新时需要清理缓存

解决方案:

  1. 实现 Zustand persist 的 migrate 函数
  2. 支持 merge 函数合并新默认值
  3. 保持用户配置不丢失