From a49c0fbf8bbe65d1bba7353ea481aa378c7adfce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=B5=A9=E7=94=9F?= Date: Wed, 13 May 2026 14:42:03 +0800 Subject: [PATCH] fix(ui): Fix the issue where custom models are not displayed in the model configuration list - Fix the issue where custom models are not shown - Fix the issue where custom models are not ollama but go through the ollama model processing function, causing the hyphen '-' in the model name to be replaced with a space " " and the last letter to be lowercase --- static/ui.js | 131 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 115 insertions(+), 16 deletions(-) diff --git a/static/ui.js b/static/ui.js index d071d93e..0548b738 100644 --- a/static/ui.js +++ b/static/ui.js @@ -764,26 +764,67 @@ async function populateModelDropdown(){ const _modelsRes=await fetch(new URL('api/models',document.baseURI||location.href).href,{credentials:'include'}); if(_redirectIfUnauth(_modelsRes)) return; const data=await _modelsRes.json(); - if(!data.groups||!data.groups.length) return; // keep HTML defaults // Store active provider globally so the send path can warn on mismatch window._activeProvider=data.active_provider||null; // Store default model so newSession() can apply it (#872). // Per-page-load — not synced across browser tabs. window._defaultModel=data.default_model||null; window._configuredModelBadges=data.configured_model_badges||{}; + + const _synthGroupsFromConfigured=()=>{ + const badgeMap=window._configuredModelBadges||{}; + const grouped=new Map(); + const addModel=(providerId,modelId)=>{ + const pid=String(providerId||'configured').trim()||'configured'; + const mid=String(modelId||'').trim(); + if(!mid) return; + if(!grouped.has(pid)) grouped.set(pid,[]); + const arr=grouped.get(pid); + if(arr.some(m=>m.id===mid)) return; + arr.push({id:mid,label:getModelLabel(mid)}); + }; + + for(const [modelId,badge] of Object.entries(badgeMap)){ + const mid=String(modelId||'').trim(); + // Prefer canonical IDs only; skip derived aliases such as + // @provider:model and provider/model to avoid noisy duplicates. + if(!mid||mid.startsWith('@')||mid.includes('/')) continue; + const provider=(badge&&badge.provider)||'configured'; + addModel(provider,mid); + } + + if(grouped.size===0&&data&&data.default_model){ + addModel(data.active_provider||'configured',data.default_model); + } + + const groups=[]; + for(const [providerId,models] of grouped.entries()){ + const display=(String(providerId).startsWith('custom:') + ? String(providerId).slice('custom:'.length) + : String(providerId))||'Configured'; + groups.push({provider:display,provider_id:providerId,models}); + } + return groups; + }; + + const groups=(Array.isArray(data.groups)&&data.groups.length) + ? data.groups + : _synthGroupsFromConfigured(); + + if(!groups.length) return; // no server groups and no configured fallback // Clear existing options sel.innerHTML=''; _dynamicModelLabels={}; - for(const g of data.groups){ + for(const g of groups){ const og=document.createElement('optgroup'); og.label=g.provider; if(g.provider_id) og.dataset.provider=g.provider_id; - for(const m of g.models){ + for(const m of (Array.isArray(g.models)?g.models:[])){ const opt=document.createElement('option'); opt.value=m.id; opt.textContent=m.label; og.appendChild(opt); - _dynamicModelLabels[m.id]=m.label; + _dynamicModelLabels[m.id]=m.id; } // Hydrate the label map from extra_models too (the catalog tail that // doesn't render as