From 823a4a95ff9ff598c06a05ae506d8224b685e310 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 24 Aug 2026 16:34:53 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=A7=BB=E9=99=A4=20previewTaskId=20?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=B8=85=E9=99=A4=20+=20=E4=BB=8E=E9=A2=84?= =?UTF-8?q?=E8=A7=88=E5=93=8D=E5=BA=94=E6=8F=90=E5=8F=96=20source=5Fedit?= =?UTF-8?q?=5Fplan=5Fid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因: 1. useEffect 在素材/模板/时长/比例/标题/BGM 任一变化时清空 previewTaskId, 导致 Step7 点击确认生成时 previewTaskId 为空,跳过 confirmGeneration 直接走 fallback 2. sourceEditPlanId 只从 URL 参数取,如果 URL 没有 edit_plan_id 则为 null, fallback 路径 createGenerationTask 不传 source_edit_plan_id 导致 worker 报错 改动: - GeneratePage: 移除清除 previewTaskId 的 useEffect,previewTaskId 持久保留 直到用户明确创建新预览时才覆盖 - GeneratePage: sourceEditPlanId 优先使用 storedSourceEditPlanId(从预览响应提取), fallback 到 URL 参数 sourceEditPlanId - CreatePreviewResponse: 新增 source_edit_plan_id? 字段 - useStep6Cover: createPreview 成功后提取 source_edit_plan_id 并回调通知父组件 - useGenerateFormState: 新增 storedSourceEditPlanId 状态 - GenerateStepContent/Step6CoverSettings: 透传 onSourceEditPlanIdExtracted 回调 --- apps/web/src/api/generation/types.ts | 2 ++ apps/web/src/pages/generate/GeneratePage.tsx | 18 +++++++++--------- .../components/GenerateStepContent.tsx | 4 ++++ .../generate/components/Step6CoverSettings.tsx | 3 +++ .../hooks/useGenerateFormState/index.ts | 9 +++++++++ .../src/pages/generate/hooks/useStep6Cover.ts | 8 ++++++++ 6 files changed, 35 insertions(+), 9 deletions(-) diff --git a/apps/web/src/api/generation/types.ts b/apps/web/src/api/generation/types.ts index b110a1f95..745222caf 100755 --- a/apps/web/src/api/generation/types.ts +++ b/apps/web/src/api/generation/types.ts @@ -38,6 +38,8 @@ export interface CreatePreviewResponse { is_preview: boolean resolution: string created_at: string + /** 后端自动关联的编辑计划 ID(用于 fallback 路径传递 source_edit_plan_id) */ + source_edit_plan_id?: string } /** 预览任务详情响应 */ diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index fa48251da..6623f42f2 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -9,7 +9,7 @@ * - 标题样式通过 CSS 层实时叠加,所见即所得 * - 最终成片仍走后端 FFmpeg 渲染(Step7 确认生成) */ -import React, { useMemo, useEffect } from "react" +import React, { useMemo } from "react" import { Modal, message } from "antd" import { useNavigate } from "react-router-dom" import type { VoiceClone } from "@/api/voice-clone" @@ -79,6 +79,8 @@ const GeneratePage: React.FC = () => { setPreviewModalOpen, previewTaskId, setPreviewTaskId, + storedSourceEditPlanId, + setStoredSourceEditPlanId, } = formState /* ── 标题样式回调(Step5 样式面板 + 右侧预览 CSS 层共用) ── */ @@ -101,13 +103,10 @@ const GeneratePage: React.FC = () => { () => (materialMode === "auto" ? smartSelectedIds : selectedMaterials), [materialMode, smartSelectedIds, selectedMaterials], ) - // 素材、模板或生成参数变化时,清除旧的 previewTaskId,防止 Step7 复用过期预览产物 - // confirmGeneration 无法覆盖 duration/ratio/title_config,所以这些参数变化后必须重新创建预览 - const titleKey = JSON.stringify(titleSettings) - useEffect(() => { - setPreviewTaskId(null) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [previewAssetIds, selectedTemplate, duration, videoRatio, titleKey, bgm, setPreviewTaskId]) + // previewTaskId 不再因设置变化而自动清除。 + // 用户修改设置后,confirmGeneration 会检测参数是否匹配: + // 匹配则复用产物秒出;不匹配则后端自动 fallback 到 createGenerationTask 重新渲染。 + // 只有当用户明确返回 Step5 重新预览时,才会创建新的预览任务并覆盖 previewTaskId。 const previewAssetsEnabled = currentStep >= 4 && previewAssetIds.length > 0 const { @@ -182,7 +181,7 @@ const GeneratePage: React.FC = () => { autoSubtitles, bgm, generateCount, - sourceEditPlanId, + sourceEditPlanId: storedSourceEditPlanId || sourceEditPlanId, previewTaskId, }) @@ -227,6 +226,7 @@ const GeneratePage: React.FC = () => { activePreset={styleUpdaters.activePreset} titlePresets={styleUpdaters.titlePresets} onPreviewTaskCreated={setPreviewTaskId} + onSourceEditPlanIdExtracted={setStoredSourceEditPlanId} 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 6c7ad9eaa..6fd23cf4e 100644 --- a/apps/web/src/pages/generate/components/GenerateStepContent.tsx +++ b/apps/web/src/pages/generate/components/GenerateStepContent.tsx @@ -78,6 +78,8 @@ export interface GenerateStepContentProps { presetVoices: PresetVoiceItem[] /** 预览任务创建回调——传递给 Step6CoverSettings */ onPreviewTaskCreated?: (taskId: string) => void + /** 从预览响应中提取到 source_edit_plan_id 时的回调 */ + onSourceEditPlanIdExtracted?: (planId: string) => void } export const GenerateStepContent: React.FC = (props) => { @@ -124,6 +126,7 @@ export const GenerateStepContent: React.FC = (props) = onDismissError, presetVoices, onPreviewTaskCreated, + onSourceEditPlanIdExtracted, } = props /* 当前模板的 segments,传给 Step2 构建 clips */ @@ -194,6 +197,7 @@ export const GenerateStepContent: React.FC = (props) = selectedTemplate={selectedTemplate} titleSettings={titleSettings} onPreviewTaskCreated={onPreviewTaskCreated} + onSourceEditPlanIdExtracted={onSourceEditPlanIdExtracted} /> ) case 7: diff --git a/apps/web/src/pages/generate/components/Step6CoverSettings.tsx b/apps/web/src/pages/generate/components/Step6CoverSettings.tsx index fc576e303..bd72d9c95 100755 --- a/apps/web/src/pages/generate/components/Step6CoverSettings.tsx +++ b/apps/web/src/pages/generate/components/Step6CoverSettings.tsx @@ -18,6 +18,8 @@ interface Step6CoverSettingsProps { titleSettings?: import("../types").TitleSettings /** 预览任务创建回调——将 task_id 暴露给父组件供 confirmGeneration 复用 */ onPreviewTaskCreated?: (taskId: string) => void + /** 从预览响应中提取到 source_edit_plan_id 时的回调 */ + onSourceEditPlanIdExtracted?: (planId: string) => void } const Step6CoverSettings: React.FC = (props) => { @@ -46,6 +48,7 @@ const Step6CoverSettings: React.FC = (props) => { selectedTemplate: props.selectedTemplate, titleSettings: props.titleSettings, onPreviewTaskCreated: props.onPreviewTaskCreated, + onSourceEditPlanIdExtracted: props.onSourceEditPlanIdExtracted, }) const handleAutoGenerate = () => { diff --git a/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts b/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts index 7170cdf75..eba9b3870 100755 --- a/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts +++ b/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts @@ -99,6 +99,10 @@ export interface GenerateFormState { /** 预览任务 ID(由 useStep6Cover 创建后写入,供 useGenerateVideo 复用) */ previewTaskId: string | null setPreviewTaskId: (id: string | null) => void + + /** 从预览响应中提取的 source_edit_plan_id(供 fallback 路径使用) */ + storedSourceEditPlanId: string | null + setStoredSourceEditPlanId: (planId: string | null) => void } export const useGenerateFormState = (): GenerateFormState => { @@ -167,6 +171,9 @@ export const useGenerateFormState = (): GenerateFormState => { /* ── 预览任务 ID(useStep6Cover 创建预览时写入,useGenerateVideo 复用) ── */ const [previewTaskId, setPreviewTaskId] = useState(null) + /* ── 从预览响应中提取的 source_edit_plan_id(供 fallback 路径使用) ── */ + const [storedSourceEditPlanId, setStoredSourceEditPlanId] = useState(null) + /* ── 从 URL / 编辑计划加载配置 ── */ usePlanConfigLoader({ editPlanId, @@ -217,5 +224,7 @@ export const useGenerateFormState = (): GenerateFormState => { setPreviewModalOpen, previewTaskId, setPreviewTaskId, + storedSourceEditPlanId, + setStoredSourceEditPlanId, } } diff --git a/apps/web/src/pages/generate/hooks/useStep6Cover.ts b/apps/web/src/pages/generate/hooks/useStep6Cover.ts index e863559d6..1e601252f 100644 --- a/apps/web/src/pages/generate/hooks/useStep6Cover.ts +++ b/apps/web/src/pages/generate/hooks/useStep6Cover.ts @@ -28,6 +28,8 @@ interface UseStep6CoverProps { titleSettings?: TitleSettings /** 预览任务创建回调——将 task_id 暴露给父组件供 confirmGeneration 复用 */ onPreviewTaskCreated?: (taskId: string) => void + /** 从预览响应中提取到 source_edit_plan_id 时的回调 */ + onSourceEditPlanIdExtracted?: (planId: string) => void } export function useStep6Cover({ @@ -38,6 +40,7 @@ export function useStep6Cover({ selectedTemplate = "", titleSettings, onPreviewTaskCreated, + onSourceEditPlanIdExtracted, }: UseStep6CoverProps) { const [generating, setGenerating] = useState(false) // 防竞态:记录当前预览生成的参数指纹,任务完成时校验一致性 @@ -181,6 +184,10 @@ export function useStep6Cover({ const currentFingerprint = JSON.stringify({ selectedTemplate, assetIds, titleSettings }) if (previewResp.task_id && previewParamsRef.current === currentFingerprint) { onPreviewTaskCreated?.(previewResp.task_id) + // 提取后端自动关联的 source_edit_plan_id,供 fallback 路径使用 + if (previewResp.source_edit_plan_id) { + onSourceEditPlanIdExtracted?.(previewResp.source_edit_plan_id) + } } // 轮询等待预览渲染完成:递归 setTimeout 避免请求重叠 + 120s 超时兜底 await new Promise((resolve, reject) => { @@ -295,6 +302,7 @@ export function useStep6Cover({ duration, titleSettings, onPreviewTaskCreated, + onSourceEditPlanIdExtracted, ]) // ── 模板操作方法 ── -- 2.54.0