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:
Regular → Executable
+56
-27
@@ -1,27 +1,35 @@
|
||||
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 { assetType, reset: resetTask } = useTaskStore()
|
||||
const addToast = useToastStore(s => s.addToast)
|
||||
|
||||
const taskStore = useTaskStore()
|
||||
const { style: projectStyle, loadProject } = useProjectStore()
|
||||
const {
|
||||
status,
|
||||
stage,
|
||||
progress,
|
||||
retryCount,
|
||||
rejectReason,
|
||||
taskId,
|
||||
submit,
|
||||
reset: resetGeneration,
|
||||
} = useGenerationStore()
|
||||
|
||||
// 加载工程风格
|
||||
useEffect(() => {
|
||||
loadProject(projectId)
|
||||
}, [projectId, loadProject])
|
||||
|
||||
// 组件卸载时重置生成状态
|
||||
useEffect(() => {
|
||||
return () => resetGeneration()
|
||||
@@ -30,48 +38,57 @@ export default function GeneratePage() {
|
||||
// 失败时显示 toast
|
||||
useEffect(() => {
|
||||
if (status === 'failed') {
|
||||
addToast({ type: 'error', message: '素材生成失败,请重试' })
|
||||
const errText = useGenerationStore.getState().error || '未知错误'
|
||||
addToast({ type: 'error', message: `素材生成失败:${errText}` })
|
||||
}
|
||||
}, [status, addToast])
|
||||
|
||||
// 完成后自动跳转
|
||||
useEffect(() => {
|
||||
if (status === 'completed' && taskId) {
|
||||
const timer = setTimeout(() => {
|
||||
navigate(`/projects/${projectId}/tasks/${taskId}`)
|
||||
}, 1500)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [status, taskId, projectId, navigate])
|
||||
|
||||
const handleSubmit = async (finalPrompt: string) => {
|
||||
await submit(projectId, finalPrompt, assetType)
|
||||
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()
|
||||
resetTask()
|
||||
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}
|
||||
stage={null}
|
||||
progress={progress}
|
||||
status={status}
|
||||
retryCount={retryCount}
|
||||
rejectReason={rejectReason}
|
||||
retryCount={0}
|
||||
rejectReason={null}
|
||||
/>
|
||||
|
||||
{/* 状态提示 */}
|
||||
{status === 'running' && (
|
||||
<p style={{ textAlign: 'center', color: 'var(--text-secondary)' }}>
|
||||
管线执行中,请稍候...
|
||||
@@ -79,14 +96,26 @@ export default function GeneratePage() {
|
||||
)}
|
||||
|
||||
{status === 'completed' && (
|
||||
<p style={{ textAlign: 'center', color: 'var(--success)' }}>
|
||||
✓ 生成完成,正在跳转到结果页...
|
||||
</p>
|
||||
<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 }}>生成失败</p>
|
||||
<p style={{ color: 'var(--error)', marginBottom: 16 }}>
|
||||
{useGenerationStore.getState().error || '生成失败'}
|
||||
</p>
|
||||
<button className="btn-primary" onClick={handleReset}>
|
||||
重新开始
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user