Upload 7 files
Browse files- web/static/app.css +7 -13
- web/static/app.js +2 -11
- web/static/index.html +229 -0
- web/static/progress.js +40 -5
- web/static/runs.js +2 -2
web/static/app.css
CHANGED
|
@@ -133,10 +133,6 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 133 |
.activity-row strong{font-size:12px}
|
| 134 |
.activity-row p{margin:2px 0 0;color:var(--muted);font-size:11px}
|
| 135 |
.activity-row em{font-style:normal;font-size:10px;border-radius:999px;padding:3px 7px;background:#f2f4f7;color:#344054}
|
| 136 |
-
.report-home p{margin:0;color:#344054;font-size:12px;line-height:1.45}
|
| 137 |
-
.report-home strong{display:none}
|
| 138 |
-
.report-link{margin-top:8px}
|
| 139 |
-
|
| 140 |
.search-wrap{position:relative}
|
| 141 |
.search-wrap input{padding-right:34px}
|
| 142 |
.search-wrap span{position:absolute;right:12px;top:50%;transform:translateY(-50%);color:var(--muted)}
|
|
@@ -487,12 +483,10 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 487 |
.activity-row.error strong{color:#be123c}
|
| 488 |
|
| 489 |
/* v78: report preview is useful by itself */
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
}
|
| 495 |
-
.
|
| 496 |
-
|
| 497 |
-
}
|
| 498 |
-
.report-link{display:none!important}
|
|
|
|
| 133 |
.activity-row strong{font-size:12px}
|
| 134 |
.activity-row p{margin:2px 0 0;color:var(--muted);font-size:11px}
|
| 135 |
.activity-row em{font-style:normal;font-size:10px;border-radius:999px;padding:3px 7px;background:#f2f4f7;color:#344054}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
.search-wrap{position:relative}
|
| 137 |
.search-wrap input{padding-right:34px}
|
| 138 |
.search-wrap span{position:absolute;right:12px;top:50%;transform:translateY(-50%);color:var(--muted)}
|
|
|
|
| 483 |
.activity-row.error strong{color:#be123c}
|
| 484 |
|
| 485 |
/* v78: report preview is useful by itself */
|
| 486 |
+
|
| 487 |
+
/* v80: timeline failed-history / recovered feedback */
|
| 488 |
+
.step.status-warn{--ring-color:var(--amber)}
|
| 489 |
+
.step.status-warn:before{background:var(--amber)}
|
| 490 |
+
.step.status-warn .step-dot{--ring-color:var(--amber);color:#92400e}
|
| 491 |
+
.step.status-warn small{color:#92400e}
|
| 492 |
+
.top-actions .bucket-pill{display:none!important}
|
|
|
|
|
|
web/static/app.js
CHANGED
|
@@ -27,7 +27,7 @@ function setOperation(message,type='info',opts={}){const el=$('operationStatus')
|
|
| 27 |
function showMessage(msg,isError=false){const type=isError===true?'error':(typeof isError==='string'?isError:'info');setOperation(msg,type);const el=$('eventsPanel');if(el&&!state.runId)el.textContent=(isError?'Error: ':'')+msg}
|
| 28 |
function setBusy(isBusy,label='Workingβ¦'){state.busy=Boolean(isBusy);if(!state.busy&&$('operationStatus')?.className.includes('loading'))setOperation('');document.querySelectorAll('[data-action-button]').forEach(btn=>{btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady});if($('launchBuild'))$('launchBuild').disabled=state.busy||!state.bucketReady;if($('launchValidate'))$('launchValidate').disabled=state.busy;if(state.busy)setOperation(label,'loading')}
|
| 29 |
function setButtonBusy(id,isBusy,busyLabel){const btn=$(id);if(!btn)return;if(isBusy){btn.dataset.oldText=btn.textContent;btn.textContent=busyLabel||'Workingβ¦';btn.disabled=true;btn.classList.add('is-busy')}else{if(btn.dataset.oldText)btn.textContent=btn.dataset.oldText;btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady;btn.classList.remove('is-busy')}}
|
| 30 |
-
function setBucketReady(ok,message){state.bucketReady=Boolean(ok);const btn=$('launchBuild');if(btn)btn.disabled=state.busy||!state.bucketReady;setText('buildGateText',state.bucketReady?'Bucket ready. You can launch builds.':(message||'Create your private run bucket before launching builds.'));setText('
|
| 31 |
function effectivePreferredHardware(){const preferred=formValue('preferredHardware')||'zero-a10g';const fallback=formValue('fallbackHardware')||'a10g-large';const tryZero=$('tryZeroGpu')?.checked??true;if(tryZero)return preferred;if(preferred==='zero-a10g'||preferred.startsWith('zero'))return fallback;return preferred}
|
| 32 |
function hardwarePlanLabel(){const tryZero=$('tryZeroGpu')?.checked??true;const preferred=effectivePreferredHardware();const fallback=formValue('fallbackHardware')||'a10g-large';const allow=$('allowFallback')?.checked??true;if(tryZero)return `ZeroGPU β ${allow?fallback:'no fixed fallback'}`;return `${preferred}${allow&&fallback!==preferred?' β '+fallback+' fallback':''}`}
|
| 33 |
function validateBuildForm(){if(!state.bucketReady)return'Create or check your private run bucket before launching a build.';if(!formValue('modelId'))return'Enter a model card URL or model ID.';if(!formValue('targetSpace'))return'Enter a fresh target Space name.';if(!formValue('piModel'))return'Enter the Pi model to use.';return''}
|
|
@@ -48,7 +48,6 @@ function resetRunDetails(){
|
|
| 48 |
const status=$('progressStatus');if(status)status.className='badge neutral';
|
| 49 |
const fill=$('progressFill');if(fill){fill.style.width='0%';fill.classList.remove('is-running','progress-bump')}
|
| 50 |
setQuickLinks({});
|
| 51 |
-
const report=$('homeReportPreview');if(report)report.innerHTML='<strong>No run selected</strong><p>Select a run to preview its report and actions.</p>';
|
| 52 |
const full=$('reportPreview');if(full)full.textContent='No report selected.';
|
| 53 |
const manual=$('manualActionPanel');if(manual)manual.hidden=true;
|
| 54 |
|
|
@@ -81,14 +80,6 @@ async function refreshRunReportPreview(runId){
|
|
| 81 |
const reportText=(detail.report||'No report available.').slice(0,8000);
|
| 82 |
const report=$('reportPreview');
|
| 83 |
if(report)report.textContent=reportText;
|
| 84 |
-
const homeReport=$('homeReportPreview');
|
| 85 |
-
if(homeReport){
|
| 86 |
-
const summary=detail.summary||{};
|
| 87 |
-
const view=detail.view||{};
|
| 88 |
-
const header=view.header||{};
|
| 89 |
-
const firstLine=reportText.split('\n').find(Boolean)||'No report available yet.';
|
| 90 |
-
homeReport.innerHTML=`<strong>${escapeHtml(header.status_label||summary.status||'Run finished')}</strong><p>${escapeHtml(shortText(reportText,1200))}</p>`;
|
| 91 |
-
}
|
| 92 |
state.selectedRunDetail=detail;
|
| 93 |
if(typeof renderSelectedLinks==='function')renderSelectedLinks({...detail.summary,bucket_source:detail.bucket_source},detail);
|
| 94 |
if(typeof renderBlockers==='function')renderBlockers(detail);
|
|
@@ -119,7 +110,7 @@ async function resumeLatestRun(){setButtonBusy('resumeLatestRun',true,'Resuming
|
|
| 119 |
function switchView(view){state.currentView=view;document.querySelectorAll('.nav-item').forEach(b=>b.classList.toggle('active',b.dataset.view===view));document.querySelectorAll('[data-section]').forEach(s=>s.hidden=s.dataset.section!==view);setOperation('');if(view==='progress')loadRuns(false);const targetMap={build:'newBuildCard',progress:'runsExplorerPanel',validate:'spaceTestSection'};const target=$(targetMap[view]||'');if(target)target.scrollIntoView({behavior:'smooth',block:'start'})}
|
| 120 |
function bindNavigation(){document.querySelectorAll('.nav-item').forEach(btn=>btn.addEventListener('click',()=>switchView(btn.dataset.view||'build')));document.querySelectorAll('[data-view-target]').forEach(btn=>btn.addEventListener('click',()=>switchView(btn.dataset.viewTarget||'build')))}
|
| 121 |
function bindRunFilters(){document.querySelectorAll('.filter[data-status]').forEach(btn=>btn.addEventListener('click',()=>{state.runStatusFilter=btn.dataset.status||'all';state.runPage=0;renderRunsFromCache()}));for(const id of ['runSearch','runSearchFull'])$(id)?.addEventListener('input',e=>{state.runSearch=e.target.value||'';state.runPage=0;for(const other of ['runSearch','runSearchFull'])if($(other)&&$(other)!==e.target)$(other).value=state.runSearch;clearTimeout(state.runSearchTimer);state.runSearchTimer=setTimeout(renderRunsFromCache,80)})}
|
| 122 |
-
function bindActions(){$('launchBuild')?.setAttribute('data-requires-bucket','true');$('launchBuild')?.setAttribute('data-action-button','true');$('launchValidate')?.setAttribute('data-action-button','true');$('launchBuild')?.addEventListener('click',launchBuild);$('launchValidate')?.addEventListener('click',launchValidate);$('prepareNewBuild')?.addEventListener('click',prepareNewBuild);$('manualGoValidate')?.addEventListener('click',prepareValidationFromActiveRun);$('selectedValidateBtn')?.addEventListener('click',prepareValidationFromActiveRun);$('checkBucket')?.addEventListener('click',()=>checkBucket(true));$('
|
| 123 |
bindNavigation();bindActions();bindRunFilters();setText('hardwarePlan',hardwarePlanLabel());setBuildModeNew();renderTimeline(DEFAULT_TIMELINE);loadAppInfo();
|
| 124 |
|
| 125 |
// Pass 47 intentionally removed automatic switchView('progress') after launch and run selection.
|
|
|
|
| 27 |
function showMessage(msg,isError=false){const type=isError===true?'error':(typeof isError==='string'?isError:'info');setOperation(msg,type);const el=$('eventsPanel');if(el&&!state.runId)el.textContent=(isError?'Error: ':'')+msg}
|
| 28 |
function setBusy(isBusy,label='Workingβ¦'){state.busy=Boolean(isBusy);if(!state.busy&&$('operationStatus')?.className.includes('loading'))setOperation('');document.querySelectorAll('[data-action-button]').forEach(btn=>{btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady});if($('launchBuild'))$('launchBuild').disabled=state.busy||!state.bucketReady;if($('launchValidate'))$('launchValidate').disabled=state.busy;if(state.busy)setOperation(label,'loading')}
|
| 29 |
function setButtonBusy(id,isBusy,busyLabel){const btn=$(id);if(!btn)return;if(isBusy){btn.dataset.oldText=btn.textContent;btn.textContent=busyLabel||'Workingβ¦';btn.disabled=true;btn.classList.add('is-busy')}else{if(btn.dataset.oldText)btn.textContent=btn.dataset.oldText;btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady;btn.classList.remove('is-busy')}}
|
| 30 |
+
function setBucketReady(ok,message){state.bucketReady=Boolean(ok);const btn=$('launchBuild');if(btn)btn.disabled=state.busy||!state.bucketReady;setText('buildGateText',state.bucketReady?'Bucket ready. You can launch builds.':(message||'Create your private run bucket before launching builds.'));setText('bucketInlineStatus',state.bucketReady?'Bucket ready':'Not ready');for(const id of ['bucketInlineDot','sidebarBucketDot']){const dot=$(id);if(dot)dot.className='status-dot '+(state.bucketReady?'green':'amber-dot')}const inline=$('bucketInlineStatus');if(inline)inline.className='badge '+(state.bucketReady?'success':'warn');document.querySelectorAll('[data-requires-bucket="true"]').forEach(btn=>btn.disabled=state.busy||!state.bucketReady)}
|
| 31 |
function effectivePreferredHardware(){const preferred=formValue('preferredHardware')||'zero-a10g';const fallback=formValue('fallbackHardware')||'a10g-large';const tryZero=$('tryZeroGpu')?.checked??true;if(tryZero)return preferred;if(preferred==='zero-a10g'||preferred.startsWith('zero'))return fallback;return preferred}
|
| 32 |
function hardwarePlanLabel(){const tryZero=$('tryZeroGpu')?.checked??true;const preferred=effectivePreferredHardware();const fallback=formValue('fallbackHardware')||'a10g-large';const allow=$('allowFallback')?.checked??true;if(tryZero)return `ZeroGPU β ${allow?fallback:'no fixed fallback'}`;return `${preferred}${allow&&fallback!==preferred?' β '+fallback+' fallback':''}`}
|
| 33 |
function validateBuildForm(){if(!state.bucketReady)return'Create or check your private run bucket before launching a build.';if(!formValue('modelId'))return'Enter a model card URL or model ID.';if(!formValue('targetSpace'))return'Enter a fresh target Space name.';if(!formValue('piModel'))return'Enter the Pi model to use.';return''}
|
|
|
|
| 48 |
const status=$('progressStatus');if(status)status.className='badge neutral';
|
| 49 |
const fill=$('progressFill');if(fill){fill.style.width='0%';fill.classList.remove('is-running','progress-bump')}
|
| 50 |
setQuickLinks({});
|
|
|
|
| 51 |
const full=$('reportPreview');if(full)full.textContent='No report selected.';
|
| 52 |
const manual=$('manualActionPanel');if(manual)manual.hidden=true;
|
| 53 |
|
|
|
|
| 80 |
const reportText=(detail.report||'No report available.').slice(0,8000);
|
| 81 |
const report=$('reportPreview');
|
| 82 |
if(report)report.textContent=reportText;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
state.selectedRunDetail=detail;
|
| 84 |
if(typeof renderSelectedLinks==='function')renderSelectedLinks({...detail.summary,bucket_source:detail.bucket_source},detail);
|
| 85 |
if(typeof renderBlockers==='function')renderBlockers(detail);
|
|
|
|
| 110 |
function switchView(view){state.currentView=view;document.querySelectorAll('.nav-item').forEach(b=>b.classList.toggle('active',b.dataset.view===view));document.querySelectorAll('[data-section]').forEach(s=>s.hidden=s.dataset.section!==view);setOperation('');if(view==='progress')loadRuns(false);const targetMap={build:'newBuildCard',progress:'runsExplorerPanel',validate:'spaceTestSection'};const target=$(targetMap[view]||'');if(target)target.scrollIntoView({behavior:'smooth',block:'start'})}
|
| 111 |
function bindNavigation(){document.querySelectorAll('.nav-item').forEach(btn=>btn.addEventListener('click',()=>switchView(btn.dataset.view||'build')));document.querySelectorAll('[data-view-target]').forEach(btn=>btn.addEventListener('click',()=>switchView(btn.dataset.viewTarget||'build')))}
|
| 112 |
function bindRunFilters(){document.querySelectorAll('.filter[data-status]').forEach(btn=>btn.addEventListener('click',()=>{state.runStatusFilter=btn.dataset.status||'all';state.runPage=0;renderRunsFromCache()}));for(const id of ['runSearch','runSearchFull'])$(id)?.addEventListener('input',e=>{state.runSearch=e.target.value||'';state.runPage=0;for(const other of ['runSearch','runSearchFull'])if($(other)&&$(other)!==e.target)$(other).value=state.runSearch;clearTimeout(state.runSearchTimer);state.runSearchTimer=setTimeout(renderRunsFromCache,80)})}
|
| 113 |
+
function bindActions(){$('launchBuild')?.setAttribute('data-requires-bucket','true');$('launchBuild')?.setAttribute('data-action-button','true');$('launchValidate')?.setAttribute('data-action-button','true');$('launchBuild')?.addEventListener('click',launchBuild);$('launchValidate')?.addEventListener('click',launchValidate);$('prepareNewBuild')?.addEventListener('click',prepareNewBuild);$('manualGoValidate')?.addEventListener('click',prepareValidationFromActiveRun);$('selectedValidateBtn')?.addEventListener('click',prepareValidationFromActiveRun);$('checkBucket')?.addEventListener('click',()=>checkBucket(true));$('createBucket')?.addEventListener('click',createBucket);$('refreshRuns')?.addEventListener('click',()=>loadRuns(true));$('refreshRunsHome')?.addEventListener('click',()=>loadRuns(true));$('resumeLatestRun')?.addEventListener('click',resumeLatestRun);$('cancelRun')?.addEventListener('click',cancelActiveRun);$('resumeLatestRunFull')?.addEventListener('click',resumeLatestRun);['tryZeroGpu','preferredHardware','fallbackHardware','allowFallback'].forEach(id=>$(id)?.addEventListener('change',()=>setText('hardwarePlan',hardwarePlanLabel())))}
|
| 114 |
bindNavigation();bindActions();bindRunFilters();setText('hardwarePlan',hardwarePlanLabel());setBuildModeNew();renderTimeline(DEFAULT_TIMELINE);loadAppInfo();
|
| 115 |
|
| 116 |
// Pass 47 intentionally removed automatic switchView('progress') after launch and run selection.
|
web/static/index.html
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
+
<title>Agentic Space Factory</title>
|
| 7 |
+
<link rel="stylesheet" href="/custom-static/app.css" />
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div class="app-shell onepage-shell">
|
| 11 |
+
<header class="topbar onepage-topbar">
|
| 12 |
+
<div class="brand">
|
| 13 |
+
<div class="brand-mark">π</div>
|
| 14 |
+
<div>
|
| 15 |
+
<h1>Agentic Space Factory</h1>
|
| 16 |
+
<p>Build, inspect, and validate private Hugging Face Spaces.</p>
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
<div class="top-actions">
|
| 20 |
+
<a class="user-chip" id="userChip" href="/oauth/huggingface/login">Sign in</a>
|
| 21 |
+
<button class="primary small" id="resumeLatestRun">Resume latest run</button>
|
| 22 |
+
<a class="logout-link" id="logoutLink" href="/logout" hidden>Logout</a>
|
| 23 |
+
</div>
|
| 24 |
+
</header>
|
| 25 |
+
|
| 26 |
+
<div class="toast-layer" id="toastLayer" aria-live="polite"><section class="operation-status floating-toast" id="operationStatus" hidden></section></div>
|
| 27 |
+
|
| 28 |
+
<main class="onepage-main">
|
| 29 |
+
<section class="auth-panel" id="authPanel" hidden></section>
|
| 30 |
+
|
| 31 |
+
<section class="workspace-grid">
|
| 32 |
+
<div class="left-stack">
|
| 33 |
+
<section class="panel bucket-setup-panel" id="bucketSetupPanel">
|
| 34 |
+
<div class="section-head compact">
|
| 35 |
+
<div><span class="panel-icon">β</span><h2>Run storage</h2></div>
|
| 36 |
+
<span class="badge" id="bucketInlineStatus">Checking</span>
|
| 37 |
+
</div>
|
| 38 |
+
<p class="muted">Runs, reports, generated files, traces, and validation outputs are written to your private bucket.</p>
|
| 39 |
+
<div class="bucket-source-line"><span class="status-dot" id="bucketInlineDot"></span><strong id="bucketInlineLabel">loadingβ¦</strong></div>
|
| 40 |
+
<div class="inline-actions bucket-actions">
|
| 41 |
+
<button class="secondary small" id="checkBucket">Check bucket</button>
|
| 42 |
+
<button class="secondary small" id="createBucket">Create private bucket</button>
|
| 43 |
+
</div>
|
| 44 |
+
</section>
|
| 45 |
+
|
| 46 |
+
<section class="panel build-panel" id="newBuildCard">
|
| 47 |
+
<div class="section-head compact">
|
| 48 |
+
<div>
|
| 49 |
+
<span class="panel-icon">β</span>
|
| 50 |
+
<h2>New Build</h2>
|
| 51 |
+
</div>
|
| 52 |
+
</div>
|
| 53 |
+
|
| 54 |
+
<div class="field-stack">
|
| 55 |
+
<label>Model card URL or model ID<input id="modelId" value="meta-llama/Meta-Llama-3.1-8B-Instruct" /></label>
|
| 56 |
+
<label>Target Space name<input id="targetSpace" placeholder="my-llama-31-8b-demo" /></label>
|
| 57 |
+
<div class="form-grid two-cols">
|
| 58 |
+
<label>Pi model<select id="piModel"><option value="Qwen/Qwen3-Coder-Next" selected>Qwen/Qwen3-Coder-Next</option></select></label>
|
| 59 |
+
<label>Implementation mode<select id="implementationMode">
|
| 60 |
+
<option value="full-inference-gated" selected>Strict real inference β validated Gradio app</option>
|
| 61 |
+
<option value="full-inference-attempt">Best-effort inference β permissive prototype</option>
|
| 62 |
+
<option value="safe-scaffold">Safe scaffold only β boots without inference guarantee</option>
|
| 63 |
+
</select></label>
|
| 64 |
+
</div>
|
| 65 |
+
<p class="muted cost-note">Pi uses Hugging Face Inference Providers for Qwen. Usage may consume paid inference credits.</p>
|
| 66 |
+
<div class="form-grid two-cols">
|
| 67 |
+
<label>Preferred GPU<select id="preferredHardware"><option value="zero-a10g" selected>ZeroGPU</option></select></label>
|
| 68 |
+
<label>Fallback GPU<select id="fallbackHardware"><option value="a10g-large" selected>A10G Large</option><option value="l40sx1">L40S x1</option><option value="cpu-basic">CPU Basic</option><option value="a100-large">A100 Large</option><option value="h200">H200</option></select></label>
|
| 69 |
+
</div>
|
| 70 |
+
<div class="toggle-list compact-toggles">
|
| 71 |
+
<label><span>Try ZeroGPU first</span><input id="tryZeroGpu" type="checkbox" checked /></label>
|
| 72 |
+
<label><span>Allow fixed GPU fallback</span><input id="allowFallback" type="checkbox" checked /></label>
|
| 73 |
+
</div>
|
| 74 |
+
<label>Expected output type<select id="buildExpectedOutput"><option value="text">Text</option><option value="image">Image</option><option value="video">Video</option><option value="audio">Audio</option><option value="any">Any</option></select></label>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
<button class="primary full" id="launchBuild" disabled>Build private Space</button>
|
| 78 |
+
<p class="muted build-gate" id="buildGateText">Checking your run bucket before enabling buildsβ¦</p>
|
| 79 |
+
</div>
|
| 80 |
+
|
| 81 |
+
<section class="panel active-run-panel" id="activeRunPanel">
|
| 82 |
+
<div class="section-head compact">
|
| 83 |
+
<div><span class="panel-icon">β·</span><h2>Active Run</h2></div>
|
| 84 |
+
<span class="badge neutral" id="progressStatus">Idle</span>
|
| 85 |
+
</div>
|
| 86 |
+
|
| 87 |
+
<div class="summary-meta active-meta">
|
| 88 |
+
<div><span>Run ID</span><strong id="detailRunId">β</strong></div>
|
| 89 |
+
<div><span>Target Space</span><strong id="detailSpace">β</strong></div>
|
| 90 |
+
<div><span>Model</span><strong id="selectedModelId">β</strong></div>
|
| 91 |
+
<div><span>Current step</span><strong id="currentStep">β</strong></div>
|
| 92 |
+
<div><span>Elapsed time</span><strong id="elapsed">β</strong></div>
|
| 93 |
+
</div>
|
| 94 |
+
|
| 95 |
+
<div class="active-run-actions active-run-cancel-only">
|
| 96 |
+
<button class="secondary danger" id="cancelRun" disabled>Cancel Job</button>
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
+
<div class="progress-caption"><span>Overall progress</span><strong id="progressPercent">0%</strong></div>
|
| 100 |
+
<div class="progress-bar"><div id="progressFill" style="width:0%"></div></div>
|
| 101 |
+
<div class="timeline" id="timeline"></div>
|
| 102 |
+
|
| 103 |
+
<div class="manual-action" id="manualActionPanel" hidden>
|
| 104 |
+
<div>
|
| 105 |
+
<strong id="manualActionTitle">Manual hardware required</strong>
|
| 106 |
+
<p id="manualActionText">Open Space settings, choose the recommended GPU, then run Space Test.</p>
|
| 107 |
+
</div>
|
| 108 |
+
<div class="inline-actions">
|
| 109 |
+
<a class="link-btn" id="manualOpenSettings" target="_blank">Open Space settings β</a>
|
| 110 |
+
<button class="secondary small" id="manualGoValidate">Prefill Space Test</button>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
|
| 114 |
+
<section class="mini-panel latest-events-card">
|
| 115 |
+
<div class="card-title-row">
|
| 116 |
+
<h4>Latest events</h4>
|
| 117 |
+
</div>
|
| 118 |
+
<div id="activityFeed" class="activity-feed"><div class="empty">No activity yet.</div></div>
|
| 119 |
+
</section>
|
| 120 |
+
</section>
|
| 121 |
+
|
| 122 |
+
<section class="panel runs-explorer-panel" id="runsExplorerPanel">
|
| 123 |
+
<div class="section-head compact">
|
| 124 |
+
<div><span class="panel-icon">β°</span><h2>Runs Explorer</h2></div>
|
| 125 |
+
</div>
|
| 126 |
+
|
| 127 |
+
<div class="search-wrap"><input id="runSearchFull" placeholder="Search runs by ID, model, or spaceβ¦" /><span>β</span></div>
|
| 128 |
+
<div class="run-filters" id="runFilters">
|
| 129 |
+
<button class="filter active" data-status="all">All</button>
|
| 130 |
+
<button class="filter" data-status="running">Running</button>
|
| 131 |
+
<button class="filter" data-status="full_inference_success">Success</button>
|
| 132 |
+
<button class="filter" data-status="manual_hardware_required">Manual</button>
|
| 133 |
+
<button class="filter" data-status="failed">Failed</button>
|
| 134 |
+
<button class="filter" data-status="stale">Stale</button>
|
| 135 |
+
</div>
|
| 136 |
+
<div id="runsTable" class="runs-table">Loading runsβ¦</div>
|
| 137 |
+
<div id="runsPagination" class="runs-pagination"></div>
|
| 138 |
+
|
| 139 |
+
<section class="selected-run-card">
|
| 140 |
+
<div class="section-head compact">
|
| 141 |
+
<div><span class="panel-icon">β£</span><h3>Selected run</h3></div>
|
| 142 |
+
</div>
|
| 143 |
+
<div class="detail-grid selected-grid">
|
| 144 |
+
<div><span>Run ID</span><strong id="selectedRunId">β</strong></div>
|
| 145 |
+
<div><span>Status</span><strong id="runDetailStatus">β</strong></div>
|
| 146 |
+
<div><span>Target Space</span><strong id="selectedTargetSpace">β</strong></div>
|
| 147 |
+
<div><span>Hardware</span><strong id="selectedHardware">β</strong></div>
|
| 148 |
+
<div><span>Model</span><strong id="selectedModelShort">β</strong></div>
|
| 149 |
+
<div><span>Mode</span><strong id="selectedMode">Gradio (Python)</strong></div>
|
| 150 |
+
</div>
|
| 151 |
+
<div class="quick-links selected-links-row">
|
| 152 |
+
<a class="link-btn disabled" id="selectedOpenSpace" target="_blank">Space β</a>
|
| 153 |
+
<a class="link-btn disabled" id="selectedOpenJob" target="_blank">Job β</a>
|
| 154 |
+
<a class="link-btn disabled" id="selectedOpenArtifacts" target="_blank">Artifacts β</a>
|
| 155 |
+
<a class="link-btn disabled" id="selectedOpenSettings" target="_blank">Settings β</a>
|
| 156 |
+
</div>
|
| 157 |
+
<div class="selected-actions-stack">
|
| 158 |
+
<button class="secondary full" id="selectedValidateBtn" disabled>Prefill Space Test</button>
|
| 159 |
+
<button class="secondary danger full" id="deleteSelectedRun" disabled>Delete run from bucket</button>
|
| 160 |
+
</div>
|
| 161 |
+
<div class="blockers" id="selectedBlockers" hidden></div>
|
| 162 |
+
</section>
|
| 163 |
+
</section>
|
| 164 |
+
</section>
|
| 165 |
+
|
| 166 |
+
<section class="panel space-test-panel" id="spaceTestSection">
|
| 167 |
+
<div class="space-test-grid">
|
| 168 |
+
<div>
|
| 169 |
+
<div class="section-head compact"><div><span class="panel-icon">β</span><h2>Space Test</h2></div></div>
|
| 170 |
+
<div class="form-grid test-top-row">
|
| 171 |
+
<label>Target Space ID<input id="validateSpace" placeholder="fffiloni/my-llama-31-8b-demo" /></label>
|
| 172 |
+
<label>API name<input id="validateApi" value="/predict" /></label>
|
| 173 |
+
<label>Expected output type<select id="expectedOutput"><option value="text">Text</option><option value="image">Image</option><option value="video">Video</option><option value="audio">Audio</option><option value="any">Any</option></select></label>
|
| 174 |
+
<label>Timeout (s)<input id="validateTimeout" value="60" /></label>
|
| 175 |
+
</div>
|
| 176 |
+
<div class="editor-grid">
|
| 177 |
+
<label>Test args (JSON)<textarea id="testArgs" rows="7">[
|
| 178 |
+
{
|
| 179 |
+
"inputs": "Explain the concept of agentic AI in one paragraph."
|
| 180 |
+
}
|
| 181 |
+
]</textarea></label>
|
| 182 |
+
<label>Test kwargs (JSON)<textarea id="testKwargs" rows="7">{
|
| 183 |
+
"max_new_tokens": 128,
|
| 184 |
+
"temperature": 0.2
|
| 185 |
+
}</textarea></label>
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
|
| 189 |
+
<div class="validation-result-card" id="spaceTestPreview">
|
| 190 |
+
<div class="validation-grid">
|
| 191 |
+
<div><span>Validation status</span><strong><span class="badge neutral" id="spaceTestStatus">Pending</span></strong></div>
|
| 192 |
+
<div><span>Latency</span><strong>β</strong></div>
|
| 193 |
+
<div><span>Validation run ID</span><strong id="spaceTestOutput">β</strong></div>
|
| 194 |
+
<div><span>Started at</span><strong id="lastPolled">β</strong></div>
|
| 195 |
+
<div><span>Verdict</span><strong id="spaceTestVerdict">pending</strong></div>
|
| 196 |
+
</div>
|
| 197 |
+
<button class="primary full" id="launchValidate">Launch validation</button>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
</section>
|
| 201 |
+
|
| 202 |
+
<div class="support-elements" hidden>
|
| 203 |
+
<input id="runSearch" />
|
| 204 |
+
<input id="runId" />
|
| 205 |
+
<textarea id="buildGoal"></textarea>
|
| 206 |
+
<button id="refreshRuns"></button>
|
| 207 |
+
<div id="runsTableHome"></div>
|
| 208 |
+
<button id="refreshRunsHome"></button>
|
| 209 |
+
<button id="resumeLatestRunFull"></button>
|
| 210 |
+
<a id="runHeaderSpace" target="_blank"></a>
|
| 211 |
+
|
| 212 |
+
<span id="progressTitle"></span><span id="lastEvent"></span><span id="hardwarePlan"></span>
|
| 213 |
+
<span id="detailJobStatus"></span><span id="detailGateStatus"></span><span id="detailHardware"></span><span id="detailTraces"></span><span id="homeRunDetailStatus"></span>
|
| 214 |
+
<span id="selectedModelIdHidden"></span><span id="selectedHardwareHidden"></span>
|
| 215 |
+
<ul id="selectedValidation"></ul><ul id="zeroGpuChecklist"></ul><div id="statusChips"></div>
|
| 216 |
+
<div id="diagBuildStatus"></div><div id="diagBuildStep"></div>
|
| 217 |
+
<div id="issueCard"><strong id="issueTitle"></strong><p id="issueDetail"></p></div>
|
| 218 |
+
<ul id="metricsList"></ul>
|
| 219 |
+
<pre id="reportPreview"></pre><div id="artifactList"></div>
|
| 220 |
+
</div>
|
| 221 |
+
</main>
|
| 222 |
+
</div>
|
| 223 |
+
<script src="/custom-static/api.js"></script>
|
| 224 |
+
<script src="/custom-static/components.js"></script>
|
| 225 |
+
<script src="/custom-static/progress.js"></script>
|
| 226 |
+
<script src="/custom-static/runs.js"></script>
|
| 227 |
+
<script src="/custom-static/app.js"></script>
|
| 228 |
+
</body>
|
| 229 |
+
</html>
|
web/static/progress.js
CHANGED
|
@@ -48,6 +48,36 @@ function visualStatusClass(status){
|
|
| 48 |
if(s.includes('running')||s.includes('queued')||s.includes('pending')||s.includes('waiting')||s.includes('building'))return'running';
|
| 49 |
return'neutral';
|
| 50 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
function stepOrderIndex(step){
|
| 52 |
const idx=WORKER_STEP_ORDER.indexOf(String(step||''));
|
| 53 |
return idx<0?-1:idx;
|
|
@@ -62,6 +92,7 @@ function timelineIcon(status){
|
|
| 62 |
if(status==='done'||status==='completed')return'β';
|
| 63 |
if(status==='running')return'β’';
|
| 64 |
if(status==='failed')return'!';
|
|
|
|
| 65 |
if(status==='blocked'||status==='cancelled'||status==='stopped')return'β ';
|
| 66 |
if(status==='skipped')return'β·';
|
| 67 |
return'β';
|
|
@@ -71,6 +102,7 @@ function timelineStatus(status,overallStatus=''){
|
|
| 71 |
const overall=normalizeTimelineOverallStatus(overallStatus);
|
| 72 |
const stopped=overall&&PROGRESS_TERMINAL_STATUSES.has(overall)&&!['success','succeeded','done','full_inference_success','full_inference_candidate_health_passed'].includes(overall);
|
| 73 |
if(status==='failed'||status==='error'||status==='failure')return'status-failed';
|
|
|
|
| 74 |
if(status==='blocked'||status==='cancelled'||status==='canceled'||status==='stopped')return'status-stopped';
|
| 75 |
if(status==='done'||status==='completed'||status==='success')return'status-done';
|
| 76 |
if(status==='running'||status==='waiting')return stopped?'status-stopped':'status-running';
|
|
@@ -140,18 +172,21 @@ function ensureTimelineStepVisible(stepKey){
|
|
| 140 |
}
|
| 141 |
root.dataset.visibleStep=stepKey;
|
| 142 |
}
|
| 143 |
-
function compactTimelineItems(items=[]){
|
| 144 |
const byStep=new Map((items||[]).map(item=>[String(item.step||item.id||item.label||''),item]));
|
| 145 |
-
const terminalRaw=new Set(['failed','blocked','stopped','cancelled','canceled']);
|
| 146 |
return COMPACT_TIMELINE_GROUPS.map(group=>{
|
| 147 |
const groupItems=group.steps.map(step=>byStep.get(step)).filter(Boolean);
|
| 148 |
const doneCount=groupItems.filter(item=>['done','completed','success'].includes(String(item.status||'').toLowerCase())).length;
|
| 149 |
const active=groupItems.find(item=>['running','waiting','stopped','failed','blocked','cancelled','canceled'].includes(String(item.status||'').toLowerCase()));
|
| 150 |
let status='pending';
|
| 151 |
-
|
|
|
|
|
|
|
| 152 |
else if(groupItems.length&&doneCount>=group.steps.length)status='done';
|
| 153 |
else if(doneCount>0)status='running';
|
| 154 |
-
const
|
|
|
|
| 155 |
const activeIndex=activeSubstep?Math.max(0,group.steps.indexOf(activeSubstep)):-1;
|
| 156 |
let percent=0;
|
| 157 |
if(status==='done')percent=100;
|
|
@@ -181,7 +216,7 @@ function renderTimeline(items,overallStatus=''){
|
|
| 181 |
if(!root)return;
|
| 182 |
if(!root.dataset.scrollBound){root.dataset.scrollBound='1';root.addEventListener('scroll',()=>{root.dataset.userScrolled='1'},{passive:true});}
|
| 183 |
const fullList=items||[];
|
| 184 |
-
const list=compactTimelineItems(fullList);
|
| 185 |
const currentKey=currentCompactGroupKey(fullList);
|
| 186 |
const signature=list.map((x,i)=>`${timelineStepKey(x,i)}:${timelineStatus(x.status,overallStatus)}:${x.label||''}:${overallStatus||''}`).join('|');
|
| 187 |
if(root.dataset.signature===signature){ensureTimelineStepVisible(currentKey);return;}
|
|
|
|
| 48 |
if(s.includes('running')||s.includes('queued')||s.includes('pending')||s.includes('waiting')||s.includes('building'))return'running';
|
| 49 |
return'neutral';
|
| 50 |
}
|
| 51 |
+
function groupForWorkerStep(step){
|
| 52 |
+
return COMPACT_TIMELINE_GROUPS.find(g=>g.steps.includes(String(step||'')))||null;
|
| 53 |
+
}
|
| 54 |
+
function latestEventIndexForGroup(group){
|
| 55 |
+
if(!group)return -1;
|
| 56 |
+
let idx=-1;
|
| 57 |
+
(activityFeedState.lastItems||[]).forEach((event,i)=>{
|
| 58 |
+
if(group.steps.includes(String(event.step||'')))idx=i;
|
| 59 |
+
});
|
| 60 |
+
return idx;
|
| 61 |
+
}
|
| 62 |
+
function failedEventInfoForGroup(group){
|
| 63 |
+
if(!group)return null;
|
| 64 |
+
let info=null;
|
| 65 |
+
(activityFeedState.lastItems||[]).forEach((event,i)=>{
|
| 66 |
+
if(group.steps.includes(String(event.step||''))&&isFailureStatus(event.status)){
|
| 67 |
+
info={event,index:i};
|
| 68 |
+
}
|
| 69 |
+
});
|
| 70 |
+
return info;
|
| 71 |
+
}
|
| 72 |
+
function failedTimelineStatusForGroup(group,overallStatus){
|
| 73 |
+
const failed=failedEventInfoForGroup(group);
|
| 74 |
+
if(!failed)return '';
|
| 75 |
+
const latestIdx=latestEventIndexForGroup(group);
|
| 76 |
+
const overall=visualStatusClass(overallStatus);
|
| 77 |
+
const superseded=latestIdx>failed.index;
|
| 78 |
+
if(overall==='error')return 'failed';
|
| 79 |
+
return superseded?'recovered':'failed';
|
| 80 |
+
}
|
| 81 |
function stepOrderIndex(step){
|
| 82 |
const idx=WORKER_STEP_ORDER.indexOf(String(step||''));
|
| 83 |
return idx<0?-1:idx;
|
|
|
|
| 92 |
if(status==='done'||status==='completed')return'β';
|
| 93 |
if(status==='running')return'β’';
|
| 94 |
if(status==='failed')return'!';
|
| 95 |
+
if(status==='recovered'||status==='superseded')return'βΊ';
|
| 96 |
if(status==='blocked'||status==='cancelled'||status==='stopped')return'β ';
|
| 97 |
if(status==='skipped')return'β·';
|
| 98 |
return'β';
|
|
|
|
| 102 |
const overall=normalizeTimelineOverallStatus(overallStatus);
|
| 103 |
const stopped=overall&&PROGRESS_TERMINAL_STATUSES.has(overall)&&!['success','succeeded','done','full_inference_success','full_inference_candidate_health_passed'].includes(overall);
|
| 104 |
if(status==='failed'||status==='error'||status==='failure')return'status-failed';
|
| 105 |
+
if(status==='recovered'||status==='superseded')return'status-warn';
|
| 106 |
if(status==='blocked'||status==='cancelled'||status==='canceled'||status==='stopped')return'status-stopped';
|
| 107 |
if(status==='done'||status==='completed'||status==='success')return'status-done';
|
| 108 |
if(status==='running'||status==='waiting')return stopped?'status-stopped':'status-running';
|
|
|
|
| 172 |
}
|
| 173 |
root.dataset.visibleStep=stepKey;
|
| 174 |
}
|
| 175 |
+
function compactTimelineItems(items=[],overallStatus=''){
|
| 176 |
const byStep=new Map((items||[]).map(item=>[String(item.step||item.id||item.label||''),item]));
|
| 177 |
+
const terminalRaw=new Set(['failed','blocked','stopped','cancelled','canceled','recovered']);
|
| 178 |
return COMPACT_TIMELINE_GROUPS.map(group=>{
|
| 179 |
const groupItems=group.steps.map(step=>byStep.get(step)).filter(Boolean);
|
| 180 |
const doneCount=groupItems.filter(item=>['done','completed','success'].includes(String(item.status||'').toLowerCase())).length;
|
| 181 |
const active=groupItems.find(item=>['running','waiting','stopped','failed','blocked','cancelled','canceled'].includes(String(item.status||'').toLowerCase()));
|
| 182 |
let status='pending';
|
| 183 |
+
const historicalFailure=failedTimelineStatusForGroup(group,overallStatus);
|
| 184 |
+
if(historicalFailure)status=historicalFailure;
|
| 185 |
+
else if(active)status=String(active.status||'running').toLowerCase();
|
| 186 |
else if(groupItems.length&&doneCount>=group.steps.length)status='done';
|
| 187 |
else if(doneCount>0)status='running';
|
| 188 |
+
const failedInfo=failedEventInfoForGroup(group);
|
| 189 |
+
const activeSubstep=failedInfo&&historicalFailure?(failedInfo.event.step||failedInfo.event.message||'failed'):active?.step||active?.id||active?.label||groupItems[groupItems.length-1]?.step||'';
|
| 190 |
const activeIndex=activeSubstep?Math.max(0,group.steps.indexOf(activeSubstep)):-1;
|
| 191 |
let percent=0;
|
| 192 |
if(status==='done')percent=100;
|
|
|
|
| 216 |
if(!root)return;
|
| 217 |
if(!root.dataset.scrollBound){root.dataset.scrollBound='1';root.addEventListener('scroll',()=>{root.dataset.userScrolled='1'},{passive:true});}
|
| 218 |
const fullList=items||[];
|
| 219 |
+
const list=compactTimelineItems(fullList,overallStatus);
|
| 220 |
const currentKey=currentCompactGroupKey(fullList);
|
| 221 |
const signature=list.map((x,i)=>`${timelineStepKey(x,i)}:${timelineStatus(x.status,overallStatus)}:${x.label||''}:${overallStatus||''}`).join('|');
|
| 222 |
if(root.dataset.signature===signature){ensureTimelineStepVisible(currentKey);return;}
|
web/static/runs.js
CHANGED
|
@@ -23,7 +23,7 @@ function renderRunRows(runs,compact=false){return (runs||[]).map(r=>`<button cla
|
|
| 23 |
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=`<button class="secondary small" data-page-action="prev" ${state.runPage<=0?'disabled':''}>βΉ</button><span>Page ${state.runPage+1} / ${pages}</span><button class="secondary small" data-page-action="next" ${state.runPage>=pages-1?'disabled':''}>βΊ</button>`;root.querySelectorAll('[data-page-action]').forEach(btn=>btn.addEventListener('click',()=>{state.runPage+=(btn.dataset.pageAction==='next'?1:-1);renderRunsFromCache()}))}
|
| 24 |
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)||'<div class="empty">No runs match this filter.</div>'}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))}
|
| 25 |
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'))}}
|
| 26 |
-
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;
|
| 27 |
async function deleteSelectedRun(){
|
| 28 |
const detail=state.selectedRunDetail||{};
|
| 29 |
const summary=detail.summary||{};
|
|
@@ -55,4 +55,4 @@ async function deleteSelectedRun(){
|
|
| 55 |
}
|
| 56 |
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}}
|
| 57 |
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('<strong>Manual hardware required</strong><p>Open Space settings, choose the recommended GPU, then run validation.</p>');for(const b of blockerItems.slice(0,4)){parts.push(`<strong>${escapeHtml(shortText(b.type||b.name||'Technical blocker',32))}</strong><p>${escapeHtml(shortText(b.claim||b.reason||b.message||'',120))}</p>`)}root.innerHTML=parts.join('')}
|
| 58 |
-
function renderRunDetail(detail){state.selectedRunDetail=detail;const summary=detail.summary||{};const smoke=detail.generation_smoke||{};const files=detail.files||[];const view=detail.view||{};const header=view.header||{};setText('selectedRunId',detail.run_id||summary.run_id||'β');setText('selectedModelId',summary.model_id||'β');setText('selectedModelShort',summary.model_id||'β');setText('selectedTargetSpace',summary.target_space||'β');setText('selectedHardware',summary.selected_hardware||summary.hardware||'β');setText('selectedMode',detail.state?.implementation_mode||detail.launch?.implementation_mode||summary.kind||'Strict real inference');setText('runDetailStatus',header.status||summary.status||'unknown');setText('homeRunDetailStatus',header.status_label||summary.status||'unknown');setText('detailRunId',summary.run_id||detail.run_id);setText('detailJobStatus',summary.status||'β');setText('detailGateStatus',(detail.inference_gate||{}).status||summary.status||'β');setText('detailSpace',summary.target_space||'β');setText('detailHardware',summary.selected_hardware||summary.hardware||'β');setText('detailTraces',Array.isArray(detail.events)?String(detail.events.length)+' events':summary.traces_count||'β');const validation=$('selectedValidation');if(validation){validation.innerHTML=`<li><span>Health</span><strong>${summary.health_passed?'passed':'β'}</strong></li><li><span>Smoke test</span><strong>${summary.smoke_test_passed?'passed':(smoke.status||'β')}</strong></li><li><span>Latency</span><strong>${summary.latency_seconds?fmtSeconds(Math.round(summary.latency_seconds)):'β'}</strong></li>`}const artifactRoot=$('artifactList');if(artifactRoot){const interesting=files.filter(f=>!/traces\//.test(f.path||'')).slice(0,18);artifactRoot.innerHTML=interesting.length?interesting.map(f=>`<a href="${escapeHtml(f.url)}" target="_blank"><span>${escapeHtml(f.path)}</span><em>${f.size?Math.round(Number(f.size)/1024)+' KB':''}</em></a>`).join(''):'No artifacts indexed yet.'}const reportText=(detail.report||'No report available.').slice(0,8000);const report=$('reportPreview');if(report)report.textContent=reportText;
|
|
|
|
| 23 |
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=`<button class="secondary small" data-page-action="prev" ${state.runPage<=0?'disabled':''}>βΉ</button><span>Page ${state.runPage+1} / ${pages}</span><button class="secondary small" data-page-action="next" ${state.runPage>=pages-1?'disabled':''}>βΊ</button>`;root.querySelectorAll('[data-page-action]').forEach(btn=>btn.addEventListener('click',()=>{state.runPage+=(btn.dataset.pageAction==='next'?1:-1);renderRunsFromCache()}))}
|
| 24 |
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)||'<div class="empty">No runs match this filter.</div>'}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))}
|
| 25 |
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'))}}
|
| 26 |
+
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;}}
|
| 27 |
async function deleteSelectedRun(){
|
| 28 |
const detail=state.selectedRunDetail||{};
|
| 29 |
const summary=detail.summary||{};
|
|
|
|
| 55 |
}
|
| 56 |
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}}
|
| 57 |
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('<strong>Manual hardware required</strong><p>Open Space settings, choose the recommended GPU, then run validation.</p>');for(const b of blockerItems.slice(0,4)){parts.push(`<strong>${escapeHtml(shortText(b.type||b.name||'Technical blocker',32))}</strong><p>${escapeHtml(shortText(b.claim||b.reason||b.message||'',120))}</p>`)}root.innerHTML=parts.join('')}
|
| 58 |
+
function renderRunDetail(detail){state.selectedRunDetail=detail;const summary=detail.summary||{};const smoke=detail.generation_smoke||{};const files=detail.files||[];const view=detail.view||{};const header=view.header||{};setText('selectedRunId',detail.run_id||summary.run_id||'β');setText('selectedModelId',summary.model_id||'β');setText('selectedModelShort',summary.model_id||'β');setText('selectedTargetSpace',summary.target_space||'β');setText('selectedHardware',summary.selected_hardware||summary.hardware||'β');setText('selectedMode',detail.state?.implementation_mode||detail.launch?.implementation_mode||summary.kind||'Strict real inference');setText('runDetailStatus',header.status||summary.status||'unknown');setText('homeRunDetailStatus',header.status_label||summary.status||'unknown');setText('detailRunId',summary.run_id||detail.run_id);setText('detailJobStatus',summary.status||'β');setText('detailGateStatus',(detail.inference_gate||{}).status||summary.status||'β');setText('detailSpace',summary.target_space||'β');setText('detailHardware',summary.selected_hardware||summary.hardware||'β');setText('detailTraces',Array.isArray(detail.events)?String(detail.events.length)+' events':summary.traces_count||'β');const validation=$('selectedValidation');if(validation){validation.innerHTML=`<li><span>Health</span><strong>${summary.health_passed?'passed':'β'}</strong></li><li><span>Smoke test</span><strong>${summary.smoke_test_passed?'passed':(smoke.status||'β')}</strong></li><li><span>Latency</span><strong>${summary.latency_seconds?fmtSeconds(Math.round(summary.latency_seconds)):'β'}</strong></li>`}const artifactRoot=$('artifactList');if(artifactRoot){const interesting=files.filter(f=>!/traces\//.test(f.path||'')).slice(0,18);artifactRoot.innerHTML=interesting.length?interesting.map(f=>`<a href="${escapeHtml(f.url)}" target="_blank"><span>${escapeHtml(f.path)}</span><em>${f.size?Math.round(Number(f.size)/1024)+' KB':''}</em></a>`).join(''):'No artifacts indexed yet.'}const reportText=(detail.report||'No report available.').slice(0,8000);const report=$('reportPreview');if(report)report.textContent=reportText;setQuickLinks({job_url:inferredJobUrl(summary,detail),target_space_url:summary.target_space_url,target_space_settings_url:summary.target_space_url?summary.target_space_url+'/settings':'',artifacts_url:summary.artifacts_url,bucket_source:detail.bucket_source,run_id:summary.run_id||detail.run_id});renderSelectedLinks({...summary,bucket_source:detail.bucket_source},detail);renderBlockers(detail);renderMetrics({...detail,summary});/* v71: avoid rendering stale view.pipeline before /progress returns canonical compact timeline. */const deleteBtn=$('deleteSelectedRun');if(deleteBtn){deleteBtn.disabled=!(summary.run_id||detail.run_id);deleteBtn.onclick=deleteSelectedRun}}
|