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
重构所有代码和文档中的项目名称,将OpenFang统一更新为ZCLAW。包括: - 配置文件中的项目名称 - 代码注释和文档引用 - 环境变量和路径 - 类型定义和接口名称 - 测试用例和模拟数据 同时优化部分代码结构,移除未使用的模块,并更新相关依赖项。
1011 lines
30 KiB
Markdown
1011 lines
30 KiB
Markdown
# 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`):
|
||
```typescript
|
||
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 |
|
||
|
||
**角色-动作映射**:
|
||
```typescript
|
||
const ROLE_ACTIONS = {
|
||
teacher: [...SLIDE_ACTIONS, ...WHITEBOARD_ACTIONS], // 全部能力
|
||
assistant: [...WHITEBOARD_ACTIONS], // 仅白板
|
||
student: [...WHITEBOARD_ACTIONS], // 仅白板
|
||
};
|
||
```
|
||
|
||
### 3.2 工具/能力系统
|
||
|
||
**动作执行架构**:
|
||
```typescript
|
||
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 生成):
|
||
```json
|
||
[
|
||
{"type": "action", "name": "spotlight", "params": {"elementId": "img_1"}},
|
||
{"type": "text", "content": "Hello students..."},
|
||
{"type": "action", "name": "wb_draw_text", "params": {...}}
|
||
]
|
||
```
|
||
|
||
### 3.3 记忆/上下文管理
|
||
|
||
**无状态架构设计**:
|
||
- 后端完全无状态,所有状态由客户端维护
|
||
- 每次请求携带完整上下文 (`StatelessChatRequest`)
|
||
|
||
**DirectorState (跨轮次传递)**:
|
||
```typescript
|
||
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 注册表**: 支持动态添加智能体
|
||
|
||
**扩展点**:
|
||
```typescript
|
||
// 添加新 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 参考:
|
||
|
||
```typescript
|
||
// StatelessChatRequest - 所有状态由客户端传递
|
||
interface StatelessChatRequest {
|
||
messages: UIMessage[]; // 对话历史
|
||
storeState: { ... }; // 应用状态
|
||
config: { agentIds, ... }; // 智能体配置
|
||
directorState?: DirectorState; // 跨轮次状态
|
||
}
|
||
```
|
||
|
||
ZCLAW 可采用类似模式,避免服务端状态管理复杂性。
|
||
|
||
#### B. LangGraph 多智能体编排
|
||
|
||
```typescript
|
||
// 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 执行引擎
|
||
|
||
```typescript
|
||
// 统一的动作执行入口
|
||
class ActionEngine {
|
||
async execute(action: Action): Promise<void> {
|
||
// Fire-and-forget vs Synchronous
|
||
}
|
||
}
|
||
```
|
||
|
||
ZCLAW 的 Hands 系统可采用类似架构。
|
||
|
||
### 5.3 潜在的整合方式
|
||
|
||
#### 方式 1: 作为 ZCLAW 的 Skill
|
||
|
||
OpenMAIC 可作为 ZCLAW 的一个 Skill 集成:
|
||
|
||
```markdown
|
||
# 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 抽象接口
|
||
|
||
```typescript
|
||
// 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 类型定义
|
||
|
||
```typescript
|
||
// lib/types/action.ts
|
||
export type Action =
|
||
| SpotlightAction
|
||
| LaserAction
|
||
| SpeechAction
|
||
| WhiteboardAction
|
||
| VideoAction
|
||
| DiscussionAction;
|
||
|
||
export interface ActionBase {
|
||
type: string;
|
||
id?: string;
|
||
}
|
||
```
|
||
|
||
### 7.3 Agent 配置结构
|
||
|
||
```typescript
|
||
// 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 协议实现(消息路由、能力发现)
|
||
- [ ] 创建教育类 Hands(whiteboard、slideshow、speech、quiz)
|
||
- [ ] 开发 classroom-generator Skill
|
||
- [ ] 增强工作流编排能力(DAG、条件分支)
|
||
|
||
---
|
||
|
||
## 11. 深度架构分析 (2026-03-26 补充)
|
||
|
||
### 11.1 Director Graph 核心实现
|
||
|
||
**文件**: `lib/orchestration/director-graph.ts`
|
||
|
||
#### 11.1.1 状态定义
|
||
|
||
```typescript
|
||
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 节点核心逻辑
|
||
|
||
```typescript
|
||
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 缓冲项类型
|
||
|
||
```typescript
|
||
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 回调接口
|
||
|
||
```typescript
|
||
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 循环核心逻辑
|
||
|
||
```typescript
|
||
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 配置选项
|
||
|
||
```typescript
|
||
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 核心执行流程
|
||
|
||
```typescript
|
||
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 白板动作实现
|
||
|
||
```typescript
|
||
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 状态结构
|
||
|
||
```typescript
|
||
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 持久化与迁移
|
||
|
||
```typescript
|
||
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 服务器配置合并
|
||
|
||
```typescript
|
||
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`
|
||
|
||
```typescript
|
||
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 事件类型完整定义
|
||
|
||
```typescript
|
||
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. 保持用户配置不丢失
|