fix(chat): keep new session model authoritative

This commit is contained in:
ai-ag2026 authored and nesquena-hermes committed 2026-05-22 16:50:17 +00:00
1 parent af1ee81f06
commit 6bcc9689aa
3 files changed
+51 -11

No files matched your search

+19 -9
View File
@@ -1576,20 +1576,30 @@ function applyBotName(){
// options are enough for first paint; the dynamic provider list can settle // options are enough for first paint; the dynamic provider list can settle
// after the saved session is visible. // after the saved session is visible.
const _hydrateBootModelDropdown=()=>populateModelDropdown().then(()=>{ const _hydrateBootModelDropdown=()=>populateModelDropdown().then(()=>{
const sessionModelState=S.session&&S.session.model
? {model:S.session.model,model_provider:S.session.model_provider||null}
: null;
const savedState=(typeof _readPersistedModelState==='function') const savedState=(typeof _readPersistedModelState==='function')
? _readPersistedModelState() ? _readPersistedModelState()
: (localStorage.getItem('hermes-webui-model')?{model:localStorage.getItem('hermes-webui-model'),model_provider:null}:null); : (localStorage.getItem('hermes-webui-model')?{model:localStorage.getItem('hermes-webui-model'),model_provider:null}:null);
const savedModel=savedState&&savedState.model; // Active sessions are authoritative. On fresh boot without a restored
// Guardrail: prefer profile/server default on boot when available. // session, keep the profile/server default ahead of stale browser model
// Persisted browser model state should not override a valid default model. // state when a default exists.
const allowBootSavedModelOverride=!window._defaultModel; const stateToApply=sessionModelState||(!window._defaultModel?savedState:null);
if(savedModel && $('modelSelect') && allowBootSavedModelOverride){ const savedModel=stateToApply&&stateToApply.model;
if(savedModel && $('modelSelect')){
const applied=(typeof _applyModelToDropdown==='function') const applied=(typeof _applyModelToDropdown==='function')
? _applyModelToDropdown(savedModel,$('modelSelect'),savedState.model_provider||null) ? (sessionModelState
? _applyModelToDropdown(sessionModelState.model,$('modelSelect'),sessionModelState.model_provider||null)
: _applyModelToDropdown(savedState.model,$('modelSelect'),savedState.model_provider||null))
: null; : null;
if(!applied) $('modelSelect').value=savedModel; if(!applied) $('modelSelect').value=stateToApply.model;
// If the value didn't take (model not in list), clear the bad pref // If the value didn't take (model not in list), clear the bad pref only
if(!applied&&$('modelSelect').value!==savedModel){ // for persisted browser preferences. Active sessions remain authoritative.
if(!applied&&sessionModelState&&typeof _ensureModelOptionInDropdown==='function'){
_ensureModelOptionInDropdown(sessionModelState.model,$('modelSelect'),sessionModelState.model_provider||null);
}
else if(!applied&&!sessionModelState&&$('modelSelect').value!==stateToApply.model){
if(typeof _clearPersistedModelState==='function') _clearPersistedModelState(); if(typeof _clearPersistedModelState==='function') _clearPersistedModelState();
else { else {
localStorage.removeItem('hermes-webui-model'); localStorage.removeItem('hermes-webui-model');
+11 -2
View File
@@ -490,8 +490,17 @@ async function newSession(flash, options={}){
const sessionProvider=S.session.model_provider||null; const sessionProvider=S.session.model_provider||null;
const currentProvider=currentModelState.model_provider||null; const currentProvider=currentModelState.model_provider||null;
if(S.session.model!==modelSel.value || sessionProvider !== currentProvider){ if(S.session.model!==modelSel.value || sessionProvider !== currentProvider){
_applyModelToDropdown(S.session.model,modelSel,sessionProvider); let sessionModelApplied=_applyModelToDropdown(S.session.model,modelSel,sessionProvider);
if(typeof syncModelChip==='function') syncModelChip(); if(!sessionModelApplied){
const opt=document.createElement('option');
opt.value=S.session.model;
opt.textContent=typeof getModelLabel==='function'?getModelLabel(S.session.model):S.session.model;
opt.dataset.custom='1';
opt.dataset.provider=sessionProvider||'';
modelSel.appendChild(opt);
sessionModelApplied=_applyModelToDropdown(S.session.model,modelSel,sessionProvider);
}
if(sessionModelApplied&&typeof syncModelChip==='function') syncModelChip();
} }
} }
// Reset per-session visual state: a fresh chat is idle even if another // Reset per-session visual state: a fresh chat is idle even if another
@@ -35,6 +35,27 @@ def test_new_chat_syncs_model_picker_when_default_provider_changes_but_model_id_
assert "_applyModelToDropdown(S.session.model,modelSel,sessionProvider)" in fn assert "_applyModelToDropdown(S.session.model,modelSel,sessionProvider)" in fn
def test_new_chat_inserts_session_model_when_static_picker_lacks_default():
fn = _new_session_function()
assert "sessionModelApplied" in fn
assert "document.createElement('option')" in fn
assert "opt.value=S.session.model" in fn
assert "opt.dataset.provider=sessionProvider||''" in fn
assert "modelSel.appendChild(opt)" in fn
def test_boot_model_hydration_prefers_active_session_over_persisted_model():
boot_js = Path("static/boot.js").read_text(encoding="utf-8")
marker = "const sessionModelState=S.session&&S.session.model"
assert marker in boot_js
session_branch = boot_js[boot_js.index(marker) : boot_js.index("if(S.session) syncTopbar();", boot_js.index(marker))]
assert "_applyModelToDropdown(sessionModelState.model,$('modelSelect'),sessionModelState.model_provider||null)" in session_branch
assert "savedState" in session_branch
assert session_branch.index("sessionModelState") < session_branch.index("savedState"), (
"active session model must be considered before localStorage so stale saved model preferences cannot override new chats"
)
def test_new_chat_does_not_send_stale_dropdown_model_when_session_has_default_model(): def test_new_chat_does_not_send_stale_dropdown_model_when_session_has_default_model():
assert "model:S.session.model||$('modelSelect').value" in MESSAGES_JS assert "model:S.session.model||$('modelSelect').value" in MESSAGES_JS
assert "model_provider:S.session.model_provider||null" in MESSAGES_JS assert "model_provider:S.session.model_provider||null" in MESSAGES_JS