Spaces:
Sleeping
Sleeping
File size: 35,896 Bytes
170959e b4a1604 170959e b4a1604 170959e cae09f4 170959e 538b97d 170959e c3c1739 170959e b4a1604 170959e 49e37d6 170959e b4a1604 170959e 620b0b3 49e37d6 620b0b3 49e37d6 620b0b3 8867275 620b0b3 49e37d6 620b0b3 c3c1739 170959e fdeb768 12ad1c7 170959e 538b97d 170959e f33a5f2 170959e 85e9bbb 170959e 85e9bbb 170959e fdcf3bf 170959e 49e37d6 85e9bbb 170959e c3c1739 170959e f33a5f2 a7bcc80 f33a5f2 170959e 538b97d 170959e eb358cd 170959e f38e020 170959e 117edae 1eba37a 117edae 170959e fdeb768 4beb8e2 fdeb768 9591b28 fdeb768 9591b28 fdeb768 9591b28 fdeb768 12ad1c7 fdeb768 170959e 423d497 170959e 423d497 170959e 423d497 170959e 423d497 170959e c3c1739 49e37d6 170959e c3c1739 170959e c3c1739 170959e c3c1739 49e37d6 170959e c3c1739 49e37d6 170959e f33a5f2 170959e f33a5f2 170959e 49e37d6 85e9bbb 49e37d6 170959e c3c1739 538b97d 170959e 620b0b3 3cf78b0 620b0b3 3cf78b0 620b0b3 8867275 620b0b3 3cf78b0 8867275 3cf78b0 620b0b3 3cf78b0 620b0b3 3cf78b0 620b0b3 3cf78b0 170959e 538b97d 170959e 538b97d f38e020 eb358cd 538b97d eb358cd 538b97d f33a5f2 170959e b4a1604 538b97d eb358cd f33a5f2 eb358cd f33a5f2 b4a1604 eb358cd b4a1604 538b97d 170959e c3c1739 170959e f38e020 eb358cd 538b97d f38e020 170959e 538b97d 170959e 538b97d 170959e 538b97d 170959e 538b97d c3c1739 fdeb768 f38e020 eb358cd 170959e 538b97d c3c1739 f38e020 eb358cd f38e020 538b97d 170959e fdeb768 170959e 996c3e9 3cf78b0 12ad1c7 170959e 3cf78b0 c3c1739 170959e c3c1739 996c3e9 620b0b3 996c3e9 eadace3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 | """
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 {"&":"&","<":"<",">":">"}[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)
|