diff --git a/apps/web/src/pages/generate/hooks/useStep4Preview.ts b/apps/web/src/pages/generate/hooks/useStep4Preview.ts index fea5ac525..cbbe8342a 100644 --- a/apps/web/src/pages/generate/hooks/useStep4Preview.ts +++ b/apps/web/src/pages/generate/hooks/useStep4Preview.ts @@ -212,99 +212,102 @@ export function useStep4Preview({ }, [clearPollTimer]) /** 轮询单个预览任务状态 */ - const pollPreviewStatus = useCallback((index: number, taskId: string) => { - const poll = async () => { - // 竞态检查 - if (taskIdsRef.current.get(index) !== taskId) return - - // 超时检查 - if (Date.now() - startTimeRef.current > POLL_TIMEOUT_MS) { - setItems((prev) => - prev.map((it) => - it.index === index ? { ...it, status: "error", error: "预览生成超时,请重试" } : it, - ), - ) - return - } - - try { - const data: PreviewTaskResponse = await getPreviewStatus(taskId) - + const pollPreviewStatus = useCallback( + (index: number, taskId: string) => { + const poll = async () => { + // 竞态检查 if (taskIdsRef.current.get(index) !== taskId) return - const status = data.status as ApiPreviewStatus + // 超时检查 + if (Date.now() - startTimeRef.current > POLL_TIMEOUT_MS) { + setItems((prev) => + prev.map((it) => + it.index === index ? { ...it, status: "error", error: "预览生成超时,请重试" } : it, + ), + ) + return + } - if (status === "completed") { - const result: PreviewResult = { - taskId: safeString(data.task_id, ""), - videoUrl: safeString(data.video_url, ""), - clipCount: safeNumber(data.clip_count), - transitionCount: safeNumber(data.transition_count), - materialUsage: safeNumber(data.material_usage), - duration: safeNumber(data.duration), - fileSize: safeNumber(data.file_size), - generateDuration: safeNumber(data.generate_duration), - progress: 100, + try { + const data: PreviewTaskResponse = await getPreviewStatus(taskId) + + if (taskIdsRef.current.get(index) !== taskId) return + + const status = data.status as ApiPreviewStatus + + if (status === "completed") { + const result: PreviewResult = { + taskId: safeString(data.task_id, ""), + videoUrl: safeString(data.video_url, ""), + clipCount: safeNumber(data.clip_count), + transitionCount: safeNumber(data.transition_count), + materialUsage: safeNumber(data.material_usage), + duration: safeNumber(data.duration), + fileSize: safeNumber(data.file_size), + generateDuration: safeNumber(data.generate_duration), + progress: 100, + } + setItems((prev) => + prev.map((it) => + it.index === index ? { ...it, status: "ready", result, progress: 100 } : it, + ), + ) + + // 保存预览视频 URL 到 plan config,供封面生成使用 + if (result.videoUrl && selectedTemplate) { + updateEditPlan(selectedTemplate, { + config: { rendered_storage_key: result.videoUrl }, + }).catch((err) => { + console.warn("[Step4] 保存预览视频URL到plan config失败:", err) + }) + } + return } - setItems((prev) => - prev.map((it) => - it.index === index ? { ...it, status: "ready", result, progress: 100 } : it, - ), - ) - // 保存预览视频 URL 到 plan config,供封面生成使用 - if (result.videoUrl && selectedTemplate) { - updateEditPlan(selectedTemplate, { - config: { rendered_storage_key: result.videoUrl }, - }).catch((err) => { - console.warn("[Step4] 保存预览视频URL到plan config失败:", err) - }) + if (status === "failed") { + setItems((prev) => + prev.map((it) => + it.index === index + ? { + ...it, + status: "error", + error: safeString(data.error_message, "预览生成失败,请重试"), + } + : it, + ), + ) + return } - return - } - if (status === "failed") { + if (status === "cancelled") { + setItems((prev) => + prev.map((it) => + it.index === index ? { ...it, status: "error", error: "预览任务已取消" } : it, + ), + ) + return + } + + // pending / generating 状态继续轮询 + const prog = safeNumber(data.progress) + const nextStatus: PreviewStatus = status === "pending" ? "pending" : "generating" setItems((prev) => prev.map((it) => - it.index === index - ? { - ...it, - status: "error", - error: safeString(data.error_message, "预览生成失败,请重试"), - } - : it, + it.index === index ? { ...it, status: nextStatus, progress: prog } : it, ), ) - return + const delay = status === "pending" ? 5000 : 2000 + pollTimersRef.current.set(index, setTimeout(poll, delay)) + } catch { + if (taskIdsRef.current.get(index) !== taskId) return + pollTimersRef.current.set(index, setTimeout(poll, 3000)) } - - if (status === "cancelled") { - setItems((prev) => - prev.map((it) => - it.index === index ? { ...it, status: "error", error: "预览任务已取消" } : it, - ), - ) - return - } - - // pending / generating 状态继续轮询 - const prog = safeNumber(data.progress) - const nextStatus: PreviewStatus = status === "pending" ? "pending" : "generating" - setItems((prev) => - prev.map((it) => - it.index === index ? { ...it, status: nextStatus, progress: prog } : it, - ), - ) - const delay = status === "pending" ? 5000 : 2000 - pollTimersRef.current.set(index, setTimeout(poll, delay)) - } catch { - if (taskIdsRef.current.get(index) !== taskId) return - pollTimersRef.current.set(index, setTimeout(poll, 3000)) } - } - pollTimersRef.current.set(index, setTimeout(poll, 1000)) - }, [selectedTemplate]) + pollTimersRef.current.set(index, setTimeout(poll, 1000)) + }, + [selectedTemplate], + ) /** 生成所有预览 */ const generatePreview = useCallback(async () => { diff --git a/apps/web/src/pages/generate/hooks/useStep6Cover.ts b/apps/web/src/pages/generate/hooks/useStep6Cover.ts index 879103213..e76c5146a 100644 --- a/apps/web/src/pages/generate/hooks/useStep6Cover.ts +++ b/apps/web/src/pages/generate/hooks/useStep6Cover.ts @@ -110,7 +110,11 @@ export function useStep6Cover({ // 提取详细错误信息 let errorMsg = "封面生成失败" - const e = err as { response?: { data?: { detail?: string; message?: string }; status?: number }; request?: unknown; message?: string } + const e = err as { + response?: { data?: { detail?: string; message?: string }; status?: number } + request?: unknown + message?: string + } if (e.response) { // 后端返回错误 const detail = e.response.data?.detail || e.response.data?.message || ""