fffiloni commited on
Commit
a6793be
·
verified ·
1 Parent(s): 49166c3

Upload 7 files

Browse files
Files changed (2) hide show
  1. web/static/app.css +28 -0
  2. web/static/app.js +76 -1
web/static/app.css CHANGED
@@ -2093,3 +2093,31 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
2093
  /* v180: direct eval archive activation */
2094
  .eval-enable-button[hidden]{display:none!important;}
2095
  .eval-enable-button{white-space:nowrap;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2093
  /* v180: direct eval archive activation */
2094
  .eval-enable-button[hidden]{display:none!important;}
2095
  .eval-enable-button{white-space:nowrap;}
2096
+
2097
+ /* v182: Run Storage / Eval Archive split section */
2098
+ .compact-storage{gap:12px!important;}
2099
+ .compact-storage .bucket-status-card{margin-bottom:0!important;}
2100
+ .eval-archive-card{
2101
+ display:grid;
2102
+ grid-template-columns:minmax(0,1fr) auto;
2103
+ align-items:center;
2104
+ gap:12px;
2105
+ margin-top:4px;
2106
+ padding:12px 12px;
2107
+ border-radius:18px;
2108
+ background:linear-gradient(180deg,rgba(255,255,255,.78),rgba(248,250,252,.7));
2109
+ border:1px solid rgba(226,232,240,.72);
2110
+ box-shadow:0 1px 0 rgba(255,255,255,.9) inset;
2111
+ }
2112
+ .eval-archive-card[hidden]{display:none!important;}
2113
+ .eval-archive-card.on{background:linear-gradient(180deg,rgba(236,253,245,.66),rgba(255,255,255,.78));border-color:rgba(34,197,94,.18)}
2114
+ .eval-archive-card.off{background:linear-gradient(180deg,rgba(248,250,252,.78),rgba(255,255,255,.8));}
2115
+ .eval-archive-copy{min-width:0;display:grid;gap:3px;}
2116
+ .eval-archive-copy strong{font-size:.88rem;line-height:1.15;color:var(--ink);letter-spacing:-.01em;}
2117
+ .eval-archive-copy p{margin:0;color:var(--muted);font-size:.72rem;line-height:1.25;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
2118
+ .eval-archive-card.on .eval-archive-copy p{color:#047857;}
2119
+ .eval-archive-actions{display:flex;align-items:center;justify-content:flex-end;gap:7px;flex-wrap:wrap;}
2120
+ .eval-archive-actions button{min-height:34px;padding:8px 10px!important;border-radius:12px!important;white-space:nowrap;}
2121
+ .eval-archive-actions .danger{color:#be123c!important;background:rgba(255,241,242,.82)!important;border-color:rgba(244,63,94,.2)!important;}
2122
+ .eval-feedback{display:block!important;width:auto!important;max-width:100%;padding:0!important;border:0!important;background:transparent!important;border-radius:0!important;font-size:.72rem!important;font-weight:650!important;line-height:1.25!important;letter-spacing:0!important;}
2123
+ @media(max-width:760px){.eval-archive-card{grid-template-columns:1fr}.eval-archive-actions{justify-content:flex-start}}
web/static/app.js CHANGED
@@ -222,7 +222,57 @@ function setBucketFeedback(mode='checking',message='Checking private bucket acce
222
  text.title=String(message||'');
223
  }
224
  }
225
- function renderAnonymousEvalStatus(me){const el=$('anonymousEvalText');const btn=$('enableEvalArchive');const cfg=me?.anonymous_eval||{};const enabled=Boolean(cfg.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(el){el.className='eval-feedback '+(enabled?'on':'off');if(enabled){el.textContent=`Eval archive on · ${bucket}${path} → ${mount}`;el.title=`Anonymous project eval archive enabled from ${cfg.source||'config'}: ${bucket}${path} mounted at ${mount}. ASF backend publishes anonymized eval records there after user Jobs write local run evals.`}else{el.textContent=cfg.activation_supported?'Eval archive off · one-click setup':'Eval archive off';el.title='Direct setup: create and mount the private operator eval bucket on this ASF Space, then click Enable eval archive. Default: <username>/agentic-space-factory-evals, bucket path evals, mount /evals. Env vars still work as an admin override.'}}if(btn){btn.hidden=enabled||!me?.username||cfg.env_override===true;btn.title=`Enable anonymous eval archive using ${cfg.proposed_bucket_source||'your namespace bucket'} mounted at ${cfg.default_mount_path||'/evals'}.`}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  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()}
227
  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}
228
  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':''}`}
@@ -384,6 +434,29 @@ async function enableEvalArchive(){
384
  }catch(e){showMessage(e.message,true)}
385
  finally{setButtonBusy('enableEvalArchive',false)}
386
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
387
 
388
  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)}}
389
  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)}}
@@ -728,6 +801,8 @@ function bindActions(){
728
  checkBucket:()=>checkBucket(true),
729
  createBucket:createBucket,
730
  enableEvalArchive:enableEvalArchive,
 
 
731
  refreshRuns:()=>loadRuns(true),
732
  refreshRunsHome:()=>loadRuns(true),
733
  cancelRun:cancelActiveRun,
 
222
  text.title=String(message||'');
223
  }
