fix(#5435): keep saved default speech prefs authoritative
This commit is contained in:
parent
7ee2757afe
commit
ca31bf969b
3 changed files with 65 additions and 25 deletions
|
|
@ -2744,22 +2744,23 @@ function _mirrorSpeechSettingsFromServer(s){
|
|||
voice_silence_ms:1800,
|
||||
raw_audio_mode:false,
|
||||
};
|
||||
const hasServerValue=(settingKey)=>Object.prototype.hasOwnProperty.call(s,settingKey);
|
||||
const cachedValue=(storageKey)=>{
|
||||
try{return localStorage.getItem(storageKey);}catch(_){return null;}
|
||||
};
|
||||
const boolValue=(value)=>value===true||value==='true';
|
||||
const resolveBool=(settingKey,storageKey)=>{
|
||||
const server=Object.prototype.hasOwnProperty.call(s,settingKey)?s[settingKey]:defaults[settingKey];
|
||||
const server=hasServerValue(settingKey)?s[settingKey]:defaults[settingKey];
|
||||
const cached=cachedValue(storageKey);
|
||||
if(cached!==null&&boolValue(server)===boolValue(defaults[settingKey])&&boolValue(cached)!==boolValue(defaults[settingKey])){
|
||||
if(!hasServerValue(settingKey)&&cached!==null){
|
||||
return boolValue(cached);
|
||||
}
|
||||
return boolValue(server);
|
||||
};
|
||||
const resolveScalar=(settingKey,storageKey)=>{
|
||||
const server=Object.prototype.hasOwnProperty.call(s,settingKey)?s[settingKey]:defaults[settingKey];
|
||||
const server=hasServerValue(settingKey)?s[settingKey]:defaults[settingKey];
|
||||
const cached=cachedValue(storageKey);
|
||||
if(cached!==null&&String(server)===String(defaults[settingKey])&&String(cached)!==String(defaults[settingKey])){
|
||||
if(!hasServerValue(settingKey)&&cached!==null){
|
||||
return cached;
|
||||
}
|
||||
return server;
|
||||
|
|
@ -2771,7 +2772,7 @@ function _mirrorSpeechSettingsFromServer(s){
|
|||
['voice_continuous','hermes-voice-continuous'],
|
||||
];
|
||||
boolKeys.forEach(([settingKey,storageKey])=>{
|
||||
if(Object.prototype.hasOwnProperty.call(s,settingKey)){
|
||||
if(hasServerValue(settingKey)){
|
||||
try{localStorage.setItem(storageKey,resolveBool(settingKey,storageKey)?'true':'false');}catch(_){}
|
||||
}
|
||||
});
|
||||
|
|
@ -2782,11 +2783,11 @@ function _mirrorSpeechSettingsFromServer(s){
|
|||
['tts_pitch','hermes-tts-pitch'],
|
||||
['voice_silence_ms','hermes-voice-silence-ms'],
|
||||
].forEach(([settingKey,storageKey])=>{
|
||||
if(Object.prototype.hasOwnProperty.call(s,settingKey)){
|
||||
if(hasServerValue(settingKey)){
|
||||
try{localStorage.setItem(storageKey,String(resolveScalar(settingKey,storageKey)));}catch(_){}
|
||||
}
|
||||
});
|
||||
if(Object.prototype.hasOwnProperty.call(s,'raw_audio_mode')){
|
||||
if(hasServerValue('raw_audio_mode')){
|
||||
const rawAudioMode=resolveBool('raw_audio_mode','hermes-raw-audio-mode');
|
||||
if(typeof window._applyRawAudioModePreference==='function'){
|
||||
window._applyRawAudioModePreference(rawAudioMode);
|
||||
|
|
|
|||
|
|
@ -8640,20 +8640,7 @@ async function loadSettingsPanel(){
|
|||
if(typeof window._mirrorSpeechSettingsFromServer==='function') window._mirrorSpeechSettingsFromServer(settings);
|
||||
const _speechSetting=function(key,storageKey,fallback,kind){
|
||||
const stored=localStorage.getItem(storageKey);
|
||||
if(settings&&Object.prototype.hasOwnProperty.call(settings,key)){
|
||||
const server=settings[key];
|
||||
if(stored!==null){
|
||||
if(kind==='bool'){
|
||||
const serverBool=server===true||server==='true';
|
||||
const fallbackBool=fallback===true||fallback==='true';
|
||||
const storedBool=stored==='true';
|
||||
if(serverBool===fallbackBool&&storedBool!==fallbackBool) return storedBool;
|
||||
}else if(String(server)===String(fallback)&&String(stored)!==String(fallback)){
|
||||
return stored;
|
||||
}
|
||||
}
|
||||
return server;
|
||||
}
|
||||
if(settings&&Object.prototype.hasOwnProperty.call(settings,key)) return settings[key];
|
||||
return stored===null?fallback:stored;
|
||||
};
|
||||
const _speechBool=function(key,storageKey,fallback){
|
||||
|
|
|
|||
|
|
@ -45,6 +45,24 @@ def post(path, body=None):
|
|||
return json.loads(exc.read()), exc.code
|
||||
|
||||
|
||||
def _extract_balanced_block(src, marker):
|
||||
start = src.index(marker)
|
||||
brace = src.index("{", start)
|
||||
depth = 0
|
||||
end = None
|
||||
for idx in range(brace, len(src)):
|
||||
ch = src[idx]
|
||||
if ch == "{":
|
||||
depth += 1
|
||||
elif ch == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
end = idx + 1
|
||||
break
|
||||
assert end is not None, f"Unbalanced block for {marker!r}"
|
||||
return src[start:end]
|
||||
|
||||
|
||||
def _reset_speech_settings(extra=None):
|
||||
payload = dict(SPEECH_DEFAULTS)
|
||||
if extra:
|
||||
|
|
@ -151,8 +169,8 @@ def test_boot_mirrors_server_settings_before_tts_apply_and_preserves_failure_fal
|
|||
assert success_call_idx < apply_idx
|
||||
assert catch_idx < failure_apply_idx
|
||||
assert "const defaults={" in BOOT_JS
|
||||
assert "cached!==null&&boolValue(server)===boolValue(defaults[settingKey])" in BOOT_JS
|
||||
assert "String(server)===String(defaults[settingKey])" in BOOT_JS
|
||||
assert "const hasServerValue=(settingKey)=>Object.prototype.hasOwnProperty.call(s,settingKey);" in BOOT_JS
|
||||
assert "if(!hasServerValue(settingKey)&&cached!==null)" in BOOT_JS
|
||||
for storage_key in [
|
||||
"hermes-tts-enabled",
|
||||
"hermes-tts-auto-read",
|
||||
|
|
@ -169,6 +187,41 @@ def test_boot_mirrors_server_settings_before_tts_apply_and_preserves_failure_fal
|
|||
assert "window._applyRawAudioModePreference" in BOOT_JS
|
||||
|
||||
|
||||
def test_server_saved_defaults_win_over_stale_local_cache_in_boot_and_panel_logic():
|
||||
mirror_fn = _extract_balanced_block(BOOT_JS, "function _mirrorSpeechSettingsFromServer")
|
||||
speech_setting_start = PANELS_JS.index("const _speechSetting=function(")
|
||||
speech_setting_end = PANELS_JS.index("const _speechBool=function", speech_setting_start)
|
||||
speech_setting_block = PANELS_JS[speech_setting_start:speech_setting_end].strip()
|
||||
script = f"""
|
||||
const assert = require('assert');
|
||||
const localStorage = {{
|
||||
store: new Map([
|
||||
['hermes-tts-enabled', 'true'],
|
||||
['hermes-tts-pitch', '0.8'],
|
||||
]),
|
||||
getItem(key) {{
|
||||
return this.store.has(key) ? this.store.get(key) : null;
|
||||
}},
|
||||
setItem(key, value) {{
|
||||
this.store.set(key, String(value));
|
||||
}},
|
||||
}};
|
||||
const window = {{}};
|
||||
{mirror_fn}
|
||||
_mirrorSpeechSettingsFromServer({{tts_enabled: false, tts_pitch: 1}});
|
||||
assert.strictEqual(localStorage.getItem('hermes-tts-enabled'), 'false');
|
||||
assert.strictEqual(localStorage.getItem('hermes-tts-pitch'), '1');
|
||||
let settings = {{tts_enabled: false, tts_pitch: 1}};
|
||||
{speech_setting_block}
|
||||
assert.strictEqual(_speechSetting('tts_enabled', 'hermes-tts-enabled', false, 'bool'), false);
|
||||
assert.strictEqual(_speechSetting('tts_pitch', 'hermes-tts-pitch', 1), 1);
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
subprocess.run(["node", "-e", script], check=True, capture_output=True, text=True)
|
||||
|
||||
|
||||
def test_settings_panel_persists_speech_fields_and_keeps_immediate_cache_writes():
|
||||
payload_idx = PANELS_JS.index("function _preferencesPayloadFromUi")
|
||||
payload_end = PANELS_JS.index("function _setPreferencesAutosaveStatus", payload_idx)
|
||||
|
|
@ -197,8 +250,7 @@ def test_settings_panel_persists_speech_fields_and_keeps_immediate_cache_writes(
|
|||
assert "savedPitch||'1'" not in panel_block
|
||||
assert "ttsRateSlider.value=(savedRate===null||savedRate===undefined)?'1':String(savedRate)" in panel_block
|
||||
assert "ttsPitchSlider.value=(savedPitch===null||savedPitch===undefined)?'1':String(savedPitch)" in panel_block
|
||||
assert "serverBool===fallbackBool&&storedBool!==fallbackBool" in PANELS_JS
|
||||
assert "String(server)===String(fallback)&&String(stored)!==String(fallback)" in PANELS_JS
|
||||
assert "if(settings&&Object.prototype.hasOwnProperty.call(settings,key)) return settings[key];" in PANELS_JS
|
||||
assert "_schedulePreferencesAutosave()" in panel_block
|
||||
assert "_applyVoiceModePref" in panel_block
|
||||
assert "_populateTtsVoices" in panel_block
|
||||
|
|
|
|||
Loading…
Reference in a new issue