const _SETTINGS_SPEECH_STORAGE_KEYS={ tts_engine:'hermes-tts-engine', tts_voice:'hermes-tts-voice', tts_rate:'hermes-tts-rate', }; let _settingsSpeechChangedKeys=new Set(); function _normalizeWebUIVersion(value){ if(!value) return ''; const s=String(value).trim(); if(!s) return ''; const lower=s.toLowerCase(); if(lower==='__webui_version__'||lower==='not detected'||lower==='unknown') return ''; return s; } function _currentWebUIBundleVersion(){ try{ const raw=window.__HERMES_WEBUI_BUNDLE_VERSION__; if(!raw) return ''; let s=String(raw); try{ s=decodeURIComponent(s.replace(/\+/g,' ')); }catch(_){} return _normalizeWebUIVersion(s); }catch(_){ return ''; } } function _showStaleWebUIClientBanner(clientVersion,serverVersion){ const banner=document.getElementById('staleClientBanner'); if(!banner) return; const msg=document.getElementById('staleClientMessage'); const versions=document.getElementById('staleClientVersions'); if(msg) msg.textContent='This tab is running a different WebUI version. Hard refresh to restore full functionality.'; if(versions) versions.textContent='Running: '+clientVersion+' → Server: '+serverVersion; banner.style.display='flex'; } function checkWebUIVersionSkew(settings){ try{ if(!settings) return; const client=_currentWebUIBundleVersion(); const server=_normalizeWebUIVersion(settings.webui_version); if(!client||!server) return; if(client===server) return; _showStaleWebUIClientBanner(client,server); }catch(_){} } window.checkWebUIVersionSkew=checkWebUIVersionSkew; function _startWebUIVersionSkewMonitor(){ let _pollTimer=null; function _isBannerVisible(){ const banner=document.getElementById('staleClientBanner'); return !!(banner&&banner.style.display==='flex'); } function _check(){ if(_isBannerVisible()) return; Promise.resolve().then(function(){ return api('/api/settings'); }).then(function(s){ checkWebUIVersionSkew(s); }).catch(function(){}); } function _startPoll(){ if(_pollTimer||document.hidden) return; _pollTimer=setInterval(function(){ if(document.hidden){ clearInterval(_pollTimer); _pollTimer=null; return; } if(_isBannerVisible()){ clearInterval(_pollTimer); _pollTimer=null; return; } _check(); },60000); } } function _speechPreferencesPayloadFromUi(){ const payload={}; const ttsVoiceSel=$('settingsTtsVoice'); if(ttsVoiceSel) _setOwnedSpeechPayload(payload,'tts_voice',ttsVoiceSel.value||''); return payload; } function loadSettingsPanel(){ const ttsEngineSel=$('settingsTtsEngine'); if(ttsEngineSel){ ttsEngineSel.onchange=function(){ localStorage.setItem('hermes-tts-engine',this.value); window._populateTtsVoices(); _schedulePreferencesAutosave(); }; } // Populate voice selector based on engine const ttsVoiceSel=$('settingsTtsVoice'); window._populateTtsVoices=function(){ if(!ttsVoiceSel) return; const engine=localStorage.getItem('hermes-tts-engine')||'browser'; const current=String(_speechSetting('tts_voice','hermes-tts-voice','')||''); _syncSpeechPreferenceCache('tts_voice',current); if(engine==='edge'){ const edgeVoices=[ {value:'en-US-AriaNeural',label:'Aria (English, Female)'}, ]; ttsVoiceSel.innerHTML=''; edgeVoices.forEach(v=>ttsVoiceSel.appendChild(v)); } }; if(ttsVoiceSel&&'speechSynthesis' in window){ window._populateTtsVoices(); ttsVoiceSel.onchange=function(){_markSpeechPreferenceChanged('tts_voice');localStorage.setItem('hermes-tts-voice',this.value);_schedulePreferencesAutosave();}; } // TTS rate/pitch sliders }