Upload 6 files
Browse files- web/static/app.js +2 -2
- web/static/progress.js +77 -4
web/static/app.js
CHANGED
|
@@ -20,7 +20,7 @@ function setBuildModeNew(){const panel=$('buildModePanel');if(panel){panel.class
|
|
| 20 |
function setBuildModeInspecting(runId){const panel=$('buildModePanel');if(panel){panel.classList.add('inspecting');panel.innerHTML=`<strong>Inspecting selected run</strong><p>You are viewing run <code>${escapeHtml(runId||'')}</code>. The build form is unchanged and the active run panel is now tracking it.</p>`}}
|
| 21 |
function resetRunDetails(){
|
| 22 |
const cancel=$('cancelRun');if(cancel)cancel.disabled=true;
|
| 23 |
-
if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;
|
| 24 |
if(typeof activityFeedState!=='undefined'){activityFeedState.showAll=false;activityFeedState.lastItems=[]}
|
| 25 |
for(const [id,value] of [
|
| 26 |
['detailRunId','—'],['detailJobStatus','—'],['detailGateStatus','—'],['detailSpace','—'],['detailHardware','—'],['detailTraces','—'],
|
|
@@ -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;const warn=$('pollWarning');if(warn)warn.hidden=true;if(state.poll)clearInterval(state.poll);saveActiveRun();refreshProgress();state.poll=setInterval(refreshProgress,2500)}
|
| 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)}}
|
|
|
|
| 20 |
function setBuildModeInspecting(runId){const panel=$('buildModePanel');if(panel){panel.classList.add('inspecting');panel.innerHTML=`<strong>Inspecting selected run</strong><p>You are viewing run <code>${escapeHtml(runId||'')}</code>. The build form is unchanged and the active run panel is now tracking it.</p>`}}
|
| 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.showAll=false;activityFeedState.lastItems=[]}
|
| 25 |
for(const [id,value] of [
|
| 26 |
['detailRunId','—'],['detailJobStatus','—'],['detailGateStatus','—'],['detailSpace','—'],['detailHardware','—'],['detailTraces','—'],
|
|
|
|
| 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,2500)}
|
| 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)}}
|
web/static/progress.js
CHANGED
|
@@ -2,6 +2,7 @@ const timelineRenderState={};
|
|
| 2 |
const activityFeedState={showAll:false,lastItems:[]};
|
| 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 elapsedClock={baseSeconds:0,baseAt:0,isRunning:false};
|
| 6 |
|
| 7 |
function normalizeTimelineOverallStatus(status){
|
|
@@ -156,10 +157,81 @@ window.setInterval(()=>{
|
|
| 156 |
setText('elapsed',fmtSeconds(live));
|
| 157 |
},1000);
|
| 158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
function renderRunViewModel(view,p={}){
|
| 160 |
const header=view.header||{};
|
| 161 |
const sm=view.status_model||{};
|
| 162 |
-
const
|
|
|
|
| 163 |
animateProgressBar(progress,header.status||p.status,header.current_phase);
|
| 164 |
setText('progressPercent',`${Math.round(progress)}%`);
|
| 165 |
setText('progressTitle',header.title||view.run_id||'Live job progress');
|
|
@@ -182,7 +254,7 @@ function renderRunViewModel(view,p={}){
|
|
| 182 |
const s=String(header.status||p.status||'').toLowerCase();
|
| 183 |
cancel.disabled=!(state.runId&&view.links?.job_url&&!TERMINAL_STATUSES.has(s));
|
| 184 |
}
|
| 185 |
-
renderTimeline(
|
| 186 |
renderActivityFeed(view.activity||[]);
|
| 187 |
renderDiagnostics(view.diagnostics||{},header);
|
| 188 |
renderSpaceTestPreview(view.space_test||{});
|
|
@@ -193,7 +265,8 @@ function renderProgress(p){
|
|
| 193 |
renderRunViewModel(p.view,p);
|
| 194 |
return;
|
| 195 |
}
|
| 196 |
-
const
|
|
|
|
| 197 |
animateProgressBar(progress,p.status,p.current_step);
|
| 198 |
setText('progressPercent',`${Math.round(progress)}%`);
|
| 199 |
setText('currentStep',p.current_step_label||'—');
|
|
@@ -207,5 +280,5 @@ function renderProgress(p){
|
|
| 207 |
animateStatusBadge(p.status);
|
| 208 |
}
|
| 209 |
setText('lastPolled',new Date().toLocaleTimeString());
|
| 210 |
-
renderTimeline(
|
| 211 |
}
|
|
|
|
| 2 |
const activityFeedState={showAll:false,lastItems:[]};
|
| 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:''};
|
| 6 |
const elapsedClock={baseSeconds:0,baseAt:0,isRunning:false};
|
| 7 |
|
| 8 |
function normalizeTimelineOverallStatus(status){
|
|
|
|
| 157 |
setText('elapsed',fmtSeconds(live));
|
| 158 |
},1000);
|
| 159 |
|
| 160 |
+
function progressIsSuccessStatus(status){
|
| 161 |
+
const s=String(status||'').toLowerCase();
|
| 162 |
+
return ['success','succeeded','done','full_inference_success','full_inference_candidate_health_passed'].includes(s);
|
| 163 |
+
}
|
| 164 |
+
function progressIsTerminalStatus(status){
|
| 165 |
+
const s=String(status||'').toLowerCase();
|
| 166 |
+
return PROGRESS_TERMINAL_STATUSES.has(s);
|
| 167 |
+
}
|
| 168 |
+
function timelineFurthestIndex(items){
|
| 169 |
+
const list=items||[];
|
| 170 |
+
let max=-1;
|
| 171 |
+
list.forEach((item,i)=>{
|
| 172 |
+
const st=String(item.status||'pending').toLowerCase();
|
| 173 |
+
if(!['pending','status-pending','muted','status-muted'].includes(st))max=i;
|
| 174 |
+
});
|
| 175 |
+
return max;
|
| 176 |
+
}
|
| 177 |
+
function cloneTimeline(items){
|
| 178 |
+
return (items||[]).map(x=>({...x}));
|
| 179 |
+
}
|
| 180 |
+
function resetProgressStability(runId=null){
|
| 181 |
+
progressStability.runId=runId;
|
| 182 |
+
progressStability.progress=0;
|
| 183 |
+
progressStability.timeline=null;
|
| 184 |
+
progressStability.furthestIndex=-1;
|
| 185 |
+
progressStability.status='';
|
| 186 |
+
progressMotionState.lastProgress=null;
|
| 187 |
+
progressMotionState.lastStatus=null;
|
| 188 |
+
progressMotionState.lastStep=null;
|
| 189 |
+
const timeline=$('timeline');
|
| 190 |
+
if(timeline)timeline.dataset.signature='';
|
| 191 |
+
}
|
| 192 |
+
function stabilizeProgressPayload(p={},view=null){
|
| 193 |
+
const runId=String(p.run_id||view?.run_id||state.runId||'');
|
| 194 |
+
if(progressStability.runId!==runId)resetProgressStability(runId);
|
| 195 |
+
const status=String(view?.header?.status||p.status||p.state?.status||'').toLowerCase();
|
| 196 |
+
const terminal=progressIsTerminalStatus(status);
|
| 197 |
+
const success=progressIsSuccessStatus(status);
|
| 198 |
+
let rawProgress=Number(p.progress||0);
|
| 199 |
+
let rawTimeline=cloneTimeline(p.timeline||view?.pipeline||[]);
|
| 200 |
+
const rawIndex=timelineFurthestIndex(rawTimeline);
|
| 201 |
+
|
| 202 |
+
if(success){
|
| 203 |
+
progressStability.progress=100;
|
| 204 |
+
progressStability.timeline=rawTimeline;
|
| 205 |
+
progressStability.furthestIndex=Math.max(progressStability.furthestIndex,rawIndex);
|
| 206 |
+
}else if(terminal){
|
| 207 |
+
progressStability.progress=Math.max(progressStability.progress,rawProgress);
|
| 208 |
+
if(rawIndex>=progressStability.furthestIndex||!progressStability.timeline){
|
| 209 |
+
progressStability.timeline=rawTimeline;
|
| 210 |
+
progressStability.furthestIndex=rawIndex;
|
| 211 |
+
}else{
|
| 212 |
+
rawTimeline=cloneTimeline(progressStability.timeline);
|
| 213 |
+
}
|
| 214 |
+
}else{
|
| 215 |
+
if(rawProgress>=progressStability.progress){
|
| 216 |
+
progressStability.progress=rawProgress;
|
| 217 |
+
}else{
|
| 218 |
+
rawProgress=progressStability.progress;
|
| 219 |
+
}
|
| 220 |
+
if(rawIndex>=progressStability.furthestIndex||!progressStability.timeline){
|
| 221 |
+
progressStability.timeline=rawTimeline;
|
| 222 |
+
progressStability.furthestIndex=rawIndex;
|
| 223 |
+
}else{
|
| 224 |
+
rawTimeline=cloneTimeline(progressStability.timeline);
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
progressStability.status=status;
|
| 228 |
+
return {progress:success?100:Math.max(rawProgress,progressStability.progress),timeline:rawTimeline,status};
|
| 229 |
+
}
|
| 230 |
function renderRunViewModel(view,p={}){
|
| 231 |
const header=view.header||{};
|
| 232 |
const sm=view.status_model||{};
|
| 233 |
+
const stable=stabilizeProgressPayload(p,view);
|
| 234 |
+
const progress=Number(stable.progress??(header.status==='succeeded'?100:0));
|
| 235 |
animateProgressBar(progress,header.status||p.status,header.current_phase);
|
| 236 |
setText('progressPercent',`${Math.round(progress)}%`);
|
| 237 |
setText('progressTitle',header.title||view.run_id||'Live job progress');
|
|
|
|
| 254 |
const s=String(header.status||p.status||'').toLowerCase();
|
| 255 |
cancel.disabled=!(state.runId&&view.links?.job_url&&!TERMINAL_STATUSES.has(s));
|
| 256 |
}
|
| 257 |
+
renderTimeline(stable.timeline,header.status||p.status);
|
| 258 |
renderActivityFeed(view.activity||[]);
|
| 259 |
renderDiagnostics(view.diagnostics||{},header);
|
| 260 |
renderSpaceTestPreview(view.space_test||{});
|
|
|
|
| 265 |
renderRunViewModel(p.view,p);
|
| 266 |
return;
|
| 267 |
}
|
| 268 |
+
const stable=stabilizeProgressPayload(p,null);
|
| 269 |
+
const progress=Number(stable.progress||0);
|
| 270 |
animateProgressBar(progress,p.status,p.current_step);
|
| 271 |
setText('progressPercent',`${Math.round(progress)}%`);
|
| 272 |
setText('currentStep',p.current_step_label||'—');
|
|
|
|
| 280 |
animateStatusBadge(p.status);
|
| 281 |
}
|
| 282 |
setText('lastPolled',new Date().toLocaleTimeString());
|
| 283 |
+
renderTimeline(stable.timeline,p.status);
|
| 284 |
}
|