diff --git a/apps/web/src/pages/generate/hooks/useStep3Preview.ts b/apps/web/src/pages/generate/hooks/useStep3Preview.ts index 088d7e66b..1e4f13c69 100644 --- a/apps/web/src/pages/generate/hooks/useStep3Preview.ts +++ b/apps/web/src/pages/generate/hooks/useStep3Preview.ts @@ -1,8 +1,9 @@ /** * Step 3 生成预览 Hook - * 封装预览信息的计算逻辑 + * 封装预览信息计算 + 预览生成逻辑 + * TODO: 后端预览接口对齐后,替换 mock 逻辑为真实 API 调用 */ -import { useMemo } from "react" +import { useState, useCallback, useMemo } from "react" import type { EditingTemplate } from "@/api/editing-planner" interface UseStep3PreviewProps { @@ -15,6 +16,22 @@ interface UseStep3PreviewProps { videoRatio: string } +export type PreviewStatus = "idle" | "generating" | "ready" | "error" + +/** 预览生成结果 */ +export interface PreviewResult { + /** 预计片段数 */ + clipCount: number + /** 预计转场数 */ + transitionCount: number + /** 素材使用率 */ + materialUsage: number + /** 预览视频URL(后端接口出来后填充) */ + previewVideoUrl: string | null + /** 生成耗时(秒) */ + generateDuration: number +} + export function useStep3Preview({ templates, selectedTemplate, @@ -36,11 +53,70 @@ export function useStep3Preview({ return `${selectedMaterials.length} 个素材` }, [materialMode, selectedMaterials.length, smartSelectedIds.length]) + const materialTotal = materialMode === "auto" ? smartSelectedIds.length : selectedMaterials.length + + /* ── 预览生成状态 ── */ + const [previewStatus, setPreviewStatus] = useState("idle") + const [previewResult, setPreviewResult] = useState(null) + const [previewError, setPreviewError] = useState("") + + /** + * 生成预览 + * TODO: 后端 /generation-preview 接口对齐后替换此处 mock 逻辑 + * 预计接口:POST /generate-preview + * 入参:template_id, material_ids, duration, video_ratio, style + * 出参:preview_id, status, clip_count, preview_video_url + */ + const generatePreview = useCallback(async () => { + if (!selectedTemplate) return + if (materialTotal === 0) return + + setPreviewStatus("generating") + setPreviewError("") + + try { + // TODO: 替换为真实后端接口调用 + // const res = await generatePreviewAPI({ templateId: selectedTemplate, ... }) + await new Promise((resolve) => setTimeout(resolve, 1500)) + + // Mock 结果 + const estimatedClips = Math.max(3, Math.min(materialTotal * 2, 20)) + const result: PreviewResult = { + clipCount: estimatedClips, + transitionCount: estimatedClips - 1, + materialUsage: Math.min(100, Math.round((materialTotal / Math.max(estimatedClips, 1)) * 100)), + previewVideoUrl: null, // 后端接口出来后填充 + generateDuration: 1.5, + } + + setPreviewResult(result) + setPreviewStatus("ready") + } catch (e) { + setPreviewError(e instanceof Error ? e.message : "预览生成失败") + setPreviewStatus("error") + } + }, [selectedTemplate, materialTotal]) + + /** 重新生成预览 */ + const regeneratePreview = useCallback(() => { + generatePreview() + }, [generatePreview]) + + /** 是否可以下一步(预览已生成) */ + const canProceed = previewStatus === "ready" + return { templateName, materialCount, duration, videoRatio, + // 预览生成状态 + previewStatus, + previewResult, + previewError, + canProceed, + generatePreview, + regeneratePreview, } }