Initial TrueGrowth source import

This commit is contained in:
2026-07-07 09:36:36 +08:00
commit 3b6781d695
2283 changed files with 691996 additions and 0 deletions

707
docs/SW_ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,707 @@
# Service Worker 通信与任务执行架构
本文档介绍 AI 输入框、Service Worker、工作流、IndexedDB、sw-debug.html 调试面板之间的时序关系、数据流向、通信方式和数据同步策略。
## 目录
1. [架构概览](#架构概览)
2. [核心组件](#核心组件)
3. [通信机制](#通信机制)
4. [数据流向](#数据流向)
5. [数据存储](#数据存储)
6. [时序图](#时序图)
7. [数据同步策略](#数据同步策略)
8. [优化方案](#优化方案)
---
## 架构概览
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 主线程 (Main Thread) │
├─────────────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ AIInputBar │ │ ChatDrawer │ │ WorkZone │ │
│ │ (用户输入) │ │ (对话展示) │ │ (步骤展示) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └───────────────────┼───────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ WorkflowContext (状态管理) │ │
│ └──────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────┼──────────────────────────────────┐ │
│ │ useWorkflowSubmission / swChannelClient │ │
│ │ (工作流提交 / SW 通信客户端) │ │
│ └──────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────┴──────────────────────────────────┐ │
│ │ sw-debug.html (调试面板 - 独立页面) │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
│ │ │ duplex-client.js → LLM API 日志 / PostMessage 日志 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────┬──────────────────────────────────┘ │
└─────────────────────────────────┼───────────────────────────────────────┘
postmessage-duplex (双工通信)
┌─────────────────────────────────┼───────────────────────────────────────┐
│ Service Worker │
├─────────────────────────────────┼───────────────────────────────────────┤
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ SWChannelManager (通道管理器) │ │
│ │ - 管理多客户端连接 │ │
│ │ - RPC 方法路由 │ │
│ │ - 事件广播 │ │
│ └────────────────────────────┬────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────┼─────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ SWTaskQueue │ │WorkflowExecutor│ │ LLM API Logger │ │
│ │ (任务队列) │ │ (工作流执行) │ │ (API 日志) │ │
│ └──────┬──────┘ └──────┬───────┘ └────────┬─────────┘ │
│ │ │ │ │
│ └───────────────────┼──────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ taskQueueStorage (IndexedDB) │ │
│ │ - tasks: 任务数据 │ │
│ │ - workflows: 工作流数据 │ │
│ │ - config: API 配置 │ │
│ │ - llm-api-logs: LLM API 日志 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## 核心组件
### 1. 主线程组件
| 组件 | 路径 | 职责 |
|------|------|------|
| **AIInputBar** | `packages/drawnix/src/components/ai-input-bar/AIInputBar.tsx` | 用户输入、参数配置、触发生成 |
| **ChatDrawer** | `packages/drawnix/src/components/chat-drawer/ChatDrawer.tsx` | 对话展示、步骤状态 |
| **WorkZone** | `packages/drawnix/src/plugins/with-workzone.ts` | 画布上的工作流步骤展示 |
| **WorkflowContext** | `packages/drawnix/src/contexts/WorkflowContext.tsx` | 工作流状态管理 |
| **swChannelClient** | `packages/drawnix/src/services/sw-channel/client.ts` | SW 通信客户端 |
| **sw-debug** | `apps/web/public/sw-debug/` | 调试面板(独立页面) |
### 2. Service Worker 组件
| 组件 | 路径 | 职责 |
|------|------|------|
| **SWChannelManager** | `apps/web/src/sw/task-queue/channel-manager.ts` | 多客户端通信管理 |
| **SWTaskQueue** | `apps/web/src/sw/task-queue/queue.ts` | 任务生命周期管理 |
| **WorkflowExecutor** | `apps/web/src/sw/task-queue/workflow-executor.ts` | 工作流执行引擎 |
| **taskQueueStorage** | `apps/web/src/sw/task-queue/storage.ts` | IndexedDB 存储层 |
| **LLM API Logger** | `apps/web/src/sw/task-queue/llm-api-logger.ts` | API 调用日志 |
---
## 通信机制
### 基于 postmessage-duplex 的双工通信
```typescript
// 主线程 → SW (RPC 调用)
const response = await channel.call('workflow:submit', { workflow });
// SW → 主线程 (广播)
channelManager.broadcastToAll('task:completed', { taskId, result });
// SW → 特定客户端 (点对点)
channelManager.sendToTaskClient(taskId, 'task:progress', { progress: 50 });
```
### 通信模式对比
| 模式 | 方向 | 方法 | 用途 |
|------|------|------|------|
| **RPC** | 主线程 → SW | `channel.call()` | 提交任务/工作流、查询状态 |
| **广播** | SW → 所有客户端 | `channel.broadcast()` | 任务创建、配置变更 |
| **点对点** | SW → 特定客户端 | `sendToMappedClient()` | 任务进度、完成、失败 |
| **请求-响应** | SW ↔ 主线程 | `channel.publish()` + handler | 主线程工具请求 |
### 关键 RPC 方法
```typescript
// 任务相关
'task:create' // 创建任务
'task:cancel' // 取消任务
'task:listPaginated' // 分页查询任务
// 工作流相关
'workflow:submit' // 提交工作流
'workflow:cancel' // 取消工作流
'workflow:getStatus' // 查询工作流状态
// 调试相关
'debug:getLLMApiLogs' // 获取 LLM API 日志
'debug:getCacheEntries' // 获取缓存条目
```
### 关键事件
```typescript
// 任务事件
'task:created' // 任务创建(广播)
'task:progress' // 任务进度(点对点)
'task:completed' // 任务完成(点对点)
'task:failed' // 任务失败(点对点)
// 工作流事件
'workflow:status' // 工作流状态变更
'workflow:stepStatus' // 步骤状态变更
'workflow:completed' // 工作流完成
'workflow:stepsAdded' // 动态添加步骤
// 调试事件
'debug:llmLog' // LLM API 日志实时推送
'postmessage:log' // PostMessage 日志
```
---
## 数据流向
### 1. 图片/视频生成流程
```
用户输入 → AIInputBar
workflow-converter.ts (转换为工作流定义)
useWorkflowSubmission.submitToSW()
│ RPC: workflow:submit
SW: WorkflowExecutor.submitWorkflow()
├─→ IndexedDB: 保存工作流
executeStep() 循环
├─ generate_image/generate_video → SW 直接执行
│ │
│ ▼
│ llmFetch() → Gemini/Veo3 API
│ │
│ ├─→ LLM API Logger (记录日志)
│ │
│ ▼
│ ImageHandler/VideoHandler (处理结果)
│ │
│ ├─→ Cache Storage (缓存媒体)
│ │
│ ▼
│ broadcast: task:completed
└─ ai_analyze/canvas_insert → 委托主线程
sendToolRequest() → 主线程处理
返回结果 → 继续执行
```
### 2. Agent 流程(动态步骤)
```
用户输入 "画一只猫" + 选择 Agent 模式
convertAgentFlowToWorkflow() → 仅包含 ai_analyze 步骤
SW: 执行 ai_analyze
主线程: AI 分析 → 返回 addSteps
SW: 添加新步骤 → broadcast: workflow:stepsAdded
主线程: WorkflowContext 更新 → ChatDrawer/WorkZone 渲染
SW: 继续执行新步骤 (generate_image 等)
```
### 3. 调试面板数据流
```
sw-debug.html 页面加载
duplex-client.js 初始化
├─→ RPC: debug:getLLMApiLogs (获取历史日志)
└─→ onBroadcast: debug:llmLog (订阅实时日志)
llmapi-logs.js 渲染日志列表
点击展开 → RPC: debug:getLLMApiLogById (懒加载详情)
```
---
## 数据存储
### IndexedDB 数据库结构
```
sw-task-queue (主数据库)
├── tasks # 任务数据
│ ├── id (主键)
│ ├── type, status, createdAt (索引)
│ └── params, result, error, remoteId, ...
├── workflows # 工作流数据
│ ├── id (主键)
│ ├── status, createdAt (索引)
│ └── steps, context, ...
├── config # 配置数据
│ ├── gemini_config
│ ├── video_config
│ └── mcp_system_prompt
├── chat-workflows # 聊天工作流
├── pending-tool-requests # 待处理工具请求
└── pending-dom-operations # 待处理 DOM 操作
llm-api-logs (日志数据库)
└── logs
├── id (主键)
├── timestamp, taskType, status, taskId (索引)
└── endpoint, model, prompt, requestBody, responseBody, ...
```
### 存储策略
| 数据类型 | 存储位置 | 容量限制 | 清理策略 |
|----------|----------|----------|----------|
| 任务数据 | IndexedDB tasks | 无限制 | 用户手动删除 |
| 工作流数据 | IndexedDB workflows | 无限制 | 用户手动删除 |
| LLM API 日志 | IndexedDB llm-api-logs | 1000 条 | FIFO 自动清理 |
| 媒体文件 | Cache Storage | 受浏览器配额限制 | LRU 清理 |
| 内存缓存 | SW 内存 | 50 条日志 | FIFO |
### 批量写入优化
```typescript
// storage.ts: 50ms 窗口期批量写入
private batchSaveTimer: ReturnType<typeof setTimeout> | null = null;
private pendingSaves: Map<string, SWTask> = new Map();
saveTask(task: SWTask): void {
this.pendingSaves.set(task.id, task);
if (!this.batchSaveTimer) {
this.batchSaveTimer = setTimeout(() => this.flushPendingSaves(), 50);
}
}
```
---
## 时序图
### 完整生成流程
```
主线程 Service Worker 外部 API
│ │ │
│ 1. 用户点击生成 │ │
│─────────────────────────────────────│ │
│ │ │
│ 2. RPC: workflow:submit │ │
│───────────────────────────────────>│ │
│ │ 3. 保存到 IndexedDB │
│ │─────────────────────> │
│ │ │
│ 4. broadcast: workflow:status │ │
│<───────────────────────────────────│ │
│ │ │
│ │ 5. 执行步骤 │
│ │ │
│ │ 6. 调用 LLM API │
│ │──────────────────────────────>│
│ │ │
│ │ 7. 记录 API 日志 │
│ │─────────────────────> │
│ │ │
│ 8. broadcast: debug:llmLog │ │
│<───────────────────────────────────│ │
│ │ │
│ │ 9. 收到 API 响应 │
│ │<──────────────────────────────│
│ │ │
│ │ 10. 缓存媒体到 Cache Storage │
│ │─────────────────────> │
│ │ │
│ 11. broadcast: task:completed │ │
│<───────────────────────────────────│ │
│ │ │
│ 12. 更新 UI │ │
│─────────────────────> │ │
```
### 页面刷新恢复流程
```
页面刷新后
│ 1. swChannelClient.initialize()
│───────────────────────────────────>│
│ │
│ │ 2. 从 IndexedDB 加载任务/工作流
│ │─────────────────────>
│ │
│ │ 3. 恢复中断的任务
│ │ - 有 remoteId: 恢复轮询
│ │ - 无 remoteId: 查 LLM 日志恢复
│ │
│ 4. broadcast: workflow:recovered │
│<───────────────────────────────────│
│ │
│ 5. 恢复 WorkflowContext 状态 │
│─────────────────────> │
```
---
## 数据同步策略
### 1. 任务状态同步
```typescript
// 主线程: useTaskQueue.ts
useEffect(() => {
// 订阅任务更新事件
const subscription = taskQueueService.observeTaskUpdates().subscribe(() => {
setTasks(taskQueueService.getAllTasks());
});
// 从 SW 同步任务数据
const syncFromSW = async () => {
await swTaskQueueService.syncTasksFromSW();
const swTasks = swTaskQueueService.getAllTasks();
taskQueueService.restoreTasks(swTasks);
setTasks(taskQueueService.getAllTasks()); // 强制刷新
};
syncFromSW();
}, []);
```
### 2. 工作流状态同步
```typescript
// 主线程: useWorkflowSubmission.ts
workflowSubmissionService.subscribeToWorkflow(workflowId, (event) => {
switch (event.type) {
case 'step':
workflowControl.updateStep(event.stepId, event.status, ...);
break;
case 'completed':
workflowControl.complete();
break;
case 'steps_added':
workflowControl.addSteps(event.steps);
break;
}
});
```
### 3. 多客户端同步
```typescript
// SW: channel-manager.ts
// 任务创建 → 广播给所有客户端
sendTaskCreated(taskId, task) {
this.broadcastToAll(SW_EVENTS.TASK_CREATED, { taskId, task });
}
// 任务进度 → 仅发送给创建者
sendTaskProgress(taskId, progress) {
this.sendToMappedClient(this.taskChannels, taskId, 'task:progress', { taskId, progress }, true);
}
```
### 4. 数据一致性保障
| 场景 | 策略 |
|------|------|
| SW 重启 | 从 IndexedDB 恢复所有状态 |
| 页面刷新 | 重新订阅事件 + 同步当前状态 |
| 多页面同时打开 | 通过广播保持同步 |
| 网络断开 | 任务保持 PROCESSING 状态,重连后恢复 |
---
## 优化方案
### 当前问题分析
1. **数据同步延迟**
- 问题:任务队列首次打开时数据为空
- 原因SW 数据同步是异步的UI 先渲染空状态
- 已修复:添加 `isLoading` 状态 + 强制刷新
2. **init RPC 超时导致状态丢失**
- 问题:`[SWChannelClient] init failed: timeout`,任务完成后提示"任务未找到"
- 原因链:
1. SW 端 `handleInit` 等待 `storageRestorePromise`IndexedDB 恢复)
2. IndexedDB 操作慢导致 init RPC 超时
3. `swTaskQueueService.initialized = false`
4. `syncTasksFromSW()` 检查失败后直接返回
5. 任务完成广播丢失(`taskChannels` 映射已失效)
- 已修复:
- `syncTasksFromSW` 不再依赖 `init` 成功,尝试重新初始化 channel
- `init` 方法超时延长至 60 秒,且超时返回失败结果而非抛异常
- 添加 5 秒轮询机制检查运行中任务状态
3. **taskChannels 映射丢失**
- 问题:页面刷新后,任务完成的点对点通知无法送达
- 原因:`taskChannels` 映射只在任务创建时建立,刷新后丢失
- 已修复:`fallbackBroadcast: true` 确保广播给所有客户端
- 兜底方案:客户端轮询运行中任务状态
4. **通信层复杂度**
- 问题:`message-bus.ts``channel-manager.ts` 功能重叠
- 影响:代码维护成本高
5. **大文件问题**
- `channel-manager.ts`: 2081 行
- `client.ts`: 1071 行
- 维护难度高
6. **LLM 日志数据量大**
- 单条日志可能包含大型 requestBody/responseBody
- 分页查询时传输数据过大
### 优化方案
#### 方案 1: 通信层统一(已部分完成)
```typescript
// 统一使用 channel-manager.ts移除 message-bus.ts
// 优点:减少代码重复
// 风险:需要更新多处引用
```
#### 方案 2: 预加载 + 骨架屏
```typescript
// 在 App 初始化时预加载任务数据
useEffect(() => {
// 应用启动时预热 SW 连接 + 加载任务
swTaskQueueService.initialize().then(() => {
swTaskQueueService.syncTasksFromSW();
});
}, []);
// 任务面板使用骨架屏而非空状态
{isLoading ? <TaskSkeleton count={5} /> : <TaskList tasks={tasks} />}
```
#### 方案 3: 增量同步
```typescript
// 当前:每次同步全量任务
await swTaskQueueService.syncTasksFromSW();
// 优化:仅同步变更的任务
interface TaskSyncResult {
updatedTasks: Task[];
deletedTaskIds: string[];
lastSyncTime: number;
}
// SW 端记录变更
const changes = await storage.getTaskChangesSince(lastSyncTime);
```
#### 方案 4: 日志数据分层
```typescript
// 当前:精简数据 vs 完整数据
// 优化:三层数据结构
interface LLMApiLogSummary { // 列表展示
id: string;
timestamp: number;
status: string;
duration: number;
}
interface LLMApiLogDetail { // 展开详情
...LLMApiLogSummary;
endpoint: string;
model: string;
prompt: string; // 截断到 500 字符
}
interface LLMApiLogFull { // 完整导出
...LLMApiLogDetail;
requestBody: string;
responseBody: string;
}
```
#### 方案 5: 工作流执行优化
```typescript
// 当前:串行执行步骤
// 优化:支持并行执行无依赖步骤
interface WorkflowStep {
id: string;
dependsOn?: string[]; // 依赖的步骤 ID
}
// 执行引擎:同时执行所有依赖已满足的步骤
const executableSteps = steps.filter(s =>
s.status === 'pending' &&
(s.dependsOn || []).every(depId =>
steps.find(d => d.id === depId)?.status === 'completed'
)
);
await Promise.all(executableSteps.map(step => executeStep(step)));
```
#### 方案 6: 通道管理器模块化
```
channel-manager/
├── index.ts # 核心类 (~400 行)
├── constants.ts # 常量定义 (~130 行) ✅ 已完成
├── task-handlers.ts # 任务 RPC 处理器 (~200 行)
├── workflow-handlers.ts # 工作流 RPC 处理器 (~200 行)
├── debug-handlers.ts # 调试 RPC 处理器 (~400 行)
├── event-senders.ts # 事件发送方法 (~300 行)
└── types.ts # 类型定义
```
### 优先级建议
| 优化 | 优先级 | 收益 | 风险 |
|------|--------|------|------|
| 预加载 + 骨架屏 | 高 | 用户体验 | 低 |
| 增量同步 | 中 | 性能 | 中 |
| 通道管理器模块化 | 中 | 可维护性 | 中 |
| 日志数据分层 | 低 | 调试体验 | 低 |
| 并行执行步骤 | 低 | 生成速度 | 高 |
---
## 故障排查
### 常见问题
#### 1. `[SWChannelClient] init failed: timeout`
**症状**:任务完成后提示"任务未找到"sw-debug.html 能看到数据
**原因**
- SW 端 `handleInit` 等待 IndexedDB 恢复超时
- 客户端 `init` RPC 超时后,`swTaskQueueService.initialized` 保持 false
- `syncTasksFromSW()` 因此跳过同步
**排查步骤**
1. 检查 IndexedDB 大小DevTools → Application → IndexedDB
2. 查看 SW Console 日志:`[SWTaskQueue] restoreFromStorage...`
3. 检查 `swChannelClient.isInitialized()` 返回值
**解决方案**
- 已修复:`init` 超时延长至 60 秒
- 已修复:`syncTasksFromSW` 尝试重新初始化 channel
- 兜底5 秒轮询运行中任务状态
#### 2. 任务完成广播丢失
**症状**SW 日志显示任务完成,但应用页面无反应
**原因**
- `taskChannels` 映射丢失(页面刷新后)
- 客户端 channel 未建立或已断开
**排查步骤**
1. 检查 SW Console`[SWChannelManager] sendTaskCompleted`
2. 检查客户端 Console是否有 `task:completed` 事件
3. 使用 sw-debug.html 的 PostMessage 日志面板
**解决方案**
- `fallbackBroadcast: true` 确保广播给所有客户端
- 客户端轮询运行中任务状态
#### 3. 多页面数据不同步
**症状**:两个应用页面,一个有任务数据,另一个没有
**原因**
- 初始化时序问题
- 点对点通信发送到错误的客户端
**排查步骤**
1. 检查两个页面的 `swChannelClient.isInitialized()`
2. 查看 SW Console 的 clients 数量
**解决方案**
- 使用 `broadcastToAll` 广播重要事件
- 页面可见时触发同步(`visibilitychange` 事件)
### 调试工具
1. **sw-debug.html**:查看 LLM API 日志、PostMessage 日志
2. **DevTools → Application → Service Workers**:查看 SW 状态
3. **DevTools → Application → IndexedDB**:查看存储数据
4. **Console 过滤**`[SWChannelManager]``[SWTaskQueue]``[SWChannelClient]`
---
## 附录
### 相关文档
- [CODING_RULES.md](./CODING_RULES.md) - postmessage-duplex 使用规范
- [UNIFIED_CACHE_DESIGN.md](./UNIFIED_CACHE_DESIGN.md) - 缓存设计
- [SW_DEBUG_POSTMESSAGE_LOGGING.md](./SW_DEBUG_POSTMESSAGE_LOGGING.md) - 调试日志
### 关键代码路径
```
# 主线程
packages/drawnix/src/components/ai-input-bar/AIInputBar.tsx
packages/drawnix/src/hooks/useWorkflowSubmission.ts
packages/drawnix/src/services/sw-channel/client.ts
packages/drawnix/src/hooks/useTaskQueue.ts
# Service Worker
apps/web/src/sw/task-queue/channel-manager.ts
apps/web/src/sw/task-queue/queue.ts
apps/web/src/sw/task-queue/storage.ts
apps/web/src/sw/task-queue/workflow-executor.ts
apps/web/src/sw/task-queue/llm-api-logger.ts
# 调试面板
apps/web/public/sw-debug/duplex-client.js
apps/web/public/sw-debug/llmapi-logs.js
apps/web/public/sw-debug/sw-communication.js
```