fffiloni commited on
Commit
690ac59
·
verified ·
1 Parent(s): a697627

Upload 6 files

Browse files
Files changed (2) hide show
  1. web/static/app.js +2 -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 progress=Number(p.progress??(header.status==='succeeded'?100:0));
 
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(p.timeline||view.pipeline||[],header.status||p.status);
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 progress=Number(p.progress||0);
 
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(p.timeline||[],p.status);
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
  }