Add /models fetch timeout and health-derived default fallback for model dropdowns.
This commit is contained in:
+24
-8
@@ -171,6 +171,7 @@ const drop=document.getElementById('drop'), fileInput=document.getElementById('f
|
|||||||
logBox=document.getElementById('logBox'),
|
logBox=document.getElementById('logBox'),
|
||||||
logHint=document.getElementById('logHint');
|
logHint=document.getElementById('logHint');
|
||||||
let chosen=null, polling=null, currentJobId=null, sheetPage={}, viewerZoom=1, reviewDirty=false;
|
let chosen=null, polling=null, currentJobId=null, sheetPage={}, viewerZoom=1, reviewDirty=false;
|
||||||
|
let defaultVisionModel='', defaultTextModel='';
|
||||||
|
|
||||||
function modelLabel(m){
|
function modelLabel(m){
|
||||||
// Include per-1M-token pricing when the catalog provides it.
|
// Include per-1M-token pricing when the catalog provides it.
|
||||||
@@ -212,6 +213,12 @@ function saveModelsCache(data){
|
|||||||
try{ localStorage.setItem(MODELS_CACHE_KEY, JSON.stringify({ts:Date.now(), data})); }
|
try{ localStorage.setItem(MODELS_CACHE_KEY, JSON.stringify({ts:Date.now(), data})); }
|
||||||
catch(e){}
|
catch(e){}
|
||||||
}
|
}
|
||||||
|
function fetchWithTimeout(url, ms){
|
||||||
|
return Promise.race([
|
||||||
|
fetch(url, {cache:'no-store'}),
|
||||||
|
new Promise((_,reject)=>setTimeout(()=>reject(new Error('timeout')), ms))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadModels(){
|
async function loadModels(){
|
||||||
const note=document.getElementById('modelNote');
|
const note=document.getElementById('modelNote');
|
||||||
@@ -225,7 +232,8 @@ async function loadModels(){
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
const res=await fetch('/models');
|
note.textContent='fetching models...';
|
||||||
|
const res=await fetchWithTimeout('/models', 5000);
|
||||||
if(!res.ok) throw new Error('models HTTP '+res.status);
|
if(!res.ok) throw new Error('models HTTP '+res.status);
|
||||||
const data=await res.json();
|
const data=await res.json();
|
||||||
saveModelsCache(data);
|
saveModelsCache(data);
|
||||||
@@ -235,10 +243,12 @@ async function loadModels(){
|
|||||||
modelsLoaded=true;
|
modelsLoaded=true;
|
||||||
note.textContent='('+(data.text||[]).length+' text / '+(data.vision||[]).length+' vision available)';
|
note.textContent='('+(data.text||[]).length+' text / '+(data.vision||[]).length+' vision available)';
|
||||||
}catch(err){
|
}catch(err){
|
||||||
visionSel.innerHTML='<option value="">(default)</option>';
|
const fallback=[{id:defaultVisionModel||'', name:defaultVisionModel||'(default)', prompt_usd_per_mtok:null, completion_usd_per_mtok:null}];
|
||||||
textSel.innerHTML='<option value="">(default)</option>';
|
fillSelect(visionSel, fallback.filter(m=>m.id), defaultVisionModel);
|
||||||
|
const fallbackText=[{id:defaultTextModel||'', name:defaultTextModel||'(default)', prompt_usd_per_mtok:null, completion_usd_per_mtok:null}];
|
||||||
|
fillSelect(textSel, fallbackText.filter(m=>m.id), defaultTextModel);
|
||||||
visionSel.disabled=false; textSel.disabled=false;
|
visionSel.disabled=false; textSel.disabled=false;
|
||||||
note.textContent='using configured defaults (list unavailable)';
|
note.textContent='using configured defaults ('+(err.message==='timeout'?'fetch timed out':'list unavailable')+')';
|
||||||
console.warn('Could not load models:', err);
|
console.warn('Could not load models:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -674,11 +684,17 @@ async function finalizeReview(){
|
|||||||
|
|
||||||
// If opened from an email link (/?job=<id>), load that job's results directly.
|
// If opened from an email link (/?job=<id>), load that job's results directly.
|
||||||
(function init(){
|
(function init(){
|
||||||
// Show the deployed build in the header so it's obvious which version is up.
|
// Fetch /health first: build tag for the header and default models as a
|
||||||
fetch('/health').then(r=>r.ok?r.json():null).then(h=>{
|
// fallback if the larger /models catalog fails or times out.
|
||||||
if(h&&h.build) document.getElementById('buildTag').textContent=' · build '+h.build;
|
fetch('/health', {cache:'no-store'}).then(r=>r.ok?r.json():null).then(h=>{
|
||||||
}).catch(()=>{});
|
if(h){
|
||||||
|
if(h.build) document.getElementById('buildTag').textContent=' · build '+h.build;
|
||||||
|
if(h.model) defaultVisionModel=h.model;
|
||||||
|
if(h.text_model) defaultTextModel=h.text_model;
|
||||||
|
}
|
||||||
|
}).catch(()=>{}).finally(()=>{
|
||||||
loadModels();
|
loadModels();
|
||||||
|
});
|
||||||
const jobId=new URLSearchParams(location.search).get('job');
|
const jobId=new URLSearchParams(location.search).get('job');
|
||||||
if(jobId){ statusEl.innerHTML='<span class="spinner"></span>Loading job '+esc(jobId)+'...'; poll(jobId); }
|
if(jobId){ statusEl.innerHTML='<span class="spinner"></span>Loading job '+esc(jobId)+'...'; poll(jobId); }
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user