| const state={runId:null,validationRunId:null,validationPoll:null,validationPollToken:null,validationRequestSeq:0,validationMode:'idle',validationArgsDirty:false,validationPollErrors:0,validationDetail:null,validationPrefillDraft:null,linkedValidationParent:null,validationTerminalByRun:{},centerTab:'active',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,runSelectionLoadingId:null,runSelectionSeq:0,deleteModalPromise:null,deleteModalRunId:null,deletingRuns:{},deletedRuns:{}}; |
| 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','partial','partial_validation','completed_with_warnings','demo_usable_full_promise_not_verified','interactive_app_available_smoke_failed','manual_test_required_smoke_failed','placeholder_scaffold_deployed','validated','validated_after_space_test','validated_after_manual_space_test','manual_validation_passed','manual_validated','recovered_by_space_test','recovered_by_manual_validation','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled','stopped','auth_refresh_required']); |
| 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);const target=out.target_space||out.target_space_id||out.space_id||out.space;if(!out.target_space_url&&target&&String(target).includes('/'))out.target_space_url=`https://huggingface.co/spaces/${String(target).replace(/^https:\/\/huggingface.co\/spaces\//,'').replace(/\/$/,'')}`;if(!out.target_space_settings_url&&out.target_space_url)out.target_space_settings_url=out.target_space_url+'/settings';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 passedSteps=new Set(['create_space','create_space_hardware','upload_files','runtime_upload_epoch']); |
| return Array.isArray(events)&&events.some(e=>passedSteps.has(String(e.step||''))&&String(e.status||'').toLowerCase()==='success'); |
| } |
| function contextTargetSpaceUrl(context={},links={}){ |
| const summary=context.summary||context.detail?.summary||{}; |
| const stateObj=context.state||context.detail?.state||{}; |
| const launch=context.launch||context.detail?.launch||{}; |
| const view=context.view||{}; |
| const linkState=context.space_link_state||view.space_link_state||context.detail?.space_link_state||{}; |
| const identity=context.space_identity||context.detail?.space_identity||{}; |
| const candidates=[ |
| links.target_space_url,links.space_url, |
| summary.target_space_url,stateObj.target_space_url,launch.target_space_url, |
| view.links?.target_space_url,view.links?.space_url, |
| linkState.target_space_url,identity.target_space_url, |
| context.target_space_url |
| ]; |
| for(const value of candidates){if(value&&String(value).trim())return String(value).trim()} |
| const target=firstNonEmpty( |
| links.target_space,links.target_space_id,links.space_id,links.space, |
| summary.target_space,summary.target_space_id,stateObj.target_space,stateObj.target_space_id, |
| launch.target_space,launch.target_space_id,linkState.target_space,linkState.target_space_id, |
| identity.target_space,identity.target_space_id,context.target_space,context.target_space_id |
| ).replace(/^https:\/\/huggingface.co\/spaces\//,'').replace(/\/$/,''); |
| return target&&target.includes('/')?`https://huggingface.co/spaces/${target}`:''; |
| } |
| function contextIndicatesSpaceLinksReady(context={},links={}){ |
| const summary=context.summary||context.detail?.summary||{}; |
| const stateObj=context.state||context.detail?.state||{}; |
| const view=context.view||{}; |
| const linkState=context.space_link_state||view.space_link_state||context.detail?.space_link_state||{}; |
| const identity=context.space_identity||context.detail?.space_identity||{}; |
| if(links.links_ready||links.space_created||links.space_uploaded||links.runtime_uploaded||links.space_runtime_known)return true; |
| if(linkState.links_ready||linkState.space_created||linkState.space_uploaded||linkState.runtime_uploaded||linkState.space_runtime_known)return true; |
| if(identity.links_ready||identity.space_created||identity.space_uploaded||identity.runtime_uploaded||identity.space_runtime_known)return true; |
| if(runPassedSpaceCreation(context))return true; |
| const statusText=[ |
| links.status,links.verdict,links.effective_status, |
| context.status,context.verdict,context.effective_status, |
| summary.status,summary.verdict,summary.effective_status,summary.gate_status, |
| stateObj.status,stateObj.verdict,stateObj.effective_status, |
| view.header?.status,view.status_model?.global_status,view.status_model?.display_status, |
| context.inference_gate?.status,summary.inference_gate?.status,stateObj.inference_gate?.status |
| ].map(x=>String(x||'').toLowerCase()).join(' '); |
| return Boolean(statusText.match(/full_inference_success|repair_success|validated_after_space_test|validated_after_manual_space_test|manual_validation_passed|manual_validated|recovered_by_space_test|recovered_by_manual_validation|full_inference_candidate_health_passed|partial_validation|completed_with_warnings|demo_usable_full_promise_not_verified|interactive_app_available_smoke_failed|manual_test_required_smoke_failed|manual_hardware_required|generated_needs_manual_hardware|waiting_manual_action|placeholder_scaffold_deployed|health_only/)); |
| } |
|
|
| 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')); |
| } |
| const VALIDATION_PHASES=[ |
| ['start','Start'], |
| ['runtime','Runtime'], |
| ['api','API'], |
| ['endpoint','Endpoint'], |
| ['payload','Payload'], |
| ['smoke','Smoke test'], |
| ['result','Result'], |
| ['report','Report'], |
| ]; |
| function validationStatusText(detail={}){ |
| const summary=detail.summary||{}; |
| const stateObj=detail.state||{}; |
| const smoke=detail.generation_smoke||summary.generation_smoke||stateObj.generation_smoke||{}; |
| const gate=detail.inference_gate||summary.inference_gate||stateObj.inference_gate||{}; |
| return [ |
| detail.verdict,detail.validation_status,detail.status,detail.result_status, |
| detail.view?.header?.status,stateObj.verdict,stateObj.validation_status,stateObj.status, |
| summary.verdict,summary.validation_status,summary.status,summary.gate_status,summary.smoke_status, |
| gate.status,smoke.status |
| ].map(x=>String(x||'').toLowerCase()).filter(Boolean).join(' '); |
| } |
| function validationCanonicalStatus(detail={}){ |
| const text=validationStatusText(detail); |
| if(!text||text.includes('idle')||text.includes('no_validation')||text.includes('deleted'))return 'idle'; |
| if(text.includes('auth_refresh_required')||text.includes('oauth_expired')||text.includes('auth expired'))return 'auth_refresh_required'; |
| if(text.includes('stale'))return 'stale'; |
| if(text.includes('technical_blocker_boot_only'))return 'technical_blocker_boot_only'; |
| if(text.includes('technical_blocker')||text.includes('technical blocker')||text.includes('blocked'))return 'technical_blocker'; |
| if(text.includes('manual_hardware_required')||text.includes('generated_needs_manual_hardware')||text.includes('waiting_manual_hardware')||text.includes('manual action')||text.includes('requires_action'))return 'manual_hardware_required'; |
| if(text.includes('failed')||text.includes('failure')||text.includes('error')||text.includes('timeout'))return 'failed'; |
| if(text.includes('full_inference_candidate_health_passed')||text.includes('health_only')||text.includes('partial')||text.includes('warning')||text.includes('not_verified')||text.includes('completed_with_warnings')||text.includes('demo_usable')||text.includes('interactive_app_available_smoke_failed')||text.includes('manual_test_required_smoke_failed'))return 'partial_validation'; |
| if(text.includes('success')||text.includes('succeed')||text.includes('passed')||text.includes('full_inference_success')||text.includes('done')||text.includes('completed'))return 'full_inference_success'; |
| if(text.includes('cancelled')||text.includes('canceled')||text.includes('stopped'))return 'stopped'; |
| if(text.includes('running')||text.includes('queued')||text.includes('pending')||text.includes('started')||text.includes('waiting')||text.includes('building'))return 'running'; |
| return 'running'; |
| } |
| function validationEventPhase(step=''){ |
| const s=String(step||'').toLowerCase(); |
| if(['token_context','bootstrap','dependencies','auth'].includes(s))return 'start'; |
| if(['space_runtime','live_wait','space_logs'].includes(s))return 'runtime'; |
| if(['api_validation','api_discovery','gradio_config','gradio_client'].includes(s))return 'api'; |
| if(['endpoint_selection','endpoint_schema','api_schema','gradio_schema'].includes(s))return 'endpoint'; |
| if(['payload','payload_prepare','test_payload','validation_payload'].includes(s))return 'payload'; |
| if(['generation_smoke','smoke_test','client_predict'].includes(s))return 'smoke'; |
| if(['inference_gate','result','validation_result'].includes(s))return 'result'; |
| if(['report_write','report','done','failure'].includes(s))return 'report'; |
| return ''; |
| } |
| function validationPhaseStatusFromEventStatus(status=''){ |
| const s=String(status||'').toLowerCase(); |
| if(s.includes('success')||s==='done'||s==='complete'||s==='completed'||s==='passed')return 'success'; |
| if(s.includes('failed')||s.includes('error')||s.includes('timeout'))return 'failed'; |
| if(s.includes('warning')||s.includes('partial'))return 'warning'; |
| if(s.includes('running')||s.includes('started')||s.includes('waiting')||s.includes('pending'))return 'running'; |
| return 'pending'; |
| } |
| function validationTimelineItems(detail={}){ |
| const events=Array.isArray(detail.events)?detail.events:(Array.isArray(detail.view?.activity)?detail.view.activity:[]); |
| const canonical=validationCanonicalStatus(detail); |
| const schema=detail.api_schema||detail.tests?.api_schema||{}; |
| const smoke=detail.generation_smoke||detail.summary?.generation_smoke||detail.state?.generation_smoke||{}; |
| const gate=detail.inference_gate||detail.summary?.inference_gate||detail.state?.inference_gate||{}; |
| const endpointNames=validationEndpointNames(detail); |
| const hasPayload=Array.isArray(smoke.test_args)||Boolean(detail.launch?.test_args_json)||Boolean(detail.summary?.test_args_json)||Object.keys(smoke.test_kwargs||{}).length; |
| const byPhase=new Map(VALIDATION_PHASES.map(([id,label])=>[id,{step:id,label,status:'pending',message:''}])); |
| const mark=(id,status,message='')=>{ |
| const item=byPhase.get(id); |
| if(!item)return; |
| const rank={pending:0,running:1,success:2,warning:3,failed:4,manual_hardware_required:4,stopped:3}; |
| const next=status==='done'?'success':status; |
| if((rank[next]??0)>=(rank[item.status]??0)){item.status=next;item.message=message||item.message;} |
| }; |
| for(const e of events){ |
| const phase=validationEventPhase(e?.step); |
| if(!phase)continue; |
| mark(phase,validationPhaseStatusFromEventStatus(e.status),e.message||''); |
| } |
| if(events.length)mark('start','success'); |
| if(endpointNames.length||Object.keys(schema||{}).length){mark('api','success');mark('endpoint','success');} |
| if(hasPayload)mark('payload','success'); |
| const smokeStatus=String(smoke.status||detail.summary?.smoke_status||'').toLowerCase(); |
| if(smokeStatus){ |
| if(smokeStatus.includes('success')||smokeStatus.includes('passed'))mark('smoke','success',smoke.message||'Generation smoke passed'); |
| else if(smokeStatus.includes('failed')||smokeStatus.includes('error'))mark('smoke','failed',smoke.error||smoke.message||'Generation smoke failed'); |
| else if(smokeStatus.includes('warning')||smokeStatus.includes('partial'))mark('smoke','warning',smoke.error||smoke.message||'Generation not fully verified'); |
| } |
| const gateStatus=String(gate.status||detail.summary?.gate_status||'').toLowerCase(); |
| if(gateStatus){ |
| if(gateStatus.includes('success')||gateStatus.includes('passed'))mark('result','success'); |
| else if(gateStatus.includes('failed')||gateStatus.includes('error'))mark('result','failed'); |
| else if(gateStatus.includes('partial')||gateStatus.includes('warning')||gateStatus.includes('health'))mark('result','warning'); |
| } |
| if(canonical==='full_inference_success'){VALIDATION_PHASES.forEach(([id])=>mark(id,'success'));} |
| else if(canonical==='partial_validation'){['start','runtime','api','endpoint','payload','report'].forEach(id=>mark(id,'success'));mark('smoke','warning');mark('result','warning');} |
| else if(canonical==='failed'){ |
| mark('report','success'); |
| if(byPhase.get('smoke')?.status==='pending'&&byPhase.get('api')?.status==='success')mark('smoke','failed'); |
| if(byPhase.get('result')?.status==='pending')mark('result','failed'); |
| }else if(canonical==='manual_hardware_required'){mark('result','manual_hardware_required');} |
| else if(canonical==='stopped'||canonical==='stale'){mark('result','stopped');} |
| const firstPending=[...byPhase.values()].find(x=>x.status==='pending'); |
| if(canonical==='running'&&firstPending){mark(firstPending.step,'running');} |
| return VALIDATION_PHASES.map(([id])=>byPhase.get(id)); |
| } |
| 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 canonical=validationCanonicalStatus(detail); |
| if(canonical&&canonical!=='running')return 100; |
| const items=validationTimelineItems(detail); |
| let score=0; |
| items.forEach((item,idx)=>{ |
| const st=String(item.status||'').toLowerCase(); |
| if(st==='success'||st==='done'||st==='completed')score=Math.max(score,idx+1); |
| else if(st==='running')score=Math.max(score,idx+0.45); |
| else if(st==='warning'||st==='failed')score=Math.max(score,idx+1); |
| }); |
| return Math.max(0,Math.min(98,Math.round((score/items.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 endpointHumanText(value){ |
| if(value==null)return''; |
| if(typeof value==='string'||typeof value==='number'||typeof value==='boolean')return String(value); |
| if(Array.isArray(value))return value.map(endpointHumanText).filter(Boolean).join(', '); |
| if(typeof value==='object'){ |
| return firstNonEmpty(value.label,value.name,value.type,value.component,value.component_type,value.type_name,value.class_name,value.__type__,value.datatype,value.python_type,value.api_info?.type,value.value); |
| } |
| return String(value||''); |
| } |
| function endpointChoices(item={}){ |
| const raw=item.choices||item.options||item.api_info?.choices||item.component?.choices||item.component?.options||[]; |
| if(!Array.isArray(raw))return []; |
| return raw.map(choice=>endpointHumanText(choice)).filter(Boolean); |
| } |
| function endpointDefaultValue(item={}){ |
| const raw=item.default ?? item.value ?? item.example_input ?? item.api_info?.default ?? item.component?.value; |
| const text=endpointHumanText(raw); |
| if(!text||text==='[object Object]')return''; |
| return text; |
| } |
| 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:'',choices:[],defaultValue:'',optional:false}; |
| const name=endpointHumanText(item.parameter_name||item.label||item.name||item.id)||`arg${index}`; |
| const type=endpointHumanText(item.type||item.python_type||item.datatype||item.api_info?.type); |
| const component=endpointHumanText(item.component||item.component_type||item.type_name||item.label||item.api_info?.component); |
| const optional=item.optional===true||item.required===false||item.parameter_has_default===true; |
| return {name,type,component,choices:endpointChoices(item),defaultValue:endpointDefaultValue(item),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:'',choices:[],defaultValue:'',optional:false}; |
| const name=endpointHumanText(item.label||item.name||item.id)||`output${index}`; |
| const type=endpointHumanText(item.type||item.python_type||item.datatype||item.api_info?.type); |
| const component=endpointHumanText(item.component||item.component_type||item.type_name||item.label); |
| return {name,type,component,choices:endpointChoices(item),defaultValue:endpointDefaultValue(item),optional:false}; |
| }).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 endpointDetailText(item={}){ |
| const component=endpointHumanText(item.component||item.component_type||item.rendered_component); |
| const type=endpointHumanText(item.type||item.datatype); |
| const required=item.optional?'optional':'required'; |
| const parts=[component||type,component&&type&&component!==type?type:'',required].filter(Boolean); |
| const choices=Array.isArray(item.choices)?item.choices.filter(Boolean):[]; |
| if(choices.length)parts.push(`choices: ${choices.slice(0,4).join(', ')}${choices.length>4?'…':''}`); |
| if(item.defaultValue)parts.push(`default: ${item.defaultValue}`); |
| return parts.join(' · '); |
| } |
| function renderEndpointParamChips(items=[]){ |
| if(!items.length)return '<span class="muted">No parameters discovered yet.</span>'; |
| return items.slice(0,10).map(item=>{ |
| const name=endpointHumanText(item.name||item.label||item.id)||'param'; |
| const detail=endpointDetailText(item); |
| return `<em class="endpoint-param" title="${escapeHtml(detail||name)}"><strong>${escapeHtml(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; |
| const title=$('spaceTestContextTitle'); |
| const hint=$('spaceTestContextHint'); |
| 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(title)title.textContent=names.length?'Gradio API endpoints':'Linked validation context'; |
| if(hint)hint.textContent=names.length?'discovered by Gradio client':'Build Run context'; |
| 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>`:''; |
| const meta=`<div class="endpoint-meta"><span>${escapeHtml(selected)}</span><span>${details.inputs.length} input${details.inputs.length===1?'':'s'}</span><span>${details.outputs.length} output${details.outputs.length===1?'':'s'}</span></div>`; |
| root.innerHTML=`<div class="endpoint-chip-list">${chips}</div><div class="endpoint-info-card">${meta}${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||{});const resolvedSpaceUrl=contextTargetSpaceUrl(context,links);if(resolvedSpaceUrl&&!links.target_space_url)links.target_space_url=resolvedSpaceUrl;if(!links.target_space_settings_url&&links.target_space_url)links.target_space_settings_url=links.target_space_url+'/settings';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=Boolean(links.target_space_url&&contextIndicatesSpaceLinksReady(context,links));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'))setValidationLaunchEnabled();if(state.busy)setOperation(label,'loading')} |
| function setButtonBusy(id,isBusy,busyLabel){const btn=$(id);if(!btn)return;if(isBusy){if(!btn.classList.contains('is-busy')&&!btn.dataset.oldText)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;delete btn.dataset.oldText}if(id==='launchBuild')setBuildLaunchEnabled();else btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady;btn.classList.remove('is-busy')}} |
| function bucketFeedbackSummary(message=''){ |
| const text=String(message||'').trim(); |
| if(!text)return'Create your private run bucket before launching builds.'; |
| if(/Bucket Not Found|404 Client Error|Repository not found/i.test(text))return'Bucket not found. Create it to store runs and artifacts.'; |
| if(/permission|authenticated|token|401|403/i.test(text))return'Bucket is private or inaccessible with this token.'; |
| if(text.length>150)return text.slice(0,147)+'…'; |
| return text; |
| } |
| function setBucketFeedback(mode='checking',message='Checking private bucket access…'){ |
| const orb=$('bucketInlineOrb'); |
| const text=$('bucketFeedbackText'); |
| if(orb)orb.className='bucket-status-orb '+mode; |
| if(text){ |
| text.textContent=bucketFeedbackSummary(message); |
| text.title=String(message||''); |
| } |
| } |
| function renderAnonymousEvalStatus(me){ |
| const card=$('evalArchiveCard'); |
| const title=$('evalArchiveTitle'); |
| const text=$('anonymousEvalText'); |
| const enable=$('enableEvalArchive'); |
| const disable=$('disableEvalArchive'); |
| const verify=$('verifyEvalArchive'); |
| const flush=$('flushEvalArchive'); |
| const stateBadge=$('evalArchiveStateBadge'); |
| const cfg=me?.anonymous_eval||{}; |
| const enabled=Boolean(cfg.enabled); |
| const canManage=Boolean(cfg.can_manage)&&cfg.env_override!==true; |
| const readonly=Boolean(cfg.readonly)||Boolean(enabled&&!canManage); |
| const canFlush=Boolean(cfg.can_flush)&&enabled; |
| const bucket=cfg.bucket_source||cfg.proposed_bucket_source||'configured bucket'; |
| const path=cfg.bucket_path?'/'+cfg.bucket_path:(cfg.default_bucket_path?'/'+cfg.default_bucket_path:''); |
| const mount=cfg.job_mount_path||cfg.default_mount_path||'/evals'; |
| if(card){ |
| card.hidden=false; |
| card.className='eval-archive-panel '+(enabled?'on':'off')+(readonly?' readonly':'')+(canManage?' manageable':''); |
| } |
| if(title){ |
| title.textContent=enabled?'Anonymous eval archive':'Anonymous eval archive'; |
| } |
| if(stateBadge){ |
| const unavailable=String(cfg.reason||'')==='status_unavailable'; |
| stateBadge.textContent=enabled?'On':(unavailable?'Unknown':'Off'); |
| stateBadge.className='badge '+(enabled?'success':unavailable?'warn':'neutral'); |
| } |
| if(text){ |
| text.className='eval-feedback '+(enabled?'on':'off'); |
| if(enabled){ |
| const manager=cfg.managed_by==='you'?'You manage this instance setting.':`Managed by ${cfg.managed_by||'instance owner'}.`; |
| text.textContent=`On · ${bucket}${path} → ${mount}`; |
| text.title=`Anonymous project eval archive enabled from ${cfg.source||'config'}: ${bucket}${path} mounted at ${mount}. ${manager} ASF backend publishes anonymized eval records after user Jobs write local run evals.`; |
| }else if(String(cfg.reason||'')==='status_unavailable'){ |
| text.textContent='Status unavailable · could not verify eval archive configuration.'; |
| text.title='Refresh to retry the public eval archive status check. Unknown is not treated as Off.'; |
| }else if(me?.username){ |
| text.textContent='Mount an operator eval bucket at /evals, then enable publishing.'; |
| text.title='Recommended: private operator bucket, bucket path evals, mounted read-write at /evals on this ASF Space. Env vars still work as an admin override.'; |
| }else{ |
| text.textContent='Sign in as the instance owner to enable it.'; |
| text.title='Anonymous eval archive can be enabled by the instance owner or configured eval admins.'; |
| } |
| } |
| if(enable){ |
| enable.hidden=enabled||!me?.username||cfg.env_override===true||cfg.can_manage===false; |
| enable.disabled=!me?.username||state.busy; |
| enable.title=`Enable anonymous eval archive using ${cfg.proposed_bucket_source||'your namespace bucket'} mounted at ${cfg.default_mount_path||'/evals'}.`; |
| } |
| if(disable){ |
| disable.hidden=!enabled||!canManage; |
| disable.disabled=state.busy; |
| disable.title='Disable anonymous eval publishing for this ASF instance. Existing archived eval records are not deleted.'; |
| } |
| if(verify){ |
| verify.hidden=!enabled||!canManage; |
| verify.disabled=state.busy; |
| verify.title='Refresh and verify the eval archive configuration.'; |
| } |
| if(flush){ |
| flush.hidden=!canFlush; |
| flush.disabled=state.busy; |
| flush.title='Delete all anonymized eval records from the mounted operator archive. Config is preserved.'; |
| } |
| } |
| 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.':bucketFeedbackSummary(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 authLifetimeFromUser(me){ |
| const lifetime=me?.auth_lifetime||{}; |
| const expiresAt=lifetime.expires_at||me?.expires_at||null; |
| let seconds=Number.isFinite(Number(lifetime.seconds_until_expiry))?Number(lifetime.seconds_until_expiry):null; |
| if(seconds===null&&expiresAt){ |
| const ts=Date.parse(expiresAt); |
| if(Number.isFinite(ts))seconds=Math.floor((ts-Date.now())/1000); |
| } |
| let status=String(lifetime.status||'').toLowerCase(); |
| let severity=String(lifetime.severity||'').toLowerCase(); |
| if(!status){ |
| if(seconds===null)status='unknown'; |
| else if(seconds<=0)status='expired'; |
| else if(seconds<30*60)status='critical'; |
| else if(seconds<90*60)status='warning'; |
| else status='ok'; |
| } |
| if(!severity){ |
| severity=status==='ok'?'success':status==='warning'?'warn':(status==='critical'||status==='expired')?'error':'neutral'; |
| } |
| return {status,severity,seconds,expiresAt,label:lifetime.label||'',recommendation:lifetime.recommendation||''}; |
| } |
| function formatAuthRemaining(seconds){ |
| if(seconds===null||seconds===undefined||!Number.isFinite(Number(seconds)))return'expiry unknown'; |
| const total=Math.max(0,Math.floor(Number(seconds))); |
| const h=Math.floor(total/3600),m=Math.floor((total%3600)/60); |
| if(h)return`${h}h ${m}m left`; |
| if(m)return`${m}m left`; |
| return`${total}s left`; |
| } |
| function formatRecommendedMinutes(minutes){ |
| const total=Math.max(0,Math.floor(Number(minutes)||0)); |
| if(total>=60){const h=Math.floor(total/60),m=total%60;return`${h}h${m?` ${m}m`:''}`;} |
| return`${total}m`; |
| } |
| function compactList(items,limit=3){ |
| const clean=(items||[]).filter(Boolean).map(x=>String(x).trim()).filter(Boolean); |
| return {visible:clean.slice(0,limit),hidden:clean.slice(limit),all:clean}; |
| } |
| function renderPrescanDetails(summary,sections){ |
| const safeSections=(sections||[]).filter(s=>s&&s.items&&s.items.length); |
| if(!safeSections.length)return''; |
| return `<details class="prescan-details"><summary>${escapeHtml(summary||'Show details')}</summary>${safeSections.map(section=>`<div class="prescan-detail-section"><b>${escapeHtml(section.title)}</b><ul>${section.items.map(item=>`<li>${escapeHtml(item)}</li>`).join('')}</ul></div>`).join('')}</details>`; |
| } |
| function renderBuildRiskBlock(scan){ |
| const risk=scan?.build_risk||scan?.metadata?.build_risk||null; |
| if(!risk)return''; |
| const level=String(risk.level||'low').toLowerCase(); |
| const cls=level==='very_high'||level==='high'?'error':level==='medium'?'warn':'success'; |
| const label=risk.label||({very_high:'Very high risk',high:'High risk',medium:'Medium risk',low:'Low risk'}[level]||'Build risk'); |
| const minutes=Number(risk.recommended_session_minutes||Math.ceil(Number(risk.recommended_session_seconds||0)/60)||0); |
| const auth=authLifetimeFromUser(state.user); |
| const authKnown=auth.seconds!==null&&auth.seconds!==undefined&&Number.isFinite(Number(auth.seconds)); |
| const authMinutes=authKnown?Math.floor(Number(auth.seconds)/60):null; |
| const authShort=authKnown&&minutes>0&&authMinutes<minutes; |
| const authText=authKnown?formatAuthRemaining(auth.seconds):'expiry unknown'; |
| const authClass=authShort?'error':(auth.status==='unknown'?'warn':cls); |
| const authSummary=authShort?`${authText} · refresh sign-in`:(level==='high'||level==='very_high'?`${authText} · recommended ${formatRecommendedMinutes(minutes)}+`:authText); |
| const signals=compactList(risk.signals,3); |
| const mitigations=compactList(risk.mitigations||risk.recommendations,4); |
| const notes=signals.visible.length?signals.visible:[level==='low'?'No major build-risk signals':'Review before launch']; |
| const more=signals.hidden.length?`<span class="prescan-chip-more">+${signals.hidden.length} more</span>`:''; |
| const signalPills=`<div class="prescan-pill-row prescan-risk-signals">${notes.map(x=>`<span class="prescan-pill ${cls==='success'?'good':'risk'}"><b class="prescan-icon">${cls==='success'?'✓':'!'}</b>${escapeHtml(x)}</span>`).join('')}${more}</div>`; |
| const authAdvice=(authShort||level==='high'||level==='very_high')?`<div class="prescan-auth-risk ${authClass}"><b>HF Auth</b><span>${escapeHtml(authSummary)}</span></div>`:''; |
| const detailSections=[ |
| {title:'Detected signals',items:signals.all}, |
| {title:'Mitigations',items:mitigations.all}, |
| ]; |
| const details=renderPrescanDetails('Show build-risk details',detailSections); |
| return `<div class="prescan-build-risk ${cls}"><div class="prescan-build-risk-head"><b>Build risk</b><span>${escapeHtml(label)} · ${escapeHtml(formatRecommendedMinutes(minutes))}+ session</span></div>${authAdvice}${signalPills}${details}</div>`; |
| } |
|
|
| function renderKernelStrategyBlock(scan){ |
| const strategy=scan?.kernel_strategy||scan?.metadata?.kernel_strategy||null; |
| if(!strategy||!strategy.native_kernel_risk)return''; |
| const signals=compactList(strategy.signals,2); |
| const backends=compactList(strategy.candidate_backends,4); |
| const signalText=signals.visible.length?signals.visible.join(' · '):'Native kernel dependency detected'; |
| const more=signals.hidden.length?` <span class="prescan-chip-more">+${signals.hidden.length} more</span>`:''; |
| const backendText=backends.visible.length?`Suggested mitigation: ${backends.visible.join(', ')}`:'Suggested mitigation: use runtime-compatible backends before source builds'; |
| const details=renderPrescanDetails('Show native-kernel details',[ |
| {title:'Detected kernel signals',items:signals.all}, |
| {title:'Candidate backends',items:backends.all}, |
| {title:'Policy',items:[strategy.policy||'Prefer runtime-compatible backends before source-built native packages.']} |
| ]); |
| return `<div class="prescan-kernel-strategy warn"><div class="prescan-build-risk-head"><b>Native kernels</b><span>${escapeHtml(signalText)}${more}</span></div><div class="prescan-kernel-hint">${escapeHtml(backendText)}</div>${details}</div>`; |
| } |
|
|
| function authAwareBuildGate(scan=state.modelScan,me=state.user){ |
| const risk=scan?.build_risk||scan?.metadata?.build_risk||null; |
| const auth=authLifetimeFromUser(me); |
| if(!me?.username)return {blocking:false,severity:'neutral',message:'Sign in with Hugging Face to launch builds.',reason:'not_signed_in'}; |
| if(auth.status==='expired')return {blocking:true,severity:'error',message:'HF session expired. Sign in again before launching a build.',reason:'auth_expired'}; |
| if(!risk)return {blocking:false,severity:auth.severity||'neutral',message:auth.recommendation||'',reason:'no_model_risk'}; |
| const level=String(risk.level||'low').toLowerCase(); |
| const requiredMinutes=Number(risk.recommended_session_minutes||0); |
| const isHighRisk=['high','very_high'].includes(level); |
| const isRisky=['medium','high','very_high'].includes(level); |
| if(!isRisky||!requiredMinutes)return {blocking:false,severity:auth.severity||'neutral',message:auth.recommendation||'',reason:'low_model_risk'}; |
| const requiredText=formatRecommendedMinutes(requiredMinutes); |
| if(auth.seconds===null||auth.seconds===undefined||!Number.isFinite(Number(auth.seconds))){ |
| const msg=isHighRisk?`HF session expiry is unknown. This ${level.replace('_',' ')} model recommends ${requiredText}+ remaining; refresh sign-in before launch.`:'HF session expiry is unknown; refresh sign-in before long builds if this session is old.'; |
| return {blocking:false,severity:isHighRisk?'warn':'neutral',message:msg,reason:'auth_unknown_for_risky_model',recommended_minutes:requiredMinutes,risk_level:level}; |
| } |
| const remainingMinutes=Math.floor(Number(auth.seconds)/60); |
| if(remainingMinutes<requiredMinutes){ |
| return {blocking:false,severity:isHighRisk?'error':'warn',message:`HF session has ${formatAuthRemaining(auth.seconds)}. This ${level.replace('_',' ')} model recommends ${requiredText}+ remaining; refresh sign-in before launching.`,reason:'auth_short_for_model_risk',recommended_minutes:requiredMinutes,remaining_minutes:remainingMinutes,risk_level:level}; |
| } |
| return {blocking:false,severity:'success',message:`HF session looks safe for this ${level.replace('_',' ')} model (${formatAuthRemaining(auth.seconds)}; recommended ${requiredText}+).`,reason:'auth_sufficient_for_model_risk',recommended_minutes:requiredMinutes,remaining_minutes:remainingMinutes,risk_level:level}; |
| } |
|
|
| function authLifetimeLabel(info){ |
| if(!state.user?.username)return'HF Auth: sign in'; |
| if(info.status==='expired')return'HF Auth: expired'; |
| if(info.status==='unknown')return'HF Auth: expiry unknown'; |
| return`HF Auth: ${formatAuthRemaining(info.seconds)}`; |
| } |
| function renderAuthLifetimeBadge(me=state.user){ |
| const badge=$('authLifetimeBadge'); |
| const hint=$('authBuildHint'); |
| if(!me?.username){ |
| if(badge){badge.textContent='HF Auth: sign in';badge.className='auth-lifetime-badge neutral';badge.title='Sign in with Hugging Face to launch Jobs.';} |
| if(hint){hint.hidden=true;hint.textContent='';hint.className='auth-build-hint';} |
| return; |
| } |
| const info=authLifetimeFromUser(me); |
| const label=authLifetimeLabel(info); |
| if(badge){ |
| badge.textContent=label; |
| badge.className='auth-lifetime-badge '+(info.severity||'neutral'); |
| const expiry=info.expiresAt?`Expires at ${new Date(info.expiresAt).toLocaleString()}. `:''; |
| badge.title=expiry+(info.recommendation||'OAuth session lifetime for builds and linked validation.'); |
| } |
| if(hint){ |
| const launchGate=authAwareBuildGate(state.modelScan,me); |
| const visible=Boolean(launchGate.message)&&(['warning','critical','expired','unknown'].includes(info.status)||launchGate.reason==='auth_short_for_model_risk'||launchGate.reason==='auth_unknown_for_risky_model'||launchGate.blocking); |
| hint.hidden=!visible; |
| hint.className='auth-build-hint '+(launchGate.severity||info.severity||'neutral'); |
| if(visible)hint.textContent=launchGate.message||info.recommendation||''; |
| else hint.textContent=''; |
| } |
| } |
| function startAuthLifetimeTicker(){ |
| if(state.authLifetimeTimer)clearInterval(state.authLifetimeTimer); |
| renderAuthLifetimeBadge(state.user); |
| state.authLifetimeTimer=setInterval(()=>renderAuthLifetimeBadge(state.user),60000); |
| } |
| function clearClientAuthState(){ |
| if(state.poll){clearInterval(state.poll);state.poll=null;} |
| if(state.validationPoll){clearInterval(state.validationPoll);state.validationPoll=null;} |
| if(state.authLifetimeTimer){clearInterval(state.authLifetimeTimer);state.authLifetimeTimer=null;} |
| state.user=null; |
| state.bucketReady=false; |
| state.bucketSource=null; |
| state.runsCache=[]; |
| state.runId=null; |
| state.validationRunId=null; |
| state.activeLinks={}; |
| clearSavedRun(); |
| } |
| function handleLogoutClick(){ |
| clearClientAuthState(); |
| } |
|
|
| function configureAuthAnchor(anchor,href,{newTab=false}={}){ |
| if(!anchor)return; |
| if(href)anchor.href=href; |
| if(newTab){ |
| anchor.target='_blank'; |
| anchor.rel='noopener noreferrer'; |
| }else{ |
| anchor.removeAttribute('target'); |
| anchor.removeAttribute('rel'); |
| } |
| } |
| function authLoginUrl(){return '/oauth/huggingface/login'} |
| function authRefreshLoginUrl(){return '/auth/refresh-login'} |
| function authResetLocalUrl(){return '/auth/logout-local'} |
| function authLogoutUrl(){return '/auth/logout-local'} |
|
|
|
|
| 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 normalizeBuildRiskLevel(level){ |
| const clean=String(level||'low').toLowerCase().replace(/\s+/g,'_'); |
| if(['very_high','high','medium','low'].includes(clean))return clean; |
| return 'low'; |
| } |
| function buildRiskFromModelScan(scan={}){return scan?.build_risk||scan?.metadata?.build_risk||null} |
| function kernelStrategyFromModelScan(scan={}){return scan?.kernel_strategy||scan?.metadata?.kernel_strategy||null} |
| function buildPreScanTechnicalSections(scan={}){ |
| const good=(scan.good_signals||[]).filter(Boolean); |
| const risk=(scan.risk_signals||[]).filter(Boolean); |
| const recs=(scan.recommendations||[]).filter(Boolean); |
| const buildRisk=buildRiskFromModelScan(scan); |
| const kernel=kernelStrategyFromModelScan(scan); |
| const sections=[ |
| {title:'Checks passed',items:good}, |
| {title:'Scan signals',items:risk}, |
| {title:'Recommendations',items:recs}, |
| ]; |
| if(buildRisk){ |
| sections.push({title:'Build hints',items:[ |
| buildRisk.label||'', |
| ...(buildRisk.signals||[]), |
| ...(buildRisk.mitigations||buildRisk.recommendations||[]), |
| ].filter(Boolean)}); |
| } |
| if(kernel&&kernel.native_kernel_risk){ |
| sections.push({title:'Runtime hints',items:[ |
| ...(kernel.signals||[]), |
| ...(kernel.candidate_backends||[]).map(x=>`Candidate backend: ${x}`), |
| kernel.policy||'Prefer runtime-compatible backends before source-built native packages.', |
| ].filter(Boolean)}); |
| } |
| return sections; |
| } |
| function buildPreScanViewModel(scan=null){ |
| const model=normalizeModelInput(formValue('modelId')); |
| const isTransient=scan&&scan.transient===true; |
| const matches=scan&&((scan.ok&&normalizeModelInput(scan.model_id)===model)||isTransient); |
| if(!scan||!matches){ |
| return { |
| mode:'required',tone:'neutral',badgeLabel:'Required',headline:'Pre-scan required', |
| summary:'Run a quick metadata check before launching. Pi will use the result to prepare an autonomous build plan.', |
| facts:[{icon:'↗',label:'Scan type',value:'Metadata only'},{icon:'◇',label:'Weights',value:'No download'}], |
| automationNote:'This keeps the launch gate lightweight while the worker performs deeper runtime checks during the Job.', |
| actionWarnings:[],technicalSections:[],isScanning:false,ctaLabel:'Scan model',empty:true, |
| }; |
| } |
| const verdict=String(scan.verdict||'caution').toLowerCase(); |
| const tone=scanVerdictClass(verdict); |
| const output=outputTypeFromModelScan(scan); |
| const buildRisk=buildRiskFromModelScan(scan); |
| const kernel=kernelStrategyFromModelScan(scan); |
| const level=normalizeBuildRiskLevel(buildRisk?.level); |
| const minutes=Number(buildRisk?.recommended_session_minutes||Math.ceil(Number(buildRisk?.recommended_session_seconds||0)/60)||0); |
| const longBuild=minutes>=60||['medium','high','very_high'].includes(level); |
| const score=scan.score!==undefined&&scan.score!==null&&verdict!=='scanning'&&verdict!=='error'?`Metadata score ${scan.score}`:''; |
| const auth=authAwareBuildGate(scan,state.user); |
| const manualSignals=[...(scan.risk_signals||[]),...(buildRisk?.signals||[])].map(x=>String(x).toLowerCase()); |
| const mayNeedManualGpu=manualSignals.some(x=>x.includes('manual gpu')||x.includes('manual hardware')||x.includes('hardware selection')||x.includes('48gb')||x.includes('multi-gpu')); |
| const badgeMap={safe:'Ready',caution:'Review',risky:'Risky',unsupported:'Unsupported',scanning:'Scanning',error:'Scan failed'}; |
| let headline='Review before build'; |
| let summary=scan.summary||'Model metadata scan completed.'; |
| if(verdict==='safe'){ |
| headline=longBuild?'Ready, longer build expected':'Ready to build'; |
| summary=longBuild?'This model looks compatible with an autonomous Space build, but the session may take longer than usual.':'This model looks compatible with an autonomous Space build.'; |
| }else if(verdict==='caution'){ |
| headline=mayNeedManualGpu?'Ready, hardware may need review':'Ready, review before launch'; |
| summary='Pi can start the build, but review the visible launch expectations before continuing.'; |
| }else if(verdict==='risky'){ |
| headline='Review before build'; |
| summary='This model may need manual setup or additional validation. Confirm the risk acknowledgement before launching.'; |
| }else if(verdict==='unsupported'){ |
| headline='Not recommended for autonomous build'; |
| summary='This model is likely to need manual setup before a reliable autonomous build.'; |
| }else if(verdict==='scanning'){ |
| headline='Scanning model metadata'; |
| summary='Analyzing model card, Hub metadata and small config files. No model weights are downloaded.'; |
| }else if(verdict==='error'){ |
| headline='Pre-scan could not complete'; |
| summary='Try the scan again, or fix the issue before launching a Job.'; |
| } |
| const estimate=minutes?`${level==='low'?'Quick':level==='medium'?'Medium':level==='high'?'Long':'Very long'} · ${formatRecommendedMinutes(minutes)}+ possible`:(longBuild?'Longer session possible':'Standard session'); |
| const facts=[]; |
| if(output)facts.push({icon:'↳',label:'Output',value:output.charAt(0).toUpperCase()+output.slice(1)}); |
| if(scan.pipeline_tag)facts.push({icon:'◈',label:'Pipeline',value:scan.pipeline_tag}); |
| facts.push({icon:'⌁',label:'Build estimate',value:verdict==='scanning'?'Checking…':estimate}); |
| if(score)facts.push({icon:'✓',label:'Pre-scan',value:score}); |
| const actionWarnings=[]; |
| if(mayNeedManualGpu)actionWarnings.push('May require manual GPU or hardware review.'); |
| if(auth?.message&&(auth.reason==='auth_short_for_model_risk'||auth.reason==='auth_unknown_for_risky_model'||auth.blocking))actionWarnings.push(auth.message); |
| if(verdict==='unsupported')actionWarnings.push('Autonomous launch is blocked for unsupported pre-scan verdicts.'); |
| if(verdict==='error'&&scan.error)actionWarnings.push(scan.error); |
| const automationNote=verdict==='scanning' |
| ?'Pi will use this signal to prepare the worker strategy.' |
| :'Pi will adapt runtime settings and fallback strategies during the build.'; |
| return { |
| mode:verdict,tone,badgeLabel:badgeMap[verdict]||'Review',headline,summary, |
| facts:facts.slice(0,4),automationNote,actionWarnings, |
| technicalSections:buildPreScanTechnicalSections(scan),isScanning:verdict==='scanning', |
| ctaLabel:verdict==='scanning'?'Scanning…':(verdict==='error'?'Try scan again':'Scan again'), |
| }; |
| } |
| function renderPreScanFactCards(facts=[]){ |
| if(!facts.length)return''; |
| return `<div class="prescan-decision-facts prescan-compact-grid">${facts.map(f=>`<span class="prescan-decision-fact prescan-chip"><i>${escapeHtml(f.icon||'◇')}</i><em>${escapeHtml(f.label||'Info')}</em><strong>${escapeHtml(String(f.value||''))}</strong></span>`).join('')}</div>`; |
| } |
| function renderPreScanWarnings(items=[]){ |
| const clean=(items||[]).filter(Boolean); |
| if(!clean.length)return''; |
| return `<div class="prescan-action-warnings">${clean.slice(0,3).map(item=>`<div class="prescan-action-warning"><b>!</b><span>${escapeHtml(item)}</span></div>`).join('')}</div>`; |
| } |
| function renderPreScanTechnicalDetails(sections=[]){ |
| |
| return renderPrescanDetails('Show technical scan details',sections); |
| } |
| function renderPreScanSkeleton(){ |
| return '<div class="prescan-skeleton-grid"><span class="prescan-skeleton"></span><span class="prescan-skeleton"></span><span class="prescan-skeleton wide"></span></div>'; |
| } |
| function renderModelPreScan(scan=null){ |
| const card=$('modelPreScanCard'),badge=$('modelPreScanBadge'),signals=$('modelPreScanSignals'),ackWrap=$('modelRiskAckWrap'); |
| if(!card)return; |
| const vm=buildPreScanViewModel(scan); |
| const cls=vm.tone||'neutral'; |
| card.className=`model-prescan-card prescan-decision-card ${cls}${vm.isScanning?' is-scanning':''}${vm.mode==='required'?' required':''}`; |
| card.setAttribute('aria-busy',vm.isScanning?'true':'false'); |
| setText('modelPreScanTitle',vm.headline); |
| setText('modelPreScanSummary',vm.summary); |
| if(badge){badge.textContent=vm.badgeLabel;badge.className='badge '+cls} |
| if(signals){ |
| signals.hidden=false; |
| const body=[ |
| renderPreScanFactCards(vm.facts), |
| vm.isScanning?renderPreScanSkeleton():'', |
| vm.automationNote?`<div class="prescan-automation-note"><b>Automation</b><span>${escapeHtml(vm.automationNote)}</span></div>`:'', |
| renderPreScanWarnings(vm.actionWarnings), |
| renderPreScanTechnicalDetails(vm.technicalSections), |
| ].filter(Boolean).join(''); |
| signals.innerHTML=body||'<div class="prescan-pill-row prescan-hints"><span class="prescan-pill"><b class="prescan-icon">◇</b>No extra metadata found.</span></div>'; |
| } |
| if(ackWrap)ackWrap.hidden=!(String(scan?.verdict||'').toLowerCase()==='risky'); |
| const scanButton=$('scanModel'); |
| if(scanButton&&!scanButton.disabled)scanButton.textContent=vm.ctaLabel||'Scan model'; |
| renderAuthLifetimeBadge(state.user); |
| } |
| function resetModelPreScan(){state.modelScan=null;const ack=$('modelRiskAck');if(ack)ack.checked=false;renderModelPreScan(null);setBuildLaunchEnabled()} |
| function setBuildLaunchEnabled(){const btn=$('launchBuild');const err=validateBuildForm();const gate=authAwareBuildGate(state.modelScan,state.user);if(btn){btn.disabled=state.busy||Boolean(err);btn.title=err||((gate.reason==='auth_short_for_model_risk'||gate.reason==='auth_unknown_for_risky_model')?gate.message:'');}if($('buildGateText'))setText('buildGateText',err||((gate.reason==='auth_short_for_model_risk'||gate.reason==='auth_unknown_for_risky_model')?gate.message:'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…'); |
| renderModelPreScan({ok:true,transient:true,model_id:model,verdict:'scanning',summary:'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({ok:true,transient:true,model_id:model,verdict:'error',summary:'The model pre-scan could not complete. Fix the issue or try again before launching a Job.',error:e.message});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 authGate=authAwareBuildGate(state.modelScan,state.user);if(authGate.blocking)return authGate.message||'Refresh HF sign-in before launching.';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 validationTimeoutValue(){const raw=formValue('validateTimeout');if(!raw)return 60;const value=Number(raw);if(!Number.isFinite(value))return NaN;return Math.max(30,Math.min(3600,Math.round(value)))} |
| function validationPayloadSizeError(){const enc=typeof TextEncoder!=='undefined'?new TextEncoder():null;for(const [id,label] of [['testArgs','Test args'],['testKwargs','Test kwargs']]){const text=$(id)?.value||'';const bytes=enc?enc.encode(text).length:text.length;if(bytes>16000)return`${label} JSON is too large for this version (${bytes} bytes). Keep validation payloads under 16 KB and avoid pasted base64/files.`}return''} |
| function validateValidationForm(){ |
| const resolved=resolveLinkedValidationPayload(); |
| return resolved.launchable?'':(resolved.error||'Space Test is not ready to launch.'); |
| } |
| function setValidationLaunchEnabled(){const btn=$('launchValidate');if(!btn)return;const running=state.validationMode==='running'||Boolean(state.validationRunId&&state.validationDetail&&validationCanonicalStatus(state.validationDetail)==='running');const error=validateValidationForm();btn.disabled=Boolean(state.busy||running||error);btn.title=error||'';renderSpaceTestReadiness();} |
| function validationIsTerminalStatus(status){const canonical=String(status||'').toLowerCase();return ['full_inference_success','partial_validation','manual_hardware_required','technical_blocker','technical_blocker_boot_only','failed','stale','stopped','auth_refresh_required'].includes(canonical)} |
| function validationIsNonTerminalStatus(status){const canonical=String(status||'').toLowerCase();return !canonical||['running','pending','waiting','started','queued','building','unknown'].includes(canonical)} |
| function validationDetailRunId(detail={}){return detail.run_id||detail.summary?.run_id||detail.state?.run_id||detail.launch?.run_id||''} |
| function rememberTerminalValidation(detail={}){const runId=validationDetailRunId(detail)||state.validationRunId;const canonical=validationCanonicalStatus(detail);if(runId&&validationIsTerminalStatus(canonical)){state.validationTerminalByRun=state.validationTerminalByRun||{};state.validationTerminalByRun[runId]={canonical,detail:{...detail,_terminalLocked:true}};}return canonical} |
| function terminalValidationForRun(runId=state.validationRunId){return runId&&state.validationTerminalByRun?state.validationTerminalByRun[runId]||null:null} |
| function mergeValidationWithTerminalLock(incoming={},runId=state.validationRunId){const terminal=terminalValidationForRun(runId);if(!terminal)return incoming;const incomingCanonical=validationCanonicalStatus(incoming);if(validationIsNonTerminalStatus(incomingCanonical)){return {...(terminal.detail||{}),_refreshing:true,_ignoredIncomingStatus:incomingCanonical};}return incoming;} |
| function shouldIgnoreValidationUpdate(incoming={},runId=state.validationRunId){const incomingCanonical=validationCanonicalStatus(incoming);const terminal=terminalValidationForRun(runId);if(terminal&&incomingCanonical==='running')return true;return Boolean(terminal&&validationIsNonTerminalStatus(incomingCanonical));} |
| function extractEffectiveValidationPayload(detail={}){const smoke=detail.generation_smoke||detail.summary?.generation_smoke||detail.state?.generation_smoke||{};const payload=detail.validation_payload||detail.tests?.validation_payload||detail.summary?.validation_payload||{};const args=firstArrayPath(detail.effective_args,smoke.effective_args,payload.effective_args,smoke.test_args,payload.test_args);const kwargs=firstObjectPath(detail.effective_kwargs,smoke.effective_kwargs,payload.effective_kwargs,smoke.test_kwargs,payload.test_kwargs);const source=detail.args_source||smoke.args_source||payload.args_source||'';return {args,kwargs,source,argsWereAutofilled:Boolean(detail.args_were_autofilled||smoke.args_were_autofilled||payload.args_were_autofilled||source==='gradio_schema_auto_fill')};} |
| function applyEffectiveValidationArgs(detail={}){const payload=extractEffectiveValidationPayload(detail);if(!payload.argsWereAutofilled||!Array.isArray(payload.args))return;if(state.validationArgsDirty)return;setInputValue('testArgs',prettyJson(payload.args));if(payload.kwargs&&typeof payload.kwargs==='object'&&!Array.isArray(payload.kwargs))setInputValue('testKwargs',prettyJson(payload.kwargs));const note=$('spaceTestArgsNote');if(note){note.hidden=false;note.textContent='Arguments filled from discovered Gradio schema.';} } |
| 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 renderActiveRunEmptyState(){ |
| const timeline=$('timeline'); |
| if(timeline){timeline.dataset.signature='empty';timeline.className='timeline empty-timeline';timeline.innerHTML='<div class="empty compact-empty"><strong>No active run selected</strong><span>Launch a build or select a run from Runs Explorer. The timeline will appear once a run starts.</span></div>';if(timeline.style?.removeProperty)timeline.style.removeProperty('--timeline-phase-count');} |
| const note=$('timelineTraceNote');if(note){note.hidden=true;note.innerHTML='';note.className='timeline-trace-note';} |
| const details=$('timelinePhaseDetails');if(details){details.hidden=true;details.innerHTML='';details.className='timeline-phase-details';} |
| } |
| 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','—'],['detailTargetGpu','—'],['detailFallbackGpu','—'],['detailExpectedOutput','—'],['detailPiAssistant','—'],['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); |
| setText('activeLatencyValue','—');setText('activeZeroGpuDuration','—');setText('activeLatencySource','waiting for live smoke test');const latencyCard=$('activeLatencyCard');if(latencyCard){latencyCard.classList.remove('has-latency');latencyCard.classList.add('is-loading')} |
| 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','is-success','is-warn','is-error','is-running-state','is-stopped','is-neutral')}const cap=$('progressCaptionLabel');if(cap)cap.textContent='Overall progress';const sem=$('progressSemantics');if(sem){sem.hidden=true;sem.innerHTML=''}const bar=$('progressBar');if(bar)bar.setAttribute('aria-valuenow','0') |
| 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=''} |
| if(typeof clearEvalArchiveStatus==='function')clearEvalArchiveStatus('active-run-reset'); |
| for(const id of ['deleteActiveRun','activePrefillSpaceTest','deleteValidationRun']){const btn=$(id);if(btn)btn.disabled=true} |
| |
| renderActiveRunEmptyState(); |
| } function prepareNewBuild(){if(state.poll){clearInterval(state.poll);state.poll=null}state.runId=null;state.activeRunProgress=null;state.selectedRunDetail=null;state.documentCache={};state.latencyCache={};state.activeLinks={};state.pollErrors=0;clearSavedRun();document.querySelectorAll('.run-row').forEach(r=>r.classList.remove('selected'));setBuildModeNew();resetRunDetails();setText('progressPercent','0%');const cap=$('progressCaptionLabel');if(cap)cap.textContent='Overall progress';const sem=$('progressSemantics');if(sem){sem.hidden=true;sem.innerHTML=''}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 renderQuietLaunchState(result={}){setText('progressTitle','Starting build Job…');setText('progressStatus','Running');const statusEl=$('progressStatus');if(statusEl)statusEl.className='badge '+statusClass('running');setText('progressPercent','1%');const fill=$('progressFill');if(fill)fill.style.width='1%';setText('lastEvent','Build Job launched; waiting for the first canonical run snapshot.');setText('elapsed','—');setText('lastPolled','—');const timeline=$('timeline');if(timeline){timeline.innerHTML='<div class="timeline-skeleton">Waiting for first run snapshot…</div>';timeline.classList.remove('timeline-canonical');timeline.style.gridTemplateColumns=''}const note=$('timelineTraceNote');if(note){note.hidden=true;note.innerHTML=''}const details=$('timelinePhaseDetails');if(details){details.hidden=true;details.innerHTML='';details.className='timeline-phase-details'}} |
|
|
| function resetSpaceTestForNewBuild(runId=''){ |
| if(typeof stopValidationPolling==='function')stopValidationPolling(); |
| state.validationRequestSeq=(state.validationRequestSeq||0)+1; |
| state.validationPollToken=null; |
| state.validationRunId=null; |
| state.validationDetail=null; |
| state.validationMode='idle'; |
| state.validationPollErrors=0; |
| state.validationPrefillDraft=null; |
| state.linkedValidationParent=null; |
| state.validationArgsDirty=false; |
| const note=$('spaceTestArgsNote');if(note)note.hidden=true; |
| const readiness=$('spaceTestLaunchReadiness');if(readiness){readiness.hidden=false;readiness.className='validation-args-note launch-readiness blocked';readiness.textContent='Space Test will be available after this build reaches a final Space state.';} |
| setInputValue('validateSpace',''); |
| setInputValue('validateApi',''); |
| setInputValue('testArgs',prettyJson(defaultValidationArgs(formValue('buildExpectedOutput')||'image'))); |
| setInputValue('testKwargs','{}'); |
| setText('spaceTestEndpoint','—'); |
| setText('spaceTestOutput','Not launched'); |
| setText('validationStartedAt','Not launched'); |
| setText('spaceTestLatency','—'); |
| setText('spaceTestVerdict',runId?`Waiting for Build Run ${runId} to finish.`:'Waiting for a Build Run to finish.'); |
| const badge=$('spaceTestStatus');if(badge){badge.textContent='Blocked';badge.className='badge neutral';} |
| const tabDot=$('spaceTestTabDot');if(tabDot)tabDot.className='tab-status-dot neutral'; |
| const timeline=$('spaceTestTimeline');if(timeline){timeline.hidden=true;timeline.innerHTML='';} |
| setValidationProgressVisible(false); |
| const deleteBtn=$('deleteValidationRun');if(deleteBtn)deleteBtn.disabled=true; |
| const launchBtn=$('launchValidate');if(launchBtn){launchBtn.textContent='Run linked validation';launchBtn.disabled=true;launchBtn.title='Available after the build reaches a final Space state.';} |
| const endpoints=$('spaceTestEndpoints');if(endpoints){endpoints.innerHTML='<div class="linked-context-card linked-context-card--compact"><span>Space Test</span><strong>Waiting for current Build Run to finish</strong></div>';} |
| } |
|
|
|
|
| function clearValidationRunStateForBuildContext(parentRunId='',reason='build_selection'){ |
| if(typeof stopValidationPolling==='function')stopValidationPolling(); |
| state.validationRequestSeq=(state.validationRequestSeq||0)+1; |
| state.validationPollToken=null; |
| state.validationRunId=null; |
| state.validationDetail=null; |
| state.validationMode='idle'; |
| state.validationPollErrors=0; |
| state.validationArgsDirty=false; |
| state.validationPrefillDraft=null; |
| state.linkedValidationParent=parentRunId||null; |
| const note=$('spaceTestArgsNote');if(note)note.hidden=true; |
| setText('spaceTestOutput','Not launched'); |
| setText('validationStartedAt','Not launched'); |
| setText('spaceTestLatency','—'); |
| setText('spaceTestVerdict',parentRunId?`Linked to Build Run ${parentRunId}. No validation run launched yet.`:'Select a Build Run to prepare Space Test.'); |
| setText('spaceTestEndpoint','—'); |
| const timeline=$('spaceTestTimeline');if(timeline){timeline.hidden=true;timeline.innerHTML='';timeline.dataset.parentRunId=parentRunId||'';} |
| setValidationProgressVisible(false); |
| const deleteBtn=$('deleteValidationRun');if(deleteBtn){deleteBtn.disabled=true;deleteBtn.onclick=null;} |
| const tabDot=$('spaceTestTabDot');if(tabDot){tabDot.className='tab-status-dot neutral';tabDot.title='No active linked validation';} |
| const badge=$('spaceTestStatus');if(badge){badge.textContent='Preparing';badge.className='badge neutral';} |
| const card=$('spaceTestPreview');if(card){card.classList.remove('validation-running','validation-success','validation-error');card.classList.add('space-test-context-only');} |
| const readiness=$('spaceTestLaunchReadiness');if(readiness){readiness.hidden=false;readiness.className='validation-args-note launch-readiness blocked';readiness.textContent='Preparing Space Test context for the selected Build Run.';} |
| return true; |
| } |
| function requestSpaceTestContextResetForBuild(runId){ |
| state.spaceTestContextResetForRun=runId||true; |
| } |
|
|
| function setActiveRun(result){if(state.poll){clearInterval(state.poll);state.poll=null}resetSpaceTestForNewBuild(result?.run_id||'');resetRunDetails();state.documentCache={};state.latencyCache={};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(typeof renderRunStats==='function')renderRunStats();}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});renderQuietLaunchState(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();markPendingActiveFields();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,1500); |
| } |
| 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 refreshAnonymousEvalStatus(){ |
| try{ |
| const anonymous_eval=await apiGet('/api/eval-archive/status'); |
| renderAnonymousEvalStatus({anonymous_eval}); |
| return anonymous_eval; |
| }catch(e){ |
| renderAnonymousEvalStatus({anonymous_eval:{enabled:false,reason:'status_unavailable',managed_by:'instance owner'}}); |
| return null; |
| } |
| } |
| function isAuthExpiredError(error){return /expired|oauth_expired|session expired/i.test(String(error?.message||error||''))} |
| 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;startAuthLifetimeTicker();renderAnonymousEvalStatus(me);setText('userChip','@'+me.username);configureAuthAnchor($('userChip'),`https://huggingface.co/${me.username}`);const logout=$('logoutLink');if(logout){logout.hidden=false;logout.textContent='Reset auth';configureAuthAnchor(logout,me.logout_url||authLogoutUrl(),{newTab:false})}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){const expired=isAuthExpiredError(e);clearClientAuthState();await refreshAnonymousEvalStatus();setText('userChip',expired?'Refresh sign-in':'Sign in');configureAuthAnchor($('userChip'),expired?authRefreshLoginUrl():authLoginUrl(),{newTab:expired});const logout=$('logoutLink');if(logout){logout.hidden=false;logout.textContent='Reset auth';configureAuthAnchor(logout,authResetLocalUrl(),{newTab:false})}renderSignedOut({expired});renderBillingPanel(null);setBucketReady(false,expired?'HF OAuth expired. Refresh sign-in before continuing.':'Sign in with Hugging Face first.');showMessage(expired?'HF OAuth expired. Use Refresh sign-in to reconnect cleanly.':'Please sign in with Hugging Face to continue.',true)}} |
| function renderSignedOut({expired=false}={}){const auth=$('authPanel');if(!auth)return;auth.hidden=false;auth.className='auth-panel warning';if(expired){auth.innerHTML=`<strong>HF Auth expired</strong><p>Your Hugging Face OAuth session expired or is stale. Refresh sign-in clears the local OAuth session first, then starts a fresh authorization. Use Reset auth if the browser is still stuck.</p><a class="link-btn" href="${authRefreshLoginUrl()}" target="_blank" rel="noopener noreferrer">Refresh sign-in</a><a class="link-btn secondary" href="${authResetLocalUrl()}">Reset local auth</a>`;return}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="${authLoginUrl()}">Sign in with Hugging Face</a><a class="link-btn secondary" href="${authResetLocalUrl()}">Reset local auth</a>`} |
| function renderAuthWarnings(me){const auth=$('authPanel');if(!auth)return;const missing=me.missing_scopes||[];const warnings=[...(me.warnings||[])];const lifetime=authLifetimeFromUser(me);if(['warning','critical','expired'].includes(lifetime.status))warnings.push(lifetime.recommendation||authLifetimeLabel(lifetime));if(!missing.length&&!warnings.length){auth.hidden=true;auth.innerHTML='';return}auth.hidden=false;auth.className='auth-panel warning';const refresh=String(lifetime.status||'')==='expired'?authRefreshLoginUrl():authRefreshLoginUrl();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="${refresh}" target="_blank" rel="noopener noreferrer">Refresh sign-in</a><a class="link-btn secondary" href="${authResetLocalUrl()}">Reset local auth</a>`} |
| function renderBillingPanel(info){ |
| |
| 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 enableEvalArchive(){ |
| const me=state.user; |
| const cfg=me?.anonymous_eval||{}; |
| const bucketSource=cfg.proposed_bucket_source||(me?.username?`${me.username}/agentic-space-factory-evals`:null); |
| if(!bucketSource){showMessage('Sign in before enabling the eval archive.',true);return} |
| setButtonBusy('enableEvalArchive',true,'Enabling…'); |
| try{ |
| const result=await apiPost('/api/eval-archive/activate',{bucket_source:bucketSource,bucket_path:cfg.default_bucket_path||'evals',mount_path:cfg.default_mount_path||'/evals'}); |
| state.user={...(state.user||{}),anonymous_eval:result}; |
| renderAnonymousEvalStatus(state.user); |
| showMessage(result.message||`Eval archive enabled: ${bucketSource}`,'success'); |
| }catch(e){showMessage(e.message,true)} |
| finally{setButtonBusy('enableEvalArchive',false)} |
| } |
| async function verifyEvalArchive(){ |
| setButtonBusy('verifyEvalArchive',true,'Verifying…'); |
| try{ |
| const result=await apiGet('/api/eval-archive/status'); |
| state.user={...(state.user||{}),anonymous_eval:result}; |
| renderAnonymousEvalStatus(state.user); |
| showMessage(result.enabled?'Eval archive configuration verified.':'Eval archive is not enabled.',result.enabled?'success':'warning'); |
| }catch(e){showMessage(e.message,true)} |
| finally{setButtonBusy('verifyEvalArchive',false)} |
| } |
| async function disableEvalArchive(){ |
| const ok=window.confirm('Disable anonymous eval publishing for this ASF instance? Existing archived records will not be deleted.'); |
| if(!ok)return; |
| setButtonBusy('disableEvalArchive',true,'Disabling…'); |
| try{ |
| const result=await apiPost('/api/eval-archive/disable',{}); |
| state.user={...(state.user||{}),anonymous_eval:result}; |
| renderAnonymousEvalStatus(state.user); |
| showMessage(result.message||'Eval archive disabled.','success'); |
| }catch(e){showMessage(e.message,true)} |
| finally{setButtonBusy('disableEvalArchive',false)} |
| } |
|
|
|
|
| async function flushEvalArchive(){ |
| const ok=window.confirm('Delete all anonymized eval archive records from the mounted operator bucket? This preserves the eval archive configuration but cannot be undone.'); |
| if(!ok)return; |
| setButtonBusy('flushEvalArchive',true,'Flushing…'); |
| try{ |
| const result=await apiPost('/api/eval-archive/flush',{}); |
| state.user={...(state.user||{}),anonymous_eval:result}; |
| renderAnonymousEvalStatus(state.user); |
| showMessage(result.message||'Eval archive records flushed.',result.partial_flush?'warning':'success'); |
| }catch(e){showMessage(e.message,true)} |
| finally{setButtonBusy('flushEvalArchive',false)} |
| } |
|
|
|
|
| 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',options={}){ |
| const next=tab==='validate'?'validate':'active'; |
| if(options&&options.source==='user'){ |
| state.centerTabUserTouchedAt=Date.now(); |
| state.centerTabUserTouchedSelectionSeq=state.runSelectionSeq||0; |
| } |
| 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 firstArrayPath(...values){ |
| for(const value of values){ |
| if(Array.isArray(value))return value; |
| const parsed=parsedJsonOrNull(value); |
| if(Array.isArray(parsed))return parsed; |
| } |
| return null; |
| } |
| function firstObjectPath(...values){ |
| for(const value of values){ |
| if(value&&typeof value==='object'&&!Array.isArray(value))return value; |
| const parsed=parsedJsonOrNull(value); |
| if(parsed&&typeof parsed==='object'&&!Array.isArray(parsed))return parsed; |
| } |
| return null; |
| } |
| function testPayloadFromEndpointParameters(parameters=[],expected='text'){ |
| if(!Array.isArray(parameters)||!parameters.length)return null; |
| const args=[]; |
| for(const param of parameters){ |
| const name=String(param?.name||param?.parameter_name||param?.label||'').toLowerCase(); |
| const component=String(param?.component||param?.component_type||param?.type||'').toLowerCase(); |
| const choices=Array.isArray(param?.choices)?param.choices:(Array.isArray(param?.options)?param.options:[]); |
| if(name.includes('negative'))args.push(''); |
| else if(name.includes('prompt'))args.push(defaultValidationArgs(expected)[0]); |
| else if(name.includes('resolution')&&choices.length)args.push(String(choices[0])); |
| else if(name.includes('height'))args.push(Number(param?.default||1024)); |
| else if(name.includes('width'))args.push(Number(param?.default||1024)); |
| else if(name.includes('step'))args.push(Number(param?.default||9)); |
| else if(name.includes('guidance'))args.push(Number(param?.default||0)); |
| else if(name.includes('seed')&&component.includes('checkbox'))args.push(Boolean(param?.default??true)); |
| else if(name.includes('random')&&name.includes('seed'))args.push(Boolean(param?.default??true)); |
| else if(name.includes('seed'))args.push(Number(param?.default??42)); |
| else if(choices.length)args.push(String(choices[0])); |
| else if(component.includes('checkbox'))args.push(Boolean(param?.default??false)); |
| else if(component.includes('number')||component.includes('slider'))args.push(Number(param?.default??0)); |
| else args.push(param?.default??''); |
| } |
| return args.length?args: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 gate={...(detail.inference_gate||{}),...(progress.inference_gate||{})}; |
| const gateSmoke=gate.generation_smoke&&typeof gate.generation_smoke==='object'?gate.generation_smoke:{}; |
| const smoke={...(detail.state?.generation_smoke||{}),...(detail.tests?.generation_smoke||{}),...(detail.generation_smoke||{}),...(progress.generation_smoke||{}),...(progress.state?.generation_smoke||{}),...gateSmoke}; |
| const smokePayload=firstObjectPath( |
| detail.generation_smoke_payload_retry, |
| detail.tests?.generation_smoke_payload_retry, |
| progress.generation_smoke_payload_retry, |
| progress.tests?.generation_smoke_payload_retry, |
| detail.generation_smoke_payload, |
| detail.tests?.generation_smoke_payload, |
| progress.generation_smoke_payload, |
| progress.tests?.generation_smoke_payload |
| )||{}; |
| const launch={...(detail.launch||{}),...(progress.launch||{})}; |
| const stateObj={...(detail.state||{}),...(progress.state||{})}; |
| 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'; |
| const endpointParameters=smoke.parameters||smoke.endpoint_parameters||gate.endpoint_parameters||gate.smoke_parameters||[]; |
| let args=firstArrayPath(smoke.effective_args,smoke.test_args,smoke.args,smokePayload.effective_args,smokePayload.test_args,smokePayload.args,gate.effective_args,gate.test_args,gateSmoke.effective_args,gateSmoke.test_args,launch.test_args_json,summary.test_args_json); |
| if(!Array.isArray(args))args=testPayloadFromEndpointParameters(endpointParameters,expected); |
| let kwargs=firstObjectPath(smoke.effective_kwargs,smoke.test_kwargs,smoke.kwargs,smokePayload.effective_kwargs,smokePayload.test_kwargs,smokePayload.kwargs,gate.effective_kwargs,gate.test_kwargs,gateSmoke.effective_kwargs,gateSmoke.test_kwargs,launch.test_kwargs_json,summary.test_kwargs_json); |
| if(!Array.isArray(args))args=defaultValidationArgs(expected); |
| if(!kwargs||Array.isArray(kwargs)||typeof kwargs!=='object')kwargs={}; |
| const policy=detail.space_test_policy||detail.view?.space_test_policy||detail.space_test?.policy||progress.view?.space_test_policy||progress.view?.space_test?.policy||{}; |
| const target=policy.target_space||summary.target_space||stateObj.target_space||launch.target_space||progress.target_space||$('detailSpace')?.textContent||''; |
| const parentBuildRunId=policy.parent_build_run_id||detail.run_id||detail.summary?.run_id||state.runId||progress.run_id||''; |
| const automaticSmokePassed=Boolean(smoke.ok||smoke.status==='success'||policy.mode==='replay'||policy.allow_replay||policy.automatic_smoke_passed||policy.reason==='automatic_smoke_passed'||gate.strong_full_inference_success||summary.generation_smoke_passed||stateObj.generation_smoke_passed); |
| const smokeArtifactHasPayload=Boolean(smokePayload.effective_args||smokePayload.test_args||smokePayload.args||smoke.effective_args||smoke.test_args||smoke.args); |
| const policyContext={automaticSmokePassed,smokeArtifactPayloadAvailable:smokeArtifactHasPayload}; |
| const api=spaceTestPolicyBlocksGeneration(policy,policyContext)?'':(policy.requires_endpoint_discovery?'':normalizeApiName(policy.endpoint||smoke.api_name||gate.api_name||launch.api_name||gateSmoke.api_name||stateObj.primary_api_name||stateObj.promise_validation?.primary_api_name||progress.api_name||'/generate')); |
| const requiresEndpointDiscovery=Boolean(policy.requires_endpoint_discovery&&!spaceTestPolicyRuntimeProofAllowsGeneration(policy,policyContext)); |
| const payloadSource=requiresEndpointDiscovery?'endpoint_discovery_first':((smoke.effective_args||smoke.test_args||smoke.args||smokeArtifactHasPayload||automaticSmokePassed)?'automatic_smoke_artifact':(endpointParameters.length?'gradio_schema_defaults':'default_payload')); |
| return {target,api,expected,args,kwargs,parentBuildRunId,sourceRunId:parentBuildRunId,linkedTargetSpace:target,automaticSmokePassed,smokeLatency:smoke.latency_seconds||smoke.observed_latency_seconds||summary.latency_seconds||stateObj.latency_seconds,spaceTestPolicy:policy,requiresEndpointDiscovery,payloadSource,smokeArtifactPayloadAvailable:smokeArtifactHasPayload}; |
| } |
| function setValidationProgressVisible(visible){ |
| const show=Boolean(visible); |
| const caption=(document.querySelector?document.querySelector('.validation-progress-caption'):null); |
| const bar=(document.querySelector?document.querySelector('.validation-progress-bar'):null); |
| const timeline=$('spaceTestTimeline'); |
| if(caption)caption.hidden=!show; |
| if(bar)bar.hidden=!show; |
| if(timeline)timeline.hidden=!show; |
| } |
| function spaceTestLaunchLabel(prefill={}){ |
| const policy=prefill.spaceTestPolicy||prefill.space_test_policy||{}; |
| if(spaceTestPolicyDisabledByStaleBlocker(policy,prefill))return policy.label||'Space Test blocked'; |
| if(spaceTestPolicyAuthRecovery(policy))return'Retry linked validation'; |
| if((prefill.requiresEndpointDiscovery||policy.requires_endpoint_discovery)&&!spaceTestPolicyRuntimeProofAllowsGeneration(policy,prefill))return'Discover endpoints and validate'; |
| const mode=String(policy.mode||'').toLowerCase(); |
| if(mode==='replay'||prefill.automaticSmokePassed)return'Re-run linked validation'; |
| if(mode==='recover')return'Run recovery validation'; |
| return'Run linked validation'; |
| } |
| function smokeLatencyLabel(value){ |
| return value?`${formatLatencySeconds(value)} from automatic smoke`:'—'; |
| } |
| function spaceTestPolicyRuntimeProofAllowsGeneration(policy={},context={}){ |
| const reason=String(policy.reason||'').toLowerCase(); |
| const mode=String(policy.mode||'').toLowerCase(); |
| return Boolean(context.automaticSmokePassed||context.smokeArtifactPayloadAvailable||policy.allow_replay||policy.automatic_smoke_passed||policy.runtime_proof_authoritative||mode==='replay'||reason==='automatic_smoke_passed'||reason==='manual_validation_already_passed'); |
| } |
| function spaceTestPolicyBlocksGeneration(policy={},context={}){ |
| const reason=String(policy.reason||'').toLowerCase(); |
| if(spaceTestPolicyRuntimeProofAllowsGeneration(policy,context))return false; |
| return Boolean(policy.no_generation_endpoint||reason==='no_generation_endpoint_by_contract'||reason==='contract_declared_no_full_inference'); |
| } |
| function spaceTestPolicyDisabledByStaleBlocker(policy={},context={}){ |
| if(policy.enabled!==false)return false; |
| const reason=String(policy.reason||'').toLowerCase(); |
| if(spaceTestPolicyRuntimeProofAllowsGeneration(policy,context)&&(reason==='no_generation_endpoint_by_contract'||reason==='contract_declared_no_full_inference'))return false; |
| return true; |
| } |
| function spaceTestPolicyAuthRecovery(policy={}){ |
| const reason=String(policy.reason||'').toLowerCase(); |
| return Boolean(reason==='auth_refresh_required'||reason==='repair_validation_inconclusive_auth'); |
| } |
|
|
| function linkedValidationPayloadError(message,meta={}){ |
| return {launchable:false,error:message,...meta}; |
| } |
| function resolveLinkedValidationPayload(options={}){ |
| const draft=state.validationPrefillDraft||{}; |
| const policy=draft.spaceTestPolicy||draft.space_test_policy||{}; |
| const parentBuildRunId=draft.parentBuildRunId||draft.sourceRunId||state.linkedValidationParent||''; |
| if(!parentBuildRunId)return linkedValidationPayloadError('Select a completed Build Run before launching Space Test. Standalone validation is not supported.'); |
| const target=(formValue('validateSpace')||draft.target||draft.linkedTargetSpace||'').trim(); |
| if(!target)return linkedValidationPayloadError('This linked Space Test has no target Space. Select a Build Run with a generated Space first.',{parentBuildRunId}); |
| const linkedTarget=draft.target||draft.linkedTargetSpace||''; |
| if(linkedTarget&&target!==linkedTarget)return linkedValidationPayloadError('This Space Test is locked to the selected Build Run target Space. Standalone validation is not supported.',{parentBuildRunId,target}); |
| if(spaceTestPolicyDisabledByStaleBlocker(policy,draft))return linkedValidationPayloadError(policy.message||'This Build Run is not eligible for linked Space Test yet.',{parentBuildRunId,target,policy}); |
| if(state.user?.username&&target.includes('/')&&target.split('/')[0]!==state.user.username)return linkedValidationPayloadError(`For now, Space Test can only validate Spaces in your own namespace (${state.user.username}/...).`,{parentBuildRunId,target}); |
| const requiresEndpointDiscovery=Boolean((draft.requiresEndpointDiscovery||policy.requires_endpoint_discovery)&&!spaceTestPolicyRuntimeProofAllowsGeneration(policy,draft)); |
| const rawApi=(formValue('validateApi')||draft.api||policy.endpoint||'').trim(); |
| const apiName=requiresEndpointDiscovery?'':normalizeApiName(rawApi||'/generate'); |
| if(!requiresEndpointDiscovery&&!apiName)return linkedValidationPayloadError('No endpoint is selected for this linked validation.',{parentBuildRunId,target}); |
| const timeout=validationTimeoutValue(); |
| if(!Number.isFinite(timeout))return linkedValidationPayloadError('Timeout must be a number of seconds.',{parentBuildRunId,target}); |
| const sizeError=validationPayloadSizeError(); |
| if(sizeError)return linkedValidationPayloadError(sizeError,{parentBuildRunId,target}); |
| let args,kwargs; |
| try{ |
| args=parseJsonField('testArgs',Array.isArray(draft.args)?draft.args:[]); |
| kwargs=parseJsonField('testKwargs',(draft.kwargs&&typeof draft.kwargs==='object'&&!Array.isArray(draft.kwargs))?draft.kwargs:{}); |
| }catch(e){return linkedValidationPayloadError(`Invalid JSON payload: ${e.message}`,{parentBuildRunId,target});} |
| if(!Array.isArray(args))return linkedValidationPayloadError('Test args must be a JSON array because they are passed as positional arguments to gradio_client.predict(...).',{parentBuildRunId,target}); |
| if(!kwargs||Array.isArray(kwargs)||typeof kwargs!=='object')return linkedValidationPayloadError('Test kwargs must be a JSON object because they are passed as keyword arguments.',{parentBuildRunId,target}); |
| const expectedOutputType=formValue('expectedOutput')||draft.expected||'any'; |
| const validationMode=String(policy.mode||draft.validationMode||'complete').toLowerCase(); |
| const payloadSource=draft.payloadSource||(requiresEndpointDiscovery?'endpoint_discovery_first':(draft.automaticSmokePassed?'automatic_smoke':'linked_payload')); |
| return {launchable:true,parentBuildRunId,targetSpaceId:target,apiName,expectedOutputType,args,kwargs,timeout,validationMode,payloadSource,requiresEndpointDiscovery,policy}; |
| } |
| function renderSpaceTestReadiness(){ |
| const note=$('spaceTestLaunchReadiness'); |
| if(!note)return; |
| if(!state.validationPrefillDraft){note.hidden=true;note.textContent='';return;} |
| const resolved=resolveLinkedValidationPayload(); |
| note.hidden=false; |
| note.className='validation-args-note launch-readiness '+(resolved.launchable?'ready':'blocked'); |
| if(resolved.launchable){ |
| const discovery=resolved.requiresEndpointDiscovery?'Endpoint discovery will run before validation.':'Payload ready.'; |
| note.textContent=`${discovery} Source: ${String(resolved.payloadSource||'linked_payload').replace(/_/g,' ')}.`; |
| }else{ |
| note.textContent=resolved.error||'Space Test is not ready to launch.'; |
| } |
| } |
| function renderValidationStatus(data={}){ |
| const candidateRunId=validationDetailRunId(data)||state.validationRunId; |
| data=mergeValidationWithTerminalLock(data||state.validationDetail||{},candidateRunId); |
| state.validationDetail=data||state.validationDetail; |
| const summary=data.summary||{}; |
| const smoke=data.generation_smoke||data.summary?.generation_smoke||data.state?.generation_smoke||{}; |
| const canonical=validationCanonicalStatus(data); |
| const displayStatus={idle:'idle',full_inference_success:'full inference success',partial_validation:'partial validation',manual_hardware_required:'manual action required',failed:'failed',stale:'stale',stopped:'stopped',auth_refresh_required:'auth refresh required',running:'running'}[canonical]||canonical; |
| const klass=statusClass(displayStatus); |
| const isRunning=canonical==='running'; |
| const isIdle=canonical==='idle'; |
| const runIdForState=validationDetailRunId(data)||state.validationRunId; |
| if(runIdForState)rememberTerminalValidation(data); |
| applyEffectiveValidationArgs(data); |
| const badge=$('spaceTestStatus'); |
| if(badge){badge.textContent=badgeLabel(displayStatus);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||'—'; |
| const endpoint=normalizeApiName(data.api_name||summary.api_name||smoke.api_name||data.state?.api_name||state.validationPrefillDraft?.api||formValue('validateApi')||'')||'—'; |
| setText('spaceTestEndpoint',isIdle?'—':endpoint); |
| setText('spaceTestOutput',isIdle?'Not launched':runId); |
| setText('validationStartedAt',isIdle?'Not launched':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',isIdle?'—':(latency?formatLatencySeconds(latency):(canonical==='running'?'—':'Not recorded'))); |
| const diagnosis=data.validation_failure_diagnosis||data.summary?.validation_failure_diagnosis||data.state?.validation_failure_diagnosis||smoke.validation_failure_diagnosis||{}; |
| const diagnosisMsg=diagnosis.message||diagnosis.error||diagnosis.failure_reason||diagnosis.failure_type||smoke.error||summary.error||data.error||''; |
| const verdictText=isIdle?(data.message||'No validation run selected.'):data._refreshing?`${badgeLabel(displayStatus)} · Refreshing…`:(canonical==='failed'&&diagnosisMsg?`Failed · ${String(diagnosisMsg).slice(0,220)}`:badgeLabel(displayStatus)); |
| setText('spaceTestVerdict',verdictText); |
| const launchBtn=$('launchValidate'); |
| if(launchBtn&&!(launchBtn.classList?.contains&&launchBtn.classList.contains('is-busy'))){ |
| const draft=state.validationPrefillDraft||{}; |
| launchBtn.textContent=runId&&runId!=='—'&&!isRunning&&!isIdle?'Re-run linked validation':(draft.parentBuildRunId?spaceTestLaunchLabel(draft):'Launch validation'); |
| } |
| setValidationLaunchEnabled(); |
| const deleteBtn=$('deleteValidationRun'); |
| if(deleteBtn){deleteBtn.disabled=isIdle||!(runId&&runId!=='—')||isRunning;deleteBtn.onclick=(!deleteBtn.disabled)?()=>deleteRunById(runId,{kind:'validation',detail:data}):null;} |
| |
| const percent=validationProgressPercent(data); |
| setText('spaceTestProgressPercent',isIdle?'0%':`${Math.round(percent)}%`); |
| const fill=$('spaceTestProgressFill'); |
| if(fill){ |
| fill.style.width=isIdle?'0%':`${Math.max(0,Math.min(100,percent))}%`; |
| fill.className='validation-progress-fill '+(isRunning?'is-running':klass); |
| } |
| setValidationProgressVisible(!isIdle); |
| if(isIdle){const timeline=$('spaceTestTimeline');if(timeline)timeline.innerHTML='<div class="empty compact-empty">No validation run selected.</div>';const endpoints=$('spaceTestEndpoints');if(endpoints)endpoints.innerHTML='<div class="empty compact-empty">Endpoint schema will appear after a validation run.</div>';}else{renderValidationTimeline(data);renderValidationEndpoints(data);} |
| } |
| function resetValidationRunView({reason='idle'}={}){ |
| stopValidationPolling(); |
| state.validationMode='idle'; |
| state.validationRunId=null; |
| state.validationDetail=null; |
| state.validationPrefillDraft=null; |
| state.linkedValidationParent=null; |
| renderValidationStatus({status:'idle',run_id:'—',events:[],message:reason==='deleted'?'Validation run deleted.':'No validation run selected.'}); |
| const readiness=$('spaceTestLaunchReadiness');if(readiness){readiness.hidden=true;readiness.textContent='';} |
| setValidationLaunchEnabled(); |
| } |
|
|
| 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={}){ |
| const incomingRunId=detail.run_id||detail.summary?.run_id||state.validationRunId; |
| state.validationRunId=incomingRunId; |
| detail=mergeValidationWithTerminalLock(detail,incomingRunId); |
| state.validationMode='view'; |
| state.validationDetail=detail; |
| state.validationPrefillDraft=null; |
| rememberTerminalValidation(detail); |
| stopValidationPolling(); |
| renderValidationStatus(detail); |
| applyValidationPrefill(detail,'validation run',{preserveSelection:true}); |
| switchCenterTab('validate'); |
| if(state.validationRunId&&validationCanonicalStatus(detail)==='running')startValidationPolling(state.validationRunId); |
| } |
|
|
| function linkedValidationParentFromDetail(detail={}){ |
| return firstNonEmpty( |
| detail.parent_build_run_id, |
| detail.summary?.parent_build_run_id, |
| detail.state?.parent_build_run_id, |
| detail.launch?.parent_build_run_id, |
| state.linkedValidationParent, |
| state.validationPrefillDraft?.parentBuildRunId, |
| state.validationPrefillDraft?.sourceRunId |
| ); |
| } |
| async function refreshLinkedValidationParentAfterTerminal(detail={}){ |
| const parentId=linkedValidationParentFromDetail(detail); |
| if(!parentId)return; |
| try{ |
| const snapshot=await fetchRunViewSnapshotOnce(parentId,{includeJobLogs:false}); |
| if(snapshot){ |
| if(typeof rememberRunDetail==='function')rememberRunDetail(parentId,snapshot,true); |
| if(typeof upsertRunExplorerFromProgress==='function')upsertRunExplorerFromProgress(snapshot); |
| if(state.runId===parentId&&typeof renderRunViewSnapshot==='function')renderRunViewSnapshot(snapshot); |
| if(typeof renderRunStats==='function')renderRunStats(state.runsCache||[]); |
| } |
| }catch(err){console.warn('linked validation parent refresh failed',err)} |
| try{await loadRuns(true)}catch(err){console.warn('linked validation runs refresh failed',err)} |
| } |
|
|
| function stopValidationPolling(){ |
| if(state.validationPoll){clearInterval(state.validationPoll);state.validationPoll=null} |
| state.validationPollToken=null; |
| } |
| async function refreshValidationProgress(pollToken=state.validationPollToken){ |
| const requestedRunId=state.validationRunId; |
| if(!requestedRunId)return; |
| try{ |
| const p=await apiGet(`/api/runs/${encodeURIComponent(requestedRunId)}/progress?bucket_name=${encodeURIComponent(state.bucketName)}&include_job_logs=1`); |
| if(pollToken!==state.validationPollToken||requestedRunId!==state.validationRunId)return; |
| if(shouldIgnoreValidationUpdate(p,requestedRunId)){const terminal=terminalValidationForRun(requestedRunId);if(terminal){renderValidationStatus({...terminal.detail,_refreshing:true});stopValidationPolling();await refreshLinkedValidationParentAfterTerminal(terminal.detail);}return;} |
| state.validationPollErrors=0; |
| renderValidationStatus(p); |
| if(typeof upsertRunExplorerFromProgress==='function')upsertRunExplorerFromProgress(p); |
| const canonical=validationCanonicalStatus(p); |
| if(validationIsTerminalStatus(canonical)){state.validationMode='view';rememberTerminalValidation(p);stopValidationPolling();showMessage(`Validation finished: ${badgeLabel(canonical)}`,statusClass(canonical)==='success'?'success':statusClass(canonical)==='error'?'error':'warning');await refreshLinkedValidationParentAfterTerminal(p)} |
| }catch(e){ |
| if(pollToken!==state.validationPollToken||requestedRunId!==state.validationRunId)return; |
| state.validationPollErrors+=1; |
| console.warn('validation polling failed',e); |
| if(state.validationPollErrors<=2)showMessage(`Validation polling failed. Retrying…`,'warning'); |
| } |
| } |
| function startValidationPolling(runId){ |
| const terminal=terminalValidationForRun(runId); |
| if(terminal){stopValidationPolling();state.validationMode='view';state.validationRunId=runId;renderValidationStatus({...terminal.detail,_refreshing:true});refreshLinkedValidationParentAfterTerminal(terminal.detail);return;} |
| stopValidationPolling(); |
| state.validationMode='running'; |
| state.validationRunId=runId; |
| state.validationPollToken=`${runId}:${Date.now()}:${++state.validationRequestSeq}`; |
| switchCenterTab('validate'); |
| refreshValidationProgress(state.validationPollToken); |
| state.validationPoll=setInterval(()=>refreshValidationProgress(state.validationPollToken),2000); |
| } |
| function renderValidationPrefillDraft(prefill={},sourceLabel='selected Space'){ |
| stopValidationPolling(); |
| state.validationMode='draft'; |
| state.validationRunId=null; |
| state.validationDetail=null; |
| state.validationArgsDirty=false; |
| state.validationPrefillDraft={...prefill,sourceLabel,parentBuildRunId:prefill.parentBuildRunId||prefill.sourceRunId||state.runId||'',sourceRunId:prefill.sourceRunId||prefill.parentBuildRunId||state.runId||'',linkedTargetSpace:prefill.linkedTargetSpace||prefill.target||''}; |
| state.linkedValidationParent=state.validationPrefillDraft.parentBuildRunId||''; |
| const policy=state.validationPrefillDraft.spaceTestPolicy||{}; |
| const policyMode=String(policy.mode||'').toLowerCase(); |
| const policyLabel=policy.label||spaceTestLaunchLabel(prefill).replace(/^Re-run /,'Replay ').replace(/^Run /,''); |
| const badge=$('spaceTestStatus'); |
| if(badge){badge.textContent=policyLabel;badge.className='badge '+(spaceTestPolicyDisabledByStaleBlocker(policy,prefill)?'neutral':statusClass(policyMode||'info'))} |
| const tabDot=$('spaceTestTabDot'); |
| if(tabDot)tabDot.className='tab-status-dot '+(spaceTestPolicyDisabledByStaleBlocker(policy,prefill)?'neutral':statusClass(policyMode||'info')); |
| const card=$('spaceTestPreview'); |
| if(card)card.classList.remove('is-validating'); |
| setText('spaceTestEndpoint',prefill.requiresEndpointDiscovery?'Discovery first':(prefill.api||'—')); |
| setText('spaceTestOutput','Not launched'); |
| setText('validationStartedAt','Not launched'); |
| setText('spaceTestLatency',prefill.automaticSmokePassed?smokeLatencyLabel(prefill.smokeLatency):'—'); |
| setText('spaceTestVerdict',policy.message|| (prefill.automaticSmokePassed?`Automatic smoke test passed. You can replay the validated request.`:`Linked to ${sourceLabel}`)); |
| setText('spaceTestProgressPercent','0%'); |
| const fill=$('spaceTestProgressFill'); |
| if(fill){fill.style.width='0%';fill.className='validation-progress-fill neutral'} |
| setValidationProgressVisible(false); |
| const timeline=$('spaceTestTimeline'); |
| if(timeline){timeline.innerHTML='<div class="empty compact-empty">No linked validation launched yet.</div>';} |
| const title=$('spaceTestContextTitle'); |
| const hint=$('spaceTestContextHint'); |
| if(title)title.textContent='Linked validation context'; |
| if(hint)hint.textContent=spaceTestPolicyDisabledByStaleBlocker(policy,prefill)?'blocked by parent verdict':(spaceTestPolicyAuthRecovery(policy)?'refresh sign-in, then retry':(prefill.requiresEndpointDiscovery?'endpoint discovery first':'ready to replay or validate')); |
| const endpoints=$('spaceTestEndpoints'); |
| if(endpoints){ |
| const modeLabel=escapeHtml(policyLabel|| (prefill.automaticSmokePassed?'Replay automatic smoke':'Linked Space Test')); |
| const endpointLine=spaceTestPolicyBlocksGeneration(policy,prefill)?'No generation endpoint exists.':(prefill.requiresEndpointDiscovery?'Endpoints will be discovered before validation.':`<code>${escapeHtml(prefill.api||'—')}</code>`); |
| const targetLine=prefill.target?`<code>${escapeHtml(prefill.target)}</code>`:'—'; |
| const latencyLine=prefill.automaticSmokePassed&&prefill.smokeLatency?escapeHtml(smokeLatencyLabel(prefill.smokeLatency)):'—'; |
| const payloadSource=escapeHtml(String(prefill.payloadSource||'linked_payload').replace(/_/g,' ')); |
| const readiness=spaceTestPolicyDisabledByStaleBlocker(policy,prefill)?'Blocked':(spaceTestPolicyAuthRecovery(policy)?'Ready after sign-in refresh':(prefill.requiresEndpointDiscovery?'Discovery first':'Ready to launch')); |
| endpoints.innerHTML=`<div class="linked-context-card linked-context-card--pretty"><span>Linked Build Run</span><strong><code>${escapeHtml(state.validationPrefillDraft.parentBuildRunId||'—')}</code></strong><span>Target Space</span><strong>${targetLine}</strong><span>Mode</span><strong>${modeLabel}</strong><span>Endpoint</span><strong>${endpointLine}</strong><span>Payload source</span><strong>${payloadSource}</strong><span>Automatic smoke</span><strong>${latencyLine}</strong><span>Launch readiness</span><strong>${escapeHtml(readiness)}</strong></div>`; |
| } |
| const note=$('spaceTestArgsNote'); |
| if(note){note.hidden=false;note.textContent=spaceTestPolicyDisabledByStaleBlocker(policy,prefill)?(policy.message||'Space Test is blocked by the parent Build Run verdict.'):(spaceTestPolicyAuthRecovery(policy)?'Refresh sign-in, then retry linked validation.':`Payload source: ${String(prefill.payloadSource||'linked_payload').replace(/_/g,' ')}.`);} |
| const launchBtn=$('launchValidate'); |
| if(launchBtn&&!launchBtn.classList.contains('is-busy'))launchBtn.textContent=spaceTestLaunchLabel(prefill); |
| setValidationLaunchEnabled(); |
| const deleteBtn=$('deleteValidationRun'); |
| if(deleteBtn){deleteBtn.disabled=true;deleteBtn.onclick=null;} |
| if(typeof renderRunsFromCache==='function')renderRunsFromCache(); |
| } |
| function applyValidationPrefill(detail={},sourceLabel='selected Space',options={}){ |
| 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)); |
| state.validationArgsDirty=false; |
| const note=$('spaceTestArgsNote');if(note)note.hidden=true; |
| switchCenterTab('validate'); |
| if(!options.preserveSelection)renderValidationPrefillDraft(prefill,sourceLabel); |
| 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 run',{preserveSelection:false}); |
| } |
| function contextualizeSpaceTestFromBuildRun(detail={}){ |
| if(!detail||typeof detail!=='object')return; |
| if(typeof isValidationRunDetail==='function'&&isValidationRunDetail(detail))return; |
| const prefill=validationPrefillFromDetail(detail); |
| if(!prefill.parentBuildRunId||!prefill.target||prefill.target==='—')return; |
| const nextParent=String(prefill.parentBuildRunId||''); |
| const currentParent=String(state.linkedValidationParent||state.validationPrefillDraft?.parentBuildRunId||''); |
| const explicitReset=state.spaceTestContextResetForRun===true||state.spaceTestContextResetForRun===nextParent; |
| const parentChanged=Boolean(currentParent&&nextParent&¤tParent!==nextParent); |
| if(explicitReset||parentChanged){ |
| clearValidationRunStateForBuildContext(nextParent,'build_selection'); |
| state.spaceTestContextResetForRun=null; |
| }else if(state.validationMode==='running'&¤tParent===nextParent){ |
| return; |
| } |
| state.validationPrefillDraft={...prefill,sourceLabel:'selected Build Run',parentBuildRunId:prefill.parentBuildRunId,sourceRunId:prefill.parentBuildRunId,linkedTargetSpace:prefill.target}; |
| state.linkedValidationParent=prefill.parentBuildRunId; |
| state.validationArgsDirty=false; |
| setInputValue('validateSpace',prefill.target); |
| setInputValue('expectedOutput',prefill.expected); |
| setInputValue('validateApi',prefill.api); |
| setInputValue('testArgs',prettyJson(prefill.args)); |
| setInputValue('testKwargs',prettyJson(prefill.kwargs)); |
| const note=$('spaceTestArgsNote');if(note){note.hidden=false;note.textContent=`Payload source: ${String(prefill.payloadSource||'linked_payload').replace(/_/g,' ')}.`;} |
| if(state.centerTab==='validate'){ |
| renderValidationPrefillDraft(prefill,'selected Build Run'); |
| }else{ |
| const title=$('spaceTestContextTitle'); |
| const hint=$('spaceTestContextHint'); |
| if(title)title.textContent='Linked validation context'; |
| if(hint)hint.textContent=spaceTestPolicyDisabledByStaleBlocker(prefill.spaceTestPolicy||{},prefill)?'blocked by parent verdict':(spaceTestPolicyAuthRecovery(prefill.spaceTestPolicy||{})?'refresh sign-in, then retry':(prefill.requiresEndpointDiscovery?'endpoint discovery first':'ready to replay or validate')); |
| const endpoints=$('spaceTestEndpoints'); |
| if(endpoints){ |
| const modeLabel=escapeHtml(prefill.spaceTestPolicy?.label|| (prefill.automaticSmokePassed?'Replay automatic smoke':'Linked Space Test')); |
| const endpointLine=spaceTestPolicyBlocksGeneration(prefill.spaceTestPolicy||{},prefill)?'No generation endpoint exists.':(prefill.requiresEndpointDiscovery?'Endpoints will be discovered before validation.':`<code>${escapeHtml(prefill.api||'—')}</code>`); |
| endpoints.innerHTML=`<div class="linked-context-card linked-context-card--compact"><span>Linked Build Run</span><strong><code>${escapeHtml(prefill.parentBuildRunId)}</code></strong><span>Mode</span><strong>${modeLabel}</strong><span>Endpoint</span><strong>${endpointLine}</strong></div>`; |
| } |
| } |
| setValidationLaunchEnabled(); |
| } |
| function prepareValidationFromSelectedRun(){ |
| applyValidationPrefill(state.selectedRunDetail||{},'selected run',{preserveSelection:false}); |
| } |
| 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,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,try_zero_gpu_first:$('tryZeroGpu')?.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 resolved=resolveLinkedValidationPayload(); |
| if(!resolved.launchable){showMessage(resolved.error||'Space Test is not ready to launch.',true);setValidationLaunchEnabled();return} |
| setBusy(true,'Launching validation Job…'); |
| setButtonBusy('launchValidate',true,'Launching…'); |
| try{ |
| const payload={ |
| bucket_name:state.bucketName, |
| parent_build_run_id:resolved.parentBuildRunId, |
| target_space_id:resolved.targetSpaceId, |
| api_name:resolved.apiName, |
| expected_output_type:resolved.expectedOutputType, |
| test_args_json:prettyJson(resolved.args), |
| test_kwargs_json:prettyJson(resolved.kwargs), |
| live_timeout_seconds:resolved.timeout, |
| validation_mode:resolved.validationMode, |
| payload_source:resolved.payloadSource, |
| ui_payload_arg_count:Array.isArray(resolved.args)?resolved.args.length:0, |
| ui_payload_kwargs_keys:resolved.kwargs&&typeof resolved.kwargs==='object'?Object.keys(resolved.kwargs):[] |
| }; |
| const result=await apiPost('/api/validate',payload); |
| state.validationArgsDirty=false; |
| state.validationMode='running'; |
| state.validationRunId=result.run_id; |
| 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); |
| setValidationLaunchEnabled(); |
| } |
| } |
| 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);markPendingActiveFields(); |
| 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;try{if(typeof renderRunViewSnapshot==='function')renderRunViewSnapshot(p);else renderProgress(p);}catch(renderErr){console.warn('renderRunViewSnapshot failed; keeping events visible',renderErr);try{renderProgress(p)}catch(_){}}renderEvents(p.view?.activity||p.events_recent||p.events||[]);renderMetrics(p);if(typeof upsertRunExplorerFromProgress==='function')upsertRunExplorerFromProgress(p);if(typeof renderRunStats==='function')renderRunStats();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||'—')}const canonicalUi=typeof canonicalRunUiState==='function'?canonicalRunUiState(p):{label:badgeLabel(p.view?.header?.status||p.status||p.state?.status||'running'),status:p.view?.header?.status||p.status||p.state?.status||'running'};setText('detailJobStatus',canonicalUi.label||badgeLabel(canonicalUi.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',canonicalUi.label||badgeLabel(canonicalUi.status||'running'));setQuickLinks({...p.links,...p.view?.links,...p.space_link_state,...p.view?.space_link_state,target_space_url:p.summary?.target_space_url||p.state?.target_space_url||p.links?.target_space_url||p.view?.links?.target_space_url,target_space:p.summary?.target_space||p.state?.target_space||p.links?.target_space||p.view?.links?.target_space,bucket_source:p.bucket_source,run_id:p.run_id},{...p,space_identity:p.space_identity||p.view?.space_link_state||p.space_link_state||{},space_link_state:p.space_link_state||p.view?.space_link_state||{},events:p.events||[]});saveActiveRun();const modelProgress=p.view?.timeline_model?.progress||{};const terminalSnapshot=modelProgress.terminal===true||p.view?.status_model?.is_terminal===true;const canonicalForTerminal=typeof canonicalRunUiState==='function'?canonicalRunUiState(p):null;const status=String(canonicalForTerminal?.status||p.view?.header?.status||p.status||p.state?.status||'').toLowerCase();if((terminalSnapshot||TERMINAL_STATUSES.has(status))&&state.poll){clearInterval(state.poll);state.poll=null;if(state.lastReportRefreshRunId!==state.runId){ |
| state.lastReportRefreshRunId=state.runId; |
| const finalRunId=state.runId; |
| setTimeout(()=>{if(state.runId===finalRunId)refreshRunReportPreview(finalRunId)},120); |
| setTimeout(()=>{if(state.runId===finalRunId)refreshRunReportPreview(finalRunId)},900); |
| setTimeout(()=>{if(state.runId===finalRunId)refreshRunReportPreview(finalRunId)},2200); |
| setTimeout(()=>{if(state.runId===finalRunId&&state.documentCache)state.documentCache[finalRunId]=mergeStableRunDocuments(finalRunId,state.documentCache[finalRunId]||[])},2250); |
| setTimeout(()=>loadRuns(true),160); |
| setTimeout(()=>loadRuns(true),980); |
| setTimeout(()=>loadRuns(true),2300); |
| }{const visual=String(modelProgress.visual_status||'').toLowerCase();const type=visual==='success'?'success':visual==='error'?'error':(status.includes('success')||status.includes('succeed')?'success':status.includes('failed')||status.includes('blocker')?'error':'warning');setOperation(`Run finished with status: ${badgeLabel(modelProgress.verdict||status)}`,type)}}markPendingActiveFields();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 metrics=p.validation_metrics||p.view?.validation_metrics||p.summary?.validation_metrics||{}; |
| 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 manual=p.manual_validation_status||summary.manual_validation_status||p.view?.manual_validation||{}; |
| const validation=gate.validation||summary.validation||{}; |
| 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(metrics.latency_seconds,metrics.observed_latency_seconds,smoke.latency_seconds,smoke.observed_latency_seconds,summary.latency_seconds,summary.observed_latency_seconds,p.latency_seconds,p.observed_latency_seconds,manual.latency_seconds,manual.observed_latency_seconds,eventLatency); |
| const duration=firstPositiveNumber(metrics.recommended_zero_gpu_duration_seconds,metrics.recommended_zerogpu_duration_seconds,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,manual.recommended_zero_gpu_duration_seconds,gate.recommended_zerogpu_duration_seconds,gate.recommended_zero_gpu_duration_seconds,gate.zero_gpu_duration_recommendation,eventDuration); |
| const source=metrics.source||smoke.recommendation_source||summary.recommendation_source||manual.recommendation_source||gate.recommendation_source||(manual.status==='success'?'linked Space Test':'')||(eventLatency?'generation_smoke event':''); |
| const hardware=metrics.hardware_used_for_validation||metrics.recommendation_hardware||manual.hardware_used_for_validation||manual.recommendation_hardware||summary.hardware_used_for_validation||summary.recommendation_hardware||p.hardware_used_for_validation||p.recommendation_hardware||p.state?.selected_hardware||summary.selected_hardware||''; |
| const failureType=smoke.failure_type||validation.failure_type||gate.failure_type||''; |
| const failureOwner=smoke.failure_owner||validation.failure_owner||gate.failure_owner||''; |
| const missingArgument=smoke.missing_argument||validation.missing_argument||gate.missing_argument||''; |
| const error=smoke.error||smoke.message||validation.failure_reason||gate.message||''; |
| return {smoke,gate,metrics,latency,duration,source,hardware,failureType,failureOwner,missingArgument,error}; |
| } |
| function smokeFailureLatencyNote({smoke={},failureType='',failureOwner='',missingArgument='',error=''}={}){ |
| const status=String(smoke.status||'').toLowerCase(); |
| if(!status||status==='success')return ''; |
| if(missingArgument){ |
| return `Not measured because generation smoke failed before inference started. Missing required argument: ${missingArgument}.`; |
| } |
| if(failureType==='validator_request_error'||failureOwner==='factory_validator'){ |
| return 'Not measured because ASF sent an invalid smoke-test request before live inference could start.'; |
| } |
| if(failureType==='app_runtime_error'||failureOwner==='generated_space'){ |
| return 'Not measured because the generated Space raised an error during the live generation smoke test.'; |
| } |
| if(String(error||'').trim()){ |
| return `Not measured because generation smoke failed: ${shortText(error,120)}`; |
| } |
| return 'Not measured because the generation smoke test did not produce a verified output.'; |
| } |
| function renderActiveLatency(p={}){ |
| const card=$('activeLatencyCard'); |
| if(!card)return; |
| const runId=String(p.run_id||p.summary?.run_id||state.runId||''); |
| state.latencyCache=state.latencyCache||{}; |
| const payload=readRunLatencyPayload(p); |
| const {latency,duration,source,hardware}=payload; |
| const hasLatency=Number.isFinite(latency)&&latency>0; |
| const hasDuration=Number.isFinite(duration)&&duration>0; |
| const cached=state.latencyCache[runId]||{}; |
| const stableLatency=hasLatency?latency:cached.latency; |
| const stableDuration=hasDuration?duration:cached.duration; |
| const runStatus=String(p.status||p.state?.status||p.summary?.status||p.view?.header?.status||'').toLowerCase(); |
| const finalBlocker=p.build_error_observation?' Final blocker: build error after repair.':''; |
| const failedWithoutSmoke=(runStatus==='failed'||runStatus==='error')&&!hasLatency; |
| const failureNote=smokeFailureLatencyNote(payload)||(failedWithoutSmoke?`Not measured because live validation did not reach a successful generation smoke test.${finalBlocker}`:''); |
| const hardwareSuffix=hardware&&String(hardware).toLowerCase()!=='unknown'?` on ${hardware}`:''; |
| const stableSource=hasLatency?`${source||'live smoke test'}${hardwareSuffix}`:(failureNote?'not measured: smoke failed':(cached.source||'measured when smoke test passes')); |
| if(hasLatency||hasDuration)state.latencyCache[runId]={latency:stableLatency,duration:stableDuration,source:stableSource}; |
| const finalHasLatency=Number.isFinite(stableLatency)&&stableLatency>0; |
| const finalHasDuration=Number.isFinite(stableDuration)&&stableDuration>0; |
| setText('activeLatencyValue',finalHasLatency?formatLatencySeconds(stableLatency):'—'); |
| setText('activeZeroGpuDuration',finalHasDuration?fmtSeconds(Math.round(stableDuration)):'—'); |
| setText('activeLatencySource',stableSource); |
| const note=$('activeLatencyNote'); |
| if(note){ |
| note.textContent=finalHasDuration |
| ? `This measured latency is normally used to tune the \`@spaces.GPU(duration=...)\` decorator for ZeroGPU when the generated Space uses ZeroGPU. Source hardware: ${hardware||'not recorded'}.` |
| : (failureNote||'When available, the live smoke-test latency is used to recommend the `@spaces.GPU(duration=...)` value for ZeroGPU apps.'); |
| } |
| card.classList.toggle('has-latency',finalHasLatency); |
| card.classList.toggle('is-loading',!finalHasLatency); |
| } |
| 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 shortModelLabel(value){ |
| const raw=String(value||'').trim(); |
| if(!raw)return '—'; |
| return raw.length>30?raw.split('/').slice(-1)[0]||raw:raw; |
| } |
| function piAssistantResolution(p={}){ |
| const summary=p.summary||{}; |
| const stateObj=p.state||{}; |
| const launch=p.launch||{}; |
| const resolution=p.pi_model_resolution||stateObj.pi_model_resolution||summary.pi_model_resolution||{}; |
| const requested=resolution.requested_model||stateObj.pi_model||launch.pi_model||summary.pi_model||''; |
| const effective=resolution.effective_model||resolution.configured_model||requested||''; |
| const mismatch=Boolean(resolution.mismatch&&effective&&requested&&normalizeComparableModelName(effective)!==normalizeComparableModelName(requested)); |
| return {requested,effective,mismatch,provider:resolution.provider||'huggingface',source:resolution.source||''}; |
| } |
| function normalizeComparableModelName(value){ |
| const raw=String(value||'').trim().toLowerCase(); |
| const tail=raw.includes('/')?raw.split('/').pop():raw; |
| return tail.replace(/[^a-z0-9]+/g,''); |
| } |
| function renderPiAssistantMeta(p={}){ |
| const el=$('detailPiAssistant'); |
| if(!el)return; |
| const info=piAssistantResolution(p); |
| const requested=shortModelLabel(info.requested); |
| const effective=shortModelLabel(info.effective); |
| el.textContent=info.mismatch?`${requested} → ${effective}`:(effective||requested||'—'); |
| el.title=info.mismatch?`Requested ${info.requested||'—'}; effective provider model ${info.effective||'—'}`:`Requested/effective assistant: ${info.effective||info.requested||'—'}`; |
| el.classList.toggle('warn-text',Boolean(info.mismatch)); |
| } |
| function renderActiveRunMeta(p={}){ |
| const meta=runHardwareMeta(p); |
| setText('detailTargetGpu',meta.target||'—'); |
| setText('detailFallbackGpu',meta.fallback||'—'); |
| setText('detailExpectedOutput',meta.output||'—'); |
| renderPiAssistantMeta(p); |
| } |
| function markPendingActiveFields(){ |
| ['detailRunId','detailSpace','selectedModelId','detailTargetGpu','detailFallbackGpu','detailExpectedOutput','detailPiAssistant','currentStep','elapsed'].forEach(id=>{ |
| const el=$(id); |
| if(!el)return; |
| const pending=!String(el.textContent||'').trim()||String(el.textContent||'').trim()==='—'; |
| el.classList.toggle('is-pending',pending); |
| }); |
| } |
| function renderMetrics(p){const root=$('metricsList');const {smoke,gate,latency,duration,source,hardware}=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;const provenance=[source||'',hardware?`hardware ${hardware}`:''].filter(Boolean).join(' · ');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>${provenance?`<em>${escapeHtml(provenance)}</em>`:''}</li>`} |
| function renderEvents(events){const root=$('eventsPanel');if(!root)return;try{const rows=Array.isArray(events)?events:[];root.textContent=rows.slice(-30).map(e=>`${e?.ts||e?.time||''} ${e?.step||e?.phase||''} ${e?.status||''} ${e?.message||e?.title||''}`).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',{source:'user'})); |
| } |
| 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 bindValidationDraftInputs(){ |
| for(const id of ['testArgs','testKwargs'])bindOnce($(id),'input',`validationArgsDirty${id}`,()=>{state.validationArgsDirty=true;const note=$('spaceTestArgsNote');if(note)note.hidden=true;setValidationLaunchEnabled();}); |
| for(const id of ['validateSpace','validateApi','expectedOutput','validateTimeout'])bindOnce($(id),'input',`validationForm${id}`,setValidationLaunchEnabled); |
| } |
| function bindActions(){ |
| $('launchBuild')?.setAttribute('data-requires-bucket','true'); |
| $('launchBuild')?.setAttribute('data-action-button','true'); |
| $('launchValidate')?.setAttribute('data-action-button','true'); |
| bindOnce($('logoutLink'),'click','logoutClientState',handleLogoutClick); |
| const actions={ |
| launchBuild:launchBuild, |
| scanModel:scanModel, |
| launchValidate:launchValidate, |
| prepareNewBuild:prepareNewBuild, |
| manualGoValidate:prepareValidationFromActiveRun, |
| activePrefillSpaceTest:prepareValidationFromActiveRun, |
| checkBucket:()=>checkBucket(true), |
| createBucket:createBucket, |
| enableEvalArchive:enableEvalArchive, |
| verifyEvalArchive:verifyEvalArchive, |
| disableEvalArchive:disableEvalArchive, |
| flushEvalArchive:flushEvalArchive, |
| refreshRuns:()=>loadRuns(true), |
| refreshRunsHome:()=>loadRuns(true), |
| cancelRun:cancelActiveRun, |
| }; |
| 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();bindValidationDraftInputs();setText('hardwarePlan',hardwarePlanLabel());updateImplementationModeHelp();setBuildModeNew();renderActiveRunEmptyState();resetValidationRunView();loadAppInfo(); |
|
|
| |
| |
|
|
| |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
|
|