File size: 61,686 Bytes
1b50886 56b58d8 c843485 7584325 bc18c93 d6ef0ec 65a9f09 2af92fd bc18c93 c843485 bc18c93 c843485 1b50886 c843485 bc18c93 1b50886 bc18c93 c843485 bc18c93 1b50886 bc18c93 e3f778e c1f7b6c b353c3a 1b50886 e3f778e bc18c93 2af92fd bc18c93 2af92fd bc18c93 2af92fd c843485 bc18c93 c843485 5660fa3 c843485 e3f778e 06436a6 e3f778e 06436a6 e3f778e c843485 e3f778e c843485 e3f778e c843485 1b50886 c843485 1b50886 c2bd1f6 d6ef0ec d1586da 690ac59 c778235 d1586da bc18c93 d1586da 2af92fd d1586da c778235 d1586da 91e71d6 bc18c93 d1586da bc18c93 d1586da 1da73cf cea6366 25bbd6e b353c3a 25bbd6e 8be4404 2af92fd bc18c93 c23f51b a02cf72 bc18c93 25bbd6e bc18c93 1b50886 25bbd6e bc18c93 25bbd6e bc18c93 1da73cf 8be4404 c2bd1f6 c843485 c2bd1f6 06436a6 c2bd1f6 06436a6 c2bd1f6 bc18c93 1da73cf c2bd1f6 1da73cf bc18c93 c843485 1da73cf bc18c93 c2bd1f6 bc18c93 c2bd1f6 1da73cf c2bd1f6 1da73cf bc18c93 1da73cf 06436a6 1da73cf 1b50886 c2bd1f6 e15e363 c1f7b6c e15e363 bc18c93 cea6366 bc18c93 e15e363 cea6366 25bbd6e c843485 25bbd6e 40bd1f5 25bbd6e 40bd1f5 25bbd6e c843485 cea6366 c843485 e477c9c 56b58d8 1da73cf c843485 fcf6644 c843485 1b50886 e3f778e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 | const state={runId:null,validationRunId:null,validationPoll:null,centerTab:'active',validationPollErrors:0,validationDetail:null,spaceLinksUnlockedFor:null,bucketName:'space-factory-runs',bucketSource:null,bucketReady:false,poll:null,activeLinks:{},activeRunProgress:null,selectedRunDetail:null,runStatusFilter:'all',runSearch:'',runPage:0,runsPerPage:5,runsCache:[],runsLastLoaded:0,lastRunsRefreshFromPoll:0,lastJobLogPoll:0,pollErrors:0,busy:false,user:null,currentView:'onepage',modelScan:null,runDetailCache:{},runDetailCacheTtlMs:300000};
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 bindOnce(el,eventName,key,handler,options){
if(!el)return;
const attr='bound'+String(key||eventName).replace(/[^a-zA-Z0-9]/g,'');
if(el.dataset[attr]==='true')return;
el.dataset[attr]='true';
el.addEventListener(eventName,handler,options);
}
function bindAllOnce(selector,eventName,key,handler,options){
document.querySelectorAll(selector).forEach((el,index)=>bindOnce(el,eventName,`${key}${index}`,handler,options));
}
function updateImplementationModeHelp(){
const value=formValue('implementationMode')||'full-inference-gated';
const copy=implementationModeCopy(value);
const el=$('implementationModeHelp');
if(!el)return;
el.innerHTML=`<strong>${escapeHtml(copy.title)}</strong><span>${escapeHtml(copy.description)}</span>`;
}
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(_){} state.spaceLinksUnlockedFor=null}
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 isValidationRunDetail(detail={}){
const summary=detail.summary||{};
const launch=detail.launch||{};
const stateObj=detail.state||{};
const view=detail.view||{};
const values=[detail.kind,summary.kind,launch.kind,stateObj.kind,view.kind,summary.run_type,launch.run_type,stateObj.run_type,detail.run_type];
return values.some(v=>String(v||'').toLowerCase().includes('validate_existing_space')||String(v||'').toLowerCase().includes('validation'));
}
function validationTimelineItems(detail={}){
const events=detail.events||detail.view?.activity||[];
if(Array.isArray(detail.timeline)&&detail.timeline.length)return detail.timeline;
if(Array.isArray(detail.progress?.timeline)&&detail.progress.timeline.length)return detail.progress.timeline;
if(!Array.isArray(events)||!events.length)return [
{step:'bootstrap',label:'Bootstrap',status:'pending'},
{step:'dependencies',label:'Dependencies',status:'pending'},
{step:'auth',label:'Auth',status:'pending'},
{step:'live_wait',label:'Live wait',status:'pending'},
{step:'generation_smoke',label:'Generation smoke',status:'pending'},
{step:'report_write',label:'Report',status:'pending'},
];
const latest=new Map();
events.forEach(e=>{if(e&&e.step)latest.set(String(e.step),e)});
return Array.from(latest.values()).map(e=>({step:e.step,label:String(e.step||'').replace(/_/g,' '),status:e.status||'running',message:e.message||''}));
}
function validationProgressPercent(detail={}){
const explicit=Number(detail.progress||detail.percent||detail.summary?.progress);
if(Number.isFinite(explicit)&&explicit>0)return Math.max(0,Math.min(100,explicit));
const status=String(detail.status||detail.view?.header?.status||detail.state?.status||detail.summary?.status||'').toLowerCase();
if(TERMINAL_STATUSES.has(status)||status.includes('success')||status.includes('succeed'))return 100;
const items=validationTimelineItems(detail);
const order=['bootstrap','dependencies','auth','live_wait','api_validation','generation_smoke','inference_gate','report_write','done'];
let score=0;
items.forEach(item=>{
const idx=order.indexOf(String(item.step||''));
const st=String(item.status||'').toLowerCase();
if(idx>=0&&(st.includes('success')||st==='done'||st==='completed'))score=Math.max(score,idx+1);
if(idx>=0&&(st.includes('running')||st.includes('started')||st.includes('waiting')||st.includes('pending')))score=Math.max(score,idx+0.35);
});
return Math.max(0,Math.min(98,Math.round((score/order.length)*100)));
}
function uniqueStrings(values=[]){const out=[];for(const value of values){const text=String(value||'').trim();if(text&&!out.includes(text))out.push(text)}return out}
function validationEndpointNames(detail={}){
const smoke=detail.generation_smoke||detail.summary?.generation_smoke||detail.state?.generation_smoke||{};
const schema=detail.api_schema||detail.tests?.api_schema||{};
const names=[];
for(const source of [schema.api_names,schema.discovered_api_names,smoke.discovered_api_names,smoke.api_names]){
if(Array.isArray(source))names.push(...source);
}
if(smoke.api_name)names.push(smoke.api_name);
if(schema.selected_api_name)names.push(schema.selected_api_name);
const nested=schema.schema&&typeof schema.schema==='object'?schema.schema:{};
for(const source of [nested.api_names,nested.named_endpoints]){
if(Array.isArray(source))names.push(...source);
}
return uniqueStrings(names.map(normalizeApiName));
}
function endpointSchemaEntriesFromApiSchema(schema={}){
const raw=schema.schema&&typeof schema.schema==='object'?schema.schema:schema;
const named=raw.named_endpoints||raw.endpoints||raw.dependencies||[];
if(Array.isArray(named))return named;
if(named&&typeof named==='object')return Object.entries(named).map(([api_name,value])=>({api_name,...(value&&typeof value==='object'?value:{})}));
return [];
}
function endpointNameFromEntry(entry={}){
return normalizeApiName(entry.api_name||entry.api_name_for_predict||entry.name||entry.endpoint||entry.path||entry.id||'');
}
function validationEndpointParameters(detail={},endpointName=''){
const smoke=detail.generation_smoke||detail.summary?.generation_smoke||detail.state?.generation_smoke||{};
const schema=detail.api_schema||detail.tests?.api_schema||{};
const selected=normalizeApiName(endpointName||smoke.api_name||schema.selected_api_name||formValue('validateApi')||'');
const entries=endpointSchemaEntriesFromApiSchema(schema);
const match=entries.find(entry=>endpointNameFromEntry(entry)===selected);
const possible=match?.parameters||match?.inputs||match?.input_components||match?.components||smoke.endpoint_parameters||schema.endpoint_parameters||schema.schema?.endpoint_parameters||[];
const params=Array.isArray(possible)?possible.map(item=>typeof item==='string'?item:(item?.parameter_name||item?.label||item?.name||item?.id||item?.component||'')):[];
return uniqueStrings(params);
}
function applyValidationEndpoint(endpointName,detail={}){
const name=normalizeApiName(endpointName);
setInputValue('validateApi',name);
const input=$('validateApi');
if(input){input.dispatchEvent(new Event('input',{bubbles:true}));input.focus({preventScroll:true})}
renderValidationEndpoints(detail||state.validationDetail||{});
showMessage(`API name set to ${name}.`,'success');
}
function bindValidationEndpointPicker(root){
if(!root||root.dataset.boundEndpointPicker==='true')return;
root.dataset.boundEndpointPicker='true';
root.addEventListener('click',e=>{
const btn=e.target.closest('[data-endpoint-name]');
if(!btn)return;
e.preventDefault();
applyValidationEndpoint(btn.dataset.endpointName,state.validationDetail||{});
});
}
function endpointInputItemsFromEntry(entry={}){
const raw=entry.parameters||entry.inputs||entry.input_components||entry.components||[];
if(!Array.isArray(raw))return [];
return raw.map((item,index)=>{
if(typeof item==='string')return {name:item,type:'',component:''};
const name=item.parameter_name||item.label||item.name||item.id||item.component||`arg${index}`;
const type=item.type||item.python_type||item.datatype||item.api_info?.type||item.component_type||'';
const component=item.component||item.component_type||item.type_name||item.label||'';
const optional=item.optional===true||item.required===false;
return {name:String(name||''),type:String(type||''),component:String(component||''),optional};
}).filter(x=>x.name);
}
function endpointOutputItemsFromEntry(entry={}){
const raw=entry.returns||entry.outputs||entry.output_components||[];
if(!Array.isArray(raw))return [];
return raw.map((item,index)=>{
if(typeof item==='string')return {name:item,type:'',component:''};
return {name:String(item.label||item.name||item.id||`output${index}`),type:String(item.type||item.python_type||item.datatype||''),component:String(item.component||item.component_type||item.type_name||'')};
}).filter(x=>x.name);
}
function endpointDetails(detail={},endpointName=''){
const schema=detail.api_schema||detail.tests?.api_schema||{};
const entries=endpointSchemaEntriesFromApiSchema(schema);
const selected=normalizeApiName(endpointName||formValue('validateApi')||detail.generation_smoke?.api_name||schema.selected_api_name||'');
const match=entries.find(entry=>endpointNameFromEntry(entry)===selected)||{};
const inputs=endpointInputItemsFromEntry(match);
const fallbackParams=validationEndpointParameters(detail,selected).map(name=>({name,type:'',component:''}));
return {
name:selected,
description:match.description||match.documentation||match.doc||'',
inputs:inputs.length?inputs:fallbackParams,
outputs:endpointOutputItemsFromEntry(match),
raw:match,
};
}
function renderEndpointParamChips(items=[]){
if(!items.length)return '<span class="muted">No parameters discovered yet.</span>';
return items.slice(0,10).map(item=>{
const detail=[item.type,item.component,item.optional?'optional':''].filter(Boolean).join(' · ');
return `<em title="${escapeHtml(detail||item.name)}"><strong>${escapeHtml(item.name)}</strong>${detail?`<span>${escapeHtml(detail)}</span>`:''}</em>`;
}).join('')+(items.length>10?'<em><strong>…</strong></em>':'');
}
function renderValidationEndpoints(detail={}){
const root=$('spaceTestEndpoints');
if(!root)return;
bindValidationEndpointPicker(root);
const names=validationEndpointNames(detail);
const selected=normalizeApiName(formValue('validateApi')||detail.generation_smoke?.api_name||detail.api_schema?.selected_api_name||names[0]||'');
if(!names.length){root.innerHTML='<div class="empty compact-empty">No Gradio endpoints discovered yet. They will appear after the live API schema step.</div>';return}
const chips=names.slice(0,12).map(name=>`<button class="endpoint-chip ${name===selected?'selected':''}" type="button" data-endpoint-name="${escapeHtml(name)}" title="Use ${escapeHtml(name)} for validation"><span>${escapeHtml(name)}</span>${name===selected?'<em>selected</em>':''}</button>`).join('');
const details=endpointDetails(detail,selected);
const signature=JSON.stringify({names:names.slice(0,12),selected,inputs:details.inputs,outputs:details.outputs,description:details.description||''});
if(root.dataset.signature===signature)return;
root.dataset.signature=signature;
const inputLine=`<div class="endpoint-io"><strong>Inputs</strong><span>${renderEndpointParamChips(details.inputs)}</span></div>`;
const outputLine=details.outputs.length?`<div class="endpoint-io"><strong>Outputs</strong><span>${renderEndpointParamChips(details.outputs)}</span></div>`:'';
root.innerHTML=`<div class="endpoint-chip-list">${chips}</div><div class="endpoint-info-card">${inputLine}${outputLine}${details.description?`<p>${escapeHtml(details.description)}</p>`:''}</div>`;
}
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 ['activeOpenSpace','openSpace','launchOpenSpace'])setLink(id,space);
for(const id of ['activeOpenSettings','openSettings','manualOpenSettings','launchOpenSettings'])setLink(id,settings);
}
function setQuickLinks(links={},context={}){links=normalizeLinks(links||{});state.activeLinks=links;for(const id of ['activeOpenJob','openJob','launchOpenJob'])setLink(id,links.job_url);const runForLinks=links.run_id||context.run_id||context.summary?.run_id||state.runId;let spaceReady=runPassedSpaceCreation(context);if(spaceReady&&links.target_space_url)state.spaceLinksUnlockedFor=runForLinks;if(!spaceReady&&state.spaceLinksUnlockedFor&&state.spaceLinksUnlockedFor===runForLinks&&links.target_space_url)spaceReady=true;setGeneratedSpaceLinksEnabled(links,spaceReady);for(const id of ['activeOpenArtifacts','openArtifacts','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'))setBuildLaunchEnabled();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;if(id==='launchBuild')setBuildLaunchEnabled();else btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady;btn.classList.remove('is-busy')}}
function setBucketFeedback(mode='checking',message='Checking private bucket access…'){
const orb=$('bucketInlineOrb');
if(orb)orb.className='bucket-status-orb '+mode;
setText('bucketFeedbackText',message);
}
function setBucketReady(ok,message){state.bucketReady=Boolean(ok);setBuildLaunchEnabled();const feedback=state.bucketReady?'Ready for builds, validations and artifacts.':(message||'Create your private run bucket before launching builds.');setText('buildGateText',state.bucketReady?'Bucket ready. You can launch builds.':feedback);setText('bucketInlineStatus',state.bucketReady?'Ready':'Setup needed');setBucketFeedback(state.bucketReady?'ready':'error',feedback);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);setBuildLaunchEnabled()}
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 normalizeModelInput(value){return String(value||'').trim().replace('https://huggingface.co/','').split('?')[0].split('#')[0].replace(/^\/+|\/+$/g,'')}
function modelScanMatchesCurrent(){return state.modelScan?.ok&&normalizeModelInput(state.modelScan.model_id)===normalizeModelInput(formValue('modelId'))}
function scanVerdictClass(verdict){verdict=String(verdict||'').toLowerCase();if(verdict==='safe')return'success';if(verdict==='caution')return'warn';if(verdict==='risky'||verdict==='unsupported')return'error';if(verdict==='scanning')return'running';return'neutral'}
function outputTypeFromModelScan(scan={}){
const value=String(scan.expected_output_type||scan.metadata?.expected_output_type||'').toLowerCase();
return ['text','image','video','audio','any'].includes(value)?value:'';
}
function syncExpectedOutputFromModelScan(scan={}){
const inferred=outputTypeFromModelScan(scan);
const select=$('buildExpectedOutput');
if(!inferred||!select)return false;
const previous=select.value;
select.value=inferred;
select.dataset.prescanOutput=inferred;
select.dataset.prescanPreviousOutput=previous||'';
select.dispatchEvent(new Event('change',{bubbles:true}));
return previous!==inferred;
}
function renderModelPreScan(scan=null){
const card=$('modelPreScanCard'),badge=$('modelPreScanBadge'),signals=$('modelPreScanSignals'),ackWrap=$('modelRiskAckWrap');
if(!card)return;
const current=normalizeModelInput(formValue('modelId'));
const matches=scan&&scan.ok&&normalizeModelInput(scan.model_id)===current;
if(!scan||!matches){
card.className='model-prescan-card required';
setText('modelPreScanTitle','Pre-scan required');
setText('modelPreScanSummary','Run a fast metadata scan before launching a paid Job. It checks the model card guidance, runnable examples, Diffusers signals, files, safetensors, gated access and custom-code risk without downloading weights.');
if(badge){badge.textContent='Required';badge.className='badge neutral'}
if(signals){signals.hidden=true;signals.innerHTML=''}
if(ackWrap)ackWrap.hidden=true;
return;
}
const verdict=String(scan.verdict||'caution').toLowerCase();
const cls=scanVerdictClass(verdict);
card.className='model-prescan-card '+cls;
setText('modelPreScanTitle',`${verdict.charAt(0).toUpperCase()+verdict.slice(1)} · score ${scan.score ?? '—'}/100`);
setText('modelPreScanSummary',scan.summary||'Model metadata scan completed.');
if(badge){badge.textContent=verdict;badge.className='badge '+cls}
const good=(scan.good_signals||[]).slice(0,3);
const risk=(scan.risk_signals||[]).slice(0,4);
const outputType=outputTypeFromModelScan(scan);
const recs=[...(scan.recommendations||[]).slice(0,2)];
if(outputType)recs.unshift(`Expected output: ${outputType}`);
if(signals){
signals.hidden=false;
signals.innerHTML=`${good.length?`<div><strong>Good</strong>${good.map(x=>`<span class="good">${escapeHtml(x)}</span>`).join('')}</div>`:''}${risk.length?`<div><strong>Watch</strong>${risk.map(x=>`<span class="risk">${escapeHtml(x)}</span>`).join('')}</div>`:''}${recs.length?`<div><strong>Recommendation</strong>${recs.map(x=>`<span>${escapeHtml(x)}</span>`).join('')}</div>`:''}`;
}
if(ackWrap)ackWrap.hidden=!(verdict==='risky');
}
function resetModelPreScan(){state.modelScan=null;const ack=$('modelRiskAck');if(ack)ack.checked=false;renderModelPreScan(null);setBuildLaunchEnabled()}
function setBuildLaunchEnabled(){const btn=$('launchBuild');const err=validateBuildForm();if(btn)btn.disabled=state.busy||Boolean(err);if($('buildGateText'))setText('buildGateText',err||'Bucket and model pre-scan ready. You can launch builds.')}
async function scanModel(){
const model=formValue('modelId');
if(!model){showMessage('Enter a model card URL or model ID before scanning.',true);return}
setButtonBusy('scanModel',true,'Scanning…');
const badge=$('modelPreScanBadge');if(badge){badge.textContent='Scanning';badge.className='badge running'}
setText('modelPreScanTitle','Scanning model metadata…');
setText('modelPreScanSummary','Reading the model card, Hub metadata and small config files. We do not download model weights.');
try{
const result=await apiPost('/api/models/pre-scan',{model_id:model});
state.modelScan=result;
syncExpectedOutputFromModelScan(result);
const ack=$('modelRiskAck');if(ack)ack.checked=false;
renderModelPreScan(result);
setBuildLaunchEnabled();
showMessage(`Model pre-scan: ${result.verdict} (${result.score}/100)`,result.verdict==='safe'?'success':(result.verdict==='unsupported'||result.verdict==='risky'?true:'warning'));
}catch(e){state.modelScan=null;renderModelPreScan(null);showMessage(e.message,true)}
finally{setButtonBusy('scanModel',false);setBuildLaunchEnabled()}
}
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('piModel'))return'Enter the Pi model to use.';if(!modelScanMatchesCurrent())return'Run the model pre-scan before launching this build.';const verdict=String(state.modelScan?.verdict||'').toLowerCase();if(verdict==='unsupported')return'This model pre-scan marked the repo as unsupported for automatic build.';if(verdict==='risky'&&!$('modelRiskAck')?.checked)return'Review the model pre-scan risk signals and confirm before launching.';return''}
function parseJsonField(id,fallback){const raw=formValue(id);if(!raw)return fallback;return JSON.parse(raw)}
function prettyJson(value){return JSON.stringify(value,null,2)}
function validateValidationForm(){if(!formValue('validateSpace'))return'Enter the target Space ID to validate.';try{const args=parseJsonField('testArgs',[]);const kwargs=parseJsonField('testKwargs',{});if(!Array.isArray(args))return'Test args must be a JSON array because they are passed as positional arguments to gradio_client.predict(...).';if(!kwargs||Array.isArray(kwargs)||typeof kwargs!=='object')return'Test kwargs must be a JSON object because they are passed as keyword arguments.'}catch(e){return`Invalid JSON payload: ${e.message}`}return''}
function setBuildModeNew(){const panel=$('buildModePanel');if(panel){panel.classList.remove('inspecting');panel.innerHTML='<strong>Preparing a new build</strong><p>This form launches new builds. Existing runs can be inspected in the Runs Explorer on the right.</p>'}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=`<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>`}}
function resetRunDetails(){
const cancel=$('cancelRun');if(cancel)cancel.disabled=true;
if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;if(typeof resetProgressStability==='function')resetProgressStability(null);
if(typeof activityFeedState!=='undefined'){activityFeedState.lastItems=[];activityFeedState.runId=null}
for(const [id,value] of [
['detailRunId','—'],['detailJobStatus','—'],['detailGateStatus','—'],['detailSpace','—'],['detailHardware','—'],['detailTraces','—'],
['homeRunDetailStatus','No run'],['selectedModelId','—'],['progressTitle','No run selected'],['progressStatus','Idle'],
['currentStep','—'],['lastEvent','Select a run from the explorer or launch a new build.'],['elapsed','—'],['lastPolled','—'],
['progressPercent','0%']
])setText(id,value);
const status=$('progressStatus');if(status)status.className='badge neutral';
const fill=$('progressFill');if(fill){fill.style.width='0%';fill.classList.remove('is-running','progress-bump')}
setQuickLinks({});
const full=$('reportPreview');if(full)full.textContent='No report selected.';
const manual=$('manualActionPanel');if(manual)manual.hidden=true;
const activity=$('activityFeed');if(activity){activity.dataset.signature='';activity.classList.remove('show-all');activity.innerHTML='<div class="empty">No activity yet.</div>'}
const events=$('eventsPanel');if(events)events.textContent='No events yet.';
const artifacts=$('artifactList');if(artifacts)artifacts.textContent='No artifacts indexed yet.';
const recovery=$('agentRecoveryCard');if(recovery){recovery.hidden=true;const body=$('agentRecoveryBody');if(body)body.innerHTML='';const badge=$('agentRecoveryBadge');if(badge){badge.textContent='Not needed';badge.className='badge neutral'}}
const docs=$('runDocumentsDock');if(docs)docs.innerHTML='<div class="empty">Run documents will appear as files are written to the bucket.</div>';
const blockers=$('selectedBlockers');if(blockers){blockers.hidden=true;blockers.innerHTML=''}
for(const id of ['deleteActiveRun','activePrefillSpaceTest','deleteValidationRun']){const btn=$(id);if(btn)btn.disabled=true}
renderTimeline(DEFAULT_TIMELINE,'idle');
} function prepareNewBuild(){if(state.poll){clearInterval(state.poll);state.poll=null}state.runId=null;state.activeRunProgress=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')}
function setActiveRun(result){if(state.poll){clearInterval(state.poll);state.poll=null}resetRunDetails();state.runId=result.run_id;state.spaceLinksUnlockedFor=null;if(Array.isArray(state.runsCache)&&typeof sortRunsNewestFirst==='function'){state.runsCache=sortRunsNewestFirst([{...result,run_id:result.run_id,status:result.status||'running',created_at:result.created_at||new Date().toISOString()},...state.runsCache.filter(r=>r.run_id!==result.run_id)]);renderRunsFromCache();}if(result.bucket_source)state.bucketSource=result.bucket_source;setText('detailRunId',result.run_id);setText('detailJobStatus',result.status||'RUNNING');setText('detailGateStatus',result.kind||'running');setText('detailSpace',result.target_space||'—');setText('detailHardware',result.selected_hardware||result.preferred_space_hardware||'—');renderActiveRunMeta({summary:result,state:result,launch:result});renderProgress({run_id:result.run_id,status:result.status||'running',progress:1,current_step_label:'Launching build Job…',events:[{step:'launch',status:'started',message:'Build Job launched; waiting for first worker event.'}],summary:result,state:result});setText('homeRunDetailStatus',result.status||'Running');setBuildModeInspecting(result.run_id);setQuickLinks({...result.links,bucket_source:result.bucket_source,run_id:result.run_id,job_id:result.job_id,job_owner:state.user?.username,target_space_url:result.target_space_url},result);saveActiveRun();startPolling(result.run_id)}
function startPolling(runId,options={}){
if(!runId)return;
const sameRun=state.runId===runId;
if(sameRun&&state.poll)return;
if(!sameRun){
state.spaceLinksUnlockedFor=null;
const blockers=$('selectedBlockers');if(blockers){blockers.hidden=true;blockers.innerHTML=''}
}
state.runId=runId;
state.pollErrors=0;
state.lastJobLogPoll=0;
if(typeof resetProgressStability==='function')resetProgressStability(runId);
if(state.poll)clearInterval(state.poll);
saveActiveRun();
if(!options.deferFirstTick)refreshProgress();
else setTimeout(()=>{if(state.runId===runId)refreshProgress()},80);
state.poll=setInterval(refreshProgress,2000);
}
async function bootstrapRunRecovery(){return restoreActiveRun()}
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||{},resumable.view||{});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}
async function loadAppInfo(){setOperation('Checking sign-in and run storage…','loading');setBucketFeedback('checking','Checking private bucket access…');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);renderBillingPanel(me);loadBillingStatus().catch(()=>{});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();renderBillingPanel(null);setBucketReady(false,'Sign in with Hugging Face first.');showMessage('Please sign in with Hugging Face, then refresh this page.',true)}}
function renderSignedOut(){const auth=$('authPanel');if(!auth)return;auth.hidden=false;auth.className='auth-panel warning';auth.innerHTML='<strong>Sign in required</strong><p>Use Hugging Face OAuth before launching Jobs or reading your Bucket.</p><a class="link-btn" href="/oauth/huggingface/login">Sign in with Hugging Face ↗</a>'}
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=`<strong>OAuth session warning</strong><p>${escapeHtml([...warnings,missing.length?'Missing scopes: '+missing.join(', '):''].filter(Boolean).join(' • '))}</p><a class="link-btn" href="/oauth/huggingface/login">Refresh sign-in ↗</a>`}
function renderBillingPanel(info){
// Open HF Billing: Live quotas, spend and compute usage remain in the HF Billing dashboard.
const badge=$('billingStatusBadge');
const canPay=info?.can_pay===true||info?.payment_ready===true;
if(badge){
badge.textContent=canPay?'Billing ready':'Check billing';
badge.className='badge '+(canPay?'success':'warn');
}
const billing=$('openBilling');
if(billing)billing.href=info?.links?.billing||'https://huggingface.co/settings/billing';
}
async function loadBillingStatus(){
try{const data=await apiGet('/api/billing/status');renderBillingPanel({...state.user,...data});return data}catch(e){console.warn('billing status failed',e);return null}
}
async function checkBucket(noisy=true){setButtonBusy('checkBucket',true,'Checking…');setBucketFeedback('checking','Checking private bucket access…');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…');setBucketFeedback('creating','Creating private bucket…');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 switchCenterTab(tab='active'){
const next=tab==='validate'?'validate':'active';
state.centerTab=next;
document.querySelectorAll('[data-center-tab]').forEach(btn=>{
const active=btn.dataset.centerTab===next;
btn.classList.toggle('active',active);
btn.setAttribute('aria-selected',active?'true':'false');
});
document.querySelectorAll('[data-center-panel]').forEach(panel=>{
const active=panel.dataset.centerPanel===next;
panel.classList.toggle('active',active);
panel.hidden=!active;
});
}
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 defaultValidationArgs(expected='text'){
const kind=String(expected||'text').toLowerCase();
if(kind==='image')return ["a cinematic robot cat astronaut, detailed, studio lighting"];
if(kind==='video')return ["a short cinematic shot of a robot cat astronaut walking on the moon"];
if(kind==='audio')return ["A calm voice saying hello from Agentic Space Factory."];
return ["Explain the concept of agentic AI in one paragraph."];
}
function normalizeApiName(value){
value=String(value||'').trim();
if(!value)return'/generate';
return value.startsWith('/')?value:'/'+value;
}
function firstPositiveNumber(...values){
for(const value of values.flat()){
const n=Number(value);
if(Number.isFinite(n)&&n>0)return n;
}
return null;
}
function formatLatencySeconds(value){
const n=Number(value);
if(!Number.isFinite(n)||n<=0)return'—';
if(n<10)return`${n.toFixed(2).replace(/\.00$/,'')}s`;
if(n<60)return`${n.toFixed(1).replace(/\.0$/,'')}s`;
return fmtSeconds(Math.round(n));
}
function parsedJsonOrNull(value){
if(value==null||value==='')return null;
try{return typeof value==='string'?JSON.parse(value):value}catch(_){return null}
}
function validationPrefillFromDetail(detail={}){
const progress=(state.activeRunProgress&&((state.activeRunProgress.run_id||state.activeRunProgress.summary?.run_id)===(detail.run_id||detail.summary?.run_id||state.runId)))?state.activeRunProgress:{};
const summary={...(detail.summary||{}),...(progress.summary||{})};
const smoke={...(detail.state?.generation_smoke||{}),...(detail.tests?.generation_smoke||{}),...(detail.generation_smoke||{}),...(progress.generation_smoke||{}),...(progress.state?.generation_smoke||{})};
const launch={...(detail.launch||{}),...(progress.launch||{})};
const stateObj={...(detail.state||{}),...(progress.state||{})};
const gate={...(detail.inference_gate||{}),...(progress.inference_gate||{})};
const expected=smoke.expected_output_type||gate.expected_output_type||launch.expected_output_type||summary.expected_output_type||stateObj.expected_output_type||formValue('buildExpectedOutput')||'text';
let args=Array.isArray(smoke.test_args)?smoke.test_args:parsedJsonOrNull(launch.test_args_json);
let kwargs=smoke.test_kwargs&&typeof smoke.test_kwargs==='object'&&!Array.isArray(smoke.test_kwargs)?smoke.test_kwargs:parsedJsonOrNull(launch.test_kwargs_json);
if(!Array.isArray(args))args=defaultValidationArgs(expected);
if(!kwargs||Array.isArray(kwargs)||typeof kwargs!=='object')kwargs={};
const target=summary.target_space||stateObj.target_space||launch.target_space||progress.target_space||$('detailSpace')?.textContent||'';
const api=normalizeApiName(smoke.api_name||gate.api_name||launch.api_name||progress.api_name||'/generate');
return {target,api,expected,args,kwargs};
}
function renderValidationStatus(data={}){
state.validationDetail=data||state.validationDetail;
const summary=data.summary||{};
const smoke=data.generation_smoke||data.summary?.generation_smoke||data.state?.generation_smoke||{};
const status=data.status||data.view?.header?.status||data.state?.status||summary.status||smoke.status||'running';
const normalized=String(status||'running').toLowerCase();
const klass=statusClass(status);
const isRunning=!TERMINAL_STATUSES.has(normalized)&&klass!=='success'&&klass!=='error';
const badge=$('spaceTestStatus');
if(badge){badge.textContent=badgeLabel(status);badge.className='badge '+klass}
const tabDot=$('spaceTestTabDot');
if(tabDot)tabDot.className='tab-status-dot '+(isRunning?'running':klass);
const card=$('spaceTestPreview');
if(card)card.classList.toggle('is-validating',isRunning);
const runId=data.run_id||summary.run_id||state.validationRunId||'—';
setText('spaceTestOutput',runId);
setText('lastPolled',formatTime(data.updated_at||summary.updated_at||data.state?.updated_at||data.created_at||summary.created_at||new Date().toISOString()));
const latency=firstPositiveNumber(smoke.latency_seconds,smoke.observed_latency_seconds,summary.latency_seconds,summary.observed_latency_seconds,data.latency_seconds,data.observed_latency_seconds);
setText('spaceTestLatency',latency?formatLatencySeconds(latency):'—');
setText('spaceTestVerdict',badgeLabel(status));
const deleteBtn=$('deleteValidationRun');
if(deleteBtn){deleteBtn.disabled=!(runId&&runId!=='—');deleteBtn.onclick=()=>deleteRunById(runId,{kind:'validation',detail:data});}
const percent=validationProgressPercent(data);
setText('spaceTestProgressPercent',`${Math.round(percent)}%`);
const fill=$('spaceTestProgressFill');
if(fill){
fill.style.width=`${Math.max(0,Math.min(100,percent))}%`;
fill.className='validation-progress-fill '+(isRunning?'is-running':klass);
}
renderValidationTimeline(data);
renderValidationEndpoints(data);
}
function renderValidationLaunchResult(result={}){
state.validationRunId=result.run_id;
renderValidationStatus({...result,status:result.status||'running'});
}
function renderValidationTimeline(detail={}){
const root=$('spaceTestTimeline');
if(!root)return;
const items=validationTimelineItems(detail);
root.innerHTML=items.map((item,idx)=>{
const st=statusClass(item.status||'pending');
const label=item.label||String(item.step||`Step ${idx+1}`).replace(/_/g,' ');
return `<div class="validation-step ${st}"><span class="dot ${st}"></span><strong>${escapeHtml(label)}</strong><em>${escapeHtml(badgeLabel(item.status||'pending'))}</em></div>`;
}).join('')||'<div class="empty">No validation events yet.</div>';
}
function showValidationRunDetail(detail={}){
state.validationRunId=detail.run_id||detail.summary?.run_id||state.validationRunId;
state.validationDetail=detail;
stopValidationPolling();
renderValidationStatus(detail);
applyValidationPrefill(detail,'validation run');
switchCenterTab('validate');
const status=String(detail.view?.header?.status||detail.status||detail.state?.status||detail.summary?.status||'').toLowerCase();
if(state.validationRunId&&!TERMINAL_STATUSES.has(status))startValidationPolling(state.validationRunId);
}
function stopValidationPolling(){
if(state.validationPoll){clearInterval(state.validationPoll);state.validationPoll=null}
}
async function refreshValidationProgress(){
if(!state.validationRunId)return;
try{
const p=await apiGet(`/api/runs/${encodeURIComponent(state.validationRunId)}/progress?bucket_name=${encodeURIComponent(state.bucketName)}&include_job_logs=1`);
state.validationPollErrors=0;
renderValidationStatus(p);
const status=String(p.view?.header?.status||p.status||p.state?.status||'').toLowerCase();
if(TERMINAL_STATUSES.has(status)){stopValidationPolling();showMessage(`Validation finished: ${badgeLabel(status)}`,statusClass(status)==='success'?'success':statusClass(status)==='error'?'error':'warning');loadRuns(false)}
}catch(e){
state.validationPollErrors+=1;
console.warn('validation polling failed',e);
if(state.validationPollErrors<=2)showMessage(`Validation polling failed. Retrying…`,'warning');
}
}
function startValidationPolling(runId){
state.validationRunId=runId;
switchCenterTab('validate');
stopValidationPolling();
refreshValidationProgress();
state.validationPoll=setInterval(refreshValidationProgress,3000);
}
function applyValidationPrefill(detail={},sourceLabel='selected Space'){
const prefill=validationPrefillFromDetail(detail);
if(prefill.target&&prefill.target!=='—')setInputValue('validateSpace',prefill.target);
setInputValue('expectedOutput',prefill.expected);
setInputValue('validateApi',prefill.api);
setInputValue('testArgs',prettyJson(prefill.args));
setInputValue('testKwargs',prettyJson(prefill.kwargs));
switchCenterTab('validate');
showMessage(`Prepared validation form for ${prefill.target||sourceLabel}.`,'info');
}
function prepareValidationFromActiveRun(){
const selected=((state.selectedRunDetail?.run_id===state.runId||state.selectedRunDetail?.summary?.run_id===state.runId)?state.selectedRunDetail:{});
const activeDetail={...selected,...(state.activeRunProgress||{}),summary:{...(selected.summary||{}),...(state.activeRunProgress?.summary||{})}};
applyValidationPrefill(activeDetail,'active Space');
}
function prepareValidationFromSelectedRun(){
applyValidationPrefill(state.selectedRunDetail||{},'selected Space');
}
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')||undefined,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 args=parseJsonField('testArgs',[]);const kwargs=parseJsonField('testKwargs',{});const payload={bucket_name:state.bucketName,target_space_id:formValue('validateSpace'),api_name:normalizeApiName(formValue('validateApi')||'/generate'),expected_output_type:formValue('expectedOutput')||'any',test_args_json:prettyJson(args),test_kwargs_json:prettyJson(kwargs),live_timeout_seconds:Number(formValue('validateTimeout')||1800)};const result=await apiPost('/api/validate',payload);renderValidationLaunchResult(result);startValidationPolling(result.run_id);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;
state.selectedRunDetail=detail;
if(typeof renderRunDetail==='function')renderRunDetail(detail);
if(typeof renderActiveRunActions==='function')renderActiveRunActions(detail.summary||{},detail);
if(typeof renderRunDocuments==='function')renderRunDocuments(detail);
if(typeof renderMetrics==='function')renderMetrics({...detail,summary:detail.summary||{}});
if(typeof renderBlockers==='function')renderBlockers(detail);
if(typeof renderRunDetail!=='function'){
const reportText=(detail.report||'No report available.').slice(0,8000);
const report=$('reportPreview');
if(report)report.textContent=reportText;
}
}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 requestedRunId=state.runId;const p=await apiGet(`/api/runs/${encodeURIComponent(requestedRunId)}/progress?bucket_name=${encodeURIComponent(state.bucketName)}${includeLogs?'&include_job_logs=1':''}`);if(requestedRunId!==state.runId)return;if((p.run_id||p.summary?.run_id)&&state.runId&&(p.run_id||p.summary?.run_id)!==state.runId)return;state.activeRunProgress=p;state.pollErrors=0;if(p.bucket_source)state.bucketSource=p.bucket_source;renderProgress(p);renderEvents(p.events||[]);renderMetrics(p);if(typeof upsertRunExplorerFromProgress==='function')upsertRunExplorerFromProgress(p);if(typeof renderActiveRunActions==='function')renderActiveRunActions({...p.summary,bucket_source:p.bucket_source},p);if(typeof renderRunDocuments==='function')renderRunDocuments({...p,summary:{...p.summary,bucket_source:p.bucket_source}});if(typeof renderAgentRecovery==='function')renderAgentRecovery(p);if(typeof renderBlockers==='function')renderBlockers(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)}renderActiveRunMeta(p);if(p.state){setText('detailSpace',p.state.target_space||p.summary?.target_space||'—');const mode=runImplementationMode(p.summary||{},p);setText('detailImplementationMode',mode?implementationModeLabel(mode):'—');const modeEl=$('detailImplementationMode');if(modeEl&&mode)modeEl.title=implementationModeDescription(mode);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;setTimeout(()=>refreshRunReportPreview(state.runId),120);setTimeout(()=>loadRuns(true),160)}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 numberFromEvents(events=[],keys=[]){
const rows=Array.isArray(events)?events:[];
for(let i=rows.length-1;i>=0;i--){
const event=rows[i]||{};
const data=event.data||{};
for(const key of keys){
const direct=Number(event[key]);
if(Number.isFinite(direct)&&direct>0)return direct;
const nested=Number(data[key]);
if(Number.isFinite(nested)&&nested>0)return nested;
}
}
return null;
}
function readRunLatencyPayload(p={}){
const smoke=p.generation_smoke||p.tests?.generation_smoke||p.summary?.generation_smoke||p.state?.generation_smoke||{};
const gate=p.inference_gate||p.summary?.inference_gate||{};
const summary=p.summary||{};
const eventLatency=numberFromEvents(p.events||[],['latency_seconds','observed_latency_seconds']);
const eventDuration=numberFromEvents(p.events||[],['recommended_zerogpu_duration_seconds','recommended_zero_gpu_duration_seconds','recommended_duration_seconds','zero_gpu_duration_recommendation']);
const latency=firstPositiveNumber(smoke.latency_seconds,smoke.observed_latency_seconds,summary.latency_seconds,summary.observed_latency_seconds,p.latency_seconds,p.observed_latency_seconds,eventLatency);
const duration=firstPositiveNumber(smoke.recommended_zerogpu_duration_seconds,smoke.recommended_zero_gpu_duration_seconds,smoke.recommended_duration_seconds,summary.recommended_zerogpu_duration_seconds,summary.recommended_zero_gpu_duration_seconds,summary.recommended_duration_seconds,gate.recommended_zerogpu_duration_seconds,gate.recommended_zero_gpu_duration_seconds,gate.zero_gpu_duration_recommendation,eventDuration);
const source=smoke.recommendation_source||summary.recommendation_source||gate.recommendation_source||(eventLatency?'generation_smoke event':'');
return {smoke,gate,latency,duration,source};
}
function renderActiveLatency(p={}){
const card=$('activeLatencyCard');
if(!card)return;
const {latency,duration,source}=readRunLatencyPayload(p);
const hasLatency=Number.isFinite(latency)&&latency>0;
const hasDuration=Number.isFinite(duration)&&duration>0;
setText('activeLatencyValue',hasLatency?formatLatencySeconds(latency):'—');
setText('activeZeroGpuDuration',hasDuration?fmtSeconds(Math.round(duration)):'—');
setText('activeLatencySource',hasLatency?(source||'live smoke test'):'measured when smoke test passes');
const note=$('activeLatencyNote');
if(note){
note.textContent=hasDuration
? 'This measured latency is normally used to tune the `@spaces.GPU(duration=...)` decorator for ZeroGPU when the generated Space uses ZeroGPU.'
: 'When available, the live smoke-test latency is used to recommend the `@spaces.GPU(duration=...)` value for ZeroGPU apps.';
}
card.classList.toggle('has-latency',hasLatency);
}
function runHardwareMeta(p={}){
const summary=p.summary||{};
const stateObj=p.state||{};
const launch=p.launch||{};
const hardware=p.hardware_strategy||summary.hardware_strategy||{};
const target=summary.preferred_space_hardware||summary.target_gpu||stateObj.preferred_space_hardware||stateObj.target_gpu||launch.preferred_space_hardware||hardware.preferred||hardware.target||summary.selected_hardware||stateObj.selected_hardware||'—';
const fallback=summary.fallback_space_hardware||summary.fallback_gpu||stateObj.fallback_space_hardware||stateObj.fallback_gpu||launch.fallback_space_hardware||hardware.fallback||'—';
const output=summary.expected_output_type||stateObj.expected_output_type||launch.expected_output_type||p.generation_smoke?.expected_output_type||p.inference_gate?.expected_output_type||'—';
return {target,fallback,output};
}
function renderActiveRunMeta(p={}){
const meta=runHardwareMeta(p);
setText('detailTargetGpu',meta.target||'—');
setText('detailFallbackGpu',meta.fallback||'—');
setText('detailExpectedOutput',meta.output||'—');
}
function renderMetrics(p){const root=$('metricsList');const {smoke,gate,latency,duration}=readRunLatencyPayload(p);const health=(gate.implementation_signals&&gate.implementation_signals.health_passed)||smoke.health_passed;const smokeOk=smoke.ok||smoke.status==='success';renderActiveLatency(p);if(!root)return;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>${Number.isFinite(latency)&&latency>0?formatLatencySeconds(latency):'—'}</strong></li><li><span>Recommended ZeroGPU duration</span><strong>${Number.isFinite(duration)&&duration>0?fmtSeconds(Math.round(duration)):'—'}</strong></li>`}
function renderEvents(events){const root=$('eventsPanel');if(!root)return;try{const rows=Array.isArray(events)?events:[];root.textContent=rows.slice(-12).map(e=>`${e?.ts||''} ${e?.step||''} ${e?.status||''} ${e?.message||''}`).join('\n')||'No events yet. The run may still be scheduling.'}catch(e){console.warn('renderEvents failed',e);root.textContent='Events could not be rendered.'}}
async function cancelActiveRun(){
if(!state.runId){showMessage('Select a running run before cancelling.', 'warning');return}
if(!confirm(`Cancel Job for run ${state.runId}?`))return;
setButtonBusy('cancelRun',true,'Cancelling…');
try{
const result=await apiPost(`/api/runs/${encodeURIComponent(state.runId)}/cancel?bucket_name=${encodeURIComponent(state.bucketName)}`,{});
showMessage(`Cancelled Job ${result.job_id||''}`,'success');
if(state.poll){clearInterval(state.poll);state.poll=null}
await refreshProgress();
await loadRuns(true);
}catch(e){
showMessage(e.message,true);
}finally{
setButtonBusy('cancelRun',false);
}
}
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('');if(view==='progress')loadRuns(false);if(view==='validate')switchCenterTab('validate');if(view==='active')switchCenterTab('active');const targetMap={build:'newBuildCard',progress:'runsExplorerPanel',validate:'spaceTestSection',active:'activeRunPanel'};const target=$(targetMap[view]||'');if(target)target.scrollIntoView({behavior:'smooth',block:'start'})}
function bindNavigation(){
bindAllOnce('.nav-item','click','navItem',e=>switchView(e.currentTarget.dataset.view||'build'));
bindAllOnce('[data-view-target]','click','viewTarget',e=>switchView(e.currentTarget.dataset.viewTarget||'build'));
bindAllOnce('[data-center-tab]','click','centerTab',e=>switchCenterTab(e.currentTarget.dataset.centerTab||'active'));
}
function bindRunFilters(){
bindAllOnce('.filter[data-status]','click','runStatusFilter',e=>{
state.runStatusFilter=e.currentTarget.dataset.status||'all';
state.runPage=0;
renderRunsFromCache();
});
for(const id of ['runSearch','runSearchFull'])bindOnce($(id),'input',`runSearch${id}`,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);
});
}
function bindActions(){
$('launchBuild')?.setAttribute('data-requires-bucket','true');
$('launchBuild')?.setAttribute('data-action-button','true');
$('launchValidate')?.setAttribute('data-action-button','true');
const actions={
launchBuild:launchBuild,
scanModel:scanModel,
launchValidate:launchValidate,
prepareNewBuild:prepareNewBuild,
manualGoValidate:prepareValidationFromActiveRun,
activePrefillSpaceTest:prepareValidationFromActiveRun,
checkBucket:()=>checkBucket(true),
createBucket:createBucket,
refreshRuns:()=>loadRuns(true),
refreshRunsHome:()=>loadRuns(true),
cancelRun:cancelActiveRun,
deleteActiveRun:()=>deleteRunById(state.runId,{kind:'active',detail:state.selectedRunDetail||state.activeRunProgress||{}}),
deleteValidationRun:()=>deleteRunById(state.validationRunId,{kind:'validation',detail:state.validationDetail||{}}),
};
Object.entries(actions).forEach(([id,handler])=>bindOnce($(id),'click',`action${id}`,handler));
bindOnce($('modelId'),'input','modelPreScanReset',resetModelPreScan);
bindOnce($('modelRiskAck'),'change','modelRiskAck',setBuildLaunchEnabled);
['tryZeroGpu','preferredHardware','fallbackHardware','allowFallback'].forEach(id=>bindOnce($(id),'change',`hardware${id}`,()=>setText('hardwarePlan',hardwarePlanLabel())));
bindOnce($('implementationMode'),'change','implementationModeHelp',updateImplementationModeHelp);
}
bindNavigation();bindActions();bindRunFilters();setText('hardwarePlan',hardwarePlanLabel());updateImplementationModeHelp();setBuildModeNew();renderTimeline(DEFAULT_TIMELINE);loadAppInfo();
// Pass 47 intentionally removed automatic switchView('progress') after launch and run selection.
// Compatibility note for v108 regression: implementationMode')?.addEventListener('change',updateImplementationModeHelp is now bound through bindOnce().
// Live quotas, spend and compute usage remain in the HF Billing dashboard; Open HF Billing for numeric values.
// Legacy endpoint picker wording kept for tests: API name used for validation / Set API name.
|