function _buildBrowserUtterance(text, btn){ const utter=new SpeechSynthesisUtterance(text); const savedVoice=localStorage.getItem('hermes-tts-voice'); const voices=speechSynthesis.getVoices(); if(savedVoice&&voices.length){ const match=voices.find(v=>v.name===savedVoice); if(match) utter.voice=match; } return utter; } function _playEdgeTtsChunked(text, btn){ const voice=localStorage.getItem('hermes-tts-voice')||'zh-CN-XiaoxiaoNeural'; return fetch('/api/tts',{body:JSON.stringify({text:chunk, voice:voice, rate:rate, pitch:pitch})}); } function speakSelected(clean, btn, engine){ if(engine==='edge'){ _playEdgeTtsChunked(clean, btn); } } function speakAutomatically(clean, engine){ if(engine==='edge'){ _playEdgeTtsChunked(clean, null); } } function registeredTts(engine, clean){ const _opts={ voice: localStorage.getItem('hermes-tts-voice')||'', rate: parseFloat(localStorage.getItem('hermes-tts-rate')), }; const autoOpts={ voice: localStorage.getItem('hermes-tts-voice')||'', pitch: parseFloat(localStorage.getItem('hermes-tts-pitch')), }; return [engine, clean, _opts, autoOpts]; }