From 4f377d4fd3b7b554183f24b60f50a20d09976f49 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 14 Sep 2026 18:05:01 +0800 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20#1898=20=E9=85=8D=E9=9F=B3?= =?UTF-8?q?=E5=BA=93AI=E9=85=8D=E9=9F=B3=E5=A2=9E=E5=8A=A0=E6=83=85?= =?UTF-8?q?=E7=BB=AA=E8=AF=AD=E8=A8=80=E9=80=89=E6=8B=A9=20(#1904)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: xiaoxia Co-committed-by: xiaoxia --- apps/web/src/api/tts/types.ts | 2 + apps/web/src/pages/voices/VoiceLibrary.tsx | 8 ++++ .../src/pages/voices/components/TtsModal.tsx | 16 +++++++ .../pages/voices/components/VoiceModals.tsx | 13 +++++ .../components/tts-modal/EmotionControl.tsx | 47 +++++++++++++++++++ .../components/tts-modal/LanguageControl.tsx | 47 +++++++++++++++++++ .../voices/components/tts-modal/constants.ts | 22 +++++++++ .../voices/components/tts-modal/types.ts | 5 ++ .../pages/voices/hooks/useTtsSynthesize.ts | 18 ++++++- 9 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/pages/voices/components/tts-modal/EmotionControl.tsx create mode 100644 apps/web/src/pages/voices/components/tts-modal/LanguageControl.tsx create mode 100644 apps/web/src/pages/voices/components/tts-modal/constants.ts diff --git a/apps/web/src/api/tts/types.ts b/apps/web/src/api/tts/types.ts index c3e686017..ab4dda8b8 100644 --- a/apps/web/src/api/tts/types.ts +++ b/apps/web/src/api/tts/types.ts @@ -16,6 +16,7 @@ export interface TTSSynthesizeRequest { voice_id?: string output_name?: string language?: string + emotion?: string speed?: number voice_model?: string voice_clone_profile_id?: string @@ -103,6 +104,7 @@ export interface TTSPreviewRequest { voice_id: string speed?: number pitch?: number + language?: string emotion?: string // 情绪参数:natural/excited/calm/friendly } diff --git a/apps/web/src/pages/voices/VoiceLibrary.tsx b/apps/web/src/pages/voices/VoiceLibrary.tsx index 271fe6d06..25fffc98e 100755 --- a/apps/web/src/pages/voices/VoiceLibrary.tsx +++ b/apps/web/src/pages/voices/VoiceLibrary.tsx @@ -132,12 +132,16 @@ const VoiceLibrary: React.FC = () => { ttsText, ttsVoiceId, ttsSpeed, + ttsEmotion, + ttsLanguage, ttsStatus, ttsAudioUrl, ttsError, setTtsText, setTtsVoiceId, setTtsSpeed, + setTtsEmotion, + setTtsLanguage, setTtsOpen, handleTtsSynthesize, handleTtsSave, @@ -363,6 +367,8 @@ const VoiceLibrary: React.FC = () => { ttsText={ttsText} ttsVoiceId={ttsVoiceId} ttsSpeed={ttsSpeed} + ttsEmotion={ttsEmotion} + ttsLanguage={ttsLanguage} ttsStatus={ttsStatus} ttsAudioUrl={ttsAudioUrl} ttsError={ttsError} @@ -374,6 +380,8 @@ const VoiceLibrary: React.FC = () => { onTtsTextChange={setTtsText} onTtsVoiceChange={setTtsVoiceId} onTtsSpeedChange={setTtsSpeed} + onTtsEmotionChange={setTtsEmotion} + onTtsLanguageChange={setTtsLanguage} onTtsSynthesize={handleTtsSynthesize} onTtsSave={handleTtsSave} /> diff --git a/apps/web/src/pages/voices/components/TtsModal.tsx b/apps/web/src/pages/voices/components/TtsModal.tsx index 686018bb6..29ff0202f 100755 --- a/apps/web/src/pages/voices/components/TtsModal.tsx +++ b/apps/web/src/pages/voices/components/TtsModal.tsx @@ -4,6 +4,8 @@ import { type TtsModalProps, type TtsStatus } from "./tts-modal/types" import TextInputSection from "./tts-modal/TextInputSection" import VoiceSelector from "./tts-modal/VoiceSelector" import SpeedControl from "./tts-modal/SpeedControl" +import EmotionControl from "./tts-modal/EmotionControl" +import LanguageControl from "./tts-modal/LanguageControl" import SynthesizeButton from "./tts-modal/SynthesizeButton" import ErrorAlert from "./tts-modal/ErrorAlert" import ResultPanel from "./tts-modal/ResultPanel" @@ -14,6 +16,8 @@ const TtsModal: React.FC = ({ ttsText, ttsVoiceId, ttsSpeed, + ttsEmotion, + ttsLanguage, ttsStatus, ttsAudioUrl, ttsError, @@ -23,6 +27,8 @@ const TtsModal: React.FC = ({ onTextChange, onVoiceChange, onSpeedChange, + onEmotionChange, + onLanguageChange, onSynthesize, onSave, }) => { @@ -43,6 +49,16 @@ const TtsModal: React.FC = ({ presetVoices={presetVoices} clonedVoices={clonedVoices} /> +
+ + +
{ttsError && } diff --git a/apps/web/src/pages/voices/components/VoiceModals.tsx b/apps/web/src/pages/voices/components/VoiceModals.tsx index a95b3ce8e..a44e7fb3c 100644 --- a/apps/web/src/pages/voices/components/VoiceModals.tsx +++ b/apps/web/src/pages/voices/components/VoiceModals.tsx @@ -6,6 +6,7 @@ import type { ClonedVoiceDisplay, PresetVoiceDisplay } from "../types" import type { VoiceClone } from "@/api/voice-clone" import type { TtsStatus } from "./TtsModal" import type { TtsClonedVoiceOption } from "./tts-modal/VoiceSelector" +import type { TtsEmotion, TtsLanguage } from "./tts-modal/constants" import CloneModal from "@/components/voice/CloneModal" import CloneDetailModal from "./CloneDetailModal" import UploadVoiceModal from "./UploadVoiceModal" @@ -42,6 +43,8 @@ export interface VoiceModalsProps { ttsText: string ttsVoiceId: string ttsSpeed: number + ttsEmotion: TtsEmotion + ttsLanguage: TtsLanguage ttsStatus: TtsStatus ttsAudioUrl: string | null ttsError: string | null @@ -52,6 +55,8 @@ export interface VoiceModalsProps { onTtsTextChange: (text: string) => void onTtsVoiceChange: (id: string) => void onTtsSpeedChange: (speed: number) => void + onTtsEmotionChange: (emotion: TtsEmotion) => void + onTtsLanguageChange: (language: TtsLanguage) => void onTtsSynthesize: () => void onTtsSave: () => void } @@ -80,6 +85,8 @@ export const VoiceModals: React.FC = ({ ttsText, ttsVoiceId, ttsSpeed, + ttsEmotion, + ttsLanguage, ttsStatus, ttsAudioUrl, ttsError, @@ -89,6 +96,8 @@ export const VoiceModals: React.FC = ({ onTtsTextChange, onTtsVoiceChange, onTtsSpeedChange, + onTtsEmotionChange, + onTtsLanguageChange, onTtsSynthesize, onTtsSave, }) => { @@ -129,6 +138,8 @@ export const VoiceModals: React.FC = ({ ttsText={ttsText} ttsVoiceId={ttsVoiceId} ttsSpeed={ttsSpeed} + ttsEmotion={ttsEmotion} + ttsLanguage={ttsLanguage} ttsStatus={ttsStatus} ttsAudioUrl={ttsAudioUrl} ttsError={ttsError} @@ -138,6 +149,8 @@ export const VoiceModals: React.FC = ({ onTextChange={onTtsTextChange} onVoiceChange={onTtsVoiceChange} onSpeedChange={onTtsSpeedChange} + onEmotionChange={onTtsEmotionChange} + onLanguageChange={onTtsLanguageChange} onSynthesize={onTtsSynthesize} onSave={onTtsSave} /> diff --git a/apps/web/src/pages/voices/components/tts-modal/EmotionControl.tsx b/apps/web/src/pages/voices/components/tts-modal/EmotionControl.tsx new file mode 100644 index 000000000..c14210856 --- /dev/null +++ b/apps/web/src/pages/voices/components/tts-modal/EmotionControl.tsx @@ -0,0 +1,47 @@ +import React from "react" +import { TTS_EMOTION_OPTIONS, type TtsEmotion } from "./constants" + +interface EmotionControlProps { + emotion: TtsEmotion + onChange: (emotion: TtsEmotion) => void +} + +/** 情绪选择下拉 */ +const EmotionControl: React.FC = ({ emotion, onChange }) => { + return ( +
+
+ 情绪 +
+ +
+ ) +} + +export default EmotionControl diff --git a/apps/web/src/pages/voices/components/tts-modal/LanguageControl.tsx b/apps/web/src/pages/voices/components/tts-modal/LanguageControl.tsx new file mode 100644 index 000000000..a84668b83 --- /dev/null +++ b/apps/web/src/pages/voices/components/tts-modal/LanguageControl.tsx @@ -0,0 +1,47 @@ +import React from "react" +import { TTS_LANGUAGE_OPTIONS, type TtsLanguage } from "./constants" + +interface LanguageControlProps { + language: TtsLanguage + onChange: (language: TtsLanguage) => void +} + +/** 语言选择下拉 */ +const LanguageControl: React.FC = ({ language, onChange }) => { + return ( +
+
+ 语言 +
+ +
+ ) +} + +export default LanguageControl diff --git a/apps/web/src/pages/voices/components/tts-modal/constants.ts b/apps/web/src/pages/voices/components/tts-modal/constants.ts new file mode 100644 index 000000000..e57ff482b --- /dev/null +++ b/apps/web/src/pages/voices/components/tts-modal/constants.ts @@ -0,0 +1,22 @@ +/** TTS 情绪选项(对齐后端 CosyVoice 支持:natural/excited/calm/friendly) */ +export const TTS_EMOTION_OPTIONS = [ + { value: "natural", label: "自然" }, + { value: "excited", label: "兴奋" }, + { value: "calm", label: "沉稳" }, + { value: "friendly", label: "亲切" }, +] as const + +export type TtsEmotion = (typeof TTS_EMOTION_OPTIONS)[number]["value"] + +/** TTS 语言选项 */ +export const TTS_LANGUAGE_OPTIONS = [ + { value: "zh-CN", label: "中文" }, + { value: "en", label: "英文" }, + { value: "ja", label: "日文" }, + { value: "ko", label: "韩文" }, +] as const + +export type TtsLanguage = (typeof TTS_LANGUAGE_OPTIONS)[number]["value"] + +export const DEFAULT_TTS_EMOTION: TtsEmotion = "natural" +export const DEFAULT_TTS_LANGUAGE: TtsLanguage = "zh-CN" diff --git a/apps/web/src/pages/voices/components/tts-modal/types.ts b/apps/web/src/pages/voices/components/tts-modal/types.ts index 0d288c20b..266be0db3 100755 --- a/apps/web/src/pages/voices/components/tts-modal/types.ts +++ b/apps/web/src/pages/voices/components/tts-modal/types.ts @@ -1,5 +1,6 @@ import { type PresetVoiceDisplay } from "@/pages/voices/types" import type { TtsClonedVoiceOption } from "./VoiceSelector" +import type { TtsEmotion, TtsLanguage } from "./constants" export type TtsStatus = "idle" | "synthesizing" | "done" | "error" @@ -8,6 +9,8 @@ export interface TtsModalProps { ttsText: string ttsVoiceId: string ttsSpeed: number + ttsEmotion: TtsEmotion + ttsLanguage: TtsLanguage ttsStatus: TtsStatus ttsAudioUrl: string | null ttsError: string | null @@ -18,6 +21,8 @@ export interface TtsModalProps { onTextChange: (text: string) => void onVoiceChange: (voiceId: string) => void onSpeedChange: (speed: number) => void + onEmotionChange: (emotion: TtsEmotion) => void + onLanguageChange: (language: TtsLanguage) => void onSynthesize: () => void onSave: () => void } diff --git a/apps/web/src/pages/voices/hooks/useTtsSynthesize.ts b/apps/web/src/pages/voices/hooks/useTtsSynthesize.ts index 19c777e44..f5c2e841e 100644 --- a/apps/web/src/pages/voices/hooks/useTtsSynthesize.ts +++ b/apps/web/src/pages/voices/hooks/useTtsSynthesize.ts @@ -4,6 +4,12 @@ import { message } from "antd" import { synthesizeSpeech, getTTSJobStatus, saveTtsToLibrary } from "@/api/tts" import { type PresetVoiceDisplay } from "../types" import type { TtsClonedVoiceOption } from "../components/tts-modal/VoiceSelector" +import { + DEFAULT_TTS_EMOTION, + DEFAULT_TTS_LANGUAGE, + type TtsEmotion, + type TtsLanguage, +} from "../components/tts-modal/constants" export type TtsStatus = "idle" | "synthesizing" | "done" | "error" @@ -29,6 +35,8 @@ export function useTtsSynthesize({ const [ttsText, setTtsText] = useState("") const [ttsVoiceId, setTtsVoiceId] = useState("") const [ttsSpeed, setTtsSpeed] = useState(1.0) + const [ttsEmotion, setTtsEmotion] = useState(DEFAULT_TTS_EMOTION) + const [ttsLanguage, setTtsLanguage] = useState(DEFAULT_TTS_LANGUAGE) const [ttsJobId, setTtsJobId] = useState(null) const [ttsStatus, setTtsStatus] = useState("idle") const [ttsAudioUrl, setTtsAudioUrl] = useState(null) @@ -51,6 +59,8 @@ export function useTtsSynthesize({ text: ttsText.trim(), voice_id: ttsVoiceId || undefined, speed: ttsSpeed, + emotion: ttsEmotion, + language: ttsLanguage, }) setTtsJobId(resp.job_id) @@ -81,7 +91,7 @@ export function useTtsSynthesize({ setTtsStatus("error") setTtsError(msg) } - }, [ttsText, ttsVoiceId, ttsSpeed]) + }, [ttsText, ttsVoiceId, ttsSpeed, ttsEmotion, ttsLanguage]) /** 保存 TTS 结果到素材库 */ const handleTtsSave = useCallback(async () => { @@ -103,6 +113,8 @@ export function useTtsSynthesize({ setTtsText("") setTtsVoiceId("") setTtsSpeed(1.0) + setTtsEmotion(DEFAULT_TTS_EMOTION) + setTtsLanguage(DEFAULT_TTS_LANGUAGE) setTtsStatus("idle") setTtsAudioUrl(null) setTtsError(null) @@ -132,6 +144,8 @@ export function useTtsSynthesize({ ttsText, ttsVoiceId, ttsSpeed, + ttsEmotion, + ttsLanguage, ttsJobId, ttsStatus, ttsAudioUrl, @@ -143,6 +157,8 @@ export function useTtsSynthesize({ setTtsText, setTtsVoiceId, setTtsSpeed, + setTtsEmotion, + setTtsLanguage, setTtsOpen, // Actions handleTtsSynthesize,