From bc84dedeb732f02eb1a286aedd30bd42e236dcd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=89=8D=E7=AB=AFAgent?= Date: Fri, 31 Jul 2026 16:17:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(#1197):=20Phase1=20=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=89=8D=E7=AB=AF=E6=94=B9=E9=80=A0=20-=20?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=E6=8E=A5=E5=8F=A3=E5=AF=B9=E6=8E=A5+?= =?UTF-8?q?=E7=AB=96=E5=B1=8F9:16+=E5=8F=B3=E4=BE=A7=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E5=B8=B8=E9=A9=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 /api/generation 层:preview 创建/查询接口 + 类型定义 - useStep3Preview 重写:mock 替换为真实 /generation/preview 接口 + 轮询状态机(pending/generating/completed/failed/cancelled) - Step3GeneratePreview 组件:加视频播放器 + 进度条 + 排队中/生成中/失败/成功 四态 - 新增 PreviewVideoPanel:右侧预览视频常驻面板(Step3+ 显示) - videoRatio 默认值从 16:9 改为 9:16(竖屏) - 生成页右侧布局:预览面板 + 正式结果面板上下排列 - 补 smoke test 覆盖新增模块 --- apps/web/src/api/generation/index.ts | 8 + apps/web/src/api/generation/preview.ts | 16 ++ apps/web/src/api/generation/types.ts | 47 +++++ apps/web/src/pages/generate/GeneratePage.tsx | 47 +++-- .../components/GenerateStepContent.tsx | 3 + .../generate/components/PreviewVideoPanel.tsx | 106 ++++++++++ .../components/Step3GeneratePreview.tsx | 53 +++-- apps/web/src/pages/generate/generate.css | 140 +++++++++++++ .../hooks/useGenerateFormState/index.ts | 2 +- .../pages/generate/hooks/useStep3Preview.ts | 185 +++++++++++++----- .../src/test/pages/generate/smoke.test.tsx | 11 ++ 11 files changed, 541 insertions(+), 77 deletions(-) create mode 100644 apps/web/src/api/generation/index.ts create mode 100644 apps/web/src/api/generation/preview.ts create mode 100644 apps/web/src/api/generation/types.ts create mode 100644 apps/web/src/pages/generate/components/PreviewVideoPanel.tsx diff --git a/apps/web/src/api/generation/index.ts b/apps/web/src/api/generation/index.ts new file mode 100644 index 000000000..a3785acc7 --- /dev/null +++ b/apps/web/src/api/generation/index.ts @@ -0,0 +1,8 @@ +export type { + PreviewStatus, + CreatePreviewRequest, + CreatePreviewResponse, + PreviewTaskResponse, +} from "./types" + +export { createPreview, getPreviewStatus } from "./preview" diff --git a/apps/web/src/api/generation/preview.ts b/apps/web/src/api/generation/preview.ts new file mode 100644 index 000000000..4084eac1c --- /dev/null +++ b/apps/web/src/api/generation/preview.ts @@ -0,0 +1,16 @@ +import apiClient from "../client" +import type { CreatePreviewRequest, CreatePreviewResponse, PreviewTaskResponse } from "./types" + +/** 创建预览生成任务(单版本) */ +export const createPreview = async ( + params: CreatePreviewRequest, +): Promise => { + const response = await apiClient.post("/generation/preview", params) + return response.data +} + +/** 查询预览任务状态及结果 */ +export const getPreviewStatus = async (taskId: string): Promise => { + const response = await apiClient.get(`/generation/preview/${taskId}`) + return response.data +} diff --git a/apps/web/src/api/generation/types.ts b/apps/web/src/api/generation/types.ts new file mode 100644 index 000000000..502d19df0 --- /dev/null +++ b/apps/web/src/api/generation/types.ts @@ -0,0 +1,47 @@ +/** 预览任务状态 */ +export type PreviewStatus = "pending" | "generating" | "completed" | "failed" | "cancelled" + +/** 创建预览任务请求 */ +export interface CreatePreviewRequest { + template_id: string + asset_ids: string[] + title_ids?: string[] + voice_ids?: string[] + video_title?: string + duration?: number + video_ratio?: string + bgm_config?: { + enabled: boolean + preset_id?: string + volume?: number + } +} + +/** 创建预览任务响应 */ +export interface CreatePreviewResponse { + task_id: string + status: PreviewStatus + is_preview: boolean + resolution: string + created_at: string +} + +/** 预览任务详情响应 */ +export interface PreviewTaskResponse { + task_id: string + status: PreviewStatus + progress: number + is_preview: boolean + resolution: string + video_url?: string + duration?: number + file_size?: number + clip_count?: number + transition_count?: number + material_usage?: number + error_message?: string + created_at: string + started_at?: string + finished_at?: string + generate_duration?: number +} diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index 36ae84d81..3b80e207d 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -18,6 +18,7 @@ import CloneModal from "@/components/voice/CloneModal" import GenerateHeader from "./components/GenerateHeader" import GenerateStepsBar from "./components/GenerateStepsBar" import GenerateResultPanel from "./components/GenerateResultPanel" +import PreviewVideoPanel from "./components/PreviewVideoPanel" import GenerateStepContent from "./components/GenerateStepContent" import GenerateStepActions from "./components/GenerateStepActions" import { useGenerateFormState } from "./hooks/useGenerateFormState" @@ -191,6 +192,7 @@ const GeneratePage: React.FC = () => { previewError={step3Preview.previewError} previewTemplateName={step3Preview.templateName} previewMaterialCount={step3Preview.materialCount} + previewProgress={step3Preview.progress} onGeneratePreview={step3Preview.generatePreview} onRegeneratePreview={step3Preview.regeneratePreview} /> @@ -206,21 +208,36 @@ const GeneratePage: React.FC = () => { /> - {/* ════ 右侧:生成结果 ════ */} - { - setPreviewVideo(video) - setPreviewModalOpen(true) - }} - onDownload={handleDownload} - onShare={handleShare} - onGoToLibrary={() => navigate("/app/products")} - /> + {/* ════ 右侧:预览 + 生成结果 ════ */} +
+ {/* 预览视频面板(Step3+ 常驻) */} + {currentStep >= 3 && ( + + )} + + {/* 正式生成结果 */} + { + setPreviewVideo(video) + setPreviewModalOpen(true) + }} + onDownload={handleDownload} + onShare={handleShare} + onGoToLibrary={() => navigate("/app/products")} + /> +
{/* ── 视频预览弹窗 ── */} diff --git a/apps/web/src/pages/generate/components/GenerateStepContent.tsx b/apps/web/src/pages/generate/components/GenerateStepContent.tsx index b5bf728f5..007b85b02 100644 --- a/apps/web/src/pages/generate/components/GenerateStepContent.tsx +++ b/apps/web/src/pages/generate/components/GenerateStepContent.tsx @@ -69,6 +69,7 @@ export interface GenerateStepContentProps { previewError: string previewTemplateName: string previewMaterialCount: string + previewProgress: number onGeneratePreview: () => void onRegeneratePreview: () => void } @@ -117,6 +118,7 @@ export const GenerateStepContent: React.FC = (props) = previewError, previewTemplateName, previewMaterialCount, + previewProgress, onGeneratePreview, onRegeneratePreview, } = props @@ -151,6 +153,7 @@ export const GenerateStepContent: React.FC = (props) = previewStatus={previewStatus} previewResult={previewResult} previewError={previewError} + progress={previewProgress} onGeneratePreview={onGeneratePreview} onRegeneratePreview={onRegeneratePreview} /> diff --git a/apps/web/src/pages/generate/components/PreviewVideoPanel.tsx b/apps/web/src/pages/generate/components/PreviewVideoPanel.tsx new file mode 100644 index 000000000..b3f00cea8 --- /dev/null +++ b/apps/web/src/pages/generate/components/PreviewVideoPanel.tsx @@ -0,0 +1,106 @@ +/** + * 右侧预览视频面板 + * Step3 生成预览后常驻显示预览视频 + */ +import React from "react" +import { PlayCircleOutlined, LoadingOutlined } from "@ant-design/icons" +import type { PreviewResult, PreviewStatus } from "../hooks/useStep3Preview" + +interface PreviewVideoPanelProps { + previewStatus: PreviewStatus + previewResult: PreviewResult | null + previewError: string + progress: number + videoRatio: string + onRegenerate: () => void +} + +export const PreviewVideoPanel: React.FC = ({ + previewStatus, + previewResult, + previewError, + progress, + videoRatio, + onRegenerate, +}) => { + const hasPreview = previewStatus === "ready" && previewResult + const isLoading = previewStatus === "pending" || previewStatus === "generating" + const isError = previewStatus === "error" + + return ( +
+
+

预览视频

+ {hasPreview && 480p 预览版} +
+ + {/* 空状态:还没生成预览 */} + {previewStatus === "idle" && ( +
+ +

暂无预览

+

在第 3 步生成预览后在此查看

+
+ )} + + {/* 生成中 */} + {isLoading && ( +
+
+
+ +

+ {previewStatus === "pending" ? "排队中..." : `生成中 ${progress}%`} +

+
+
+
+
+
+
+ )} + + {/* 生成失败 */} + {isError && ( +
+
+

预览生成失败

+
+

{previewError || "请重试"}

+ +
+ )} + + {/* 预览成功 */} + {hasPreview && ( +
+
+ )} + + {/* 预览信息 */} + {hasPreview && previewResult && ( +
+
+ 时长 + {previewResult.duration.toFixed(1)} 秒 +
+
+ 片段数 + {previewResult.clipCount} 段 +
+
+ 比例 + {videoRatio} +
+
+ )} +
+ ) +} + +export default PreviewVideoPanel diff --git a/apps/web/src/pages/generate/components/Step3GeneratePreview.tsx b/apps/web/src/pages/generate/components/Step3GeneratePreview.tsx index c2551982d..766ecd2de 100644 --- a/apps/web/src/pages/generate/components/Step3GeneratePreview.tsx +++ b/apps/web/src/pages/generate/components/Step3GeneratePreview.tsx @@ -1,7 +1,6 @@ /** * Step 3 生成预览组件 - * 展示剪辑方案预览,用户确认后进入下一步 - * TODO: 后端预览接口对齐后,替换 mock 预览为真实视频/方案预览 + * 调用后端预览生成接口,展示真实视频预览 */ import React from "react" import { @@ -10,6 +9,7 @@ import { ReloadOutlined, PlayCircleOutlined, ExclamationCircleFilled, + ClockCircleOutlined, } from "@ant-design/icons" import type { PreviewResult, PreviewStatus } from "../hooks/useStep3Preview" @@ -21,6 +21,7 @@ interface Step3GeneratePreviewProps { previewStatus: PreviewStatus previewResult: PreviewResult | null previewError: string + progress: number onGeneratePreview: () => void onRegeneratePreview: () => void } @@ -33,6 +34,7 @@ const Step3GeneratePreview: React.FC = ({ previewStatus, previewResult, previewError, + progress, onGeneratePreview, onRegeneratePreview, }) => { @@ -47,7 +49,7 @@ const Step3GeneratePreview: React.FC = ({

一键生成剪辑预览

- AI 将根据您选择的模板和素材,智能生成剪辑方案预览 + AI 将根据您选择的模板和素材,智能生成完整视频预览(480p 低清版)

+ {/* 视频播放器 */} +
+
+ + {/* 剪辑方案信息 */}
-
剪辑方案预览
+
剪辑方案信息
模板 @@ -107,7 +132,7 @@ const Step3GeneratePreview: React.FC = ({ {materialCount}
- 预计片段数 + 片段数 {previewResult.clipCount} 段
@@ -119,17 +144,17 @@ const Step3GeneratePreview: React.FC = ({ {previewResult.materialUsage}%
- 预计时长 - {duration} 秒 + 实际时长 + + {previewResult.duration.toFixed(1)} 秒 +
视频比例 {videoRatio}
-
- 💡 点击「下一步」进入标题设置,AI 将根据素材内容为您推荐标题 -
+
💡 这是 480p 预览版,正式生成将输出高清视频
)} diff --git a/apps/web/src/pages/generate/generate.css b/apps/web/src/pages/generate/generate.css index b7317e851..f79cd5c8d 100644 --- a/apps/web/src/pages/generate/generate.css +++ b/apps/web/src/pages/generate/generate.css @@ -2544,6 +2544,146 @@ 生成结果(右侧) ================================================================ */ +.xx-generate-right-col { + display: flex; + flex-direction: column; + gap: 16px; +} + +.xx-preview-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.xx-preview-header h3 { + margin: 0; + font-size: 16px; + font-weight: 600; +} + +.xx-preview-badge { + font-size: 12px; + color: var(--text-secondary); + background: var(--bg-tertiary); + padding: 2px 8px; + border-radius: 12px; +} + +.xx-preview-empty { + text-align: center; + padding: 40px 0; +} + +.xx-preview-empty-title { + font-size: 14px; + font-weight: 500; + color: var(--text-secondary); + margin: 0 0 4px 0; +} + +.xx-preview-empty-desc { + font-size: 12px; + color: var(--text-tertiary); + margin: 0; +} + +.xx-preview-loading-panel { + text-align: center; +} + +.xx-preview-loading-center { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + position: relative; + z-index: 2; +} + +.xx-preview-progress-bar-wrap { + width: 100%; + height: 4px; + background: var(--bg-tertiary); + border-radius: 2px; + margin-top: 12px; + overflow: hidden; +} + +.xx-preview-progress-fill { + height: 100%; + background: linear-gradient(90deg, #3b82f6, #8b5cf6); + border-radius: 2px; + transition: width 0.3s ease; +} + +.xx-preview-error-panel { + text-align: center; +} + +.xx-preview-video--error { + opacity: 0.7; +} + +.xx-preview-error-msg { + font-size: 13px; + color: #ef4444; + margin: 12px 0; +} + +.xx-preview-info { + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid var(--border-color); + display: flex; + flex-direction: column; + gap: 8px; +} + +.xx-preview-info-row { + display: flex; + justify-content: space-between; + font-size: 13px; + color: var(--text-secondary); +} + +.xx-preview-info-row span:last-child { + color: var(--text-primary); + font-weight: 500; +} + +/* Step3 内部进度条 */ +.xx-preview-loading .xx-preview-progress-bar { + width: 200px; + height: 6px; + background: var(--bg-tertiary); + border-radius: 3px; + margin: 12px auto 0; + overflow: hidden; +} + +.xx-preview-loading .xx-preview-progress-fill { + height: 100%; + background: linear-gradient(90deg, #3b82f6, #8b5cf6); + border-radius: 3px; + transition: width 0.3s ease; +} + +.xx-preview-video-wrapper { + margin: 16px 0; + display: flex; + justify-content: center; +} + +.xx-preview-video-wrapper .xx-preview-video { + max-width: 300px; + width: 100%; + aspect-ratio: 9 / 16; + margin-bottom: 0; +} + .xx-generate-result { background: var(--bg-secondary); border-radius: var(--radius-xl); diff --git a/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts b/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts index 91de56c79..7367a7ef8 100755 --- a/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts +++ b/apps/web/src/pages/generate/hooks/useGenerateFormState/index.ts @@ -137,7 +137,7 @@ export const useGenerateFormState = (): GenerateFormState => { const [generateCount, setGenerateCount] = useState(1) /* ── 高级设置(隐藏但保留) ── */ - const [videoRatio] = useState("16:9") + const [videoRatio] = useState("9:16") const [duration] = useState(30) const [style] = useState("business") const [autoSubtitles] = useState(true) diff --git a/apps/web/src/pages/generate/hooks/useStep3Preview.ts b/apps/web/src/pages/generate/hooks/useStep3Preview.ts index bf75735a9..5c9a942c0 100644 --- a/apps/web/src/pages/generate/hooks/useStep3Preview.ts +++ b/apps/web/src/pages/generate/hooks/useStep3Preview.ts @@ -1,9 +1,10 @@ /** * Step 3 生成预览 Hook - * 封装预览信息计算 + 预览生成逻辑 - * TODO: 后端预览接口对齐后,替换 mock 逻辑为真实 API 调用 + * 调用 /generation/preview 接口创建预览任务,轮询状态直到完成 */ 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" interface UseStep3PreviewProps { @@ -14,22 +15,23 @@ interface UseStep3PreviewProps { smartSelectedIds: string[] duration: number videoRatio: string + titleText?: string + voiceId?: string } -export type PreviewStatus = "idle" | "generating" | "ready" | "error" +export type PreviewStatus = "idle" | "pending" | "generating" | "ready" | "error" /** 预览生成结果 */ export interface PreviewResult { - /** 预计片段数 */ + taskId: string + videoUrl: string clipCount: number - /** 预计转场数 */ transitionCount: number - /** 素材使用率 */ materialUsage: number - /** 预览视频URL(后端接口出来后填充) */ - previewVideoUrl: string | null - /** 生成耗时(秒) */ + duration: number + fileSize: number generateDuration: number + progress: number } export function useStep3Preview({ @@ -59,11 +61,24 @@ export function useStep3Preview({ const [previewStatus, setPreviewStatus] = useState("idle") const [previewResult, setPreviewResult] = useState(null) const [previewError, setPreviewError] = useState("") + const [progress, setProgress] = useState(0) - // 请求 ID 计数器,用于防止竞态条件(旧请求覆盖新状态) - const requestIdRef = useRef(0) + // 任务 ID + 轮询定时器,用于防止竞态条件 + const currentTaskIdRef = useRef(null) + const pollTimerRef = useRef>() + const startTimeRef = useRef(0) - /* ── 关键参数变化时重置预览状态(避免旧数据预览被误用) ── */ + // 轮询超时时间(10 分钟) + const POLL_TIMEOUT_MS = 10 * 60 * 1000 + + const clearPollTimer = useCallback(() => { + if (pollTimerRef.current) { + clearTimeout(pollTimerRef.current) + pollTimerRef.current = undefined + } + }, []) + + /* ── 参数变化时重置预览状态 ── */ const prevDepsRef = useRef({ selectedTemplate, materialMode, @@ -93,11 +108,13 @@ export function useStep3Preview({ ].join("|") if (prevKey !== currentKey && previewStatus !== "idle") { - // 参数变化时递增请求 ID,作废之前进行中的预览生成 - requestIdRef.current += 1 + // 参数变化,作废当前任务 + currentTaskIdRef.current = null + clearPollTimer() setPreviewStatus("idle") setPreviewResult(null) setPreviewError("") + setProgress(0) } prevDepsRef.current = { @@ -116,15 +133,86 @@ export function useStep3Preview({ duration, videoRatio, previewStatus, + clearPollTimer, ]) - /** - * 生成预览 - * TODO: 后端 /generation-preview 接口对齐后替换此处 mock 逻辑 - * 预计接口:POST /generate-preview - * 入参:template_id, material_ids, duration, video_ratio, style - * 出参:preview_id, status, clip_count, preview_video_url - */ + // 组件卸载时清理轮询 + useEffect(() => { + return () => { + clearPollTimer() + } + }, [clearPollTimer]) + + /** 轮询预览任务状态 */ + const pollPreviewStatus = useCallback((taskId: string) => { + const poll = async () => { + // 检查是否已被取消(参数变化或重新生成) + if (currentTaskIdRef.current !== taskId) return + + // 超时检查 + if (Date.now() - startTimeRef.current > POLL_TIMEOUT_MS) { + setPreviewError("预览生成超时,请重试") + setPreviewStatus("error") + return + } + + try { + const data: PreviewTaskResponse = await getPreviewStatus(taskId) + + if (currentTaskIdRef.current !== taskId) return + + const status = data.status as ApiPreviewStatus + + 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, + progress: 100, + }) + setPreviewStatus("ready") + return + } + + if (status === "failed") { + setPreviewError(data.error_message || "预览生成失败,请重试") + setPreviewStatus("error") + return + } + + if (status === "cancelled") { + setPreviewError("预览任务已取消") + setPreviewStatus("error") + return + } + + // pending / generating 状态继续轮询 + setProgress(data.progress || 0) + if (status === "pending") { + setPreviewStatus("pending") + pollTimerRef.current = setTimeout(poll, 5000) + } else { + setPreviewStatus("generating") + pollTimerRef.current = setTimeout(poll, 2000) + } + } catch (pollErr) { + if (currentTaskIdRef.current !== taskId) return + // 轮询出错,延迟后重试 + pollTimerRef.current = setTimeout(poll, 3000) + } + } + + // 首次延迟 1 秒开始轮询 + pollTimerRef.current = setTimeout(poll, 1000) + }, []) + + /** 生成预览 */ const generatePreview = useCallback(async () => { if (!selectedTemplate) { setPreviewError("请先选择模板") @@ -137,42 +225,44 @@ export function useStep3Preview({ return } - // 生成新的请求 ID,用于竞态检查 - const requestId = requestIdRef.current + 1 - requestIdRef.current = requestId + // 取消之前的轮询 + clearPollTimer() - setPreviewStatus("generating") + setPreviewStatus("pending") setPreviewError("") + setProgress(0) + setPreviewResult(null) + startTimeRef.current = Date.now() try { - // TODO: 替换为真实后端接口调用 - // const res = await generatePreviewAPI({ templateId: selectedTemplate, ... }) - await new Promise((resolve) => setTimeout(resolve, 1500)) + const assetIds = materialMode === "auto" ? smartSelectedIds : selectedMaterials + const response = await createPreview({ + template_id: selectedTemplate, + asset_ids: assetIds, + duration: duration || undefined, + video_ratio: videoRatio, + }) - // 竞态检查:如果请求已被新请求或参数变化作废,则丢弃结果 - if (requestIdRef.current !== requestId) return + // 竞态检查 + if (startTimeRef.current === 0) return // 已被重置 - // 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") + currentTaskIdRef.current = response.task_id + pollPreviewStatus(response.task_id) } catch (e) { - if (requestIdRef.current !== requestId) return setPreviewError(e instanceof Error ? e.message : "预览生成失败") setPreviewStatus("error") } - }, [selectedTemplate, materialTotal]) + }, [ + selectedTemplate, + materialTotal, + materialMode, + smartSelectedIds, + selectedMaterials, + duration, + videoRatio, + clearPollTimer, + pollPreviewStatus, + ]) /** 重新生成预览 */ const regeneratePreview = useCallback(() => { @@ -191,6 +281,7 @@ export function useStep3Preview({ previewStatus, previewResult, previewError, + progress, canProceed, generatePreview, regeneratePreview, diff --git a/apps/web/src/test/pages/generate/smoke.test.tsx b/apps/web/src/test/pages/generate/smoke.test.tsx index 753418e38..0171f0629 100755 --- a/apps/web/src/test/pages/generate/smoke.test.tsx +++ b/apps/web/src/test/pages/generate/smoke.test.tsx @@ -5,14 +5,24 @@ * * 重构记录: * - useStep2Materials 拆分为 useMaterialLibrary + useSmartMatch 子 Hook + * - Phase1 预览生成:新增 generation API 层 + PreviewVideoPanel */ import { describe, it, expect } from "vitest" +// API 层 - 预览生成 +import "@/api/generation" +import "@/api/generation/preview" +import "@/api/generation/types" + // 直接引入所有 Step 组件,建立完整依赖链 import "@/pages/generate/GeneratePage" import "@/pages/generate/components/Step2MaterialSelect" +import "@/pages/generate/components/Step3GeneratePreview" import "@/pages/generate/components/Step4TitleSettings" import "@/pages/generate/components/Step5VoiceSelect" +import "@/pages/generate/components/PreviewVideoPanel" +import "@/pages/generate/components/GenerateResultPanel" +import "@/pages/generate/components/GenerateStepContent" import "@/pages/generate/components/voice/VoiceRecommendSection" import "@/pages/generate/components/voice/VoiceChoiceCard" import "@/pages/generate/components/voice/PresetVoiceDetail" @@ -37,6 +47,7 @@ describe("GeneratePage module smoke test", () => { }) }) import "@/pages/generate/hooks/useGenerateVideo" +import "@/pages/generate/hooks/useStep3Preview" import "@/pages/generate/hooks/generate-video/useGenerationPolling" import "@/pages/generate/hooks/useGenerateFormState" import "@/pages/generate/hooks/useGenerateFormState/useTemplateSelection"