from __future__ import annotations import hashlib import html import json import os import re from functools import lru_cache from typing import Any APP_TITLE = "Trollsona" APP_SUBTITLE = "Summon the little menace living behind your respectable personality." TRACK_NAME = "An Adventure in Thousand Token Wood" DEFAULT_MODEL_ID = "RthItalia/nano_compact_3b_qkvfp16" DEFAULT_FALLBACK_MODEL_ID = "Qwen/Qwen2.5-0.5B-Instruct" MAX_PROFILE_CHARS = 700 MAX_NAME_CHARS = 36 def parse_bool_env(name: str, default: bool) -> bool: raw_value = os.getenv(name) if raw_value is None: return default normalized = raw_value.strip().lower() if normalized in {"1", "true", "yes", "on"}: return True if normalized in {"0", "false", "no", "off"}: return False return default def parse_int_env(name: str, default: int, min_value: int, max_value: int) -> int: raw_value = os.getenv(name) if raw_value is None: return default try: value = int(raw_value) except ValueError: return default return max(min_value, min(max_value, value)) MODEL_ID = os.getenv("TROLLSONA_MODEL_ID", DEFAULT_MODEL_ID) FALLBACK_MODEL_ID = os.getenv("TROLLSONA_FALLBACK_MODEL_ID", DEFAULT_FALLBACK_MODEL_ID) MODEL_ENABLED = parse_bool_env("TROLLSONA_ENABLE_MODEL", default=False) MAX_NEW_TOKENS = parse_int_env("TROLLSONA_MAX_NEW_TOKENS", 200, 32, 512) PERSONA_STYLES = { "Back-Alley Oracle": { "flavor": "candlelit prophecy from a very suspicious side street", "noun_pool": ["Candle", "Omen", "Alley", "Brass", "Whisper", "Ledger"], }, "Basement Prince": { "flavor": "royal delusion wrapped in dust, snacks, and old cables", "noun_pool": ["Basement", "Velvet", "Outlet", "Throne", "Snack", "Static"], }, "Forest Heckler": { "flavor": "mossy woodland sarcasm with a pocket full of bad advice", "noun_pool": ["Moss", "Root", "Twig", "Bog", "Fern", "Stump"], }, "Union Goblin": { "flavor": "petty workplace grievance with ceremonial clipboard energy", "noun_pool": ["Clause", "Mug", "Breakroom", "Badge", "Staple", "Shift"], }, "Dungeon Intern": { "flavor": "overworked dungeon bureaucracy and unpaid dramatic labor", "noun_pool": ["Ledger", "Torch", "Mop", "Key", "Goblet", "Trapdoor"], }, "Mall Witch": { "flavor": "food-court divination with lip gloss and thunder", "noun_pool": ["Kiosk", "Charm", "Receipt", "Fountain", "Mascara", "Pretzel"], }, "Parking Lot Philosopher": { "flavor": "deep truths delivered beside a dented shopping cart", "noun_pool": ["Asphalt", "Cart", "Neon", "Cone", "Puddle", "Keychain"], }, "Saint of Bad Decisions": { "flavor": "holy nonsense for people who turn errands into lore", "noun_pool": ["Halo", "Candle", "Excuse", "Relic", "Errand", "Confetti"], }, "Meme Caporegime": { "flavor": "old-neighborhood swagger filtered through cursed screenshots", "noun_pool": ["Pixel", "Prophecy", "Caption", "Scroll", "Vibe", "Echo"], }, } SPICE_LABELS = { 1: "tiny pinch", 2: "polite sting", 3: "back-room heckle", 4: "crispy little judgment", 5: "full dossier incident", } BLOCKED_PATTERNS = [ r"\bkill yourself\b", r"\bkys\b", r"\bself[- ]?harm\b", r"\bsuicide\b", r"\bhate\b", r"\bidiot\b", r"\bstupid\b", r"\bmoron\b", r"\bdumb\b", r"\bloser\b", r"\bugly\b", r"\bworthless\b", r"\bsubhuman\b", r"\bslur\b", r"\bterrorist\b", r"\bsexual\b", r"\bexplicit\b", r"\bprotected class\b", ] PROTECTED_TARGETING_PATTERNS = [ r"\bbecause of your race\b", r"\bbecause of your religion\b", r"\bbecause of your gender\b", r"\bbecause of your sexuality\b", r"\bbecause of your disability\b", r"\bbecause of your nationality\b", r"\bbecause of your ethnicity\b", ] SAFE_REPLY = ( "The dossier hissed, smoked, and refused to punch down. " "Final harmless verdict: your chaos has excellent posture and a suspicious little hat." ) SAFE_ADVICE = "Make the next useful move before you decorate the excuse." PRESET_DOSSIERS = [ { "button": "Mira - coffee-built UI oracle", "values": ( "Mira", "I overbuild side projects, drink too much coffee, and love weird UI.", "Back-Alley Oracle", 3, True, ), }, { "button": "Alex - label-system dungeon clerk", "values": ( "Alex", "I start productivity systems and then reorganize the labels forever.", "Dungeon Intern", 4, True, ), }, { "button": "Sam - tiny-game screenshot boss", "values": ( "Sam", "I make tiny games, forget lunch, and name variables like ancient spells.", "Meme Caporegime", 2, False, ), }, ] def stable_int(*parts: str) -> int: payload = "||".join(parts).encode("utf-8", errors="ignore") return int(hashlib.sha256(payload).hexdigest()[:12], 16) def clean_text(value: Any, max_chars: int) -> str: text = "" if value is None else str(value) text = re.sub(r"\s+", " ", text).strip() return text[:max_chars] def clamp_spice(value: Any) -> int: try: spice = int(value) except (TypeError, ValueError): spice = 3 return max(1, min(5, spice)) def compute_cringe_score(profile: str, persona: str, spice: int) -> int: base = stable_int(profile.lower(), persona.lower(), str(spice)) % 61 return max(0, min(100, 22 + base + (spice * 3))) def cringe_label(score: int) -> str: if score < 35: return "barely haunted" if score < 60: return "noticeably cursed" if score < 82: return "dossier-grade cringe" return "full goblin canon event" def build_prompt( user_name: str, profile: str, persona: str, spice: int, include_advice: bool, score: int, ) -> str: style = PERSONA_STYLES.get(persona, PERSONA_STYLES["Forest Heckler"]) advice_rule = "Include one practical useful_advice sentence." if include_advice else ( "Set useful_advice to a short note that advice was disabled." ) return f""" You are Trollsona, a theatrical troll alter-ego generator. Track: {TRACK_NAME}. Your job is to transform the user's self-description into a funny, slightly grotesque, whimsical troll persona. Make it feel like a stained-paper character dossier that was dictated by a back-alley fortune teller, stamped by a petty clerk, and lightly heckled by an italo-american cousin who has opinions but not cruelty. Return only valid minified JSON with these fields: trollsona_name, troll_reply, useful_advice, cringe_score, cringe_score_label. Objective: - Make the result absurd, memorable, specific, and theatrical. - Make trollsona_name sound like a summoned character, not a username. - Keep it roasty, not hateful. - Keep the humor sharp but warm: playful sting, never humiliation. Style rules: - Write in vivid, punchy English. - Use occasional light italo-american flavor, but sparingly. - Good flavor examples: "listen, paisan", "madone", "capisce". - Do not overuse slang or turn the voice into a caricature. - Use grotesque but charming imagery: candle wax, receipts, tiny crowns, haunted binders, dented carts, snack dust, side quests, suspicious paperwork. - No generic roast bot voice. - No generic assistant copy, no filler, no disclaimers, no moralizing. - troll_reply must be the strongest comedic line, 1-3 short sentences max. - useful_advice must contain one real insight in 1 sentence max. Humor boundaries: - Roast only habits, vibe, overthinking, productivity rituals, startup energy, internet behavior, wording, or harmless personal lore. - Never attack protected characteristics or identity. - Never insult appearance, race, ethnicity, religion, disability, nationality, gender, sexuality, trauma, mental health, or protected traits. - Never include threats, self-harm, sexual content, profanity, or slurs. - Never punch down. User name: {user_name or "Anonymous traveler"} User profile: {profile or "No profile supplied."} Persona: {persona} Persona flavor: {style["flavor"]} Spice level: {spice}/5 ({SPICE_LABELS[spice]}) Use this exact deterministic cringe_score: {score} Use this matching cringe_score_label: {cringe_label(score)} {advice_rule} """.strip() def is_safe_text(text: str) -> bool: normalized = text.lower() for pattern in BLOCKED_PATTERNS + PROTECTED_TARGETING_PATTERNS: if re.search(pattern, normalized): return False return True def fallback_trollsona( user_name: str, profile: str, persona: str, spice: int, include_advice: bool, reason: str, ) -> dict[str, Any]: style = PERSONA_STYLES.get(persona, PERSONA_STYLES["Forest Heckler"]) seed = stable_int(user_name.lower(), profile.lower(), persona.lower(), str(spice)) adjectives = ["Velvet", "Candle", "Ashen", "Brass", "Crooked", "Sainted", "Static"] titles = [ "Overthinker in Residence", "Snack Baron of Almost", "Dossier Clerk", "Chaos Notary", "Sidequest Duke", "Patron Saint of Later", ] noun = style["noun_pool"][seed % len(style["noun_pool"])] adjective = adjectives[(seed // 7) % len(adjectives)] title = titles[(seed // 13) % len(titles)] safe_name = re.sub(r"[^A-Za-z0-9 ]+", "", user_name).strip()[:MAX_NAME_CHARS] name_prefix = safe_name.title() if safe_name else adjective trollsona_name = f"{name_prefix} {noun}-{title}" roast_templates = [ "Listen, paisan: your vibe is a candlelit side quest that opened twelve tabs, found a tiny crown, and called it destiny.", "Your aura says main character, but your calendar is dressed like a haunted binder asking for rent.", "You are one dramatic cape away from turning a normal errand into a village ordinance.", "Your brain is a basement tavern where every idea demands a theme song, a snack bowl, and a separate invoice.", "Madone, you carry the confidence of a bridge troll charging tolls in vibes and loose receipts.", "You alphabetize chaos, misplace the alphabet, then file a complaint with the moon.", ] advice_templates = [ "Pick one task, make it smaller, and finish that version before you rename the kingdom.", "Write the next concrete step in one sentence, then do only that step. Capisce?", "Keep the weird idea, but give it a deadline and a visible done state.", "Trade one dramatic plan for one shipped artifact before the candles burn out.", "Use the chaos as seasoning, not as project management.", ] score = compute_cringe_score(profile, persona, spice) reply = roast_templates[(seed // 17 + spice) % len(roast_templates)] advice = advice_templates[(seed // 23 + spice) % len(advice_templates)] if not include_advice: advice = "Truth withheld. The dossier clerk stamps the page and looks away." return { "trollsona_name": trollsona_name, "troll_reply": reply, "useful_advice": advice, "cringe_score": score, "cringe_score_label": cringe_label(score), "include_advice": include_advice, "runtime": f"model_id={MODEL_ID}; fallback_model_id={FALLBACK_MODEL_ID}; model_enabled={MODEL_ENABLED}", "source": "deterministic_fallback", "fallback_reason": reason, } @lru_cache(maxsize=1) def load_model() -> tuple[Any | None, Any | None, str, str]: if not MODEL_ENABLED: return ( None, None, "model disabled by TROLLSONA_ENABLE_MODEL", f"model_id={MODEL_ID}; fallback_model_id={FALLBACK_MODEL_ID}; device=disabled", ) try: import torch from transformers import AutoModelForCausalLM, AutoTokenizer except Exception as exc: return ( None, None, f"model dependencies unavailable: {type(exc).__name__}: {exc}", f"model_id={MODEL_ID}; fallback_model_id={FALLBACK_MODEL_ID}; device=unavailable", ) failures: list[str] = [] def load_tokenizer(candidate_id: str) -> Any: tokenizer = AutoTokenizer.from_pretrained( candidate_id, use_fast=True, trust_remote_code=True, ) if tokenizer.pad_token_id is None and tokenizer.eos_token is not None: tokenizer.pad_token = tokenizer.eos_token return tokenizer def load_cuda_model(candidate_id: str) -> Any: load_attempts = [ { "trust_remote_code": True, "device_map": "cuda", "dtype": torch.float16, "low_cpu_mem_usage": True, }, { "trust_remote_code": True, "device_map": "cuda", "torch_dtype": torch.float16, "low_cpu_mem_usage": True, }, { "trust_remote_code": True, "torch_dtype": torch.float16, "low_cpu_mem_usage": True, }, ] last_error: Exception | None = None for kwargs in load_attempts: try: model = AutoModelForCausalLM.from_pretrained(candidate_id, **kwargs) if "device_map" not in kwargs: model = model.to("cuda") return model except Exception as exc: last_error = exc if last_error is not None: raise last_error raise RuntimeError("CUDA model load failed without exception") def load_cpu_model(candidate_id: str) -> Any: try: return AutoModelForCausalLM.from_pretrained( candidate_id, trust_remote_code=True, low_cpu_mem_usage=True, ) except TypeError: return AutoModelForCausalLM.from_pretrained(candidate_id, trust_remote_code=True) candidates = [ {"role": "primary", "model_id": MODEL_ID, "requires_cuda": True}, {"role": "fallback_model", "model_id": FALLBACK_MODEL_ID, "requires_cuda": False}, ] seen_model_ids: set[str] = set() for candidate in candidates: candidate_id = str(candidate["model_id"]).strip() if not candidate_id or candidate_id in seen_model_ids: continue seen_model_ids.add(candidate_id) role = str(candidate["role"]) requires_cuda = bool(candidate["requires_cuda"]) if requires_cuda and not torch.cuda.is_available(): failures.append(f"{role} {candidate_id}: CUDA unavailable") continue try: tokenizer = load_tokenizer(candidate_id) if torch.cuda.is_available(): model = load_cuda_model(candidate_id) device = "cuda" else: model = load_cpu_model(candidate_id) device = "cpu" model.eval() torch.manual_seed(0) if torch.cuda.is_available(): torch.cuda.manual_seed_all(0) fallback_note = "; ".join(failures) status = "model loaded" if not fallback_note else f"model loaded after fallback: {fallback_note}" runtime = ( f"model_id={candidate_id}; role={role}; device={device}; " f"cuda_available={torch.cuda.is_available()}" ) return tokenizer, model, status, runtime except Exception as exc: failures.append(f"{role} {candidate_id}: {type(exc).__name__}: {exc}") failure_text = " | ".join(failures) if failures else "no model candidates configured" runtime = ( f"model_id={MODEL_ID}; fallback_model_id={FALLBACK_MODEL_ID}; " f"cuda_available={torch.cuda.is_available()}" ) return None, None, f"model load failed: {failure_text}", runtime def format_generation_prompt(tokenizer: Any, prompt: str) -> str: try: if getattr(tokenizer, "chat_template", None): return tokenizer.apply_chat_template( [{"role": "user", "content": prompt}], tokenize=False, add_generation_prompt=True, ) except Exception: return prompt return prompt def generation_temperature(spice: int) -> float: return round(0.48 + (clamp_spice(spice) * 0.08), 2) def model_device(model: Any) -> Any: target_device = getattr(model, "device", None) if target_device is not None and str(target_device) != "meta": return target_device try: return next(model.parameters()).device except Exception: return None def generate_with_model(prompt: str, spice: int) -> tuple[str | None, str, str]: tokenizer, model, status, runtime = load_model() if tokenizer is None or model is None: return None, status, runtime try: import torch model_prompt = format_generation_prompt(tokenizer, prompt) inputs = tokenizer(model_prompt, return_tensors="pt", truncation=True, max_length=1536) target_device = model_device(model) if target_device is not None: inputs = {key: value.to(target_device) for key, value in inputs.items()} seed = stable_int(prompt, str(spice), runtime) % (2**31) torch.manual_seed(seed) if hasattr(torch, "cuda") and torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) with torch.no_grad(): output_ids = model.generate( **inputs, max_new_tokens=MAX_NEW_TOKENS, do_sample=True, temperature=generation_temperature(spice), num_beams=1, repetition_penalty=1.1, pad_token_id=tokenizer.eos_token_id, ) prompt_len = inputs["input_ids"].shape[-1] generated_ids = output_ids[0][prompt_len:] return tokenizer.decode(generated_ids, skip_special_tokens=True).strip(), status, runtime except Exception as exc: return None, f"model generation failed: {type(exc).__name__}: {exc}", runtime def parse_loose_model_fields(raw_text: str) -> dict[str, str]: fields: dict[str, str] = {} for field in ["trollsona_name", "troll_reply", "useful_advice", "cringe_score_label"]: pattern = rf'"{field}"\s*:\s*"((?:\\.|[^"\\])*)' match = re.search(pattern, raw_text or "", flags=re.DOTALL) if not match: continue try: value = json.loads(f'"{match.group(1)}"') except json.JSONDecodeError: value = match.group(1) fields[field] = str(value) return fields def coerce_model_result( parsed: dict[str, Any], fallback: dict[str, Any], score: int, include_advice: bool, fallback_reason: str, runtime: str, ) -> dict[str, Any] | None: result = dict(fallback) field_limits = { "trollsona_name": 80, "troll_reply": 360, "useful_advice": 280, "cringe_score_label": 80, } used_fields: list[str] = [] missing_fields: list[str] = [] for field, limit in field_limits.items(): value = clean_text(parsed.get(field), limit) if value and is_safe_text(value): result[field] = value used_fields.append(field) else: missing_fields.append(field) if not used_fields: return None result["cringe_score"] = score result["include_advice"] = include_advice result["source"] = "transformers_model" result["runtime"] = runtime if missing_fields: partial_reason = f"model output partial; fallback filled: {', '.join(missing_fields)}" result["fallback_reason"] = ( f"{fallback_reason}; {partial_reason}" if fallback_reason else partial_reason ) else: result["fallback_reason"] = fallback_reason return result def parse_model_output( raw_text: str, fallback: dict[str, Any], score: int, include_advice: bool, fallback_reason: str, runtime: str, ) -> dict[str, Any] | None: decoder = json.JSONDecoder() parsed = None for match in re.finditer(r"\{", raw_text or ""): try: candidate, _ = decoder.raw_decode(raw_text[match.start() :]) except json.JSONDecodeError: continue if isinstance(candidate, dict): parsed = candidate break if parsed is None: parsed = parse_loose_model_fields(raw_text) return coerce_model_result(parsed, fallback, score, include_advice, fallback_reason, runtime) def repair_model_output( raw_text: str, fallback: dict[str, Any], fallback_reason: str, runtime: str, ) -> dict[str, Any] | None: repaired_reply = clean_text(raw_text, 360) repaired_reply = re.sub(r"^```(?:json)?|```$", "", repaired_reply).strip() if not repaired_reply or repaired_reply.startswith("{"): return None if not is_safe_text(repaired_reply): return None result = dict(fallback) result["troll_reply"] = repaired_reply result["source"] = "transformers_model_repaired" result["runtime"] = runtime repair_reason = "model output was not valid JSON and was repaired" result["fallback_reason"] = f"{fallback_reason}; {repair_reason}" if fallback_reason else repair_reason return result def safety_guard(result: dict[str, Any], fallback: dict[str, Any]) -> dict[str, Any]: fields = [ result.get("trollsona_name", ""), result.get("troll_reply", ""), result.get("useful_advice", ""), result.get("cringe_score_label", ""), ] if not all(is_safe_text(str(field)) for field in fields): guarded = dict(fallback) guarded["troll_reply"] = SAFE_REPLY guarded["useful_advice"] = SAFE_ADVICE guarded["fallback_reason"] = "safety guard replaced unsafe output" return guarded return result def render_card(result: dict[str, Any]) -> str: esc = {key: html.escape(str(value)) for key, value in result.items()} score = max(0, min(100, int(result.get("cringe_score", 0)))) useful_advice = clean_text(result.get("useful_advice", ""), 280) show_advice = bool(result.get("include_advice", True)) and bool(useful_advice) advice_tile = ( f"""
A USEFUL SLAP
{html.escape(useful_advice)}
""".rstrip() if show_advice else "" ) grid_class = "trollsona-grid" if show_advice else "trollsona-grid trollsona-grid-single" return f"""
THE SUMMONED MENACE

{esc["trollsona_name"]}

{esc["troll_reply"]}
{advice_tile}
GOBLIN METER
{score}/100 - {esc["cringe_score_label"]}
""".strip() def render_cursed_paperwork(result: dict[str, Any]) -> str: source = clean_text(result.get("source", "unknown"), 80) runtime = clean_text(result.get("runtime", "runtime unavailable"), 260) fallback_reason = clean_text(result.get("fallback_reason", ""), 180) if not fallback_reason: fallback_reason = "No fallback note." return ( f"**Source:** `{source}` \n" f"**Runtime:** `{runtime}` \n" f"**Fallback note:** {fallback_reason}" ) def render_empty_card() -> str: return """
The dossier is sealed

No menace has signed the paperwork yet.

Feed the booth a little lore, pick a resident menace, and pull the handle.

""".strip() def load_preset(index: int) -> tuple[str, str, str, int, bool]: return PRESET_DOSSIERS[index]["values"] def generate_trollsona( user_name: str, profile: str, persona: str, spice: int, include_advice: bool, ) -> tuple[str, dict[str, Any], str]: user_name = clean_text(user_name, MAX_NAME_CHARS) profile = clean_text(profile, MAX_PROFILE_CHARS) persona = persona if persona in PERSONA_STYLES else "Forest Heckler" spice = clamp_spice(spice) include_advice = bool(include_advice) fallback = fallback_trollsona( user_name=user_name, profile=profile, persona=persona, spice=spice, include_advice=include_advice, reason="model unavailable or output invalid", ) score = compute_cringe_score(profile, persona, spice) prompt = build_prompt(user_name, profile, persona, spice, include_advice, score) raw_text, model_status, runtime = generate_with_model(prompt, spice) model_fallback_reason = "" if model_status == "model loaded" else model_status result = None if raw_text: result = parse_model_output( raw_text=raw_text, fallback=fallback, score=score, include_advice=include_advice, fallback_reason=model_fallback_reason, runtime=runtime, ) if result is None: result = repair_model_output(raw_text, fallback, model_fallback_reason, runtime) if result is None: result = dict(fallback) result["fallback_reason"] = model_status result["runtime"] = runtime result = safety_guard(result, fallback) return render_card(result), result, render_cursed_paperwork(result) def build_demo() -> Any: import gradio as gr css = "" css_path = os.path.join(os.path.dirname(__file__), "assets", "style.css") if os.path.exists(css_path): with open(css_path, "r", encoding="utf-8") as handle: css = handle.read() with gr.Blocks(title=APP_TITLE, css=css) as demo: gr.HTML( f"""
Trollsona

{APP_TITLE}

{APP_SUBTITLE}

Build Small Hackathon Small model Safe grotesque humor {TRACK_NAME}
""".strip() ) with gr.Row(elem_classes=["ritual-layout"]): with gr.Column(scale=1, elem_classes=["summoning-panel"]): gr.HTML('
The summoning booth
') user_name = gr.Textbox( label="What do they call you?", placeholder="Mira", max_lines=1, ) profile = gr.Textbox( label="Confess your little lore", placeholder="I overbuild side projects, drink too much coffee, and love weird UI.", lines=5, max_lines=7, ) persona = gr.Dropdown( label="Pick your resident menace", choices=list(PERSONA_STYLES.keys()), value="Back-Alley Oracle", ) spice = gr.Slider( label="How hard should it sting?", minimum=1, maximum=5, value=3, step=1, ) include_advice = gr.Checkbox(label="Slip in one useful truth", value=True) generate_button = gr.Button("Summon Trollsona", variant="primary") with gr.Column(scale=1, elem_classes=["dossier-stage"]): card_output = gr.HTML(value=render_empty_card()) debug_state = gr.State() with gr.Accordion("See the cursed paperwork", open=False): debug_output = gr.Markdown( value=( "**Source:** `not summoned` \n" "**Runtime:** `not summoned` \n" "**Fallback note:** The dossier clerk is still asleep." ) ) generate_button.click( fn=generate_trollsona, inputs=[user_name, profile, persona, spice, include_advice], outputs=[card_output, debug_state, debug_output], ) with gr.Accordion("Stolen dossiers", open=False): gr.HTML('
Tap a stolen dossier to pre-fill the booth.
') with gr.Row(elem_classes=["preset-row"]): for preset_index, preset in enumerate(PRESET_DOSSIERS): preset_button = gr.Button( preset["button"], variant="secondary", elem_classes=["preset-card"], ) preset_button.click( fn=lambda index=preset_index: load_preset(index), inputs=[], outputs=[user_name, profile, persona, spice, include_advice], ) return demo demo = None if parse_bool_env("TROLLSONA_SKIP_UI_BUILD", default=False) else build_demo() if __name__ == "__main__": (demo or build_demo()).launch()