diff --git a/apps/web/src/pages/generate/components/PreviewVideoPanel.tsx b/apps/web/src/pages/generate/components/PreviewVideoPanel.tsx index b3f00cea8..e7cb8d336 100644 --- a/apps/web/src/pages/generate/components/PreviewVideoPanel.tsx +++ b/apps/web/src/pages/generate/components/PreviewVideoPanel.tsx @@ -68,7 +68,9 @@ export const PreviewVideoPanel: React.FC = ({

预览生成失败

-

{previewError || "请重试"}

+

+ {typeof previewError === "string" && previewError ? previewError : "请重试"} +

@@ -87,7 +89,10 @@ export const PreviewVideoPanel: React.FC = ({
时长 - {previewResult.duration.toFixed(1)} 秒 + + {(typeof previewResult.duration === "number" ? previewResult.duration : 0).toFixed(1)}{" "} + 秒 +
片段数 diff --git a/apps/web/src/pages/generate/components/Step3GeneratePreview.tsx b/apps/web/src/pages/generate/components/Step3GeneratePreview.tsx index cffc4c8b9..4505f67eb 100644 --- a/apps/web/src/pages/generate/components/Step3GeneratePreview.tsx +++ b/apps/web/src/pages/generate/components/Step3GeneratePreview.tsx @@ -86,7 +86,9 @@ const Step3GeneratePreview: React.FC = ({

预览生成失败

-

{previewError || "请稍后重试"}

+

+ {typeof previewError === "string" && previewError ? previewError : "请稍后重试"} +

@@ -145,7 +147,11 @@ const Step3GeneratePreview: React.FC = ({
实际时长 - {previewResult.duration.toFixed(1)} 秒 + {(typeof previewResult.duration === "number" + ? previewResult.duration + : 0 + ).toFixed(1)}{" "} + 秒
diff --git a/apps/web/src/pages/generate/hooks/useStep3Preview.ts b/apps/web/src/pages/generate/hooks/useStep3Preview.ts index aa8274d65..8cfbcc1f8 100644 --- a/apps/web/src/pages/generate/hooks/useStep3Preview.ts +++ b/apps/web/src/pages/generate/hooks/useStep3Preview.ts @@ -6,6 +6,24 @@ import { useState, useCallback, useMemo, useEffect, useRef } from "react" import { createPreview, getPreviewStatus } from "@/api/generation" import type { PreviewTaskResponse, PreviewStatus as ApiPreviewStatus } from "@/api/generation" import type { EditingTemplate } from "@/api/editing-planner" +import { safeExtractError } from "./generate-video/errorUtils" + +/** 安全地将值转为字符串,防止对象被直接渲染导致 React Error #31 */ +const safeString = (val: unknown, fallback: string): string => { + if (val == null) return fallback + const s = safeExtractError(val) + return s || fallback +} + +/** 安全地将值转为数字,防止非数字值进入渲染 */ +const safeNumber = (val: unknown, fallback = 0): number => { + if (typeof val === "number" && !Number.isNaN(val)) return val + if (typeof val === "string") { + const n = Number(val) + return Number.isNaN(n) ? fallback : n + } + return fallback +} interface UseStep3PreviewProps { templates: EditingTemplate[] @@ -165,14 +183,14 @@ export function useStep3Preview({ if (status === "completed") { setProgress(100) setPreviewResult({ - taskId: data.task_id, - videoUrl: data.video_url ?? "", - clipCount: data.clip_count ?? 0, - transitionCount: data.transition_count ?? 0, - materialUsage: data.material_usage ?? 0, - duration: data.duration ?? 0, - fileSize: data.file_size ?? 0, - generateDuration: data.generate_duration ?? 0, + 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, }) setPreviewStatus("ready") @@ -180,7 +198,7 @@ export function useStep3Preview({ } if (status === "failed") { - setPreviewError(data.error_message || "预览生成失败,请重试") + setPreviewError(safeString(data.error_message, "预览生成失败,请重试")) setPreviewStatus("error") return } @@ -192,7 +210,7 @@ export function useStep3Preview({ } // pending / generating 状态继续轮询 - setProgress(data.progress || 0) + setProgress(safeNumber(data.progress)) if (status === "pending") { setPreviewStatus("pending") pollTimerRef.current = setTimeout(poll, 5000) @@ -248,7 +266,7 @@ export function useStep3Preview({ currentTaskIdRef.current = response.task_id pollPreviewStatus(response.task_id) } catch (e) { - setPreviewError(e instanceof Error ? e.message : "预览生成失败") + setPreviewError(safeString(e instanceof Error ? e.message : e, "预览生成失败")) setPreviewStatus("error") } }, [