feat(generate): 生成管线改为异步,前端接入轮询进度
后端: - POST /api/v1/generate 改为异步模式,立即返回 taskId - 管线在后台 goroutine 执行,状态通过 GET /tasks/:taskId 轮询 - 任务状态流转: pending → running → saving → completed/failed 前端: - generation store: submit 后每 2s 轮询 getTask,完成时自动 getAssets - GeneratePage: 实时显示轮询状态文本 + 进度条 - ResultPage: 挂载时轮询任务,未完成显示骨架屏+进度,完成自动加载素材 - Types: Task.status 增加 submitted / saving 状态
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import type { Asset, GenerateRequest, GenerateResponse } from '../api/types'
|
||||
import { submitGenerate } from '../api/generate'
|
||||
import type { Asset, GenerateRequest } from '../api/types'
|
||||
import { submitGenerate, getTask, getAssets } from '../api/generate'
|
||||
|
||||
type Status = 'idle' | 'submitting' | 'running' | 'completed' | 'failed'
|
||||
|
||||
@@ -9,66 +9,99 @@ interface GenerationState {
|
||||
projectId: string | null
|
||||
progress: number
|
||||
status: Status
|
||||
statusText: string
|
||||
assets: Asset[]
|
||||
error: string | null
|
||||
submit: (req: GenerateRequest) => Promise<void>
|
||||
reset: () => void
|
||||
}
|
||||
|
||||
export const useGenerationStore = create<GenerationState>((set) => ({
|
||||
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
function stopPolling() {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer)
|
||||
pollTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
export const useGenerationStore = create<GenerationState>((set, get) => ({
|
||||
taskId: null,
|
||||
projectId: null,
|
||||
progress: 0,
|
||||
status: 'idle',
|
||||
statusText: '',
|
||||
assets: [],
|
||||
error: null,
|
||||
|
||||
submit: async (req) => {
|
||||
set({ status: 'submitting', error: null })
|
||||
stopPolling()
|
||||
set({ status: 'submitting', error: null, statusText: '提交中...' })
|
||||
try {
|
||||
set({ status: 'running', progress: 30 })
|
||||
const result = await submitGenerate(req)
|
||||
|
||||
set({ progress: 80 })
|
||||
const assets = mapAssets(result)
|
||||
const { taskId } = await submitGenerate(req)
|
||||
|
||||
set({
|
||||
taskId: result.taskId,
|
||||
taskId,
|
||||
projectId: req.projectId,
|
||||
status: 'completed',
|
||||
progress: 100,
|
||||
assets,
|
||||
status: 'running',
|
||||
progress: 10,
|
||||
statusText: '任务已提交,等待生成...',
|
||||
})
|
||||
|
||||
// 开始轮询进度
|
||||
pollTimer = setInterval(async () => {
|
||||
try {
|
||||
const task = await getTask(taskId)
|
||||
|
||||
set({
|
||||
progress: task.progress ?? get().progress,
|
||||
statusText:
|
||||
task.status === 'running'
|
||||
? '生成中...'
|
||||
: task.status === 'pending'
|
||||
? '排队中...'
|
||||
: task.status,
|
||||
})
|
||||
|
||||
if (task.status === 'completed') {
|
||||
stopPolling()
|
||||
set({ progress: 90, statusText: '获取结果...' })
|
||||
|
||||
const assets = await getAssets(taskId)
|
||||
set({
|
||||
status: 'completed',
|
||||
progress: 100,
|
||||
statusText: '生成完成',
|
||||
assets,
|
||||
})
|
||||
} else if (task.status === 'failed') {
|
||||
stopPolling()
|
||||
set({
|
||||
status: 'failed',
|
||||
error: task.error || '生成失败',
|
||||
statusText: '生成失败',
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
// 网络错误不中断轮询
|
||||
}
|
||||
}, 2000)
|
||||
} catch (err) {
|
||||
const errMsg = (err as Error).message
|
||||
set({ status: 'failed', error: errMsg })
|
||||
stopPolling()
|
||||
set({ status: 'failed', error: (err as Error).message, statusText: '提交失败' })
|
||||
}
|
||||
},
|
||||
|
||||
reset: () => {
|
||||
stopPolling()
|
||||
set({
|
||||
taskId: null,
|
||||
projectId: null,
|
||||
progress: 0,
|
||||
status: 'idle',
|
||||
statusText: '',
|
||||
assets: [],
|
||||
error: null,
|
||||
})
|
||||
},
|
||||
}))
|
||||
|
||||
function mapAssets(resp: 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,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user