const state={runId:null,bucketName:'space-factory-runs',bucketSource:null,bucketReady:false,poll:null,activeLinks:{},selectedRunDetail:null,runStatusFilter:'all',runSearch:'',runPage:0,runsPerPage:5,runsCache:[],runsLastLoaded:0,lastRunsRefreshFromPoll:0,lastJobLogPoll:0,pollErrors:0,busy:false,user:null,currentView:'onepage'}; const 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']); const DEFAULT_TIMELINE=[{label:'Bootstrap',status:'pending'},{label:'Dependencies',status:'pending'},{label:'Auth',status:'pending'},{label:'Model analysis',status:'pending'},{label:'Workspace',status:'pending'},{label:'Node/npm',status:'pending'},{label:'Pi install',status:'pending'},{label:'Pi config',status:'pending'},{label:'Pi run',status:'pending'},{label:'Pi verification',status:'pending'},{label:'Metadata sanitize',status:'pending'},{label:'Requirements sanitize',status:'pending'},{label:'Hardware strategy',status:'pending'},{label:'Create with hardware',status:'pending'},{label:'Create Space',status:'pending'},{label:'Repair pass',status:'pending'},{label:'Upload files',status:'pending'},{label:'Space runtime',status:'pending'},{label:'Space logs',status:'pending'},{label:'API validation',status:'pending'},{label:'Live wait',status:'pending'},{label:'Generation smoke',status:'pending'},{label:'Inference gate',status:'pending'},{label:'Report',status:'pending'},{label:'Done',status:'pending'},{label:'Failure',status:'pending'}]; function saveActiveRun(){try{localStorage.setItem('asf.activeRun',JSON.stringify({runId:state.runId,bucketName:state.bucketName,links:state.activeLinks||{}}))}catch(_){}} function clearSavedRun(){try{localStorage.removeItem('asf.activeRun')}catch(_){}} function artifactUrl(runId,bucketSource){if(!runId||!bucketSource)return'';return `https://huggingface.co/buckets/${bucketSource}/tree/runs/${runId}`} function ownerFromBucketSource(bucket){return bucket?String(bucket).split('/')[0]:''} function normalizeLinks(links={}){const out={...links};const bucket=out.bucket_source||state.bucketSource;const run=out.run_id||state.runId;if(!out.artifacts_url&&bucket&&run)out.artifacts_url=artifactUrl(run,bucket);if(!out.job_url){const jobId=out.job_id||out.jobId;const owner=out.job_owner||out.created_by||out.owner||out.username||state.user?.username||ownerFromBucketSource(bucket);if(jobId&&owner)out.job_url=`https://huggingface.co/jobs/${owner}/${jobId}`}return out} function runPassedSpaceCreation(payload={}){ const events=payload.events||payload.view?.activity||payload.detail?.events||[]; const summary=payload.summary||payload.detail?.summary||{}; const stateObj=payload.state||payload.detail?.state||{}; const success=String(payload.status||summary.status||stateObj.status||'').toLowerCase(); if(success.includes('success')||success.includes('succeed')||success==='done'||success==='completed')return true; const passedSteps=new Set(['create_space','upload_files','space_runtime','space_logs','api_validation','live_wait','generation_smoke','inference_gate','report_write','done']); return Array.isArray(events)&&events.some(e=>passedSteps.has(String(e.step||''))&&String(e.status||'').toLowerCase()!=='started'); } function setGeneratedSpaceLinksEnabled(links={},enabled=false){ const space=enabled?links.target_space_url:''; const settings=enabled?(links.target_space_settings_url||links.target_space_url&&links.target_space_url+'/settings'):''; for(const id of ['openSpace','selectedOpenSpace','launchOpenSpace'])setLink(id,space); for(const id of ['openSettings','selectedOpenSettings','manualOpenSettings','launchOpenSettings'])setLink(id,settings); } function setQuickLinks(links={},context={}){links=normalizeLinks(links||{});state.activeLinks=links;for(const id of ['openJob','selectedOpenJob','launchOpenJob'])setLink(id,links.job_url);const spaceReady=runPassedSpaceCreation(context);setGeneratedSpaceLinksEnabled(links,spaceReady);for(const id of ['openArtifacts','selectedOpenArtifacts','launchOpenArtifacts'])setLink(id,links.artifacts_url);renderManualAction({links:{...links,target_space_url:spaceReady?links.target_space_url:'',target_space_settings_url:spaceReady?links.target_space_settings_url:''}})} let operationTimer=null;function toastDuration(type='info'){if(type==='loading')return 12000;if(type==='error')return 7000;if(type==='warning'||type==='warn')return 6000;return 3500} function setOperation(message,type='info',opts={}){const el=$('operationStatus');if(!el)return;if(operationTimer){clearTimeout(operationTimer);operationTimer=null}if(!message){el.hidden=true;el.textContent='';el.className='operation-status floating-toast';return}el.hidden=false;el.className='operation-status floating-toast '+type;el.textContent=message;const duration=opts.duration??toastDuration(type);if(duration>0)operationTimer=setTimeout(()=>{setOperation('')},duration)} function showMessage(msg,isError=false){const type=isError===true?'error':(typeof isError==='string'?isError:'info');setOperation(msg,type);const el=$('eventsPanel');if(el&&!state.runId)el.textContent=(isError?'Error: ':'')+msg} function setBusy(isBusy,label='Working…'){state.busy=Boolean(isBusy);if(!state.busy&&$('operationStatus')?.className.includes('loading'))setOperation('');document.querySelectorAll('[data-action-button]').forEach(btn=>{btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady});if($('launchBuild'))$('launchBuild').disabled=state.busy||!state.bucketReady;if($('launchValidate'))$('launchValidate').disabled=state.busy;if(state.busy)setOperation(label,'loading')} function setButtonBusy(id,isBusy,busyLabel){const btn=$(id);if(!btn)return;if(isBusy){btn.dataset.oldText=btn.textContent;btn.textContent=busyLabel||'Working…';btn.disabled=true;btn.classList.add('is-busy')}else{if(btn.dataset.oldText)btn.textContent=btn.dataset.oldText;btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady;btn.classList.remove('is-busy')}} function setBucketReady(ok,message){state.bucketReady=Boolean(ok);const btn=$('launchBuild');if(btn)btn.disabled=state.busy||!state.bucketReady;setText('buildGateText',state.bucketReady?'Bucket ready. You can launch builds.':(message||'Create your private run bucket before launching builds.'));setText('bucketInlineStatus',state.bucketReady?'Bucket ready':'Not ready');for(const id of ['bucketInlineDot','sidebarBucketDot']){const dot=$(id);if(dot)dot.className='status-dot '+(state.bucketReady?'green':'amber-dot')}const inline=$('bucketInlineStatus');if(inline)inline.className='badge '+(state.bucketReady?'success':'warn');document.querySelectorAll('[data-requires-bucket="true"]').forEach(btn=>btn.disabled=state.busy||!state.bucketReady)} function effectivePreferredHardware(){const preferred=formValue('preferredHardware')||'zero-a10g';const fallback=formValue('fallbackHardware')||'a10g-large';const tryZero=$('tryZeroGpu')?.checked??true;if(tryZero)return preferred;if(preferred==='zero-a10g'||preferred.startsWith('zero'))return fallback;return preferred} function hardwarePlanLabel(){const tryZero=$('tryZeroGpu')?.checked??true;const preferred=effectivePreferredHardware();const fallback=formValue('fallbackHardware')||'a10g-large';const allow=$('allowFallback')?.checked??true;if(tryZero)return `ZeroGPU → ${allow?fallback:'no fixed fallback'}`;return `${preferred}${allow&&fallback!==preferred?' → '+fallback+' fallback':''}`} function validateBuildForm(){if(!state.bucketReady)return'Create or check your private run bucket before launching a build.';if(!formValue('modelId'))return'Enter a model card URL or model ID.';if(!formValue('targetSpace'))return'Enter a fresh target Space name.';if(!formValue('piModel'))return'Enter the Pi model to use.';return''} function validateValidationForm(){if(!formValue('validateSpace'))return'Enter the target Space ID to validate.';try{JSON.parse(formValue('testArgs')||'[]');JSON.parse(formValue('testKwargs')||'{}')}catch(e){return`Invalid JSON payload: ${e.message}`}return''} function setBuildModeNew(){const panel=$('buildModePanel');if(panel){panel.classList.remove('inspecting');panel.innerHTML='Preparing a new build
This form launches new builds. Existing runs can be inspected in the Runs Explorer on the right.
'}const launch=$('launchResultPanel');if(launch)launch.hidden=true;const status=$('progressStatus');if(status){status.textContent='Idle';status.className='badge neutral'}setText('progressTitle','No run selected')} function setBuildModeInspecting(runId){const panel=$('buildModePanel');if(panel){panel.classList.add('inspecting');panel.innerHTML=`Inspecting selected runYou are viewing run ${escapeHtml(runId||'')}. The build form is unchanged and the active run panel is now tracking it.
Use Hugging Face OAuth before launching Jobs or reading your Bucket.
Sign in with Hugging Face ↗'} function renderAuthWarnings(me){const auth=$('authPanel');if(!auth)return;const missing=me.missing_scopes||[];const warnings=me.warnings||[];if(!missing.length&&!warnings.length){auth.hidden=true;auth.innerHTML='';return}auth.hidden=false;auth.className='auth-panel warning';auth.innerHTML=`OAuth session warning${escapeHtml([...warnings,missing.length?'Missing scopes: '+missing.join(', '):''].filter(Boolean).join(' • '))}
Refresh sign-in ↗`} async function checkBucket(noisy=true){setButtonBusy('checkBucket',true,'Checking…');try{const data=await apiGet(`/api/bucket/status?bucket_name=${encodeURIComponent(state.bucketName)}`);const source=data.bucket_source||state.bucketName;state.bucketSource=source;setText('bucketInlineLabel',source);setBucketReady(Boolean(data.ok),data.error||'Create your private run bucket before launching builds.');if(noisy)showMessage(data.ok?`Bucket ready: ${source}`:`Bucket not ready: ${data.error||'not found'}`,!data.ok);return data}catch(e){setBucketReady(false,e.message);if(noisy)showMessage(e.message,true);throw e}finally{setButtonBusy('checkBucket',false)}} async function createBucket(){setButtonBusy('createBucket',true,'Creating…');try{const data=await apiPost('/api/bucket/create',{bucket_name:state.bucketName});const source=data.bucket_source||state.bucketName;state.bucketSource=source;setText('bucketInlineLabel',source);setBucketReady(Boolean(data.ok),data.error||'Could not create bucket.');showMessage(data.ok?`Created/ready: ${source}`:`Could not create bucket: ${data.error}`,!data.ok);if(data.ok)loadRuns(true)}catch(e){setBucketReady(false,e.message);showMessage(e.message,true)}finally{setButtonBusy('createBucket',false)}} function renderManualAction(p={}){const panel=$('manualActionPanel');if(!panel)return;const view=p.view||{};const sm=view.status_model||{};const status=String(p.status||p.state?.status||p.inference_gate?.status||sm.global_status||'');const manual=Boolean(sm.requires_manual_action||p.manual_hardware_required||p.inference_gate?.manual_hardware_required||p.hardware_strategy?.manual_action_required||status.includes('manual_hardware_required')||status.includes('generated_needs_manual_hardware')||status.includes('waiting_manual_action'));panel.hidden=!manual;if(!manual)return;setText('manualActionTitle','Manual hardware required');setText('manualActionText','Automatic hardware selection was not available. Open Space settings, choose the recommended GPU, then run Space Test.');setLink('manualOpenSettings',(p.links||view.links||state.activeLinks||{}).target_space_settings_url)} 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')} 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)}} 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)}} async function refreshRunReportPreview(runId){ if(!runId)return; try{ const detail=await apiGet(`/api/runs/${encodeURIComponent(runId)}?bucket_name=${encodeURIComponent(state.bucketName)}`); if(detail.bucket_source)state.bucketSource=detail.bucket_source; const reportText=(detail.report||'No report available.').slice(0,8000); const report=$('reportPreview'); if(report)report.textContent=reportText; state.selectedRunDetail=detail; if(typeof renderSelectedLinks==='function')renderSelectedLinks({...detail.summary,bucket_source:detail.bucket_source},detail); if(typeof renderBlockers==='function')renderBlockers(detail); renderMetrics({...detail,summary:detail.summary||{}}); }catch(e){ console.warn('report refresh failed',e); } } 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;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.status||p.state?.status||p.view?.header?.raw_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',badgeLabel(p.status||p.state?.status||p.view?.header?.status||'running'));setQuickLinks({...p.links,...p.view?.links,bucket_source:p.bucket_source,run_id:p.run_id},{...p,events:p.events||[]});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;if(state.lastReportRefreshRunId!==state.runId){state.lastReportRefreshRunId=state.runId;refreshRunReportPreview(state.runId)}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);setOperation(`Progress polling failed ${state.pollErrors} time${state.pollErrors===1?'':'s'}. Retrying automatically…`,'warning')}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=`