From a7deeaa54f9a933cecd4144811c862fa99f8ed7f Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Tue, 25 Aug 2026 13:50:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=A2=84=E8=A7=88=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E7=BC=A9=E5=B0=8F=20+=20=E9=85=8D=E9=9F=B3=E7=BC=BA=E5=A4=B1?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - generate.css: 右侧栏 400px→320px - FrontendPreviewPlayer: 手机框 maxWidth 360→280, borderRadius 28→24 - 中央播放按钮 72→52, fontSize 36→26 - 片段指示器缩小(fontSize 12→10, padding 4px 12px→2px 8px) - GeneratePage: 调用 previewTts 获取配音预览音频 - 根据 selectedVoice/selectedClonedVoice + titleSettings.title 调用 TTS API - 将 audio_url 传给 FrontendPreviewPlayer 的 voiceAudioUrl prop - 切换配音或标题时自动重新生成预览音频 --- apps/web/src/pages/generate/GeneratePage.tsx | 40 ++++++++++++++++++- .../components/FrontendPreviewPlayer.tsx | 26 ++++++------ apps/web/src/pages/generate/generate.css | 2 +- 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index f2d5ec4ad..ae0d0a2e3 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -8,7 +8,7 @@ * - 标题样式编辑时 CSS 层实时叠加预览,所见即所得 * - 点"确认生成"时调用 createGenerationTask 创建一次服务器渲染任务 */ -import React, { useMemo } from "react" +import React, { useMemo, useState, useEffect, useRef } from "react" import { Modal, message } from "antd" import { useNavigate } from "react-router-dom" import type { VoiceClone } from "@/api/voice-clone" @@ -29,6 +29,7 @@ import { useStepNavigation } from "./hooks/useStepNavigation" import { useGenerateVideo } from "./hooks/useGenerateVideo" import { usePreviewAssets } from "./hooks/usePreviewAssets" import { useTitleStyleUpdaters } from "./hooks/useStep4Title/useTitleStyleUpdaters" +import { previewTts } from "@/api/tts" import "./generate.css" const GeneratePage: React.FC = () => { @@ -86,6 +87,42 @@ const GeneratePage: React.FC = () => { onTitleSettingsChange: setTitleSettings, }) + /* ── 配音预览音频(TTS 试听)── */ + const [previewVoiceAudioUrl, setPreviewVoiceAudioUrl] = useState(null) + const ttsAbortRef = useRef(null) + + useEffect(() => { + 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]) + /* ── 克隆声音 ── */ const { clones: clonedVoices, addClone, hasProcessing } = useCloneProgress() @@ -261,6 +298,7 @@ const GeneratePage: React.FC = () => { template={currentTemplate} videoRatio={videoRatio} ready={previewAssets.length > 0} + voiceAudioUrl={previewVoiceAudioUrl || undefined} titleSettings={{ title: titleSettings.title, size: titleSettings.size, diff --git a/apps/web/src/pages/generate/components/FrontendPreviewPlayer.tsx b/apps/web/src/pages/generate/components/FrontendPreviewPlayer.tsx index 33c2f72e7..460434a57 100644 --- a/apps/web/src/pages/generate/components/FrontendPreviewPlayer.tsx +++ b/apps/web/src/pages/generate/components/FrontendPreviewPlayer.tsx @@ -280,10 +280,10 @@ const FrontendPreviewPlayer: React.FC = ({ style={{ position: "relative", width: "100%", - maxWidth: 360, + maxWidth: 280, aspectRatio: "9 / 16", background: "#0a0a0a", - borderRadius: 28, + borderRadius: 24, overflow: "hidden", boxShadow: "0 4px 6px -1px rgba(0,0,0,0.3), 0 20px 50px -12px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(255,255,255,0.06)", @@ -312,10 +312,10 @@ const FrontendPreviewPlayer: React.FC = ({ style={{ position: "relative", width: "100%", - maxWidth: 360, + maxWidth: 280, aspectRatio: "9 / 16", background: "#0a0a0a", - borderRadius: 28, + borderRadius: 24, overflow: "hidden", boxShadow: "0 4px 6px -1px rgba(0,0,0,0.3), 0 20px 50px -12px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(255,255,255,0.06)", @@ -377,10 +377,10 @@ const FrontendPreviewPlayer: React.FC = ({ style={{ position: "relative", width: "100%", - maxWidth: 360, + maxWidth: 280, aspectRatio: "9 / 16", background: "#0a0a0a", - borderRadius: 28, + borderRadius: 24, overflow: "hidden", boxShadow: "0 4px 6px -1px rgba(0,0,0,0.3), 0 20px 50px -12px rgba(0,0,0,0.5), inset 0 0 0 1px rgba(255,255,255,0.06)", @@ -493,14 +493,14 @@ const FrontendPreviewPlayer: React.FC = ({ WebkitBackdropFilter: "blur(12px)", border: "1px solid rgba(255,255,255,0.15)", borderRadius: "50%", - width: 72, - height: 72, + width: 52, + height: 52, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", color: "#fff", - fontSize: 36, + fontSize: 26, zIndex: 10, transition: "transform 0.2s ease, background 0.2s ease", boxShadow: "0 4px 20px rgba(0,0,0,0.4)", @@ -522,15 +522,15 @@ const FrontendPreviewPlayer: React.FC = ({