708 lines
28 KiB
Markdown
708 lines
28 KiB
Markdown
# 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
|
||
```
|