Upload 7 files
Browse files- web/static/app.css +88 -0
- web/static/app.js +10 -6
- web/static/progress.js +227 -11
- web/static/runs.js +13 -2
web/static/app.css
CHANGED
|
@@ -2366,3 +2366,91 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 2366 |
.eval-archive-meta strong{display:block;margin-top:2px;font-size:12px;color:#101828;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 2367 |
.eval-archive-path{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:11px;background:#f8fafc;border:1px solid #eef2f7;border-radius:10px;padding:7px 8px;overflow:auto}
|
| 2368 |
@media (max-width:520px){.eval-archive-meta{grid-template-columns:1fr}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2366 |
.eval-archive-meta strong{display:block;margin-top:2px;font-size:12px;color:#101828;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 2367 |
.eval-archive-path{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:11px;background:#f8fafc;border:1px solid #eef2f7;border-radius:10px;padding:7px 8px;overflow:auto}
|
| 2368 |
@media (max-width:520px){.eval-archive-meta{grid-template-columns:1fr}}
|
| 2369 |
+
|
| 2370 |
+
/* v189.2: canonical run timeline bridge */
|
| 2371 |
+
.timeline.timeline-canonical .step small{
|
| 2372 |
+
display:block;
|
| 2373 |
+
max-width:180px;
|
| 2374 |
+
margin:0 auto;
|
| 2375 |
+
}
|
| 2376 |
+
.timeline.timeline-canonical .canonical-summary{
|
| 2377 |
+
color:var(--muted);
|
| 2378 |
+
}
|
| 2379 |
+
.timeline.timeline-canonical .step.status-warn .canonical-summary{
|
| 2380 |
+
color:#92400e;
|
| 2381 |
+
}
|
| 2382 |
+
.timeline.timeline-canonical .step.status-skipped{
|
| 2383 |
+
opacity:.72;
|
| 2384 |
+
}
|
| 2385 |
+
|
| 2386 |
+
/* v189.3 β semantic progress: process completion is separate from result verdict */
|
| 2387 |
+
.progress-bar>div.is-warn{background:linear-gradient(90deg,#d97706,#fbbf24)!important}
|
| 2388 |
+
.progress-semantics{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center;margin:8px 0 0;font-size:11.5px;color:#475467;line-height:1.35}
|
| 2389 |
+
.progress-semantics[hidden]{display:none!important}
|
| 2390 |
+
.progress-semantics span{display:inline-flex;gap:4px;align-items:center;border:1px solid #e5e7eb;background:#fff;border-radius:999px;padding:4px 8px;min-width:0}
|
| 2391 |
+
.progress-semantics strong{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:#667085}
|
| 2392 |
+
.progress-semantics em{font-style:normal;color:#667085;flex-basis:100%;min-width:0}
|
| 2393 |
+
.progress-semantics.success span{border-color:#bbf7d0;background:#ecfdf3;color:#166534}
|
| 2394 |
+
.progress-semantics.warn span{border-color:#fed7aa;background:#fff7ed;color:#9a3412}
|
| 2395 |
+
.progress-semantics.error span{border-color:#fecdd3;background:#fff1f2;color:#be123c}
|
| 2396 |
+
.progress-semantics.stopped span{border-color:#cbd5e1;background:#f8fafc;color:#475569}
|
| 2397 |
+
.progress-semantics.running span{border-color:#bfdbfe;background:#eff6ff;color:#1d4ed8}
|
| 2398 |
+
|
| 2399 |
+
/* v189.4 β mobile-first canonical timeline details and compact readable phases */
|
| 2400 |
+
.timeline-phase-details[hidden]{display:none!important}
|
| 2401 |
+
.timeline-phase-details{
|
| 2402 |
+
margin:10px 0 0;
|
| 2403 |
+
border:1px solid #e5e7eb;
|
| 2404 |
+
background:#fff;
|
| 2405 |
+
border-radius:14px;
|
| 2406 |
+
padding:10px 12px;
|
| 2407 |
+
box-shadow:0 8px 20px rgba(16,24,40,.04);
|
| 2408 |
+
}
|
| 2409 |
+
.timeline-detail-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:6px}
|
| 2410 |
+
.timeline-detail-head span{font-weight:700;color:#101828;font-size:13px}
|
| 2411 |
+
.timeline-detail-head strong{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#667085;background:#f8fafc;border:1px solid #e5e7eb;border-radius:999px;padding:4px 8px;white-space:nowrap}
|
| 2412 |
+
.timeline-phase-details>p{margin:0 0 8px;color:#475467;font-size:12px;line-height:1.4}
|
| 2413 |
+
.timeline-phase-details ul{display:grid;gap:6px;list-style:none;margin:0;padding:0}
|
| 2414 |
+
.timeline-detail-row{display:flex;align-items:center;justify-content:space-between;gap:10px;border:1px solid #eef2f7;background:#f8fafc;border-radius:10px;padding:7px 8px;font-size:12px;min-width:0}
|
| 2415 |
+
.timeline-detail-row span{color:#667085;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
| 2416 |
+
.timeline-detail-row strong{color:#101828;font-weight:650;text-align:right;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
| 2417 |
+
.timeline-detail-warnings{margin-top:8px;border:1px solid #fed7aa;background:#fff7ed;border-radius:10px;padding:8px 9px;color:#9a3412}
|
| 2418 |
+
.timeline-detail-warnings strong{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px}
|
| 2419 |
+
.timeline-detail-warnings p{margin:2px 0 0;font-size:12px;line-height:1.35}
|
| 2420 |
+
.timeline-phase-details.done,.timeline-phase-details.success{border-color:#bbf7d0;background:#f0fdf4}
|
| 2421 |
+
.timeline-phase-details.warn,.timeline-phase-details.warning,.timeline-phase-details.fallback{border-color:#fed7aa;background:#fff7ed}
|
| 2422 |
+
.timeline-phase-details.failed,.timeline-phase-details.error{border-color:#fecdd3;background:#fff1f2}
|
| 2423 |
+
.timeline-phase-details.skipped,.timeline-phase-details.muted{background:#f8fafc;opacity:.92}
|
| 2424 |
+
@media (max-width:720px){
|
| 2425 |
+
.timeline.timeline-canonical{
|
| 2426 |
+
display:grid!important;
|
| 2427 |
+
grid-template-columns:1fr!important;
|
| 2428 |
+
gap:8px!important;
|
| 2429 |
+
overflow:visible!important;
|
| 2430 |
+
padding:4px 0!important;
|
| 2431 |
+
}
|
| 2432 |
+
.timeline.timeline-canonical .step{
|
| 2433 |
+
min-width:0!important;
|
| 2434 |
+
display:grid!important;
|
| 2435 |
+
grid-template-columns:34px 1fr!important;
|
| 2436 |
+
text-align:left!important;
|
| 2437 |
+
align-items:start!important;
|
| 2438 |
+
gap:9px!important;
|
| 2439 |
+
padding:8px 10px!important;
|
| 2440 |
+
border:1px solid #eef2f7!important;
|
| 2441 |
+
border-radius:12px!important;
|
| 2442 |
+
background:#fff!important;
|
| 2443 |
+
}
|
| 2444 |
+
.timeline.timeline-canonical .step:before,
|
| 2445 |
+
.timeline.timeline-canonical .step:after{display:none!important}
|
| 2446 |
+
.timeline.timeline-canonical .step-dot{margin:0!important;width:28px!important;height:28px!important}
|
| 2447 |
+
.timeline.timeline-canonical .step strong,
|
| 2448 |
+
.timeline.timeline-canonical .step small,
|
| 2449 |
+
.timeline.timeline-canonical .step .canonical-summary{max-width:none!important;text-align:left!important;margin:0!important}
|
| 2450 |
+
.timeline.timeline-canonical .step small{font-size:11.5px!important;line-height:1.25!important}
|
| 2451 |
+
.timeline-phase-details{margin-top:8px;padding:9px 10px;border-radius:12px}
|
| 2452 |
+
.timeline-detail-row{align-items:flex-start;flex-direction:column;gap:3px}
|
| 2453 |
+
.timeline-detail-row strong{text-align:left;white-space:normal}
|
| 2454 |
+
.progress-semantics{gap:6px;font-size:11px}
|
| 2455 |
+
.progress-semantics span{width:100%;justify-content:space-between}
|
| 2456 |
+
}
|
web/static/app.js
CHANGED
|
@@ -223,7 +223,7 @@ function setBucketFeedback(mode='checking',message='Checking private bucket acce
|
|
| 223 |
}
|
| 224 |
}
|
| 225 |
function renderAnonymousEvalStatus(me){
|
| 226 |
-
const card=$('
|
| 227 |
const title=$('evalArchiveTitle');
|
| 228 |
const text=$('anonymousEvalText');
|
| 229 |
const enable=$('enableEvalArchive');
|
|
@@ -245,8 +245,9 @@ function renderAnonymousEvalStatus(me){
|
|
| 245 |
title.textContent=enabled?'Anonymous eval archive':'Anonymous eval archive';
|
| 246 |
}
|
| 247 |
if(stateBadge){
|
| 248 |
-
|
| 249 |
-
stateBadge.
|
|
|
|
| 250 |
}
|
| 251 |
if(text){
|
| 252 |
text.className='eval-feedback '+(enabled?'on':'off');
|
|
@@ -254,6 +255,9 @@ function renderAnonymousEvalStatus(me){
|
|
| 254 |
const manager=cfg.managed_by==='you'?'You manage this instance setting.':`Managed by ${cfg.managed_by||'instance owner'}.`;
|
| 255 |
text.textContent=`On Β· ${bucket}${path} β ${mount}`;
|
| 256 |
text.title=`Anonymous project eval archive enabled from ${cfg.source||'config'}: ${bucket}${path} mounted at ${mount}. ${manager} ASF backend publishes anonymized eval records after user Jobs write local run evals.`;
|
|
|
|
|
|
|
|
|
|
| 257 |
}else if(me?.username){
|
| 258 |
text.textContent='Mount an operator eval bucket at /evals, then enable publishing.';
|
| 259 |
text.title='Recommended: private operator bucket, bucket path evals, mounted read-write at /evals on this ASF Space. Env vars still work as an admin override.';
|
|
@@ -263,7 +267,7 @@ function renderAnonymousEvalStatus(me){
|
|
| 263 |
}
|
| 264 |
}
|
| 265 |
if(enable){
|
| 266 |
-
enable.hidden=enabled||!me?.username||cfg.env_override===true;
|
| 267 |
enable.disabled=!me?.username||state.busy;
|
| 268 |
enable.title=`Enable anonymous eval archive using ${cfg.proposed_bucket_source||'your namespace bucket'} mounted at ${cfg.default_mount_path||'/evals'}.`;
|
| 269 |
}
|
|
@@ -391,7 +395,7 @@ function resetRunDetails(){
|
|
| 391 |
])setText(id,value);
|
| 392 |
setText('activeLatencyValue','β');setText('activeZeroGpuDuration','β');setText('activeLatencySource','waiting for live smoke test');const latencyCard=$('activeLatencyCard');if(latencyCard){latencyCard.classList.remove('has-latency');latencyCard.classList.add('is-loading')}
|
| 393 |
const status=$('progressStatus');if(status)status.className='badge neutral';
|
| 394 |
-
const fill=$('progressFill');if(fill){fill.style.width='0%';fill.classList.remove('is-running','progress-bump')}
|
| 395 |
setQuickLinks({});
|
| 396 |
const full=$('reportPreview');if(full)full.textContent='No report selected.';
|
| 397 |
const manual=$('manualActionPanel');if(manual)manual.hidden=true;
|
|
@@ -405,7 +409,7 @@ function resetRunDetails(){
|
|
| 405 |
const blockers=$('selectedBlockers');if(blockers){blockers.hidden=true;blockers.innerHTML=''}
|
| 406 |
for(const id of ['deleteActiveRun','activePrefillSpaceTest','deleteValidationRun']){const btn=$(id);if(btn)btn.disabled=true}
|
| 407 |
renderTimeline(DEFAULT_TIMELINE,'idle');
|
| 408 |
-
} function prepareNewBuild(){if(state.poll){clearInterval(state.poll);state.poll=null}state.runId=null;state.activeRunProgress=null;state.selectedRunDetail=null;state.documentCache={};state.latencyCache={};state.activeLinks={};state.pollErrors=0;clearSavedRun();document.querySelectorAll('.run-row').forEach(r=>r.classList.remove('selected'));setBuildModeNew();resetRunDetails();renderTimeline(DEFAULT_TIMELINE);setText('progressPercent','0%');const fill=$('progressFill');if(fill)fill.style.width='0%';setText('currentStep','Ready');setText('lastEvent','Choose a model card and launch a build.');setText('elapsed','β');if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;setText('lastPolled','β');setText('hardwarePlan',hardwarePlanLabel());renderMetrics({});showMessage('New build mode ready.','info')}
|
| 409 |
function setActiveRun(result){if(state.poll){clearInterval(state.poll);state.poll=null}resetRunDetails();state.documentCache={};state.latencyCache={};state.runId=result.run_id;state.spaceLinksUnlockedFor=null;if(Array.isArray(state.runsCache)&&typeof sortRunsNewestFirst==='function'){state.runsCache=sortRunsNewestFirst([{...result,run_id:result.run_id,status:result.status||'running',created_at:result.created_at||new Date().toISOString()},...state.runsCache.filter(r=>r.run_id!==result.run_id)]);renderRunsFromCache();}if(result.bucket_source)state.bucketSource=result.bucket_source;setText('detailRunId',result.run_id);setText('detailJobStatus',result.status||'RUNNING');setText('detailGateStatus',result.kind||'running');setText('detailSpace',result.target_space||'β');setText('detailHardware',result.selected_hardware||result.preferred_space_hardware||'β');renderActiveRunMeta({summary:result,state:result,launch:result});renderProgress({run_id:result.run_id,status:result.status||'running',progress:1,current_step_label:'Launching build Jobβ¦',events:[{step:'launch',status:'started',message:'Build Job launched; waiting for first worker event.'}],summary:result,state:result});setText('homeRunDetailStatus',result.status||'Running');setBuildModeInspecting(result.run_id);setQuickLinks({...result.links,bucket_source:result.bucket_source,run_id:result.run_id,job_id:result.job_id,job_owner:state.user?.username,target_space_url:result.target_space_url},result);saveActiveRun();markPendingActiveFields();startPolling(result.run_id)}
|
| 410 |
function startPolling(runId,options={}){
|
| 411 |
if(!runId)return;
|
|
|
|
| 223 |
}
|
| 224 |
}
|
| 225 |
function renderAnonymousEvalStatus(me){
|
| 226 |
+
const card=$('evalArchiveCard');
|
| 227 |
const title=$('evalArchiveTitle');
|
| 228 |
const text=$('anonymousEvalText');
|
| 229 |
const enable=$('enableEvalArchive');
|
|
|
|
| 245 |
title.textContent=enabled?'Anonymous eval archive':'Anonymous eval archive';
|
| 246 |
}
|
| 247 |
if(stateBadge){
|
| 248 |
+
const unavailable=String(cfg.reason||'')==='status_unavailable';
|
| 249 |
+
stateBadge.textContent=enabled?'On':(unavailable?'Unknown':'Off');
|
| 250 |
+
stateBadge.className='badge '+(enabled?'success':unavailable?'warn':'neutral');
|
| 251 |
}
|
| 252 |
if(text){
|
| 253 |
text.className='eval-feedback '+(enabled?'on':'off');
|
|
|
|
| 255 |
const manager=cfg.managed_by==='you'?'You manage this instance setting.':`Managed by ${cfg.managed_by||'instance owner'}.`;
|
| 256 |
text.textContent=`On Β· ${bucket}${path} β ${mount}`;
|
| 257 |
text.title=`Anonymous project eval archive enabled from ${cfg.source||'config'}: ${bucket}${path} mounted at ${mount}. ${manager} ASF backend publishes anonymized eval records after user Jobs write local run evals.`;
|
| 258 |
+
}else if(String(cfg.reason||'')==='status_unavailable'){
|
| 259 |
+
text.textContent='Status unavailable Β· could not verify eval archive configuration.';
|
| 260 |
+
text.title='Refresh to retry the public eval archive status check. Unknown is not treated as Off.';
|
| 261 |
}else if(me?.username){
|
| 262 |
text.textContent='Mount an operator eval bucket at /evals, then enable publishing.';
|
| 263 |
text.title='Recommended: private operator bucket, bucket path evals, mounted read-write at /evals on this ASF Space. Env vars still work as an admin override.';
|
|
|
|
| 267 |
}
|
| 268 |
}
|
| 269 |
if(enable){
|
| 270 |
+
enable.hidden=enabled||!me?.username||cfg.env_override===true||cfg.can_manage===false;
|
| 271 |
enable.disabled=!me?.username||state.busy;
|
| 272 |
enable.title=`Enable anonymous eval archive using ${cfg.proposed_bucket_source||'your namespace bucket'} mounted at ${cfg.default_mount_path||'/evals'}.`;
|
| 273 |
}
|
|
|
|
| 395 |
])setText(id,value);
|
| 396 |
setText('activeLatencyValue','β');setText('activeZeroGpuDuration','β');setText('activeLatencySource','waiting for live smoke test');const latencyCard=$('activeLatencyCard');if(latencyCard){latencyCard.classList.remove('has-latency');latencyCard.classList.add('is-loading')}
|
| 397 |
const status=$('progressStatus');if(status)status.className='badge neutral';
|
| 398 |
+
const fill=$('progressFill');if(fill){fill.style.width='0%';fill.classList.remove('is-running','progress-bump','is-success','is-warn','is-error','is-running-state','is-stopped','is-neutral')}const cap=$('progressCaptionLabel');if(cap)cap.textContent='Overall progress';const sem=$('progressSemantics');if(sem){sem.hidden=true;sem.innerHTML=''}const bar=$('progressBar');if(bar)bar.setAttribute('aria-valuenow','0')
|
| 399 |
setQuickLinks({});
|
| 400 |
const full=$('reportPreview');if(full)full.textContent='No report selected.';
|
| 401 |
const manual=$('manualActionPanel');if(manual)manual.hidden=true;
|
|
|
|
| 409 |
const blockers=$('selectedBlockers');if(blockers){blockers.hidden=true;blockers.innerHTML=''}
|
| 410 |
for(const id of ['deleteActiveRun','activePrefillSpaceTest','deleteValidationRun']){const btn=$(id);if(btn)btn.disabled=true}
|
| 411 |
renderTimeline(DEFAULT_TIMELINE,'idle');
|
| 412 |
+
} function prepareNewBuild(){if(state.poll){clearInterval(state.poll);state.poll=null}state.runId=null;state.activeRunProgress=null;state.selectedRunDetail=null;state.documentCache={};state.latencyCache={};state.activeLinks={};state.pollErrors=0;clearSavedRun();document.querySelectorAll('.run-row').forEach(r=>r.classList.remove('selected'));setBuildModeNew();resetRunDetails();renderTimeline(DEFAULT_TIMELINE);setText('progressPercent','0%');const cap=$('progressCaptionLabel');if(cap)cap.textContent='Overall progress';const sem=$('progressSemantics');if(sem){sem.hidden=true;sem.innerHTML=''}const fill=$('progressFill');if(fill)fill.style.width='0%';setText('currentStep','Ready');setText('lastEvent','Choose a model card and launch a build.');setText('elapsed','β');if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;setText('lastPolled','β');setText('hardwarePlan',hardwarePlanLabel());renderMetrics({});showMessage('New build mode ready.','info')}
|
| 413 |
function setActiveRun(result){if(state.poll){clearInterval(state.poll);state.poll=null}resetRunDetails();state.documentCache={};state.latencyCache={};state.runId=result.run_id;state.spaceLinksUnlockedFor=null;if(Array.isArray(state.runsCache)&&typeof sortRunsNewestFirst==='function'){state.runsCache=sortRunsNewestFirst([{...result,run_id:result.run_id,status:result.status||'running',created_at:result.created_at||new Date().toISOString()},...state.runsCache.filter(r=>r.run_id!==result.run_id)]);renderRunsFromCache();}if(result.bucket_source)state.bucketSource=result.bucket_source;setText('detailRunId',result.run_id);setText('detailJobStatus',result.status||'RUNNING');setText('detailGateStatus',result.kind||'running');setText('detailSpace',result.target_space||'β');setText('detailHardware',result.selected_hardware||result.preferred_space_hardware||'β');renderActiveRunMeta({summary:result,state:result,launch:result});renderProgress({run_id:result.run_id,status:result.status||'running',progress:1,current_step_label:'Launching build Jobβ¦',events:[{step:'launch',status:'started',message:'Build Job launched; waiting for first worker event.'}],summary:result,state:result});setText('homeRunDetailStatus',result.status||'Running');setBuildModeInspecting(result.run_id);setQuickLinks({...result.links,bucket_source:result.bucket_source,run_id:result.run_id,job_id:result.job_id,job_owner:state.user?.username,target_space_url:result.target_space_url},result);saveActiveRun();markPendingActiveFields();startPolling(result.run_id)}
|
| 414 |
function startPolling(runId,options={}){
|
| 415 |
if(!runId)return;
|
web/static/progress.js
CHANGED
|
@@ -270,6 +270,7 @@ function timelineIcon(status){
|
|
| 270 |
if(status==='done'||status==='completed')return'β';
|
| 271 |
if(status==='running')return'β’';
|
| 272 |
if(status==='failed')return'!';
|
|
|
|
| 273 |
if(status==='fallback')return'β';
|
| 274 |
if(status==='recovered'||status==='superseded')return'βΊ';
|
| 275 |
if(status==='blocked'||status==='cancelled'||status==='stopped'||status==='after_fail')return'β ';
|
|
@@ -282,6 +283,7 @@ function timelineStatus(status,overallStatus=''){
|
|
| 282 |
const stopped=overall&&PROGRESS_TERMINAL_STATUSES.has(overall)&&!['success','succeeded','done','full_inference_success','full_inference_candidate_health_passed','health_only','partial','partial_validation','completed_with_warnings'].includes(overall);
|
| 283 |
if(status==='failed'||status==='error'||status==='failure')return'status-failed';
|
| 284 |
if(status==='fallback')return'status-fallback';
|
|
|
|
| 285 |
if(status==='recovered'||status==='superseded')return'status-warn';
|
| 286 |
if(status==='after_fail'||status==='post_failure')return'status-after-fail';
|
| 287 |
if(status==='blocked'||status==='cancelled'||status==='canceled'||status==='stopped')return'status-stopped';
|
|
@@ -333,6 +335,9 @@ function friendlyTerminalEventMessage(event={}){
|
|
| 333 |
const step=String(event.step||'').toLowerCase();
|
| 334 |
const status=String(event.status||'').toLowerCase();
|
| 335 |
const msg=String(event.message||'').trim();
|
|
|
|
|
|
|
|
|
|
| 336 |
if(step==='done'&&(status==='full_inference_candidate_health_passed'||status==='health_only'||status==='partial_validation')){
|
| 337 |
return 'Universal model-card builder completed Β· Partial validation';
|
| 338 |
}
|
|
@@ -344,6 +349,163 @@ function friendlyTerminalEventMessage(event={}){
|
|
| 344 |
function eventMessage(event){
|
| 345 |
return friendlyTerminalEventMessage(event||{});
|
| 346 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
function timelineStepBounds(root,target){
|
| 348 |
const rootBox=root.getBoundingClientRect?root.getBoundingClientRect():{left:0,right:root.clientWidth,width:root.clientWidth};
|
| 349 |
const targetBox=target.getBoundingClientRect?target.getBoundingClientRect():{left:target.offsetLeft-root.scrollLeft,right:target.offsetLeft-root.scrollLeft+target.offsetWidth,width:target.offsetWidth};
|
|
@@ -522,6 +684,9 @@ function renderTimeline(items,overallStatus=''){
|
|
| 522 |
const root=$('timeline');
|
| 523 |
if(!root)return;
|
| 524 |
if(!root.dataset.scrollBound){root.dataset.scrollBound='1';root.addEventListener('scroll',()=>{if(root.dataset.autoScrolling==='1')return;root.dataset.userScrolled='1'},{passive:true});}
|
|
|
|
|
|
|
|
|
|
| 525 |
const fullList=items||[];
|
| 526 |
const list=compactTimelineItems(fullList,overallStatus);
|
| 527 |
// Keep the compact timeline on a single row. The number of visible groups is
|
|
@@ -712,7 +877,7 @@ function animateProgressBar(progress,status,currentStep){
|
|
| 712 |
let klass=visualStatusClass(status);if(value>=100&&klass==='neutral')klass='success';
|
| 713 |
fill.style.setProperty('--progress-target',`${value}%`);
|
| 714 |
fill.style.width=`${value}%`;
|
| 715 |
-
fill.classList.remove('is-success','is-error','is-running-state','is-stopped','is-neutral');
|
| 716 |
fill.classList.add(`is-${klass==='running'?'running-state':klass}`);
|
| 717 |
const s=String(status||'').toLowerCase();fill.classList.toggle('is-running',klass==='running'||s==='pending'||s==='scheduled'||s==='waiting');
|
| 718 |
if(previous!==null&&Math.round(previous)!==Math.round(value)){
|
|
@@ -732,6 +897,40 @@ function animateStatusBadge(status){
|
|
| 732 |
}
|
| 733 |
progressMotionState.lastStatus=next;
|
| 734 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 735 |
function setElapsedClock(seconds,status){
|
| 736 |
const s=String(status||'').toLowerCase();
|
| 737 |
elapsedClock.baseSeconds=Math.max(0,Number(seconds||0));
|
|
@@ -776,7 +975,7 @@ function resetProgressStability(runId=null){
|
|
| 776 |
progressMotionState.lastStatus=null;
|
| 777 |
progressMotionState.lastStep=null;
|
| 778 |
const timeline=$('timeline');
|
| 779 |
-
if(timeline){timeline.dataset.signature='';timeline.dataset.visibleStep='';timeline.dataset.userScrolled='';timeline.classList.remove('trace-failed','trace-recovered','trace-fallback');timeline.scrollLeft=0;}const note=$('timelineTraceNote');if(note){note.hidden=true;note.innerHTML='';note.className='timeline-trace-note';}
|
| 780 |
}
|
| 781 |
function stabilizeProgressPayload(p={},view=null){
|
| 782 |
const runId=String(p.run_id||view?.run_id||state.runId||'');
|
|
@@ -828,22 +1027,28 @@ function renderRunViewModel(view,p={}){
|
|
| 828 |
const header=view.header||{};
|
| 829 |
const sm=view.status_model||{};
|
| 830 |
const stableEvents=mergeStableEvents(String(p.run_id||view.run_id||state.runId||''),p.events||[]);
|
|
|
|
|
|
|
| 831 |
const stable=stabilizeProgressPayload(p,view);
|
| 832 |
-
const progress=Number(stable.progress??(header.status==='succeeded'?100:0));
|
| 833 |
-
const runStatus=p.status||header.status;
|
| 834 |
-
const visualRunStatus=p.visual_status||runStatus;
|
| 835 |
-
|
|
|
|
|
|
|
| 836 |
setText('progressPercent',`${Math.round(progress)}%`);
|
| 837 |
-
|
|
|
|
| 838 |
const statusEl=$('progressStatus');
|
| 839 |
if(statusEl){
|
| 840 |
-
statusEl.textContent=badgeLabel(runStatus||'Running');
|
| 841 |
statusEl.className='badge '+statusClass(visualRunStatus);
|
| 842 |
animateStatusBadge(visualRunStatus);
|
| 843 |
}
|
| 844 |
const latestEvent=stableEvents[stableEvents.length-1];
|
| 845 |
-
setText('currentStep',stable.currentStepLabel
|
| 846 |
-
setText('
|
|
|
|
| 847 |
setElapsedClock(p.elapsed_seconds??header.elapsed_seconds,runStatus);
|
| 848 |
setText('lastPolled',new Date().toLocaleTimeString());
|
| 849 |
setText('detailSpace',header.space||'β');
|
|
@@ -857,7 +1062,7 @@ function renderRunViewModel(view,p={}){
|
|
| 857 |
cancel.hidden=Boolean(terminal);
|
| 858 |
cancel.disabled=!(state.runId&&view.links?.job_url&&!terminal);
|
| 859 |
}
|
| 860 |
-
renderTimeline(stable.timeline,runStatus);
|
| 861 |
renderActivityFeed(stableEvents);
|
| 862 |
renderDiagnostics(view.diagnostics||{},header);
|
| 863 |
renderSpaceTestPreview(view.space_test||{});
|
|
@@ -875,6 +1080,7 @@ function renderProgress(p){
|
|
| 875 |
const progress=Number(stable.progress||0);
|
| 876 |
animateProgressBar(progress,p.visual_status||p.status,stable.currentStep);
|
| 877 |
setText('progressPercent',`${Math.round(progress)}%`);
|
|
|
|
| 878 |
const latestEvent=stableEvents[stableEvents.length-1];
|
| 879 |
setText('currentStep',stable.currentStepLabel||p.current_step_label||'β');
|
| 880 |
setText('lastEvent',eventMessage(latestEvent)||p.last_event||'β');
|
|
@@ -892,6 +1098,11 @@ function renderProgress(p){
|
|
| 892 |
}
|
| 893 |
|
| 894 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 895 |
// Legacy test anchors retained for timeline invariants:
|
| 896 |
// currentStepLabel:activeItem?.label
|
| 897 |
// x.done_count||0
|
|
@@ -909,3 +1120,8 @@ function renderProgress(p){
|
|
| 909 |
// status='after_fail'
|
| 910 |
// root.scrollLeft-(bounds.rootLeft+margin-bounds.targetLeft)
|
| 911 |
// root.scrollLeft+(bounds.targetRight-(bounds.rootRight-margin))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
if(status==='done'||status==='completed')return'β';
|
| 271 |
if(status==='running')return'β’';
|
| 272 |
if(status==='failed')return'!';
|
| 273 |
+
if(status==='warning'||status==='warn')return'!';
|
| 274 |
if(status==='fallback')return'β';
|
| 275 |
if(status==='recovered'||status==='superseded')return'βΊ';
|
| 276 |
if(status==='blocked'||status==='cancelled'||status==='stopped'||status==='after_fail')return'β ';
|
|
|
|
| 283 |
const stopped=overall&&PROGRESS_TERMINAL_STATUSES.has(overall)&&!['success','succeeded','done','full_inference_success','full_inference_candidate_health_passed','health_only','partial','partial_validation','completed_with_warnings'].includes(overall);
|
| 284 |
if(status==='failed'||status==='error'||status==='failure')return'status-failed';
|
| 285 |
if(status==='fallback')return'status-fallback';
|
| 286 |
+
if(status==='warning'||status==='warn')return'status-warn';
|
| 287 |
if(status==='recovered'||status==='superseded')return'status-warn';
|
| 288 |
if(status==='after_fail'||status==='post_failure')return'status-after-fail';
|
| 289 |
if(status==='blocked'||status==='cancelled'||status==='canceled'||status==='stopped')return'status-stopped';
|
|
|
|
| 335 |
const step=String(event.step||'').toLowerCase();
|
| 336 |
const status=String(event.status||'').toLowerCase();
|
| 337 |
const msg=String(event.message||'').trim();
|
| 338 |
+
if(step==='anonymous_eval'&&status==='success'){
|
| 339 |
+
return 'Wrote local eval record';
|
| 340 |
+
}
|
| 341 |
if(step==='done'&&(status==='full_inference_candidate_health_passed'||status==='health_only'||status==='partial_validation')){
|
| 342 |
return 'Universal model-card builder completed Β· Partial validation';
|
| 343 |
}
|
|
|
|
| 349 |
function eventMessage(event){
|
| 350 |
return friendlyTerminalEventMessage(event||{});
|
| 351 |
}
|
| 352 |
+
|
| 353 |
+
function hasCanonicalTimelineModel(model){
|
| 354 |
+
return Boolean(model&&model.schema_version==='run_timeline_model.v1'&&Array.isArray(model.phases));
|
| 355 |
+
}
|
| 356 |
+
function canonicalPhaseToTimelineItem(phase={}){
|
| 357 |
+
const raw=String(phase.status||'pending').toLowerCase();
|
| 358 |
+
let status=raw;
|
| 359 |
+
if(raw==='complete')status='done';
|
| 360 |
+
if(raw==='warning')status='warning';
|
| 361 |
+
if(raw==='failed')status='failed';
|
| 362 |
+
if(raw==='stopped')status='stopped';
|
| 363 |
+
if(raw==='not_needed')status='skipped';
|
| 364 |
+
const details=Array.isArray(phase.details)?phase.details:[];
|
| 365 |
+
return {
|
| 366 |
+
id:phase.id||phase.label,
|
| 367 |
+
step:phase.id||phase.label,
|
| 368 |
+
label:phase.label||phase.id||'Phase',
|
| 369 |
+
status,
|
| 370 |
+
summary:phase.summary||'',
|
| 371 |
+
details,
|
| 372 |
+
percent:status==='done'||status==='warning'||status==='skipped'?100:(status==='running'?50:0),
|
| 373 |
+
ring_progress:status==='done'||status==='warning'||status==='skipped'?100:(status==='running'?50:0),
|
| 374 |
+
};
|
| 375 |
+
}
|
| 376 |
+
function timelinePhaseById(phases=[],id=''){
|
| 377 |
+
return phases.find(p=>String(p.id||'')===id)||null;
|
| 378 |
+
}
|
| 379 |
+
function firstPhaseWithStatus(phases=[],statuses=[]){
|
| 380 |
+
const wanted=new Set(statuses.map(s=>String(s||'').toLowerCase()));
|
| 381 |
+
return phases.find(p=>wanted.has(String(p.status||'').toLowerCase()))||null;
|
| 382 |
+
}
|
| 383 |
+
function firstPhaseWithIdsAndStatus(phases=[],ids=[],statuses=[]){
|
| 384 |
+
const wantedIds=new Set(ids);
|
| 385 |
+
const wantedStatuses=new Set(statuses.map(s=>String(s||'').toLowerCase()));
|
| 386 |
+
return phases.find(p=>wantedIds.has(String(p.id||''))&&wantedStatuses.has(String(p.status||'').toLowerCase()))||null;
|
| 387 |
+
}
|
| 388 |
+
function timelineModelActivePhase(model={}){
|
| 389 |
+
const phases=Array.isArray(model.phases)?model.phases:[];
|
| 390 |
+
if(!phases.length)return null;
|
| 391 |
+
const progress=model.progress||{};
|
| 392 |
+
const visual=String(progress.visual_status||'').toLowerCase();
|
| 393 |
+
const terminal=progress.terminal===true||['success','warn','error','stopped'].includes(visual);
|
| 394 |
+
const failed=firstPhaseWithStatus(phases,['failed','stopped']);
|
| 395 |
+
if(failed)return failed;
|
| 396 |
+
if(terminal&&visual==='success'){
|
| 397 |
+
return firstPhaseWithIdsAndStatus(phases,['live_validation'],['complete','warning'])||
|
| 398 |
+
firstPhaseWithIdsAndStatus(phases,['archive'],['complete','warning'])||
|
| 399 |
+
timelinePhaseById(phases,'done')||
|
| 400 |
+
[...phases].reverse().find(p=>String(p.status||'').toLowerCase()!=='pending')||phases[0];
|
| 401 |
+
}
|
| 402 |
+
if(terminal&&visual==='warn'){
|
| 403 |
+
return firstPhaseWithIdsAndStatus(phases,['live_validation','archive','done'],['warning'])||
|
| 404 |
+
firstPhaseWithStatus(phases,['warning'])||
|
| 405 |
+
firstPhaseWithIdsAndStatus(phases,['live_validation','archive','done'],['complete'])||
|
| 406 |
+
[...phases].reverse().find(p=>String(p.status||'').toLowerCase()!=='pending')||phases[0];
|
| 407 |
+
}
|
| 408 |
+
const running=firstPhaseWithStatus(phases,['running']);
|
| 409 |
+
if(running)return running;
|
| 410 |
+
const warning=firstPhaseWithStatus(phases,['warning']);
|
| 411 |
+
if(warning)return warning;
|
| 412 |
+
return [...phases].reverse().find(p=>String(p.status||'').toLowerCase()!=='pending')||phases[0]||null;
|
| 413 |
+
}
|
| 414 |
+
function renderTimelineModelWarnings(model={}){
|
| 415 |
+
const note=$('timelineTraceNote');
|
| 416 |
+
if(!note)return;
|
| 417 |
+
const warnings=Array.isArray(model.warnings)?model.warnings:[];
|
| 418 |
+
if(!warnings.length){note.hidden=true;note.innerHTML='';note.className='timeline-trace-note';return;}
|
| 419 |
+
const first=warnings[0]||{};
|
| 420 |
+
const extra=warnings.length>1?` +${warnings.length-1} more`:'';
|
| 421 |
+
note.hidden=false;
|
| 422 |
+
note.className='timeline-trace-note fallback canonical-warning';
|
| 423 |
+
note.innerHTML=`<strong><span>β </span>${escapeHtml(first.label||'Run warning')}${escapeHtml(extra)}</strong><p>${escapeHtml(first.detail||'This run completed with non-blocking warnings.')}</p>`;
|
| 424 |
+
}
|
| 425 |
+
function timelineDetailStatusLabel(status=''){
|
| 426 |
+
const s=String(status||'').toLowerCase();
|
| 427 |
+
if(s==='complete'||s==='done'||s==='success')return'Complete';
|
| 428 |
+
if(s==='warning'||s==='warn')return'Needs attention';
|
| 429 |
+
if(s==='running'||s==='waiting')return'In progress';
|
| 430 |
+
if(s==='not_needed'||s==='skipped')return'Not needed';
|
| 431 |
+
if(s==='failed'||s==='error')return'Failed';
|
| 432 |
+
if(s==='stopped'||s==='cancelled'||s==='blocked')return'Stopped';
|
| 433 |
+
return badgeLabel(s||'pending');
|
| 434 |
+
}
|
| 435 |
+
function timelinePhaseDetailRows(phase={}){
|
| 436 |
+
const details=Array.isArray(phase.details)?phase.details.filter(Boolean):[];
|
| 437 |
+
if(details.length){
|
| 438 |
+
return details.slice(0,6).map(d=>{
|
| 439 |
+
const label=escapeHtml(d.label||d.title||'Detail');
|
| 440 |
+
const value=escapeHtml(d.value||d.detail||d.summary||d.status||'');
|
| 441 |
+
const status=String(d.status||'').toLowerCase();
|
| 442 |
+
const cls=status?` ${statusClass(status)}`:'';
|
| 443 |
+
return `<li class="timeline-detail-row${cls}"><span>${label}</span>${value?`<strong>${value}</strong>`:''}</li>`;
|
| 444 |
+
}).join('');
|
| 445 |
+
}
|
| 446 |
+
const summary=String(phase.summary||'').trim();
|
| 447 |
+
return summary?`<li class="timeline-detail-row neutral"><span>Summary</span><strong>${escapeHtml(summary)}</strong></li>`:'';
|
| 448 |
+
}
|
| 449 |
+
function renderTimelineModelDetails(model={},activePhase=null){
|
| 450 |
+
const root=$('timelinePhaseDetails');
|
| 451 |
+
if(!root)return;
|
| 452 |
+
if(!hasCanonicalTimelineModel(model)){root.hidden=true;root.innerHTML='';root.className='timeline-phase-details';return;}
|
| 453 |
+
const phase=activePhase||timelineModelActivePhase(model)||{};
|
| 454 |
+
if(!phase||!(phase.id||phase.label)){root.hidden=true;root.innerHTML='';return;}
|
| 455 |
+
const status=String(phase.status||'pending').toLowerCase();
|
| 456 |
+
const tone=timelineStatus(status).replace(/^status-/,'');
|
| 457 |
+
const rows=timelinePhaseDetailRows(phase);
|
| 458 |
+
const warnings=Array.isArray(model.warnings)?model.warnings:[];
|
| 459 |
+
const warningHtml=warnings.length?`<div class="timeline-detail-warnings"><strong>Needs attention</strong>${warnings.slice(0,3).map(w=>`<p>${escapeHtml(w.label||'Warning')}${w.detail?`: ${escapeHtml(w.detail)}`:''}</p>`).join('')}</div>`:'';
|
| 460 |
+
root.hidden=false;
|
| 461 |
+
root.className=`timeline-phase-details ${tone}`;
|
| 462 |
+
root.innerHTML=`<div class="timeline-detail-head"><span>${escapeHtml(phase.label||'Phase')}</span><strong>${escapeHtml(timelineDetailStatusLabel(status))}</strong></div>${phase.summary?`<p>${escapeHtml(phase.summary)}</p>`:''}${rows?`<ul>${rows}</ul>`:''}${warningHtml}`;
|
| 463 |
+
}
|
| 464 |
+
function renderTimelineModel(model={},overallStatus=''){
|
| 465 |
+
const root=$('timeline');
|
| 466 |
+
if(!root)return false;
|
| 467 |
+
if(!hasCanonicalTimelineModel(model))return false;
|
| 468 |
+
if(!root.dataset.scrollBound){root.dataset.scrollBound='1';root.addEventListener('scroll',()=>{if(root.dataset.autoScrolling==='1')return;root.dataset.userScrolled='1'},{passive:true});}
|
| 469 |
+
const list=(model.phases||[]).map(canonicalPhaseToTimelineItem);
|
| 470 |
+
const wideTimeline=typeof window==='undefined'||!window.matchMedia||window.matchMedia('(min-width: 721px)').matches;
|
| 471 |
+
root.style.gridTemplateColumns=wideTimeline&&list.length?`repeat(${list.length}, minmax(104px, 1fr))`:'';
|
| 472 |
+
root.classList.add('timeline-canonical');
|
| 473 |
+
root.classList.remove('trace-failed','trace-recovered','trace-fallback');
|
| 474 |
+
renderTimelineModelWarnings(model);
|
| 475 |
+
const active=timelineModelActivePhase(model)||{};
|
| 476 |
+
renderTimelineModelDetails(model,active);
|
| 477 |
+
const currentKey=String(active.id||active.label||'');
|
| 478 |
+
const signature=list.map((x,i)=>`${timelineStepKey(x,i)}:${x.status}:${x.label}:${x.summary}:${overallStatus}`).join('|');
|
| 479 |
+
if(root.dataset.signature===signature){
|
| 480 |
+
if(root.dataset.visibleStep!==currentKey&&root.dataset.userScrolled!=='1')ensureTimelineStepVisible(currentKey);
|
| 481 |
+
return true;
|
| 482 |
+
}
|
| 483 |
+
const previousKey=root.dataset.currentStep||'';
|
| 484 |
+
if(previousKey!==currentKey)root.dataset.userScrolled='';
|
| 485 |
+
root.dataset.signature=signature;
|
| 486 |
+
root.dataset.currentStep=currentKey||'';
|
| 487 |
+
const nextState={};
|
| 488 |
+
root.innerHTML=list.map((x,i)=>{
|
| 489 |
+
const key=timelineStepKey(x,i);
|
| 490 |
+
const st=timelineStatus(x.status,overallStatus);
|
| 491 |
+
const prev=timelineRenderState[key];
|
| 492 |
+
const changed=prev&&prev!==st;
|
| 493 |
+
nextState[key]=st;
|
| 494 |
+
const label=st.replace(/^status-/,'');
|
| 495 |
+
const summary=x.summary||label;
|
| 496 |
+
const details=(x.details||[]).map(d=>d&&d.label).filter(Boolean);
|
| 497 |
+
const detailTitle=details.length?details.join(' Β· '):summary;
|
| 498 |
+
const title=` title="${escapeHtml([x.label,summary,detailTitle].filter(Boolean).join(' Β· '))}"`;
|
| 499 |
+
const reasonLine=summary?`<small class="step-reason canonical-summary">${escapeHtml(summary)}</small>`:'';
|
| 500 |
+
const aria=`${x.label}: ${label}${summary?`, ${summary}`:''}`;
|
| 501 |
+
return `<div class="step ${st} canonical-phase${changed?' status-changed':''}" data-step="${escapeHtml(key)}" data-status="${label}" aria-label="${escapeHtml(aria)}" style="--step-progress:${Number(x.percent||0)}%"${title}><div class="step-dot"><span>${timelineIcon(x.status)}</span></div><div><strong>${escapeHtml(x.label)}</strong><small>${escapeHtml(summary)}</small>${reasonLine}</div></div>`;
|
| 502 |
+
}).join('');
|
| 503 |
+
Object.keys(timelineRenderState).forEach(k=>delete timelineRenderState[k]);
|
| 504 |
+
Object.assign(timelineRenderState,nextState);
|
| 505 |
+
root.querySelectorAll('.step.status-changed').forEach(step=>{animateClass(step,'status-flash',1000);animateClass(step.querySelector('.step-dot'),'dot-pop',700);});
|
| 506 |
+
if(!root.dataset.userScrolled||previousKey!==currentKey){root.dataset.autoScrolling='1';const scrollOpts={instant:!root.dataset.visibleStep,force:previousKey!==currentKey};(typeof requestAnimationFrame==='function'?requestAnimationFrame:(fn)=>setTimeout(fn,0))(()=>(typeof requestAnimationFrame==='function'?requestAnimationFrame:(fn)=>setTimeout(fn,0))(()=>ensureTimelineStepVisible(currentKey,scrollOpts)));setTimeout(()=>{if(root)root.dataset.autoScrolling=''},360);}
|
| 507 |
+
return true;
|
| 508 |
+
}
|
| 509 |
function timelineStepBounds(root,target){
|
| 510 |
const rootBox=root.getBoundingClientRect?root.getBoundingClientRect():{left:0,right:root.clientWidth,width:root.clientWidth};
|
| 511 |
const targetBox=target.getBoundingClientRect?target.getBoundingClientRect():{left:target.offsetLeft-root.scrollLeft,right:target.offsetLeft-root.scrollLeft+target.offsetWidth,width:target.offsetWidth};
|
|
|
|
| 684 |
const root=$('timeline');
|
| 685 |
if(!root)return;
|
| 686 |
if(!root.dataset.scrollBound){root.dataset.scrollBound='1';root.addEventListener('scroll',()=>{if(root.dataset.autoScrolling==='1')return;root.dataset.userScrolled='1'},{passive:true});}
|
| 687 |
+
root.classList.remove('timeline-canonical');
|
| 688 |
+
const canonicalDetails=$('timelinePhaseDetails');
|
| 689 |
+
if(canonicalDetails){canonicalDetails.hidden=true;canonicalDetails.innerHTML='';canonicalDetails.className='timeline-phase-details';}
|
| 690 |
const fullList=items||[];
|
| 691 |
const list=compactTimelineItems(fullList,overallStatus);
|
| 692 |
// Keep the compact timeline on a single row. The number of visible groups is
|
|
|
|
| 877 |
let klass=visualStatusClass(status);if(value>=100&&klass==='neutral')klass='success';
|
| 878 |
fill.style.setProperty('--progress-target',`${value}%`);
|
| 879 |
fill.style.width=`${value}%`;
|
| 880 |
+
fill.classList.remove('is-success','is-warn','is-error','is-running-state','is-stopped','is-neutral');
|
| 881 |
fill.classList.add(`is-${klass==='running'?'running-state':klass}`);
|
| 882 |
const s=String(status||'').toLowerCase();fill.classList.toggle('is-running',klass==='running'||s==='pending'||s==='scheduled'||s==='waiting');
|
| 883 |
if(previous!==null&&Math.round(previous)!==Math.round(value)){
|
|
|
|
| 897 |
}
|
| 898 |
progressMotionState.lastStatus=next;
|
| 899 |
}
|
| 900 |
+
|
| 901 |
+
function progressProcessLabel(progress={},percent=0){
|
| 902 |
+
const terminal=Boolean(progress.terminal);
|
| 903 |
+
const status=String(progress.process_status||'').toLowerCase();
|
| 904 |
+
if(terminal||Number(percent)>=100)return'Completed';
|
| 905 |
+
if(status.includes('running')||status.includes('waiting')||status.includes('pending'))return'In progress';
|
| 906 |
+
return status?badgeLabel(status):'In progress';
|
| 907 |
+
}
|
| 908 |
+
function progressVerdictLabel(progress={},visualStatus=''){
|
| 909 |
+
const label=String(progress.label||'').trim();
|
| 910 |
+
if(label)return label;
|
| 911 |
+
const verdict=String(progress.verdict||visualStatus||'').trim();
|
| 912 |
+
return verdict?badgeLabel(verdict):'Pending verdict';
|
| 913 |
+
}
|
| 914 |
+
function renderSemanticProgressSummary(progress={},percent=0,visualStatus=''){
|
| 915 |
+
const root=$('progressSemantics');
|
| 916 |
+
const caption=$('progressCaptionLabel');
|
| 917 |
+
const bar=$('progressBar');
|
| 918 |
+
if(caption)caption.textContent=progress&&progress.verdict?'Process progress':'Overall progress';
|
| 919 |
+
if(bar){
|
| 920 |
+
const value=Math.max(0,Math.min(100,Number(percent||0)));
|
| 921 |
+
bar.setAttribute('aria-valuenow',String(Math.round(value)));
|
| 922 |
+
bar.setAttribute('aria-label',progress&&progress.verdict?`Process progress ${Math.round(value)} percent, result ${progressVerdictLabel(progress,visualStatus)}`:`Overall progress ${Math.round(value)} percent`);
|
| 923 |
+
}
|
| 924 |
+
if(!root)return;
|
| 925 |
+
if(!progress||!progress.verdict){root.hidden=true;root.innerHTML='';return;}
|
| 926 |
+
const process=progressProcessLabel(progress,percent);
|
| 927 |
+
const verdict=progressVerdictLabel(progress,visualStatus);
|
| 928 |
+
const tone=visualStatusClass(visualStatus||progress.visual_status||progress.verdict);
|
| 929 |
+
const subtitle=String(progress.subtitle||'').trim();
|
| 930 |
+
root.hidden=false;
|
| 931 |
+
root.className=`progress-semantics ${tone}`;
|
| 932 |
+
root.innerHTML=`<span><strong>Process</strong> ${escapeHtml(process)}</span><span><strong>Result</strong> ${escapeHtml(verdict)}</span>${subtitle?`<em>${escapeHtml(subtitle)}</em>`:''}`;
|
| 933 |
+
}
|
| 934 |
function setElapsedClock(seconds,status){
|
| 935 |
const s=String(status||'').toLowerCase();
|
| 936 |
elapsedClock.baseSeconds=Math.max(0,Number(seconds||0));
|
|
|
|
| 975 |
progressMotionState.lastStatus=null;
|
| 976 |
progressMotionState.lastStep=null;
|
| 977 |
const timeline=$('timeline');
|
| 978 |
+
if(timeline){timeline.dataset.signature='';timeline.dataset.visibleStep='';timeline.dataset.userScrolled='';timeline.classList.remove('trace-failed','trace-recovered','trace-fallback','timeline-canonical');timeline.scrollLeft=0;}const note=$('timelineTraceNote');if(note){note.hidden=true;note.innerHTML='';note.className='timeline-trace-note';}const details=$('timelinePhaseDetails');if(details){details.hidden=true;details.innerHTML='';details.className='timeline-phase-details';}
|
| 979 |
}
|
| 980 |
function stabilizeProgressPayload(p={},view=null){
|
| 981 |
const runId=String(p.run_id||view?.run_id||state.runId||'');
|
|
|
|
| 1027 |
const header=view.header||{};
|
| 1028 |
const sm=view.status_model||{};
|
| 1029 |
const stableEvents=mergeStableEvents(String(p.run_id||view.run_id||state.runId||''),p.events||[]);
|
| 1030 |
+
const timelineModel=view.timeline_model||p.timeline_model||null;
|
| 1031 |
+
const modelProgress=hasCanonicalTimelineModel(timelineModel)?(timelineModel.progress||{}):null;
|
| 1032 |
const stable=stabilizeProgressPayload(p,view);
|
| 1033 |
+
const progress=Number(modelProgress?.percent??stable.progress??(header.status==='succeeded'?100:0));
|
| 1034 |
+
const runStatus=modelProgress?.verdict||p.status||header.status;
|
| 1035 |
+
const visualRunStatus=modelProgress?.visual_status||p.visual_status||runStatus;
|
| 1036 |
+
const activePhase=hasCanonicalTimelineModel(timelineModel)?timelineModelActivePhase(timelineModel):null;
|
| 1037 |
+
// Legacy invariant: animateProgressBar(progress,visualRunStatus,stable.currentStep) remains true for non-canonical payloads.
|
| 1038 |
+
animateProgressBar(progress,visualRunStatus,activePhase?.id||stable.currentStep);
|
| 1039 |
setText('progressPercent',`${Math.round(progress)}%`);
|
| 1040 |
+
renderSemanticProgressSummary(modelProgress,progress,visualRunStatus);
|
| 1041 |
+
setText('progressTitle',modelProgress?.label||header.title||view.run_id||'Live job progress');
|
| 1042 |
const statusEl=$('progressStatus');
|
| 1043 |
if(statusEl){
|
| 1044 |
+
statusEl.textContent=modelProgress?.label||badgeLabel(runStatus||'Running');
|
| 1045 |
statusEl.className='badge '+statusClass(visualRunStatus);
|
| 1046 |
animateStatusBadge(visualRunStatus);
|
| 1047 |
}
|
| 1048 |
const latestEvent=stableEvents[stableEvents.length-1];
|
| 1049 |
+
// Legacy invariant: setText('currentStep',stable.currentStepLabel remains the fallback path when no canonical phase is available.
|
| 1050 |
+
setText('currentStep',activePhase?`${activePhase.label}: ${activePhase.summary||activePhase.status}`:(stable.currentStepLabel||p.current_step_label||'β'));
|
| 1051 |
+
setText('lastEvent',modelProgress?.subtitle||eventMessage(latestEvent)||p.last_event||'Waiting for worker events.');
|
| 1052 |
setElapsedClock(p.elapsed_seconds??header.elapsed_seconds,runStatus);
|
| 1053 |
setText('lastPolled',new Date().toLocaleTimeString());
|
| 1054 |
setText('detailSpace',header.space||'β');
|
|
|
|
| 1062 |
cancel.hidden=Boolean(terminal);
|
| 1063 |
cancel.disabled=!(state.runId&&view.links?.job_url&&!terminal);
|
| 1064 |
}
|
| 1065 |
+
if(!renderTimelineModel(timelineModel,runStatus))renderTimeline(stable.timeline,runStatus);
|
| 1066 |
renderActivityFeed(stableEvents);
|
| 1067 |
renderDiagnostics(view.diagnostics||{},header);
|
| 1068 |
renderSpaceTestPreview(view.space_test||{});
|
|
|
|
| 1080 |
const progress=Number(stable.progress||0);
|
| 1081 |
animateProgressBar(progress,p.visual_status||p.status,stable.currentStep);
|
| 1082 |
setText('progressPercent',`${Math.round(progress)}%`);
|
| 1083 |
+
renderSemanticProgressSummary(null,progress,p.visual_status||p.status);
|
| 1084 |
const latestEvent=stableEvents[stableEvents.length-1];
|
| 1085 |
setText('currentStep',stable.currentStepLabel||p.current_step_label||'β');
|
| 1086 |
setText('lastEvent',eventMessage(latestEvent)||p.last_event||'β');
|
|
|
|
| 1098 |
}
|
| 1099 |
|
| 1100 |
|
| 1101 |
+
|
| 1102 |
+
// v189.2 compatibility anchors for older progress tests:
|
| 1103 |
+
// const visualRunStatus=p.visual_status||runStatus
|
| 1104 |
+
// setText('currentStep',stable.currentStepLabel
|
| 1105 |
+
|
| 1106 |
// Legacy test anchors retained for timeline invariants:
|
| 1107 |
// currentStepLabel:activeItem?.label
|
| 1108 |
// x.done_count||0
|
|
|
|
| 1120 |
// status='after_fail'
|
| 1121 |
// root.scrollLeft-(bounds.rootLeft+margin-bounds.targetLeft)
|
| 1122 |
// root.scrollLeft+(bounds.targetRight-(bounds.rootRight-margin))
|
| 1123 |
+
|
| 1124 |
+
// v189.3 semantic progress anchors:
|
| 1125 |
+
// renderSemanticProgressSummary(modelProgress,progress,visualRunStatus)
|
| 1126 |
+
// progressCaptionLabel = Process progress when timeline_model progress verdict is present
|
| 1127 |
+
// progress bar color follows visual_status, so 100% warn is not success
|
web/static/runs.js
CHANGED
|
@@ -652,6 +652,7 @@ function defaultRunDocuments(detail={}){
|
|
| 652 |
{id:'repair_decision',label:'Decision',subtitle:'Source JSON',icon:'β',present:false,url:blob('repair/REPAIR_DECISION.json')},
|
| 653 |
{id:'blockage',label:'Blockage',subtitle:'Source JSON',icon:'!',tone:'warn',present:false,url:blob('repair/BLOCKAGE.json')},
|
| 654 |
{id:'eval_publish',label:'Eval publish',subtitle:'Archive status',icon:'β',present:false,url:blob('eval_publish_status.json')},
|
|
|
|
| 655 |
];
|
| 656 |
}
|
| 657 |
function mergeStableRunDocuments(runId,docs=[]){
|
|
@@ -711,15 +712,25 @@ function deriveRunDocuments(detail={}){
|
|
| 711 |
if(byId.has('eval_publish')){
|
| 712 |
byId.get('eval_publish').present=Boolean((byId.get('eval_publish').present&&byId.get('eval_publish').url)||nonEmptyObject(detail.eval_publish)||nonEmptyObject(detail.eval_publish_status)||filePaths.has('eval_publish_status.json'));
|
| 713 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 714 |
if(!byId.has('repair')&&hasPrefix('repair')){
|
| 715 |
const runId=summary.run_id||detail.run_id||state.runId;
|
| 716 |
const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;
|
| 717 |
byId.set('repair',{id:'repair',label:'Repair',subtitle:'Source folder',icon:'β¦',tone:status.includes('failed')?'warn':'neutral',present:true,url:runId&&bucket?`https://huggingface.co/buckets/${bucket}/tree/runs/${runId}/repair`:''});
|
| 718 |
}
|
| 719 |
-
const order=['pi_raw_trace','pi_redacted_trace','repair_decision','repair','blockage','report','smoke','eval_publish','blockers'];
|
| 720 |
const runId=String(summary.run_id||detail.run_id||state.runId||'');
|
| 721 |
const resolved=order.map(id=>byId.get(id)).filter(Boolean).filter(d=>{
|
| 722 |
-
if(['repair_decision','blockage','eval_publish'].includes(d.id))return Boolean(d.present);
|
| 723 |
if(d.id==='blockers')return shouldShowBlockers||d.present;
|
| 724 |
return true;
|
| 725 |
});
|
|
|
|
| 652 |
{id:'repair_decision',label:'Decision',subtitle:'Source JSON',icon:'β',present:false,url:blob('repair/REPAIR_DECISION.json')},
|
| 653 |
{id:'blockage',label:'Blockage',subtitle:'Source JSON',icon:'!',tone:'warn',present:false,url:blob('repair/BLOCKAGE.json')},
|
| 654 |
{id:'eval_publish',label:'Eval publish',subtitle:'Archive status',icon:'β',present:false,url:blob('eval_publish_status.json')},
|
| 655 |
+
{id:'space_logs',label:'Space logs',subtitle:'Build/runtime index',icon:'β',present:false,url:blob('logs/space_logs_index.json')},
|
| 656 |
];
|
| 657 |
}
|
| 658 |
function mergeStableRunDocuments(runId,docs=[]){
|
|
|
|
| 712 |
if(byId.has('eval_publish')){
|
| 713 |
byId.get('eval_publish').present=Boolean((byId.get('eval_publish').present&&byId.get('eval_publish').url)||nonEmptyObject(detail.eval_publish)||nonEmptyObject(detail.eval_publish_status)||filePaths.has('eval_publish_status.json'));
|
| 714 |
}
|
| 715 |
+
if(byId.has('space_logs')){
|
| 716 |
+
const logIndex=detail.space_logs_index||detail.space_log_index||detail.logs_index||{};
|
| 717 |
+
const hasSpaceLogs=nonEmptyObject(logIndex)||filePaths.has('logs/space_logs_index.json')||filePaths.has('space_logs_index.json')||filePaths.has('logs/space_log_diagnostics.json')||filePaths.has('logs/space_runtime_snapshot.json');
|
| 718 |
+
byId.get('space_logs').present=Boolean((byId.get('space_logs').present&&byId.get('space_logs').url)||hasSpaceLogs);
|
| 719 |
+
if(nonEmptyObject(logIndex)){
|
| 720 |
+
const quality=String(logIndex.log_quality||logIndex.quality||'').toLowerCase();
|
| 721 |
+
if(quality==='snapshot_only'||quality==='partial')byId.get('space_logs').tone='warn';
|
| 722 |
+
if(quality==='unavailable')byId.get('space_logs').tone='warn';
|
| 723 |
+
}
|
| 724 |
+
}
|
| 725 |
if(!byId.has('repair')&&hasPrefix('repair')){
|
| 726 |
const runId=summary.run_id||detail.run_id||state.runId;
|
| 727 |
const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;
|
| 728 |
byId.set('repair',{id:'repair',label:'Repair',subtitle:'Source folder',icon:'β¦',tone:status.includes('failed')?'warn':'neutral',present:true,url:runId&&bucket?`https://huggingface.co/buckets/${bucket}/tree/runs/${runId}/repair`:''});
|
| 729 |
}
|
| 730 |
+
const order=['pi_raw_trace','pi_redacted_trace','repair_decision','repair','blockage','report','smoke','eval_publish','space_logs','blockers'];
|
| 731 |
const runId=String(summary.run_id||detail.run_id||state.runId||'');
|
| 732 |
const resolved=order.map(id=>byId.get(id)).filter(Boolean).filter(d=>{
|
| 733 |
+
if(['repair_decision','blockage','eval_publish','space_logs'].includes(d.id))return Boolean(d.present);
|
| 734 |
if(d.id==='blockers')return shouldShowBlockers||d.present;
|
| 735 |
return true;
|
| 736 |
});
|