fix: Step7 确认生成优先调用 confirmGeneration 复用预览产物,解决卡 10% 问题
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 49s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 49s
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Failing after 1m48s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m48s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m55s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m8s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m13s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m19s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m35s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 3m44s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled

根因:前端调用 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
This commit is contained in:
xiaoxia
2026-08-24 15:02:46 +08:00
parent c873bb635f
commit dffb642a68
7 changed files with 89 additions and 28 deletions
@@ -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}
@@ -76,6 +76,8 @@ export interface GenerateStepContentProps {
onDismissError: () => void
/* 其他 */
presetVoices: PresetVoiceItem[]
/** 预览任务创建回调——传递给 Step6CoverSettings */
onPreviewTaskCreated?: (taskId: string) => void
}
export const GenerateStepContent: React.FC<GenerateStepContentProps> = (props) => {
@@ -121,6 +123,7 @@ export const GenerateStepContent: React.FC<GenerateStepContentProps> = (props) =
onRetry,
onDismissError,
presetVoices,
onPreviewTaskCreated,
} = props
/* 当前模板的 segments,传给 Step2 构建 clips */
@@ -190,6 +193,7 @@ export const GenerateStepContent: React.FC<GenerateStepContentProps> = (props) =
assetIds={materialMode === "auto" ? smartSelectedIds : selectedMaterials}
selectedTemplate={selectedTemplate}
titleSettings={titleSettings}
onPreviewTaskCreated={onPreviewTaskCreated}
/>
)
case 7:
@@ -16,6 +16,8 @@ interface Step6CoverSettingsProps {
selectedTemplate?: string
/** Step4 标题设置,用于预览视频烧录标题 & 封面叠加标题 */
titleSettings?: import("../types").TitleSettings
/** 预览任务创建回调——将 task_id 暴露给父组件供 confirmGeneration 复用 */
onPreviewTaskCreated?: (taskId: string) => void
}
const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
@@ -43,6 +45,7 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
assetIds: props.assetIds,
selectedTemplate: props.selectedTemplate,
titleSettings: props.titleSettings,
onPreviewTaskCreated: props.onPreviewTaskCreated,
})
const handleAutoGenerate = () => {
@@ -21,6 +21,8 @@ export interface UseGenerateVideoProps {
generateCount: number
/** 当前草稿 IDURL 参数 edit_plan_id,用于后端回写任务关联) */
sourceEditPlanId?: string | null
/** 预览任务 ID(由 useStep6Cover 创建后写入,供 confirmGeneration 复用预览产物) */
previewTaskId?: string | null
}
/** 生成阶段 */
@@ -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<GeneratedVideo | null>(null)
const [previewModalOpen, setPreviewModalOpen] = useState(false)
/* ── 预览任务 IDuseStep6Cover 创建预览时写入,useGenerateVideo 复用) ── */
const [previewTaskId, setPreviewTaskId] = useState<string | null>(null)
/* ── 从 URL / 编辑计划加载配置 ── */
usePlanConfigLoader({
editPlanId,
@@ -208,5 +215,7 @@ export const useGenerateFormState = (): GenerateFormState => {
setPreviewVideo,
previewModalOpen,
setPreviewModalOpen,
previewTaskId,
setPreviewTaskId,
}
}
@@ -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,请稍后在任务列表查看")
}
@@ -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<void>((resolve, reject) => {
let finished = false
@@ -282,6 +289,7 @@ export function useStep6Cover({
generating,
duration,
titleSettings,
onPreviewTaskCreated,
])
// ── 模板操作方法 ──