andreiski's picture
Upload app.py with huggingface_hub
12ad1c7 verified
Raw
History Blame Contribute Delete
35.9 kB
"""
Dialectical Intuitions — judge intuitions one at a time, Qwen3-8B writes and
rewrites an answer that expresses the held ones without restating them.
Frontend is a single custom HTML/JS interface (gr.HTML + head script). Each
judgment posts the full commitment state to /gradio_api/call/generate; the
prompt shows the model its old commitments, the new or changed one, and its
previous answer, and asks for a minimal revision.
"""
import spaces # MUST be imported before anything initializes CUDA
import json
import os
import re
from threading import Thread
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
from huggingface_hub import snapshot_download
import gradio as gr
MODEL_ID = "Qwen/Qwen3-8B"
HF_TOKEN = os.environ.get("HF_TOKEN")
print("warming weight cache ...")
snapshot_download(MODEL_ID, token=HF_TOKEN)
TOK = AutoTokenizer.from_pretrained(MODEL_ID, token=HF_TOKEN)
if TOK.pad_token is None:
TOK.pad_token = TOK.eos_token
MODEL = None
def _ensure_model():
"""Load lazily INSIDE the GPU context (ZeroGPU has no GPU at import)."""
global MODEL
if MODEL is None:
m = AutoModelForCausalLM.from_pretrained(
MODEL_ID, torch_dtype=torch.bfloat16, token=HF_TOKEN).to("cuda")
m.eval()
MODEL = m
return MODEL
# ------------------------------------------------------------------ prompt
STANCE = {1: "held", 0: "undecided", -1: "rejected"}
def build_prompt(p):
kind = p.get("kind", "question")
items = p.get("items", [])
ev = p.get("event") or {}
prev = (p.get("prev") or "").strip()
hold = [f"- {it['s']} {it['t']}." for it in items if it["j"] == 1]
rej = [f"- {it['s']} {it['t']}." for it in items if it["j"] == -1]
und = [f"- {it['s']} {it['t']}." for it in items if it["j"] == 0]
L = []
if kind == "task":
L.append(f"A user asks you: {p['q']}")
else:
L.append(f"Question: {p['q']}")
if hold:
L += ["", "Convictions you hold:"] + hold
if rej:
L += ["", "Descriptions you DENY — you consider each of these false:"] + rej
if und:
L += ["", "You are genuinely undecided about:"] + und
if prev and ev.get("type") == "add":
L += ["", f"Your previous answer:\n{prev}", "",
f"You just formed one NEW stance ({STANCE.get(ev.get('stance'), 'held')}): "
f"{ev.get('text', '')}",
"Revise your previous answer so it also expresses what lies behind this, "
"changing only what must change."]
elif prev and ev.get("type") == "change":
L += ["", f"Your previous answer:\n{prev}", "",
f"You CHANGED YOUR MIND about one stance: {ev.get('text', '')} "
f"It was {STANCE.get(ev.get('old'), 'held')}; it is now "
f"{STANCE.get(ev.get('stance'), 'held')}.",
"Revise your previous answer accordingly, changing only what must change."]
elif prev:
L += ["", f"Your previous answer:\n{prev}", "",
"Some of your stances have changed since you wrote that answer. Revise it "
"so it expresses the stances above as they now stand, changing only what "
"must change."]
if kind == "task":
L += ["", "Write the email itself, about 110 words, and nothing else. The stances "
"above are your convictions about how such an email should and should not "
"be written; the email must embody the held ones and must not do what you "
"reject. Never mention the stances. Think briefly."]
else:
L += ["", "Write your answer, about 110 words. Express the outlook that lies "
"behind your convictions as one coherent, committed view. The answer must "
"be CONSISTENT with every conviction, but it does not need to mention any "
"of them explicitly — never restate, quote, or list them; a reader should "
"be able to guess them from the view alone. Never assert anything you "
"deny: you believe those descriptions are wrong, and where it matters "
"your view should quietly imply the opposite. Stay unsettled only on the "
"undecided points; on everything else commit — no both-sides closing, no "
"calls for further dialogue. Think briefly. Output the answer only."]
return "\n".join(L)
def _split(gen, done):
"""Partition streamed text into thinking / answer phases."""
if "</think>" in gen:
head, _, tail = gen.partition("</think>")
return {"phase": "done" if done else "answer",
"think": head.replace("<think>", "").strip(),
"answer": tail.strip()}
body = gen.replace("<think>", "").strip()
if done:
# never closed the think block: treat the text as the answer if there
# was no <think> at all, otherwise surface the truncated thinking
if "<think>" in gen:
return {"phase": "done", "think": body, "answer": ""}
return {"phase": "done", "think": "", "answer": body}
return {"phase": "thinking", "think": body, "answer": ""}
@spaces.GPU(duration=60)
def _gpu_generate(payload):
p = json.loads(payload)
model = _ensure_model()
msgs = [{"role": "user", "content": build_prompt(p)}]
text = TOK.apply_chat_template(
msgs, tokenize=False, add_generation_prompt=True, enable_thinking=True)
ids = TOK(text, return_tensors="pt").to("cuda")
streamer = TextIteratorStreamer(TOK, skip_prompt=True, skip_special_tokens=True)
th = Thread(target=model.generate, kwargs=dict(
**ids, max_new_tokens=768, do_sample=True,
temperature=0.6, top_p=0.95, top_k=20,
pad_token_id=TOK.pad_token_id, streamer=streamer))
th.start()
acc, n = "", 0
for tok in streamer:
acc += tok
n += 1
if n % 6 == 0:
yield json.dumps(_split(acc, False))
th.join()
yield json.dumps(_split(acc, True))
from collections import OrderedDict
STATE = OrderedDict()
def generate(payload):
"""Plain generator gradio can introspect; the GPU work happens inside.
Each chunk is mirrored into STATE (web process) so the peek side-channel
can serve live progress even when DOM streaming does not."""
try:
jid = json.loads(payload).get("jid") or ""
except Exception:
jid = ""
for chunk in _gpu_generate(payload):
if jid:
try:
c = json.loads(chunk)
c["jid"] = jid
chunk = json.dumps(c)
except Exception:
pass
STATE[jid] = chunk
while len(STATE) > 40:
STATE.popitem(last=False)
yield chunk
def peek(jid):
return STATE.get(jid, "")
@spaces.GPU(duration=30)
def _gpu_score(payload):
p = json.loads(payload)
items = p.get("items", [])
lines = "\n".join(f"{i+1}. [{STANCE.get(it['j'], 'held')}] {it['s']} {it['t']}."
for i, it in enumerate(items))
prompt = (f"An answer to the question \"{p.get('q','')}\" is below.\n\n"
f"ANSWER:\n{p.get('answer','')}\n\n"
"Rate each numbered stance from 1 to 5 for how well the answer satisfies it.\n"
"- held: 5 = the answer fully expresses the outlook behind it, 1 = it ignores or cuts against it.\n"
"- rejected: 5 = the answer avoids it entirely or implies its opposite, 1 = the answer asserts it.\n"
"- undecided: 5 = the answer takes a clear position on it, 1 = it is silent.\n\n"
f"{lines}\n\nReply with ONE line only:\nRATINGS: 1:<n> 2:<n> ...")
model = _ensure_model()
msgs = [{"role": "user", "content": prompt}]
text = TOK.apply_chat_template(
msgs, tokenize=False, add_generation_prompt=True, enable_thinking=False)
ids = TOK(text, return_tensors="pt").to("cuda")
with torch.no_grad():
out = model.generate(**ids, max_new_tokens=16 + 6 * len(items),
do_sample=False, pad_token_id=TOK.pad_token_id)
gen = TOK.decode(out[0][ids["input_ids"].shape[1]:], skip_special_tokens=True)
ratings = {}
for m in re.finditer(r"(\d+)\s*[:=]\s*([1-5])", gen):
ratings[m.group(1)] = int(m.group(2))
return json.dumps({"ratings": ratings, "jid": p.get("jid", "")})
def score(payload):
return _gpu_score(payload)
# ------------------------------------------------------------------ frontend
UI_HTML = """
<div id="ivx">
<nav class="tabs">
<button id="t0" class="tab on">a question</button>
<button id="t1" class="tab">a question pt 2</button>
<button id="t2" class="tab">a task</button>
<span class="ver">v22</span>
</nav>
<p class="q" id="q"></p>
<div class="cols">
<aside class="rail"><ul id="held"></ul></aside>
<main class="work">
<div id="deck">
<div class="card">
<p class="stmt"><span id="sit" spellcheck="false"></span><span> </span><span id="thick" spellcheck="false"></span><span class="fin">.</span></p>
<div id="pop" hidden>
<span class="plabel">or call it</span>
<button class="popt" id="p0"></button>
<button class="popt" id="p1"></button>
<button class="popt ped" id="pe">say it your way</button>
</div>
</div>
<div class="acts">
<button id="down" class="act dn">disagree</button>
<button id="pass" class="act">pass</button>
<button id="up" class="act up">agree</button>
</div>
</div>
<div id="done" hidden><button id="reset" class="act">start over</button></div>
<button id="gen" class="genbtn" hidden>write the answer</button>
<div id="out" hidden>
<div class="ansrule"></div>
<span id="status" class="status" hidden></span>
<details id="thinkbox" hidden><summary>thinking</summary><div id="think"></div></details>
<p class="ans" id="ans"></p>
</div>
</main>
</div>
</div>
"""
UI_CSS = """
#ivx{--pg:#fbfbfd;--card:#ffffff;--ink:#232329;--dim:#8f8f99;--line:#e9e9ef;
--up:#5f977c;--upbg:#e9f2ec;--dn:#bd7c89;--dnbg:#f6eaed;
--lav:#7c79b8;--lavbg:#efeef8;--amb:#c9a221;--shadow:0 2px 6px rgba(35,35,50,.05),0 14px 36px rgba(124,121,184,.12);
font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif;
font-size:14.5px;line-height:1.65;color:var(--ink);background:var(--pg);
max-width:960px;margin:0 auto;padding:40px 24px 80px;border-radius:12px}
@media(prefers-color-scheme:dark){#ivx{--pg:#131316;--card:#1a1a1f;--ink:#e7e7ec;--dim:#84848e;
--line:#27272e;--up:#8bc3a6;--upbg:#1a2420;--dn:#d4a1ac;--dnbg:#241b1e;
--lav:#a3a0d6;--lavbg:#232238;--amb:#d9b86a;--shadow:0 2px 6px rgba(0,0,0,.35),0 14px 36px rgba(0,0,0,.35)}}
#ivx *{box-sizing:border-box}
#ivx .tabs{display:flex;gap:20px;margin-bottom:32px}
#ivx .tab{font:inherit;font-size:12px;letter-spacing:.07em;background:none;border:none;
color:var(--dim);cursor:pointer;padding:0 0 4px;border-bottom:1.5px solid transparent}
#ivx .tab.on{color:var(--ink);border-bottom-color:var(--lav)}
#ivx .ver{margin-left:auto;font-size:10px;color:var(--dim);letter-spacing:.08em;align-self:center}
#ivx .q{margin:0 0 32px;color:var(--dim);max-width:66ch}
#ivx .cols{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:44px;align-items:start}
@media(max-width:720px){#ivx .cols{grid-template-columns:1fr;gap:28px}}
#ivx .rail ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}
#ivx .rail li{font-size:12.5px;line-height:1.55;padding:10px 13px;border-radius:7px;display:block}
#ivx .rrow{display:flex;gap:8px;align-items:flex-start;justify-content:space-between}
#ivx .bar{position:relative;height:3px;border-radius:2px;margin-top:8px}
#ivx .bar::before{content:"";position:absolute;left:0;right:0;top:0;bottom:0;
background:currentColor;opacity:.16;border-radius:2px}
#ivx .bar i{position:absolute;left:0;top:0;bottom:0;
border-radius:2px;transition:width .6s ease,background .6s ease}
#ivx .rail li b{font-weight:600}
#ivx .re{cursor:text;border-radius:3px}
#ivx .re:hover{background:rgba(124,121,184,.12)}
#ivx .re[contenteditable="true"]{background:rgba(124,121,184,.16);outline:none}
#ivx .rail li.y{background:var(--upbg);color:var(--up)}
#ivx .rail li.n{background:var(--dnbg);color:var(--dn)}
#ivx .rail li.p{background:none;border:1px dashed var(--line);color:var(--dim)}
#ivx .rail li.p b{font-weight:500}
#ivx .rev{display:flex;gap:3px;flex:none;margin-top:1px}
#ivx .rv{font:inherit;font-size:10.5px;font-weight:700;line-height:17px;width:18px;height:18px;
border-radius:50%;border:none;background:none;cursor:pointer;opacity:.35;padding:0;text-align:center}
#ivx .rv:hover{opacity:.8}
#ivx .rv-y{color:var(--up)}
#ivx .rv-p{color:var(--dim)}
#ivx .rv-n{color:var(--dn)}
#ivx .rv.cur{opacity:1;color:#fff}
#ivx .rv-y.cur{background:var(--up)}
#ivx .rv-p.cur{background:var(--dim)}
#ivx .rv-n.cur{background:var(--dn)}
#ivx .status{display:inline-block;font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
color:var(--lav);background:var(--lavbg);border-radius:999px;padding:4px 12px;margin-bottom:14px}
#ivx .status[hidden]{display:none}
#ivx .status::after{content:"";display:inline-block;width:1.1em;text-align:left;
animation:ivxdots 1.2s steps(4,end) infinite}
@keyframes ivxdots{0%{content:""}25%{content:"."}50%{content:".."}75%{content:"..."}}
#ivx .status.write{color:var(--up);background:var(--upbg)}
#ivx .card{position:relative;background:var(--card);border:1px solid var(--line);border-radius:14px;
padding:28px 30px 26px;box-shadow:var(--shadow);margin-bottom:20px}
#ivx .stmt{font-size:17.5px;line-height:1.75;margin:0;min-height:96px}
#ivx #sit{cursor:text;border-radius:4px;padding:.05em .1em;margin:-.05em -.1em;transition:background .15s}
#ivx #sit:hover{background:var(--lavbg)}
#ivx #sit[contenteditable="true"]{background:var(--lavbg);outline:none}
#ivx #thick{font-weight:600;color:var(--lav);background:var(--lavbg);border-radius:5px;
padding:.08em .4em;cursor:pointer;transition:filter .15s}
#ivx #thick:hover,#ivx #thick.open{filter:brightness(.96)}
#ivx #thick::after{content:"\\2304";font-size:.72em;opacity:.55;margin-left:.4em;font-weight:400}
#ivx #thick[contenteditable="true"]{cursor:text;outline:none}
#ivx #thick[contenteditable="true"]::after{content:""}
#ivx #pop{position:absolute;left:30px;right:30px;top:calc(100% - 12px);z-index:3;
background:var(--card);border:1px solid var(--line);border-radius:11px;
box-shadow:var(--shadow);padding:9px}
#ivx .plabel{display:block;font-size:10px;letter-spacing:.13em;text-transform:uppercase;
color:var(--dim);padding:4px 10px 7px}
#ivx .popt{display:block;width:100%;text-align:left;font:inherit;font-size:13.5px;line-height:1.5;
background:none;border:none;border-radius:7px;padding:8px 10px;color:var(--ink);cursor:pointer}
#ivx .popt:hover{background:var(--lavbg);color:var(--lav)}
#ivx .popt.ped{color:var(--dim);font-size:12px}
#ivx .acts{display:flex;gap:10px}
#ivx .genbtn{font:inherit;font-size:13px;font-weight:600;letter-spacing:.02em;
margin-top:18px;padding:11px 22px;border:none;border-radius:999px;cursor:pointer;
background:var(--lav);color:#fff;transition:filter .15s}
#ivx .genbtn:hover{filter:brightness(1.08)}
#ivx .genbtn:disabled{background:var(--lavbg);color:var(--dim);cursor:default}
#ivx .genbtn[hidden]{display:none}
#ivx .act{font:inherit;font-size:12.5px;padding:9px 18px;background:none;border:1px solid var(--line);
border-radius:8px;color:var(--dim);cursor:pointer;transition:all .15s}
#ivx .act:hover{color:var(--ink);border-color:var(--dim)}
#ivx .act.up:hover{color:var(--up);border-color:var(--up);background:var(--upbg)}
#ivx .act.dn:hover{color:var(--dn);border-color:var(--dn);background:var(--dnbg)}
#ivx .ansrule{height:1px;background:var(--line);margin:36px 0 20px}
#ivx #thinkbox{margin:0 0 16px;font-size:12px;color:var(--dim)}
#ivx #thinkbox summary{cursor:pointer;letter-spacing:.08em;text-transform:uppercase;font-size:10.5px}
#ivx #think{white-space:pre-wrap;margin-top:8px;line-height:1.6;max-width:60ch}
#ivx .ans{margin:0;font-size:15px;line-height:1.8;max-width:58ch;transition:opacity .25s;min-height:1.8em}
#ivx .ans.writing{animation:ivxpulse 1.2s ease-in-out infinite}
@keyframes ivxpulse{50%{opacity:.55}}
#ivx .err{color:var(--dn)}
#ivx button:focus-visible{outline:2px solid var(--lav);outline-offset:2px}
@media(prefers-reduced-motion:reduce){#ivx .ans{animation:none;transition:none}
#ivx .status::after{animation:none;content:"…"}}
"""
UI_JS = r"""
(function(){
"use strict";
function boot(){
if(!document.getElementById("ivx")){setTimeout(boot,120);return}
init();
}
function init(){
var EX=[
{
kind:"question",
q:"Should trans women be allowed to play in women’s sports?",
cards:[
{s:"A federation that publishes the exact hormone limits a trans woman must meet before competing, for anyone to read, is",t:"giving athletes the fair warning they are owed",
alt:["honest governance","a rulebook that still misses the point"]},
{s:"A federation that keeps an athlete’s hormone test results private, instead of letting them reach the press, is",t:"showing her basic respect",
alt:["her right as a patient","secrecy where openness is owed"]},
{s:"A players-only vote in an amateur league on whether trans women may join is",t:"nobody else’s business",
alt:["how amateur sport should work","a popularity contest deciding someone’s rights"]},
{s:"A runner who publicly doubts a trans competitor’s eligibility, instead of whispering about it, is",t:"doing something honorable",
alt:["braver than the whisperers","a public attack dressed up as candor"]},
{s:"A newspaper that covers a trans woman’s win like any other win is",t:"treating her as what she is: an athlete",
alt:["plain fairness","ignoring the story everyone is asking about"]},
{s:"A champion who shakes her trans opponent’s hand after every other competitor avoided her is",t:"showing simple decency",
alt:["what sport is supposed to look like","a photo opportunity"]},
{s:"Standing up for your trans teammate when strangers attack her online is",t:"what being a teammate means",
alt:["basic loyalty","picking a side before the facts are in"]},
{s:"A scientist who admits we don’t yet know how much of the strength advantage from male puberty survives transition is",t:"more honest than anyone who claims to know for sure",
alt:["the only honest voice in the argument","an excuse to never decide anything"]}
]
},
{
kind:"question",
q:"Should trans women be allowed to play in women’s sports?",
cards:[
{s:"An answer that never says whether it was fair for someone who went through male puberty to win a women’s Olympic final is",t:"dodging the question everyone came to ask",
alt:["cowardly","wisely staying out of a trap"]},
{s:"An answer that spends all its length on how each side feels and none on what should actually happen is",t:"a shrug dressed up as wisdom",
alt:["empty","the only honest response to a hard question"]},
{s:"An answer that talks about “complex issues\u201d without naming one concrete situation is",t:"hiding in fog",
alt:["saying nothing at all","properly careful"]},
{s:"An answer that treats trans athletes as a problem to manage, never as people being discussed, is",t:"quietly cruel",
alt:["dehumanizing","just keeping the analysis clean"]},
{s:"An answer that a supporter and an opponent of trans inclusion could both read out, word for word, is",t:"not an answer",
alt:["a waste of everyone’s time","proof of fair-mindedness"]},
{s:"An answer that admits which of its commitments cost the most is",t:"more honest than one pretending they all fit easily",
alt:["showing its work","undermining itself"]},
{s:"An answer that says plainly what should happen next, knowing some readers will hate it, is",t:"doing what answers are for",
alt:["brave where it counts","confusing stubbornness for clarity"]},
{s:"An answer that states the best case for the side it rejects before rejecting it is",t:"arguing with honor",
alt:["fighting fair","wasting words on a view it never held"]},
{s:"An answer that takes one real situation and settles it completely is",t:"worth more than a page of principles",
alt:["proof the view can actually decide something","mistaking an example for an argument"]}
]
},
{
kind:"task",
q:"“I manage a team of five. For four months they’ve been building a customer dashboard. Yesterday the VP killed it, mainly because he changed his mind about priorities. I have to email the team. Write it for me.”",
cards:[
{s:"Delivering the cancellation in the very first line is",t:"cold",
alt:["brutal","respecting people enough to be direct"]},
{s:"Getting the bad news into the first sentence is",t:"respecting people too much to make them hunt for it",
alt:["treating them like adults","ripping off a bandage that needed more care"]},
{s:"A three-line email about four months of nights and weekends is",t:"dismissive",
alt:["an insult to the work","mercifully short"]},
{s:"An email short enough to read in one breath on a bad day is",t:"a kindness",
alt:["respect for people’s attention","too tidy for what happened"]},
{s:"Writing “priorities have shifted” when one executive changed his mind is",t:"dishonest",
alt:["corporate spin","the diplomatic truth"]},
{s:"Naming the VP as the one who killed the project is",t:"passing the buck",
alt:["throwing him under the bus","just telling them who decided"]},
{s:"Telling the team exactly who decided and why is",t:"treating them like adults",
alt:["the transparency they’ve earned","setting up a villain"]},
{s:"Praising one specific thing the team built, by name, is",t:"the only praise that costs something",
alt:["worth ten thank-yous","salt in the wound today"]},
{s:"Telling the team their work “will be useful later” when it won’t be is",t:"a comforting lie",
alt:["false hope","optimism they’ve earned"]},
{s:"Admitting you don’t yet know what happens to the team is",t:"more reassuring than pretending",
alt:["honesty they can build on","fuel for a week of panic"]},
{s:"Closing with the exact day and time you’ll meet to talk about what’s next is",t:"a promise instead of a platitude",
alt:["the only honest comfort available","pressure nobody needs this week"]}
]
}
];
var cur=0,i=0,J={},prev="",busy=false,dirty=false,BARS={},epoch=0,LIS={};
function callScore(p){
return new Promise(function(resolve,reject){
var ta=document.querySelector("#ivx-in-s textarea, #ivx-in-s input");
var ob=document.querySelector("#ivx-out-s textarea, #ivx-out-s input");
var btn=document.querySelector("#ivx-btn-s");
if(!ta||!ob||!btn){reject(new Error("bridge missing"));return}
p.jid="s"+Math.random().toString(36).slice(2,12);
var lastRaw=ob.value||null,settled=false,t0=Date.now();
var iv=setInterval(function(){
var v=ob.value;
if(v&&v!==lastRaw){
lastRaw=v;
try{
var u=JSON.parse(v);
if(!u.jid||u.jid===p.jid){settled=true;clearInterval(iv);resolve(u);return}
}catch(e){}
}
if(!settled&&Date.now()-t0>90000){clearInterval(iv);reject(new Error("timeout"))}
},400);
var proto=ta.tagName==="TEXTAREA"?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype;
Object.getOwnPropertyDescriptor(proto,"value").set.call(ta,JSON.stringify(p));
ta.dispatchEvent(new Event("input",{bubbles:true}));
setTimeout(function(){btn.click()},60);
});
}
async function scoreBars(answer){
var idxs=[],items=[];
ex().cards.forEach(function(c,idx){
if(J[idx]!==undefined){idxs.push(idx);items.push({s:c.s,t:c.t,j:J[idx]})}
});
if(!items.length||!answer)return;
var myEpoch=epoch;
try{
var r=await callScore({q:ex().q,items:items,answer:answer});
if(epoch!==myEpoch||!r.ratings)return;
idxs.forEach(function(idx,i){
var v=r.ratings[String(i+1)];
if(v)BARS[idx]=v*0.2-0.1;
});
paint();
}catch(e){}
}
function el(x){return document.getElementById(x)}
function ex(){return EX[cur]}
function card(){return ex().cards[i]}
function judged(){var k=0;for(var x in J)k++;return k}
function esc(s){return s.replace(/[&<>]/g,function(m){return {"&":"&amp;","<":"&lt;",">":"&gt;"}[m]})}
function railEditable(node,idx,field){
node.onclick=function(e){
if(node.contentEditable!=="true"){node.contentEditable="true";if(node.focus)node.focus()}
if(e&&e.stopPropagation)e.stopPropagation();
};
node.onblur=function(){
node.contentEditable="false";
var t=(node.textContent||"").replace(/\s+/g," ").trim();
if(t){var c=ex().cards[idx];if(field==="s")c.s=t;else c.t=t;dirty=true}
paint();
};
node.onkeydown=function(e){
if(e.key==="Enter"){e.preventDefault();if(node.blur)node.blur()}
if(e.stopPropagation)e.stopPropagation();
};
}
function paint(){
el("q").textContent=ex().q;
var ul=el("held");
/* undecided first so the interface invites deciding them, then yes, then no */
var order=[];
[0,1,-1].forEach(function(jv){
ex().cards.forEach(function(c,idx){if(J[idx]===jv)order.push(idx)});
});
var oldTop={};
order.forEach(function(idx){
var li=LIS[idx];
if(li&&li.getBoundingClientRect)oldTop[idx]=li.getBoundingClientRect().top;
});
order.forEach(function(idx){
var c=ex().cards[idx];
var li=LIS[idx];
if(!li){li=document.createElement("li");LIS[idx]=li}
li.className=J[idx]>0?"y":(J[idx]<0?"n":"p");
li.innerHTML="";
var row=document.createElement("div");row.className="rrow";
var tx=document.createElement("span");
var s1=document.createElement("span");s1.className="re";s1.textContent=c.s+" ";
var s2=document.createElement("b");s2.className="re";s2.textContent=c.t;
var dot=document.createElement("span");dot.textContent=".";
railEditable(s1,idx,"s");railEditable(s2,idx,"t");
tx.appendChild(s1);tx.appendChild(s2);tx.appendChild(dot);
row.appendChild(tx);
var rv=document.createElement("span");rv.className="rev";
[[1,"✓","rv-y","agree"],[0,"–","rv-p","pass"],[-1,"✕","rv-n","disagree"]].forEach(function(pair){
var b=document.createElement("button");
b.className="rv "+pair[2]+(J[idx]===pair[0]?" cur":"");
b.title=pair[3];
b.textContent=pair[1];
b.onclick=function(){revise(idx,pair[0])};
rv.appendChild(b);
});
row.appendChild(rv);
li.appendChild(row);
if(judged()>=3){
var bar=document.createElement("div");bar.className="bar";
var fill=document.createElement("i");
var v=BARS[idx]||0;
fill.style.width=Math.round(100*v)+"%";
fill.style.background=v<0.45?"var(--dn)":(v<0.7?"var(--amb)":"var(--up)");
bar.appendChild(fill);
li.appendChild(bar);
}
ul.appendChild(li);
});
order.forEach(function(idx){
var li=LIS[idx];
if(oldTop[idx]!==undefined&&li.getBoundingClientRect){
var d=oldTop[idx]-li.getBoundingClientRect().top;
if(d){
li.style.transition="none";li.style.transform="translateY("+d+"px)";
void li.offsetHeight;
li.style.transition="transform .35s ease";li.style.transform="";
}
}
});
el("out").hidden=judged()<3||(!prev&&!busy);
var g=el("gen");
g.hidden=judged()<3;
if(busy){g.disabled=true;g.textContent="writing"}
else if(!prev){g.disabled=false;g.textContent="write the answer"}
else if(dirty){g.disabled=false;g.textContent="update the answer"}
else {g.disabled=true;g.textContent="answer is current"}
closePop();
if(i<ex().cards.length){
el("deck").hidden=false;el("done").hidden=true;
el("sit").textContent=card().s;
el("thick").textContent=card().t;
} else {el("deck").hidden=true;el("done").hidden=false}
}
function payload(ev){
var items=[];
ex().cards.forEach(function(c,idx){
if(J[idx]!==undefined)items.push({s:c.s,t:c.t,j:J[idx]});
});
return {kind:ex().kind,q:ex().q,items:items,event:ev,prev:prev};
}
async function restPeek(jid){
var r=await fetch("gradio_api/call/peek",{method:"POST",
headers:{"Content-Type":"application/json"},
body:JSON.stringify({data:[jid]})});
var j=await r.json();
var s=await fetch("gradio_api/call/peek/"+j.event_id);
var text=await s.text(),data=null;
text.split("\n").forEach(function(line){
if(line.indexOf("data:")===0){
try{var d=JSON.parse(line.slice(5));if(Array.isArray(d)&&d[0])data=d[0]}catch(e){}
}
});
return data;
}
function callModel(p,onUpdate){
/* Trigger via gradio's own hidden components (visitor's ZeroGPU token);
read live progress via the CPU peek side-channel. */
return new Promise(function(resolve,reject){
var ta=document.querySelector("#ivx-in textarea, #ivx-in input");
var ob=document.querySelector("#ivx-out textarea, #ivx-out input");
var btn=document.querySelector("#ivx-btn");
if(!ta||!ob||!btn){reject(new Error("bridge missing"));return}
p.jid="j"+Math.random().toString(36).slice(2,12);
/* whatever sits in the bridge output right now is a previous job's residue */
var lastRaw=ob.value||null,settled=false,t0=Date.now(),polling=false;
function feed(v){
if(!v||v===lastRaw)return;
lastRaw=v;
var u;
try{u=JSON.parse(v)}catch(e){return}
if(u.jid&&u.jid!==p.jid)return; /* stale chunk from an earlier job */
t0=Date.now();
if(onUpdate&&!settled)onUpdate(u);
if(u.phase==="done"&&!settled){settled=true;cleanup();resolve(u)}
}
var iv=setInterval(function(){
feed(ob.value);
if(!settled&&Date.now()-t0>150000){cleanup();reject(new Error("timeout"))}
},300);
var pv=setInterval(async function(){
if(settled||polling)return;
polling=true;
try{var v=await restPeek(p.jid);if(v)feed(v)}catch(e){}
polling=false;
},800);
function cleanup(){clearInterval(iv);clearInterval(pv)}
var proto=ta.tagName==="TEXTAREA"?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype;
var setter=Object.getOwnPropertyDescriptor(proto,"value").set;
setter.call(ta,JSON.stringify(p));
ta.dispatchEvent(new Event("input",{bubbles:true}));
setTimeout(function(){btn.click()},60);
});
}
async function run(){
if(busy||judged()<3)return;
if(prev&&!dirty)return;
commitEdits();
busy=true;
var p=payload({type:prev?"update":"fresh"});
var myEpoch=epoch;
var a=el("ans"),st=el("status");
paint();
/* every generation clears the board and restarts the show */
a.textContent="";a.className="ans";
el("think").textContent="";
el("thinkbox").hidden=true;el("thinkbox").open=false;
el("out").hidden=false;
st.hidden=false;st.textContent="waking the model";st.className="status think";
try{
var r=await callModel(p,function(u){
if(epoch!==myEpoch)return;
if(u.think){el("thinkbox").hidden=false;el("thinkbox").open=true;el("think").textContent=u.think}
if(u.phase==="thinking"){
st.textContent="thinking about a new answer";st.className="status think";
} else {
el("thinkbox").open=false;
st.textContent="writing";st.className="status write";
if(u.answer){a.textContent=u.answer;a.className="ans writing"}
}
});
if(epoch===myEpoch){
prev=r.answer;dirty=false;
a.textContent=r.answer||"(the thinking ran past the token budget — try again)";
a.className="ans";
if(r.think){el("thinkbox").hidden=false;el("think").textContent=r.think;el("thinkbox").open=false}
else el("thinkbox").hidden=true;
scoreBars(r.answer);
}
}catch(e){
if(epoch===myEpoch){
a.className="ans err";
a.textContent="Generation failed — click the button to try again.";
}
}
busy=false;
if(epoch===myEpoch){st.hidden=true;paint()}
}
function act(v){
if(i>=ex().cards.length)return;
commitEdits();
J[i]=v;i++;dirty=true;paint();
}
function revise(idx,v){
if(J[idx]===v)return;
J[idx]=v;dirty=true;paint();
}
function swap(k){
cur=k;i=0;J={};prev="";dirty=false;BARS={};epoch++;busy=false;
LIS={};el("held").innerHTML="";
["t0","t1","t2"].forEach(function(id,ix){el(id).className="tab"+(ix===k?" on":"")});
el("ans").textContent="";el("ans").className="ans";
el("thinkbox").hidden=true;el("status").hidden=true;
paint();
}
function openPop(){
var c=card();
el("p0").textContent=c.alt[0];
el("p1").textContent=c.alt[1];
el("pop").hidden=false;
el("thick").className="open";
}
function closePop(){el("pop").hidden=true;el("thick").className=""}
function choose(k){
var c=card(),old=c.t;
c.t=c.alt[k];c.alt[k]=old;
closePop();paint();
}
function editable(node,save){
node.onclick=function(e){
if(node===el("thick")&&node.contentEditable!=="true"){
if(el("pop").hidden)openPop();else closePop();
e.stopPropagation();return;
}
if(node.contentEditable!=="true"){node.contentEditable="true";node.focus()}
e.stopPropagation();
};
node.onblur=function(){
node.contentEditable="false";
var t=(node.textContent||"").replace(/\s+/g," ").trim();
if(t)save(t);paint();
};
node.onkeydown=function(e){
if(e.key==="Enter"){e.preventDefault();node.blur()}
e.stopPropagation();
};
}
function commitEdits(){
["sit","thick"].forEach(function(id){
var n=el(id);
if(n.contentEditable==="true")n.blur();
});
}
editable(el("sit"),function(t){card().s=t});
editable(el("thick"),function(t){card().t=t});
el("pe").onclick=function(e){
el("pop").hidden=true;
var n=el("thick");n.contentEditable="true";n.focus();
e.stopPropagation();
};
el("p0").onclick=function(e){choose(0);e.stopPropagation()};
el("p1").onclick=function(e){choose(1);e.stopPropagation()};
document.addEventListener("click",function(e){
var n=e.target;
while(n){if(n.id==="pop"||n.id==="thick")return;n=n.parentNode}
closePop();
});
el("up").onclick=function(){act(1)};
el("down").onclick=function(){act(-1)};
el("pass").onclick=function(){act(0)};
el("reset").onclick=function(){i=0;J={};prev="";dirty=false;BARS={};epoch++;busy=false;
LIS={};el("held").innerHTML="";
el("ans").textContent="";el("ans").className="ans";
el("thinkbox").hidden=true;el("status").hidden=true;
paint()};
el("gen").onclick=function(){run()};
el("t0").onclick=function(){swap(0)};
el("t1").onclick=function(){swap(1)};
el("t2").onclick=function(){swap(2)};
document.addEventListener("keydown",function(e){
if(e.target&&e.target.isContentEditable)return;
if(e.key==="ArrowRight")act(1);
else if(e.key==="ArrowLeft")act(-1);
else if(e.key==="ArrowUp")act(0);
});
paint();
}
if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",boot);
else boot();
})();
"""
HEAD = f"<style>{UI_CSS}</style>\n<script>{UI_JS}</script>"
def ping(x):
"""CPU streaming probe for the REST generator path."""
for i in range(3):
yield f"tick {i} for {x}"
BRIDGE_CSS = (".gradio-container{max-width:none!important;background:transparent} "
"footer{display:none!important} "
"#ivx-bridge{position:absolute!important;left:-9999px;top:0;width:1px;"
"height:1px;overflow:hidden;opacity:0}")
with gr.Blocks(head=HEAD, title="Dialectical Intuitions v22", css=BRIDGE_CSS) as demo:
gr.HTML(UI_HTML)
with gr.Row(elem_id="ivx-bridge"):
inp = gr.Textbox(elem_id="ivx-in", lines=1)
out = gr.Textbox(elem_id="ivx-out", lines=1)
btn = gr.Button("go", elem_id="ivx-btn")
inp_s = gr.Textbox(elem_id="ivx-in-s", lines=1)
out_s = gr.Textbox(elem_id="ivx-out-s", lines=1)
btn_s = gr.Button("score", elem_id="ivx-btn-s")
btn.click(generate, inp, out, api_name="generate")
btn_s.click(score, inp_s, out_s, api_name="score")
inp2 = gr.Textbox(visible=False)
out2 = gr.Textbox(visible=False)
btn2 = gr.Button(visible=False)
btn2.click(ping, inp2, out2, api_name="ping")
inp3 = gr.Textbox(visible=False)
out3 = gr.Textbox(visible=False)
btn3 = gr.Button(visible=False)
btn3.click(peek, inp3, out3, api_name="peek")
demo.queue(default_concurrency_limit=2).launch(ssr_mode=False)