Files
TrueGrowth/docs/SW_ARCHITECTURE.md

708 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```