Files
gen2d/frontend/src/pages/GeneratePage.tsx
T
Gmarker689 5d5fe60426 feat: 管线四阶段进度上报 + 图片预览 + Vite 代理
后端:
- pipeline.go: ProgressReporter 回调类型 + WithProgressReporter 注入 context
- nodes.go: 各节点 pre/post handler 调用 reportProgress() 上报阶段进度
- generate.go: TaskResponse 新增 stage 字段,runPipelineBg 注入进度回调

前端:
- vite.config.ts: 添加 /generation 代理到后端静态文件服务
- generation.ts: 轮询读取 stage 字段,暴露 stage/retryCount/rejectReason
- GeneratePage.tsx: ProgressBar 接收真实管线阶段数据
2026-05-25 14:26:26 +08:00

133 lines
4.1 KiB
TypeScript
Executable File

import { useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useTaskStore } from '../stores/task'
import { useProjectStore } from '../stores/project'
import { useGenerationStore } from '../stores/generation'
import { useToastStore } from '../stores/toast'
import { extractTags } from '../api/prompt'
import { mergeStyles } from '../utils/style'
import type { GenerateRequest } from '../api/types'
import GenerateForm from '../components/GenerateForm'
import ProgressBar from '../components/ProgressBar'
export default function GeneratePage() {
const { projectId = 'proj-default' } = useParams()
const navigate = useNavigate()
const addToast = useToastStore(s => s.addToast)
const taskStore = useTaskStore()
const { style: projectStyle, loadProject } = useProjectStore()
const {
status,
stage,
progress,
taskId,
statusText,
retryCount,
rejectReason,
submit,
reset: resetGeneration,
} = useGenerationStore()
// 加载工程风格
useEffect(() => {
loadProject(projectId)
}, [projectId, loadProject])
// 组件卸载时重置生成状态
useEffect(() => {
return () => resetGeneration()
}, [resetGeneration])
// 失败时显示 toast
useEffect(() => {
if (status === 'failed') {
const errText = useGenerationStore.getState().error || '未知错误'
addToast({ type: 'error', message: `素材生成失败:${errText}` })
}
}, [status, addToast])
const handleSubmit = async (finalPrompt: string) => {
const { taskStyle, params, enableAI, optimizedPrompt } = taskStore
const mergedStyle = mergeStyles(projectStyle, taskStyle)
const tags = extractTags(mergedStyle)
const req: GenerateRequest = {
projectId,
prompt: enableAI && optimizedPrompt ? optimizedPrompt : finalPrompt,
assetType: taskStore.assetType,
tags,
projectStyle,
taskStyle,
resolution: params.resolution,
directions: params.frames?.directions,
framesPerDir: params.frames?.framesPerDirection,
format: params.format,
}
await submit(req)
}
const handleViewResult = () => {
if (taskId) navigate(`/projects/${projectId}/tasks/${taskId}`)
}
const handleReset = () => {
resetGeneration()
taskStore.reset()
}
return (
<div className="container page-enter" style={{ paddingTop: 24, paddingBottom: 40 }}>
<h1 style={{ fontSize: 24, marginBottom: 32 }}>新建生成</h1>
{status === 'idle' || status === 'submitting' ? (
<GenerateForm onSubmit={handleSubmit} submitting={status === 'submitting'} />
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}>
<ProgressBar
stage={stage}
progress={progress}
status={status}
retryCount={retryCount}
rejectReason={rejectReason}
/>
{status === 'running' && (
<p style={{ textAlign: 'center', color: 'var(--text-secondary)' }}>
{statusText || '管线执行中,请稍候...'}
</p>
)}
{status === 'completed' && (
<div style={{ textAlign: 'center' }}>
<p style={{ color: 'var(--success)', marginBottom: 16 }}>
生成完成
</p>
<div style={{ display: 'flex', gap: 12, justifyContent: 'center' }}>
<button className="btn-primary" onClick={handleViewResult}>
查看结果
</button>
<button className="btn-secondary" onClick={handleReset}>
继续生成
</button>
</div>
</div>
)}
{status === 'failed' && (
<div style={{ textAlign: 'center' }}>
<p style={{ color: 'var(--error)', marginBottom: 16 }}>
{useGenerationStore.getState().error || '生成失败'}
</p>
<button className="btn-primary" onClick={handleReset}>
重新开始
</button>
</div>
)}
</div>
)}
</div>
)
}