Download web/static/app.js from SLM-Archive/hyperdex-trainer: direct link, hf CLI and curl.
- Browser
- Download file 6.82 kB
-
https://huggingface.co/SLM-Archive/hyperdex-trainer/resolve/main/web/static/app.js
- Command line
-
hf download hf://SLM-Archive/hyperdex-trainer/web/static/app.js
-
curl -L -o app.js https://huggingface.co/SLM-Archive/hyperdex-trainer/resolve/main/web/static/app.js
6.82 kB
| /* NanoDex — shared frontend helpers */ | |
| const NX = { | |
| fmtTokens(n){ n = Number(n||0); | |
| if(n>=1e9) return (n/1e9).toFixed(2)+'B'; | |
| if(n>=1e6) return (n/1e6).toFixed(n>=1e7?0:1).replace(/\.0$/,'')+'M'; | |
| if(n>=1e3) return Math.round(n/1e3)+'k'; | |
| return String(n); }, | |
| fmtParams(n){ n = Number(n||0); | |
| return n>=1e6 ? (n/1e6).toFixed(2)+'M' : Math.round(n/1e3)+'k'; }, | |
| fmtTime(s){ s = Number(s||0); if(!isFinite(s)||s<0) return '—'; | |
| if(s<90) return Math.round(s)+'s'; | |
| if(s<5400) return Math.round(s/60)+' min'; | |
| return (s/3600).toFixed(1)+' h'; }, | |
| ago(ts){ if(!ts) return '—'; | |
| const d = Date.now()/1000 - ts; | |
| if(d<60) return 'just now'; | |
| if(d<3600) return Math.floor(d/60)+'m ago'; | |
| if(d<86400) return Math.floor(d/3600)+'h ago'; | |
| return Math.floor(d/86400)+'d ago'; }, | |
| esc(s){ const d=document.createElement('div'); d.textContent=s==null?'':String(s); | |
| return d.innerHTML; }, | |
| chip(status, extra){ | |
| const label = {queued:'queued', running:'training', done:'completed', | |
| failed:'failed', cancelled:'cancelled'}[status] || status; | |
| return `<span class="chip ${status}"><i></i>${label}${extra?' · '+NX.esc(extra):''}</span>`; | |
| }, | |
| async get(url){ | |
| const r = await fetch(url, {headers:{'Accept':'application/json'}}); | |
| if(!r.ok) throw new Error((await r.json().catch(()=>({}))).error || r.statusText); | |
| return r.json(); | |
| }, | |
| async post(url, body){ | |
| const r = await fetch(url, {method:'POST', | |
| headers:{'Content-Type':'application/json'}, body: JSON.stringify(body||{})}); | |
| const j = await r.json().catch(()=>({})); | |
| if(!r.ok) throw new Error(j.error || r.statusText); | |
| return j; | |
| }, | |
| toast(msg, kind){ | |
| let t = document.querySelector('.toast'); | |
| if(!t){ t = document.createElement('div'); t.className='toast'; | |
| document.body.appendChild(t); } | |
| t.className = 'toast ' + (kind||''); | |
| t.innerHTML = msg; | |
| requestAnimationFrame(()=> t.classList.add('on')); | |
| clearTimeout(t._h); | |
| t._h = setTimeout(()=> t.classList.remove('on'), 5200); | |
| }, | |
| runRow(j){ | |
| const pct = (j.progress*100).toFixed(1); | |
| const bits = []; | |
| if(j.status==='running'){ | |
| bits.push(`<span><b>${pct}%</b> of ${NX.fmtTokens(j.target_tokens)}</span>`); | |
| if(j.loss) bits.push(`<span>loss <b>${j.loss.toFixed(4)}</b></span>`); | |
| bits.push(`<span><b>${Math.round(j.tok_per_s/1e3)}k</b> tok/s</span>`); | |
| if(j.eta_s) bits.push(`<span><b>${NX.fmtTime(j.eta_s)}</b> left</span>`); | |
| } else if(j.status==='queued'){ | |
| bits.push(`<span>position <b>#${j.queue_position||'?'}</b></span>`); | |
| bits.push(`<span><b>${NX.fmtTokens(j.target_tokens)}</b> tokens queued</span>`); | |
| } else if(j.status==='done'){ | |
| bits.push(`<span><b>${NX.fmtTokens(j.tokens_seen)}</b> tokens</span>`); | |
| if(j.loss) bits.push(`<span>final loss <b>${j.loss.toFixed(4)}</b></span>`); | |
| bits.push(`<span>${j.pushed_repo ? 'published' : 'not published'}</span>`); | |
| } else { | |
| bits.push(`<span>${NX.esc(j.error||'—')}</span>`); | |
| } | |
| const showBar = j.status==='running'; | |
| return `<a class="run" href="/models/${j.id}"> | |
| <div class="run-top"> | |
| <h4>${NX.esc(j.name)}</h4> | |
| ${NX.chip(j.status)} | |
| </div> | |
| <div class="run-meta"> | |
| <span class="mono">${NX.esc(j.tier_label)}</span> | |
| <span>${Number(j.n_params).toLocaleString()} params</span> | |
| ${bits.join('')} | |
| <span class="muted">${NX.ago(j.created_at)}</span> | |
| </div> | |
| ${showBar ? `<div class="bar"><i style="width:${pct}%"></i></div>` : ''} | |
| </a>`; | |
| }, | |
| /* Minimal dependency-free loss chart. | |
| The viewBox is set to the element's real pixel size so that 1 user unit == | |
| 1 CSS pixel: a fixed 760-wide viewBox squeezed into a 280px phone would | |
| render the axis labels at ~4px. */ | |
| lossChart(el, pts){ | |
| if(!el) return; | |
| el._pts = pts; | |
| const rect = el.getBoundingClientRect(); | |
| const W = Math.max(280, Math.round(rect.width) || 760); | |
| const H = Math.max(170, Math.round(rect.height) || 280); | |
| const narrow = W < 460; | |
| const PL = narrow ? 40 : 54, PR = narrow ? 8 : 14; | |
| const PT = 16, PB = narrow ? 28 : 34; | |
| const TICKS = narrow ? 3 : 4; | |
| el.setAttribute('viewBox', `0 0 ${W} ${H}`); | |
| if(!pts || pts.length < 2){ | |
| el.innerHTML = `<text x="${W/2}" y="${H/2}" class="ph" | |
| text-anchor="middle">waiting for the first metrics…</text>`; | |
| return; | |
| } | |
| const xs = pts.map(p=>p.tokens), ys = pts.map(p=>p.loss); | |
| const x0=Math.min(...xs), x1=Math.max(...xs); | |
| let y0=Math.min(...ys), y1=Math.max(...ys); | |
| const padY=(y1-y0)*0.12 || 0.4; y0-=padY; y1+=padY; | |
| const sx = v => PL + (v-x0)/((x1-x0)||1)*(W-PL-PR); | |
| const sy = v => PT + (1-(v-y0)/((y1-y0)||1))*(H-PT-PB); | |
| const d = pts.map((p,i)=> (i?'L':'M')+sx(p.tokens).toFixed(1)+' '+sy(p.loss).toFixed(1)).join(' '); | |
| const area = d + ` L${sx(x1).toFixed(1)} ${H-PB} L${sx(x0).toFixed(1)} ${H-PB} Z`; | |
| let g=''; | |
| for(let i=0;i<=TICKS;i++){ | |
| const v = y0+(y1-y0)*i/TICKS, y = sy(v); | |
| g += `<line class="gl" x1="${PL}" y1="${y.toFixed(1)}" x2="${W-PR}" y2="${y.toFixed(1)}"/>`; | |
| g += `<text class="ax" x="${PL-7}" y="${(y+3.5).toFixed(1)}" text-anchor="end">${v.toFixed(2)}</text>`; | |
| } | |
| for(let i=0;i<=TICKS;i++){ | |
| const v = x0+(x1-x0)*i/TICKS, x = sx(v); | |
| const anchor = i===0 ? 'start' : (i===TICKS ? 'end' : 'middle'); | |
| g += `<text class="ax" x="${x.toFixed(1)}" y="${H-PB+18}" text-anchor="${anchor}">${NX.fmtTokens(v)}</text>`; | |
| } | |
| const last = pts[pts.length-1]; | |
| el.innerHTML = `<defs><linearGradient id="lg" x1="0" y1="0" x2="0" y2="1"> | |
| <stop offset="0%" stop-color="#2ee6a8" stop-opacity=".22"/> | |
| <stop offset="100%" stop-color="#2ee6a8" stop-opacity="0"/> | |
| </linearGradient></defs> | |
| ${g} | |
| <path class="ar" d="${area}"/> | |
| <path class="ln" d="${d}"/> | |
| <circle cx="${sx(last.tokens).toFixed(1)}" cy="${sy(last.loss).toFixed(1)}" | |
| r="4" fill="#2ee6a8"/> | |
| <text class="ax" x="${W-PR}" y="${PT+2}" text-anchor="end" | |
| style="fill:#2ee6a8;font-size:12px">loss ${last.loss.toFixed(4)}</text>`; | |
| }, | |
| poll(fn, ms){ | |
| let stop=false; | |
| const tick = async () => { | |
| if(stop) return; | |
| try { await fn(); } catch(e){ /* keep polling */ } | |
| if(!stop) setTimeout(tick, ms); | |
| }; | |
| tick(); | |
| return ()=>{ stop=true; }; | |
| } | |
| }; | |
| window.NX = NX; | |
| /* Charts use pixel-sized viewBoxes, so they must be redrawn when the box | |
| changes — rotating a phone, or opening the keyboard. */ | |
| let _rsT; | |
| addEventListener('resize', () => { | |
| clearTimeout(_rsT); | |
| _rsT = setTimeout(() => { | |
| document.querySelectorAll('svg.chart').forEach(el => { | |
| if(el._pts) NX.lossChart(el, el._pts); | |
| }); | |
| }, 160); | |
| }); | |