From 172914c7f28b272ff63d1a0cd0dbdf4f0ad1a701 Mon Sep 17 00:00:00 2001 From: wonder Date: Sun, 24 May 2026 10:22:15 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=90=8E=E7=AB=AF=E9=87=87=E7=94=A8=20?= =?UTF-8?q?Eino=20=E6=A1=86=E6=9E=B6=E7=BC=96=E6=8E=92=E7=AE=A1=E7=BA=BF?= =?UTF-8?q?=EF=BC=8C=E5=AE=8C=E5=96=84=E5=89=8D=E5=90=8E=E7=AB=AF=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=AF=B9=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端管线从手写编排改为 Eino compose.Graph,含质检重试分支 - 移除 CORS 中间件 - api.md 补充工程管理接口、任务状态机、管线阶段、WebSocket 消息示例 - frontend.md 组件树和交互流程改用 mermaid 图,stores 对齐 PipelineInput/Output - CLAUDE.md 修复死链,技术栈补充 Eino --- CLAUDE.md | 6 +- docs/_index.md | 5 +- docs/api.md | 232 ++++++++++++++++++++++++++++++++--- docs/backend.md | 169 +++++++++++++++++++++++-- docs/frontend.md | 231 +++++++++++++++++++++++----------- docs/multi-agent-pipeline.md | 27 ++-- docs/style-keys.md | 14 +++ 7 files changed, 561 insertions(+), 123 deletions(-) create mode 100644 docs/style-keys.md diff --git a/CLAUDE.md b/CLAUDE.md index 8ed8276..739b820 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/docs/_index.md b/docs/_index.md index 5e243e0..d76ac41 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -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) — 美术风格、色调、线条等风格键分类与可选值 diff --git a/docs/api.md b/docs/api.md index 1b8b30c..e7a3897 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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":[...]}} +``` + ## 缓存管理 | 状态 | 方法 | 路径 | 说明 | diff --git a/docs/backend.md b/docs/backend.md index 518374e..715a76f 100644 --- a/docs/backend.md +++ b/docs/backend.md @@ -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 节点在生成提示词时执行合并。 diff --git a/docs/frontend.md b/docs/frontend.md index 7229fa2..80ff314 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -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/ ## 组件树 -``` - - - - # 工程风格编辑 - # 历史任务列表 - - - - # sprite / background / UI / animation - # 任务风格覆盖(基于工程风格,高亮差异) - # 三段式提示词预览 & 编辑 - # 分辨率、帧数等技术参数 - - # 提交后显示管线进度 - - - # 素材预览 - # 元数据展示 - # 下载素材 - - - +```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; // kvPairs loading: boolean; + loadProject: (projectId: string) => Promise; loadStyle: (projectId: string) => Promise; updateStyle: (kvPairs: Record) => void; saveStyle: () => Promise; } ``` -**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) => 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; 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; + params?: { + resolution?: number; + frames?: { directions?: number; framesPerDirection?: number }; + format?: 'spritesheet' | 'individual'; + }; +} + +interface UpdateStyleRequest { + kvPairs: Record; +} + +// 响应 +interface Project { + id: string; + name: string; + style: { kvPairs: Record }; + 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; +} +``` ## 预设风格键分类 diff --git a/docs/multi-agent-pipeline.md b/docs/multi-agent-pipeline.md index c5355de..6b50dc8 100644 --- a/docs/multi-agent-pipeline.md +++ b/docs/multi-agent-pipeline.md @@ -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 | - -(具体键值后续可扩展,这里是初始集合) diff --git a/docs/style-keys.md b/docs/style-keys.md new file mode 100644 index 0000000..cc20738 --- /dev/null +++ b/docs/style-keys.md @@ -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 | + +(具体键值后续可扩展,这里是初始集合)