fix(ui): preserve endpoint-style custom slugs host:port in model parsers (#6884)
This commit is contained in:
1 parent
7db2695a5e
commit
c784ef7a12
2 files changed
+55
-6
No files matched your search
+35
-5
@@ -2982,9 +2982,19 @@ function _getOptionProviderId(opt){
|
||||
const value=String(opt.value||'');
|
||||
if(value.startsWith('@') && value.includes(':')){
|
||||
// Non-greedy parse for @custom:<slug>:<model> — provider is the slug only.
|
||||
// For @custom:backup:model-a:free → provider="custom:backup", not "custom:backup:model-a"
|
||||
// Preserves endpoint-style host:port custom slugs (e.g. custom:localhost:11434)
|
||||
// while keeping colon-bearing model ids (e.g. @custom:backup:model-a:free -> custom:backup).
|
||||
if(value.startsWith('@custom:')){
|
||||
const afterCustom=value.substring('@custom:'.length);
|
||||
const parts=afterCustom.split(':');
|
||||
if(parts.length>=3 && /^\d+$/.test(parts[1])){
|
||||
const port=parseInt(parts[1], 10);
|
||||
const host=parts[0];
|
||||
const hl=host.toLowerCase();
|
||||
if(port>=1 && port<=65535 && (hl==='localhost' || host.includes('.'))){
|
||||
return 'custom:'+host+':'+parts[1];
|
||||
}
|
||||
}
|
||||
const firstColon=afterCustom.indexOf(':');
|
||||
if(firstColon>=0) return 'custom:'+afterCustom.substring(0,firstColon);
|
||||
return 'custom:'+afterCustom;
|
||||
@@ -2998,9 +3008,19 @@ function _providerFromModelValue(modelId){
|
||||
const value=String(modelId||'').trim();
|
||||
if(value.startsWith('@')&&value.includes(':')){
|
||||
// Non-greedy parse for @custom:<slug>:<model> — provider is the slug only.
|
||||
// For @custom:backup:model-a:free → provider="custom:backup", not "custom:backup:model-a"
|
||||
// Preserves endpoint-style host:port custom slugs (e.g. custom:localhost:11434)
|
||||
// while keeping colon-bearing model ids (e.g. @custom:backup:model-a:free -> custom:backup).
|
||||
if(value.startsWith('@custom:')){
|
||||
const afterCustom=value.substring('@custom:'.length);
|
||||
const parts=afterCustom.split(':');
|
||||
if(parts.length>=3 && /^\d+$/.test(parts[1])){
|
||||
const port=parseInt(parts[1], 10);
|
||||
const host=parts[0];
|
||||
const hl=host.toLowerCase();
|
||||
if(port>=1 && port<=65535 && (hl==='localhost' || host.includes('.'))){
|
||||
return 'custom:'+host+':'+parts[1];
|
||||
}
|
||||
}
|
||||
const firstColon=afterCustom.indexOf(':');
|
||||
if(firstColon>=0) return 'custom:'+afterCustom.substring(0,firstColon);
|
||||
return 'custom:'+afterCustom;
|
||||
@@ -3018,9 +3038,19 @@ function _modelPickerOptionIdentity(modelId, providerId){
|
||||
if(exactPrefix && value.toLowerCase().startsWith(exactPrefix.toLowerCase())){
|
||||
value=value.substring(exactPrefix.length);
|
||||
}else if(value.startsWith('@custom:')){
|
||||
const namedProvider=value.substring('@custom:'.length);
|
||||
const splitAt=namedProvider.indexOf(':');
|
||||
value=splitAt>=0 ? namedProvider.substring(splitAt+1) : namedProvider;
|
||||
const afterCustom=value.substring('@custom:'.length);
|
||||
const parts=afterCustom.split(':');
|
||||
let splitAt=-1;
|
||||
if(parts.length>=3 && /^\d+$/.test(parts[1])){
|
||||
const port=parseInt(parts[1], 10);
|
||||
const host=parts[0];
|
||||
const hl=host.toLowerCase();
|
||||
if(port>=1 && port<=65535 && (hl==='localhost' || host.includes('.'))){
|
||||
splitAt=parts[0].length + 1 + parts[1].length;
|
||||
}
|
||||
}
|
||||
if(splitAt<0) splitAt=afterCustom.indexOf(':');
|
||||
value=splitAt>=0 ? afterCustom.substring(splitAt+1) : afterCustom;
|
||||
}else{
|
||||
value=value.substring(value.indexOf(':')+1);
|
||||
}
|
||||
|
||||
@@ -229,9 +229,16 @@ const modelAFree = {
|
||||
dataset: {}, // no data-model — normal catalog render path
|
||||
parentElement: group,
|
||||
};
|
||||
const localhostGroup = {tagName: 'OPTGROUP', dataset: {provider: 'custom:localhost:11434'}};
|
||||
const llama = {
|
||||
value: '@custom:localhost:11434:llama3.2',
|
||||
textContent: 'llama3.2',
|
||||
dataset: {},
|
||||
parentElement: localhostGroup,
|
||||
};
|
||||
const select = {
|
||||
id: 'modelSelect',
|
||||
options: [luna, sol, modelAFree],
|
||||
options: [luna, sol, modelAFree, llama],
|
||||
querySelectorAll() { return []; },
|
||||
get selectedOptions() { return [sol]; },
|
||||
get value() { return sol.value; },
|
||||
@@ -242,6 +249,8 @@ process.stdout.write(JSON.stringify({
|
||||
nonDefault: _modelStateForSelect(select, '@custom:hetmer.net:sol'),
|
||||
defaultUnprefixed: _modelStateForSelect(select, 'luna'),
|
||||
colonBearingModel: _modelStateForSelect(select, '@custom:hetmer.net:model-a:free'),
|
||||
localhostEndpoint: _modelStateForSelect(select, '@custom:localhost:11434:llama3.2'),
|
||||
missingOptionCustomInput: _modelStateForSelect(select, '@custom:localhost:11434:mistral-custom'),
|
||||
}));
|
||||
"""
|
||||
|
||||
@@ -276,6 +285,16 @@ def test_non_default_named_custom_provider_model_strips_qualified_prefix():
|
||||
"model": "model-a:free",
|
||||
"model_provider": "custom:hetmer.net",
|
||||
}
|
||||
# Endpoint-style custom provider (e.g. host:port derived from base_url authority):
|
||||
# preserves full custom:localhost:11434 for both catalog and missing-option paths.
|
||||
assert payload["localhostEndpoint"] == {
|
||||
"model": "llama3.2",
|
||||
"model_provider": "custom:localhost:11434",
|
||||
}
|
||||
assert payload["missingOptionCustomInput"] == {
|
||||
"model": "mistral-custom",
|
||||
"model_provider": "custom:localhost:11434",
|
||||
}
|
||||
|
||||
|
||||
# Round-trip: send -> persist -> restore for a colon-bearing named
|
||||
|
||||
Reference in new issue
Block a user