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.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"]],
|
||||||
|
|||||||
Reference in new issue
Block a user