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 = ({