import { useSyncExternalStore } from "react"; import { speechVoiceIdSchema, type SpeechVoice } from "@/shared/tts/voices"; export const SPEECH_VOICE_STORAGE_KEY = "tjwater.agent.speech-voice"; const VOICE_CHANGE_EVENT = "tjwater:speech-voice-change"; let memoryVoice = ""; let useMemoryPreference = false; export function getSelectedSpeechVoice(): string { let value: string; try { value = useMemoryPreference ? memoryVoice : localStorage.getItem(SPEECH_VOICE_STORAGE_KEY) ?? ""; } catch { value = memoryVoice; } return speechVoiceIdSchema.safeParse(value).success ? value : ""; } export function setSelectedSpeechVoice(value: string) { const voice = speechVoiceIdSchema.safeParse(value).success ? value : ""; memoryVoice = voice; try { if (voice) localStorage.setItem(SPEECH_VOICE_STORAGE_KEY, voice); else localStorage.removeItem(SPEECH_VOICE_STORAGE_KEY); useMemoryPreference = false; } catch { useMemoryPreference = true; } window.dispatchEvent(new Event(VOICE_CHANGE_EVENT)); } function subscribe(onChange: () => void) { window.addEventListener(VOICE_CHANGE_EVENT, onChange); window.addEventListener("storage", onChange); return () => { window.removeEventListener(VOICE_CHANGE_EVENT, onChange); window.removeEventListener("storage", onChange); }; } export function useSelectedSpeechVoice() { return useSyncExternalStore(subscribe, getSelectedSpeechVoice, () => ""); } const chineseNames: Record = { "zh-CN-XiaoxiaoNeural": "晓晓", "zh-CN-XiaoyiNeural": "晓伊", "zh-CN-YunxiNeural": "云希", "zh-CN-YunyangNeural": "云扬", "zh-CN-YunjianNeural": "云健", "zh-CN-YunxiaNeural": "云夏" }; export function getSpeechVoiceName(voice: SpeechVoice) { return chineseNames[voice.id] ?? voice.id.replace(`${voice.locale}-`, "").replace(/Neural$/, ""); } export function getSpeechVoiceDescription(voice: SpeechVoice) { return `普通话 · ${voice.gender === "Female" ? "女声" : "男声"}`; }