Merge branch 'domain' into develop

This commit is contained in:
liquidliang committed 2026-02-05 17:43:45 +08:00
commit f14829569f
7 files changed
+1807 -306

No files matched your search

+670
View File
@@ -0,0 +1,670 @@
# 简化重构方案:真正降低复杂度
## 执行摘要
**目标**: 解决类型重复、服务分散等实际问题,同时减少代码量
**预期结果**: 删除 3-5 个文件,减少 ~500 行代码
**时间估计**: 1-2 天
**风险**: 低 - 每步独立可测试,可随时回滚
## 当前问题分析
### 问题 1: 类型定义重复(高优先级)
**现状**:
- `packages/drawnix/src/domain/task/` - 新 DDD 层类型(7 个文件)
- `packages/drawnix/src/types/task.types.ts` - 旧类型(已改为 re-export)
- `apps/web/src/sw/task-queue/types.ts` - SW 端重复定义
- `packages/drawnix/src/services/sw-channel/types/task.ts` - 通信层类型
**问题**:
- 4 处定义,依赖注释 "Keep in sync" 维护一致性
- DDD 重构后变成 3 种表示:interface、class、snapshot
- 转换代码散落各处
### 问题 2: Workflow 服务职责分散(中优先级)
**现状**:
- `workflowSubmissionService` - 提交工作流
- `workflowPollingService` - 轮询状态
- `workflowCompletionService` - 完成后处理
- 3 个服务管理同一个工作流生命周期
**问题**:
- 状态分散在 3 个服务中
- 服务间通过方法调用耦合
- 难以追踪完整生命周期
### 问题 3: 事件类型不统一(低优先级)
**现状**:
- 各服务使用独立 RxJS Subject
- 事件结构不一致
- 缺少类型约束
**问题**:
- 订阅者需要知道具体 Subject 位置
- 事件数据结构不统一
- 难以调试事件流
### 问题 4: DDD 重构增加的复杂度(需回滚)
**现状**:
- 新增 30 个文件(domain/application/infrastructure)
- Task 有 3 种表示(interface/class/snapshot)
- Repository 包装现有 storage
- Strategy 包装现有条件判断
**问题**:
- 代码量增加 3000+ 行
- 旧代码保留"向后兼容",实际是双重维护
- 抽象层没有解决实际问题
## 解决方案
### 方案 1: 统一类型定义(解决问题 1)
#### 目标
- 所有类型定义在一个位置
- SW 和主线程都从同一源导入
- 删除重复定义
#### 实施步骤
**Step 1.1: 创建共享类型文件**
创建 `packages/drawnix/src/types/shared/core.types.ts`:
```typescript
/**
* 核心类型定义 - 主线程和 SW 共享
*
* 这是任务和工作流类型的唯一真相来源。
* 主线程和 SW 都从这里导入,确保类型一致性。
*/
// ============================================================================
// Task 核心类型
// ============================================================================
export enum TaskStatus {
PENDING = 'pending',
PROCESSING = 'processing',
COMPLETED = 'completed',
FAILED = 'failed',
CANCELLED = 'cancelled',
}
export enum TaskType {
IMAGE = 'image',
VIDEO = 'video',
CHARACTER = 'character',
INSPIRATION_BOARD = 'inspiration_board',
CHAT = 'chat',
}
export enum TaskExecutionPhase {
SUBMITTING = 'submitting',
POLLING = 'polling',
DOWNLOADING = 'downloading',
}
export interface GenerationParams {
prompt: string;
width?: number;
height?: number;
size?: string;
duration?: number;
style?: string;
model?: string;
seed?: number;
sourceVideoTaskId?: string;
characterTimestamps?: string;
sourceLocalTaskId?: string;
gridImageRows?: number;
gridImageCols?: number;
gridImageLayoutStyle?: 'scattered' | 'grid' | 'circular';
inspirationBoardLayoutStyle?: 'inspiration-board';
isInspirationBoard?: boolean;
inspirationBoardImageCount?: number;
autoInsertToCanvas?: boolean;
aspectRatio?: string;
[key: string]: unknown;
}
export interface TaskResult {
url: string;
width?: number;
height?: number;
duration?: number;
thumbnailUrl?: string;
metadata?: Record<string, unknown>;
}
export interface TaskError {
code: string;
message: string;
details?: unknown;
}
export interface Task {
id: string;
type: TaskType;
status: TaskStatus;
params: GenerationParams;
createdAt: number;
updatedAt: number;
startedAt?: number;
completedAt?: number;
result?: TaskResult;
error?: TaskError;
userId?: string;
progress?: number;
remoteId?: string;
executionPhase?: TaskExecutionPhase;
savedToLibrary?: boolean;
insertedToCanvas?: boolean;
}
// ============================================================================
// 工具函数
// ============================================================================
export const TERMINAL_STATUSES: TaskStatus[] = [
TaskStatus.COMPLETED,
TaskStatus.FAILED,
TaskStatus.CANCELLED,
];
export const RETRYABLE_STATUSES: TaskStatus[] = [
TaskStatus.FAILED,
TaskStatus.CANCELLED,
];
export function isTerminalStatus(status: TaskStatus): boolean {
return TERMINAL_STATUSES.includes(status);
}
export function isRetryableStatus(status: TaskStatus): boolean {
return RETRYABLE_STATUSES.includes(status);
}
```
**Step 1.2: 更新 SW 端导入**
修改 `apps/web/src/sw/task-queue/types.ts`:
```typescript
/**
* Service Worker Task Queue Type Definitions
*
* 从共享类型导入,确保与主线程一致。
*/
// 从共享类型导入核心定义
export {
TaskStatus,
TaskType,
TaskExecutionPhase,
GenerationParams,
TaskResult,
TaskError,
Task,
TERMINAL_STATUSES,
RETRYABLE_STATUSES,
isTerminalStatus,
isRetryableStatus,
} from '../../../../packages/drawnix/src/types/shared/core.types';
// SW 特有的类型定义保留在这里
export interface SWTaskQueueConfig {
// ...
}
```
**Step 1.3: 删除 domain 层**
删除整个 `packages/drawnix/src/domain/` 目录(18 个文件):
- `domain/task/` (7 文件)
- `domain/workflow/` (5 文件)
- `domain/events/` (5 文件)
- `domain/index.ts` (1 文件)
**Step 1.4: 删除 application 层**
删除 `packages/drawnix/src/application/` 目录(3 个文件):
- `application/task-application-service.ts`
- `application/workflow-application-service.ts`
- `application/index.ts`
**Step 1.5: 删除 infrastructure 层**
删除 `packages/drawnix/src/infrastructure/` 目录(9 个文件):
- `infrastructure/execution/` (5 文件)
- `infrastructure/persistence/` (3 文件)
- `infrastructure/index.ts` (1 文件)
**Step 1.6: 恢复原有服务**
将 `packages/drawnix/src/types/task.types.ts` 改回直接定义或 re-export 共享类型。
#### 验证
```bash
# 类型检查
cd packages/drawnix && npx tsc --noEmit
# 确保 SW 构建成功
pnpm run build
# 运行测试
pnpm nx test drawnix
```
#### 预期结果
- **删除**: 30 个文件(domain/application/infrastructure)
- **添加**: 1 个文件(shared/core.types.ts)
- **净变化**: -29 文件
- **代码行数**: 减少 ~2500 行
---
---
### 方案 2: 评估 Workflow 服务结构(解决问题 2)
#### 当前服务分析
经过代码审查,发现 3 个 Workflow 服务实际上有不同的职责:
1. **workflow-submission-service.ts** (主服务)
- 提交工作流到 SW
- 监听 SW 事件(通过 swChannelClient)
- 处理主线程工具请求
- 处理画布操作请求
- 管理降级模式(MainThreadWorkflowEngine)
2. **workflow-polling-service.ts** (辅助服务)
- 轮询 IndexedDB 中的工作流
- 执行标记为 `pending_main_thread` 的步骤
- 依赖 workflow-submission-service
3. **workflow-completion-service.ts** (追踪服务)
- 追踪后处理状态(拆分、插入画布)
- 发布完成事件
- 独立的 RxJS Subject
#### 问题重新评估
**原计划**: 合并 3 个服务为 1 个
**重新评估**: 这 3 个服务职责不同,不应强制合并
**真正的问题**:
- polling-service 依赖 submission-service,耦合度高
- completion-service 独立追踪状态,但与其他服务缺少协调
- 事件流分散(submission 的 RxJS Subject + completion 的 Subject)
#### 优化方案
**不合并服务**,而是优化协调机制:
1. **明确服务边界**
- submission-service: 工作流生命周期管理(提交、监听、恢复)
- polling-service: 主线程步骤执行器(可选,仅在需要时启用)
- completion-service: 后处理追踪器(可选,仅追踪需要后处理的工作流)
2. **统一事件流**(见方案 3)
- 所有服务发布到统一的事件总线
- 避免直接服务间调用
3. **解除循环依赖**
- polling-service 不应导入 submission-service
- 通过事件或共享存储通信
#### 实施步骤
**Step 2.1: 解除 polling-service 对 submission-service 的依赖**
当前代码:
```typescript
// workflow-polling-service.ts
import { workflowSubmissionService, type WorkflowDefinition } from './workflow-submission-service';
```
修改为通过事件通信:
```typescript
// workflow-polling-service.ts
import { workflowEvents$ } from './workflow-events'; // 统一事件流
// 监听工作流提交事件,而不是直接调用 submission-service
workflowEvents$.pipe(
filter(e => e.type === 'WORKFLOW_SUBMITTED')
).subscribe(event => {
// 开始轮询该工作流
});
```
**Step 2.2: 标准化服务接口**
为每个服务定义清晰的公共接口:
```typescript
// workflow-submission-service.ts
export interface WorkflowSubmissionService {
// 提交工作流
submitWorkflow(definition: WorkflowDefinition): Promise<string>;
// 恢复工作流
recoverWorkflows(boardId: string): Promise<void>;
// 取消工作流
cancelWorkflow(workflowId: string): Promise<void>;
// 事件流(只读)
readonly events$: Observable<WorkflowEvent>;
}
```
#### 预期结果
- **不删除文件** - 3 个服务保持独立
- **解除耦合** - 通过事件通信,不直接导入
- **明确边界** - 每个服务职责清晰
- **代码行数**: 减少 ~100 行(删除重复的类型定义和耦合代码)
---
### 方案 3: 统一事件类型(解决问题 3)
#### 目标
- 定义标准的事件类型
- 使用单一事件流(而非多个 Subject)
- 类型安全的事件订阅
#### 实施步骤
**Step 3.1: 创建事件类型定义**
创建 `packages/drawnix/src/types/shared/events.types.ts`:
```typescript
/**
* 统一事件类型定义
*
* 所有服务发布的事件都应符合这些类型。
*/
import type { Task, TaskStatus } from './core.types';
// ============================================================================
// 事件基类
// ============================================================================
export interface BaseEvent {
type: string;
timestamp: number;
source: string; // 事件来源(如 'task-queue', 'workflow-submission')
}
// ============================================================================
// Task 事件
// ============================================================================
export interface TaskCreatedEvent extends BaseEvent {
type: 'TASK_CREATED';
task: Task;
}
export interface TaskStatusChangedEvent extends BaseEvent {
type: 'TASK_STATUS_CHANGED';
taskId: string;
oldStatus: TaskStatus;
newStatus: TaskStatus;
task: Task;
}
export interface TaskCompletedEvent extends BaseEvent {
type: 'TASK_COMPLETED';
task: Task;
}
export interface TaskFailedEvent extends BaseEvent {
type: 'TASK_FAILED';
task: Task;
error: string;
}
export type TaskEvent =
| TaskCreatedEvent
| TaskStatusChangedEvent
| TaskCompletedEvent
| TaskFailedEvent;
// ============================================================================
// Workflow 事件
// ============================================================================
export interface WorkflowSubmittedEvent extends BaseEvent {
type: 'WORKFLOW_SUBMITTED';
workflowId: string;
boardId?: string;
}
export interface WorkflowStepCompletedEvent extends BaseEvent {
type: 'WORKFLOW_STEP_COMPLETED';
workflowId: string;
stepId: string;
}
export interface WorkflowCompletedEvent extends BaseEvent {
type: 'WORKFLOW_COMPLETED';
workflowId: string;
}
export interface WorkflowFailedEvent extends BaseEvent {
type: 'WORKFLOW_FAILED';
workflowId: string;
error: string;
}
export type WorkflowEvent =
| WorkflowSubmittedEvent
| WorkflowStepCompletedEvent
| WorkflowCompletedEvent
| WorkflowFailedEvent;
// ============================================================================
// 所有事件联合类型
// ============================================================================
export type AppEvent = TaskEvent | WorkflowEvent;
```
**Step 3.2: 创建统一事件流**
创建 `packages/drawnix/src/services/app-events.ts`:
```typescript
/**
* 统一应用事件流
*
* 所有服务通过这个事件流发布和订阅事件。
*/
import { Subject } from 'rxjs';
import { filter } from 'rxjs/operators';
import type { AppEvent, TaskEvent, WorkflowEvent } from '../types/shared/events.types';
/**
* 全局事件流
*/
const appEvents$ = new Subject<AppEvent>();
/**
* 发布事件
*/
export function publishEvent(event: Omit<AppEvent, 'timestamp'>): void {
appEvents$.next({
...event,
timestamp: Date.now(),
} as AppEvent);
}
/**
* 订阅所有事件
*/
export function subscribeToEvents(handler: (event: AppEvent) => void) {
return appEvents$.subscribe(handler);
}
/**
* 订阅特定类型的事件
*/
export function subscribeToEventType<T extends AppEvent['type']>(
type: T,
handler: (event: Extract<AppEvent, { type: T }>) => void
) {
return appEvents$.pipe(
filter((e): e is Extract<AppEvent, { type: T }> => e.type === type)
).subscribe(handler);
}
/**
* 订阅 Task 事件
*/
export function subscribeToTaskEvents(handler: (event: TaskEvent) => void) {
return appEvents$.pipe(
filter((e): e is TaskEvent =>
e.type.startsWith('TASK_')
)
).subscribe(handler);
}
/**
* 订阅 Workflow 事件
*/
export function subscribeToWorkflowEvents(handler: (event: WorkflowEvent) => void) {
return appEvents$.pipe(
filter((e): e is WorkflowEvent =>
e.type.startsWith('WORKFLOW_')
)
).subscribe(handler);
}
/**
* 导出只读的事件流(用于高级用法)
*/
export const events$ = appEvents$.asObservable();
```
**Step 3.3: 更新服务使用统一事件流**
示例 - 更新 task queue service:
```typescript
// sw-task-queue-service.ts
import { publishEvent } from './app-events';
class SWTaskQueueService {
async createTask(type: TaskType, params: GenerationParams): Promise<Task> {
const task = { /* ... */ };
// 发布事件
publishEvent({
type: 'TASK_CREATED',
source: 'sw-task-queue',
task,
});
return task;
}
}
```
#### 预期结果
- **添加**: 2 个文件(events.types.ts, app-events.ts)
- **修改**: 各服务改用统一事件流
- **删除**: 各服务内部的独立 Subject
- **代码行数**: 净增加 ~200 行(事件定义),但提高了类型安全性
---
## 实施路线图
### 阶段 1: 回滚 DDD 重构(1-2 小时)
**优先级**: 最高 - 立即执行
1. 删除 `domain/` 目录(18 文件)
2. 删除 `application/` 目录(3 文件)
3. 删除 `infrastructure/` 目录(9 文件)
4. 恢复 `types/task.types.ts` 为直接定义
5. 验证构建和测试通过
**验证**:
```bash
pnpm run build
pnpm nx test drawnix
```
### 阶段 2: 统一类型定义(2-3 小时)
**优先级**: 高
1. 创建 `types/shared/core.types.ts`
2. 更新 SW 端导入
3. 更新主线程导入
4. 删除重复定义
5. 验证类型检查通过
**验证**:
```bash
cd packages/drawnix && npx tsc --noEmit
```
### 阶段 3: 标准化事件(2-3 小时)
**优先级**: 中
1. 创建 `types/shared/events.types.ts`
2. 创建 `services/app-events.ts`
3. 更新各服务使用统一事件流
4. 验证事件流工作正常
**验证**:
- 手动测试任务创建和完成
- 检查事件是否正确发布
### 阶段 4: 优化服务协调(可选,3-4 小时)
**优先级**: 低
1. 解除 polling-service 对 submission-service 的依赖
2. 标准化服务接口
3. 添加服务文档
---
## 总结对比
| 指标 | DDD 方案 | 简化方案 |
|------|---------|---------|
| 新增文件 | +30 | +3 |
| 删除文件 | 0 | -30 |
| 净变化 | +30 | -27 |
| 代码行数 | +3000 | -2300 |
| 实施时间 | 2-3 周 | 1-2 天 |
| 风险 | 高(大规模重构) | 低(渐进式优化) |
| 复杂度 | 增加(多层抽象) | 降低(删除冗余) |
## 下一步行动
1. **立即执行**: 回滚 DDD 重构(阶段 1)
2. **本周完成**: 统一类型定义(阶段 2)
3. **下周完成**: 标准化事件(阶段 3)
4. **可选**: 优化服务协调(阶段 4)
每个阶段独立可测试,可随时停止或调整。
@@ -0,0 +1,585 @@
---
name: task-workflow-ddd-architecture
overview: 引入领域驱动开发(DDD)架构来重构任务和工作流系统,通过定义清晰的领域模型、聚合根、领域事件和应用服务来降低复杂度,收敛领域知识,便于后续扩展。
todos:
- id: create-domain-task
content: 创建 Task 领域模型:实现 Task 聚合根、TaskStatus/TaskType 值对象、TaskRepository 接口,统一 3 处重复的类型定义
status: completed
- id: create-domain-workflow
content: 创建 Workflow 领域模型:实现 Workflow 聚合根、WorkflowStep 实体、WorkflowRepository 接口
status: completed
- id: create-domain-events
content: 创建领域事件系统:实现 DomainEvent 基类、Task/Workflow 相关事件类、DomainEventBus 单例
status: completed
dependencies:
- create-domain-task
- create-domain-workflow
- id: create-execution-strategies
content: 创建执行策略模块:实现 TaskExecutionStrategy 接口、SWExecutionStrategy、MainThreadExecutionStrategy、ExecutionStrategySelector
status: completed
dependencies:
- create-domain-task
- id: create-repositories
content: 创建仓储实现:实现 IndexedDBTaskRepository、IndexedDBWorkflowRepository,封装现有 storage reader/writer
status: completed
dependencies:
- create-domain-task
- create-domain-workflow
- id: create-application-services
content: 创建应用服务层:实现 TaskApplicationService、WorkflowApplicationService,整合领域逻辑和基础设施
status: completed
dependencies:
- create-domain-events
- create-execution-strategies
- create-repositories
- id: create-react-integration
content: 创建 React 集成层:实现 useDomainEvent Hook,将领域事件桥接到 React 组件
status: completed
dependencies:
- create-domain-events
---
## 用户需求
对当前项目的任务(Task)和工作流(Workflow)相关逻辑进行架构分析,引入领域驱动设计(DDD)方式来降低复杂度,收敛领域知识,并便于后续扩展。
## 产品概述
当前系统是一个 AI 图像/视频生成工具,核心功能涉及:
- **任务队列管理**:支持图片、视频、角色提取、灵感板等多种生成任务
- **工作流编排**:支持多步骤 MCP 工具调用的工作流执行
- **双模式执行**:Service Worker 模式(主用)和主线程降级模式
- **状态同步**:主线程与 SW 之间的双向通信和状态同步
## 核心问题分析
### 1. 类型定义散落重复(3处)
- `packages/drawnix/src/types/task.types.ts` - 主线程类型
- `apps/web/src/sw/task-queue/types.ts` - SW 端类型
- `packages/drawnix/src/services/sw-channel/types/` - 通信层类型
- 依赖注释 "Keep in sync with..." 维护一致性,极易出错
### 2. 双模式维护成本高
- SW 模式:`swTaskQueueService` + `SWTaskQueue` + `WorkflowExecutor`
- 降级模式:`taskQueueService` + `WorkflowEngine`
- 两套几乎相同的任务执行和状态管理逻辑
### 3. 工作流状态分散
- `WorkflowContext` - React 状态
- `WorkflowSubmissionService` - 提交服务
- `WorkflowPollingService` - 轮询服务
- `WorkflowCompletionService` - 后处理追踪
- `workflowStorageReader/Writer` - 存储服务
- 各服务独立维护状态,难以追踪完整生命周期
### 4. 职责边界模糊
- `swTaskQueueService` 既管理任务状态,又负责 SW 通信
- `workflowSubmissionService` 既负责提交,又处理恢复和事件分发
- 缺乏清晰的领域边界
### 5. 缺乏统一领域事件
- 各服务使用独立 RxJS Subject,事件类型不统一
- 跨服务协调依赖直接方法调用,耦合度高
## 设计目标
1. **统一领域模型**:建立任务和工作流的单一真相来源(Single Source of Truth)
2. **清晰边界划分**:应用 DDD 分层架构,分离领域逻辑与基础设施
3. **降低耦合度**:通过领域事件实现服务间松耦合通信
4. **消除重复**:统一类型定义,抽象共享行为
5. **可扩展性**:支持新任务类型和工作流步骤的低成本接入
## 技术栈
- **语言**:TypeScript(严格模式)
- **状态管理**:RxJS(领域事件流)
- **持久化**:IndexedDB(通过 localforage)
- **通信**:postmessage-duplex(SW 双工通信)
- **UI 框架**:React + Context API
## 实现方案
### DDD 分层架构设计
采用经典 DDD 四层架构,适配当前 Service Worker + 主线程的双模式场景:
```
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ (React Hooks/Context, WorkflowSubmissionService) │
├─────────────────────────────────────────────────────────────┤
│ Domain Layer (NEW) │
│ Task Aggregate │ Workflow Aggregate │ Domain Events │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ SW Channel │ Storage │ API Services │ Executors │
└─────────────────────────────────────────────────────────────┘
```
### 1. 统一领域模型(Domain Layer)
#### 核心聚合根
```
packages/drawnix/src/domain/
├── task/
│ ├── task.ts # Task 聚合根(值对象 + 行为)
│ ├── task-status.ts # TaskStatus 值对象
│ ├── task-type.ts # TaskType 值对象
│ └── task-repository.ts # 仓储接口
├── workflow/
│ ├── workflow.ts # Workflow 聚合根
│ ├── workflow-step.ts # WorkflowStep 实体
│ └── workflow-repository.ts # 仓储接口
├── events/
│ ├── domain-event.ts # 领域事件基类
│ ├── task-events.ts # 任务相关事件
│ ├── workflow-events.ts # 工作流相关事件
│ └── event-bus.ts # 统一事件总线
└── index.ts # 公共导出
```
#### 设计要点
**Task 聚合根**:封装任务状态机和业务规则
- 状态转换:`PENDING → PROCESSING → COMPLETED/FAILED/CANCELLED`
- 执行阶段:`SUBMITTING → POLLING → DOWNLOADING`
- 业务规则:只有 FAILED/CANCELLED 状态的任务可重试
**Workflow 聚合根**:封装工作流编排逻辑
- 步骤依赖解析和并行执行
- 动态步骤添加(AI 分析后添加)
- 主线程/SW 步骤分类和路由
**领域事件**:统一的事件契约
- `TaskCreated`, `TaskStatusChanged`, `TaskCompleted`, `TaskFailed`
- `WorkflowStarted`, `WorkflowStepCompleted`, `WorkflowCompleted`
### 2. 仓储模式(Repository Pattern)
抽象存储访问,隔离 IndexedDB 细节:
```typescript
// 仓储接口(领域层)
interface TaskRepository {
findById(id: string): Promise<Task | null>;
findByStatus(status: TaskStatus): Promise<Task[]>;
save(task: Task): Promise<void>;
delete(id: string): Promise<void>;
}
// 实现(基础设施层)
class IndexedDBTaskRepository implements TaskRepository {
// 封装 taskStorageReader/Writer
}
```
### 3. 执行策略模式(Strategy Pattern)
统一 SW 和主线程执行器接口:
```typescript
// 执行策略接口
interface TaskExecutionStrategy {
execute(task: Task): Promise<TaskResult>;
cancel(taskId: string): Promise<void>;
resume(task: Task): Promise<TaskResult>;
}
// SW 执行策略
class SWExecutionStrategy implements TaskExecutionStrategy { }
// 主线程降级执行策略
class MainThreadExecutionStrategy implements TaskExecutionStrategy { }
// 策略选择器
class ExecutionStrategySelector {
async getStrategy(): Promise<TaskExecutionStrategy> {
return await this.isSWAvailable()
? this.swStrategy
: this.mainThreadStrategy;
}
}
```
### 4. 统一事件总线
替代分散的 RxJS Subject:
```typescript
// 领域事件基类
abstract class DomainEvent {
readonly eventId: string;
readonly occurredAt: number;
readonly aggregateId: string;
}
// 事件总线
class DomainEventBus {
private subject = new Subject<DomainEvent>();
publish(event: DomainEvent): void {
this.subject.next(event);
}
subscribe<T extends DomainEvent>(
eventType: new (...args: any[]) => T,
handler: (event: T) => void
): Subscription {
return this.subject.pipe(
filter((e): e is T => e instanceof eventType)
).subscribe(handler);
}
// 支持按聚合 ID 过滤
subscribeToAggregate(
aggregateId: string,
handler: (event: DomainEvent) => void
): Subscription { }
}
```
### 5. 应用服务重构
简化现有服务,专注协调领域操作:
```typescript
// 重构后的 TaskApplicationService
class TaskApplicationService {
constructor(
private taskRepo: TaskRepository,
private executionStrategy: ExecutionStrategySelector,
private eventBus: DomainEventBus
) {}
async createTask(params: CreateTaskParams): Promise<Task> {
const task = Task.create(params);
await this.taskRepo.save(task);
this.eventBus.publish(new TaskCreated(task));
// 异步执行
this.executeTask(task);
return task;
}
private async executeTask(task: Task): Promise<void> {
const strategy = await this.executionStrategy.getStrategy();
try {
const result = await strategy.execute(task);
task.complete(result);
this.eventBus.publish(new TaskCompleted(task));
} catch (error) {
task.fail(error);
this.eventBus.publish(new TaskFailed(task, error));
}
await this.taskRepo.save(task);
}
}
```
## 实现说明
### 迁移策略
采用**渐进式重构**,不一次性重写:
1. **Phase 1**:建立领域层,新代码使用新模型
2. **Phase 2**:逐步迁移现有服务到新架构
3. **Phase 3**:废弃旧代码,统一到新模型
### 关键设计决策
1. **类型统一**:领域层定义唯一类型,SW/主线程通过序列化/反序列化共享
2. **事件溯源预留**:领域事件设计支持未来引入事件溯源
3. **SW 边界清晰**:SW 只作为执行器,领域逻辑在主线程
4. **React 集成**:通过 `useDomainEvent` Hook 将领域事件桥接到 React
### 性能考量
- 领域事件使用 RxJS Subject,内存占用可控
- 仓储层实现缓存策略,减少 IndexedDB 访问
- 大批量任务支持分页加载
## 架构设计
### 系统架构图
```mermaid
graph TB
subgraph "Application Layer"
RC[React Components]
HC[Hooks & Contexts]
AS[Application Services]
end
subgraph "Domain Layer"
TA[Task Aggregate]
WA[Workflow Aggregate]
EB[Event Bus]
DE[Domain Events]
end
subgraph "Infrastructure Layer"
TR[Task Repository]
WR[Workflow Repository]
SS[SW Execution Strategy]
MS[MainThread Strategy]
SC[SW Channel]
DB[(IndexedDB)]
end
RC --> HC
HC --> AS
AS --> TA
AS --> WA
AS --> EB
TA --> DE
WA --> DE
DE --> EB
TA --> TR
WA --> WR
TR --> DB
WR --> DB
AS --> SS
AS --> MS
SS --> SC
SC -->|postMessage| SW[Service Worker]
```
### 领域模型类图
```mermaid
classDiagram
class Task {
+id: string
+type: TaskType
+status: TaskStatus
+params: GenerationParams
+result?: TaskResult
+error?: TaskError
+create(params): Task
+start(): void
+complete(result): void
+fail(error): void
+cancel(): void
+canRetry(): boolean
}
class Workflow {
+id: string
+steps: WorkflowStep[]
+status: WorkflowStatus
+context: WorkflowContext
+addStep(step): void
+updateStepStatus(stepId, status): void
+getExecutableSteps(): WorkflowStep[]
+isCompleted(): boolean
}
class WorkflowStep {
+id: string
+mcp: string
+args: Record
+status: StepStatus
+dependsOn?: string[]
+taskId?: string
}
class DomainEvent {
<<abstract>>
+eventId: string
+occurredAt: number
+aggregateId: string
}
class TaskCreated {
+task: Task
}
class TaskCompleted {
+task: Task
+result: TaskResult
}
Task --> TaskStatus
Task --> TaskType
Workflow --> WorkflowStep
Workflow --> WorkflowStatus
WorkflowStep --> Task : creates
DomainEvent <|-- TaskCreated
DomainEvent <|-- TaskCompleted
```
## 目录结构
```
packages/drawnix/src/
├── domain/ # [NEW] 领域层 - 核心业务逻辑
│ ├── task/
│ │ ├── task.ts # Task 聚合根。封装任务状态机(PENDING→PROCESSING→COMPLETED/FAILED),包含 create()、start()、complete()、fail()、cancel() 等状态转换方法,以及 canRetry() 等业务规则校验。
│ │ ├── task-status.ts # TaskStatus 值对象。定义任务状态枚举,统一主线程和 SW 的状态定义,消除当前 3 处重复定义的问题。
│ │ ├── task-type.ts # TaskType 值对象。定义任务类型(IMAGE/VIDEO/CHARACTER/INSPIRATION_BOARD/CHAT),包含类型相关的配置(超时时间等)。
│ │ ├── task-result.ts # TaskResult 值对象。封装任务结果数据结构,支持不同类型任务的结果格式。
│ │ ├── task-repository.ts # TaskRepository 接口。定义任务仓储契约(findById/findByStatus/save/delete),隔离持久化细节。
│ │ └── index.ts # Task 模块导出。
│ ├── workflow/
│ │ ├── workflow.ts # Workflow 聚合根。封装工作流状态管理和步骤编排逻辑,包含 addStep()、updateStepStatus()、getExecutableSteps() 等方法。
│ │ ├── workflow-step.ts # WorkflowStep 实体。定义工作流步骤结构,包含依赖关系、执行状态、关联的任务 ID 等。
│ │ ├── workflow-status.ts # WorkflowStatus 值对象。定义工作流状态枚举(pending/running/completed/failed/cancelled)。
│ │ ├── workflow-repository.ts # WorkflowRepository 接口。定义工作流仓储契约。
│ │ └── index.ts # Workflow 模块导出。
│ ├── events/
│ │ ├── domain-event.ts # DomainEvent 基类。定义所有领域事件的公共属性(eventId、occurredAt、aggregateId),提供事件序列化能力。
│ │ ├── task-events.ts # 任务领域事件。包含 TaskCreated、TaskStatusChanged、TaskCompleted、TaskFailed、TaskCancelled 等事件类。
│ │ ├── workflow-events.ts # 工作流领域事件。包含 WorkflowStarted、WorkflowStepStatusChanged、WorkflowStepCompleted、WorkflowCompleted、WorkflowFailed 等事件类。
│ │ ├── event-bus.ts # DomainEventBus 单例。统一的事件发布/订阅中心,替代分散的 RxJS Subject,支持按事件类型和聚合 ID 过滤订阅。
│ │ └── index.ts # Events 模块导出。
│ ├── shared/
│ │ ├── generation-params.ts # GenerationParams 值对象。统一的生成参数定义,被 Task 和 Workflow 共享。
│ │ └── index.ts # Shared 模块导出。
│ └── index.ts # 领域层统一导出。
├── application/ # [NEW] 应用服务层 - 用例编排
│ ├── task-application-service.ts # TaskApplicationService。协调任务创建、执行、状态更新的用例,使用仓储和执行策略,发布领域事件。替代当前 swTaskQueueService 中的业务逻辑。
│ ├── workflow-application-service.ts # WorkflowApplicationService。协调工作流提交、步骤执行、状态同步的用例,整合当前 workflowSubmissionService、workflowPollingService、workflowCompletionService 的职责。
│ └── index.ts # 应用服务层导出。
├── infrastructure/ # [MODIFY] 基础设施层 - 重组现有服务
│ ├── persistence/
│ │ ├── indexeddb-task-repository.ts # IndexedDBTaskRepository。实现 TaskRepository 接口,封装 taskStorageReader/Writer,提供任务持久化能力。
│ │ ├── indexeddb-workflow-repository.ts # IndexedDBWorkflowRepository。实现 WorkflowRepository 接口,封装 workflowStorageReader/Writer。
│ │ └── index.ts # 持久化模块导出。
│ ├── execution/
│ │ ├── execution-strategy.ts # TaskExecutionStrategy 接口。定义任务执行策略契约(execute/cancel/resume)。
│ │ ├── sw-execution-strategy.ts # SWExecutionStrategy。通过 SW Channel 执行任务的策略实现,封装 swChannelClient 调用。
│ │ ├── mainthread-execution-strategy.ts # MainThreadExecutionStrategy。主线程降级执行策略,封装 media-executor 调用。
│ │ ├── execution-strategy-selector.ts # ExecutionStrategySelector。根据 SW 可用性自动选择执行策略的选择器。
│ │ └── index.ts # 执行策略模块导出。
│ └── index.ts # 基础设施层导出。
├── hooks/
│ ├── useDomainEvent.ts # [NEW] useDomainEvent Hook。将领域事件桥接到 React,订阅指定类型的事件并触发组件更新。
│ ├── useTask.ts # [MODIFY] 重构为使用 TaskApplicationService 和 useDomainEvent,简化状态管理逻辑。
│ └── useWorkflow.ts # [MODIFY] 重构为使用 WorkflowApplicationService 和 useDomainEvent。
└── services/
├── sw-task-queue-service.ts # [MODIFY] 简化为薄层,委托给 TaskApplicationService,保留向后兼容的 API。
├── workflow-submission-service.ts # [MODIFY] 简化为薄层,委托给 WorkflowApplicationService。
└── ... # 其他服务保持不变或逐步迁移。
```
## 核心代码结构
### Task 聚合根接口
```typescript
/**
* Task 聚合根
* 封装任务状态机和业务规则
*/
export class Task {
private constructor(
public readonly id: string,
public readonly type: TaskType,
private _status: TaskStatus,
public readonly params: GenerationParams,
public readonly createdAt: number,
private _updatedAt: number,
private _startedAt?: number,
private _completedAt?: number,
private _result?: TaskResult,
private _error?: TaskError,
private _progress?: number,
private _executionPhase?: TaskExecutionPhase
) {}
// 工厂方法
static create(params: CreateTaskParams): Task;
// 状态查询
get status(): TaskStatus;
get result(): TaskResult | undefined;
get error(): TaskError | undefined;
// 状态转换
start(): void;
updateProgress(progress: number, phase?: TaskExecutionPhase): void;
complete(result: TaskResult): void;
fail(error: TaskError): void;
cancel(): void;
// 业务规则
canRetry(): boolean;
isTerminal(): boolean;
}
```
### DomainEventBus 接口
```typescript
/**
* 领域事件总线
* 统一的事件发布/订阅中心
*/
export class DomainEventBus {
private static instance: DomainEventBus;
private subject = new Subject<DomainEvent>();
static getInstance(): DomainEventBus;
// 发布事件
publish(event: DomainEvent): void;
// 按类型订阅
subscribe<T extends DomainEvent>(
eventType: new (...args: any[]) => T,
handler: (event: T) => void
): Subscription;
// 按聚合 ID 订阅
subscribeToAggregate(
aggregateId: string,
handler: (event: DomainEvent) => void
): Subscription;
// 订阅所有事件(调试用)
subscribeAll(handler: (event: DomainEvent) => void): Subscription;
}
```
### ExecutionStrategySelector 接口
```typescript
/**
* 执行策略选择器
* 自动选择 SW 或主线程执行策略
*/
export class ExecutionStrategySelector {
constructor(
private swStrategy: SWExecutionStrategy,
private mainThreadStrategy: MainThreadExecutionStrategy
) {}
async getStrategy(): Promise<TaskExecutionStrategy> {
const swAvailable = await this.checkSWAvailability();
return swAvailable ? this.swStrategy : this.mainThreadStrategy;
}
private async checkSWAvailability(): Promise<boolean>;
}
```
## Agent Extensions
### SubAgent
- **code-explorer**
- 目的:在实现阶段深度探索现有服务的具体实现细节,包括 SW 端的 handlers、存储实现、通信协议等
- 预期结果:获取完整的代码上下文,确保新领域层与现有基础设施的正确集成
+3
View File
@@ -147,6 +147,9 @@ Service Worker (后台执行)
16. **数据安全保护**:破坏性操作(删除、覆盖)前必须安全检查,当前编辑项不可删除,批量删除需确认,不可逆操作需输入确认文字
17. **模块循环依赖**:将共享函数(如 `shouldUseSWTaskQueue`)提取到独立模块打破循环;单例构造函数用 `queueMicrotask` 延迟访问其他模块导出
18. **配置对象深拷贝**:`getSetting()` 等返回配置对象的方法需返回深拷贝,防止脱敏函数等外部操作修改全局状态
19. **避免过度设计**:优先使用简单的 interface + service 模式;只在有明确需求时才添加 Repository、Strategy 等抽象层
20. **删除未使用代码**:未使用的"基础设施"代码应删除,不要"为未来预留";遵循 YAGNI 原则
21. **重构先问问题**:重构前明确要解决的实际问题;验证方案是否真的解决问题而非增加复杂度
### Service Worker 规则
+15 -100
View File
@@ -4,112 +4,27 @@
* Defines all types for SW-based task queue management and
* postMessage communication between main thread and Service Worker.
*
* Note: Types are defined independently here to avoid import issues in SW context.
* Keep in sync with packages/drawnix/src/types/task.types.ts
* Core types are imported from the shared types module to ensure consistency.
* 核心类型从共享类型模块导入,确保与主线程一致。
*/
import type { ChatToolCall } from './chat-workflow/types';
// ============================================================================
// Task Enums and Core Types (mirrored from task.types.ts)
// Re-export Core Types from Shared Module
// ============================================================================
/**
* Task status enumeration
*/
export enum TaskStatus {
PENDING = 'pending',
PROCESSING = 'processing',
COMPLETED = 'completed',
FAILED = 'failed',
CANCELLED = 'cancelled',
}
// Re-export all core types from shared module
export * from '../../../../../packages/drawnix/src/types/shared/core.types';
/**
* Task type enumeration
*/
export enum TaskType {
IMAGE = 'image',
VIDEO = 'video',
CHARACTER = 'character',
INSPIRATION_BOARD = 'inspiration_board',
CHAT = 'chat',
}
/**
* Task execution phase enumeration
*/
export enum TaskExecutionPhase {
SUBMITTING = 'submitting',
POLLING = 'polling',
DOWNLOADING = 'downloading',
}
/**
* Generation parameters interface
*/
export interface GenerationParams {
prompt: string;
width?: number;
height?: number;
size?: string;
duration?: number;
style?: string;
model?: string;
seed?: number;
sourceVideoTaskId?: string;
characterTimestamps?: string;
sourceLocalTaskId?: string;
gridImageRows?: number;
gridImageCols?: number;
gridImageLayoutStyle?: 'scattered' | 'grid' | 'circular';
inspirationBoardLayoutStyle?: 'inspiration-board';
isInspirationBoard?: boolean;
inspirationBoardImageCount?: number;
/** Whether to auto-insert the result to canvas when task completes */
autoInsertToCanvas?: boolean;
/** image aspect ratio token (e.g. 16:9) */
aspectRatio?: string;
[key: string]: unknown;
}
/**
* Task result interface
*/
export interface TaskResult {
url: string;
format: string;
size: number;
width?: number;
height?: number;
duration?: number;
thumbnailUrl?: string;
characterUsername?: string;
characterProfileUrl?: string;
characterPermalink?: string;
/** Chat response content (chat only) */
chatResponse?: string;
/** Tool calls made during chat (chat only) */
toolCalls?: ChatToolCall[];
}
/**
* Task error details interface
*/
export interface TaskErrorDetails {
originalError?: string;
apiResponse?: unknown;
timestamp?: number;
}
/**
* Task error interface
*/
export interface TaskError {
code: string;
message: string;
details?: TaskErrorDetails;
}
// Import types for use in this file
import type {
ChatToolCall,
GenerationParams,
TaskResult,
TaskError,
TaskStatus,
TaskType,
TaskExecutionPhase,
} from '../../../../../packages/drawnix/src/types/shared/core.types';
// ============================================================================
// Task Config
+232
View File
@@ -8,6 +8,7 @@
## 目录
- [架构与重构规范](#架构与重构规范)
- [文件命名规范](#文件命名规范)
- [TypeScript 规范](#typescript-规范)
- [React 组件规范](#react-组件规范)
@@ -21,6 +22,237 @@
---
## 架构与重构规范
### 避免过度设计和不必要的抽象层
**场景**: 在重构或添加新功能时,避免引入不必要的架构模式
**核心原则**:
- 优先使用简单的 interface + service 模式
- 只在有明确需求时才添加抽象层
- 遵循 YAGNI 原则(You Aren't Gonna Need It)
❌ **错误示例 - 过度使用 DDD 模式**:
```typescript
// 错误:为简单的任务管理引入过多抽象层
// domain/task/task.ts (487 行)
export class Task {
private constructor(...) {} // 私有构造函数
static create(...) {} // 工厂方法
start() {} // 状态转换方法
complete() {}
fail() {}
// ... 50+ 个方法
}
// domain/task/task-repository.ts
interface TaskRepository {
findById(id: string): Promise<Task | null>;
save(task: Task): Promise<void>;
}
// infrastructure/persistence/indexeddb-task-repository.ts
class IndexedDBTaskRepository implements TaskRepository {
// 只是包装了现有的 taskStorageReader/Writer
}
// infrastructure/execution/execution-strategy.ts
interface TaskExecutionStrategy {
execute(task: Task): Promise<TaskResult>;
}
// infrastructure/execution/sw-execution-strategy.ts
class SWExecutionStrategy implements TaskExecutionStrategy {
// 包装了一个简单的条件判断
}
// 结果:30 个新文件,3000+ 行代码,但没有解决实际问题
```
✅ **正确示例 - 保持简单**:
```typescript
// types/task.types.ts - 简单的接口定义
export interface Task {
id: string;
type: TaskType;
status: TaskStatus;
params: GenerationParams;
// ... 其他字段
}
// services/task-queue-service.ts - 直接的服务实现
export class TaskQueueService {
async createTask(params: GenerationParams): Promise<Task> {
const task = { /* ... */ };
await taskStorageWriter.saveTask(task);
return task;
}
async executeTask(task: Task): Promise<void> {
// 简单的条件判断,不需要 Strategy 模式
if (shouldUseSWTaskQueue()) {
await swTaskQueueService.createTask(task.type, task.params);
} else {
await taskQueueService.createTask(task.type, task.params);
}
}
}
// 结果:清晰、简单、易维护
```
**判断是否需要抽象层的标准**:
1. **Repository 模式**:
- ❌ 不需要:只有一种存储实现(如只用 IndexedDB)
- ✅ 需要:需要支持多种存储后端(IndexedDB、LocalStorage、远程 API)
2. **Strategy 模式**:
- ❌ 不需要:只是简单的 if/else 判断(如 `if (shouldUseSW)`)
- ✅ 需要:有 3+ 种算法需要动态切换,且逻辑复杂
3. **Factory 模式**:
- ❌ 不需要:对象创建逻辑简单(如 `{ id, type, status }`)
- ✅ 需要:创建逻辑复杂,需要根据类型创建不同的对象
4. **Aggregate Root**:
- ❌ 不需要:简单的数据结构,状态转换逻辑简单
- ✅ 需要:复杂的业务规则,需要保证不变性约束
**原因**:
- 过度设计会增加代码量和维护成本
- 抽象层会增加理解难度和调试复杂度
- 简单的问题应该用简单的方案解决
- 只在有明确需求时才引入复杂模式
---
### 删除未使用的"基础设施"代码
**场景**: 创建了"为未来准备"的代码,但实际上没有被使用
**核心原则**:
- 代码应该解决当前的实际问题
- 未使用的代码应该删除,不要"为未来预留"
- 如果未来真的需要,可以从 Git 历史恢复
❌ **错误示例 - 创建未使用的事件系统**:
```typescript
// services/app-events.ts - 创建了统一事件总线
export function publishEvent(event: AppEvent): void { }
export function subscribeToEventType<T>(type: T, handler: Function) { }
// types/events.types.ts - 定义了完整的事件类型
export interface TaskCreatedEvent extends BaseEvent { }
export interface TaskCompletedEvent extends BaseEvent { }
// ... 10+ 个事件类型
// 问题:没有任何代码使用这些文件
// grep -r "from.*app-events" 返回 0 个结果
```
✅ **正确做法 - 删除未使用的代码**:
```bash
# 检查是否有代码使用
grep -r "from.*app-events" packages/drawnix/src
# 如果没有使用,直接删除
rm src/services/app-events.ts
rm src/types/shared/events.types.ts
# 如果未来需要,可以从 Git 历史恢复
git log --all --full-history -- "**/app-events.ts"
```
**判断是否应该保留代码的标准**:
1. **有实际使用**:
- ✅ 保留:至少有 1 处代码导入并使用
- ❌ 删除:没有任何地方使用
2. **解决实际问题**:
- ✅ 保留:解决了当前存在的问题
- ❌ 删除:只是"可能有用"的基础设施
3. **维护成本**:
- ✅ 保留:维护成本低,不会成为负担
- ❌ 删除:需要持续维护,但没有实际价值
**原因**:
- 未使用的代码会增加维护负担
- 会让新开发者困惑("这个是干什么的?")
- Git 历史可以保存所有删除的代码
- YAGNI 原则:不要为未来可能的需求编写代码
---
### 重构前先问"解决什么问题"
**场景**: 在进行架构重构时,应该先明确要解决的实际问题
**核心原则**:
- 重构应该解决具体的痛点,而不是追求"更好的架构"
- 先列出当前的实际问题,再设计解决方案
- 验证重构是否真的解决了问题
❌ **错误示例 - 为了架构而重构**:
```typescript
// 问题描述:"代码不够优雅,需要引入 DDD"
// 实际情况:代码工作正常,没有明确的痛点
// 重构方案:
// - 添加 domain 层(18 个文件)
// - 添加 application 层(3 个文件)
// - 添加 infrastructure 层(9 个文件)
// 结果:
// - 新增 30 个文件,3000+ 行代码
// - 原有问题没有解决
// - 引入了新的复杂度
```
✅ **正确示例 - 针对问题重构**:
```typescript
// 问题 1:类型定义在 3 处重复,需要手动同步
// 解决方案:创建共享类型文件
// 结果:删除 2 处重复定义,统一到 1 个文件
// 问题 2:DDD 层增加了 30 个文件但没有解决实际问题
// 解决方案:删除 DDD 层,回归简单的 interface + service
// 结果:删除 30 个文件,减少 3000 行代码
// 验证:
// - 类型定义只有 1 处 ✓
// - 代码量减少 ✓
// - 构建和测试通过 ✓
```
**重构前的检查清单**:
1. **明确问题**:
- [ ] 列出当前存在的具体问题
- [ ] 问题是否真的影响开发效率或代码质量
- [ ] 问题是否频繁出现
2. **评估方案**:
- [ ] 方案是否直接解决问题
- [ ] 方案是否引入新的复杂度
- [ ] 方案的成本是否合理
3. **验证结果**:
- [ ] 原有问题是否解决
- [ ] 是否引入新问题
- [ ] 代码是否更简单(文件数、行数)
**原因**:
- 架构模式不是目的,解决问题才是
- 好的架构应该让代码更简单,而不是更复杂
- 重构应该有明确的收益,而不是"看起来更好"
---
### 文件命名规范
- **组件**: `PascalCase.tsx` (如 `ImageCropPopup.tsx`)
@@ -0,0 +1,290 @@
/**
* Core Type Definitions - Shared between Main Thread and Service Worker
*
* This is the single source of truth for task and workflow types.
* Both main thread and SW import from here to ensure type consistency.
*
* 这是任务和工作流类型的唯一真相来源。
* 主线程和 SW 都从这里导入,确保类型一致性。
*/
// ============================================================================
// Chat Tool Call
// ============================================================================
/**
* Chat tool call interface
* Represents a tool call made during AI chat/analysis
*/
export interface ChatToolCall {
/** Tool name (e.g., 'generate_image', 'generate_video') */
name: string;
/** Tool arguments */
arguments: Record<string, unknown>;
/** Tool call result */
result?: {
success: boolean;
taskId?: string;
error?: string;
data?: unknown;
};
}
// ============================================================================
// Task Enums
// ============================================================================
/**
* Task status enumeration
* Represents all possible states a task can be in during its lifecycle
*/
export enum TaskStatus {
/** Task is waiting to be executed */
PENDING = 'pending',
/** Task is currently being processed */
PROCESSING = 'processing',
/** Task completed successfully */
COMPLETED = 'completed',
/** Task failed */
FAILED = 'failed',
/** Task was cancelled by the user */
CANCELLED = 'cancelled'
}
/**
* Task type enumeration
* Defines the types of content that can be generated
*/
export enum TaskType {
/** Image generation task */
IMAGE = 'image',
/** Video generation task */
VIDEO = 'video',
/** Character extraction task */
CHARACTER = 'character',
/** Inspiration board generation task (image + split + layout) */
INSPIRATION_BOARD = 'inspiration_board',
/** Chat/AI analysis task (text model streaming) */
CHAT = 'chat',
}
/**
* Task execution phase enumeration
* Used for tracking async task progress and enabling recovery after page refresh
*/
export enum TaskExecutionPhase {
/** Task is being submitted to the API */
SUBMITTING = 'submitting',
/** Task submitted, polling for completion (video only) */
POLLING = 'polling',
/** Task completed, downloading result */
DOWNLOADING = 'downloading'
}
// ============================================================================
// Generation Parameters
// ============================================================================
/**
* Generation parameters interface
* Contains all parameters needed for AI content generation
*/
export interface GenerationParams {
/** Text prompt describing the desired content */
prompt: string;
/** Image/video width in pixels */
width?: number;
/** Image/video height in pixels */
height?: number;
/** Size parameter for API (e.g., '16x9', '1x1') */
size?: string;
/** Video duration in seconds (video only) */
duration?: number;
/** Style or model to use for generation */
style?: string;
/** AI model to use (e.g., 'veo3', 'sora-2') */
model?: string;
/** Random seed for reproducible generation */
seed?: number;
/** Source video task ID for character extraction */
sourceVideoTaskId?: string;
/** Time range for character extraction (format: "start,end") */
characterTimestamps?: string;
/** Local task ID of source video */
sourceLocalTaskId?: string;
/** Grid image grid rows (grid_image only) */
gridImageRows?: number;
/** Grid image grid columns (grid_image only) */
gridImageCols?: number;
/** Grid image layout style (grid_image only) */
gridImageLayoutStyle?: 'scattered' | 'grid' | 'circular';
/** Inspiration board layout style (inspiration_board only) */
inspirationBoardLayoutStyle?: 'inspiration-board';
/** Whether this is an inspiration board task */
isInspirationBoard?: boolean;
/** Inspiration board image count */
inspirationBoardImageCount?: number;
/** Whether to auto-insert the result to canvas when task completes */
autoInsertToCanvas?: boolean;
/** Additional parameters for specific generation types */
[key: string]: any;
}
// ============================================================================
// Task Result
// ============================================================================
/**
* Task result interface
* Contains the output from a successfully completed task
*/
export interface TaskResult {
/** URL to the generated content */
url: string;
/** File format (e.g., 'png', 'jpg', 'mp4') */
format: string;
/** File size in bytes */
size: number;
/** Content width in pixels */
width?: number;
/** Content height in pixels */
height?: number;
/** Video duration in seconds (video only) */
duration?: number;
/** Video thumbnail URL (video only) */
thumbnailUrl?: string;
/** Character username for @mention (character only) */
characterUsername?: string;
/** Character profile picture URL (character only) */
characterProfileUrl?: string;
/** Character permalink (character only) */
characterPermalink?: string;
/** Chat response content (chat only) */
chatResponse?: string;
/** Tool calls made during chat (chat only) */
toolCalls?: ChatToolCall[];
}
// ============================================================================
// Task Error
// ============================================================================
/**
* Task error details interface
* Contains the original error information for debugging
*/
export interface TaskErrorDetails {
/** Original error message from the API or system */
originalError?: string;
/** API response data (sensitive info filtered) */
apiResponse?: any;
/** Error occurrence timestamp */
timestamp?: number;
}
/**
* Task error interface
* Contains detailed information about task failures
*/
export interface TaskError {
/** Error code for categorization (e.g., 'TIMEOUT', 'NETWORK', 'API_ERROR') */
code: string;
/** Human-readable error message (user-friendly) */
message: string;
/** Detailed error information for debugging */
details?: TaskErrorDetails;
}
// ============================================================================
// Task Interface
// ============================================================================
/**
* Core task interface
* Represents a single generation task with all its metadata
*/
export interface Task {
/** Unique task identifier (UUID v4) */
id: string;
/** Type of content to generate */
type: TaskType;
/** Current task status */
status: TaskStatus;
/** Parameters for content generation */
params: GenerationParams;
/** Task creation timestamp (Unix milliseconds) */
createdAt: number;
/** Last update timestamp (Unix milliseconds) */
updatedAt: number;
/** Execution start timestamp (Unix milliseconds) */
startedAt?: number;
/** Completion timestamp (Unix milliseconds) */
completedAt?: number;
/** Generation result (if successful) */
result?: TaskResult;
/** Error information (if failed) */
error?: TaskError;
/** User identifier (reserved for multi-user support) */
userId?: string;
/** Task progress percentage (0-100) for video generation */
progress?: number;
/** Remote task ID from API (e.g., videoId for video generation) */
remoteId?: string;
/** Current execution phase for recovery support */
executionPhase?: TaskExecutionPhase;
/** Whether the task result has been saved to the media library */
savedToLibrary?: boolean;
/** Whether the task result has been inserted to canvas */
insertedToCanvas?: boolean;
}
// ============================================================================
// Utility Constants and Functions
// ============================================================================
/**
* Terminal task statuses (task lifecycle has ended)
*/
export const TERMINAL_STATUSES: TaskStatus[] = [
TaskStatus.COMPLETED,
TaskStatus.FAILED,
TaskStatus.CANCELLED,
];
/**
* Retryable task statuses (task can be retried)
*/
export const RETRYABLE_STATUSES: TaskStatus[] = [
TaskStatus.FAILED,
TaskStatus.CANCELLED,
];
/**
* Active task statuses (task is currently being processed)
*/
export const ACTIVE_STATUSES: TaskStatus[] = [
TaskStatus.PENDING,
TaskStatus.PROCESSING,
];
/**
* Check if a task status is terminal (lifecycle has ended)
*/
export function isTerminalStatus(status: TaskStatus): boolean {
return TERMINAL_STATUSES.includes(status);
}
/**
* Check if a task status is retryable
*/
export function isRetryableStatus(status: TaskStatus): boolean {
return RETRYABLE_STATUSES.includes(status);
}
/**
* Check if a task status is active (currently being processed)
*/
export function isActiveStatus(status: TaskStatus): boolean {
return ACTIVE_STATUSES.includes(status);
}
+12 -206
View File
@@ -1,216 +1,22 @@
/**
* Task Queue System Type Definitions
*
* Defines all TypeScript types and interfaces for the task queue system.
* These types form the foundation for task management, state tracking, and
* integration with the generation API.
* This file re-exports core types from the shared types module.
* The shared types are the single source of truth used by both main thread and SW.
*
* 此文件从共享类型模块重新导出核心类型。
* 共享类型是主线程和 SW 使用的唯一真相来源。
*/
/**
* Chat tool call interface
* Represents a tool call made during AI chat/analysis
*/
export interface ChatToolCall {
/** Tool name (e.g., 'generate_image', 'generate_video') */
name: string;
/** Tool arguments */
arguments: Record<string, unknown>;
/** Tool call result */
result?: {
success: boolean;
taskId?: string;
error?: string;
data?: unknown;
};
}
// ============================================================================
// Re-export Core Types from Shared Module
// ============================================================================
/**
* Task status enumeration
* Represents all possible states a task can be in during its lifecycle
*/
export enum TaskStatus {
/** Task is waiting to be executed */
PENDING = 'pending',
/** Task is currently being processed */
PROCESSING = 'processing',
/** Task completed successfully */
COMPLETED = 'completed',
/** Task failed */
FAILED = 'failed',
/** Task was cancelled by the user */
CANCELLED = 'cancelled'
}
// Re-export all types from shared module
export * from './shared/core.types';
/**
* Task type enumeration
* Defines the types of content that can be generated
*/
export enum TaskType {
/** Image generation task */
IMAGE = 'image',
/** Video generation task */
VIDEO = 'video',
/** Character extraction task */
CHARACTER = 'character',
/** Inspiration board generation task (image + split + layout) */
INSPIRATION_BOARD = 'inspiration_board',
/** Chat/AI analysis task (text model streaming) */
CHAT = 'chat',
}
/**
* Generation parameters interface
* Contains all parameters needed for AI content generation
*/
export interface GenerationParams {
/** Text prompt describing the desired content */
prompt: string;
/** Image/video width in pixels */
width?: number;
/** Image/video height in pixels */
height?: number;
/** Size parameter for API (e.g., '16x9', '1x1') */
size?: string;
/** Video duration in seconds (video only) */
duration?: number;
/** Style or model to use for generation */
style?: string;
/** AI model to use (e.g., 'veo3', 'sora-2') */
model?: string;
/** Random seed for reproducible generation */
seed?: number;
/** Source video task ID for character extraction */
sourceVideoTaskId?: string;
/** Time range for character extraction (format: "start,end") */
characterTimestamps?: string;
/** Local task ID of source video */
sourceLocalTaskId?: string;
/** Grid image grid rows (grid_image only) */
gridImageRows?: number;
/** Grid image grid columns (grid_image only) */
gridImageCols?: number;
/** Grid image layout style (grid_image only) */
gridImageLayoutStyle?: 'scattered' | 'grid' | 'circular';
/** Inspiration board layout style (inspiration_board only) */
inspirationBoardLayoutStyle?: 'inspiration-board';
/** Whether this is an inspiration board task */
isInspirationBoard?: boolean;
/** Inspiration board image count */
inspirationBoardImageCount?: number;
/** Whether to auto-insert the result to canvas when task completes */
autoInsertToCanvas?: boolean;
/** Additional parameters for specific generation types */
[key: string]: any;
}
/**
* Task result interface
* Contains the output from a successfully completed task
*/
export interface TaskResult {
/** URL to the generated content */
url: string;
/** File format (e.g., 'png', 'jpg', 'mp4') */
format: string;
/** File size in bytes */
size: number;
/** Content width in pixels */
width?: number;
/** Content height in pixels */
height?: number;
/** Video duration in seconds (video only) */
duration?: number;
/** Video thumbnail URL (video only) */
thumbnailUrl?: string;
/** Character username for @mention (character only) */
characterUsername?: string;
/** Character profile picture URL (character only) */
characterProfileUrl?: string;
/** Character permalink (character only) */
characterPermalink?: string;
/** Chat response content (chat only) */
chatResponse?: string;
/** Tool calls made during chat (chat only) */
toolCalls?: ChatToolCall[];
}
/**
* Task error details interface
* Contains the original error information for debugging
*/
export interface TaskErrorDetails {
/** Original error message from the API or system */
originalError?: string;
/** API response data (sensitive info filtered) */
apiResponse?: any;
/** Error occurrence timestamp */
timestamp?: number;
}
/**
* Task error interface
* Contains detailed information about task failures
*/
export interface TaskError {
/** Error code for categorization (e.g., 'TIMEOUT', 'NETWORK', 'API_ERROR') */
code: string;
/** Human-readable error message (user-friendly) */
message: string;
/** Detailed error information for debugging */
details?: TaskErrorDetails;
}
/**
* Task execution phase enumeration
* Used for tracking async task progress and enabling recovery after page refresh
*/
export enum TaskExecutionPhase {
/** Task is being submitted to the API */
SUBMITTING = 'submitting',
/** Task submitted, polling for completion (video only) */
POLLING = 'polling',
/** Task completed, downloading result */
DOWNLOADING = 'downloading'
}
/**
* Core task interface
* Represents a single generation task with all its metadata
*/
export interface Task {
/** Unique task identifier (UUID v4) */
id: string;
/** Type of content to generate */
type: TaskType;
/** Current task status */
status: TaskStatus;
/** Parameters for content generation */
params: GenerationParams;
/** Task creation timestamp (Unix milliseconds) */
createdAt: number;
/** Last update timestamp (Unix milliseconds) */
updatedAt: number;
/** Execution start timestamp (Unix milliseconds) */
startedAt?: number;
/** Completion timestamp (Unix milliseconds) */
completedAt?: number;
/** Generation result (if successful) */
result?: TaskResult;
/** Error information (if failed) */
error?: TaskError;
/** User identifier (reserved for multi-user support) */
userId?: string;
/** Task progress percentage (0-100) for video generation */
progress?: number;
/** Remote task ID from API (e.g., videoId for video generation) */
remoteId?: string;
/** Current execution phase for recovery support */
executionPhase?: TaskExecutionPhase;
/** Whether the task result has been saved to the media library */
savedToLibrary?: boolean;
/** Whether the task result has been inserted to canvas */
insertedToCanvas?: boolean;
}
// Import types for use in this file
import type { Task } from './shared/core.types';
/**
* Task queue state interface