2026-05-23 18:49:50 +08:00
|
|
|
|
# 前端工程
|
|
|
|
|
|
|
|
|
|
|
|
## 技术栈
|
|
|
|
|
|
|
|
|
|
|
|
Vite 6 + React 18 + TypeScript + zustand + react-router-dom
|
|
|
|
|
|
|
|
|
|
|
|
| 类别 | 选型 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| 构建 | Vite 6 | 开发服务器端口 3000,`/api` 代理到 `localhost:8080` |
|
|
|
|
|
|
| UI 框架 | React 18 | 函数组件 + Hooks |
|
|
|
|
|
|
| 状态管理 | zustand | 轻量,支持 devtools 中间件 |
|
|
|
|
|
|
| 路由 | react-router-dom v6 | SPA 模式 |
|
2026-05-24 10:22:15 +08:00
|
|
|
|
| 实时通信 | 原生 WebSocket | 连接后端 `/api/v1/tasks/:taskId/ws` |
|
2026-05-23 18:49:50 +08:00
|
|
|
|
| HTTP 请求 | fetch + 封装层 | 统一错误处理、响应解包 |
|
|
|
|
|
|
|
|
|
|
|
|
## 目录结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
frontend/src/
|
|
|
|
|
|
├── main.tsx # 入口
|
|
|
|
|
|
├── App.tsx # 根组件 + 路由配置
|
|
|
|
|
|
├── api/ # API 封装层
|
|
|
|
|
|
│ ├── client.ts # fetch 封装:baseURL、统一错误处理、响应解包
|
2026-05-24 10:22:15 +08:00
|
|
|
|
│ ├── project.ts # 工程 CRUD + 风格 GET/PUT
|
|
|
|
|
|
│ ├── generate.ts # POST /generate、GET /tasks/:taskId、GET /tasks/:taskId/assets
|
|
|
|
|
|
│ └── types.ts # API 请求/响应类型定义(Task、Asset、Style、PipelineProgress 等)
|
2026-05-23 18:49:50 +08:00
|
|
|
|
├── stores/ # zustand stores
|
2026-05-24 10:22:15 +08:00
|
|
|
|
│ ├── project.ts # 当前工程(id、风格、任务列表)
|
2026-05-23 18:49:50 +08:00
|
|
|
|
│ ├── task.ts # 当前任务草稿(用户文本、素材类型、任务风格覆盖、技术参数)
|
2026-05-24 10:22:15 +08:00
|
|
|
|
│ └── generation.ts # 生成状态(taskId、进度、阶段、结果、错误)
|
2026-05-23 18:49:50 +08:00
|
|
|
|
├── pages/ # 页面级组件
|
|
|
|
|
|
│ ├── ProjectPage.tsx # 工程首页:工程风格配置 + 任务列表
|
|
|
|
|
|
│ ├── GeneratePage.tsx # 生成页:提示词构建 + 提交
|
|
|
|
|
|
│ └── ResultPage.tsx # 结果页:素材预览 + 下载 + 元数据
|
|
|
|
|
|
├── components/ # 可复用组件
|
|
|
|
|
|
│ ├── StyleSelector.tsx # 风格选择器
|
|
|
|
|
|
│ ├── PromptEditor.tsx # 三段式提示词编辑器
|
|
|
|
|
|
│ ├── GenerateForm.tsx # 生成表单
|
2026-05-24 10:22:15 +08:00
|
|
|
|
│ ├── ProgressBar.tsx # 管线进度条(显示当前阶段 + 重试状态)
|
2026-05-23 18:49:50 +08:00
|
|
|
|
│ └── AssetPreview.tsx # 素材预览(spritesheet 预览、单帧预览)
|
|
|
|
|
|
├── hooks/ # 自定义 Hooks
|
|
|
|
|
|
│ ├── useGenerate.ts # 提交生成任务 + WebSocket 订阅进度
|
|
|
|
|
|
│ └── useProjectStyle.ts # 工程风格加载/保存
|
|
|
|
|
|
├── router/ # 路由定义
|
|
|
|
|
|
│ └── index.tsx
|
|
|
|
|
|
└── utils/ # 工具函数
|
|
|
|
|
|
└── style.ts # 风格合并逻辑(与后端保持一致的 merge 算法)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 路由规划
|
|
|
|
|
|
|
|
|
|
|
|
| 路径 | 页面 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| `/` | — | 重定向到默认工程 |
|
|
|
|
|
|
| `/projects/:projectId` | ProjectPage | 工程首页,配置工程风格,查看历史任务 |
|
|
|
|
|
|
| `/projects/:projectId/generate` | GeneratePage | 提示词构建 + 提交生成 |
|
|
|
|
|
|
| `/projects/:projectId/tasks/:taskId` | ResultPage | 任务结果页,预览素材、下载 |
|
|
|
|
|
|
|
|
|
|
|
|
## 组件树
|
|
|
|
|
|
|
2026-05-24 10:22:15 +08:00
|
|
|
|
```mermaid
|
|
|
|
|
|
graph TD
|
|
|
|
|
|
App --> Router
|
|
|
|
|
|
Router --> ProjectPage
|
|
|
|
|
|
Router --> GeneratePage
|
|
|
|
|
|
Router --> ResultPage
|
|
|
|
|
|
|
|
|
|
|
|
ProjectPage --> StyleSelector["StyleSelector(工程风格编辑)"]
|
|
|
|
|
|
ProjectPage --> TaskList["TaskList(历史任务列表)"]
|
|
|
|
|
|
|
|
|
|
|
|
GeneratePage --> GenerateForm
|
|
|
|
|
|
GenerateForm --> AssetTypePicker["AssetTypePicker"]
|
|
|
|
|
|
GenerateForm --> StyleSelector2["StyleSelector(任务风格覆盖)"]
|
|
|
|
|
|
GenerateForm --> PromptEditor["PromptEditor(三段式预览)"]
|
|
|
|
|
|
GenerateForm --> ParamsForm["ParamsForm(分辨率、帧数)"]
|
|
|
|
|
|
GeneratePage --> ProgressBar["ProgressBar(管线进度)"]
|
|
|
|
|
|
|
|
|
|
|
|
ResultPage --> AssetPreview["AssetPreview(素材预览)"]
|
|
|
|
|
|
ResultPage --> MetadataPanel["MetadataPanel(元数据)"]
|
|
|
|
|
|
ResultPage --> DownloadButton["DownloadButton"]
|
2026-05-23 18:49:50 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 核心交互流程
|
|
|
|
|
|
|
|
|
|
|
|
### 生成流程
|
|
|
|
|
|
|
2026-05-24 10:22:15 +08:00
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
actor User
|
|
|
|
|
|
participant GP as GeneratePage
|
|
|
|
|
|
participant API as 后端 API
|
|
|
|
|
|
participant WS as WebSocket
|
|
|
|
|
|
|
|
|
|
|
|
User->>GP: 填写 prompt、选择素材类型
|
|
|
|
|
|
User->>GP: StyleSelector 点选任务风格覆盖
|
|
|
|
|
|
GP->>GP: PromptEditor 实时预览三段式提示词
|
|
|
|
|
|
User->>GP: 点击提交
|
|
|
|
|
|
GP->>API: POST /api/v1/generate
|
|
|
|
|
|
API-->>GP: 返回 taskId
|
|
|
|
|
|
GP->>WS: ws://host/api/v1/tasks/:taskId/ws
|
|
|
|
|
|
loop 管线执行中
|
|
|
|
|
|
WS-->>GP: PipelineProgress(stage + progress)
|
|
|
|
|
|
GP->>GP: ProgressBar 更新阶段和进度
|
|
|
|
|
|
end
|
|
|
|
|
|
WS-->>GP: format_adapter completed + assets
|
|
|
|
|
|
GP->>GP: 跳转 ResultPage
|
2026-05-23 18:49:50 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-24 10:22:15 +08:00
|
|
|
|
管线阶段对应 Eino Graph 节点,ProgressBar 展示:
|
2026-05-23 18:49:50 +08:00
|
|
|
|
|
2026-05-24 10:22:15 +08:00
|
|
|
|
| 阶段 | 说明 | ProgressBar 展示 |
|
|
|
|
|
|
|------|------|-----------------|
|
|
|
|
|
|
| `prompt_builder` | 合并风格,生成三段式提示词 | 第 1 步 |
|
|
|
|
|
|
| `asset_generator` | 调用 AI 推理 API 出图 | 第 2 步 |
|
|
|
|
|
|
| `quality_supervisor` | 视觉模型质检 | 第 3 步(可能回退到第 1 步) |
|
|
|
|
|
|
| `format_adapter` | 格式转换、spritesheet 打包 | 第 4 步 |
|
|
|
|
|
|
|
|
|
|
|
|
质检重试时,ProgressBar 显示回退动画和重试次数。
|
2026-05-23 18:49:50 +08:00
|
|
|
|
|
|
|
|
|
|
### 风格编辑流程
|
|
|
|
|
|
|
2026-05-24 10:22:15 +08:00
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
actor User
|
|
|
|
|
|
participant SP as StyleSelector
|
|
|
|
|
|
participant API as 后端 API
|
|
|
|
|
|
|
|
|
|
|
|
User->>SP: 进入 ProjectPage
|
|
|
|
|
|
SP->>API: GET /api/v1/projects/:id/style
|
|
|
|
|
|
API-->>SP: 返回 kvPairs
|
|
|
|
|
|
SP->>SP: 按分类展示风格键值对
|
|
|
|
|
|
User->>SP: 点选/修改风格
|
|
|
|
|
|
SP->>SP: 实时展示当前配置
|
|
|
|
|
|
User->>SP: 点击保存
|
|
|
|
|
|
SP->>API: PUT /api/v1/projects/:id/style
|
|
|
|
|
|
API-->>SP: 保存成功
|
2026-05-23 18:49:50 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 状态管理
|
|
|
|
|
|
|
|
|
|
|
|
### zustand stores
|
|
|
|
|
|
|
2026-05-24 10:22:15 +08:00
|
|
|
|
**project store** — 当前工程
|
2026-05-23 18:49:50 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
interface ProjectStore {
|
|
|
|
|
|
projectId: string;
|
2026-05-24 10:22:15 +08:00
|
|
|
|
name: string;
|
2026-05-23 18:49:50 +08:00
|
|
|
|
style: Record<string, string>; // kvPairs
|
|
|
|
|
|
loading: boolean;
|
2026-05-24 10:22:15 +08:00
|
|
|
|
loadProject: (projectId: string) => Promise<void>;
|
2026-05-23 18:49:50 +08:00
|
|
|
|
loadStyle: (projectId: string) => Promise<void>;
|
|
|
|
|
|
updateStyle: (kvPairs: Record<string, string>) => void;
|
|
|
|
|
|
saveStyle: () => Promise<void>;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-24 10:22:15 +08:00
|
|
|
|
**task store** — 任务草稿(对应后端 `PipelineInput`)
|
2026-05-23 18:49:50 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
interface TaskStore {
|
|
|
|
|
|
prompt: string;
|
|
|
|
|
|
assetType: 'sprite' | 'background' | 'ui' | 'animation';
|
|
|
|
|
|
taskStyle: Record<string, string>; // 仅覆盖的键
|
|
|
|
|
|
params: {
|
|
|
|
|
|
resolution: number;
|
|
|
|
|
|
frames?: { directions: number; framesPerDirection: number };
|
2026-05-24 10:22:15 +08:00
|
|
|
|
format: 'spritesheet' | 'individual';
|
2026-05-23 18:49:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
setPrompt: (text: string) => void;
|
2026-05-24 10:22:15 +08:00
|
|
|
|
setAssetType: (type: TaskStore['assetType']) => void;
|
2026-05-23 18:49:50 +08:00
|
|
|
|
toggleTaskStyle: (key: string, value: string) => void;
|
|
|
|
|
|
setParams: (params: Partial<TaskStore['params']>) => void;
|
|
|
|
|
|
reset: () => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**generation store** — 生成状态
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
interface GenerationStore {
|
2026-05-24 10:22:15 +08:00
|
|
|
|
taskId: string | null;
|
|
|
|
|
|
stage: PipelineStage | null;
|
2026-05-23 18:49:50 +08:00
|
|
|
|
progress: number;
|
|
|
|
|
|
status: 'idle' | 'submitting' | 'running' | 'completed' | 'failed';
|
2026-05-24 10:22:15 +08:00
|
|
|
|
retryCount: number;
|
|
|
|
|
|
rejectReason: string | null;
|
2026-05-23 18:49:50 +08:00
|
|
|
|
assets: Asset[];
|
|
|
|
|
|
error: string | null;
|
|
|
|
|
|
submit: (projectId: string, task: TaskStore) => Promise<void>;
|
|
|
|
|
|
reset: () => void;
|
|
|
|
|
|
}
|
2026-05-24 10:22:15 +08:00
|
|
|
|
|
|
|
|
|
|
type PipelineStage =
|
|
|
|
|
|
| 'prompt_builder'
|
|
|
|
|
|
| 'asset_generator'
|
|
|
|
|
|
| 'quality_supervisor'
|
|
|
|
|
|
| 'format_adapter';
|
2026-05-23 18:49:50 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## API 封装
|
|
|
|
|
|
|
|
|
|
|
|
`api/client.ts` 统一封装:
|
|
|
|
|
|
|
|
|
|
|
|
- baseURL 从环境变量读取,开发模式默认 `/api/v1`
|
|
|
|
|
|
- 所有响应按 `{ code, message, data }` 解包,`code !== 0` 时抛错
|
|
|
|
|
|
- 统一 401/403/500 错误处理
|
2026-05-24 10:22:15 +08:00
|
|
|
|
- WebSocket 连接封装为 `createTaskSocket(taskId)` 返回可订阅对象
|
|
|
|
|
|
|
|
|
|
|
|
### API 类型定义(api/types.ts)
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 请求
|
|
|
|
|
|
interface CreateProjectRequest {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface GenerateRequest {
|
|
|
|
|
|
projectId: string;
|
|
|
|
|
|
prompt: string;
|
|
|
|
|
|
assetType: 'sprite' | 'background' | 'ui' | 'animation';
|
|
|
|
|
|
taskStyle?: Record<string, string>;
|
|
|
|
|
|
params?: {
|
|
|
|
|
|
resolution?: number;
|
|
|
|
|
|
frames?: { directions?: number; framesPerDirection?: number };
|
|
|
|
|
|
format?: 'spritesheet' | 'individual';
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface UpdateStyleRequest {
|
|
|
|
|
|
kvPairs: Record<string, string>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 响应
|
|
|
|
|
|
interface Project {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
style: { kvPairs: Record<string, string> };
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface Task {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
projectId: string;
|
|
|
|
|
|
prompt: string;
|
|
|
|
|
|
assetType: string;
|
|
|
|
|
|
status: 'pending' | 'running' | 'completed' | 'failed';
|
|
|
|
|
|
stage?: PipelineStage;
|
|
|
|
|
|
progress?: number;
|
|
|
|
|
|
retryCount?: number;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface Asset {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
url: string;
|
|
|
|
|
|
format: string;
|
|
|
|
|
|
width: number;
|
|
|
|
|
|
height: number;
|
|
|
|
|
|
metadata: {
|
|
|
|
|
|
frameWidth?: number;
|
|
|
|
|
|
frameHeight?: number;
|
|
|
|
|
|
frameCount?: number;
|
|
|
|
|
|
directions?: number;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// WebSocket 消息
|
|
|
|
|
|
interface PipelineProgress {
|
|
|
|
|
|
stage: PipelineStage;
|
|
|
|
|
|
status: 'running' | 'completed' | 'failed';
|
|
|
|
|
|
progress: number;
|
|
|
|
|
|
message?: string;
|
|
|
|
|
|
retryCount?: number;
|
|
|
|
|
|
rejectReason?: string;
|
|
|
|
|
|
result?: { assets: Asset[] };
|
|
|
|
|
|
error?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
2026-05-23 18:49:50 +08:00
|
|
|
|
|
|
|
|
|
|
## 预设风格键分类
|
|
|
|
|
|
|
|
|
|
|
|
前端 StyleSelector 以分类标签组织,用户点选:
|
|
|
|
|
|
|
|
|
|
|
|
| 分类 | 键名 | 可选值 |
|
|
|
|
|
|
|------|------|--------|
|
|
|
|
|
|
| 美术风格 | `artStyle` | pixel, cartoon, hand-drawn, vector, flat |
|
|
|
|
|
|
| 色调 | `palette` | warm, cool, neutral, vibrant, muted, monochrome |
|
|
|
|
|
|
| 线条 | `lineWeight` | none, thin, medium, thick |
|
|
|
|
|
|
| 场景 | `scene` | forest, dungeon, city, space, underwater, desert |
|
|
|
|
|
|
| 光照 | `lighting` | bright, dim, dramatic, ambient, neon |
|
|
|
|
|
|
| 情绪 | `mood` | cheerful, dark, mysterious, epic, calm |
|
|
|
|
|
|
|
|
|
|
|
|
StyleSelector 两种使用场景:
|
|
|
|
|
|
1. **工程风格**(ProjectPage):全量编辑,保存到后端
|
|
|
|
|
|
2. **任务覆盖**(GeneratePage):基于工程风格展示,高亮已覆盖的键,仅记录差异
|