Upload 6 files
Browse files- web/static/app.css +4 -0
- web/static/app.js +6 -6
- web/static/progress.js +26 -25
web/static/app.css
CHANGED
|
@@ -379,3 +379,7 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 379 |
max-height:235px;
|
| 380 |
overflow-y:auto;
|
| 381 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
max-height:235px;
|
| 380 |
overflow-y:auto;
|
| 381 |
}
|
| 382 |
+
|
| 383 |
+
/* v70: simpler events list and visible-only timeline scrolling */
|
| 384 |
+
.activity-feed.show-all{max-height:184px;overflow-y:auto;scrollbar-width:thin}
|
| 385 |
+
.timeline{scroll-behavior:smooth}
|
web/static/app.js
CHANGED
|
@@ -21,7 +21,7 @@ function setBuildModeInspecting(runId){const panel=$('buildModePanel');if(panel)
|
|
| 21 |
function resetRunDetails(){
|
| 22 |
const cancel=$('cancelRun');if(cancel)cancel.disabled=true;
|
| 23 |
if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;if(typeof resetProgressStability==='function')resetProgressStability(null);
|
| 24 |
-
if(typeof activityFeedState!=='undefined'){activityFeedState.
|
| 25 |
for(const [id,value] of [
|
| 26 |
['detailRunId','—'],['detailJobStatus','—'],['detailGateStatus','—'],['detailSpace','—'],['detailHardware','—'],['detailTraces','—'],
|
| 27 |
['homeRunDetailStatus','No run'],['selectedRunId','—'],['selectedModelId','—'],['selectedModelShort','—'],['selectedTargetSpace','—'],
|
|
@@ -37,7 +37,7 @@ function resetRunDetails(){
|
|
| 37 |
const manual=$('manualActionPanel');if(manual)manual.hidden=true;
|
| 38 |
const warning=$('pollWarning');if(warning)warning.hidden=true;
|
| 39 |
const activity=$('activityFeed');if(activity){activity.dataset.signature='';activity.classList.remove('show-all');activity.innerHTML='<div class="empty">No activity yet.</div>'}
|
| 40 |
-
|
| 41 |
const events=$('eventsPanel');if(events)events.textContent='No events yet.';
|
| 42 |
const artifacts=$('artifactList');if(artifacts)artifacts.textContent='No artifacts indexed yet.';
|
| 43 |
const blockers=$('selectedBlockers');if(blockers){blockers.hidden=true;blockers.innerHTML=''}
|
|
@@ -45,7 +45,7 @@ function resetRunDetails(){
|
|
| 45 |
renderTimeline(DEFAULT_TIMELINE,'idle');
|
| 46 |
} function prepareNewBuild(){if(state.poll){clearInterval(state.poll);state.poll=null}state.runId=null;state.selectedRunDetail=null;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')}
|
| 47 |
function renderLaunchResult(result={}){const panel=$('launchResultPanel');if(panel)panel.hidden=false;setText('launchRunId',result.run_id||'—');setText('launchStatus',result.status||'running');setText('launchSpace',result.target_space||'—');setText('launchHardware',result.selected_hardware||result.preferred_space_hardware||'—');const links=normalizeLinks({...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});setLink('launchOpenJob',links.job_url);setLink('launchOpenSpace',links.target_space_url);setLink('launchOpenSettings',links.target_space_settings_url||links.target_space_url&&links.target_space_url+'/settings');setLink('launchOpenArtifacts',links.artifacts_url)}function setActiveRun(result){state.runId=result.run_id;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||'—');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});renderLaunchResult(result);saveActiveRun();startPolling(result.run_id)}
|
| 48 |
-
function startPolling(runId){state.runId=runId;state.pollErrors=0;state.lastJobLogPoll=0;if(typeof resetProgressStability==='function')resetProgressStability(runId);const warn=$('pollWarning');if(warn)warn.hidden=true;if(state.poll)clearInterval(state.poll);saveActiveRun();refreshProgress();state.poll=setInterval(refreshProgress,
|
| 49 |
async function bootstrapRunRecovery(){return restoreActiveRun()}
|
| 50 |
async function restoreActiveRun(){try{const resumable=await apiGet(`/api/runs/resumable?bucket_name=${encodeURIComponent(state.bucketName)}`);if(resumable?.run?.run_id){state.runId=resumable.run.run_id;if(resumable.bucket_source)state.bucketSource=resumable.bucket_source;setBuildModeInspecting(state.runId);if(resumable.view)renderRunViewModel(resumable.view,{progress:0,events:[]});setQuickLinks(resumable.view?.links||{});startPolling(state.runId);return true}}catch(e){console.warn('resumable lookup failed',e)}try{const raw=localStorage.getItem('asf.activeRun');if(!raw)return false;const saved=JSON.parse(raw);if(saved?.bucketName)state.bucketName=saved.bucketName;if(saved?.runId){state.runId=saved.runId;setText('detailRunId',saved.runId);setQuickLinks(saved.links||{});setBuildModeInspecting(saved.runId);startPolling(saved.runId);return true}}catch(_){}return false}
|
| 51 |
async function loadAppInfo(){setOperation('Checking sign-in and run storage…','loading');try{const me=await apiGet('/api/me');state.user=me;setText('userChip','@'+me.username);const userChip=$('userChip');if(userChip)userChip.href=`https://huggingface.co/${me.username}`;const logout=$('logoutLink');if(logout){logout.hidden=false;logout.href=me.logout_url||'/logout'}state.bucketSource=me.username+'/'+state.bucketName;setText('bucketInlineLabel',state.bucketSource);renderAuthWarnings(me);await checkBucket(false);await loadRuns(true);clearSavedRun();if(!state.bucketReady)setOperation('Create your private run bucket before launching builds.','warning');else setOperation('')}catch(e){state.user=null;setText('userChip','Sign in');const userChip=$('userChip');if(userChip)userChip.href='/oauth/huggingface/login';const logout=$('logoutLink');if(logout)logout.hidden=true;renderSignedOut();setBucketReady(false,'Sign in with Hugging Face first.');showMessage('Please sign in with Hugging Face, then refresh this page.',true)}}
|
|
@@ -57,7 +57,7 @@ function renderManualAction(p={}){const panel=$('manualActionPanel');if(!panel)r
|
|
| 57 |
function prepareValidationFromActiveRun(){const detail=state.selectedRunDetail;const summary=detail?.summary||{};const target=summary.target_space||$('detailSpace')?.textContent||'';if(target&&target!=='—')setInputValue('validateSpace',target);const expected=summary.expected_output_type||detail?.generation_smoke?.expected_output_type||formValue('buildExpectedOutput')||'image';setInputValue('expectedOutput',expected);setInputValue('validateApi','/generate');switchView('validate');showMessage(`Prepared validation form for ${target||'selected Space'}.`,'info')}
|
| 58 |
async function launchBuild(){const formError=validateBuildForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching build Job…');setButtonBusy('launchBuild',true,'Launching…');try{setText('hardwarePlan',hardwarePlanLabel());const payload={bucket_name:state.bucketName,run_id:formValue('runId')||undefined,model_id:formValue('modelId'),target_space_name:formValue('targetSpace'),pi_model:formValue('piModel'),implementation_mode:formValue('implementationMode'),preferred_space_hardware:effectivePreferredHardware(),fallback_space_hardware:formValue('fallbackHardware'),allow_fixed_gpu_fallback:$('allowFallback')?.checked??true,expected_output_type:formValue('buildExpectedOutput')};const result=await apiPost('/api/build',payload);setActiveRun(result);showMessage(`Build launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchBuild',false)}}
|
| 59 |
async function launchValidate(){const formError=validateValidationForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching validation Job…');setButtonBusy('launchValidate',true,'Launching…');try{const payload={bucket_name:state.bucketName,target_space_id:formValue('validateSpace'),api_name:formValue('validateApi')||'/generate',expected_output_type:formValue('expectedOutput')||'image',test_args_json:formValue('testArgs')||'[]',test_kwargs_json:formValue('testKwargs')||'{}',live_timeout_seconds:Number(formValue('validateTimeout')||1800)};const result=await apiPost('/api/validate',payload);setActiveRun(result);showMessage(`Validation launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchValidate',false)}}
|
| 60 |
-
async function refreshProgress(){if(!state.runId)return;try{const now=Date.now();const includeLogs=!state.lastJobLogPoll||now-state.lastJobLogPoll>
|
| 61 |
function renderEvents(events){const root=$('eventsPanel');if(!root)return;root.textContent=(events||[]).slice(-12).map(e=>`${e.ts||''} ${e.step||''} ${e.status||''} ${e.message||''}`).join('\n')||'No events yet. The run may still be scheduling.'}
|
| 62 |
async function cancelActiveRun(){
|
| 63 |
if(!state.runId){showMessage('Select a running run before cancelling.', 'warning');return}
|
|
@@ -75,11 +75,11 @@ async function cancelActiveRun(){
|
|
| 75 |
setButtonBusy('cancelRun',false);
|
| 76 |
}
|
| 77 |
}
|
| 78 |
-
async function resumeLatestRun(){try{const data=await apiGet(`/api/runs/resumable?bucket_name=${encodeURIComponent(state.bucketName)}`);if(!data.run){showMessage('No
|
| 79 |
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('');const warn=$('pollWarning');if(warn)warn.hidden=true;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'})}
|
| 80 |
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')))}
|
| 81 |
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)})}
|
| 82 |
-
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);$('homeValidateBtn')?.addEventListener('click',prepareValidationFromActiveRun);$('selectedValidateBtn')?.addEventListener('click',prepareValidationFromActiveRun);$('checkBucket')?.addEventListener('click',()=>checkBucket(true));$('bucketStatusPill')?.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);
|
| 83 |
bindNavigation();bindActions();bindRunFilters();setText('hardwarePlan',hardwarePlanLabel());setBuildModeNew();renderTimeline(DEFAULT_TIMELINE);loadAppInfo();
|
| 84 |
|
| 85 |
// Pass 47 intentionally removed automatic switchView('progress') after launch and run selection.
|
|
|
|
| 21 |
function resetRunDetails(){
|
| 22 |
const cancel=$('cancelRun');if(cancel)cancel.disabled=true;
|
| 23 |
if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;if(typeof resetProgressStability==='function')resetProgressStability(null);
|
| 24 |
+
if(typeof activityFeedState!=='undefined'){activityFeedState.lastItems=[];activityFeedState.runId=null}
|
| 25 |
for(const [id,value] of [
|
| 26 |
['detailRunId','—'],['detailJobStatus','—'],['detailGateStatus','—'],['detailSpace','—'],['detailHardware','—'],['detailTraces','—'],
|
| 27 |
['homeRunDetailStatus','No run'],['selectedRunId','—'],['selectedModelId','—'],['selectedModelShort','—'],['selectedTargetSpace','—'],
|
|
|
|
| 37 |
const manual=$('manualActionPanel');if(manual)manual.hidden=true;
|
| 38 |
const warning=$('pollWarning');if(warning)warning.hidden=true;
|
| 39 |
const activity=$('activityFeed');if(activity){activity.dataset.signature='';activity.classList.remove('show-all');activity.innerHTML='<div class="empty">No activity yet.</div>'}
|
| 40 |
+
|
| 41 |
const events=$('eventsPanel');if(events)events.textContent='No events yet.';
|
| 42 |
const artifacts=$('artifactList');if(artifacts)artifacts.textContent='No artifacts indexed yet.';
|
| 43 |
const blockers=$('selectedBlockers');if(blockers){blockers.hidden=true;blockers.innerHTML=''}
|
|
|
|
| 45 |
renderTimeline(DEFAULT_TIMELINE,'idle');
|
| 46 |
} function prepareNewBuild(){if(state.poll){clearInterval(state.poll);state.poll=null}state.runId=null;state.selectedRunDetail=null;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')}
|
| 47 |
function renderLaunchResult(result={}){const panel=$('launchResultPanel');if(panel)panel.hidden=false;setText('launchRunId',result.run_id||'—');setText('launchStatus',result.status||'running');setText('launchSpace',result.target_space||'—');setText('launchHardware',result.selected_hardware||result.preferred_space_hardware||'—');const links=normalizeLinks({...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});setLink('launchOpenJob',links.job_url);setLink('launchOpenSpace',links.target_space_url);setLink('launchOpenSettings',links.target_space_settings_url||links.target_space_url&&links.target_space_url+'/settings');setLink('launchOpenArtifacts',links.artifacts_url)}function setActiveRun(result){state.runId=result.run_id;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||'—');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});renderLaunchResult(result);saveActiveRun();startPolling(result.run_id)}
|
| 48 |
+
function startPolling(runId){state.runId=runId;state.pollErrors=0;state.lastJobLogPoll=0;if(typeof resetProgressStability==='function')resetProgressStability(runId);const warn=$('pollWarning');if(warn)warn.hidden=true;if(state.poll)clearInterval(state.poll);saveActiveRun();refreshProgress();state.poll=setInterval(refreshProgress,2000)}
|
| 49 |
async function bootstrapRunRecovery(){return restoreActiveRun()}
|
| 50 |
async function restoreActiveRun(){try{const resumable=await apiGet(`/api/runs/resumable?bucket_name=${encodeURIComponent(state.bucketName)}`);if(resumable?.run?.run_id){state.runId=resumable.run.run_id;if(resumable.bucket_source)state.bucketSource=resumable.bucket_source;setBuildModeInspecting(state.runId);if(resumable.view)renderRunViewModel(resumable.view,{progress:0,events:[]});setQuickLinks(resumable.view?.links||{});startPolling(state.runId);return true}}catch(e){console.warn('resumable lookup failed',e)}try{const raw=localStorage.getItem('asf.activeRun');if(!raw)return false;const saved=JSON.parse(raw);if(saved?.bucketName)state.bucketName=saved.bucketName;if(saved?.runId){state.runId=saved.runId;setText('detailRunId',saved.runId);setQuickLinks(saved.links||{});setBuildModeInspecting(saved.runId);startPolling(saved.runId);return true}}catch(_){}return false}
|
| 51 |
async function loadAppInfo(){setOperation('Checking sign-in and run storage…','loading');try{const me=await apiGet('/api/me');state.user=me;setText('userChip','@'+me.username);const userChip=$('userChip');if(userChip)userChip.href=`https://huggingface.co/${me.username}`;const logout=$('logoutLink');if(logout){logout.hidden=false;logout.href=me.logout_url||'/logout'}state.bucketSource=me.username+'/'+state.bucketName;setText('bucketInlineLabel',state.bucketSource);renderAuthWarnings(me);await checkBucket(false);await loadRuns(true);clearSavedRun();if(!state.bucketReady)setOperation('Create your private run bucket before launching builds.','warning');else setOperation('')}catch(e){state.user=null;setText('userChip','Sign in');const userChip=$('userChip');if(userChip)userChip.href='/oauth/huggingface/login';const logout=$('logoutLink');if(logout)logout.hidden=true;renderSignedOut();setBucketReady(false,'Sign in with Hugging Face first.');showMessage('Please sign in with Hugging Face, then refresh this page.',true)}}
|
|
|
|
| 57 |
function prepareValidationFromActiveRun(){const detail=state.selectedRunDetail;const summary=detail?.summary||{};const target=summary.target_space||$('detailSpace')?.textContent||'';if(target&&target!=='—')setInputValue('validateSpace',target);const expected=summary.expected_output_type||detail?.generation_smoke?.expected_output_type||formValue('buildExpectedOutput')||'image';setInputValue('expectedOutput',expected);setInputValue('validateApi','/generate');switchView('validate');showMessage(`Prepared validation form for ${target||'selected Space'}.`,'info')}
|
| 58 |
async function launchBuild(){const formError=validateBuildForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching build Job…');setButtonBusy('launchBuild',true,'Launching…');try{setText('hardwarePlan',hardwarePlanLabel());const payload={bucket_name:state.bucketName,run_id:formValue('runId')||undefined,model_id:formValue('modelId'),target_space_name:formValue('targetSpace'),pi_model:formValue('piModel'),implementation_mode:formValue('implementationMode'),preferred_space_hardware:effectivePreferredHardware(),fallback_space_hardware:formValue('fallbackHardware'),allow_fixed_gpu_fallback:$('allowFallback')?.checked??true,expected_output_type:formValue('buildExpectedOutput')};const result=await apiPost('/api/build',payload);setActiveRun(result);showMessage(`Build launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchBuild',false)}}
|
| 59 |
async function launchValidate(){const formError=validateValidationForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching validation Job…');setButtonBusy('launchValidate',true,'Launching…');try{const payload={bucket_name:state.bucketName,target_space_id:formValue('validateSpace'),api_name:formValue('validateApi')||'/generate',expected_output_type:formValue('expectedOutput')||'image',test_args_json:formValue('testArgs')||'[]',test_kwargs_json:formValue('testKwargs')||'{}',live_timeout_seconds:Number(formValue('validateTimeout')||1800)};const result=await apiPost('/api/validate',payload);setActiveRun(result);showMessage(`Validation launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchValidate',false)}}
|
| 60 |
+
async function refreshProgress(){if(!state.runId||state.progressPollInFlight)return;state.progressPollInFlight=true;try{const now=Date.now();const includeLogs=!state.lastJobLogPoll||now-state.lastJobLogPoll>8000;if(includeLogs)state.lastJobLogPoll=now;const p=await apiGet(`/api/runs/${encodeURIComponent(state.runId)}/progress?bucket_name=${encodeURIComponent(state.bucketName)}${includeLogs?'&include_job_logs=1':''}`);state.pollErrors=0;const warn=$('pollWarning');if(warn)warn.hidden=true;if(p.bucket_source)state.bucketSource=p.bucket_source;renderProgress(p);renderEvents(p.events||[]);renderMetrics(p);if(p.view){setText('detailRunId',p.view.run_id||p.run_id);setText('selectedModelId',p.view.header?.model||p.summary?.model_id||'—')}else{setText('detailRunId',p.run_id||state.runId)}if(p.state){setText('detailSpace',p.state.target_space||p.summary?.target_space||'—');setText('detailHardware',p.state.selected_hardware||p.summary?.selected_hardware||'—')}setText('detailJobStatus',p.view?.header?.raw_status||p.state?.status||p.status||'running');setText('detailGateStatus',p.inference_gate?.status||p.summary?.gate_status||'—');setText('detailTraces',Array.isArray(p.events)?String(p.events.length)+' events':'—');setText('homeRunDetailStatus',p.view?.header?.status_label||p.state?.status||p.status||'running');setQuickLinks({...p.links,...p.view?.links,bucket_source:p.bucket_source,run_id:p.run_id});saveActiveRun();const status=String(p.view?.header?.status||p.status||p.state?.status||'').toLowerCase();if(TERMINAL_STATUSES.has(status)&&state.poll){clearInterval(state.poll);state.poll=null;setOperation(`Run finished with status: ${badgeLabel(status)}`,status.includes('success')||status.includes('succeed')?'success':status.includes('failed')||status.includes('blocker')?'error':'warning')}if(now-(state.lastRunsRefreshFromPoll||0)>15000){state.lastRunsRefreshFromPoll=now;loadRuns(false)}}catch(e){state.pollErrors+=1;console.warn(e);const warn=$('pollWarning');if(warn){warn.textContent=`Progress polling failed ${state.pollErrors} time${state.pollErrors===1?'':'s'}. The Job may still be running; retrying automatically…`;warn.hidden=false}}finally{state.progressPollInFlight=false}} function renderMetrics(p){const root=$('metricsList');if(!root)return;const smoke=p.generation_smoke||{};const gate=p.inference_gate||{};const health=(gate.implementation_signals&&gate.implementation_signals.health_passed)||smoke.health_passed;const smokeOk=smoke.ok||smoke.status==='success';const latency=smoke.latency_seconds||p.summary?.latency_seconds;const duration=smoke.recommended_zerogpu_duration_seconds||smoke.recommended_duration_seconds;root.innerHTML=`<li><span>/health</span><strong class="${health?'metric-ok':''}">${health?'passed':'pending'}</strong></li><li><span>/generate smoke test</span><strong class="${smokeOk?'metric-ok':'metric-warn'}">${smokeOk?'passed':(smoke.status||'pending')}</strong></li><li><span>Latency</span><strong>${latency?fmtSeconds(Math.round(latency)):'—'}</strong></li><li><span>Recommended ZeroGPU duration</span><strong>${duration?fmtSeconds(Math.round(duration)):'—'}</strong></li>`}
|
| 61 |
function renderEvents(events){const root=$('eventsPanel');if(!root)return;root.textContent=(events||[]).slice(-12).map(e=>`${e.ts||''} ${e.step||''} ${e.status||''} ${e.message||''}`).join('\n')||'No events yet. The run may still be scheduling.'}
|
| 62 |
async function cancelActiveRun(){
|
| 63 |
if(!state.runId){showMessage('Select a running run before cancelling.', 'warning');return}
|
|
|
|
| 75 |
setButtonBusy('cancelRun',false);
|
| 76 |
}
|
| 77 |
}
|
| 78 |
+
async function resumeLatestRun(){setButtonBusy('resumeLatestRun',true,'Resuming…');try{await loadRuns(true);const data=await apiGet(`/api/runs/resumable?bucket_name=${encodeURIComponent(state.bucketName)}`);if(!data.run){showMessage('No latest run found in this bucket.','warning');return}state.runId=data.run.run_id;if(data.bucket_source)state.bucketSource=data.bucket_source;setText('detailRunId',state.runId);setBuildModeInspecting(state.runId);setQuickLinks(data.view?.links||{});if(data.view)renderRunViewModel(data.view,{run_id:state.runId,progress:data.view.header?.status==='succeeded'?100:0,events:data.view.activity||[]});startPolling(state.runId);showMessage(`Resumed ${state.runId}`,'success')}catch(e){showMessage(e.message,true)}finally{setButtonBusy('resumeLatestRun',false)}}
|
| 79 |
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('');const warn=$('pollWarning');if(warn)warn.hidden=true;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'})}
|
| 80 |
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')))}
|
| 81 |
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)})}
|
| 82 |
+
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);$('homeValidateBtn')?.addEventListener('click',prepareValidationFromActiveRun);$('selectedValidateBtn')?.addEventListener('click',prepareValidationFromActiveRun);$('checkBucket')?.addEventListener('click',()=>checkBucket(true));$('bucketStatusPill')?.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())))}
|
| 83 |
bindNavigation();bindActions();bindRunFilters();setText('hardwarePlan',hardwarePlanLabel());setBuildModeNew();renderTimeline(DEFAULT_TIMELINE);loadAppInfo();
|
| 84 |
|
| 85 |
// Pass 47 intentionally removed automatic switchView('progress') after launch and run selection.
|
web/static/progress.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
const timelineRenderState={};
|
| 2 |
-
const activityFeedState={
|
| 3 |
const PROGRESS_TERMINAL_STATUSES=new Set(['done','success','failed','full_inference_success','full_inference_candidate_health_passed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','health_only','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled']);
|
| 4 |
const progressMotionState={lastProgress:null,lastStatus:null,lastStep:null};
|
| 5 |
const progressStability={runId:null,progress:0,timeline:null,furthestIndex:-1,status:'',currentStep:null};
|
|
@@ -48,9 +48,7 @@ function mergeStableEvents(runId,items){
|
|
| 48 |
if(activityFeedState.runId!==runId){
|
| 49 |
activityFeedState.runId=runId;
|
| 50 |
activityFeedState.lastItems=[];
|
| 51 |
-
|
| 52 |
-
const btn=$('viewAllEvents');
|
| 53 |
-
if(btn)btn.textContent='View all events';
|
| 54 |
}
|
| 55 |
const byKey=new Map();
|
| 56 |
[...activityFeedState.lastItems,...incoming].forEach(event=>{
|
|
@@ -74,17 +72,25 @@ function activeTimelineItem(items=[]){
|
|
| 74 |
function eventMessage(event){
|
| 75 |
return event?.message||event?.step||'Waiting for worker events.';
|
| 76 |
}
|
| 77 |
-
function
|
| 78 |
const root=$('timeline');
|
| 79 |
if(!root||!stepKey)return;
|
| 80 |
-
if(root.dataset.centeredStep===stepKey)return;
|
| 81 |
const safeStep=(typeof CSS!=='undefined'&&CSS.escape)?CSS.escape(stepKey):stepKey.replace(/"/g,'\\"');
|
| 82 |
const target=root.querySelector(`[data-step="${safeStep}"]`);
|
| 83 |
if(!target)return;
|
| 84 |
-
|
| 85 |
-
const
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
}
|
| 89 |
function renderTimeline(items,overallStatus=''){
|
| 90 |
const root=$('timeline');
|
|
@@ -92,7 +98,7 @@ function renderTimeline(items,overallStatus=''){
|
|
| 92 |
const list=items||[];
|
| 93 |
const currentKey=activeTimelineStepKey(list);
|
| 94 |
const signature=list.map((x,i)=>`${timelineStepKey(x,i)}:${timelineStatus(x.status,overallStatus)}:${x.label||''}:${overallStatus||''}`).join('|');
|
| 95 |
-
if(root.dataset.signature===signature){
|
| 96 |
root.dataset.signature=signature;
|
| 97 |
const nextState={};
|
| 98 |
root.innerHTML=list.map((x,i)=>{
|
|
@@ -112,30 +118,25 @@ function renderTimeline(items,overallStatus=''){
|
|
| 112 |
const dot=step.querySelector('.step-dot');
|
| 113 |
animateClass(dot,'dot-pop',700);
|
| 114 |
});
|
| 115 |
-
|
| 116 |
}
|
| 117 |
-
function renderActivityFeed(items
|
| 118 |
const root=$('activityFeed');
|
| 119 |
if(!root)return;
|
| 120 |
const list=mergeStableEvents(String(state.runId||''),items||[]);
|
| 121 |
-
|
| 122 |
-
const visible=
|
| 123 |
-
const signature=`${activityFeedState.showAll?'all':'compact'}|`+visible.map(item=>`${item.ts||''}:${item.step||''}:${item.status||''}:${item.message||''}`).join('|');
|
| 124 |
if(root.dataset.signature===signature)return;
|
| 125 |
root.dataset.signature=signature;
|
|
|
|
| 126 |
if(!list.length){
|
| 127 |
root.innerHTML='<div class="empty">No activity yet. Waiting for the first worker event.</div>';
|
| 128 |
return;
|
| 129 |
}
|
| 130 |
-
root.
|
| 131 |
-
root.
|
| 132 |
-
}
|
| 133 |
-
function toggleAllEvents(){
|
| 134 |
-
activityFeedState.showAll=!activityFeedState.showAll;
|
| 135 |
-
const btn=$('viewAllEvents');
|
| 136 |
-
if(btn)btn.textContent=activityFeedState.showAll?'Show latest 3':'View all events';
|
| 137 |
-
renderActivityFeed(activityFeedState.lastItems,activityFeedState.showAll);
|
| 138 |
}
|
|
|
|
| 139 |
function renderDiagnostics(d={},header={}){
|
| 140 |
setText('diagBuildStatus',badgeLabel(d.build_status||'pending'));
|
| 141 |
setText('diagBuildStep',header.current_phase_label||'Waiting for run data');
|
|
@@ -235,7 +236,7 @@ function resetProgressStability(runId=null){
|
|
| 235 |
progressMotionState.lastStatus=null;
|
| 236 |
progressMotionState.lastStep=null;
|
| 237 |
const timeline=$('timeline');
|
| 238 |
-
if(timeline){timeline.dataset.signature='';timeline.dataset.
|
| 239 |
}
|
| 240 |
function stabilizeProgressPayload(p={},view=null){
|
| 241 |
const runId=String(p.run_id||view?.run_id||state.runId||'');
|
|
|
|
| 1 |
const timelineRenderState={};
|
| 2 |
+
const activityFeedState={lastItems:[],runId:null};
|
| 3 |
const PROGRESS_TERMINAL_STATUSES=new Set(['done','success','failed','full_inference_success','full_inference_candidate_health_passed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','health_only','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled']);
|
| 4 |
const progressMotionState={lastProgress:null,lastStatus:null,lastStep:null};
|
| 5 |
const progressStability={runId:null,progress:0,timeline:null,furthestIndex:-1,status:'',currentStep:null};
|
|
|
|
| 48 |
if(activityFeedState.runId!==runId){
|
| 49 |
activityFeedState.runId=runId;
|
| 50 |
activityFeedState.lastItems=[];
|
| 51 |
+
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
const byKey=new Map();
|
| 54 |
[...activityFeedState.lastItems,...incoming].forEach(event=>{
|
|
|
|
| 72 |
function eventMessage(event){
|
| 73 |
return event?.message||event?.step||'Waiting for worker events.';
|
| 74 |
}
|
| 75 |
+
function ensureTimelineStepVisible(stepKey){
|
| 76 |
const root=$('timeline');
|
| 77 |
if(!root||!stepKey)return;
|
|
|
|
| 78 |
const safeStep=(typeof CSS!=='undefined'&&CSS.escape)?CSS.escape(stepKey):stepKey.replace(/"/g,'\\"');
|
| 79 |
const target=root.querySelector(`[data-step="${safeStep}"]`);
|
| 80 |
if(!target)return;
|
| 81 |
+
const margin=18;
|
| 82 |
+
const visibleLeft=root.scrollLeft;
|
| 83 |
+
const visibleRight=visibleLeft+root.clientWidth;
|
| 84 |
+
const targetLeft=target.offsetLeft;
|
| 85 |
+
const targetRight=targetLeft+target.offsetWidth;
|
| 86 |
+
let nextLeft=null;
|
| 87 |
+
if(targetLeft<visibleLeft+margin)nextLeft=Math.max(0,targetLeft-margin);
|
| 88 |
+
else if(targetRight>visibleRight-margin)nextLeft=Math.max(0,targetRight-root.clientWidth+margin);
|
| 89 |
+
if(nextLeft!==null){
|
| 90 |
+
try{root.scrollTo({left:nextLeft,behavior:'smooth'});}
|
| 91 |
+
catch(_){root.scrollLeft=nextLeft;}
|
| 92 |
+
}
|
| 93 |
+
root.dataset.visibleStep=stepKey;
|
| 94 |
}
|
| 95 |
function renderTimeline(items,overallStatus=''){
|
| 96 |
const root=$('timeline');
|
|
|
|
| 98 |
const list=items||[];
|
| 99 |
const currentKey=activeTimelineStepKey(list);
|
| 100 |
const signature=list.map((x,i)=>`${timelineStepKey(x,i)}:${timelineStatus(x.status,overallStatus)}:${x.label||''}:${overallStatus||''}`).join('|');
|
| 101 |
+
if(root.dataset.signature===signature){ensureTimelineStepVisible(currentKey);return;}
|
| 102 |
root.dataset.signature=signature;
|
| 103 |
const nextState={};
|
| 104 |
root.innerHTML=list.map((x,i)=>{
|
|
|
|
| 118 |
const dot=step.querySelector('.step-dot');
|
| 119 |
animateClass(dot,'dot-pop',700);
|
| 120 |
});
|
| 121 |
+
ensureTimelineStepVisible(currentKey);
|
| 122 |
}
|
| 123 |
+
function renderActivityFeed(items){
|
| 124 |
const root=$('activityFeed');
|
| 125 |
if(!root)return;
|
| 126 |
const list=mergeStableEvents(String(state.runId||''),items||[]);
|
| 127 |
+
const visible=[...list].reverse();
|
| 128 |
+
const signature=visible.map(item=>`${item.ts||''}:${item.step||''}:${item.status||''}:${item.message||''}`).join('|');
|
|
|
|
| 129 |
if(root.dataset.signature===signature)return;
|
| 130 |
root.dataset.signature=signature;
|
| 131 |
+
root.classList.add('show-all');
|
| 132 |
if(!list.length){
|
| 133 |
root.innerHTML='<div class="empty">No activity yet. Waiting for the first worker event.</div>';
|
| 134 |
return;
|
| 135 |
}
|
| 136 |
+
root.innerHTML=visible.map(item=>`<div class="activity-row ${statusClass(item.severity||item.status)}"><time>${escapeHtml(formatTime(item.ts))}</time><span class="activity-dot"></span><div><strong>${escapeHtml(item.message||'Event received')}</strong><p>${escapeHtml(item.step||'event')} · ${escapeHtml(item.status||'')}</p></div><em>${escapeHtml(item.agent||'Factory Agent')}</em></div>`).join('');
|
| 137 |
+
root.scrollTop=0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
}
|
| 139 |
+
|
| 140 |
function renderDiagnostics(d={},header={}){
|
| 141 |
setText('diagBuildStatus',badgeLabel(d.build_status||'pending'));
|
| 142 |
setText('diagBuildStep',header.current_phase_label||'Waiting for run data');
|
|
|
|
| 236 |
progressMotionState.lastStatus=null;
|
| 237 |
progressMotionState.lastStep=null;
|
| 238 |
const timeline=$('timeline');
|
| 239 |
+
if(timeline){timeline.dataset.signature='';timeline.dataset.visibleStep='';}
|
| 240 |
}
|
| 241 |
function stabilizeProgressPayload(p={},view=null){
|
| 242 |
const runId=String(p.run_id||view?.run_id||state.runId||'');
|