docs: 后端采用 Eino 框架编排管线,完善前后端文档对齐
- 后端管线从手写编排改为 Eino compose.Graph,含质检重试分支 - 移除 CORS 中间件 - api.md 补充工程管理接口、任务状态机、管线阶段、WebSocket 消息示例 - frontend.md 组件树和交互流程改用 mermaid 图,stores 对齐 PipelineInput/Output - CLAUDE.md 修复死链,技术栈补充 Eino
This commit is contained in:
@@ -8,11 +8,11 @@ gen2d — AI 驱动的 2D 游戏素材生成工具。用户通过文本提示词
|
||||
|
||||
## Directory Structure
|
||||
|
||||
详见 [docs/architecture.md](docs/architecture.md) 中的后端分层结构与前端组件树。
|
||||
详见 [docs/_index.md](docs/_index.md) 中的后端分层结构与前端组件树。
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Backend (Go + Gin)
|
||||
### Backend (Go + Gin + Eino)
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
@@ -62,7 +62,7 @@ npm run typecheck
|
||||
|
||||
## Architecture
|
||||
|
||||
详见 [docs/architecture.md](docs/architecture.md),涵盖多智能体管线、后端分层、前端组件树、API 设计、素材生成约束等。
|
||||
详见 [docs/_index.md](docs/_index.md),涵盖多智能体管线、后端分层、前端组件树、API 设计、素材生成约束等。
|
||||
|
||||
## Conventions
|
||||
|
||||
|
||||
+3
-2
@@ -4,13 +4,14 @@ gen2d — AI 驱动的 2D 游戏素材生成工具。通过文本提示词生成
|
||||
|
||||
## 技术栈
|
||||
|
||||
Go + Gin / Vite + React + TypeScript + zustand / 可替换 AI 推理模型
|
||||
Go + Gin + Eino / Vite + React + TypeScript + zustand / 可替换 AI 推理模型
|
||||
|
||||
## 文档索引
|
||||
|
||||
- [多智能体生成管线](multi-agent-pipeline.md) — PromptBuilder → AssetGenerator → QualitySupervisor → FormatAdapter 三阶段流水线
|
||||
- [多智能体生成管线](multi-agent-pipeline.md) — PromptBuilder → AssetGenerator → QualitySupervisor → FormatAdapter 四阶段流水线
|
||||
- [后端工程](backend.md) — 分层结构、目录组织、分层原则
|
||||
- [前端工程](frontend.md) — 组件树、状态管理、路由、WebSocket 通信
|
||||
- [异步任务](async-tasks.md) — 任务队列、状态机、并发控制、失败重试
|
||||
- [数据存储](database.md) — 数据库选型、表结构、素材文件存储
|
||||
- [API 设计](api.md) — 接口列表、请求/响应示例、实现状态
|
||||
- [预设风格键](style-keys.md) — 美术风格、色调、线条等风格键分类与可选值
|
||||
|
||||
+214
-18
@@ -19,35 +19,64 @@
|
||||
|------|------|------|------|
|
||||
| [x] | GET | `/api/v1/health` | 健康检查 |
|
||||
|
||||
## 素材生成
|
||||
## 工程管理
|
||||
|
||||
| 状态 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| [ ] | POST | `/api/v1/generate` | 提交生成任务,返回 jobId |
|
||||
| [ ] | GET | `/api/v1/generate/:jobId` | 查询任务状态与进度 |
|
||||
| [ ] | GET | `/api/v1/generate/:jobId/result` | 获取生成结果(素材 URL + 元数据) |
|
||||
| [ ] | WS | `/api/v1/generate/:jobId/ws` | WebSocket 实时进度推送 |
|
||||
| [ ] | POST | `/api/v1/projects` | 创建工程 |
|
||||
| [ ] | GET | `/api/v1/projects/:projectId` | 获取工程信息 |
|
||||
| [ ] | GET | `/api/v1/projects/:projectId/tasks` | 获取工程下的任务列表 |
|
||||
|
||||
请求体示例 (POST /api/v1/generate):
|
||||
### POST /api/v1/projects
|
||||
|
||||
```json
|
||||
{
|
||||
"prompt": "一个拿剑的小人",
|
||||
"assetType": "sprite",
|
||||
"taskStyle": {
|
||||
"scene": "dungeon",
|
||||
"mood": "dark"
|
||||
},
|
||||
"params": {
|
||||
"resolution": 64,
|
||||
"frames": { "directions": 8, "framesPerDirection": 4 },
|
||||
"format": "spritesheet"
|
||||
"name": "我的像素游戏"
|
||||
}
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"id": "proj_abc123",
|
||||
"name": "我的像素游戏",
|
||||
"style": { "kvPairs": {} },
|
||||
"createdAt": "2026-05-24T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `prompt`:用户原始文本,后端 PromptBuilder 负责三段式重写
|
||||
- `taskStyle`:可选,任务级别风格覆盖(同名键覆盖工程风格)
|
||||
### GET /api/v1/projects/:projectId/tasks
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `page` | int | 页码,默认 1 |
|
||||
| `pageSize` | int | 每页条数,默认 20 |
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"total": 42,
|
||||
"tasks": [
|
||||
{
|
||||
"id": "task_xyz789",
|
||||
"prompt": "一个拿剑的小人",
|
||||
"assetType": "sprite",
|
||||
"status": "completed",
|
||||
"createdAt": "2026-05-24T10:05:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 工程风格
|
||||
|
||||
@@ -69,6 +98,173 @@
|
||||
}
|
||||
```
|
||||
|
||||
## 素材生成
|
||||
|
||||
| 状态 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| [ ] | POST | `/api/v1/generate` | 提交生成任务,返回 taskId |
|
||||
| [ ] | GET | `/api/v1/tasks/:taskId` | 查询任务状态与进度 |
|
||||
| [ ] | GET | `/api/v1/tasks/:taskId/assets` | 获取生成结果(素材列表 + 元数据) |
|
||||
| [ ] | WS | `/api/v1/tasks/:taskId/ws` | WebSocket 实时进度推送 |
|
||||
|
||||
### POST /api/v1/generate
|
||||
|
||||
请求体对应后端 `PipelineInput`:
|
||||
|
||||
```json
|
||||
{
|
||||
"projectId": "proj_abc123",
|
||||
"prompt": "一个拿剑的小人",
|
||||
"assetType": "sprite",
|
||||
"taskStyle": {
|
||||
"scene": "dungeon",
|
||||
"mood": "dark"
|
||||
},
|
||||
"params": {
|
||||
"resolution": 64,
|
||||
"frames": { "directions": 8, "framesPerDirection": 4 },
|
||||
"format": "spritesheet"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `projectId` | string | 是 | 工程 ID,用于获取工程风格 |
|
||||
| `prompt` | string | 是 | 用户原始文本,后端 PromptBuilder 负责三段式重写 |
|
||||
| `assetType` | string | 是 | 素材类型:`sprite` / `background` / `ui` / `animation` |
|
||||
| `taskStyle` | object | 否 | 任务级别风格覆盖(同名键覆盖工程风格) |
|
||||
| `params.resolution` | int | 否 | 分辨率,默认 64 |
|
||||
| `params.frames` | object | 否 | 帧参数(仅 sprite/animation) |
|
||||
| `params.frames.directions` | int | 否 | 方向数,默认 4 |
|
||||
| `params.frames.framesPerDirection` | int | 否 | 每方向帧数,默认 4 |
|
||||
| `params.format` | string | 否 | 输出格式:`spritesheet` / `individual`,默认 `spritesheet` |
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"taskId": "task_xyz789",
|
||||
"status": "pending"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/v1/tasks/:taskId
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"taskId": "task_xyz789",
|
||||
"projectId": "proj_abc123",
|
||||
"prompt": "一个拿剑的小人",
|
||||
"assetType": "sprite",
|
||||
"status": "running",
|
||||
"stage": "asset_generator",
|
||||
"progress": 45,
|
||||
"retryCount": 0,
|
||||
"createdAt": "2026-05-24T10:05:00Z",
|
||||
"updatedAt": "2026-05-24T10:05:30Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
任务状态机:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> pending
|
||||
pending --> running : 管线开始执行
|
||||
running --> completed : 四阶段全部通过
|
||||
running --> failed : 节点执行失败 / 超过重试次数
|
||||
completed --> [*]
|
||||
failed --> [*]
|
||||
```
|
||||
|
||||
管线阶段(对应 Eino Graph 节点):
|
||||
|
||||
| 阶段 | 说明 |
|
||||
|------|------|
|
||||
| `prompt_builder` | 合并风格,生成三段式提示词 |
|
||||
| `asset_generator` | 调用 AI 推理 API 出图 |
|
||||
| `quality_supervisor` | 视觉模型质检(可能触发重试回到 prompt_builder) |
|
||||
| `format_adapter` | 格式转换、spritesheet 打包 |
|
||||
|
||||
### GET /api/v1/tasks/:taskId/assets
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "ok",
|
||||
"data": {
|
||||
"assets": [
|
||||
{
|
||||
"id": "asset_001",
|
||||
"url": "/files/tasks/task_xyz789/spritesheet.png",
|
||||
"format": "png",
|
||||
"width": 256,
|
||||
"height": 64,
|
||||
"metadata": {
|
||||
"frameWidth": 64,
|
||||
"frameHeight": 64,
|
||||
"frameCount": 4,
|
||||
"directions": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### WebSocket 消息格式
|
||||
|
||||
连接路径:`ws://host/api/v1/tasks/:taskId/ws`
|
||||
|
||||
```typescript
|
||||
interface PipelineProgress {
|
||||
stage: 'prompt_builder' | 'asset_generator' | 'quality_supervisor' | 'format_adapter';
|
||||
status: 'running' | 'completed' | 'failed';
|
||||
progress: number; // 0-100
|
||||
message?: string; // 阶段描述
|
||||
retryCount?: number; // 质检重试次数(仅 quality_supervisor 阶段)
|
||||
rejectReason?: string; // 质检不通过原因(仅 quality_supervisor fail 时)
|
||||
result?: { // 仅在 format_adapter completed 时返回
|
||||
assets: Asset[];
|
||||
};
|
||||
error?: string; // 仅在 failed 时返回
|
||||
}
|
||||
```
|
||||
|
||||
消息示例(正常流程):
|
||||
|
||||
```json
|
||||
{"stage":"prompt_builder","status":"running","progress":0}
|
||||
{"stage":"prompt_builder","status":"completed","progress":100}
|
||||
{"stage":"asset_generator","status":"running","progress":0}
|
||||
{"stage":"asset_generator","status":"completed","progress":100}
|
||||
{"stage":"quality_supervisor","status":"completed","progress":100}
|
||||
{"stage":"format_adapter","status":"running","progress":0}
|
||||
{"stage":"format_adapter","status":"completed","progress":100,"result":{"assets":[...]}}
|
||||
```
|
||||
|
||||
消息示例(质检重试):
|
||||
|
||||
```json
|
||||
{"stage":"quality_supervisor","status":"running","progress":0}
|
||||
{"stage":"quality_supervisor","status":"failed","progress":100,"retryCount":1,"rejectReason":"风格不一致"}
|
||||
{"stage":"prompt_builder","status":"running","progress":0}
|
||||
{"stage":"asset_generator","status":"running","progress":0}
|
||||
{"stage":"quality_supervisor","status":"completed","progress":100}
|
||||
{"stage":"format_adapter","status":"completed","progress":100,"result":{"assets":[...]}}
|
||||
```
|
||||
|
||||
## 缓存管理
|
||||
|
||||
| 状态 | 方法 | 路径 | 说明 |
|
||||
|
||||
+160
-9
@@ -1,5 +1,11 @@
|
||||
# 后端工程
|
||||
|
||||
## 技术选型
|
||||
|
||||
- **HTTP 框架**:Gin
|
||||
- **管线编排**:[Eino](https://github.com/cloudwego/eino) `compose.Graph` — 四阶段管线,含质检→重生成分支
|
||||
- **AI 组件**:Eino 的 model / tool 组件,统一接口便于替换模型提供商
|
||||
|
||||
## 分层结构
|
||||
|
||||
```
|
||||
@@ -9,15 +15,16 @@ backend/internal/
|
||||
│ ├── project_style.go # 工程风格 CRUD
|
||||
│ └── health.go # [已有] 健康检查
|
||||
├── service/ # 业务逻辑层
|
||||
│ ├── pipeline.go # 核心管线:接收请求 → PromptBuilder → AssetGenerator → QualitySupervisor → FormatAdapter
|
||||
│ ├── inference.go # AI 推理 API 调用封装(可替换模型提供商)
|
||||
│ ├── pipeline.go # Eino compose.Graph 编排:PromptBuilder → AssetGenerator → QualitySupervisor → FormatAdapter
|
||||
│ ├── nodes.go # 管线四个节点的实现(每个节点单一职责)
|
||||
│ ├── types.go # 管线输入/状态/输出的显式结构体定义
|
||||
│ ├── inference.go # AI 推理 API 调用封装(Eino model 组件)
|
||||
│ └── project_style.go # 工程风格管理 & 风格合并逻辑
|
||||
├── model/ # 数据模型 / DTO
|
||||
│ ├── response.go # [已有] 统一响应
|
||||
│ ├── task.go # 生成任务 & 素材
|
||||
│ └── style.go # 工程风格 & 任务风格覆盖
|
||||
├── middleware/ # 中间件
|
||||
│ ├── cors.go
|
||||
│ ├── logger.go
|
||||
│ └── ratelimit.go
|
||||
├── config/ # [已有] 配置
|
||||
@@ -31,9 +38,152 @@ backend/internal/
|
||||
## 分层原则
|
||||
|
||||
- **handler**:只做参数绑定、校验、调用 service、返回响应。一个 handler 对应一组 API 路由。
|
||||
- **service**:承载所有业务逻辑。`pipeline.go` 是唯一编排入口,不再拆分成 orchestrator/preprocess/generator/postprocess 四个文件——这些是同一根管线的顺序步骤,拆开反而增加耦合面。
|
||||
- **service**:承载所有业务逻辑。`pipeline.go` 通过 Eino `compose.Graph` 编排四个节点;`nodes.go` 实现各节点逻辑;`types.go` 定义显式状态结构体。
|
||||
- **model**:纯数据结构,不含业务逻辑。
|
||||
|
||||
## 管线设计(Eino compose.Graph)
|
||||
|
||||
四阶段管线,含质检不通过时的重生成分支:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
START --> PromptBuilder --> AssetGenerator --> QualitySupervisor
|
||||
QualitySupervisor -- pass --> FormatAdapter --> END
|
||||
QualitySupervisor -- fail --> PromptBuilder
|
||||
```
|
||||
|
||||
### 类型定义(types.go)
|
||||
|
||||
```go
|
||||
// PipelineInput 管线入口输入
|
||||
type PipelineInput struct {
|
||||
Prompt string // 用户原始文本
|
||||
AssetType string // 素材类型:sprite / background / ui / animation
|
||||
ProjectStyle map[string]string // 工程风格键值对
|
||||
TaskStyle map[string]string // 任务风格覆盖
|
||||
Params AssetParams // 技术参数(分辨率、帧数、格式等)
|
||||
}
|
||||
|
||||
// PipelineState Graph 全局状态,通过 WithGenLocalState 注入
|
||||
type PipelineState struct {
|
||||
Input PipelineInput
|
||||
FinalPrompt string // PromptBuilder 输出的三段式提示词
|
||||
RawImages []GeneratedImage // AssetGenerator 输出的原始图片
|
||||
PassQuality bool // QualitySupervisor 质检结果
|
||||
RejectReason string // 质检不通过原因
|
||||
RetryCount int // 重试次数
|
||||
}
|
||||
|
||||
// PipelineOutput 管线最终输出
|
||||
type PipelineOutput struct {
|
||||
Assets []Asset // 生成结果素材列表
|
||||
Metadata AssetMetadata // 元数据(分辨率、帧数、格式)
|
||||
}
|
||||
```
|
||||
|
||||
### 编排入口(pipeline.go)
|
||||
|
||||
```go
|
||||
const (
|
||||
nodePromptBuilder = "prompt_builder"
|
||||
nodeAssetGenerator = "asset_generator"
|
||||
nodeQualitySupervisor = "quality_supervisor"
|
||||
nodeFormatAdapter = "format_adapter"
|
||||
)
|
||||
|
||||
func NewGenerateGraph() (*compose.Graph[PipelineInput, PipelineOutput], error) {
|
||||
g := compose.NewGraph[PipelineInput, PipelineOutput](
|
||||
compose.WithGenLocalState(func(ctx context.Context) *PipelineState {
|
||||
return &PipelineState{}
|
||||
}),
|
||||
)
|
||||
|
||||
// 添加节点
|
||||
_ = g.AddLambdaNode(nodePromptBuilder, promptBuilderNode,
|
||||
compose.WithStatePostHandler(promptBuilderPostHandler))
|
||||
_ = g.AddLambdaNode(nodeAssetGenerator, assetGeneratorNode,
|
||||
compose.WithStatePostHandler(assetGeneratorPostHandler))
|
||||
_ = g.AddLambdaNode(nodeQualitySupervisor, qualitySupervisorNode,
|
||||
compose.WithStatePostHandler(qualitySupervisorPostHandler))
|
||||
_ = g.AddLambdaNode(nodeFormatAdapter, formatAdapterNode)
|
||||
|
||||
// 连线:正常路径
|
||||
_ = g.AddEdge(compose.START, nodePromptBuilder)
|
||||
_ = g.AddEdge(nodePromptBuilder, nodeAssetGenerator)
|
||||
_ = g.AddEdge(nodeAssetGenerator, nodeQualitySupervisor)
|
||||
_ = g.AddEdge(nodeFormatAdapter, compose.END)
|
||||
|
||||
// 连线:质检分支
|
||||
_ = g.AddBranch(nodeQualitySupervisor, compose.NewGraphBranch(
|
||||
func(ctx context.Context, state *PipelineState) (string, error) {
|
||||
if state.PassQuality {
|
||||
return nodeFormatAdapter, nil
|
||||
}
|
||||
if state.RetryCount >= 3 {
|
||||
return nodeFormatAdapter, nil // 超过重试次数,降级输出
|
||||
}
|
||||
return nodePromptBuilder, nil // 重生成
|
||||
},
|
||||
map[string]bool{nodePromptBuilder: true, nodeFormatAdapter: true},
|
||||
))
|
||||
|
||||
return g, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 节点实现(nodes.go)
|
||||
|
||||
每个节点是 Graph 中的 Lambda,通过 `StatePreHandler` / `StatePostHandler` 读写全局状态:
|
||||
|
||||
| 节点 | Lambda 输入→输出 | State 交互 | 职责 |
|
||||
|------|-----------------|-----------|------|
|
||||
| PromptBuilder | `PipelineInput → string` | PostHandler 写入 FinalPrompt | 合并风格 → 生成三段式提示词 |
|
||||
| AssetGenerator | `string → []GeneratedImage` | PostHandler 写入 RawImages | 调用 AI 推理 API 出图 |
|
||||
| QualitySupervisor | `[]GeneratedImage → bool` | PostHandler 写入 PassQuality + RejectReason + RetryCount | 视觉模型质检 |
|
||||
| FormatAdapter | `[]GeneratedImage → PipelineOutput` | 无 | 格式转换、spritesheet 打包 |
|
||||
|
||||
```go
|
||||
// PromptBuilder 节点:接收输入,输出提示词
|
||||
var promptBuilderNode = compose.InvokableLambda(func(ctx context.Context, in PipelineInput) (string, error) {
|
||||
// 合并 projectStyle + taskStyle,生成三段式提示词
|
||||
return buildPrompt(in), nil
|
||||
})
|
||||
|
||||
// StatePostHandler:将提示词写入全局状态
|
||||
func promptBuilderPostHandler(ctx context.Context, out string, state *PipelineState) (string, error) {
|
||||
state.FinalPrompt = out
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// QualitySupervisor StatePostHandler:写入质检结果和重试计数
|
||||
func qualitySupervisorPostHandler(ctx context.Context, out bool, state *PipelineState) (bool, error) {
|
||||
state.PassQuality = out
|
||||
if !out {
|
||||
state.RetryCount++
|
||||
state.RejectReason = "风格不一致" // 实际由视觉模型返回
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 运行入口
|
||||
|
||||
```go
|
||||
func RunPipeline(ctx context.Context, in PipelineInput) (*PipelineOutput, error) {
|
||||
g, err := NewGenerateGraph()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := g.Compile(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.Invoke(ctx, in)
|
||||
}
|
||||
```
|
||||
|
||||
## 关键实体
|
||||
|
||||
| 实体 | 说明 | 关系 |
|
||||
@@ -44,10 +194,11 @@ backend/internal/
|
||||
| Prompt | PromptBuilder 输出的三段式提示词(主题+约束+内容),管线中间产物,不持久化 | 由 Task 生成 |
|
||||
| Asset | 生成结果素材,关联到任务,包含 URL、元数据(分辨率、帧数、格式) | 属于 Task |
|
||||
|
||||
```
|
||||
Project 1──1 ProjectStyle
|
||||
Project 1──N Task
|
||||
Task 1──N Asset
|
||||
```mermaid
|
||||
erDiagram
|
||||
Project ||--|| ProjectStyle : has
|
||||
Project ||--|{ Task : has
|
||||
Task ||--|{ Asset : has
|
||||
```
|
||||
|
||||
## 风格模型
|
||||
@@ -82,4 +233,4 @@ type TaskStyle struct {
|
||||
finalStyle = merge(projectStyle.KVPairs, taskStyle.KVPairs)
|
||||
```
|
||||
|
||||
任务同名键覆盖工程风格,由 PromptBuilder 在生成提示词时执行合并。
|
||||
任务同名键覆盖工程风格,由 PromptBuilder 节点在生成提示词时执行合并。
|
||||
|
||||
+160
-71
@@ -10,7 +10,7 @@ Vite 6 + React 18 + TypeScript + zustand + react-router-dom
|
||||
| UI 框架 | React 18 | 函数组件 + Hooks |
|
||||
| 状态管理 | zustand | 轻量,支持 devtools 中间件 |
|
||||
| 路由 | react-router-dom v6 | SPA 模式 |
|
||||
| 实时通信 | 原生 WebSocket | 连接后端 `/api/v1/generate/:jobId/ws` |
|
||||
| 实时通信 | 原生 WebSocket | 连接后端 `/api/v1/tasks/:taskId/ws` |
|
||||
| HTTP 请求 | fetch + 封装层 | 统一错误处理、响应解包 |
|
||||
|
||||
## 目录结构
|
||||
@@ -21,13 +21,13 @@ frontend/src/
|
||||
├── App.tsx # 根组件 + 路由配置
|
||||
├── api/ # API 封装层
|
||||
│ ├── client.ts # fetch 封装:baseURL、统一错误处理、响应解包
|
||||
│ ├── generate.ts # POST /generate、GET /generate/:jobId、GET /generate/:jobId/result
|
||||
│ ├── project.ts # GET/PUT /projects/:projectId/style
|
||||
│ └── types.ts # API 请求/响应类型定义(Task、Asset、Style、JobStatus 等)
|
||||
│ ├── 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 # 工程风格(从 API 加载 + 本地编辑 + 持久化回写)
|
||||
│ ├── project.ts # 当前工程(id、风格、任务列表)
|
||||
│ ├── task.ts # 当前任务草稿(用户文本、素材类型、任务风格覆盖、技术参数)
|
||||
│ └── generation.ts # 生成状态(jobId、进度、阶段、结果、错误)
|
||||
│ └── generation.ts # 生成状态(taskId、进度、阶段、结果、错误)
|
||||
├── pages/ # 页面级组件
|
||||
│ ├── ProjectPage.tsx # 工程首页:工程风格配置 + 任务列表
|
||||
│ ├── GeneratePage.tsx # 生成页:提示词构建 + 提交
|
||||
@@ -36,7 +36,7 @@ frontend/src/
|
||||
│ ├── StyleSelector.tsx # 风格选择器
|
||||
│ ├── PromptEditor.tsx # 三段式提示词编辑器
|
||||
│ ├── GenerateForm.tsx # 生成表单
|
||||
│ ├── ProgressBar.tsx # 管线进度条(显示当前阶段)
|
||||
│ ├── ProgressBar.tsx # 管线进度条(显示当前阶段 + 重试状态)
|
||||
│ └── AssetPreview.tsx # 素材预览(spritesheet 预览、单帧预览)
|
||||
├── hooks/ # 自定义 Hooks
|
||||
│ ├── useGenerate.ts # 提交生成任务 + WebSocket 订阅进度
|
||||
@@ -58,95 +58,104 @@ frontend/src/
|
||||
|
||||
## 组件树
|
||||
|
||||
```
|
||||
<App>
|
||||
<Router>
|
||||
<ProjectPage>
|
||||
<StyleSelector /> # 工程风格编辑
|
||||
<TaskList /> # 历史任务列表
|
||||
</ProjectPage>
|
||||
<GeneratePage>
|
||||
<GenerateForm>
|
||||
<AssetTypePicker /> # sprite / background / UI / animation
|
||||
<StyleSelector /> # 任务风格覆盖(基于工程风格,高亮差异)
|
||||
<PromptEditor /> # 三段式提示词预览 & 编辑
|
||||
<ParamsForm /> # 分辨率、帧数等技术参数
|
||||
</GenerateForm>
|
||||
<ProgressBar /> # 提交后显示管线进度
|
||||
</GeneratePage>
|
||||
<ResultPage>
|
||||
<AssetPreview /> # 素材预览
|
||||
<MetadataPanel /> # 元数据展示
|
||||
<DownloadButton /> # 下载素材
|
||||
</ResultPage>
|
||||
</Router>
|
||||
</App>
|
||||
```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"]
|
||||
```
|
||||
|
||||
## 核心交互流程
|
||||
|
||||
### 生成流程
|
||||
|
||||
```
|
||||
1. 用户进入 GeneratePage
|
||||
2. 填写文本描述(prompt)
|
||||
3. 选择素材类型(sprite / background / UI / animation)
|
||||
4. StyleSelector 展示工程风格,用户可点选覆盖(任务风格)
|
||||
5. PromptEditor 实时预览三段式提示词:
|
||||
- 【主题】从用户文本提取
|
||||
- 【约束】从风格选择自动生成(含负面提示词)
|
||||
- 【内容】从素材类型 + 技术参数生成
|
||||
6. 用户确认后提交
|
||||
7. 前端 POST /api/v1/generate → 获取 jobId
|
||||
8. 建立 WebSocket 连接 /api/v1/generate/:jobId/ws
|
||||
9. ProgressBar 实时显示管线阶段:PromptBuilder → AssetGenerator → QualitySupervisor → FormatAdapter
|
||||
10. 管线完成后跳转 ResultPage
|
||||
```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
|
||||
```
|
||||
|
||||
### WebSocket 消息格式
|
||||
管线阶段对应 Eino Graph 节点,ProgressBar 展示:
|
||||
|
||||
```typescript
|
||||
interface PipelineProgress {
|
||||
stage: 'prompt_builder' | 'asset_generator' | 'quality_supervisor' | 'format_adapter';
|
||||
status: 'running' | 'completed' | 'failed';
|
||||
progress: number; // 0-100
|
||||
message?: string; // 阶段描述
|
||||
result?: { // 仅在 format_adapter completed 时返回
|
||||
assets: Asset[];
|
||||
};
|
||||
error?: string; // 仅在 failed 时返回
|
||||
}
|
||||
```
|
||||
| 阶段 | 说明 | ProgressBar 展示 |
|
||||
|------|------|-----------------|
|
||||
| `prompt_builder` | 合并风格,生成三段式提示词 | 第 1 步 |
|
||||
| `asset_generator` | 调用 AI 推理 API 出图 | 第 2 步 |
|
||||
| `quality_supervisor` | 视觉模型质检 | 第 3 步(可能回退到第 1 步) |
|
||||
| `format_adapter` | 格式转换、spritesheet 打包 | 第 4 步 |
|
||||
|
||||
质检重试时,ProgressBar 显示回退动画和重试次数。
|
||||
|
||||
### 风格编辑流程
|
||||
|
||||
```
|
||||
1. 用户进入 ProjectPage
|
||||
2. StyleSelector 从 API 加载工程风格(GET /projects/:id/style)
|
||||
3. 用户按分类点选风格键值对(美术风格、色调、线条、场景、光照、情绪)
|
||||
4. 实时展示当前风格配置
|
||||
5. 保存 → PUT /projects/:id/style
|
||||
6. 工程风格持久化到后端,后续生成任务自动继承
|
||||
```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: 保存成功
|
||||
```
|
||||
|
||||
## 状态管理
|
||||
|
||||
### zustand stores
|
||||
|
||||
**project store** — 工程风格
|
||||
**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** — 任务草稿
|
||||
**task store** — 任务草稿(对应后端 `PipelineInput`)
|
||||
|
||||
```typescript
|
||||
interface TaskStore {
|
||||
@@ -156,10 +165,10 @@ interface TaskStore {
|
||||
params: {
|
||||
resolution: number;
|
||||
frames?: { directions: number; framesPerDirection: number };
|
||||
format: string;
|
||||
format: 'spritesheet' | 'individual';
|
||||
};
|
||||
setPrompt: (text: string) => void;
|
||||
setAssetType: (type: string) => void;
|
||||
setAssetType: (type: TaskStore['assetType']) => void;
|
||||
toggleTaskStyle: (key: string, value: string) => void;
|
||||
setParams: (params: Partial<TaskStore['params']>) => void;
|
||||
reset: () => void;
|
||||
@@ -170,15 +179,23 @@ interface TaskStore {
|
||||
|
||||
```typescript
|
||||
interface GenerationStore {
|
||||
jobId: string | null;
|
||||
stage: PipelineProgress['stage'] | null;
|
||||
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 封装
|
||||
@@ -188,7 +205,79 @@ interface GenerationStore {
|
||||
- baseURL 从环境变量读取,开发模式默认 `/api/v1`
|
||||
- 所有响应按 `{ code, message, data }` 解包,`code !== 0` 时抛错
|
||||
- 统一 401/403/500 错误处理
|
||||
- WebSocket 连接封装为 `createJobSocket(jobId)` 返回可订阅对象
|
||||
- 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;
|
||||
}
|
||||
```
|
||||
|
||||
## 预设风格键分类
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# 多阶段生成管线
|
||||
|
||||
gen2d 的核心生成流程采用四阶段顺序管线,上一步输出即下一步输入,无需编排。
|
||||
gen2d 的核心生成流程采用 Eino `compose.Graph` 编排的四阶段管线,含质检不通过时的重生成分支。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["PromptBuilder\n(提示词工程)"] --> B["AssetGenerator\n(AI 出图)"]
|
||||
B --> C["QualitySupervisor\n(质检)"]
|
||||
C --> D["FormatAdapter\n(格式适配)"]
|
||||
C -- pass --> D["FormatAdapter\n(格式适配)"]
|
||||
C -- fail --> A
|
||||
```
|
||||
|
||||
---
|
||||
@@ -42,32 +43,18 @@ finalStyle = merge(projectStyle.KVPairs, taskStyle.KVPairs)
|
||||
- **输入**:PromptBuilder 的输出
|
||||
- **输出**:原始生成图片(单张或多张)
|
||||
- **职责**:调用 AI 推理 API 出图。不同素材类型的差异化需求已在上一步注入提示词中。
|
||||
- **接口规范**:统一采用 OpenAI 兼容的 `/v1/images/generations` 格式。多张生成通过 `n` 参数请求,实际支持数量取决于后端模型(如 DALL-E 3 仅支持 `n=1`,需多次调用模拟多张)。
|
||||
|
||||
## 3. QualitySupervisor
|
||||
|
||||
- **输入**:原始生成图片 + 风格配置
|
||||
- **输出**:质量评分 + 是否通过
|
||||
- **职责**:评估素材质量,不通过则触发重新生成(最多 3 次)。
|
||||
- **输出**:通过 / 不通过 + 不通过的原因
|
||||
- **职责**:通过视觉模型检查素材是否符合提示词要求与风格约束。不通过时,Graph 分支将 RejectReason 回填到 PipelineState,路由回 PromptBuilder 重新生成(最多 3 次,超过则降级输出)。
|
||||
|
||||
## 4. FormatAdapter
|
||||
|
||||
- **输入**:通过质检的图片 + 素材类型
|
||||
- **输出**:游戏引擎可用的素材文件 + 元数据
|
||||
- **职责**:格式转换、spritesheet 打包、元数据生成。
|
||||
- **示例**:输入 4 张 64×64 的角色行走帧 PNG → 输出一张 256×64 的 spritesheet + JSON 元数据(帧尺寸、帧数、锚点偏移),可直接拖入 Unity 2D Animation 使用。
|
||||
|
||||
---
|
||||
|
||||
## 预设风格键分类
|
||||
|
||||
前端以分类标签组织,用户点选:
|
||||
|
||||
| 分类 | 键名 | 可选值示例 |
|
||||
|------|------|-----------|
|
||||
| 美术风格 | `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 |
|
||||
|
||||
(具体键值后续可扩展,这里是初始集合)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# 预设风格键分类
|
||||
|
||||
前端以分类标签组织,用户点选:
|
||||
|
||||
| 分类 | 键名 | 可选值示例 |
|
||||
|------|------|-----------|
|
||||
| 美术风格 | `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 |
|
||||
|
||||
(具体键值后续可扩展,这里是初始集合)
|
||||
Reference in New Issue
Block a user