"""A free ZeroGPU demo of DecidaBERT-large: a state and typed questions in, a probability distribution per question out, in one forward pass. This is a Gradio-native re-implementation for this Space; it does not import or modify the `decida` package (see model.py). The full project — REST API, MCP, the real testbench of games — lives at https://github.com/heldernoid/decida and runs the same model, plus Qwen3-0.6B and the Laya family, locally on your own hardware.""" import html import json import time import gradio as gr import torch from model import Primitive, Question, load, predict try: import spaces # only present when actually running on a Hugging Face Space GPU = spaces.GPU except ImportError: # running app.py locally, outside a Space def GPU(fn): return fn # ZeroGPU: torch.cuda.is_available() is True even outside an @spaces.GPU call, through the emulation mode # documented at https://huggingface.co/docs/hub/spaces-zerogpu, so this picks "cuda" correctly on the Space and # falls back sanely (mps/cpu) when running app.py locally to test model.py's logic. DEVICE = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu" MODEL, LAYOUT = load(DEVICE) PRESETS = { "Billing routing": { "state": "Customer: my invoice was charged twice and nobody answers the phone!", "questions": { "department": {"type": "choice", "instructions": "Which team should handle this?", "criteria": {"billing": "Charges, invoices, refunds", "technical": "Bugs and outages", "sales": "Pricing, demos, new seats"}}, "frustration": {"type": "score", "instructions": "How frustrated is the customer?", "criteria": ["Calm", "Frustrated", "Very angry"]}, "urgent": {"type": "noul", "instructions": "Is this urgent and needs an answer today?"}, }, }, "Tool-call gate": { "state": "Agent wants to run: rm -rf ./build && git push --force origin main", "questions": { "action": {"type": "choice", "instructions": "What should happen to this command?", "criteria": {"allow": "Safe to run", "confirm": "Ask the user first", "block": "Do not run"}}, "destructive": {"type": "noul", "instructions": "Can this command destroy work that cannot be recovered?"}, }, }, "Churn risk": { "state": "Cancelled two seats last month and asked about export options this week.", "questions": { "risk": {"type": "score", "instructions": "How likely is this account to leave?", "criteria": ["Low", "Medium", "High"]}, "call": {"type": "noul", "instructions": "Should a person reach out this week?"}, }, }, "Content moderation": { "state": "Comment on a public forum: \"Nobody with half a brain would ever agree with this take, get a clue.\"", "questions": { "action": {"type": "choice", "instructions": "What should happen to this comment?", "criteria": {"keep": "Within normal debate", "warn": "Rude, but not a policy breach", "remove": "Harassment or a personal attack"}}, "toxic": {"type": "noul", "instructions": "Does this comment personally attack another user?"}, }, }, "News topic": { "state": "The central bank raised interest rates by half a point to fight inflation, its third hike this year.", "questions": { "topic": {"type": "choice", "instructions": "What is this news about?", "criteria": {"business": "Companies, markets, the economy", "sports": "Teams, athletes, results", "science": "Research, technology, space", "politics": "Government, elections, policy"}}, }, }, "Product review": { "state": "Absolutely wonderful, the best purchase I have made this year. Fast shipping too.", "questions": { "stars": {"type": "score", "instructions": "How many stars did the reviewer give?", "criteria": ["1 star", "2 stars", "3 stars", "4 stars", "5 stars"]}, "recommend": {"type": "noul", "instructions": "Would the reviewer recommend this to a friend?"}, }, }, "Spam filter": { "state": "WINNER!! You have been selected for a $1000 gift card. Click http://bit.ly/x9 now to claim.", "questions": { "action": {"type": "choice", "instructions": "What should happen to this message?", "criteria": {"inbox": "Legitimate, deliver normally", "spam": "Unsolicited mail or a scam"}}, }, }, "PII detection": { "state": "My card number is 4111 1111 1111 1111 and it expires 09/28, please charge it today.", "questions": { "pii": {"type": "noul", "instructions": "Does the text contain payment card details?"}, "urgent": {"type": "noul", "instructions": "Does the writer want this handled today?"}, }, }, "Emotion": { "state": "Nobody told me the meeting was cancelled and I drove two hours for nothing.", "questions": { "emotion": {"type": "choice", "instructions": "Which emotion does the writer express?", "criteria": {"joy": "Happy, excited, delighted", "sadness": "Sad, hurt, hopeless", "anger": "Angry, irritated, furious", "fear": "Afraid, anxious, worried"}}, }, }, "Meeting request": { "state": "Could we push tomorrow's 9am sync to the afternoon? Something came up this morning.", "questions": { "urgency": {"type": "score", "instructions": "How urgent is this request?", "criteria": ["Not urgent", "Somewhat urgent", "Urgent"]}, "reschedule": {"type": "noul", "instructions": "Is the writer asking to move a meeting?"}, }, }, } CSS = """ :root { --bg:#f4f5f8; --card:#fff; --ink:#141a2e; --ink2:#4a5270; --ink3:#7b839e; --line:#e3e6ee; --accent:#4338ca; --track:#eceefb; } .gradio-container { max-width: 1040px !important; margin: 0 auto !important; font-family: -apple-system, "Segoe UI", sans-serif !important; background: var(--bg) !important; } #title h1 { font-size: 30px; font-weight: 800; letter-spacing: -0.01em; margin-bottom: 2px; } #subtitle { color: var(--ink2); font-size: 15px; margin-bottom: 6px; } .dc-card { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 16px 18px; } .dc-q { padding: 12px 0; border-top: 1px solid var(--line); } .dc-q:first-child { border-top: none; padding-top: 0; } .dc-qhead { display:flex; align-items:center; gap:8px; font-weight:700; font-size:15px; color: var(--ink); margin-bottom: 2px; } .dc-type { font: 700 10px ui-monospace, monospace; color: var(--ink3); border: 1px solid var(--line); border-radius: 5px; padding: 1px 6px; text-transform: uppercase; } .dc-instr { color: var(--ink2); font-size: 13px; margin-bottom: 8px; } .dc-bar-row { display: grid; grid-template-columns: minmax(90px,160px) 1fr 48px; gap: 10px; align-items: center; margin: 5px 0; font-size: 13.5px; } .dc-bar-track { display: block; height: 10px; border-radius: 6px; background: var(--track); overflow: hidden; } .dc-bar-fill { display: block; height: 100%; border-radius: 6px; } .dc-bar-label.top { font-weight: 700; color: var(--ink); } .dc-bar-num { font-family: ui-monospace, monospace; text-align: right; color: var(--ink2); } .dc-meta { color: var(--ink3); font-size: 12.5px; margin-top: 10px; } .dc-meta b { color: var(--ink); } """ def _render(answers: dict, raw: dict) -> str: blocks = [] for qid, a in answers.items(): instr = html.escape(str(raw[qid].get("instructions", ""))) rows = [] if a["type"] == "noul": items = [("true", a["noul"]), ("false", 1 - a["noul"])] else: probs = a["probabilities"] items = list(probs.items()) top_key = max(items, key=lambda kv: kv[1])[0] for k, v in items: label = f"{k}: {a['legend'][k]}" if a["type"] == "score" else str(k) top = "top" if k == top_key else "" # Colour scales with the value itself, not just a binary "is this the top answer": a near-zero bar # stays a faint sliver, a near-one bar is a solid, saturated fill. alpha = 0.15 + 0.80 * v fill_style = f"width:{v * 100:.1f}%; background:rgba(67,56,202,{alpha:.2f})" rows.append( f'
' ) blocks.append( f'