From dffb642a6882aee2f01c56854daf89905a7ed8dc Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 24 Aug 2026 15:02:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20Step7=20=E7=A1=AE=E8=AE=A4=E7=94=9F?= =?UTF-8?q?=E6=88=90=E4=BC=98=E5=85=88=E8=B0=83=E7=94=A8=20confirmGenerati?= =?UTF-8?q?on=20=E5=A4=8D=E7=94=A8=E9=A2=84=E8=A7=88=E4=BA=A7=E7=89=A9?= =?UTF-8?q?=EF=BC=8C=E8=A7=A3=E5=86=B3=E5=8D=A1=2010%=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:前端调用 createGenerationTask 创建全新渲染任务,后端 worker 因 source_edit_plan_id 为空进入 failed 分支但未更新 DB 状态, 导致前端轮询永远停在 running(卡 10%)。 修复策略: - 主路径:Step6 封面流程创建预览时,将 previewTaskId 暴露给父组件; Step7 确认生成优先调用 confirmGeneration(previewTaskId) 复用预览 产物,后端直接提升为正式任务(秒出)。 - Fallback:无预览任务或 confirmGeneration 失败时,退回 createGenerationTask 并传递 source_edit_plan_id,后端走全量渲染。 改动文件(7 个): - useGenerateVideo.ts:导入 confirmGeneration,新增双路径逻辑 - useGenerateFormState:新增 previewTaskId 共享状态 - useStep6Cover:createPreview 后回调 onPreviewTaskCreated - Step6CoverSettings / GenerateStepContent:透传回调 - generate-video/types.ts:UseGenerateVideoProps 新增 previewTaskId --- apps/web/src/pages/generate/GeneratePage.tsx | 4 + .../components/GenerateStepContent.tsx | 4 + .../components/Step6CoverSettings.tsx | 3 + .../generate/hooks/generate-video/types.ts | 2 + .../hooks/useGenerateFormState/index.ts | 9 ++ .../pages/generate/hooks/useGenerateVideo.ts | 87 +++++++++++++------ .../src/pages/generate/hooks/useStep6Cover.ts | 8 ++ 7 files changed, 89 insertions(+), 28 deletions(-) diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index 2c822aa6d..57df9d2f5 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -77,6 +77,8 @@ const GeneratePage: React.FC = () => { setPreviewVideo, previewModalOpen, setPreviewModalOpen, + previewTaskId, + setPreviewTaskId, } = formState /* ── 标题样式回调(Step5 样式面板 + 右侧预览 CSS 层共用) ── */ @@ -173,6 +175,7 @@ const GeneratePage: React.FC = () => { bgm, generateCount, sourceEditPlanId, + previewTaskId, }) /* ================================================================ @@ -215,6 +218,7 @@ const GeneratePage: React.FC = () => { onApplyPreset={styleUpdaters.applyPreset} activePreset={styleUpdaters.activePreset} titlePresets={styleUpdaters.titlePresets} + onPreviewTaskCreated={setPreviewTaskId} coverSettings={coverSettings} onCoverSettingsChange={setCoverSettings} duration={duration} diff --git a/apps/web/src/pages/generate/components/GenerateStepContent.tsx b/apps/web/src/pages/generate/components/GenerateStepContent.tsx index 7f7cf93f8..6c7ad9eaa 100644 --- a/apps/web/src/pages/generate/components/GenerateStepContent.tsx +++ b/apps/web/src/pages/generate/components/GenerateStepContent.tsx @@ -76,6 +76,8 @@ export interface GenerateStepContentProps { onDismissError: () => void /* 其他 */ presetVoices: PresetVoiceItem[] + /** 预览任务创建回调——传递给 Step6CoverSettings */ + onPreviewTaskCreated?: (taskId: string) => void } export const GenerateStepContent: React.FC = (props) => { @@ -121,6 +123,7 @@ export const GenerateStepContent: React.FC = (props) = onRetry, onDismissError, presetVoices, + onPreviewTaskCreated, } = props /* 当前模板的 segments,传给 Step2 构建 clips */ @@ -190,6 +193,7 @@ export const GenerateStepContent: React.FC = (props) = assetIds={materialMode === "auto" ? smartSelectedIds : selectedMaterials} selectedTemplate={selectedTemplate} titleSettings={titleSettings} + onPreviewTaskCreated={onPreviewTaskCreated} /> ) case 7: diff --git a/apps/web/src/pages/generate/components/Step6CoverSettings.tsx b/apps/web/src/pages/generate/components/Step6CoverSettings.tsx index 8d50ded18..fc576e303 100755 --- a/apps/web/src/pages/generate/components/Step6CoverSettings.tsx +++ b/apps/web/src/pages/generate/components/Step6CoverSettings.tsx @@ -16,6 +16,8 @@ interface Step6CoverSettingsProps { selectedTemplate?: string /** Step4 标题设置,用于预览视频烧录标题 & 封面叠加标题 */ titleSettings?: import("../types").TitleSettings + /** 预览任务创建回调——将 task_id 暴露给父组件供 confirmGeneration 复用 */ + onPreviewTaskCreated?: (taskId: string) => void } const Step6CoverSettings: React.FC = (props) => { @@ -43,6 +45,7 @@ const Step6CoverSettings: React.FC = (props) => { assetIds: props.assetIds, selectedTemplate: props.selectedTemplate, titleSettings: props.titleSettings, + onPreviewTaskCreated: props.onPreviewTaskCreated, }) const handleAutoGenerate = () => { diff --git a/apps/web/src/pages/generate/hooks/generate-video/types.ts b/apps/web/src/pages/generate/hooks/generate-video/types.ts index 1c2b6e2a5..a6352d553 100755 --- a/apps/web/src/pages/generate/hooks/generate-video/types.ts +++ b/apps/web/src/pages/generate/hooks/generate-video/types.ts @@ -21,6 +21,8 @@ export interface UseGenerateVideoProps { generateCount: number /** 当前草稿 ID(URL 参数 edit_plan_id,用于后端回写任务关联) */ sourceEditPlanId?: string | null + /** 预览任务 ID(由 useStep6Cover 创建后写入,供 confirmGeneration 复用预览产物) */ + previewTaskId?: string | null } /** 生成阶段 */ diff --git a/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts b/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts index d1cffd1ff..7170cdf75 100755 --- a/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts +++ b/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts @@ -95,6 +95,10 @@ export interface GenerateFormState { setPreviewVideo: (v: GeneratedVideo | null) => void previewModalOpen: boolean setPreviewModalOpen: (open: boolean) => void + + /** 预览任务 ID(由 useStep6Cover 创建后写入,供 useGenerateVideo 复用) */ + previewTaskId: string | null + setPreviewTaskId: (id: string | null) => void } export const useGenerateFormState = (): GenerateFormState => { @@ -160,6 +164,9 @@ export const useGenerateFormState = (): GenerateFormState => { const [previewVideo, setPreviewVideo] = useState(null) const [previewModalOpen, setPreviewModalOpen] = useState(false) + /* ── 预览任务 ID(useStep6Cover 创建预览时写入,useGenerateVideo 复用) ── */ + const [previewTaskId, setPreviewTaskId] = useState(null) + /* ── 从 URL / 编辑计划加载配置 ── */ usePlanConfigLoader({ editPlanId, @@ -208,5 +215,7 @@ export const useGenerateFormState = (): GenerateFormState => { setPreviewVideo, previewModalOpen, setPreviewModalOpen, + previewTaskId, + setPreviewTaskId, } } diff --git a/apps/web/src/pages/generate/hooks/useGenerateVideo.ts b/apps/web/src/pages/generate/hooks/useGenerateVideo.ts index 58d1c3b8e..569c05793 100755 --- a/apps/web/src/pages/generate/hooks/useGenerateVideo.ts +++ b/apps/web/src/pages/generate/hooks/useGenerateVideo.ts @@ -6,6 +6,7 @@ import { useState, useCallback } from "react" import { message } from "antd" import type { GeneratedVideo } from "@/api/template-editor" import { createGenerationTask } from "@/api/tasks/tasks" +import { confirmGeneration } from "@/api/generation/confirm" import type { UseGenerateVideoProps } from "./generate-video/types" import { getGenerationPhase } from "./generate-video/phase" import { useGenerationPolling } from "./generate-video/useGenerationPolling" @@ -92,35 +93,65 @@ export function useGenerateVideo(props: UseGenerateVideoProps) { // 封面 URL:优先 AI 生成缩略图,兜底用户上传 const coverUrl = props.coverSettings?.thumbnail_url || props.coverSettings?.upload_url || "" - // 直接创建正式生成任务 - const taskResp = await createGenerationTask({ - template_id: selectedTemplate, - asset_ids: assetIds, - output_width: outputWidth, - output_height: outputHeight, - cover_url: coverUrl, - custom_title: props.titleSettings?.title || "", - duration: props.duration || undefined, - video_ratio: props.videoRatio, - ...(props.sourceEditPlanId ? { source_edit_plan_id: props.sourceEditPlanId } : {}), - ...(props.titleSettings?.title - ? { - title_config: { - text: props.titleSettings.title, - font: props.titleSettings.font, - font_size: props.titleSettings.size, - font_color: props.titleSettings.color, - position: props.titleSettings.position, - bold: props.titleSettings.bold, - stroke: props.titleSettings.stroke, - shadow: props.titleSettings.shadow, - }, - } - : {}), - }) + // ── Step7 确认生成:优先复用预览产物(秒出),fallback 到全量渲染 ── + let taskId: string | undefined + + // 主路径:如果有预览任务 ID,调用 confirmGeneration 复用预览产物 + if (props.previewTaskId) { + try { + console.log( + "[handleGenerate] 尝试 confirmGeneration 复用预览产物, previewTaskId:", + props.previewTaskId, + ) + const confirmResp = await confirmGeneration(props.previewTaskId, { + output_width: outputWidth, + output_height: outputHeight, + cover_url: coverUrl, + custom_title: props.titleSettings?.title || "", + }) + taskId = confirmResp.items?.[0]?.id + if (taskId) { + console.log("[handleGenerate] confirmGeneration 成功, taskId:", taskId) + } + } catch (confirmErr) { + console.warn( + "[handleGenerate] confirmGeneration 失败, fallback 到 createGenerationTask:", + confirmErr, + ) + // 继续走 fallback 路径 + } + } + + // Fallback 路径:没有预览任务或 confirmGeneration 失败,创建新的生成任务 + if (!taskId) { + const taskResp = await createGenerationTask({ + template_id: selectedTemplate, + asset_ids: assetIds, + output_width: outputWidth, + output_height: outputHeight, + cover_url: coverUrl, + custom_title: props.titleSettings?.title || "", + duration: props.duration || undefined, + video_ratio: props.videoRatio, + ...(props.sourceEditPlanId ? { source_edit_plan_id: props.sourceEditPlanId } : {}), + ...(props.titleSettings?.title + ? { + title_config: { + text: props.titleSettings.title, + font: props.titleSettings.font, + font_size: props.titleSettings.size, + font_color: props.titleSettings.color, + position: props.titleSettings.position, + bold: props.titleSettings.bold, + stroke: props.titleSettings.stroke, + shadow: props.titleSettings.shadow, + }, + } + : {}), + }) + taskId = taskResp.items?.[0]?.id + } - // 从创建响应直接拿 task_id,改用新接口轮询 - const taskId = taskResp.items?.[0]?.id if (!taskId) { throw new Error("创建任务成功但未返回任务 ID,请稍后在任务列表查看") } diff --git a/apps/web/src/pages/generate/hooks/useStep6Cover.ts b/apps/web/src/pages/generate/hooks/useStep6Cover.ts index f86141680..4ddffa546 100644 --- a/apps/web/src/pages/generate/hooks/useStep6Cover.ts +++ b/apps/web/src/pages/generate/hooks/useStep6Cover.ts @@ -26,6 +26,8 @@ interface UseStep6CoverProps { selectedTemplate?: string /** Step4 标题设置,用于预览视频烧录标题 & 封面叠加标题 */ titleSettings?: TitleSettings + /** 预览任务创建回调——将 task_id 暴露给父组件供 confirmGeneration 复用 */ + onPreviewTaskCreated?: (taskId: string) => void } export function useStep6Cover({ @@ -35,6 +37,7 @@ export function useStep6Cover({ assetIds = [], selectedTemplate = "", titleSettings, + onPreviewTaskCreated, }: UseStep6CoverProps) { const [generating, setGenerating] = useState(false) @@ -170,6 +173,10 @@ export function useStep6Cover({ } : {}), }) + // 将预览任务 ID 暴露给父组件,供 Step7 确认生成时复用(confirmGeneration) + if (previewResp.task_id) { + onPreviewTaskCreated?.(previewResp.task_id) + } // 轮询等待预览渲染完成:递归 setTimeout 避免请求重叠 + 120s 超时兜底 await new Promise((resolve, reject) => { let finished = false @@ -282,6 +289,7 @@ export function useStep6Cover({ generating, duration, titleSettings, + onPreviewTaskCreated, ]) // ── 模板操作方法 ──