mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-07-19 14:12:01 +00:00
fix: prevent cross-provider model selection from silently reverting to default (#4363)
When the model dropdown rebuilds (panel switch, provider refresh, or a live-model fetch that times out and serves a fallback catalog), a cross-provider selection could vanish from the refreshed catalog and the browser would snap the <select> back to its first <option> (the default model). _reconcileModelDropdownSelection now routes each selection branch through an _applyOrEnsure helper that delegates to _ensureModelOptionInDropdown, re-injecting the chosen model as a custom option when it is missing from the rebuilt catalog instead of returning null. Branch order and per-branch model/provider arguments are unchanged. Maintainer note: simplified the contributor's helper to delegate straight to _ensureModelOptionInDropdown (which already applies-first-then-injects), removing a redundant double scan; re-pointed the test_populate_model_dropdown_reconciles_selection_after_rebuild string assertions to the new form (the behavioral node-driver tests for boot-default precedence and in-page selection preservation are unchanged and still pass). Co-authored-by: Ed <edou@edoudoux.com>
This commit is contained in:
+17
-3
@@ -1531,14 +1531,28 @@ function _reconcileModelDropdownSelection(sel,data,previousState,opts){
|
||||
// rebuild should preserve the loaded session model or the user's current
|
||||
// in-page selection when it still exists in the refreshed catalog.
|
||||
const shouldApplyBootDefault=!!(opts&&opts.preferProfileDefaultOnFreshBoot);
|
||||
|
||||
// Helper: apply the requested model, but if it is missing from the current
|
||||
// catalog (cross-provider selection after a partial/timed-out rebuild), inject
|
||||
// it as a custom option instead of returning null and letting the browser
|
||||
// silently snap to the first <option>. _ensureModelOptionInDropdown already
|
||||
// tries _applyModelToDropdown first, so delegate to it (single scan) and keep
|
||||
// the plain-apply fallback for the unlikely case it is unavailable.
|
||||
const _applyOrEnsure = function(modelId, providerId) {
|
||||
if (typeof _ensureModelOptionInDropdown === 'function') {
|
||||
return _ensureModelOptionInDropdown(modelId, sel, providerId);
|
||||
}
|
||||
return _applyModelToDropdown(modelId, sel, providerId);
|
||||
};
|
||||
|
||||
if(shouldApplyBootDefault && data&&data.default_model && !(activeSession&&activeSession.model)){
|
||||
return _applyModelToDropdown(data.default_model,sel,data.active_provider||null);
|
||||
return _applyOrEnsure(data.default_model, data.active_provider||null);
|
||||
}
|
||||
if(activeSession&&activeSession.model){
|
||||
return _applyModelToDropdown(activeSession.model,sel,activeSession.model_provider||null);
|
||||
return _applyOrEnsure(activeSession.model, activeSession.model_provider||null);
|
||||
}
|
||||
if(previousState&&previousState.model){
|
||||
return _applyModelToDropdown(previousState.model,sel,previousState.model_provider||null);
|
||||
return _applyOrEnsure(previousState.model, previousState.model_provider||null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -196,9 +196,16 @@ def test_populate_model_dropdown_reconciles_selection_after_rebuild():
|
||||
assert "_reconcileModelDropdownSelection(sel,data,previousSelection,opts);" in UI_JS
|
||||
snippet = _reconcile_selection_snippet()
|
||||
assert "preferProfileDefaultOnFreshBoot" in snippet
|
||||
assert "return _applyModelToDropdown(data.default_model,sel,data.active_provider||null);" in snippet
|
||||
assert "return _applyModelToDropdown(activeSession.model,sel,activeSession.model_provider||null);" in snippet
|
||||
assert "return _applyModelToDropdown(previousState.model,sel,previousState.model_provider||null);" in snippet
|
||||
# #4363: each branch now routes through _applyOrEnsure, which delegates to
|
||||
# _ensureModelOptionInDropdown so a cross-provider model missing from a
|
||||
# partially-rebuilt catalog is injected as a custom option instead of the
|
||||
# browser silently snapping the <select> to its first <option>. The
|
||||
# branch ORDER + per-branch model/provider arguments are unchanged.
|
||||
assert "_applyOrEnsure(data.default_model, data.active_provider||null)" in snippet
|
||||
assert "_applyOrEnsure(activeSession.model, activeSession.model_provider||null)" in snippet
|
||||
assert "_applyOrEnsure(previousState.model, previousState.model_provider||null)" in snippet
|
||||
# the helper must fall back to injecting the missing option, not return null
|
||||
assert "_ensureModelOptionInDropdown(modelId, sel, providerId)" in snippet
|
||||
assert "_readPersistedModelState()" not in snippet
|
||||
assert "localStorage.getItem('hermes-webui-model')" not in snippet
|
||||
|
||||
|
||||
Reference in New Issue
Block a user