fix(#5989): separate picker dedup from restore matching

This commit is contained in:
Rod Boev authored and nesquena-hermes committed 2026-07-13 18:57:15 +00:00
1 parent 052effcfc4
commit 58725d5de9
3 files changed
+61 -9

No files matched your search

+9 -8
View File
@@ -2739,7 +2739,6 @@ function _modelPickerOptionIdentity(modelId){
? value.substring(value.lastIndexOf(':')+1) ? value.substring(value.lastIndexOf(':')+1)
: value.substring(value.indexOf(':')+1); : value.substring(value.indexOf(':')+1);
} }
value=value.split('/').pop();
return value.replace(/-/g,'.').toLowerCase(); return value.replace(/-/g,'.').toLowerCase();
} }
function _deduplicateModelPickerOptions(sel,selectedValue){ function _deduplicateModelPickerOptions(sel,selectedValue){
@@ -2758,8 +2757,7 @@ function _deduplicateModelPickerOptions(sel,selectedValue){
if(candidates.length<2) continue; if(candidates.length<2) continue;
const selected=candidates.find(opt=>opt.value===selectedValue); const selected=candidates.find(opt=>opt.value===selectedValue);
const routable=candidates.find(opt=>String(opt.value||'').startsWith('@')); const routable=candidates.find(opt=>String(opt.value||'').startsWith('@'));
const survivor=(selected&&String(selected.value||'').startsWith('@')) const survivor=selected||routable||candidates[0];
|| !routable ? (selected||routable||candidates[0]) : routable;
for(const opt of candidates){ for(const opt of candidates){
if(opt===survivor) continue; if(opt===survivor) continue;
group.removeChild(opt); group.removeChild(opt);
@@ -3055,11 +3053,14 @@ function _findModelInDropdown(modelId, sel, preferredProviderId){
const pref=String(preferredProviderId||'').toLowerCase(); const pref=String(preferredProviderId||'').toLowerCase();
if(!pref || !exactProv || exactProv===pref) return modelId; if(!pref || !exactProv || exactProv===pref) return modelId;
} }
// 1. Normalize: lowercase, strip namespace prefix, replace hyphens→dots. // 1. Restore lookup keeps the older hierarchy-preserving matcher instead of
// Also strip @provider: prefix from deduplicated model IDs (#1228, #1313). // the picker-dedup identity, so missing qualified models do not substitute a
const norm=s=>typeof _modelPickerOptionIdentity==='function' // different suffix-sharing sibling.
? _modelPickerOptionIdentity(s) const norm=s=>String(s||'')
: s.toLowerCase().replace(/^[^/]+\//,'').replace(/^@([^:]+:)+/,'').replace(/-/g,'.'); .toLowerCase()
.replace(/^@([^:]+:)+/,'')
.replace(/^[^/]+\//,'')
.replace(/-/g,'.');
const target=norm(modelId); const target=norm(modelId);
let explicitProvider=''; let explicitProvider='';
const rawModel=String(modelId||''); const rawModel=String(modelId||'');
@@ -262,6 +262,29 @@ console.log(_findModelInDropdown('google/gemma-4-27b', sel, 'custom:beta') || ''
resolved = subprocess.run(["node", "-e", script], capture_output=True, text=True, check=True) resolved = subprocess.run(["node", "-e", script], capture_output=True, text=True, check=True)
assert resolved.stdout.strip() == "@custom:beta:google/gemma-4-27b" assert resolved.stdout.strip() == "@custom:beta:google/gemma-4-27b"
def test_find_model_returns_no_match_for_missing_qualified_same_suffix_sibling(self):
import re
import subprocess
src = self._read_js()
helper = re.search(r"function _getOptionProviderId\(opt\)\{.*?\n\}", src, re.S)
finder = re.search(r"function _findModelInDropdown\(modelId, sel, preferredProviderId\)\{.*?\n\}", src, re.S)
assert helper, "_getOptionProviderId() not found in ui.js"
assert finder, "_findModelInDropdown() not found in ui.js"
script = f"""
{helper.group(0)}
{finder.group(0)}
const sel = {{
options: [
{{ value: 'vendor-b/catalog/deepseek-v4-pro', parentElement: {{ tagName: 'OPTGROUP', dataset: {{ provider: 'custom:beta' }} }} }},
]
}};
console.log(_findModelInDropdown('vendor-a/deepseek-v4-pro', sel, 'custom:alpha') || '');
"""
resolved = subprocess.run(["node", "-e", script], capture_output=True, text=True, check=True)
assert resolved.stdout.strip() == ""
class TestResolveModelProviderColonInProviderId(unittest.TestCase): class TestResolveModelProviderColonInProviderId(unittest.TestCase):
"""resolve_model_provider() must handle provider_ids containing ':'. """resolve_model_provider() must handle provider_ids containing ':'.
@@ -79,10 +79,24 @@ const other=new Node('option'); other.value='x-ai/grok-4.5'; otherGroup.appendCh
const catalogFirst=makeSelect(); const catalogFirst=makeSelect();
addCatalog(catalogFirst,'x-ai/grok-4.5'); addCatalog(catalogFirst,'x-ai/grok-4.5');
_addLiveModelsToSelect('custom:llm-proxy',[{{id:'x-ai/grok-4.5',label:'Grok 4.5'}}],catalogFirst); _addLiveModelsToSelect('custom:llm-proxy',[{{id:'x-ai/grok-4.5',label:'Grok 4.5'}}],catalogFirst);
const selectedBare=makeSelect();
addCatalog(selectedBare,'x-ai/grok-4.5');
selectedBare.value='x-ai/grok-4.5';
_addLiveModelsToSelect('custom:llm-proxy',[{{id:'x-ai/grok-4.5',label:'Grok 4.5'}}],selectedBare);
const sameSuffix=makeSelect();
addCatalog(sameSuffix,'vendor-a/deepseek-v4-pro');
addCatalog(sameSuffix,'vendor-b/catalog/deepseek-v4-pro');
sameSuffix.value='vendor-a/deepseek-v4-pro';
const unnamespaced=makeSelect(); const unnamespaced=makeSelect();
addCatalog(unnamespaced,'gpt-4o'); addCatalog(unnamespaced,'gpt-4o');
_addLiveModelsToSelect('custom:llm-proxy',[{{id:'@custom:llm-proxy:gpt-4o',label:'GPT-4o'}}],unnamespaced); _addLiveModelsToSelect('custom:llm-proxy',[{{id:'@custom:llm-proxy:gpt-4o',label:'GPT-4o'}}],unnamespaced);
console.log(JSON.stringify({{liveFirst:snapshot(liveFirst),catalogFirst:snapshot(catalogFirst),unnamespaced:snapshot(unnamespaced)}})); console.log(JSON.stringify({{
liveFirst:snapshot(liveFirst),
catalogFirst:snapshot(catalogFirst),
selectedBare:snapshot(selectedBare),
sameSuffix:snapshot(sameSuffix),
unnamespaced:snapshot(unnamespaced),
}}));
""" """
result = subprocess.run(["node", "-e", script], capture_output=True, text=True) result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
assert result.returncode == 0, result.stderr assert result.returncode == 0, result.stderr
@@ -104,6 +118,20 @@ def test_catalog_first_keeps_the_routable_proxy_row():
} }
def test_selected_bare_occurrence_survives_same_group_dedup():
assert _run_harness()["selectedBare"] == {
"groups": [["x-ai/grok-4.5"]],
"selected": "x-ai/grok-4.5",
}
def test_same_suffix_models_in_one_group_do_not_collapse():
assert _run_harness()["sameSuffix"] == {
"groups": [["vendor-a/deepseek-v4-pro", "vendor-b/catalog/deepseek-v4-pro"]],
"selected": "vendor-a/deepseek-v4-pro",
}
def test_unnamespaced_custom_proxy_value_deduplicates_with_catalog(): def test_unnamespaced_custom_proxy_value_deduplicates_with_catalog():
assert _run_harness()["unnamespaced"] == { assert _run_harness()["unnamespaced"] == {
"groups": [["@custom:llm-proxy:gpt-4o"]], "groups": [["@custom:llm-proxy:gpt-4o"]],