Files
gen2d/docs/frontend.md
T
wonder cbcda82515 docs: 适配单机场景,简化存储与认证方案
- 数据库:MySQL → SQLite,去掉连接池配置和 golang-migrate
- 文件存储:OSS 对象存储 → 本地文件系统,统一 Gin 静态文件服务
- ID 生成:Sonyflake → 前缀+随机字符串
- 认证:Bearer Token → httpOnly Cookie,WebSocket 同步改为 Cookie 认证
- 质检重试:PromptBuilder 重试时注入 RejectReason 改进提示词
- 合并 multi-agent-pipeline.md 到 backend.md,消除文档重复
- 移除缓存管理 API(暴露内部实现细节)
2026-05-24 10:56:40 +08:00

312 lines
9.9 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.
# 前端工程
## 技术栈
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 模式 |
| 实时通信 | 原生 WebSocket | 连接后端 `/api/v1/tasks/:taskId/ws`(Cookie 认证) |
| HTTP 请求 | fetch + 封装层 | 统一错误处理、响应解包 |
## 目录结构
```
frontend/src/
├── main.tsx # 入口
├── App.tsx # 根组件 + 路由配置
├── api/ # API 封装层
│ ├── client.ts # fetch 封装:baseURL、统一错误处理、响应解包
│ ├── project.ts # 工程 CRUD + 风格 GET/PUT
│ ├── generate.ts # POST /generate、GET /tasks/:taskId、GET /tasks/:taskId/assets
│ └── types.ts # API 请求/响应类型定义(Task、Asset、Style、PipelineProgress 等)
├── stores/ # zustand stores
│ ├── project.ts # 当前工程(id、风格、任务列表)
│ ├── task.ts # 当前任务草稿(用户文本、素材类型、任务风格覆盖、技术参数)
│ └── generation.ts # 生成状态(taskId、进度、阶段、结果、错误)
├── pages/ # 页面级组件
│ ├── ProjectPage.tsx # 工程首页:工程风格配置 + 任务列表
│ ├── GeneratePage.tsx # 生成页:提示词构建 + 提交
│ └── ResultPage.tsx # 结果页:素材预览 + 下载 + 元数据
├── components/ # 可复用组件
│ ├── StyleSelector.tsx # 风格选择器
│ ├── PromptEditor.tsx # 三段式提示词编辑器
│ ├── GenerateForm.tsx # 生成表单
│ ├── ProgressBar.tsx # 管线进度条(显示当前阶段 + 重试状态)
│ └── 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 | 任务结果页,预览素材、下载 |
## 组件树
```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"]
```
## 核心交互流程
### 生成流程
```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(Cookie 自动携带)
loop 管线执行中
WS-->>GP: PipelineProgress(stage + progress)
GP->>GP: ProgressBar 更新阶段和进度
end
WS-->>GP: format_adapter completed + assets
GP->>GP: 跳转 ResultPage
```
管线阶段对应 Eino Graph 节点,ProgressBar 展示:
| 阶段 | 说明 | ProgressBar 展示 |
|------|------|-----------------|
| `prompt_builder` | 合并风格,生成三段式提示词 | 第 1 步 |
| `asset_generator` | 调用 AI 推理 API 出图 | 第 2 步 |
| `quality_supervisor` | 视觉模型质检 | 第 3 步(可能回退到第 1 步) |
| `format_adapter` | 格式转换、spritesheet 打包 | 第 4 步 |
质检重试时,ProgressBar 显示回退动画和重试次数。
### 风格编辑流程
```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: 保存成功
```
## 错误处理
### API 请求错误
- `api/client.ts` 统一拦截 `code !== 0` 的响应,抛出业务异常
- 401:跳转到登录页(Token 已过期或无效,Cookie 会被服务端清除)
- 403:提示无权访问,不自动跳转
- 429:提示请求过于频繁,稍后重试
- 500:展示通用错误提示
### WebSocket 断连
- 连接断开后自动重连(指数退避,最大间隔 10 秒)
- 重连失败超过 3 次后,降级为轮询模式(`GET /api/v1/tasks/:taskId`,间隔 2-3 秒)
- 连接恢复后自动切回 WebSocket
### 加载状态
- 页面级加载(如 ProjectPage 进入时):展示骨架屏或 Loading 指示器
- 操作级提交(如保存风格、提交生成):按钮置为 loading 态,防止重复提交
## 状态管理
### zustand stores
**project store** — 当前工程
```typescript
interface ProjectStore {
projectId: string;
name: string;
style: Record<string, string>; // kvPairs
loading: boolean;
loadProject: (projectId: string) => Promise<void>;
loadStyle: (projectId: string) => Promise<void>;
updateStyle: (kvPairs: Record<string, string>) => void;
saveStyle: () => Promise<void>;
}
```
**task store** — 任务草稿(对应后端 `PipelineInput`)
```typescript
interface TaskStore {
prompt: string;
assetType: 'sprite' | 'background' | 'ui' | 'animation';
taskStyle: Record<string, string>; // 仅覆盖的键
params: {
resolution: number;
frames?: { directions: number; framesPerDirection: number };
format: 'spritesheet' | 'individual';
};
setPrompt: (text: string) => void;
setAssetType: (type: TaskStore['assetType']) => void;
toggleTaskStyle: (key: string, value: string) => void;
setParams: (params: Partial<TaskStore['params']>) => void;
reset: () => void;
}
```
**generation store** — 生成状态
```typescript
interface GenerationStore {
taskId: string | null;
stage: PipelineStage | null;
progress: number;
status: 'idle' | 'submitting' | 'running' | 'completed' | 'failed';
retryCount: number;
rejectReason: string | null;
assets: Asset[];
error: string | null;
submit: (projectId: string, task: TaskStore) => Promise<void>;
reset: () => void;
}
type PipelineStage =
| 'prompt_builder'
| 'asset_generator'
| 'quality_supervisor'
| 'format_adapter';
```
## API 封装
`api/client.ts` 统一封装:
- baseURL 从环境变量读取,开发模式默认 `/api/v1`
- 所有 fetch 请求设置 `credentials: 'include'`,自动携带 Cookie
- 所有响应按 `{ code, message, data }` 解包,`code !== 0` 时抛错
- 统一 401/403/500 错误处理
- WebSocket 连接封装为 `createTaskSocket(taskId)` 返回可订阅对象(浏览器自动携带 Cookie)
### 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;
error?: string | null;
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;
}
```
## 预设风格键分类
前端 StyleSelector 以分类标签组织,完整键值表见 [预设风格键](style-keys.md)。
StyleSelector 两种使用场景:
1. **工程风格**(ProjectPage):全量编辑,保存到后端
2. **任务覆盖**(GeneratePage):基于工程风格展示,高亮已覆盖的键,仅记录差异