224
  }
225
+ function renderAnonymousEvalStatus(me){
226
+ const card=$('evalArchiveCard');
227
+ const title=$('evalArchiveTitle');
228
+ const text=$('anonymousEvalText');
229
+ const enable=$('enableEvalArchive');
230
+ const disable=$('disableEvalArchive');
231
+ const verify=$('verifyEvalArchive');
232
+ const cfg=me?.anonymous_eval||{};
233
+ const enabled=Boolean(cfg.enabled);
234
+ const canManage=Boolean(cfg.can_manage)&&cfg.env_override!==true;
235
+ const readonly=Boolean(cfg.readonly)||Boolean(enabled&&!canManage);
236
+ const bucket=cfg.bucket_source||cfg.proposed_bucket_source||'configured bucket';
237
+ const path=cfg.bucket_path?'/'+cfg.bucket_path:(cfg.default_bucket_path?'/'+cfg.default_bucket_path:'');
238
+ const mount=cfg.job_mount_path||cfg.default_mount_path||'/evals';
239
+ if(card){
240
+ card.hidden=false;
241
+ card.className='eval-archive-card '+(enabled?'on':'off')+(readonly?' readonly':'')+(canManage?' manageable':'');
242
+ }
243
+ if(title){
244
+ title.textContent=enabled?'Anonymous eval archive is on':'Anonymous eval archive is off';
245
+ }
246
+ if(text){
247
+ text.className='eval-feedback '+(enabled?'on':'off');
248
+ if(enabled){
249
+ const manager=cfg.managed_by==='you'?'You manage this instance setting.':`Managed by ${cfg.managed_by||'instance owner'}.`;
250
+ text.textContent=`${bucket}${path} → ${mount}`;
251
+ 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.`;
252
+ }else if(me?.username){
253
+ text.textContent='Mount an operator eval bucket at /evals, then enable publishing.';
254
+ 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.';
255
+ }else{
256
+ text.textContent='Sign in as the instance owner to enable it.';
257
+ text.title='Anonymous eval archive can be enabled by the instance owner or configured eval admins.';
258
+ }
259
+ }
260
+ if(enable){
261
+ enable.hidden=enabled||!me?.username||cfg.env_override===true;
262
+ enable.disabled=!me?.username||state.busy;
263
+ enable.title=`Enable anonymous eval archive using ${cfg.proposed_bucket_source||'your namespace bucket'} mounted at ${cfg.default_mount_path||'/evals'}.`;
264
+ }
265
+ if(disable){
266
+ disable.hidden=!enabled||!canManage;
267
+ disable.disabled=state.busy;
268
+ disable.title='Disable anonymous eval publishing for this ASF instance. Existing archived eval records are not deleted.';
269
+ }
270
+ if(verify){
271
+ verify.hidden=!enabled||!canManage;
272
+ verify.disabled=state.busy;
273
+ verify.title='Refresh and verify the eval archive configuration.';
274
+ }
275
+ }
276
  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()}
277
  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}
278
  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':''}`}
 
434
  }catch(e){showMessage(e.message,true)}
435
  finally{setButtonBusy('enableEvalArchive',false)}
436
  }
437
+ async function verifyEvalArchive(){
438
+ setButtonBusy('verifyEvalArchive',true,'Verifying…');
439
+ try{
440
+ const result=await apiGet('/api/eval-archive/status');
441
+ state.user={...(state.user||{}),anonymous_eval:result};
442
+ renderAnonymousEvalStatus(state.user);
443
+ showMessage(result.enabled?'Eval archive configuration verified.':'Eval archive is not enabled.',result.enabled?'success':'warning');
444
+ }catch(e){showMessage(e.message,true)}
445
+ finally{setButtonBusy('verifyEvalArchive',false)}
446
+ }
447
+ async function disableEvalArchive(){
448
+ const ok=window.confirm('Disable anonymous eval publishing for this ASF instance? Existing archived records will not be deleted.');
449
+ if(!ok)return;
450
+ setButtonBusy('disableEvalArchive',true,'Disabling…');
451
+ try{
452
+ const result=await apiPost('/api/eval-archive/disable',{});
453
+ state.user={...(state.user||{}),anonymous_eval:result};
454
+ renderAnonymousEvalStatus(state.user);
455
+ showMessage(result.message||'Eval archive disabled.','success');
456
+ }catch(e){showMessage(e.message,true)}
457
+ finally{setButtonBusy('disableEvalArchive',false)}
458
+ }
459
+
460
 
461
  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)}}
462
  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)}}
 
801
  checkBucket:()=>checkBucket(true),
802
  createBucket:createBucket,
803
  enableEvalArchive:enableEvalArchive,
804
+ verifyEvalArchive:verifyEvalArchive,
805
+ disableEvalArchive:disableEvalArchive,
806
  refreshRuns:()=>loadRuns(true),
807
  refreshRunsHome:()=>loadRuns(true),
808
  cancelRun:cancelActiveRun,