Spaces:
Running
Running
Download arena-ui.js from autotrust/JEV-27B-Demo: direct link, hf CLI and curl.
- Browser
- Download file 18.8 kB
-
https://huggingface.co/spaces/autotrust/JEV-27B-Demo/resolve/main/arena-ui.js
- Command line
-
hf download hf://spaces/autotrust/JEV-27B-Demo/arena-ui.js
-
curl -L -o arena-ui.js https://huggingface.co/spaces/autotrust/JEV-27B-Demo/resolve/main/arena-ui.js
18.8 kB
| (() => { | |
| ; | |
| const $ = id => document.getElementById(id); | |
| const GAMES = { | |
| mario:{title:"Mario",seed:0,caption:"The model controls movement and jumping, one decision at a time.",note:"Original World 1-1 · Movement and jump choices run sequentially.",example:"/mario-example.mp4",exampleTitle:"Mario · Recorded example · 5×"}, | |
| cube:{title:"Rubik’s Cube",seed:20260930,caption:"A full 25-turn scramble. Each model choice selects a sequence of cube turns.",note:"25-turn scramble · Watch each selected algorithm unfold in 3D.",example:"/cube-example.mp4",exampleTitle:"Rubik’s Cube · Recorded example · 10×"}, | |
| tetris:{title:"Tetris",seed:0,caption:"Clear 20 lines as the pieces fall faster. Play continues at maximum speed.",note:"Natural gravity · No drop shortcut · 20-line goal.",example:"/tetris-example.mp4",exampleTitle:"Tetris · Recorded example · 5×"} | |
| }; | |
| const TERMINAL = new Set(["completed","failed","cancelled"]); | |
| const sleep = ms => new Promise(r=>setTimeout(r,ms)); | |
| const safelyRead = key=>{try{return JSON.parse(localStorage.getItem(key))}catch{return null}}; | |
| const safelyWrite = (key,value)=>{try{if(value===null)localStorage.removeItem(key);else localStorage.setItem(key,JSON.stringify(value))}catch{}}; | |
| let clientId=safelyRead("jev-arena-client"); | |
| if(typeof clientId!=="string" || clientId.length>100){clientId=crypto.randomUUID?.() || "client-"+Date.now().toString(36)+"-"+Math.random().toString(36).slice(2);safelyWrite("jev-arena-client",clientId);} | |
| let selected=Object.hasOwn(GAMES,location.hash.slice(1))?location.hash.slice(1):"mario"; | |
| let online=false, initial={}, health={}; | |
| const states=Object.fromEntries(Object.keys(GAMES).map(game=>[game,{ | |
| game,seed:GAMES[game].seed,job:null,status:"ready",view:null,decision:null,metrics:{}, | |
| queue:[],draining:false,polling:false,nextPoll:0,after:0,summary:null,message:"",error:false, | |
| restoring:false,hasMore:false,generation:0,cancelling:false,starting:false,skip:false,initialReceived:false | |
| }])); | |
| const rendererOptions={compact:window.innerWidth<=500}; | |
| const renderer=ArenaRenderer.create($("game-canvas"),rendererOptions); | |
| const reducedMotion=matchMedia("(prefers-reduced-motion: reduce)").matches; | |
| const format=value=>Number.isFinite(Number(value))?Number(value).toLocaleString("en-US"):"—"; | |
| const pretty=value=>String(value??"—").replaceAll("_"," "); | |
| function persist(s){safelyWrite("jev-arena-"+s.game,s.job?{...s.job,game:s.game,seed:s.seed}:null);} | |
| function isRunning(s){return !!s.job&&!TERMINAL.has(s.status);} | |
| function isBusy(s){return s.starting||isRunning(s)||s.draining||s.hasMore||s.queue.length>0;} | |
| function successOf(s){return s.summary?.success??s.summary?.result?.success??s.view?.success??s.view?.hud?.success??false;} | |
| function stateLabel(s){ | |
| if(s.starting)return "Starting"; | |
| if(s.cancelling)return "Stopping"; | |
| if(s.status==="queued")return "Queued"; | |
| if(s.status==="running")return "Running"; | |
| if(s.status==="completed"&&(s.draining||s.hasMore||s.queue.length))return "Playing moves"; | |
| if(s.status==="completed")return successOf(s)?"Completed":"Run finished"; | |
| if(s.status==="failed")return "Run failed"; | |
| if(s.status==="cancelled")return "Stopped"; | |
| return "Ready"; | |
| } | |
| function notice(s){ | |
| let text=s.message; | |
| if(!text&&s.status==="queued")text="Your run is queued. It will start when this game’s current run finishes."; | |
| if(!text&&s.status==="completed"&&!isBusy(s)&&!successOf(s))text="This run ended before the goal. Start again or try another seed."; | |
| if(!text&&s.status==="failed")text="The run could not finish. Start again to try a fresh run."; | |
| $("notice").hidden=!text;$("notice").textContent=text;$("notice").classList.toggle("error",s.error||s.status==="failed"); | |
| } | |
| function metric(id,value){ | |
| const el=$(id);el.replaceChildren(); | |
| el.append(document.createTextNode(Number.isFinite(Number(value))&&value!==null&&value!==undefined?Number(value).toFixed(1):"—")); | |
| const unit=document.createElement("small");unit.textContent="ms";el.append(unit); | |
| } | |
| function renderDecisions(s){ | |
| metric("current-inference",s.metrics.current_inference_ms);metric("average-inference",s.metrics.average_inference_ms); | |
| const count=s.metrics.calls??0;$("call-count").textContent=format(count)+(count===1?" call":" calls"); | |
| $("chosen-action").textContent=s.decision?.action??(isRunning(s)?"Awaiting decision":"Waiting to start"); | |
| $("decision-description").textContent=s.decision?.description??(isRunning(s)?"The model is evaluating the current game state.":"Choose a game and start the model."); | |
| const options=$("options");options.replaceChildren(); | |
| const entries=Array.isArray(s.decision?.options)?s.decision.options:[]; | |
| const probs=Array.isArray(s.decision?.probabilities)?s.decision.probabilities:[]; | |
| const subdecisions=Array.isArray(s.decision?.subdecisions)?s.decision.subdecisions:[]; | |
| const selectedChoice=s.decision?.choice??subdecisions.at(-1)?.choice??s.decision?.action; | |
| entries.forEach((option,i)=>{ | |
| const label=typeof option==="object"?(option.label??option.name??option.action??"Option "+(i+1)):String(option); | |
| const probability=Number(probs[i]); | |
| const row=document.createElement("div");row.className="option-row"; | |
| const valid=Number.isFinite(probability)&&probability>=0; | |
| row.style.setProperty("--probability",(valid?Math.min(1,probability)*100:0)+"%"); | |
| row.classList.toggle("chosen",label===selectedChoice); | |
| const name=document.createElement("span");name.className="option-name";name.textContent=label;name.title=label; | |
| const value=document.createElement("span");value.className="option-value";value.textContent=valid?(probability*100).toFixed(1)+"%":"—"; | |
| row.append(name,value);options.append(row); | |
| }); | |
| $("subdecisions").replaceChildren(...subdecisions.map(sub=>{ | |
| const row=document.createElement("div");row.className="subdecision"; | |
| const name=document.createElement("span");name.className="subdecision-name";name.textContent=pretty(sub.name); | |
| const choice=document.createElement("span");choice.className="subdecision-choice";choice.textContent=pretty(sub.choice); | |
| const time=document.createElement("span");time.className="subdecision-time";time.textContent=Number.isFinite(sub.inference_ms)?sub.inference_ms.toFixed(1)+" ms":"—"; | |
| row.append(name,choice,time);return row; | |
| })); | |
| } | |
| function renderStats(s){ | |
| const v=s.view??initial[s.game], h=v?.hud??{}; | |
| let rows=[],progress=""; | |
| if(s.game==="mario"){ | |
| progress="World "+(h.world??1)+"-"+(h.stage??1); | |
| rows=[["Position",format(h.x_position)+" px"],["Score",format(h.score??0)],["Coins",format(h.coins??0)],["Decisions",format(h.steps??0)],["Action",pretty(v?.last_action)],["Goal",h.success?"Flag reached":"Reach the flag"]]; | |
| }else if(s.game==="cube"){ | |
| progress=(v?.solved_faces??0)+" / 6 faces"; | |
| rows=[["Faces",format(v?.solved_faces??0)+" / 6"],["Stickers",format(v?.matched_stickers??0)+" / 54"],["Quarter turns",format(v?.primitive_steps??0)],["Decisions",format(v?.steps??0)],["Phase",pretty(v?.phase)],["Scramble","25 turns"]]; | |
| }else{ | |
| progress=(v?.lines??0)+" / 20 lines"; | |
| rows=[["Lines",format(v?.lines??0)+" / 20"],["Score",format(v?.score??0)],["Pieces",format(v?.pieces??0)],["Gravity",v?.gravity_ms?Math.round(v.gravity_ms)+" ms / row":"100 ms / row"],["Speed",v?.maximum_speed_reached?"Maximum":((v?.speed_multiplier??1).toFixed(2)+"×")],["Decisions",format(v?.steps??0)]]; | |
| } | |
| $("game-progress").textContent=progress; | |
| $("stats").replaceChildren(...rows.map(([label,value])=>{ | |
| const div=document.createElement("div");div.className="stat"; | |
| const dt=document.createElement("dt");dt.textContent=label; | |
| const dd=document.createElement("dd");dd.textContent=value;div.append(dt,dd);return div; | |
| })); | |
| } | |
| function controls(s){ | |
| $("start").disabled=isBusy(s)||!online; | |
| $("start").lastChild.textContent=s.job&&TERMINAL.has(s.status)?" Run again":" Start model"; | |
| $("stop").hidden=!isBusy(s); | |
| $("stop").disabled=s.cancelling||s.starting; | |
| $("stop").textContent=TERMINAL.has(s.status)&&(s.draining||s.queue.length)?"Skip animation":"Stop run"; | |
| $("seed").disabled=isBusy(s)||s.game==="mario"; | |
| $("shuffle").hidden=s.game==="mario";$("shuffle").disabled=isBusy(s); | |
| const pill=$("state-pill");pill.textContent=stateLabel(s); | |
| pill.className="state-pill"+(isBusy(s)?" running":(s.status==="completed"&&successOf(s)?" success":s.status==="failed"?" failed":"")); | |
| $("stage-label").textContent=isBusy(s)?(s.status==="queued"?"Waiting for a slot":s.status==="completed"?"Selected moves · playback":"Live run"):s.status==="ready"?"Opening state":s.status==="cancelled"?"Run stopped":"Run result"; | |
| $("canvas-loading").hidden=!(s.starting||(isRunning(s)&&!s.initialReceived&&s.status!=="queued")); | |
| notice(s); | |
| for(const [game,state] of Object.entries(states))$("tab-status-"+game).classList.toggle("active",isBusy(state)); | |
| } | |
| function safeRender(view,context=null){ | |
| try{const result=renderer.render(view,context);if(result?.catch)result.catch(()=>{});}catch{} | |
| } | |
| function paint(s,draw=true){ | |
| if(selected!==s.game)return; | |
| controls(s);renderDecisions(s);renderStats(s); | |
| if(draw)safeRender(s.view??initial[s.game]??null); | |
| } | |
| function selectGame(game,updateHash=true){ | |
| if(!Object.hasOwn(GAMES,game))return; | |
| selected=game; | |
| for(const tab of document.querySelectorAll(".game-tab")){const active=tab.dataset.game===game;tab.setAttribute("aria-selected",String(active));tab.tabIndex=active?0:-1;} | |
| $("game-panel").setAttribute("aria-labelledby","tab-"+game); | |
| $("game-title").textContent=GAMES[game].title; | |
| $("game-caption").textContent=GAMES[game].caption; | |
| $("game-note").textContent=GAMES[game].note; | |
| $("seed").value=states[game].seed; | |
| $("game-canvas").setAttribute("aria-label",GAMES[game].title+" game view"); | |
| if(updateHash)history.replaceState(null,"","#"+game); | |
| paint(states[game]); | |
| } | |
| async function request(url,options={}){ | |
| const controller=new AbortController();const timeout=setTimeout(()=>controller.abort(),18000); | |
| try{ | |
| const response=await fetch(url,{...options,signal:controller.signal,cache:"no-store"}); | |
| let body;try{body=await response.json()}catch{body={}}; | |
| if(!response.ok){ | |
| const detail=typeof body.detail==="string"?body.detail:typeof body.error==="string"?body.error:"Request failed ("+response.status+")"; | |
| const error=new Error(detail);error.status=response.status;throw error; | |
| } | |
| return body; | |
| }catch(error){if(error.name==="AbortError")throw new Error("Connection timed out. Please try again.");throw error;} | |
| finally{clearTimeout(timeout);} | |
| } | |
| async function start(){ | |
| const s=states[selected];if(isBusy(s))return; | |
| const seed=Number($("seed").value); | |
| if(!Number.isInteger(seed)||seed<0||seed>2147483647){s.message="Enter a whole-number seed between 0 and 2147483647.";s.error=true;paint(s,false);return;} | |
| s.starting=true;s.message="";s.error=false;paint(s,false); | |
| try{ | |
| const job=await request("/api/arena/jobs",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({game:s.game,seed,client_id:clientId})}); | |
| if(!job.id||!job.ticket)throw new Error("The server did not return a run ticket. Please try again."); | |
| Object.assign(s,{seed,job,status:job.status??"queued",after:0,summary:null,view:null,decision:null,metrics:{},queue:[],nextPoll:0,restoring:false,hasMore:false,initialReceived:false,skip:false}); | |
| s.generation++;persist(s); | |
| }catch(error){s.message=error.message;s.error=true;} | |
| finally{s.starting=false;paint(s);} | |
| } | |
| async function stop(){ | |
| const s=states[selected]; | |
| if(TERMINAL.has(s.status)){s.skip=true;return;} | |
| if(!s.job||s.cancelling)return; | |
| s.cancelling=true;s.message="";paint(s,false); | |
| try{ | |
| const result=await request("/api/arena/jobs/"+encodeURIComponent(s.job.id)+"/cancel",{method:"POST",headers:{"X-Job-Ticket":s.job.ticket}}); | |
| if(result.status==="cancelled"){s.status="cancelled";s.skip=true;} | |
| s.message="Stopping this run…"; | |
| s.nextPoll=0; | |
| }catch(error){s.message=error.message;s.error=true;} | |
| finally{s.cancelling=false;paint(s,false);} | |
| } | |
| async function animateView(s,view,turn,generation){ | |
| const before=s.view??initial[s.game]; | |
| if(s.game!=="cube"||!turn||s.skip||s.restoring||selected!==s.game||reducedMotion){ | |
| s.view=view;paint(s);return; | |
| } | |
| const duration=145;let started=null; | |
| await new Promise(resolve=>{ | |
| function frame(now){ | |
| if(generation!==s.generation)return resolve(); | |
| if(started===null)started=now; | |
| const p=Math.min(1,(now-started)/duration); | |
| if(selected===s.game)safeRender(view,{previousView:before,action:turn,progress:p}); | |
| if(p<1&&!s.skip&&selected===s.game)requestAnimationFrame(frame);else resolve(); | |
| } | |
| requestAnimationFrame(frame); | |
| }); | |
| if(generation!==s.generation)return; | |
| s.view=view;paint(s); | |
| } | |
| async function processEvent(s,event,generation){ | |
| if(event.metrics)s.metrics={...s.metrics,...event.metrics}; | |
| if(event.decision)s.decision=event.decision; | |
| if(event.message){s.message=pretty(event.message);s.error=event.kind==="error";} | |
| if(event.kind==="started"||event.kind==="start"||event.kind==="initial")s.message=""; | |
| const frames=Array.isArray(event.frames)?event.frames:[]; | |
| if(event.view||frames.length)s.initialReceived=true; | |
| if(s.game==="cube"&&frames.length){ | |
| paint(s,false); | |
| for(const frame of frames){ | |
| if(generation!==s.generation)return; | |
| const view=frame.view??frame; | |
| await animateView(s,view,frame.primitive_turn??view.primitive_turn,generation); | |
| } | |
| } | |
| if(event.view){ | |
| s.view=event.view;paint(s); | |
| }else if(frames.length&&s.game!=="cube"){ | |
| s.view=frames[frames.length-1].view??frames[frames.length-1];paint(s); | |
| }else paint(s,false); | |
| } | |
| async function drain(s){ | |
| if(s.draining)return;s.draining=true; | |
| const generation=s.generation; | |
| try{ | |
| while(s.queue.length&&generation===s.generation){ | |
| const event=s.queue.shift(); | |
| await processEvent(s,event,generation); | |
| } | |
| }finally{ | |
| s.draining=false; | |
| if(TERMINAL.has(s.status)&&!s.queue.length)s.skip=false; | |
| paint(s,false); | |
| } | |
| } | |
| async function poll(s){ | |
| if(s.polling||!s.job||Date.now()<s.nextPoll||TERMINAL.has(s.status)&&!s.restoring&&!s.hasMore)return; | |
| s.polling=true; | |
| try{ | |
| const data=await request("/api/arena/jobs/"+encodeURIComponent(s.job.id)+"?after="+s.after+"&limit=30",{headers:{"X-Job-Ticket":s.job.ticket}}); | |
| const events=Array.isArray(data.events)?data.events:[]; | |
| for(const event of events){ | |
| if(!Number.isInteger(event.seq)||event.seq>s.after){s.queue.push(event);if(Number.isInteger(event.seq))s.after=event.seq;} | |
| } | |
| s.hasMore=!!data.has_more; | |
| if(data.summary)s.summary=data.summary; | |
| if(data.status)s.status=data.status; | |
| if(TERMINAL.has(s.status)&&s.message.startsWith("Stopping this run"))s.message=""; | |
| if(s.message.startsWith("Reconnecting")){s.message="";s.error=false;} | |
| if(s.status!=="queued"&&s.message.startsWith("Your run is queued"))s.message=""; | |
| // On reload, catch up to the latest recorded state without replaying old turns. | |
| if(s.restoring){ | |
| for(const event of s.queue.splice(0)){ | |
| if(event.metrics)s.metrics={...s.metrics,...event.metrics}; | |
| if(event.decision)s.decision=event.decision; | |
| if(event.view){s.view=event.view;s.initialReceived=true;} | |
| else if(event.frames?.length){s.view=event.frames.at(-1).view??event.frames.at(-1);s.initialReceived=true;} | |
| } | |
| if(!data.has_more)s.restoring=false; | |
| paint(s); | |
| }else void drain(s); | |
| s.nextPoll=Date.now()+(data.has_more?20:s.status==="queued"?1000:300); | |
| paint(s,false); | |
| }catch(error){ | |
| s.message="Reconnecting to your run… "+error.message;s.error=true;s.nextPoll=Date.now()+2500; | |
| if(error.status===403||error.status===404||error.status===410){ | |
| s.status="failed";s.restoring=false;s.message="This run is no longer available. Start a new run.";persist({...s,job:null}); | |
| } | |
| paint(s,false); | |
| }finally{s.polling=false;} | |
| } | |
| async function checkHealth(){ | |
| try{ | |
| health=await request("/api/arena/status");online=!!health.online; | |
| $("connection-text").textContent=online?"GPU connected":"GPU reconnecting"; | |
| $("connection").className="connection "+(online?"online":"offline"); | |
| }catch{ | |
| online=false;$("connection-text").textContent="Connection unavailable";$("connection").className="connection offline"; | |
| } | |
| controls(states[selected]); | |
| } | |
| document.querySelectorAll(".game-tab").forEach(tab=>{ | |
| tab.addEventListener("click",()=>selectGame(tab.dataset.game)); | |
| tab.addEventListener("keydown",event=>{ | |
| const games=Object.keys(GAMES);let index=games.indexOf(selected); | |
| if(event.key==="ArrowRight")index=(index+1)%games.length; | |
| else if(event.key==="ArrowLeft")index=(index+games.length-1)%games.length; | |
| else if(event.key==="Home")index=0; | |
| else if(event.key==="End")index=games.length-1; | |
| else return; | |
| event.preventDefault();selectGame(games[index]);$("tab-"+games[index]).focus(); | |
| }); | |
| }); | |
| $("start").addEventListener("click",start);$("stop").addEventListener("click",stop); | |
| $("shuffle").addEventListener("click",()=>{ | |
| const array=new Uint32Array(1);crypto.getRandomValues(array); | |
| states[selected].seed=array[0]%2147483647;$("seed").value=states[selected].seed; | |
| }); | |
| $("seed").addEventListener("change",()=>{const value=Number($("seed").value);if(Number.isInteger(value))states[selected].seed=value;}); | |
| window.addEventListener("hashchange",()=>selectGame(location.hash.slice(1),false)); | |
| $("watch-example").addEventListener("click",()=>{ | |
| $("example-title").textContent=GAMES[selected].exampleTitle; | |
| $("example-video").src=GAMES[selected].example; | |
| $("example-dialog").showModal(); | |
| }); | |
| function closeExample(){$("example-video").pause();$("example-dialog").close();$("example-video").removeAttribute("src");$("example-video").load();} | |
| $("close-example").addEventListener("click",closeExample); | |
| $("example-dialog").addEventListener("cancel",()=>{$("example-video").pause();}); | |
| $("example-dialog").addEventListener("click",event=>{if(event.target===$("example-dialog")){const rect=event.target.getBoundingClientRect();if(event.clientX<rect.left||event.clientX>rect.right||event.clientY<rect.top||event.clientY>rect.bottom)closeExample();}}); | |
| let lastWidth=window.innerWidth; | |
| window.addEventListener("resize",()=>{if((lastWidth<=500)!==(window.innerWidth<=500)){rendererOptions.compact=window.innerWidth<=500;paint(states[selected]);}lastWidth=window.innerWidth;}); | |
| async function init(){ | |
| for(const [game,s] of Object.entries(states)){ | |
| const saved=safelyRead("jev-arena-"+game); | |
| if(saved&&typeof saved.id==="string"&&typeof saved.ticket==="string"&&saved.game===game){ | |
| s.job=saved;s.seed=Number.isInteger(saved.seed)?saved.seed:GAMES[game].seed;s.status="running";s.restoring=true; | |
| } | |
| } | |
| selectGame(selected); | |
| try{initial=await request("/arena-initial.json");paint(states[selected]);}catch{ /* The first live view replaces the opening preview. */ } | |
| await checkHealth(); | |
| setInterval(checkHealth,7000); | |
| setInterval(()=>{for(const s of Object.values(states))void poll(s);},150); | |
| for(const s of Object.values(states))void poll(s); | |
| } | |
| void init(); | |
| })(); |