Download hdc/webui.py from AEUPH/hdcstorage: direct link, hf CLI and curl.
- Browser
- Download file 41.8 kB
-
https://huggingface.co/AEUPH/hdcstorage/resolve/main/hdc/webui.py
- Command line
-
hf download hf://AEUPH/hdcstorage/hdc/webui.py
-
curl -L -o webui.py https://huggingface.co/AEUPH/hdcstorage/resolve/main/hdc/webui.py
41.8 kB
| """The WebUI: a CRT terminal, the diagnostics, the visualisations, the assistant. | |
| Served by ``http.server`` and nothing else — no framework, no bundler, no build | |
| step. The page is one string; the data comes from JSON endpoints that call the | |
| same objects the CLI does, so the browser cannot show something the machine | |
| does not actually do. | |
| Endpoints: | |
| GET / the page (terminal + panels) | |
| GET /api/state boot screen, terminal cells, kernel status | |
| POST /api/exec {"line": "run"} -> console output | |
| POST /api/chat {"text": "..."} -> assistant reply + executed command | |
| GET /api/autonomy idle/readahead state: graded accuracy, lookahead, cache | |
| POST /api/autonomy {"enabled": true} -> toggle the autonomous pass | |
| GET /api/os the webOS frame, windows, memory map | |
| POST /api/os/key {"key": "F5"} -> one named key to the window manager | |
| POST /api/os/text {"text": "stat"} -> typing into the focused window | |
| POST /api/os/click {"x": 12, "y": 4} -> focus the window at that cell | |
| POST /api/os/launch {"app": "ide"} -> open an app | |
| POST /api/boot re-run POST + boot sector | |
| GET /api/diag every layer's measured report | |
| GET /api/viz/<name> one visualisation as ASCII | |
| GET /api/pipeline the stages, with their real counters | |
| It binds ``127.0.0.1`` by default and holds a single kernel in memory: this is a | |
| local instrument panel, not a multi-tenant service. There is no authentication | |
| because there is no remote exposure. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import threading | |
| from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | |
| from .assistant import INTENTS, Assistant | |
| from .autonomy import AutonomousMode | |
| from .boot import BootSequence | |
| from .hypervec import DEFAULT_DIM | |
| from .os import Desktop | |
| from .semantic import load as load_semantic | |
| from .terminal import Kernel | |
| from .x86 import Boot | |
| PAGE = """<!DOCTYPE html> | |
| <html lang="en"><head><meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>HDCSTORAGE</title> | |
| <style> | |
| :root{--ph:#7dffb0;--dim:#2f7a52;--amber:#ffbf5e;--bg:#04140c;--panel:#061c12} | |
| *{box-sizing:border-box} | |
| html,body{margin:0;background:var(--bg);color:var(--ph); | |
| font:14px/1.35 "Cascadia Mono","Consolas","DejaVu Sans Mono",monospace} | |
| body{padding:clamp(6px,1.1vw,12px)} | |
| #wrap{display:grid;grid-template-columns:minmax(0,1fr) minmax(300px,430px); | |
| gap:clamp(6px,0.9vw,12px);align-items:start;max-width:1900px;margin:0 auto} | |
| .panel{background:var(--panel);border:1px solid var(--dim);border-radius:10px;padding:10px; | |
| box-shadow:0 0 24px rgba(0,255,140,.07) inset,0 0 12px rgba(0,255,140,.05);min-width:0} | |
| h2{margin:0 0 8px;font-size:12px;letter-spacing:.18em;color:var(--dim);font-weight:600; | |
| text-transform:uppercase;display:flex;align-items:center;gap:8px;flex-wrap:wrap} | |
| h2 span.title{flex:1;min-width:0} | |
| #fontctl{display:flex;gap:4px;letter-spacing:0} | |
| #fontctl button{padding:2px 7px;font-size:11px;line-height:1.2;border-radius:5px} | |
| /* The CRT is a fixed 80-column character grid and must never reflow, so the font | |
| size is measured in fitCrt() from the real monospace column width *and* from | |
| the box height instead of guessed in CSS. #screen has a definite height (not | |
| a min/max pair) so that measurement is stable, and the whole grid -- menu bar | |
| to taskbar -- is always on screen: nothing needs scrolling. overflow:auto is | |
| only the fallback for A+ zoom, which is an explicit request to go bigger than | |
| the box. */ | |
| #screen{position:relative;border-radius:8px;overflow:auto;overscroll-behavior:contain; | |
| background:radial-gradient(ellipse at center,#08210f 0%,#020a06 100%); | |
| padding:clamp(6px,0.7vw,12px);height:min(76vh,720px);min-height:290px} | |
| #screen:after{content:"";position:absolute;inset:0;pointer-events:none;border-radius:8px; | |
| background:repeating-linear-gradient(0deg,rgba(0,0,0,.34) 0 1px,rgba(0,0,0,0) 1px 3px), | |
| radial-gradient(ellipse at center,rgba(0,0,0,0) 55%,rgba(0,0,0,.6) 100%);} | |
| #crt{white-space:pre;margin:0;color:var(--ph);position:relative;z-index:1; | |
| width:max-content;min-width:100%;font-size:var(--crt-size,13px);line-height:1.3; | |
| text-shadow:0 0 6px rgba(80,255,170,.5),0 0 18px rgba(40,255,140,.2)} | |
| form#row{display:flex;gap:8px;margin:8px 0 0} | |
| input,button{font:inherit;background:#03170e;color:var(--ph);border:1px solid var(--dim); | |
| border-radius:6px;padding:7px 10px;min-width:0} | |
| input{flex:1} | |
| button{cursor:pointer} | |
| button:hover{background:#072a19;border-color:var(--ph)} | |
| button:disabled{opacity:.5;cursor:progress} | |
| #tabs{display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap} | |
| #tabs button.active{background:#0b3320;border-color:var(--ph);color:#c9ffdf} | |
| #view{max-height:70vh;overflow:auto;white-space:pre-wrap;word-break:break-word;font-size:12.5px} | |
| .msg{border-left:2px solid var(--dim);padding:6px 8px;margin:6px 0;background:#05190f} | |
| .msg b{color:#c9ffdf} | |
| .msg.miss{border-color:var(--amber)} | |
| .msg.err{border-color:#ff6b6b} | |
| .small{color:var(--dim);font-size:11.5px} | |
| b.k{color:#c9ffdf} | |
| .ahead{display:flex;gap:6px;flex-wrap:wrap;margin:6px 0} | |
| .ahead span{border:1px solid var(--dim);border-radius:999px;padding:2px 9px;font-size:11.5px} | |
| .ahead span.on{border-color:var(--ph);color:#c9ffdf} | |
| #status{font-size:11.5px;color:var(--dim);margin-top:6px} | |
| /* One column as soon as the split would squeeze the 80-column grid: a 1024-wide | |
| screen at 125-150% browser zoom is really ~800px, and the side panel would | |
| leave the terminal too narrow to be legible. */ | |
| @media (max-width:1180px){#wrap{grid-template-columns:minmax(0,1fr)} | |
| #screen{height:min(58vh,560px)}#view{max-height:52vh}} | |
| @media (max-height:640px){#screen{height:46vh}} | |
| @media (max-height:480px){#screen{height:40vh}#view{max-height:40vh}} | |
| /* desktop mode: the CRT is the screen, so the console prompt steps aside, the | |
| grid takes the window instead of a share of it, and the pointer becomes a | |
| cursor over the window grid. calc() subtracts the panel header and status | |
| line so the whole grid fits the viewport without the terminal scrolling; the | |
| min-height is what 30 rows need at the 8px legibility floor, so a tiny window | |
| scrolls the page rather than cutting the grid. */ | |
| body.desktop form#row{display:none} | |
| body.desktop #crt{cursor:crosshair} | |
| body.desktop #screen{height:calc(100vh - 116px);min-height:344px} | |
| @media (max-width:1180px){body.desktop #screen{height:calc(100vh - 112px)}} | |
| @media (max-height:640px){body.desktop #screen{height:calc(100vh - 104px)}} | |
| @media (max-height:480px){body.desktop #screen{height:calc(100vh - 96px)}} | |
| </style></head><body> | |
| <div id="wrap"> | |
| <div class="panel"> | |
| <h2><span class="title">HDCSTORAGE — CRTC mono, 80x25</span> | |
| <span id="fontctl"> | |
| <button type="button" data-font="-1" title="smaller text">A-</button> | |
| <button type="button" data-font="0" title="fit the grid to the width">FIT</button> | |
| <button type="button" data-font="1" title="larger text">A+</button> | |
| </span></h2> | |
| <div id="screen"><pre id="crt"></pre></div> | |
| <form id="row" onsubmit="send(); return false;"> | |
| <input id="cmd" autocomplete="off" spellcheck="false" enterkeyhint="send" | |
| placeholder="help | asm | run | step | regs | shader | ring | state ..."> | |
| <button type="submit">EXEC</button> | |
| </form> | |
| <div id="status">connecting</div> | |
| </div> | |
| <div class="panel"> | |
| <div id="tabs"> | |
| <button type="button" data-tab="chat" class="active">ASSISTANT</button> | |
| <button type="button" data-tab="os">DESKTOP</button> | |
| <button type="button" data-tab="pipeline">PIPELINE</button> | |
| <button type="button" data-tab="viz">VISUALISE</button> | |
| <button type="button" data-tab="diag">DIAGNOSTICS</button> | |
| </div> | |
| <div id="view"></div> | |
| </div> | |
| </div> | |
| <script> | |
| const scr=document.getElementById('screen'), crt=document.getElementById('crt'), | |
| view=document.getElementById('view'), status=document.getElementById('status'); | |
| let tab='chat', lastScreen=[], booted=false, busy=false; | |
| function esc(s){return String(s==null?'':s).replace(/[&<>]/g,c=>({'&':'&','<':'<','>':'>'}[c]));} | |
| function paint(lines){ | |
| crt.innerHTML=(lines||[]).map(esc).join('\\n'); | |
| // A taller grid (25-row kernel screen, 30-row desktop) needs a smaller font to | |
| // stay whole in the same box, so re-fit when the row count changes. | |
| const rows=gridHeight((lines||[]).length); | |
| if(rows!==gridRows){ gridRows=rows; fitCrt(); } | |
| scr.scrollTop=scr.scrollHeight; // the grid scrolls inside #screen, not #crt | |
| } | |
| function note(text){status.textContent=text;} | |
| // The CRT is exactly 80 columns, so a font picked in px cannot suit every | |
| // display: on a low-resolution panel or at a large font size the grid either | |
| // overflows or turns unreadable. Measure the real column width and derive the | |
| // font size from the space that is actually available -- width *and* height. | |
| // A- / A+ scale around that fit and the choice is remembered across reloads. | |
| const probe=document.createElement('span'); | |
| probe.setAttribute('aria-hidden','true'); | |
| probe.style.cssText='position:absolute;visibility:hidden;left:-9999px;top:0;'+ | |
| 'white-space:pre;font:100px/1 "Cascadia Mono","Consolas","DejaVu Sans Mono",monospace'; | |
| document.body.appendChild(probe); | |
| let zoom=1; | |
| try{ zoom=parseFloat(localStorage.getItem('crtZoom'))||1; }catch(e){ zoom=1; } | |
| if(!(zoom>0) || !isFinite(zoom)) zoom=1; | |
| // The grid the machine draws: 80 columns always, and at least the 25 rows of the | |
| // CRTC text screen so the boot typewriter does not shrink the font line by line. | |
| const GRID_COLS=80, GRID_ROWS=25, LINE=1.3; | |
| let gridRows=GRID_ROWS; | |
| function gridHeight(rows){ return Math.max(GRID_ROWS, Math.min(200, rows||0)); } | |
| function fitCrt(){ | |
| probe.textContent='0'.repeat(GRID_COLS); | |
| const perCol=probe.getBoundingClientRect().width/GRID_COLS; // px per column at 100px | |
| if(!perCol) return; | |
| // clientWidth/Height exclude the scrollbar and border but include the padding, | |
| // which the grid cannot use, so subtract it rather than guessing a constant. | |
| const cs=getComputedStyle(scr); | |
| const padX=parseFloat(cs.paddingLeft)+parseFloat(cs.paddingRight); | |
| const padY=parseFloat(cs.paddingTop)+parseFloat(cs.paddingBottom); | |
| const availW=Math.max(120, scr.clientWidth-padX); | |
| const availH=Math.max(80, scr.clientHeight-padY); | |
| const byWidth=availW/(perCol*GRID_COLS)*100*zoom; // every column visible | |
| const byHeight=availH/(gridRows*LINE)*zoom; // every row visible | |
| // Both constraints at once, which is what removes the scrollbar: fitting width | |
| // alone let a 30-row frame run past a 76vh box, and paint() then scrolled the | |
| // desktop down to its taskbar and hid the menu bar. 8px is the legibility | |
| // floor for a genuinely tiny window; A- still goes below the fit on request. | |
| const size=Math.max(8, Math.min(44, Math.min(byWidth, byHeight))); | |
| crt.style.fontSize=size.toFixed(2)+'px'; | |
| } | |
| document.querySelectorAll('#fontctl button').forEach(btn=>{ | |
| btn.onclick=()=>{ | |
| const k=Number(btn.dataset.font); | |
| zoom = k===0 ? 1 : Math.max(0.55, Math.min(2.6, zoom*(k>0?1.15:1/1.15))); | |
| try{ localStorage.setItem('crtZoom', String(zoom)); }catch(e){} | |
| fitCrt(); note('terminal text '+Math.round(zoom*100)+'% of fit'); | |
| }; | |
| }); | |
| let resizeTimer=0; | |
| window.addEventListener('resize', ()=>{ | |
| clearTimeout(resizeTimer); | |
| resizeTimer=setTimeout(fitCrt, 120); | |
| }); | |
| async function api(path, body){ | |
| const opt = body===undefined ? {} : {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}; | |
| const r = await fetch(path, opt); | |
| const text = await r.text(); | |
| if(!r.ok) throw new Error(path+' -> HTTP '+r.status+' '+text.slice(0,180)); | |
| try { return JSON.parse(text); } catch(e){ throw new Error(path+' -> non-JSON response'); } | |
| } | |
| async function refreshState(){ | |
| try{ | |
| const s = await api('/api/state'); | |
| if(!booted && s.boot){ // type the POST log out, one line at a time | |
| booted=true; lastScreen=[]; | |
| for(const line of s.boot){ | |
| lastScreen.push(line); paint(lastScreen); | |
| await new Promise(r=>setTimeout(r, line.trim()?90:30)); | |
| } | |
| } | |
| lastScreen = s.screen; paint(lastScreen); note(s.status); | |
| }catch(e){ note('offline: '+e.message); } | |
| } | |
| // EXEC: send exactly what was typed and show the kernel's own screen back. | |
| // The kernel echoes the line and reprints the prompt, so the response is the | |
| // authoritative 80x25 grid; an optimistic echo would only add a 26th row and | |
| // then vanish. An empty box and a failed request both say so out loud instead | |
| // of doing nothing, and a failure hands the line back rather than eating it. | |
| async function send(){ | |
| const box=document.getElementById('cmd'); | |
| const line=box.value.trim(); | |
| if(!line){ note('nothing to run \u2014 type a command'); box.focus(); return; } | |
| if(busy){ note('busy: still running the previous line'); return; } | |
| box.value=''; box.disabled=true; busy=true; | |
| note('running: '+line); | |
| try{ | |
| const r = await api('/api/exec', {line}); | |
| lastScreen = r.screen; paint(lastScreen); note('ready'); | |
| }catch(e){ | |
| box.value=line; | |
| lastScreen = lastScreen.concat(['[error] '+e.message]); paint(lastScreen); note('error'); | |
| }finally{ busy=false; box.disabled=false; box.focus(); } | |
| if(tab==='pipeline') showPipeline(); | |
| } | |
| function setTab(name){ | |
| tab=name; | |
| document.querySelectorAll('#tabs button').forEach(b=>b.classList.toggle('active', b.dataset.tab===name)); | |
| document.body.classList.toggle('desktop', name==='os'); | |
| if(name==='chat') showChat(); | |
| else if(name==='os'){ if(bootPlayed) showOs(); else playBoot(); } | |
| else if(name==='pipeline') showPipeline(); | |
| else if(name==='viz') showViz(); | |
| else if(name==='diag') showDiag(); | |
| } | |
| // ---------------------------------------------------------------- the webOS | |
| // The desktop is rendered by the machine and arrives as 80 character rows, so | |
| // the browser's job is only to show them and post keys back. Keys are captured | |
| // globally while the tab is active and sent one at a time; clicks are converted | |
| // from pixels to a character cell and sent as coordinates. | |
| async function showOs(){ | |
| try{ | |
| const s = await api('/api/os'); | |
| paint(s.frame); | |
| note('webOS | '+s.windows.length+' windows | '+s.frame_ms+' ms/frame | '+s.status); | |
| }catch(e){ note('os: '+e.message); } | |
| } | |
| // The boot chain the machine recorded at startup: POST, shader splash, | |
| // AUTOEXEC. It is replayed at its own delays the first time the desktop is | |
| // opened, and any key skips it — the frames are a recording of work that | |
| // already happened, so skipping loses nothing. | |
| let bootPlayed = false, bootSkipped = false, bootPlaying = false; | |
| async function playBoot(){ | |
| if(bootPlayed) return false; | |
| bootPlayed = true; bootPlaying = true; bootSkipped = false; | |
| let frames; | |
| try{ frames = (await api('/api/boot')).frames || []; } | |
| catch(e){ bootPlaying=false; return false; } | |
| document.body.classList.add('desktop'); | |
| for(const f of frames){ | |
| if(bootSkipped) break; | |
| paint(f.lines); | |
| note('BOOT | '+f.phase+' | '+f.status+' (any key skips)'); | |
| await new Promise(r=>setTimeout(r, f.delay || 120)); | |
| } | |
| bootPlaying = false; | |
| await showOs(); | |
| return true; | |
| } | |
| async function osCall(path, body){ | |
| try{ | |
| const s = await api(path, body); | |
| if(s.frame){ paint(s.frame); } | |
| note('webOS | '+s.frame_ms+' ms/frame | '+s.status); | |
| return s; | |
| }catch(e){ note('os: '+e.message); return null; } | |
| } | |
| document.addEventListener('keydown', e=>{ | |
| if(tab!=='os') return; | |
| if(bootPlaying){ e.preventDefault(); bootSkipped=true; return; } // any key skips the boot | |
| if(e.target && e.target.tagName==='INPUT') return; // the exec box keeps its keys | |
| const named={'Tab':1,'Enter':1,'Backspace':1,'Escape':1,'ArrowUp':1,'ArrowDown':1, | |
| 'ArrowLeft':1,'ArrowRight':1,'F1':1,'F2':1,'F3':1,'F4':1,'F5':1,'F6':1}; | |
| if(e.ctrlKey && (e.key==='w'||e.key==='W')){ e.preventDefault(); osCall('/api/os/key',{key:'Ctrl+W'}); return; } | |
| if(e.ctrlKey && e.key==='Tab'){ e.preventDefault(); osCall('/api/os/key',{key:'Ctrl+Tab'}); return; } | |
| if(e.ctrlKey && ['ArrowLeft','ArrowRight','ArrowUp','ArrowDown'].includes(e.key)){ | |
| e.preventDefault(); osCall('/api/os/key',{key:'Ctrl+'+e.key.replace('Arrow','')}); return; | |
| } | |
| if(e.key===' '){ e.preventDefault(); osCall('/api/os/key',{key:' '}); return; } | |
| if(named[e.key]){ e.preventDefault(); osCall('/api/os/key',{key:e.key}); return; } | |
| if(e.key.length===1){ e.preventDefault(); osCall('/api/os/text',{text:e.key}); } | |
| }); | |
| // Click a cell: the desktop focuses whatever window is under it. #crt's own | |
| // rect already accounts for the panel padding and any scroll, so the pixel to | |
| // cell conversion is measured against it rather than against the panel. | |
| crt.addEventListener('click', e=>{ | |
| if(tab!=='os') return; | |
| if(bootPlaying){ bootSkipped=true; return; } | |
| const rect=crt.getBoundingClientRect(); | |
| const size=parseFloat(getComputedStyle(crt).fontSize)||13; | |
| probe.textContent='0'.repeat(100); | |
| const charW=probe.getBoundingClientRect().width/100; | |
| const cellH=size*1.3; | |
| const x=Math.floor((e.clientX-rect.left)/charW); | |
| const y=Math.floor((e.clientY-rect.top)/cellH); | |
| if(x>=0&&y>=0) osCall('/api/os/click',{x,y}); | |
| }); | |
| document.querySelectorAll('#tabs button').forEach(b=>{ b.onclick=()=>setTab(b.dataset.tab); }); | |
| // The idle/readahead strip: what the machine expects next, how often that has | |
| // been right, and whether the autonomous pass may pre-run it. The numbers are | |
| // the same ones `python -m hdc autonomy` prints, so the panel cannot claim | |
| // anything the layer does not. | |
| function autonomyBlock(a){ | |
| if(!a || !a.stats) return ''; | |
| const s=a.stats; | |
| const chips=(s.readahead||[]).map(p=> | |
| '<span class="'+(p.cached?'on':'')+'">'+esc(p.intent)+' '+ | |
| Number(p.confidence||0).toFixed(2)+'</span>').join('') | |
| || '<span class="small">no lookahead yet</span>'; | |
| return '<div class="msg"><b class="k">IDLE / READAHEAD</b>'+ | |
| '<div class="small">'+(s.enabled?'autonomous mode on':'readahead only')+ | |
| ' · graded '+s.graded+', correct '+s.hits+ | |
| ' ('+Math.round(100*(s.accuracy||0))+'%, chance '+s.chance+')'+ | |
| ' · ticks '+s.ticks+' · transitions '+s.transitions+ | |
| ' · pre-ran '+s.autonomous_actions+', served '+s.served_from_readahead+ | |
| (s.ms_saved?(' ('+s.ms_saved+' ms saved)'):'')+'</div>'+ | |
| '<div class="ahead">'+chips+'</div>'+ | |
| '<div><button type="button" onclick="toggleAutonomy()">'+ | |
| (s.enabled?'DISABLE':'ENABLE')+' AUTONOMY</button>'+ | |
| ' <span class="small">read-only commands only</span></div></div>'; | |
| } | |
| async function toggleAutonomy(){ | |
| try{ | |
| const a = await api('/api/autonomy'); | |
| await api('/api/autonomy',{enabled: !(a.stats||{}).enabled}); | |
| await showChat(); | |
| }catch(e){ note('error: '+e.message); } | |
| } | |
| async function showChat(){ | |
| let ahead=null; | |
| try{ ahead = await api('/api/autonomy'); }catch(e){ ahead=null; } | |
| try{ | |
| const h = await api('/api/chat/history'); | |
| view.innerHTML = autonomyBlock(ahead) + | |
| '<div class="small">TextRing encoder → prototype classifier → kernel command. '+ | |
| 'Confidence is a raw similarity, not a probability.</div>' + | |
| h.map(m=>`<div class="msg ${m.confidence<0.42?'miss':''}"> | |
| <b>${esc(m.input)}</b> | |
| <div class="small">intent=${esc(m.intent||'(none)')} conf=${m.confidence} ${m.executed?('→ '+esc(m.executed)):''}</div> | |
| <div>${esc(m.reply||'')}</div> | |
| ${m.output?`<pre class="small">${esc(String(m.output).slice(0,1400))}</pre>`:''} | |
| </div>`).join('') + | |
| '<form id="chatrow" onsubmit="ask(); return false;" style="display:flex;gap:8px;margin-top:8px">'+ | |
| '<input id="chat" placeholder="say: run the program" autocomplete="off">'+ | |
| '<button type="submit">SEND</button></form>'; | |
| document.getElementById('chat').focus(); | |
| }catch(e){ view.innerHTML='<div class="msg err">'+esc(e.message)+'</div>'; } | |
| } | |
| async function ask(){ | |
| const box=document.getElementById('chat'); const text=box.value.trim(); | |
| if(!text) return; | |
| box.value=''; | |
| view.innerHTML='<div class="small">thinking — encoding and classifying...</div>'; | |
| try{ | |
| const r = await api('/api/chat',{text}); | |
| lastScreen = r.screen; paint(lastScreen); note('ready'); | |
| }catch(e){ view.innerHTML='<div class="msg err">'+esc(e.message)+'</div>'; return; } | |
| await showChat(); | |
| } | |
| async function showPipeline(){ | |
| view.innerHTML='<div class="small">reading pipeline...</div>'; | |
| try{ | |
| const p = await api('/api/pipeline'); | |
| view.innerHTML = p.stages.map(s=>`<div class="msg"><b class="k">${esc(s.name)}</b> | |
| <div class="small">${esc(s.detail)}</div> | |
| <div class="small">${esc(s.metric)}</div></div>`).join(''); | |
| }catch(e){ view.innerHTML='<div class="msg err">'+esc(e.message)+'</div>'; } | |
| } | |
| async function showViz(){ | |
| view.innerHTML='<div class="small">listing visualisations...</div>'; | |
| try{ | |
| const names = await api('/api/viz'); | |
| view.innerHTML = names.list.map(n=>`<div class="msg"><b class="k">${esc(n)}</b> | |
| <div><button type="button" data-viz="${esc(n)}">render</button></div> | |
| <pre class="small" id="v-${esc(n)}"></pre></div>`).join(''); | |
| view.querySelectorAll('button[data-viz]').forEach(b=>{ | |
| b.onclick=()=>loadViz(b.dataset.viz, b); | |
| }); | |
| }catch(e){ view.innerHTML='<div class="msg err">'+esc(e.message)+'</div>'; } | |
| } | |
| async function loadViz(name, button){ | |
| const target=document.getElementById('v-'+name); | |
| if(target) target.textContent='rendering '+name+' ...'; | |
| if(button){ button.disabled=true; } | |
| try{ | |
| const v = await api('/api/viz/'+name); | |
| if(target) target.textContent = v.body; | |
| }catch(e){ if(target) target.textContent='[error] '+e.message; } | |
| finally{ if(button){ button.disabled=false; } } | |
| } | |
| async function showDiag(){ | |
| view.innerHTML='<div class="small">listing diagnostics...</div>'; | |
| try{ | |
| const d = await api('/api/diag'); | |
| view.innerHTML = d.sections.map(k=>`<div class="msg"><b class="k">${esc(k)}</b> | |
| <div><button type="button" data-diag="${esc(k)}">load</button></div> | |
| <pre class="small" id="d-${esc(k)}"></pre></div>`).join(''); | |
| view.querySelectorAll('button[data-diag]').forEach(b=>{ | |
| b.onclick=()=>loadDiag(b.dataset.diag, b); | |
| }); | |
| }catch(e){ view.innerHTML='<div class="msg err">'+esc(e.message)+'</div>'; } | |
| } | |
| async function loadDiag(name, button){ | |
| const target=document.getElementById('d-'+name); | |
| if(target) target.textContent='loading '+name+' ...'; | |
| if(button){ button.disabled=true; } | |
| try{ | |
| const d = await api('/api/diag/'+encodeURIComponent(name)); | |
| if(target) target.textContent = d.body + (d.cached?'\\n\\n[cached]':''); | |
| }catch(e){ if(target) target.textContent='[error] '+e.message; } | |
| finally{ if(button){ button.disabled=false; } } | |
| } | |
| (async function(){ | |
| fitCrt(); | |
| await refreshState(); | |
| fitCrt(); | |
| // #os opens straight onto the desktop, so the webOS can be linked to | |
| if((location.hash||'').replace('#','')==='os'){ setTab('os'); } | |
| else { await showChat(); } | |
| setInterval(async ()=>{ try{ const s=await api('/api/state'); note(s.status); }catch(e){} }, 20000); | |
| })(); | |
| </script></body></html> | |
| """ | |
| class App: | |
| """Kernel + assistant + boot state, shared by every request.""" | |
| def __init__(self, d: int = DEFAULT_DIM): | |
| self.d = d | |
| # Encoding is capped at 2000 bits regardless of the machine's d: the | |
| # assistant projects ~330 words at startup, and at d=10000 that alone | |
| # took 21 s before the first response. 2000 keeps it under a second and | |
| # is the codec's documented floor. | |
| self.enc_d = min(d, 2000) | |
| self.lock = threading.Lock() | |
| self.encoder = load_semantic(self.enc_d) | |
| self.kernel = Kernel(d=d) | |
| self.assistant = Assistant(self.kernel, d=self.enc_d, encoder=self.encoder) | |
| # idle + learning + readahead over the same assistant; off until asked | |
| self.autonomy = AutonomousMode(self.assistant, d=self.enc_d, enabled=False) | |
| # the webOS shares this kernel's storage, so the desktop's filesystem and | |
| # memory map describe the machine the terminal is running on | |
| self.desktop = Desktop(d=d, kernel=self.kernel, autonomy=self.autonomy) | |
| # POST -> shader splash -> AUTOEXEC, run once; the AUTOEXEC lines are the | |
| # ones that launch the windows, so the desktop handed over is the result | |
| self.boot_sequence = BootSequence(self.desktop, d=d) | |
| self.boot_frames = self.boot_sequence.run() | |
| self.boot = Boot() | |
| self.boot_log: list[str] = [] | |
| self._diag_cache: dict[str, str] = {} | |
| self._cold_boot() | |
| def _cold_boot(self) -> None: | |
| self.boot = Boot() | |
| self.boot.post() | |
| result = self.boot.boot() | |
| self.boot_log = [line for line in result["screen"] if line.strip()] | |
| self.kernel.boot(self.boot) | |
| self.boot_result = result | |
| # ---------------------------------------------------------------- views | |
| def state(self) -> dict: | |
| return { | |
| "boot": self.boot_log, | |
| "screen": self.kernel.term.screen(), | |
| "status": "kernel running | d=%d | cells=%d | pc=%d" % ( | |
| self.d, len(self.kernel.storage), self.kernel.machine.pc), | |
| "booted": self.kernel.booted, | |
| } | |
| def exec(self, line: str) -> dict: | |
| with self.lock: | |
| output = self.kernel.execute(line) | |
| return {"output": output, "screen": self.kernel.term.screen()} | |
| def chat(self, text: str) -> dict: | |
| with self.lock: | |
| # through the autonomy layer: every turn is graded against the | |
| # prediction made for it, and a readahead hit is served from cache | |
| record = self.autonomy.chat(text) | |
| return dict(record, screen=self.kernel.term.screen()) | |
| def chat_history(self) -> list[dict]: | |
| return self.assistant.history[-12:] | |
| def autonomy_state(self) -> dict: | |
| return {"stats": self.autonomy.stats(), "report": self.autonomy.report()} | |
| # ------------------------------------------------------------ the webOS | |
| def os_state(self) -> dict: | |
| with self.lock: | |
| return self.desktop.state() | |
| def os_key(self, key: str) -> dict: | |
| with self.lock: | |
| self.desktop.key(str(key)) | |
| return self.desktop.state() | |
| def os_text(self, text: str) -> dict: | |
| with self.lock: | |
| self.desktop.text(str(text)) | |
| return self.desktop.state() | |
| def os_click(self, x: int, y: int) -> dict: | |
| with self.lock: | |
| self.desktop.click(int(x), int(y)) | |
| return self.desktop.state() | |
| def os_launch(self, app: str) -> dict: | |
| with self.lock: | |
| self.desktop.launch(str(app)) | |
| return self.desktop.state() | |
| def boot_state(self) -> dict: | |
| return self.boot_frames | |
| def set_autonomy(self, enabled: bool) -> dict: | |
| with self.lock: | |
| self.autonomy.enabled = bool(enabled) | |
| if enabled: | |
| self.autonomy.idle(2) # two ticks: learn, then predict | |
| return self.autonomy_state() | |
| def pipeline(self) -> dict: | |
| kernel = self.kernel | |
| stages = [ | |
| ("1 BIOS / POST", "16-bit real mode, IVT installed, text buffer at B800", | |
| "stage=%s, %d instructions" % (self.boot_result["stage"], | |
| self.boot_result["instructions"])), | |
| ("2 Boot sector", "hand-assembled 512 bytes; int 10h teletype, int 21h handoff", | |
| "signature 55 AA verified, DS=07C0"), | |
| ("3 Kernel", "HDC interpreter took over at the handoff", | |
| "cells=%d pc=%d" % (len(kernel.storage), kernel.machine.pc)), | |
| ("4 Assembler", "text -> hypervector instruction image, verified by decoding back", | |
| "program=%s" % ("loaded" if kernel.program else "none")), | |
| ("5 Machine", "fetch from storage, decode by cleanup, execute", | |
| "steps=%d status=%s" % (kernel.machine.steps, kernel.machine.status)), | |
| ("6 Tools", "MCP tool records resolved by similarity", | |
| "%d registered" % len(kernel.tools.list_tools())), | |
| ("7 Shader", "coded-image operators: dim, edges, normals, depth", | |
| "fabric d=%d" % self.d), | |
| ("8 State", "predictive transitions, beam planner, surprise", | |
| "planner horizon 3"), | |
| ("9 Ring", "historic ledger, PAST->PRESENT->FUTURE, energy budget", | |
| "ledger capacity 16"), | |
| ("10 Text / semantic", "TextRing encoder, optional static embeddings", | |
| "encoder=%s" % ("semantic (potion-base-8M)" if self.encoder else "character n-gram")), | |
| ("11 Assistant", "prototype classification over encoded utterances", | |
| "%d intents, %d trained" % (len(INTENTS), self.assistant.trained)), | |
| ] | |
| return {"stages": [{"name": n, "detail": d_, "metric": m} for n, d_, m in stages]} | |
| DIAG = ("help", "isa", "stat", "net", "shader", "memory modes", | |
| "state", "ring", "text", "semantic", "topology", "hub", "autonomy") | |
| def diagnostics(self, name: str) -> dict: | |
| """One diagnostics section, cached. | |
| Computing all eleven at once took 9 s at d=10000 and the browser just | |
| sat there. One section at a time keeps every click responsive, and | |
| the cache makes a repeat click instant. | |
| """ | |
| if name in self._diag_cache: | |
| return {"name": name, "body": self._diag_cache[name], "cached": True} | |
| body = self._compute_diagnostic(name) | |
| self._diag_cache[name] = body | |
| return {"name": name, "body": body, "cached": False} | |
| def _compute_diagnostic(self, name: str) -> str: | |
| from .isa import opcode_table_text | |
| from .memory_modes import demo_report as memory_report | |
| from .net import selfcheck | |
| from .ring import report as ring_report | |
| from .semantic import report as semantic_report | |
| from .shader import demo_report as shader_report | |
| from .state import report as state_report | |
| from .terminal import HELP | |
| from .text import report as text_report | |
| from .topology import report as topology_report | |
| if name == "hub": | |
| # offline: the cache from `python -m hdc hub` is used, never the | |
| # network — a diagnostics click must not wait on the Hub | |
| from .hub import report as hub_report | |
| return hub_report(offline=True) | |
| if name == "autonomy": | |
| from .autonomy import report as autonomy_report | |
| return autonomy_report(d=self.enc_d) | |
| if name == "help": | |
| return HELP | |
| if name == "isa": | |
| return opcode_table_text() | |
| if name == "stat": | |
| return self.kernel.execute("stat") | |
| if name == "net": | |
| return "\n".join("%-22s %s" % (k, v) for k, v in selfcheck().items()) | |
| # the remaining reports run at the codec floor: they are measurements | |
| # of the algorithms, not of this machine's dimension | |
| if name == "shader": | |
| return shader_report(d=self.enc_d) | |
| if name == "memory modes": | |
| return memory_report(d=self.enc_d) | |
| if name == "state": | |
| return state_report(d=self.enc_d) | |
| if name == "ring": | |
| return ring_report(d=self.enc_d) | |
| if name == "text": | |
| return text_report(d=self.enc_d) | |
| if name == "semantic": | |
| return semantic_report(d=self.enc_d) | |
| if name == "topology": | |
| return topology_report(d=self.enc_d) | |
| raise KeyError(name) | |
| # ------------------------------------------------------------ visuals | |
| VIZ = ("shader2d", "shader3d", "shader4d", "fractal", "crystal", | |
| "manifold", "semantic", "ring", "state") | |
| def visualisation(self, name: str) -> dict: | |
| from .shader import ShaderFabric, step_image | |
| from .shader3d import Volume, cube_volume | |
| from .shader4d import sweep_volume | |
| d = self.d | |
| fabric = ShaderFabric(d=d) | |
| if name == "shader2d": | |
| image = step_image(fabric=fabric) | |
| body = "\n".join([ | |
| "source:", image.ascii(), | |
| "", "edges (exact |a-b|):", image.edges().ascii(), | |
| "", "dim(1) — exact halving:", image.dim(1).ascii(), | |
| "", "dilate(2) then bleed(2):", image.dilate(2).bleed(2).ascii(), | |
| ]) | |
| return {"title": "2D shader operators", "body": body} | |
| if name == "shader3d": | |
| volume = cube_volume(5, 180, fabric) | |
| return {"title": "3D volume: shaded+projected, curvature", | |
| "body": "shaded projection:\n" + volume.ascii_render() | |
| + "\n\nslice z=2:\n" + volume.ascii_slice(2) | |
| + "\n\nedges:\n" + volume.edges().ascii_slice(2)} | |
| if name == "shader4d": | |
| vol = sweep_volume(step=6, size=5, value=200, fabric=fabric) | |
| entropy = vol.entropy_along_u(8) | |
| return {"title": "4D entropic stack", | |
| "body": "slice u=0:\n" + vol.ascii_u(0) | |
| + "\n\nslice u=3:\n" + vol.ascii_u(3) | |
| + "\n\nentropy along u (z=2):\n" + entropy.ascii_slice(2) | |
| + "\n\ndrift %.4f" % vol.drift()} | |
| if name == "fractal": | |
| from .fractal import fbm, fern, sierpinski | |
| return {"title": "fractal: Sierpinski, fBm, fern", | |
| "body": "sierpinski(5):\n" + sierpinski(5, fabric=fabric).ascii() | |
| + "\n\nfBm 32x32:\n" + fbm(32, 32, 4, 8, 7, fabric).ascii() | |
| + "\n\nfern(3000):\n" + fern(3000, 34, 36, fabric).ascii()} | |
| if name == "crystal": | |
| from .crystal import grain_boundaries, lattice_shade, report | |
| volume = cube_volume(5, 180, fabric) | |
| return {"title": "crystal: grains + lattice banding", | |
| "body": report(volume) + "\n\nlattice banding z=2:\n" | |
| + lattice_shade(volume, spacing=2).ascii_slice(2)} | |
| if name == "manifold": | |
| from .manifold import curvature, geodesic_field, report | |
| rows = [[[180 if (x in (0, 4) or y in (0, 4) or z in (0, 4)) else 0 | |
| for x in range(5)] for y in range(5)] for z in range(5)] | |
| volume = Volume.from_levels(rows, fabric) | |
| return {"title": "manifold: geodesic + curvature", | |
| "body": report(volume, seed=(0, 0, 0)) | |
| + "\n\ngeodesic from (0,0,0) at z=0:\n" | |
| + geodesic_field(volume, (0, 0, 0)).ascii_slice(0)} | |
| if name == "semantic": | |
| encoder = self.encoder | |
| if encoder is None: | |
| return {"title": "semantic", "body": "model not downloaded — see hdc/semantic.py"} | |
| words = ["car", "automobile", "doctor", "physician", "banana", "tectonics"] | |
| header = "%-11s" % "" + " ".join("%-10s" % w[:10] for w in words) | |
| rows = [header] | |
| for a in words: | |
| cells = ["%+.3f" % encoder.similarity(a, b) for b in words] | |
| rows.append("%-11s" % a[:11] + " ".join("%-10s" % c for c in cells)) | |
| return {"title": "semantic similarity (model cosine)", | |
| "body": "\n".join(rows)} | |
| if name == "ring": | |
| from .ring import report | |
| return {"title": "ring controller", "body": report(d=d)} | |
| if name == "state": | |
| from .state import report | |
| return {"title": "predictive state", "body": report(d=d)} | |
| raise KeyError(name) | |
| def make_handler(app: App): | |
| class Handler(BaseHTTPRequestHandler): | |
| server_version = "HDCSTORAGE/1.0" | |
| def log_message(self, fmt, *args): # quiet: this is a local panel | |
| pass | |
| def _send(self, payload: bytes, content_type: str, code: int = 200) -> None: | |
| self.send_response(code) | |
| self.send_header("Content-Type", content_type) | |
| self.send_header("Content-Length", str(len(payload))) | |
| self.send_header("Cache-Control", "no-store") | |
| self.end_headers() | |
| self.wfile.write(payload) | |
| def _json(self, data, code: int = 200) -> None: | |
| self._send(json.dumps(data).encode("utf-8"), "application/json", code) | |
| def _body(self) -> dict: | |
| length = int(self.headers.get("Content-Length") or 0) | |
| if not length: | |
| return {} | |
| try: | |
| return json.loads(self.rfile.read(length).decode("utf-8")) | |
| except json.JSONDecodeError: | |
| return {} | |
| def do_GET(self) -> None: # noqa: N802 | |
| path = self.path.split("?")[0] | |
| if path in ("/", "/index.html"): | |
| self._send(PAGE.encode("utf-8"), "text/html; charset=utf-8") | |
| elif path == "/api/state": | |
| self._json(app.state()) | |
| elif path == "/api/chat/history": | |
| self._json(app.chat_history()) | |
| elif path == "/api/autonomy": | |
| self._json(app.autonomy_state()) | |
| elif path == "/api/os": | |
| self._json(app.os_state()) | |
| elif path == "/api/boot": | |
| self._json(app.boot_state()) | |
| elif path == "/api/diag": | |
| self._json({"sections": list(App.DIAG)}) | |
| elif path.startswith("/api/diag/"): | |
| try: | |
| self._json(app.diagnostics(path.rsplit("/", 1)[1])) | |
| except KeyError: | |
| self._json({"error": "unknown section"}, 404) | |
| elif path == "/api/pipeline": | |
| self._json(app.pipeline()) | |
| elif path == "/api/viz": | |
| self._json({"list": list(App.VIZ)}) | |
| elif path.startswith("/api/viz/"): | |
| try: | |
| self._json(app.visualisation(path.rsplit("/", 1)[1])) | |
| except KeyError: | |
| self._json({"error": "unknown visualisation"}, 404) | |
| else: | |
| self._json({"error": "not found"}, 404) | |
| def do_POST(self) -> None: # noqa: N802 | |
| path = self.path.split("?")[0] | |
| body = self._body() | |
| if path == "/api/exec": | |
| self._json(app.exec(str(body.get("line", "")))) | |
| elif path == "/api/chat": | |
| self._json(app.chat(str(body.get("text", "")))) | |
| elif path == "/api/autonomy": | |
| self._json(app.set_autonomy(bool(body.get("enabled")))) | |
| elif path == "/api/os/key": | |
| self._json(app.os_key(str(body.get("key", "")))) | |
| elif path == "/api/os/text": | |
| self._json(app.os_text(str(body.get("text", "")))) | |
| elif path == "/api/os/click": | |
| self._json(app.os_click(int(body.get("x", 0)), int(body.get("y", 0)))) | |
| elif path == "/api/os/launch": | |
| self._json(app.os_launch(str(body.get("app", "terminal")))) | |
| elif path == "/api/boot": | |
| with app.lock: | |
| app._cold_boot() | |
| self._json(app.state()) | |
| else: | |
| self._json({"error": "not found"}, 404) | |
| return Handler | |
| def main(argv: list[str] | None = None) -> int: | |
| """``python -m hdc web [--port 8765] [--d 2000] [--open]``.""" | |
| import sys | |
| import webbrowser | |
| argv = list(sys.argv[1:] if argv is None else argv) | |
| port = 8765 | |
| host = "127.0.0.1" | |
| # 2000 is the codec floor and keeps every endpoint sub-second; the CLI's | |
| # 10 000-bit default made assembling a demo take seconds in the UI | |
| d = 2000 | |
| open_browser = False | |
| i = 0 | |
| while i < len(argv): | |
| if argv[i] == "--port" and i + 1 < len(argv): | |
| port = int(argv[i + 1]); i += 2 | |
| elif argv[i] == "--d" and i + 1 < len(argv): | |
| d = int(argv[i + 1]); i += 2 | |
| elif argv[i] == "--host" and i + 1 < len(argv): | |
| host = argv[i + 1]; i += 2 | |
| elif argv[i] == "--open": | |
| open_browser = True; i += 1 | |
| else: | |
| i += 1 | |
| app = App(d=d) | |
| server = ThreadingHTTPServer((host, port), make_handler(app)) | |
| url = "http://%s:%d/" % (host, port) | |
| print("HDCSTORAGE WebUI on %s (d=%d, encoder=%s)" | |
| % (url, d, "semantic" if app.encoder else "n-gram")) | |
| print(" ctrl-c to stop") | |
| if open_browser: | |
| webbrowser.open(url) | |
| try: | |
| server.serve_forever() | |
| except KeyboardInterrupt: | |
| print("\nstopped") | |
| finally: | |
| server.server_close() | |
| return 0 | |
| if __name__ == "__main__": # pragma: no cover - manual run | |
| raise SystemExit(main()) | |