fix(#5989): separate picker dedup from restore matching
This commit is contained in:
1 parent
052effcfc4
commit
58725d5de9
3 files changed
+61
-9
No files matched your search
+9
-8
@@ -2739,7 +2739,6 @@ function _modelPickerOptionIdentity(modelId){
|
||||
? value.substring(value.lastIndexOf(':')+1)
|
||||
: value.substring(value.indexOf(':')+1);
|
||||
}
|
||||
value=value.split('/').pop();
|
||||
return value.replace(/-/g,'.').toLowerCase();
|
||||
}
|
||||
function _deduplicateModelPickerOptions(sel,selectedValue){
|
||||
@@ -2758,8 +2757,7 @@ function _deduplicateModelPickerOptions(sel,selectedValue){
|
||||
if(candidates.length<2) continue;
|
||||
const selected=candidates.find(opt=>opt.value===selectedValue);
|
||||
const routable=candidates.find(opt=>String(opt.value||'').startsWith('@'));
|
||||
const survivor=(selected&&String(selected.value||'').startsWith('@'))
|
||||
|| !routable ? (selected||routable||candidates[0]) : routable;
|
||||
const survivor=selected||routable||candidates[0];
|
||||
for(const opt of candidates){
|
||||
if(opt===survivor) continue;
|
||||
group.removeChild(opt);
|
||||
@@ -3055,11 +3053,14 @@ function _findModelInDropdown(modelId, sel, preferredProviderId){
|
||||
const pref=String(preferredProviderId||'').toLowerCase();
|
||||
if(!pref || !exactProv || exactProv===pref) return modelId;
|
||||
}
|
||||
// 1. Normalize: lowercase, strip namespace prefix, replace hyphens→dots.
|
||||
// Also strip @provider: prefix from deduplicated model IDs (#1228, #1313).
|
||||
const norm=s=>typeof _modelPickerOptionIdentity==='function'
|
||||
? _modelPickerOptionIdentity(s)
|
||||
: s.toLowerCase().replace(/^[^/]+\//,'').replace(/^@([^:]+:)+/,'').replace(/-/g,'.');
|
||||
// 1. Restore lookup keeps the older hierarchy-preserving matcher instead of
|
||||
// the picker-dedup identity, so missing qualified models do not substitute a
|
||||
// different suffix-sharing sibling.
|
||||
const norm=s=>String(s||'')
|
||||
.toLowerCase()
|
||||
.replace(/^@([^:]+:)+/,'')
|
||||
.replace(/^[^/]+\//,'')
|
||||
.replace(/-/g,'.');
|
||||
const target=norm(modelId);
|
||||
let explicitProvider='';
|
||||
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)
|
||||
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):
|
||||
"""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();
|
||||
addCatalog(catalogFirst,'x-ai/grok-4.5');
|
||||
_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();
|
||||
addCatalog(unnamespaced,'gpt-4o');
|
||||
_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)
|
||||
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():
|
||||
assert _run_harness()["unnamespaced"] == {
|
||||
"groups": [["@custom:llm-proxy:gpt-4o"]],
|
||||
|
||||
Reference in new issue
Block a user