TiGa-RCE commited on
Commit
3062b0f
·
verified ·
1 Parent(s): ff2b518

Record selected policy explicitly and reject stale playback

Browse files
Files changed (1) hide show
  1. client.js +69 -5
client.js CHANGED
@@ -2,6 +2,9 @@
2
  const $ = id => document.getElementById(id);
3
  let job = null;
4
  let busy = false;
 
 
 
5
  let localResult = null;
6
  let localAnalysis = null;
7
  let pairedAnalysisPending = false;
@@ -58,7 +61,7 @@ function batchRunContext(){
58
  const trackInfo=trackLibrary?.info()??{source:'upload'},trackFile=$('track').files[0],network=networkSource(),backend=$('backend').value;
59
  const hasTrack=trackInfo.source==='builtin'||!!trackFile;
60
  let reason='';
61
- if(busy||bundleRestoring||bucketOperation)reason='Finish the current operation before starting a batch.';
62
  else if(job)reason='Finish or delete the current result before starting a batch.';
63
  else if(!capabilitiesReady)reason='Connect to the training service before running a batch.';
64
  else if($('run_mode')?.value==='evaluate')reason='Under Action in Run & results, choose Train a population before running a batch.';
@@ -70,7 +73,7 @@ function batchRunContext(){
70
  network:network?.name||(network?'Selected network':null),backend:backend.toUpperCase()};
71
  }
