fix(presentation): 修复 presentation 模块类型错误和语法问题
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

- 创建 types.ts 定义完整的类型系统
- 重写 DocumentRenderer.tsx 修复语法错误
- 重写 QuizRenderer.tsx 修复语法错误
- 重写 PresentationContainer.tsx 添加类型守卫
- 重写 TypeSwitcher.tsx 修复类型引用
- 更新 index.ts 移除不存在的 ChartRenderer 导出

审计结果:
- 类型检查: 通过
- 单元测试: 222 passed
- 构建: 成功
This commit is contained in:
iven
2026-03-26 17:19:28 +08:00
parent d0c6319fc1
commit b7f3d94950
71 changed files with 15896 additions and 1133 deletions

View File

@@ -1,7 +1,8 @@
# OpenMAIC 深度分析报告
> **来源**: https://github.com/THU-MAIC/OpenMAIC
> **分析日期**: 2026-03-22
> **本地路径**: G:\edu\OpenMAIC
> **分析日期**: 2026-03-22 (初版) / 2026-03-26 (深度分析)
> **许可证**: AGPL-3.0
## 1. 项目概述
@@ -556,3 +557,454 @@ skills/classroom-generator/SKILL.md # 课堂生成
- [ ] 创建教育类 Handswhiteboard、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. 保持用户配置不丢失