From 396d1450d0c0437c8a1671b8cdd77b84639ae48f Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Wed, 26 Aug 2026 23:20:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=81=A2=E5=A4=8D=E6=AD=A5=E9=AA=A44-6?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E9=A2=84=E8=A7=88=EF=BC=8C=E6=AD=A5=E9=AA=A4?= =?UTF-8?q?7=E6=98=BE=E7=A4=BA=E6=9C=80=E7=BB=88=E8=A7=86=E9=A2=91?= =?UTF-8?q?=EF=BC=8C=E6=AD=A5=E9=AA=A41-3=E5=8F=B3=E4=BE=A7=E7=95=99?= =?UTF-8?q?=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 步骤1-3(模板/素材/配音):右侧不显示内容,左侧占满宽度 - 步骤4-6(标题/预览/封面):恢复 FrontendPreviewPlayer 实时预览 - 步骤7(确认生成):右侧只显示最终视频,内联播放不弹窗 - 恢复 TTS 预览音频逻辑(previewVoiceAudioUrl) - 添加 .full-width 布局类,步骤1-3 自动隐藏右侧栏 - 生成数量代码已在 PR #1511 全链路删除,保持不变 --- apps/web/src/pages/generate/GeneratePage.tsx | 146 ++++++++++--------- apps/web/src/pages/generate/generate.css | 8 + 2 files changed, 85 insertions(+), 69 deletions(-) diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index a53592397..fd9787faa 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -4,14 +4,15 @@ * 左右布局:左侧 generate-form + 右侧 generate-preview * * 架构: - * - 右侧展示生成的视频,内联播放 - * - + * - 步骤 4-6 右侧显示 FrontendPreviewPlayer 实时预览 + * - 步骤 7 右侧内联播放生成的最终视频 * - 点"确认生成"时调用 createGenerationTask 创建一次服务器渲染任务 */ -import React, { useMemo } from "react" +import React, { useMemo, useState, useEffect, useRef } from "react" import { message } from "antd" import { useNavigate } from "react-router-dom" import type { VoiceClone } from "@/api/voice-clone" +import { useQuery } from "@tanstack/react-query" import { useCloneProgress } from "@/hooks/useCloneProgress" import CloneModal from "@/components/voice/CloneModal" import GenerateHeader from "./components/GenerateHeader" @@ -19,6 +20,7 @@ import { calculateTotalVideoDuration, estimateTotalVideoDuration, } from "./utils/calculateTotalVideoDuration" +import FrontendPreviewPlayer from "./components/FrontendPreviewPlayer" import GenerateStepsBar from "./components/GenerateStepsBar" import GenerateStepContent from "./components/GenerateStepContent" import GenerateStepActions from "./components/GenerateStepActions" @@ -27,6 +29,8 @@ import { useStepNavigation } from "./hooks/useStepNavigation" import { useGenerateVideo } from "./hooks/useGenerateVideo" import { usePreviewAssets } from "./hooks/usePreviewAssets" import { useTitleStyleUpdaters } from "./hooks/useStep4Title/useTitleStyleUpdaters" +import { getAssetsByKind } from "@/api/assets" +import { previewTts } from "@/api/tts" import "./generate.css" const GeneratePage: React.FC = () => { @@ -78,6 +82,53 @@ const GeneratePage: React.FC = () => { onTitleSettingsChange: setTitleSettings, }) + /* ── 配音素材库(TTS 试听)── */ + const { data: voiceMaterials = [] } = useQuery({ + queryKey: ["assets", "voice"], + queryFn: () => getAssetsByKind("voice", { limit: 50 }), + }) + + const [previewVoiceAudioUrl, setPreviewVoiceAudioUrl] = useState(null) + const ttsAbortRef = useRef(null) + + useEffect(() => { + const voiceAsset = voiceMaterials.find((m) => m.id === selectedVoice) + if (voiceAsset?.file_url) { + setPreviewVoiceAudioUrl(voiceAsset.file_url) + return + } + + const voiceId = selectedClonedVoice || selectedVoice + if (!voiceId || !titleSettings.title) { + setPreviewVoiceAudioUrl(null) + return + } + + ttsAbortRef.current?.abort() + const controller = new AbortController() + ttsAbortRef.current = controller + let cancelled = false + + previewTts({ text: titleSettings.title, voice_id: voiceId }) + .then((res) => { + if (!cancelled && res.audio_url) { + setPreviewVoiceAudioUrl(res.audio_url) + } + }) + .catch((err) => { + if (!cancelled) { + console.warn("[预览配音生成失败]", err) + setPreviewVoiceAudioUrl(null) + } + }) + + return () => { + cancelled = true + controller.abort() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedVoice, selectedClonedVoice, titleSettings.title, voiceMaterials]) + /* ── 克隆声音 ── */ const { clones: clonedVoices, addClone, hasProcessing } = useCloneProgress() @@ -176,7 +227,7 @@ const GeneratePage: React.FC = () => { -
+
{/* ════ 左侧:表单区 ════ */}
{ />
- {/* ════ 右侧:生成结果视频 ════ */} + {/* ════ 右侧:步骤 4-6 实时预览,步骤 7 最终视频 ════ */}
- {generated && generatedVideos.length > 0 ? ( + {currentStep >= 4 && currentStep <= 6 && !!currentTemplate && ( + 0} + voiceAudioUrl={previewVoiceAudioUrl || undefined} + titleSettings={{ + title: titleSettings.title, + size: titleSettings.size, + font: titleSettings.font, + color: titleSettings.color, + position: titleSettings.position as "top" | "center" | "bottom", + bold: titleSettings.bold, + italic: titleSettings.italic, + stroke: titleSettings.stroke, + shadow: titleSettings.shadow, + }} + /> + )} + {currentStep === 7 && generated && generatedVideos.length > 0 && (
- ) : generating ? ( -
-
🎬
-
视频生成中...
-
- 预计还需 1-2 分钟,请稍候 -
-
-
-
-
- {Math.round(progress)}% -
-
- ) : generateError ? ( -
-
-
- 生成失败 -
-
- {typeof generateError === "string" ? generateError : "请重试"} -
- -
- ) : ( -
-
🎥
-
- 完成配置后点击「确认生成」 -
-
- 生成的视频将在这里展示 -
-
)}
diff --git a/apps/web/src/pages/generate/generate.css b/apps/web/src/pages/generate/generate.css index efe195e65..c2fbd8b32 100644 --- a/apps/web/src/pages/generate/generate.css +++ b/apps/web/src/pages/generate/generate.css @@ -113,6 +113,14 @@ align-items: start; } +.xx-generate-layout.full-width { + grid-template-columns: 1fr; +} + +.xx-generate-layout.full-width .xx-generate-right-col { + display: none; +} + /* ============================================================ 左侧表单区 generate-form ============================================================ */ -- 2.54.0