72
  function buttons() {
73
- const settingsUnavailable=busy||bundleRestoring||bucketOperation;
74
  const unavailable=settingsUnavailable||!!job;
75
  trackLibrary?.setBusy(unavailable);
76
  window.HFAuth?.setBusy(unavailable);
@@ -87,6 +90,13 @@ function buttons() {
87
  $('forgetLocal').disabled = busy || !localResult;
88
  $('track').disabled=unavailable;
89
  $('network').disabled=unavailable;
 
 
 
 
 
 
 
90
  if($('batch_start')){
91
  const context=batchRunContext();
92
  window.setBatchRunContext?.(context);
@@ -438,7 +448,7 @@ function saveLocal() {
438
  setTimeout(()=>URL.revokeObjectURL(url),60000);
439
  }
440
  $('saveLocal').onclick=saveLocal;
441
- $('forgetLocal').onclick=()=>{localResult=null;localAnalysis=null;pairedAnalysisPending=false;renderAnalysisPanel();buttons();say('Browser result discarded. Downloaded files are unaffected.');};
442
  async function request(path, options = {}) {
443
  const response = await fetch(path, {...options, cache:'no-store', credentials:'same-origin'});
444
  if (!response.ok) {
@@ -455,19 +465,71 @@ async function encoded(file) {
455
  }
456
  const headers = () => ({'X-Job-Key':job.key});
457
  const networkSource=()=>stagedNetwork || $('network').files[0];
 
 
 
 
 
 
 
458
  window.addEventListener('ald:batch-draft-change',buttons);
459
  document.addEventListener('track-library-change',buttons);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  async function refreshFreezeNetwork(file){
 
461
  if(!file){window.setFreezeNetwork?.(null);window.TrackReplay?.setStagedNetwork?.(null);return;}
462
  try{const network=await validateNetworkBytes(new Uint8Array(await file.arrayBuffer()));if(networkSource()===file){window.setFreezeNetwork?.(network);window.TrackReplay?.setStagedNetwork?.(network);}}
463
  catch(error){if(networkSource()===file){window.setFreezeNetwork?.(null);window.TrackReplay?.setStagedNetwork?.(null);if($('freeze_status'))$('freeze_status').textContent='Network metadata unavailable: '+error.message;}}
464
  }
465
  $('network').onchange=()=>{stagedNetwork=null;selectedWinner=null;if($('networkChoice'))$('networkChoice').textContent='Uploaded file selected.';refreshFreezeNetwork(networkSource());buttons();};
466
  window.TrainingClient={
467
- isBusy:()=>busy||!!job||bundleRestoring,
468
  hasUnsavedState:()=>!!(networkSource()||localResult||$('track').files.length||trackLibrary?.info?.()?.source==='builtin'||$('editor_file')?.files.length||window.hasUnsavedNetworkDraft?.()),
469
  useNetwork(file){
470
- if(busy||job)throw Error('Finish or delete the current result before changing the network');
471
  stagedNetwork=file;$('network').value='';
472
  if($('networkChoice'))$('networkChoice').textContent='Browser editor input: '+file.name+'. Not uploaded yet.';
473
  refreshFreezeNetwork(file);
@@ -961,6 +1023,8 @@ function showDetails(r){
961
  window.TrackReplay?.setRunAnalysis?.(localAnalysis,runReceipt||portableReceipt||(r?{report:r}:null));
962
  }
963
  $('run').onclick = async () => {
 
 
964
  const comparisonAttempt=!!comparisonPlan,comparisonRole=comparisonAttempt?comparisonPlan.roles[comparisonPlan.index]:null;
965
  const previousRunResultContext=runResultContext;
966
  cancelSteamLeaderboard();
 
2
  const $ = id => document.getElementById(id);
3
  let job = null;
4
  let busy = false;
5
+ let replayBusy = false;
6
+ let localReplay = null;
7
+ let replayRequestId = 0;
8
  let localResult = null;
9
  let localAnalysis = null;
10
  let pairedAnalysisPending = false;
 
61
  const trackInfo=trackLibrary?.info()??{source:'upload'},trackFile=$('track').files[0],network=networkSource(),backend=$('backend').value;
62
  const hasTrack=trackInfo.source==='builtin'||!!trackFile;
63
  let reason='';
64
+ if(busy||replayBusy||bundleRestoring||bucketOperation)reason='Finish the current operation before starting a batch.';
65
  else if(job)reason='Finish or delete the current result before starting a batch.';
66
  else if(!capabilitiesReady)reason='Connect to the training service before running a batch.';
67
  else if($('run_mode')?.value==='evaluate')reason='Under Action in Run & results, choose Train a population before running a batch.';
 
73
  network:network?.name||(network?'Selected network':null),backend:backend.toUpperCase()};
74
  }
75
  function buttons() {
76
+ const settingsUnavailable=busy||replayBusy||bundleRestoring||bucketOperation;
77
  const unavailable=settingsUnavailable||!!job;
78
  trackLibrary?.setBusy(unavailable);
79
  window.HFAuth?.setBusy(unavailable);
 
90
  $('forgetLocal').disabled = busy || !localResult;
91
  $('track').disabled=unavailable;
92
  $('network').disabled=unavailable;
93
+ if($('replay_record'))$('replay_record').disabled=unavailable||batchActive||!capabilitiesReady||!replayNetworkSource();
94
+ if($('replay_duration'))$('replay_duration').disabled=settingsUnavailable;
95
+ if($('replay_download'))$('replay_download').disabled=replayBusy||!localReplay;
96
+ if($('replay_policy')){
97
+ const selected=networkSource();
98
+ $('replay_policy').textContent=selected?'Policy: '+(selected.name||'Selected network'):localResult?'Policy: latest received result. Choose a winner below to record a different policy.':'Choose or create a network to record a drive.';
99
+ }
100
  if($('batch_start')){
101
  const context=batchRunContext();
102
  window.setBatchRunContext?.(context);
 
448
  setTimeout(()=>URL.revokeObjectURL(url),60000);
449
  }
450
  $('saveLocal').onclick=saveLocal;
451
+ $('forgetLocal').onclick=()=>{invalidateReplay();localResult=null;localAnalysis=null;pairedAnalysisPending=false;renderAnalysisPanel();buttons();say('Browser result discarded. Downloaded files are unaffected.');};
452
  async function request(path, options = {}) {
453
  const response = await fetch(path, {...options, cache:'no-store', credentials:'same-origin'});
454
  if (!response.ok) {
 
465
  }
466
  const headers = () => ({'X-Job-Key':job.key});
467
  const networkSource=()=>stagedNetwork || $('network').files[0];
468
+ function replayNetworkSource(){return networkSource()||localResult;}
469
+ function invalidateReplay(reason){
470
+ replayRequestId++;
471
+ localReplay=null;
472
+ window.TrackReplay?.clearRecording?.(reason);
473
+ if($('replay_download'))$('replay_download').disabled=true;
474
+ }
475
  window.addEventListener('ald:batch-draft-change',buttons);
476
  document.addEventListener('track-library-change',buttons);
477
+ document.addEventListener('track-library-change',()=>invalidateReplay('Track changed. Record a new drive for this track.'));
478
+ if($('replay_record'))$('replay_record').onclick=async()=>{
479
+ if(busy||replayBusy||job||batchActive||bundleRestoring||bucketOperation)return;
480
+ replayBusy=true;busy=true;
481
+ invalidateReplay('Recording a fresh CPU drive…');
482
+ const attempt=replayRequestId;
483
+ const controller=new AbortController();
484
+ const timeout=setTimeout(()=>controller.abort(),40000);
485
+ buttons();
486
+ try{
487
+ const track=await trackSource(),network=replayNetworkSource();
488
+ if(!track||!network)throw Error('Choose a track and a network before recording.');
489
+ const ticks=Number($('replay_duration').value);
490
+ if(![300,900].includes(ticks))throw Error('Choose a 5-second or 15-second recording.');
491
+ const [trackBytes,networkBytes]=await Promise.all([track.arrayBuffer(),network.arrayBuffer()]);
492
+ await validateNetworkBytes(new Uint8Array(networkBytes));
493
+ const [trackSha,networkSha,track64,network64]=await Promise.all([
494
+ sha(trackBytes),sha(networkBytes),encoded(track),encoded(network)
495
+ ]);
496
+ const response=await request('/api/replay',{method:'POST',signal:controller.signal,
497
+ headers:{'Content-Type':'application/json'},body:JSON.stringify({track:track64,network:network64,ticks})});
498
+ const raw=await response.text();
499
+ if(raw.length>2*1024*1024)throw Error('Recording exceeds the browser limit.');
500
+ const recording=JSON.parse(raw);
501
+ const currentTrack=await trackSource(),currentNetwork=replayNetworkSource();
502
+ if(attempt!==replayRequestId||!currentTrack||!currentNetwork)throw Error('Selection changed during recording. Record the current selection again.');
503
+ const [currentTrackSha,currentNetworkSha]=await Promise.all([
504
+ currentTrack.arrayBuffer().then(sha),currentNetwork.arrayBuffer().then(sha)
505
+ ]);
506
+ if(currentTrackSha!==trackSha||currentNetworkSha!==networkSha)throw Error('Selection changed during recording. Record the current selection again.');
507
+ if(!window.TrackReplay?.setRecording)throw Error('The recorded-drive viewer is unavailable. Reload this page.');
508
+ await window.TrackReplay.setRecording(recording,trackSha,networkSha);
509
+ if(attempt!==replayRequestId){window.TrackReplay.clearRecording();throw Error('Selection changed during recording. Record the current selection again.');}
510
+ localReplay=recording;
511
+ }catch(error){
512
+ if(attempt===replayRequestId&&$('replay_status'))$('replay_status').textContent=
513
+ error?.name==='AbortError'?'Recording timed out. Try a 5-second drive.':'Recording unavailable: '+error.message;
514
+ }finally{
515
+ clearTimeout(timeout);replayBusy=false;busy=false;buttons();
516
+ }
517
+ };
518
+ if($('replay_download'))$('replay_download').onclick=()=>{
519
+ if(localReplay)downloadTextFile(JSON.stringify(localReplay),'ald-recorded-drive.json','application/json');
520
+ };
521
  async function refreshFreezeNetwork(file){
522
+ invalidateReplay('Network changed. Record a new drive for this policy.');
523
  if(!file){window.setFreezeNetwork?.(null);window.TrackReplay?.setStagedNetwork?.(null);return;}
524
  try{const network=await validateNetworkBytes(new Uint8Array(await file.arrayBuffer()));if(networkSource()===file){window.setFreezeNetwork?.(network);window.TrackReplay?.setStagedNetwork?.(network);}}
525
  catch(error){if(networkSource()===file){window.setFreezeNetwork?.(null);window.TrackReplay?.setStagedNetwork?.(null);if($('freeze_status'))$('freeze_status').textContent='Network metadata unavailable: '+error.message;}}
526
  }
527
  $('network').onchange=()=>{stagedNetwork=null;selectedWinner=null;if($('networkChoice'))$('networkChoice').textContent='Uploaded file selected.';refreshFreezeNetwork(networkSource());buttons();};
528
  window.TrainingClient={
529
+ isBusy:()=>busy||replayBusy||!!job||bundleRestoring,
530
  hasUnsavedState:()=>!!(networkSource()||localResult||$('track').files.length||trackLibrary?.info?.()?.source==='builtin'||$('editor_file')?.files.length||window.hasUnsavedNetworkDraft?.()),
531
  useNetwork(file){
532
+ if(busy||replayBusy||job)throw Error('Finish the current operation before changing the network');
533
  stagedNetwork=file;$('network').value='';
534
  if($('networkChoice'))$('networkChoice').textContent='Browser editor input: '+file.name+'. Not uploaded yet.';
535
  refreshFreezeNetwork(file);
 
1023
  window.TrackReplay?.setRunAnalysis?.(localAnalysis,runReceipt||portableReceipt||(r?{report:r}:null));
1024
  }
1025
  $('run').onclick = async () => {
1026
+ if(replayBusy)return;
1027
+ invalidateReplay();
1028
  const comparisonAttempt=!!comparisonPlan,comparisonRole=comparisonAttempt?comparisonPlan.roles[comparisonPlan.index]:null;
1029
  const previousRunResultContext=runResultContext;
1030
  cancelSteamLeaderboard();