Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Upload 6 files
Browse files- web/static/app.css +63 -0
- web/static/app.js +80 -4
- web/static/progress.js +2 -2
- web/static/runs.js +1 -1
web/static/app.css
CHANGED
|
@@ -638,3 +638,66 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 638 |
}
|
| 639 |
.activity-row.error .event-reason{color:#be123c}
|
| 640 |
.activity-row.hardware-fallback .event-reason{color:#1d4ed8}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 638 |
}
|
| 639 |
.activity-row.error .event-reason{color:#be123c}
|
| 640 |
.activity-row.hardware-fallback .event-reason{color:#1d4ed8}
|
| 641 |
+
|
| 642 |
+
/* v87: validation is a separate Space test workflow */
|
| 643 |
+
.validation-help{
|
| 644 |
+
margin:0 0 12px;
|
| 645 |
+
padding:8px 10px;
|
| 646 |
+
border:1px solid var(--line);
|
| 647 |
+
border-radius:10px;
|
| 648 |
+
background:#f8fafc;
|
| 649 |
+
font-size:11px;
|
| 650 |
+
line-height:1.35;
|
| 651 |
+
}
|
| 652 |
+
.validation-result-card .badge.running{background:#eff6ff;color:#1d4ed8;border-color:#bfdbfe}
|
| 653 |
+
.validation-result-card .badge.success{background:#ecfdf3;color:#027a48;border-color:#bbf7d0}
|
| 654 |
+
.validation-result-card .badge.error{background:#fff1f2;color:#be123c;border-color:#fecdd3}
|
| 655 |
+
|
| 656 |
+
/* v89: Space Test lives under Active Run in the center column */
|
| 657 |
+
.active-run-panel + .space-test-panel{
|
| 658 |
+
margin-top:0;
|
| 659 |
+
}
|
| 660 |
+
.workspace-grid > .space-test-panel{
|
| 661 |
+
grid-column:2;
|
| 662 |
+
}
|
| 663 |
+
.workspace-grid > .runs-explorer-panel{
|
| 664 |
+
grid-column:3;
|
| 665 |
+
}
|
| 666 |
+
.workspace-grid > .left-stack{
|
| 667 |
+
grid-column:1;
|
| 668 |
+
}
|
| 669 |
+
.workspace-grid > .active-run-panel{
|
| 670 |
+
grid-column:2;
|
| 671 |
+
}
|
| 672 |
+
.space-test-panel .space-test-grid{
|
| 673 |
+
grid-template-columns:1fr;
|
| 674 |
+
gap:12px;
|
| 675 |
+
}
|
| 676 |
+
.space-test-panel .test-top-row{
|
| 677 |
+
grid-template-columns:1fr 120px;
|
| 678 |
+
}
|
| 679 |
+
.space-test-panel .test-top-row label:first-child{
|
| 680 |
+
grid-column:1 / -1;
|
| 681 |
+
}
|
| 682 |
+
.space-test-panel .editor-grid{
|
| 683 |
+
grid-template-columns:1fr;
|
| 684 |
+
}
|
| 685 |
+
.space-test-panel .validation-grid{
|
| 686 |
+
grid-template-columns:1fr 1fr;
|
| 687 |
+
}
|
| 688 |
+
@media (max-width:1180px){
|
| 689 |
+
.workspace-grid > .left-stack,
|
| 690 |
+
.workspace-grid > .active-run-panel,
|
| 691 |
+
.workspace-grid > .space-test-panel,
|
| 692 |
+
.workspace-grid > .runs-explorer-panel{
|
| 693 |
+
grid-column:auto;
|
| 694 |
+
}
|
| 695 |
+
.workspace-grid > .runs-explorer-panel{
|
| 696 |
+
grid-column:1 / -1;
|
| 697 |
+
}
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
/* v90: Pi/provider model mismatch warning */
|
| 701 |
+
.blockers strong:first-child{display:block}
|
| 702 |
+
.blockers p code{font-size:11px}
|
| 703 |
+
.activity-row.warning .event-reason,.activity-row.warn .event-reason{color:#92400e}
|
web/static/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
const state={runId:null,bucketName:'space-factory-runs',bucketSource:null,bucketReady:false,poll:null,activeLinks:{},selectedRunDetail:null,runStatusFilter:'all',runSearch:'',runPage:0,runsPerPage:5,runsCache:[],runsLastLoaded:0,lastRunsRefreshFromPoll:0,lastJobLogPoll:0,pollErrors:0,busy:false,user:null,currentView:'onepage'};
|
| 2 |
const TERMINAL_STATUSES=new Set(['done','success','failed','full_inference_success','full_inference_candidate_health_passed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','health_only','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled']);
|
| 3 |
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'}];
|
| 4 |
function saveActiveRun(){try{localStorage.setItem('asf.activeRun',JSON.stringify({runId:state.runId,bucketName:state.bucketName,links:state.activeLinks||{}}))}catch(_){}}
|
|
@@ -31,7 +31,9 @@ function setBucketReady(ok,message){state.bucketReady=Boolean(ok);const btn=$('l
|
|
| 31 |
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}
|
| 32 |
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':''}`}
|
| 33 |
function validateBuildForm(){if(!state.bucketReady)return'Create or check your private run bucket before launching a build.';if(!formValue('modelId'))return'Enter a model card URL or model ID.';if(!formValue('targetSpace'))return'Enter a fresh target Space name.';if(!formValue('piModel'))return'Enter the Pi model to use.';return''}
|
| 34 |
-
function
|
|
|
|
|
|
|
| 35 |
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')}
|
| 36 |
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>`}}
|
| 37 |
function resetRunDetails(){
|
|
@@ -69,9 +71,83 @@ function renderAuthWarnings(me){const auth=$('authPanel');if(!auth)return;const
|
|
| 69 |
async function checkBucket(noisy=true){setButtonBusy('checkBucket',true,'Checking…');try{const data=await apiGet(`/api/bucket/status?bucket_name=${encodeURIComponent(state.bucketName)}`);const source=data.bucket_source||state.bucketName;state.bucketSource=source;setText('bucketInlineLabel',source);setBucketReady(Boolean(data.ok),data.error||'Create your private run bucket before launching builds.');if(noisy)showMessage(data.ok?`Bucket ready: ${source}`:`Bucket not ready: ${data.error||'not found'}`,!data.ok);return data}catch(e){setBucketReady(false,e.message);if(noisy)showMessage(e.message,true);throw e}finally{setButtonBusy('checkBucket',false)}}
|
| 70 |
async function createBucket(){setButtonBusy('createBucket',true,'Creating…');try{const data=await apiPost('/api/bucket/create',{bucket_name:state.bucketName});const source=data.bucket_source||state.bucketName;state.bucketSource=source;setText('bucketInlineLabel',source);setBucketReady(Boolean(data.ok),data.error||'Could not create bucket.');showMessage(data.ok?`Created/ready: ${source}`:`Could not create bucket: ${data.error}`,!data.ok);if(data.ok)loadRuns(true)}catch(e){setBucketReady(false,e.message);showMessage(e.message,true)}finally{setButtonBusy('createBucket',false)}}
|
| 71 |
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)}
|
| 72 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
async function launchBuild(){const formError=validateBuildForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching build Job…');setButtonBusy('launchBuild',true,'Launching…');try{setText('hardwarePlan',hardwarePlanLabel());const payload={bucket_name:state.bucketName,run_id:formValue('runId')||undefined,model_id:formValue('modelId'),target_space_name:formValue('targetSpace'),pi_model:formValue('piModel'),implementation_mode:formValue('implementationMode'),preferred_space_hardware:effectivePreferredHardware(),fallback_space_hardware:formValue('fallbackHardware'),allow_fixed_gpu_fallback:$('allowFallback')?.checked??true,expected_output_type:formValue('buildExpectedOutput')};const result=await apiPost('/api/build',payload);setActiveRun(result);showMessage(`Build launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchBuild',false)}}
|
| 74 |
-
async function launchValidate(){const formError=validateValidationForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching validation Job…');setButtonBusy('launchValidate',true,'Launching…');try{const payload={bucket_name:state.bucketName,target_space_id:formValue('validateSpace'),api_name:formValue('validateApi')||'/generate',expected_output_type:formValue('expectedOutput')||'
|
| 75 |
async function refreshRunReportPreview(runId){
|
| 76 |
if(!runId)return;
|
| 77 |
try{
|
|
|
|
| 1 |
+
const state={runId:null,validationRunId:null,validationPoll:null,validationPollErrors:0,bucketName:'space-factory-runs',bucketSource:null,bucketReady:false,poll:null,activeLinks:{},selectedRunDetail:null,runStatusFilter:'all',runSearch:'',runPage:0,runsPerPage:5,runsCache:[],runsLastLoaded:0,lastRunsRefreshFromPoll:0,lastJobLogPoll:0,pollErrors:0,busy:false,user:null,currentView:'onepage'};
|
| 2 |
const TERMINAL_STATUSES=new Set(['done','success','failed','full_inference_success','full_inference_candidate_health_passed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','health_only','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled']);
|
| 3 |
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'}];
|
| 4 |
function saveActiveRun(){try{localStorage.setItem('asf.activeRun',JSON.stringify({runId:state.runId,bucketName:state.bucketName,links:state.activeLinks||{}}))}catch(_){}}
|
|
|
|
| 31 |
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}
|
| 32 |
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':''}`}
|
| 33 |
function validateBuildForm(){if(!state.bucketReady)return'Create or check your private run bucket before launching a build.';if(!formValue('modelId'))return'Enter a model card URL or model ID.';if(!formValue('targetSpace'))return'Enter a fresh target Space name.';if(!formValue('piModel'))return'Enter the Pi model to use.';return''}
|
| 34 |
+
function parseJsonField(id,fallback){const raw=formValue(id);if(!raw)return fallback;return JSON.parse(raw)}
|
| 35 |
+
function prettyJson(value){return JSON.stringify(value,null,2)}
|
| 36 |
+
function validateValidationForm(){if(!formValue('validateSpace'))return'Enter the target Space ID to validate.';try{const args=parseJsonField('testArgs',[]);const kwargs=parseJsonField('testKwargs',{});if(!Array.isArray(args))return'Test args must be a JSON array because they are passed as positional arguments to gradio_client.predict(...).';if(!kwargs||Array.isArray(kwargs)||typeof kwargs!=='object')return'Test kwargs must be a JSON object because they are passed as keyword arguments.'}catch(e){return`Invalid JSON payload: ${e.message}`}return''}
|
| 37 |
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')}
|
| 38 |
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>`}}
|
| 39 |
function resetRunDetails(){
|
|
|
|
| 71 |
async function checkBucket(noisy=true){setButtonBusy('checkBucket',true,'Checking…');try{const data=await apiGet(`/api/bucket/status?bucket_name=${encodeURIComponent(state.bucketName)}`);const source=data.bucket_source||state.bucketName;state.bucketSource=source;setText('bucketInlineLabel',source);setBucketReady(Boolean(data.ok),data.error||'Create your private run bucket before launching builds.');if(noisy)showMessage(data.ok?`Bucket ready: ${source}`:`Bucket not ready: ${data.error||'not found'}`,!data.ok);return data}catch(e){setBucketReady(false,e.message);if(noisy)showMessage(e.message,true);throw e}finally{setButtonBusy('checkBucket',false)}}
|
| 72 |
async function createBucket(){setButtonBusy('createBucket',true,'Creating…');try{const data=await apiPost('/api/bucket/create',{bucket_name:state.bucketName});const source=data.bucket_source||state.bucketName;state.bucketSource=source;setText('bucketInlineLabel',source);setBucketReady(Boolean(data.ok),data.error||'Could not create bucket.');showMessage(data.ok?`Created/ready: ${source}`:`Could not create bucket: ${data.error}`,!data.ok);if(data.ok)loadRuns(true)}catch(e){setBucketReady(false,e.message);showMessage(e.message,true)}finally{setButtonBusy('createBucket',false)}}
|
| 73 |
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)}
|
| 74 |
+
function defaultValidationArgs(expected='text'){
|
| 75 |
+
const kind=String(expected||'text').toLowerCase();
|
| 76 |
+
if(kind==='image')return ["a cinematic robot cat astronaut, detailed, studio lighting"];
|
| 77 |
+
if(kind==='video')return ["a short cinematic shot of a robot cat astronaut walking on the moon"];
|
| 78 |
+
if(kind==='audio')return ["A calm voice saying hello from Agentic Space Factory."];
|
| 79 |
+
return ["Explain the concept of agentic AI in one paragraph."];
|
| 80 |
+
}
|
| 81 |
+
function normalizeApiName(value){
|
| 82 |
+
value=String(value||'').trim();
|
| 83 |
+
if(!value)return'/generate';
|
| 84 |
+
return value.startsWith('/')?value:'/'+value;
|
| 85 |
+
}
|
| 86 |
+
function parsedJsonOrNull(value){
|
| 87 |
+
if(value==null||value==='')return null;
|
| 88 |
+
try{return typeof value==='string'?JSON.parse(value):value}catch(_){return null}
|
| 89 |
+
}
|
| 90 |
+
function validationPrefillFromDetail(detail={}){
|
| 91 |
+
const summary=detail.summary||{};
|
| 92 |
+
const smoke=detail.generation_smoke||detail.tests?.generation_smoke||{};
|
| 93 |
+
const launch=detail.launch||{};
|
| 94 |
+
const stateObj=detail.state||{};
|
| 95 |
+
const expected=smoke.expected_output_type||launch.expected_output_type||summary.expected_output_type||formValue('buildExpectedOutput')||'text';
|
| 96 |
+
let args=Array.isArray(smoke.test_args)?smoke.test_args:parsedJsonOrNull(launch.test_args_json);
|
| 97 |
+
let kwargs=smoke.test_kwargs&&typeof smoke.test_kwargs==='object'&&!Array.isArray(smoke.test_kwargs)?smoke.test_kwargs:parsedJsonOrNull(launch.test_kwargs_json);
|
| 98 |
+
if(!Array.isArray(args))args=defaultValidationArgs(expected);
|
| 99 |
+
if(!kwargs||Array.isArray(kwargs)||typeof kwargs!=='object')kwargs={};
|
| 100 |
+
return {
|
| 101 |
+
target:summary.target_space||stateObj.target_space||launch.target_space||$('detailSpace')?.textContent||'',
|
| 102 |
+
api:normalizeApiName(smoke.api_name||launch.api_name||'/generate'),
|
| 103 |
+
expected,
|
| 104 |
+
args,
|
| 105 |
+
kwargs,
|
| 106 |
+
};
|
| 107 |
+
}
|
| 108 |
+
function renderValidationStatus(data={}){
|
| 109 |
+
const status=data.status||data.view?.header?.status||data.state?.status||'running';
|
| 110 |
+
const badge=$('spaceTestStatus');
|
| 111 |
+
if(badge){badge.textContent=badgeLabel(status);badge.className='badge '+statusClass(status)}
|
| 112 |
+
setText('spaceTestOutput',data.run_id||state.validationRunId||'—');
|
| 113 |
+
setText('lastPolled',formatTime(data.updated_at||data.created_at||new Date().toISOString()));
|
| 114 |
+
const smoke=data.generation_smoke||data.summary?.generation_smoke||{};
|
| 115 |
+
const latency=smoke.latency_seconds||data.summary?.latency_seconds;
|
| 116 |
+
const card=$('spaceTestPreview');
|
| 117 |
+
const latencyNode=card?.querySelector('.validation-grid div:nth-child(2) strong');
|
| 118 |
+
if(latencyNode)latencyNode.textContent=latency?fmtSeconds(Math.round(latency)):'—';
|
| 119 |
+
setText('spaceTestVerdict',status);
|
| 120 |
+
}
|
| 121 |
+
function renderValidationLaunchResult(result={}){
|
| 122 |
+
state.validationRunId=result.run_id;
|
| 123 |
+
renderValidationStatus({...result,status:result.status||'running'});
|
| 124 |
+
}
|
| 125 |
+
function stopValidationPolling(){
|
| 126 |
+
if(state.validationPoll){clearInterval(state.validationPoll);state.validationPoll=null}
|
| 127 |
+
}
|
| 128 |
+
async function refreshValidationProgress(){
|
| 129 |
+
if(!state.validationRunId)return;
|
| 130 |
+
try{
|
| 131 |
+
const p=await apiGet(`/api/runs/${encodeURIComponent(state.validationRunId)}/progress?bucket_name=${encodeURIComponent(state.bucketName)}&include_job_logs=1`);
|
| 132 |
+
state.validationPollErrors=0;
|
| 133 |
+
renderValidationStatus(p);
|
| 134 |
+
const status=String(p.view?.header?.status||p.status||p.state?.status||'').toLowerCase();
|
| 135 |
+
if(TERMINAL_STATUSES.has(status)){stopValidationPolling();showMessage(`Validation finished: ${badgeLabel(status)}`,statusClass(status)==='success'?'success':statusClass(status)==='error'?'error':'warning');loadRuns(false)}
|
| 136 |
+
}catch(e){
|
| 137 |
+
state.validationPollErrors+=1;
|
| 138 |
+
console.warn('validation polling failed',e);
|
| 139 |
+
if(state.validationPollErrors<=2)showMessage(`Validation polling failed. Retrying…`,'warning');
|
| 140 |
+
}
|
| 141 |
+
}
|
| 142 |
+
function startValidationPolling(runId){
|
| 143 |
+
state.validationRunId=runId;
|
| 144 |
+
stopValidationPolling();
|
| 145 |
+
refreshValidationProgress();
|
| 146 |
+
state.validationPoll=setInterval(refreshValidationProgress,3000);
|
| 147 |
+
}
|
| 148 |
+
function prepareValidationFromActiveRun(){const detail=state.selectedRunDetail||{};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));switchView('validate');showMessage(`Prepared validation form for ${prefill.target||'selected Space'}.`,'info')}
|
| 149 |
async function launchBuild(){const formError=validateBuildForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching build Job…');setButtonBusy('launchBuild',true,'Launching…');try{setText('hardwarePlan',hardwarePlanLabel());const payload={bucket_name:state.bucketName,run_id:formValue('runId')||undefined,model_id:formValue('modelId'),target_space_name:formValue('targetSpace'),pi_model:formValue('piModel'),implementation_mode:formValue('implementationMode'),preferred_space_hardware:effectivePreferredHardware(),fallback_space_hardware:formValue('fallbackHardware'),allow_fixed_gpu_fallback:$('allowFallback')?.checked??true,expected_output_type:formValue('buildExpectedOutput')};const result=await apiPost('/api/build',payload);setActiveRun(result);showMessage(`Build launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchBuild',false)}}
|
| 150 |
+
async function launchValidate(){const formError=validateValidationForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching validation Job…');setButtonBusy('launchValidate',true,'Launching…');try{const args=parseJsonField('testArgs',[]);const kwargs=parseJsonField('testKwargs',{});const payload={bucket_name:state.bucketName,target_space_id:formValue('validateSpace'),api_name:normalizeApiName(formValue('validateApi')||'/generate'),expected_output_type:formValue('expectedOutput')||'any',test_args_json:prettyJson(args),test_kwargs_json:prettyJson(kwargs),live_timeout_seconds:Number(formValue('validateTimeout')||1800)};const result=await apiPost('/api/validate',payload);renderValidationLaunchResult(result);startValidationPolling(result.run_id);showMessage(`Validation launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchValidate',false)}}
|
| 151 |
async function refreshRunReportPreview(runId){
|
| 152 |
if(!runId)return;
|
| 153 |
try{
|
web/static/progress.js
CHANGED
|
@@ -3,7 +3,7 @@ const activityFeedState={lastItems:[],runId:null};
|
|
| 3 |
const PROGRESS_TERMINAL_STATUSES=new Set(['done','success','failed','full_inference_success','full_inference_candidate_health_passed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','health_only','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled']);
|
| 4 |
|
| 5 |
const WORKER_STEP_ORDER=[
|
| 6 |
-
'bootstrap','dependencies','auth','model_analysis','workspace','node','pi_install','pi_config','pi_run','pi_verification',
|
| 7 |
'metadata_sanitize','requirements_sanitize','hardware_strategy','create_space_hardware','create_space','repair','upload_files',
|
| 8 |
'space_runtime','space_logs','api_validation','live_wait','generation_smoke','inference_gate','report_write','done','failure'
|
| 9 |
];
|
|
@@ -16,7 +16,7 @@ const COMPACT_TIMELINE_GROUPS=[
|
|
| 16 |
{id:'workspace_node',label:'Workspace/node',steps:['workspace','node']},
|
| 17 |
{id:'pi_setup',label:'Pi setup',steps:['pi_install','pi_config']},
|
| 18 |
{id:'pi_run',label:'Pi run',steps:['pi_run']},
|
| 19 |
-
{id:'pi_verify',label:'Pi verify',steps:['pi_verification','metadata_sanitize','requirements_sanitize']},
|
| 20 |
{id:'hardware',label:'Hardware',steps:['hardware_strategy','create_space_hardware']},
|
| 21 |
{id:'create_space',label:'Create Space',steps:['create_space']},
|
| 22 |
{id:'upload_runtime',label:'Upload/runtime',steps:['upload_files','space_runtime','space_logs','repair']},
|
|
|
|
| 3 |
const PROGRESS_TERMINAL_STATUSES=new Set(['done','success','failed','full_inference_success','full_inference_candidate_health_passed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','health_only','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled']);
|
| 4 |
|
| 5 |
const WORKER_STEP_ORDER=[
|
| 6 |
+
'bootstrap','dependencies','auth','model_analysis','workspace','node','pi_install','pi_config','pi_run','pi_model_resolution','pi_verification',
|
| 7 |
'metadata_sanitize','requirements_sanitize','hardware_strategy','create_space_hardware','create_space','repair','upload_files',
|
| 8 |
'space_runtime','space_logs','api_validation','live_wait','generation_smoke','inference_gate','report_write','done','failure'
|
| 9 |
];
|
|
|
|
| 16 |
{id:'workspace_node',label:'Workspace/node',steps:['workspace','node']},
|
| 17 |
{id:'pi_setup',label:'Pi setup',steps:['pi_install','pi_config']},
|
| 18 |
{id:'pi_run',label:'Pi run',steps:['pi_run']},
|
| 19 |
+
{id:'pi_verify',label:'Pi verify',steps:['pi_model_resolution','pi_verification','metadata_sanitize','requirements_sanitize']},
|
| 20 |
{id:'hardware',label:'Hardware',steps:['hardware_strategy','create_space_hardware']},
|
| 21 |
{id:'create_space',label:'Create Space',steps:['create_space']},
|
| 22 |
{id:'upload_runtime',label:'Upload/runtime',steps:['upload_files','space_runtime','space_logs','repair']},
|
web/static/runs.js
CHANGED
|
@@ -54,5 +54,5 @@ async function deleteSelectedRun(){
|
|
| 54 |
}
|
| 55 |
}
|
| 56 |
function renderSelectedLinks(summary={},detail={}){const runId=summary.run_id||detail.run_id;const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;const artifacts=summary.artifacts_url||(typeof artifactUrl==='function'?artifactUrl(runId,bucket):'');const jobUrl=inferredJobUrl(summary,detail);setLink('selectedOpenJob',jobUrl);const ready=typeof runPassedSpaceCreation==='function'?runPassedSpaceCreation({summary,detail,events:detail.events||[]}):Boolean(summary.target_space_url);setLink('selectedOpenSpace',ready?summary.target_space_url:'');setLink('selectedOpenSettings',ready&&summary.target_space_url?summary.target_space_url+'/settings':'');setLink('selectedOpenArtifacts',artifacts);const btn=$('selectedValidateBtn');if(btn){btn.disabled=!summary.target_space||!ready;btn.onclick=prepareValidationFromActiveRun}}
|
| 57 |
-
function renderBlockers(detail={}){const root=$('selectedBlockers');if(!root)return;const summary=detail.summary||{};const blockers=detail.technical_blockers||{};const manual=summary.manual_hardware_required||String(summary.status||'').includes('manual_hardware_required');const blockerItems=Array.isArray(blockers.blockers)?blockers.blockers:[];if(!manual&&!blockerItems.length){root.hidden=true;root.innerHTML='';return}root.hidden=false;const parts=[];if(manual)parts.push('<strong>Manual hardware required</strong><p>Open Space settings, choose the recommended GPU, then run validation.</p>');for(const b of blockerItems.slice(0,4)){parts.push(`<strong>${escapeHtml(shortText(b.type||b.name||'Technical blocker',32))}</strong><p>${escapeHtml(shortText(b.claim||b.reason||b.message||'',120))}</p>`)}root.innerHTML=parts.join('')}
|
| 58 |
function renderRunDetail(detail){state.selectedRunDetail=detail;const summary=detail.summary||{};const smoke=detail.generation_smoke||{};const files=detail.files||[];const view=detail.view||{};const header=view.header||{};setText('selectedRunId',detail.run_id||summary.run_id||'—');setText('selectedModelId',summary.model_id||'—');setText('selectedModelShort',summary.model_id||'—');setText('selectedTargetSpace',summary.target_space||'—');setText('selectedHardware',summary.selected_hardware||summary.hardware||'—');setText('selectedMode',detail.state?.implementation_mode||detail.launch?.implementation_mode||summary.kind||'Strict real inference');setText('runDetailStatus',header.status||summary.status||'unknown');setText('homeRunDetailStatus',header.status_label||summary.status||'unknown');setText('detailRunId',summary.run_id||detail.run_id);setText('detailJobStatus',summary.status||'—');setText('detailGateStatus',(detail.inference_gate||{}).status||summary.status||'—');setText('detailSpace',summary.target_space||'—');setText('detailHardware',summary.selected_hardware||summary.hardware||'—');setText('detailTraces',Array.isArray(detail.events)?String(detail.events.length)+' events':summary.traces_count||'—');const validation=$('selectedValidation');if(validation){validation.innerHTML=`<li><span>Health</span><strong>${summary.health_passed?'passed':'—'}</strong></li><li><span>Smoke test</span><strong>${summary.smoke_test_passed?'passed':(smoke.status||'—')}</strong></li><li><span>Latency</span><strong>${summary.latency_seconds?fmtSeconds(Math.round(summary.latency_seconds)):'—'}</strong></li>`}const artifactRoot=$('artifactList');if(artifactRoot){const interesting=files.filter(f=>!/traces\//.test(f.path||'')).slice(0,18);artifactRoot.innerHTML=interesting.length?interesting.map(f=>`<a href="${escapeHtml(f.url)}" target="_blank"><span>${escapeHtml(f.path)}</span><em>${f.size?Math.round(Number(f.size)/1024)+' KB':''}</em></a>`).join(''):'No artifacts indexed yet.'}const reportText=(detail.report||'No report available.').slice(0,8000);const report=$('reportPreview');if(report)report.textContent=reportText;setQuickLinks({job_url:inferredJobUrl(summary,detail),target_space_url:summary.target_space_url,target_space_settings_url:summary.target_space_url?summary.target_space_url+'/settings':'',artifacts_url:summary.artifacts_url,bucket_source:detail.bucket_source,run_id:summary.run_id||detail.run_id});renderSelectedLinks({...summary,bucket_source:detail.bucket_source},detail);renderBlockers(detail);renderMetrics({...detail,summary});/* v71: avoid rendering stale view.pipeline before /progress returns canonical compact timeline. */const deleteBtn=$('deleteSelectedRun');if(deleteBtn){deleteBtn.disabled=!(summary.run_id||detail.run_id);deleteBtn.onclick=deleteSelectedRun}}
|
|
|
|
| 54 |
}
|
| 55 |
}
|
| 56 |
function renderSelectedLinks(summary={},detail={}){const runId=summary.run_id||detail.run_id;const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;const artifacts=summary.artifacts_url||(typeof artifactUrl==='function'?artifactUrl(runId,bucket):'');const jobUrl=inferredJobUrl(summary,detail);setLink('selectedOpenJob',jobUrl);const ready=typeof runPassedSpaceCreation==='function'?runPassedSpaceCreation({summary,detail,events:detail.events||[]}):Boolean(summary.target_space_url);setLink('selectedOpenSpace',ready?summary.target_space_url:'');setLink('selectedOpenSettings',ready&&summary.target_space_url?summary.target_space_url+'/settings':'');setLink('selectedOpenArtifacts',artifacts);const btn=$('selectedValidateBtn');if(btn){btn.disabled=!summary.target_space||!ready;btn.onclick=prepareValidationFromActiveRun}}
|
| 57 |
+
function renderBlockers(detail={}){const root=$('selectedBlockers');if(!root)return;const summary=detail.summary||{};const blockers=detail.technical_blockers||{};const manual=summary.manual_hardware_required||String(summary.status||'').includes('manual_hardware_required');const blockerItems=Array.isArray(blockers.blockers)?blockers.blockers:[];const piResolution=detail.pi_model_resolution||detail.state?.pi_model_resolution||summary.pi_model_resolution||{};const piMismatch=Boolean(piResolution.mismatch);if(!manual&&!blockerItems.length&&!piMismatch){root.hidden=true;root.innerHTML='';return}root.hidden=false;const parts=[];if(piMismatch)parts.push(`<strong>Pi assistant model changed</strong><p>Requested ${escapeHtml(piResolution.requested_model||'—')}, but Pi/provider traces indicate ${escapeHtml(piResolution.effective_model||'another model')}. This may be an Inference Providers routing/fallback behavior.</p>`);if(manual)parts.push('<strong>Manual hardware required</strong><p>Open Space settings, choose the recommended GPU, then run validation.</p>');for(const b of blockerItems.slice(0,4)){parts.push(`<strong>${escapeHtml(shortText(b.type||b.name||'Technical blocker',32))}</strong><p>${escapeHtml(shortText(b.claim||b.reason||b.message||'',120))}</p>`)}root.innerHTML=parts.join('')}
|
| 58 |
function renderRunDetail(detail){state.selectedRunDetail=detail;const summary=detail.summary||{};const smoke=detail.generation_smoke||{};const files=detail.files||[];const view=detail.view||{};const header=view.header||{};setText('selectedRunId',detail.run_id||summary.run_id||'—');setText('selectedModelId',summary.model_id||'—');setText('selectedModelShort',summary.model_id||'—');setText('selectedTargetSpace',summary.target_space||'—');setText('selectedHardware',summary.selected_hardware||summary.hardware||'—');setText('selectedMode',detail.state?.implementation_mode||detail.launch?.implementation_mode||summary.kind||'Strict real inference');setText('runDetailStatus',header.status||summary.status||'unknown');setText('homeRunDetailStatus',header.status_label||summary.status||'unknown');setText('detailRunId',summary.run_id||detail.run_id);setText('detailJobStatus',summary.status||'—');setText('detailGateStatus',(detail.inference_gate||{}).status||summary.status||'—');setText('detailSpace',summary.target_space||'—');setText('detailHardware',summary.selected_hardware||summary.hardware||'—');setText('detailTraces',Array.isArray(detail.events)?String(detail.events.length)+' events':summary.traces_count||'—');const validation=$('selectedValidation');if(validation){validation.innerHTML=`<li><span>Health</span><strong>${summary.health_passed?'passed':'—'}</strong></li><li><span>Smoke test</span><strong>${summary.smoke_test_passed?'passed':(smoke.status||'—')}</strong></li><li><span>Latency</span><strong>${summary.latency_seconds?fmtSeconds(Math.round(summary.latency_seconds)):'—'}</strong></li>`}const artifactRoot=$('artifactList');if(artifactRoot){const interesting=files.filter(f=>!/traces\//.test(f.path||'')).slice(0,18);artifactRoot.innerHTML=interesting.length?interesting.map(f=>`<a href="${escapeHtml(f.url)}" target="_blank"><span>${escapeHtml(f.path)}</span><em>${f.size?Math.round(Number(f.size)/1024)+' KB':''}</em></a>`).join(''):'No artifacts indexed yet.'}const reportText=(detail.report||'No report available.').slice(0,8000);const report=$('reportPreview');if(report)report.textContent=reportText;setQuickLinks({job_url:inferredJobUrl(summary,detail),target_space_url:summary.target_space_url,target_space_settings_url:summary.target_space_url?summary.target_space_url+'/settings':'',artifacts_url:summary.artifacts_url,bucket_source:detail.bucket_source,run_id:summary.run_id||detail.run_id});renderSelectedLinks({...summary,bucket_source:detail.bucket_source},detail);renderBlockers(detail);renderMetrics({...detail,summary});/* v71: avoid rendering stale view.pipeline before /progress returns canonical compact timeline. */const deleteBtn=$('deleteSelectedRun');if(deleteBtn){deleteBtn.disabled=!(summary.run_id||detail.run_id);deleteBtn.onclick=deleteSelectedRun}}
|