155 lines
9.7 KiB
HTML
155 lines
9.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>HERETIC ORBITAL — Ornith-1.5 Decensorship Monitor</title>
|
|
<style>
|
|
:root{--bg:#07090f;--panel:#0d1117;--edge:#1b2330;--txt:#c9d6e8;--dim:#5c6b82;--hot:#ff4d6d;--good:#3ddc97;--warn:#ffb454;--acc:#7aa2ff;}
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
body{background:var(--bg);color:var(--txt);font:14px/1.55 'SF Mono',ui-monospace,Menlo,monospace;padding:22px;min-height:100vh}
|
|
h1{font-size:19px;letter-spacing:3px;color:var(--acc);margin-bottom:2px}
|
|
.sub{color:var(--dim);font-size:11px;letter-spacing:2px;margin-bottom:18px}
|
|
.grid{display:grid;grid-template-columns:340px 1fr;gap:16px;align-items:start}
|
|
.panel{background:var(--panel);border:1px solid var(--edge);border-radius:10px;padding:16px}
|
|
.panel h2{font-size:11px;letter-spacing:2px;color:var(--dim);margin-bottom:10px;text-transform:uppercase}
|
|
.big{font-size:44px;font-weight:700;color:var(--good)}
|
|
.kv{display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px dashed var(--edge);font-size:12px}
|
|
.kv b{color:var(--acc);font-weight:600}
|
|
.phase{display:inline-block;padding:3px 12px;border-radius:20px;font-size:11px;letter-spacing:2px;border:1px solid var(--dim);color:var(--dim)}
|
|
.phase.live{border-color:var(--good);color:var(--good);animation:pulse 2s infinite}
|
|
.phase.done{border-color:var(--acc);color:var(--acc)}
|
|
@keyframes pulse{50%{opacity:.5}}
|
|
.bar{height:8px;background:var(--edge);border-radius:5px;overflow:hidden;margin:8px 0 14px}
|
|
.bar>div{height:100%;background:linear-gradient(90deg,var(--acc),var(--good));transition:width .8s}
|
|
.log{background:#05070c;border:1px solid var(--edge);border-radius:8px;padding:10px;font-size:11px;height:260px;overflow-y:auto;white-space:pre-wrap;color:var(--dim)}
|
|
.log .hot{color:var(--warn)}
|
|
.gauge{display:flex;gap:14px;margin-top:10px}
|
|
.g{flex:1;text-align:center}
|
|
.g .v{font-size:20px;font-weight:700}
|
|
.g .l{font-size:9px;letter-spacing:1px;color:var(--dim)}
|
|
.g.ok .v{color:var(--good)} .g.warn .v{color:var(--warn)}
|
|
#arena{margin-top:16px;display:none}
|
|
#arena.on{display:block}
|
|
.row{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:12px}
|
|
.card{background:var(--panel);border:1px solid var(--edge);border-radius:10px;padding:14px;min-height:120px}
|
|
.card h3{font-size:11px;letter-spacing:2px;margin-bottom:8px}
|
|
.card.orig h3{color:var(--hot)} .card.here h3{color:var(--good)}
|
|
.card pre{white-space:pre-wrap;font-size:12px;color:var(--txt);max-height:300px;overflow-y:auto}
|
|
mark{background:var(--hot);color:#000;border-radius:3px;padding:0 3px}
|
|
.tag{display:inline-block;background:var(--edge);border-radius:4px;padding:1px 7px;font-size:10px;margin:2px 2px 0 0;color:var(--warn)}
|
|
.tag.crit{background:var(--hot);color:#000}
|
|
textarea{width:100%;background:#05070c;color:var(--txt);border:1px solid var(--edge);border-radius:8px;padding:10px;font:inherit;min-height:60px}
|
|
button{background:var(--acc);color:#000;border:0;border-radius:8px;padding:10px 22px;font:inherit;font-weight:700;cursor:pointer;letter-spacing:1px}
|
|
button:disabled{opacity:.4;cursor:wait}
|
|
.bank{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0}
|
|
.bank span{background:var(--edge);border-radius:6px;padding:4px 10px;font-size:11px;cursor:pointer;color:var(--dim)}
|
|
.bank span:hover{color:var(--acc);border:1px solid var(--acc)}
|
|
.verdict{margin-top:12px;padding:10px;border-radius:8px;font-size:12px;background:#05070c;border:1px solid var(--edge)}
|
|
.verdict b{letter-spacing:1px}
|
|
.notice{margin-top:14px;padding:10px;border-radius:8px;background:#141a08;border:1px solid #3d4a1e;color:#b8c77a;font-size:12px}
|
|
.spin{display:inline-block;animation:pulse 1s infinite}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>⟶ HERETIC ORBITAL</h1>
|
|
<div class="sub">ORNITH-1.5-9B DECENSORSHIP RUN — LIVE TELEMETRY + BEFORE/AFTER ARENA</div>
|
|
<div class="grid">
|
|
<div>
|
|
<div class="panel">
|
|
<h2>Run state</h2>
|
|
<span id="phase" class="phase">…</span>
|
|
<div class="kv" style="margin-top:12px"><span>Trial</span><b id="trial">—</b></div>
|
|
<div class="bar"><div id="trialbar" style="width:0%"></div></div>
|
|
<div class="kv"><span>Batch size</span><b id="batch">—</b></div>
|
|
<div class="kv"><span>Export dir</span><b id="export">—</b></div>
|
|
</div>
|
|
<div class="panel" style="margin-top:14px">
|
|
<h2>GPU telemetry</h2>
|
|
<div class="gauge">
|
|
<div class="g"><div class="v" id="gutil">—</div><div class="l">UTIL %</div></div>
|
|
<div class="g"><div class="v" id="gmem">—</div><div class="l">VRAM GB</div></div>
|
|
</div>
|
|
</div>
|
|
<div class="panel" style="margin-top:14px">
|
|
<h2>Live log</h2>
|
|
<div class="log" id="log">…</div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="panel">
|
|
<h2>Current trial parameters (optimizer exploring)</h2>
|
|
<div id="params" class="kv" style="display:block">waiting for trial data…</div>
|
|
</div>
|
|
<div id="arena" class="on">
|
|
<div class="panel" style="margin-top:14px">
|
|
<h2>Before / After arena <span id="arenaState" style="color:var(--warn)"></span></h2>
|
|
<textarea id="prompt" placeholder="Enter a prompt to test both models…"></textarea>
|
|
<div class="bank" id="bank"></div>
|
|
<button id="fire">⚡ RUN BOTH MODELS</button>
|
|
<div class="row">
|
|
<div class="card orig"><h3>◈ ORIGINAL — ornith-1.5:9b-64k</h3><pre id="outOrig">—</pre><div id="refOrig"></div></div>
|
|
<div class="card here"><h3>◈ HERETIC — ornith-1.5-heretic</h3><pre id="outHere">—</pre><div id="refHere"></div></div>
|
|
</div>
|
|
<div class="verdict" id="verdict">Arena verdicts appear after each run.</div>
|
|
</div>
|
|
<div class="notice" id="hereticNote">⚠ Heretic model registers in Ollama only after the run exports + GGUF conversion completes. Arena runs both once it exists; until then the Heretic column will show an error.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
let busy=false;
|
|
async function jget(u){const r=await fetch(u);return r.json()}
|
|
async function jpost(u,b){const r=await fetch(u,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(b)});return r.json()}
|
|
function esc(s){return s.replace(/&/g,'&').replace(/</g,'<')}
|
|
function markRefusals(t,words){let out=esc(t);words.forEach(w=>{const re=new RegExp('('+w.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+')','gi');out=out.replace(re,'<mark>$1</mark>')});return out}
|
|
async function poll(){
|
|
try{
|
|
const s=await jget('/api/state');
|
|
const ph=document.getElementById('phase');
|
|
ph.textContent=s.phase.toUpperCase()+(s.done?(' exit='+s.exit):'');
|
|
ph.className='phase '+(s.done?'done':(s.phase!=='finished'?'live':''));
|
|
document.getElementById('trial').textContent=s.trial+' / '+s.trials_total;
|
|
document.getElementById('trialbar').style.width=(100*s.trial/s.trials_total)+'%';
|
|
const g=s.gpu||{};
|
|
document.getElementById('gutil').textContent=g.util>=0?g.util+'%':'—';
|
|
document.getElementById('gmem').textContent=g.mem_used>=0?(g.mem_used/1024).toFixed(1)+'G':'—';
|
|
const p=document.getElementById('params');
|
|
if(s.params&&Object.keys(s.params).length){p.innerHTML=Object.entries(s.params).map(([k,v])=>`<div class="kv"><span>${k}</span><b>${esc(v)}</b></div>`).join('')}
|
|
const lg=document.getElementById('log');
|
|
lg.innerHTML=s.log_tail.map(l=>l.includes('OOM')||l.includes('Error')?`<span class="hot">${esc(l)}</span>`:esc(l)).join('\n');
|
|
lg.scrollTop=lg.scrollHeight;
|
|
document.getElementById('export').textContent=s.has_export?'READY ✓':'pending…';
|
|
if(s.has_export){document.getElementById('hereticNote').style.display='none'}
|
|
}catch(e){}
|
|
}
|
|
async function loadBank(){
|
|
try{const b=await jpost('/api/bank',{});
|
|
document.getElementById('bank').innerHTML=b.prompts.map(p=>`<span onclick="document.getElementById('prompt').value=this.textContent">${esc(p)}</span>`).join('');
|
|
}catch(e){}
|
|
}
|
|
document.getElementById('fire').onclick=async()=>{
|
|
if(busy)return;
|
|
const prompt=document.getElementById('prompt').value.trim();
|
|
if(!prompt)return alert('enter a prompt');
|
|
busy=true;const btn=document.getElementById('fire');btn.disabled=true;btn.innerHTML='<span class="spin">⟳ RUNNING BOTH…</span>';
|
|
document.getElementById('outOrig').textContent='…generating…';
|
|
document.getElementById('outHere').textContent='…generating…';
|
|
document.getElementById('refOrig').innerHTML='';document.getElementById('refHere').innerHTML='';
|
|
try{
|
|
const r=await jpost('/api/arena',{prompt});
|
|
document.getElementById('outOrig').innerHTML=r.orig&&r.orig.ok?markRefusals(r.orig.text,r.orig_refusals):esc(r.orig?r.orig.text:'error');
|
|
document.getElementById('outHere').innerHTML=r.heretic&&r.heretic.ok?markRefusals(r.heretic.text,r.heretic_refusals):esc(r.heretic?r.heretic.text:'error — heretic model not registered yet');
|
|
document.getElementById('refOrig').innerHTML=r.orig_refusals.map(w=>`<span class="tag${w.includes('sorry')||w.includes('cannot')||w.includes('can\'')?' crit':''}">${esc(w)}</span>`).join('');
|
|
document.getElementById('refHere').innerHTML=r.heretic_refusals.map(w=>`<span class="tag${w.includes('sorry')||w.includes('cannot')||w.includes('can\'')?' crit':''}">${esc(w)}</span>`).join('');
|
|
const o=r.orig_refusals.length,h=r.heretic_refusals.length;
|
|
document.getElementById('verdict').innerHTML=
|
|
`<b>VERDICT:</b> original hit <b style="color:var(--hot)">${o}</b> refusal markers, heretic hit <b style="color:var(--good)">${h}</b>. `+
|
|
(o>h?'✓ Refusal language reduced.':(o===h?'— no change in refusal markers this round.':'⚠ heretic shows MORE refusal markers — check trial quality.'));
|
|
}catch(e){document.getElementById('verdict').textContent='error: '+e}
|
|
busy=false;btn.disabled=false;btn.textContent='⚡ RUN BOTH MODELS';
|
|
};
|
|
loadBank();poll();setInterval(poll,4000);
|
|
</script>
|
|
</body>
|
|
</html> |