2026-05-24 14:18:39 +08:00
|
|
|
import { create } from 'zustand'
|
2026-05-25 14:26:26 +08:00
|
|
|
import type { Asset, GenerateRequest, PipelineStage } from '../api/types'
|
2026-05-25 12:51:49 +08:00
|
|
|
import { submitGenerate, getTask, getAssets } from '../api/generate'
|
2026-05-24 14:18:39 +08:00
|
|
|
|
|
|
|
|
type Status = 'idle' | 'submitting' | 'running' | 'completed' | 'failed'
|
|
|
|
|
|
|
|
|
|
interface GenerationState {
|
|
|
|
|
taskId: string | null
|
2026-05-25 12:27:07 +08:00
|
|
|
projectId: string | null
|
2026-05-25 14:26:26 +08:00
|
|
|
stage: PipelineStage | null
|
2026-05-24 14:18:39 +08:00
|
|
|
progress: number
|
|
|
|
|
status: Status
|
2026-05-25 12:51:49 +08:00
|
|
|
statusText: string
|
2026-05-25 14:26:26 +08:00
|
|
|
retryCount: number
|
|
|
|
|
rejectReason: string | null
|
2026-05-24 14:18:39 +08:00
|
|
|
assets: Asset[]
|
|
|
|
|
error: string | null
|
2026-05-25 12:27:07 +08:00
|
|
|
submit: (req: GenerateRequest) => Promise<void>
|
2026-05-24 14:18:39 +08:00
|
|
|
reset: () => void
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-25 12:51:49 +08:00
|
|
|
let pollTimer: ReturnType<typeof setInterval> | null = null
|
|
|
|
|
|
|
|
|
|
function stopPolling() {
|
|
|
|
|
if (pollTimer) {
|
|
|
|
|
clearInterval(pollTimer)
|
|
|
|
|
pollTimer = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-25 14:26:26 +08:00
|
|
|
export const useGenerationStore = create<GenerationState>((set) => ({
|
2026-05-24 14:18:39 +08:00
|
|
|
taskId: null,
|
2026-05-25 12:27:07 +08:00
|
|
|
projectId: null,
|
2026-05-25 14:26:26 +08:00
|
|
|
stage: null,
|
2026-05-24 14:18:39 +08:00
|
|
|
progress: 0,
|
|
|
|
|
status: 'idle',
|
2026-05-25 12:51:49 +08:00
|
|
|
statusText: '',
|
2026-05-25 14:26:26 +08:00
|
|
|
retryCount: 0,
|
|
|
|
|
rejectReason: null,
|
2026-05-24 14:18:39 +08:00
|
|
|
assets: [],
|
|
|
|
|
error: null,
|
|
|
|
|
|
2026-05-25 12:27:07 +08:00
|
|
|
submit: async (req) => {
|
2026-05-25 12:51:49 +08:00
|
|
|
stopPolling()
|
2026-05-25 14:26:26 +08:00
|
|
|
set({ status: 'submitting', error: null, statusText: '提交中...', stage: null })
|
2026-05-24 14:18:39 +08:00
|
|
|
try {
|
2026-05-25 12:51:49 +08:00
|
|
|
const { taskId } = await submitGenerate(req)
|
2026-05-24 14:18:39 +08:00
|
|
|
|
2026-05-25 12:27:07 +08:00
|
|
|
set({
|
2026-05-25 12:51:49 +08:00
|
|
|
taskId,
|
2026-05-25 12:27:07 +08:00
|
|
|
projectId: req.projectId,
|
2026-05-25 12:51:49 +08:00
|
|
|
status: 'running',
|
2026-05-25 14:26:26 +08:00
|
|
|
progress: 5,
|
|
|
|
|
stage: 'prompt_builder',
|
2026-05-25 12:51:49 +08:00
|
|
|
statusText: '任务已提交,等待生成...',
|
2026-05-25 12:27:07 +08:00
|
|
|
})
|
2026-05-25 12:51:49 +08:00
|
|
|
|
|
|
|
|
pollTimer = setInterval(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const task = await getTask(taskId)
|
|
|
|
|
|
2026-05-25 14:26:26 +08:00
|
|
|
set((s) => ({
|
|
|
|
|
stage: task.stage ?? s.stage,
|
|
|
|
|
progress: task.progress ?? s.progress,
|
|
|
|
|
retryCount: task.retryCount ?? s.retryCount,
|
2026-05-25 12:51:49 +08:00
|
|
|
statusText:
|
|
|
|
|
task.status === 'running'
|
2026-05-25 14:26:26 +08:00
|
|
|
? stageLabel(task.stage)
|
2026-05-25 12:51:49 +08:00
|
|
|
: task.status === 'pending'
|
|
|
|
|
? '排队中...'
|
|
|
|
|
: task.status,
|
2026-05-25 14:26:26 +08:00
|
|
|
}))
|
2026-05-25 12:51:49 +08:00
|
|
|
|
|
|
|
|
if (task.status === 'completed') {
|
|
|
|
|
stopPolling()
|
|
|
|
|
set({ progress: 90, statusText: '获取结果...' })
|
|
|
|
|
|
|
|
|
|
const assets = await getAssets(taskId)
|
|
|
|
|
set({
|
|
|
|
|
status: 'completed',
|
|
|
|
|
progress: 100,
|
2026-05-25 14:26:26 +08:00
|
|
|
stage: 'format_adapter',
|
2026-05-25 12:51:49 +08:00
|
|
|
statusText: '生成完成',
|
|
|
|
|
assets,
|
|
|
|
|
})
|
|
|
|
|
} else if (task.status === 'failed') {
|
|
|
|
|
stopPolling()
|
|
|
|
|
set({
|
|
|
|
|
status: 'failed',
|
|
|
|
|
error: task.error || '生成失败',
|
|
|
|
|
statusText: '生成失败',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// 网络错误不中断轮询
|
|
|
|
|
}
|
2026-05-25 14:26:26 +08:00
|
|
|
}, 1500)
|
2026-05-25 12:27:07 +08:00
|
|
|
} catch (err) {
|
2026-05-25 12:51:49 +08:00
|
|
|
stopPolling()
|
|
|
|
|
set({ status: 'failed', error: (err as Error).message, statusText: '提交失败' })
|
2026-05-24 14:18:39 +08:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
reset: () => {
|
2026-05-25 12:51:49 +08:00
|
|
|
stopPolling()
|
2026-05-24 14:18:39 +08:00
|
|
|
set({
|
|
|
|
|
taskId: null,
|
2026-05-25 12:27:07 +08:00
|
|
|
projectId: null,
|
2026-05-25 14:26:26 +08:00
|
|
|
stage: null,
|
2026-05-24 14:18:39 +08:00
|
|
|
progress: 0,
|
|
|
|
|
status: 'idle',
|
2026-05-25 12:51:49 +08:00
|
|
|
statusText: '',
|
2026-05-25 14:26:26 +08:00
|
|
|
retryCount: 0,
|
|
|
|
|
rejectReason: null,
|
2026-05-24 14:18:39 +08:00
|
|
|
assets: [],
|
|
|
|
|
error: null,
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
}))
|
2026-05-25 14:26:26 +08:00
|
|
|
|
|
|
|
|
function stageLabel(stage?: string): string {
|
|
|
|
|
switch (stage) {
|
|
|
|
|
case 'prompt_builder': return '优化提示词...'
|
|
|
|
|
case 'asset_generator': return '生成素材中...'
|
|
|
|
|
case 'quality_supervisor': return '质检中...'
|
|
|
|
|
case 'format_adapter': return '格式转换中...'
|
|
|
|
|
default: return '生成中...'
|
|
|
|
|
}
|
|
|
|
|
}
|