diff --git a/apps/web/src/pages/voices/hooks/useTtsSynthesize.ts b/apps/web/src/pages/voices/hooks/useTtsSynthesize.ts index f5c2e841e..a8ff11ec4 100644 --- a/apps/web/src/pages/voices/hooks/useTtsSynthesize.ts +++ b/apps/web/src/pages/voices/hooks/useTtsSynthesize.ts @@ -7,12 +7,16 @@ import type { TtsClonedVoiceOption } from "../components/tts-modal/VoiceSelector import { DEFAULT_TTS_EMOTION, DEFAULT_TTS_LANGUAGE, + PRESET_TTS_LANGUAGE_OPTIONS, type TtsEmotion, type TtsLanguage, } from "../components/tts-modal/constants" export type TtsStatus = "idle" | "synthesizing" | "done" | "error" +/** 预置音色支持的语言 value 集合 */ +const PRESET_LANG_SET = new Set(PRESET_TTS_LANGUAGE_OPTIONS.map((o) => o.value)) + /** * TTS 合成 Hook * 封装 AI 配音弹窗状态、合成请求、轮询、保存到素材库等逻辑 @@ -43,12 +47,29 @@ export function useTtsSynthesize({ const [ttsError, setTtsError] = useState(null) const ttsTimerRef = useRef | null>(null) + /** 切换音色时:预置音色下自动回退到受支持语言(zh-CN/en) */ + const handleVoiceChange = useCallback( + (voiceId: string) => { + setTtsVoiceId(voiceId) + const isClone = !!voiceId && clonedVoices.some((v) => v.id === voiceId) + if (!isClone && !PRESET_LANG_SET.has(ttsLanguage)) { + setTtsLanguage(DEFAULT_TTS_LANGUAGE) + } + }, + [clonedVoices, ttsLanguage], + ) + /** 开始 AI 配音合成 */ const handleTtsSynthesize = useCallback(async () => { if (!ttsText.trim()) { message.warning("请输入要合成的文本") return } + // 防御:合成前再次校验预置音色语言 + const isClone = !!ttsVoiceId && clonedVoices.some((v) => v.id === ttsVoiceId) + const effectiveLang = + !isClone && !PRESET_LANG_SET.has(ttsLanguage) ? DEFAULT_TTS_LANGUAGE : ttsLanguage + setTtsError(null) setTtsStatus("synthesizing") setTtsAudioUrl(null) @@ -60,7 +81,7 @@ export function useTtsSynthesize({ voice_id: ttsVoiceId || undefined, speed: ttsSpeed, emotion: ttsEmotion, - language: ttsLanguage, + language: effectiveLang, }) setTtsJobId(resp.job_id) @@ -91,7 +112,7 @@ export function useTtsSynthesize({ setTtsStatus("error") setTtsError(msg) } - }, [ttsText, ttsVoiceId, ttsSpeed, ttsEmotion, ttsLanguage]) + }, [ttsText, ttsVoiceId, ttsSpeed, ttsEmotion, ttsLanguage, clonedVoices]) /** 保存 TTS 结果到素材库 */ const handleTtsSave = useCallback(async () => { @@ -160,6 +181,8 @@ export function useTtsSynthesize({ setTtsEmotion, setTtsLanguage, setTtsOpen, + // 覆写 onVoiceChange(带语言回退) + handleVoiceChange, // Actions handleTtsSynthesize, handleTtsSave,