function inferredOwnerFromBucket(bucket){return bucket?String(bucket).split('/')[0]:''}
function inferredJobUrl(summary={}, detail={}){if(summary.job_url)return summary.job_url;const launch=detail.launch||{};const stateObj=detail.state||{};if(launch.job_url)return launch.job_url;if(stateObj.job_url)return stateObj.job_url;const jobId=summary.job_id||launch.job_id||stateObj.job_id;const owner=summary.created_by||summary.username||summary.owner||launch.created_by||launch.username||launch.owner||stateObj.created_by||stateObj.username||stateObj.owner||state.user?.username||inferredOwnerFromBucket(summary.bucket_source||detail.bucket_source||state.bucketSource);return jobId&&owner?`https://huggingface.co/jobs/${owner}/${jobId}`:''}
function runMatchesStatus(run,status){const s=String(run.status||'unknown').toLowerCase();if(!status||status==='all')return true;if(status==='running')return s.includes('running')||s==='queued'||s==='pending'||s==='started'||s==='waiting';if(status==='full_inference_success')return s.includes('success')||s.includes('passed');if(status==='manual_hardware_required')return s.includes('manual')||s.includes('hardware');if(status==='technical_blocker')return s.includes('blocker');if(status==='failed')return s.includes('failed')||s.includes('error');return s===String(status).toLowerCase()}
function runMatchesQuery(run,query){if(!query)return true;const haystack=[run.run_id,run.model_id,run.target_space,run.status,run.kind,run.selected_hardware].map(x=>String(x||'').toLowerCase()).join(' ');return haystack.includes(String(query).toLowerCase())}
function runSortTimestamp(run={}){
const raw=run.updated_at||run.created_at||run.started_at||run.cancelled_at||'';
const ts=Date.parse(raw);
if(Number.isFinite(ts))return ts;
const id=String(run.run_id||'');
const m=id.match(/(\d{8})[-_]?(\d{6})/);
if(m){
const d=m[1],t=m[2];
return Date.parse(`${d.slice(0,4)}-${d.slice(4,6)}-${d.slice(6,8)}T${t.slice(0,2)}:${t.slice(2,4)}:${t.slice(4,6)}Z`);
}
return 0;
}
function sortRunsNewestFirst(runs=[]){
return [...(runs||[])].sort((a,b)=>runSortTimestamp(b)-runSortTimestamp(a)||String(b.run_id||'').localeCompare(String(a.run_id||'')));
}
function filteredRuns(){return (state.runsCache||[]).filter(r=>runMatchesStatus(r,state.runStatusFilter||'all')&&runMatchesQuery(r,state.runSearch||''))}
function renderRunStats(){const counts={all:state.runsCache.length,running:0,full_inference_success:0,manual_hardware_required:0,failed:0,stale:0};for(const r of state.runsCache){if(runMatchesStatus(r,'running'))counts.running+=1;if(runMatchesStatus(r,'full_inference_success'))counts.full_inference_success+=1;if(runMatchesStatus(r,'manual_hardware_required'))counts.manual_hardware_required+=1;if(runMatchesStatus(r,'failed'))counts.failed+=1;if(runMatchesStatus(r,'stale'))counts.stale+=1}document.querySelectorAll('.filter[data-status]').forEach(btn=>{const status=btn.dataset.status||'all';const label={all:'All',running:'Running',full_inference_success:'Success',manual_hardware_required:'Manual',failed:'Failed',technical_blocker:'Blockers',stale:'Stale'}[status]||status;const count=counts[status];btn.textContent=typeof count==='number'?`${label}`:label;btn.classList.toggle('active',status===state.runStatusFilter)})}
function renderRunRows(runs,compact=false){return (runs||[]).map(r=>``).join('')}
function renderRunPagination(total){const root=$('runsPagination');if(!root)return;const perPage=state.runsPerPage||5;const pages=Math.max(1,Math.ceil(total/perPage));state.runPage=Math.min(Math.max(0,state.runPage||0),pages-1);if(total<=perPage){root.innerHTML='';return}root.innerHTML=`Page ${state.runPage+1} / ${pages}`;root.querySelectorAll('[data-page-action]').forEach(btn=>btn.addEventListener('click',()=>{state.runPage+=(btn.dataset.pageAction==='next'?1:-1);renderRunsFromCache()}))}
function renderRunsFromCache(){state.runsCache=sortRunsNewestFirst(state.runsCache||[]);const runs=filteredRuns();const perPage=state.runsPerPage||5;state.runPage=Math.min(Math.max(0,state.runPage||0),Math.max(0,Math.ceil(runs.length/perPage)-1));const visible=runs.slice(state.runPage*perPage,state.runPage*perPage+perPage);for(const [id,compact] of [['runsTableHome',true],['runsTable',false]]){const root=$(id);if(root)root.innerHTML=renderRunRows(visible,compact)||'
No runs match this filter.
'}renderRunStats();renderRunPagination(runs.length);document.querySelectorAll('.run-row[data-run]').forEach(row=>row.addEventListener('click',()=>selectRun(row.dataset.run)));if(state.runId)document.querySelectorAll('.run-row').forEach(row=>row.classList.toggle('selected',row.dataset.run===state.runId))}
async function loadRuns(forceNetwork=false){const stale=Date.now()-(state.runsLastLoaded||0)>45000;const shouldFetch=forceNetwork||!state.runsCache.length||stale;try{if(shouldFetch){document.querySelectorAll('.runs-panel,.full-runs-panel').forEach(x=>x.classList.add('is-refreshing'));const data=await apiGet(`/api/runs?limit=100&bucket_name=${encodeURIComponent(state.bucketName)}`);state.runsCache=sortRunsNewestFirst(data.runs||[]);state.runsLastLoaded=Date.now();if(data.bucket_source)state.bucketSource=data.bucket_source}renderRunsFromCache()}catch(e){for(const id of ['runsTable','runsTableHome']){const root=$(id);if(root)root.textContent='Could not load runs: '+e.message}if(forceNetwork)console.warn(e)}finally{document.querySelectorAll('.runs-panel,.full-runs-panel').forEach(x=>x.classList.remove('is-refreshing'))}}
async function selectRun(runId){if(!runId)return;document.querySelectorAll('.run-row').forEach(row=>row.classList.toggle('selected',row.dataset.run===runId));setBuildModeInspecting(runId);try{const detail=await apiGet(`/api/runs/${encodeURIComponent(runId)}?bucket_name=${encodeURIComponent(state.bucketName)}`);if(detail.bucket_source)state.bucketSource=detail.bucket_source;renderRunDetail(detail);startPolling(runId);/* Keep the user on the current tab: selecting a run updates the side/detail panels without forcing navigation. */}catch(e){const report=$('reportPreview');if(report)report.textContent='Could not load run details: '+e.message;}}
async function deleteSelectedRun(){
const detail=state.selectedRunDetail||{};
const summary=detail.summary||{};
const runId=summary.run_id||detail.run_id||state.runId;
if(!runId){showMessage('Select a run before deleting it.','warning');return}
const status=String(summary.status||detail.state?.status||'').toLowerCase();
if(runMatchesStatus({status},'running')&&!confirm(`Run ${runId} looks active. Delete its bucket folder anyway?`))return;
if(!confirm(`Delete run ${runId} from the bucket? This removes its artifacts, logs, state, and report.`))return;
const btn=$('deleteSelectedRun');
setButtonBusy('deleteSelectedRun',true,'Deleting…');
try{
await apiDelete(`/api/runs/${encodeURIComponent(runId)}?bucket_name=${encodeURIComponent(state.bucketName)}`);
showMessage(`Deleted run ${runId}`,'success');
if(state.poll){clearInterval(state.poll);state.poll=null}
if(state.runId===runId)state.runId=null;
clearSavedRun();
state.selectedRunDetail=null;
state.activeLinks={};
state.runsCache=(state.runsCache||[]).filter(r=>r.run_id!==runId);
document.querySelectorAll('.run-row').forEach(row=>row.classList.remove('selected'));
resetRunDetails();
renderRunsFromCache();
await loadRuns(true);
}catch(e){
showMessage(e.message,true);
}finally{
if(btn){btn.disabled=true;btn.textContent='Delete run from bucket'}
}
}
function renderSelectedLinks(summary={},detail={}){const runId=summary.run_id||detail.run_id;const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;const artifacts=summary.artifacts_url||(typeof artifactUrl==='function'?artifactUrl(runId,bucket):'');const jobUrl=inferredJobUrl(summary,detail);setLink('selectedOpenJob',jobUrl);const ready=typeof runPassedSpaceCreation==='function'?runPassedSpaceCreation({summary,detail,events:detail.events||[]}):Boolean(summary.target_space_url);setLink('selectedOpenSpace',ready?summary.target_space_url:'');setLink('selectedOpenSettings',ready&&summary.target_space_url?summary.target_space_url+'/settings':'');setLink('selectedOpenArtifacts',artifacts);const btn=$('selectedValidateBtn');if(btn){btn.disabled=!summary.target_space||!ready;btn.onclick=prepareValidationFromActiveRun}}
function renderBlockers(detail={}){const root=$('selectedBlockers');if(!root)return;const summary=detail.summary||{};const blockers=detail.technical_blockers||{};const manual=summary.manual_hardware_required||String(summary.status||'').includes('manual_hardware_required');const blockerItems=Array.isArray(blockers.blockers)?blockers.blockers:[];if(!manual&&!blockerItems.length){root.hidden=true;root.innerHTML='';return}root.hidden=false;const parts=[];if(manual)parts.push('Manual hardware required
Open Space settings, choose the recommended GPU, then run validation.
');for(const b of blockerItems.slice(0,4)){parts.push(`${escapeHtml(shortText(b.type||b.name||'Technical blocker',32))}