feat(generate): 图片持久化到本地 generation/ 目录,前端对接真实 API

后端:
- POST /api/v1/generate 接收 projectId,生成 taskId,将图片保存至 generation/{projectId}/{taskId}/
- 新增 GET /api/v1/tasks/:taskId 和 GET /api/v1/tasks/:taskId/assets 查询端点
- 添加 /generation 静态文件服务,前端可通过 URL 直接访问生成的图片
- PipelineInput 增加 ProjectID/TaskID 字段

前端:
- generate.ts 对接真实后端 API,移除 mock 模式
- 401 时不再强制登出跳转,改为抛错由调用方处理
- GeneratePage 加载工程风格,传递 projectId 和完整参数
- generation store 简化为同步 API 模式,移除 WebSocket mock
- ResultPage 使用 getTask/getAssets 按 taskId 查询结果
This commit is contained in:
2026-05-25 12:27:07 +08:00
parent 6bda5a1f35
commit 74c1725473
13 changed files with 297 additions and 114 deletions
Regular → Executable
-4
View File
@@ -41,10 +41,6 @@ async function request<T>(url: string, options: RequestInit = {}): Promise<T> {
const json: ApiResponse<T> = await res.json()
if (json.code !== 0) {
if (json.code === 401) {
clearToken()
window.location.href = '/login'
}
throw new ApiError(json.code, json.message)
}
Regular → Executable
+33 -14
View File
@@ -1,23 +1,42 @@
import type { Asset, Task } from './types'
import { mockGetAssets, mockGetTask, mockSubmitGenerate } from './mock'
const USE_MOCK = true
import { post, get } from './client'
import type {
Asset,
AssetsResponse,
GenerateRequest,
GenerateResponse,
Task,
} from './types'
export async function submitGenerate(
projectId: string,
prompt: string,
assetType: string
): Promise<string> {
if (USE_MOCK) return mockSubmitGenerate(projectId, prompt, assetType)
throw new Error('Not implemented')
req: GenerateRequest,
): Promise<GenerateResponse> {
return post<GenerateResponse>('/api/v1/generate', req)
}
export async function getTask(taskId: string): Promise<Task> {
if (USE_MOCK) return mockGetTask(taskId)
throw new Error('Not implemented')
return get<Task>(`/api/v1/tasks/${taskId}`)
}
export async function getAssets(taskId: string): Promise<Asset[]> {
if (USE_MOCK) return mockGetAssets(taskId)
throw new Error('Not implemented')
const resp = await get<AssetsResponse>(`/api/v1/tasks/${taskId}/assets`)
return toAssetList(resp)
}
/** 将响应转为 Asset[] 供前端组件使用 */
function toAssetList(
resp: AssetsResponse | GenerateResponse,
): Asset[] {
return resp.assets.map((a, i) => ({
id: `asset-${i}`,
url: a.url,
format: a.format,
width: resp.metadata.frameWidth,
height: resp.metadata.frameHeight,
metadata: {
frameWidth: resp.metadata.frameWidth,
frameHeight: resp.metadata.frameHeight,
frameCount: resp.metadata.frameCount,
directions: resp.metadata.directions,
},
}))
}
Regular → Executable
+37 -6
View File
@@ -91,16 +91,47 @@ export interface Asset {
}
}
// 生成请求
// 生成请求 — 对应 POST /api/v1/generate
export interface GenerateRequest {
projectId: string
prompt: string
prompt?: string
assetType: AssetType
tags?: string[]
userNote?: string
projectStyle?: Record<string, string>
taskStyle?: Record<string, string>
params?: {
resolution?: number
frames?: { directions?: number; framesPerDirection?: number }
format?: 'spritesheet' | 'individual'
resolution?: number
directions?: number
framesPerDir?: number
format?: 'spritesheet' | 'individual'
}
// 生成响应 — 对应 POST /api/v1/generate 返回
export interface GenerateResponse {
taskId: string
assets: {
url: string
format: string
}[]
metadata: {
frameWidth: number
frameHeight: number
frameCount: number
directions: number
}
}
// 素材列表响应 — 对应 GET /api/v1/tasks/:taskId/assets
export interface AssetsResponse {
assets: {
url: string
format: string
}[]
metadata: {
frameWidth: number
frameHeight: number
frameCount: number
directions: number
}
}