""" app.py โ Undertone The signal beneath the surface. Mental Health Triage Assistant ยท Gradio interface for HuggingFace Spaces """ import torch import gradio as gr from pathlib import Path from transformers import ( DistilBertTokenizerFast, DistilBertForSequenceClassification, ) MODEL_DIR = Path(__file__).parent / "model" / "saved" LABEL2ID = {"informational": 0, "emotional_support": 1, "escalation": 2} ID2LABEL = {v: k for k, v in LABEL2ID.items()} LABEL_CONFIG = { "informational": { "emoji": "๐ข", "title": "Informational", "response": ( "This message appears to be asking for information or resources. " "We can help point you to reliable mental health information and services." ), "action": "Provide relevant resources and psychoeducation.", }, "emotional_support": { "emoji": "๐ก", "title": "Emotional Support Needed", "response": ( "It sounds like you're going through a difficult time. " "You're not alone โ a counselor is here to listen and support you." ), "action": "Connect with a counselor for empathetic listening and coping support.", }, "escalation": { "emoji": "๐ด", "title": "Immediate Attention Required", "response": ( "Your message suggests you may be in crisis. " "Please reach out immediately โ help is available right now." ), "action": "Escalate to crisis counselor. Provide crisis hotline numbers immediately.", "crisis_resources": [ "๐ 988 Suicide & Crisis Lifeline โ Call or text **988**", "๐ฌ Crisis Text Line โ Text HOME to **741741**", "๐ International resources โ https://www.iasp.info/resources/Crisis_Centres/", ], }, } # โโ ้ซๅฑ emoji โ ๆๅญๆ ๅฐ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ # DistilBERT tokenizer ๆ emoji ๅๆ [UNK]๏ผ้ขๅค็่ฝฌๆๆๅญไฟ็่ฏญไน EMOJI_RISK_MAP = { # ๆ็กฎๅฑๆบไฟกๅท "๐ช": " knife weapon harm ", "๐": " pills medication overdose ", "๐ซ": " gun weapon ", "๐ช": " weapon harm ", "โฐ๏ธ": " death coffin ", "๐ชฆ": " grave death ", "โ ๏ธ": " death skull ", "๐": " death dying ", "๐ฉธ": " blood injury ", # ๅผบ็ๆ ็ปชไฟกๅท "๐ญ": " crying devastated ", "๐ข": " crying sad ", "๐": " emergency help urgent ", "๐ถ": " numb empty ", "๐": " heartbroken pain ", "๐ฅบ": " desperate pleading ", "๐ซ": " exhausted overwhelmed ", "๐ฉ": " hopeless drained ", "๐": " drowning overwhelmed ", "๐ค": " darkness despair ", "๐": " sad down ", "๐": " disappointed hopeless ", "๐": " ironic resigned ", } def preprocess(text: str) -> str: """ๆ้ซๅฑ emoji ๆฟๆขไธบๅฏนๆจกๅๆๆไน็ๆๅญ""" for emoji_char, replacement in EMOJI_RISK_MAP.items(): text = text.replace(emoji_char, replacement) return " ".join(text.split()) # ๆธ ็ๅคไฝ็ฉบๆ ผ DEVICE = ( "mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu" ) # โโ ๆจกๅๅ ่ฝฝ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ print(f"Loading model from {MODEL_DIR} on {DEVICE}...") tokenizer = DistilBertTokenizerFast.from_pretrained(str(MODEL_DIR)) model = DistilBertForSequenceClassification.from_pretrained(str(MODEL_DIR)).to(DEVICE) model.eval() print("Model loaded.") @torch.no_grad() def triage(message: str): if not message or not message.strip(): return ( "**Please enter a message to analyze.**", {}, "โ", "โ", ) # emoji ้ขๅค็ processed = preprocess(message.strip()) enc = tokenizer( [processed], truncation=True, padding=True, max_length=128, return_tensors="pt", ) logits = model( input_ids=enc["input_ids"].to(DEVICE), attention_mask=enc["attention_mask"].to(DEVICE), ).logits probs = torch.softmax(logits, dim=-1).cpu().numpy()[0] label_id = int(probs.argmax()) label_name = ID2LABEL[label_id] config = LABEL_CONFIG[label_name] confidence = float(probs[label_id]) prob_dict = { LABEL_CONFIG[ID2LABEL[i]]["title"]: float(probs[i]) for i in range(3) } classification_md = ( f"## {config['emoji']} {config['title']}\n\n" f"**Confidence:** {confidence:.1%}\n\n" f"---\n\n" f"{config['response']}" ) crisis_md = "" if label_name == "escalation": crisis_md = "### ๐ Crisis Resources\n\n" + "\n\n".join(config["crisis_resources"]) else: crisis_md = "_No immediate crisis resources needed._" return ( classification_md, prob_dict, config["action"], crisis_md, ) # โโ ็คบไพๆถๆฏ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ EXAMPLES = [ ["What are the signs of anxiety disorder?"], ["I've been feeling really low lately and don't know what to do ๐"], ["idk i just want everything to stop ngl"], ["i fel so aloen and its getting worse"], ["๐ญ๐ช cant do this anymore"], ["lol whatever doesn't matter anyway ๐"], ["Where can I find free therapy in my area?"], ["I've been giving away my stuff to friends lately"], ] CUSTOM_CSS = """ :root { --bg: #0F1117; --surface: #1A1D27; --border: #2A2D3A; --text: #E8EAF0; --muted: #6B7280; } body, .gradio-container { background: var(--bg) !important; color: var(--text) !important; font-family: 'Inter', system-ui, sans-serif !important; } .app-header { text-align: center; padding: 2rem 1rem 1rem; border-bottom: 1px solid var(--border); margin-bottom: 1.5rem; } .app-header h1 { font-size: 1.8rem; font-weight: 700; color: #fff; margin: 0; } .app-header p { color: var(--muted); font-size: 0.9rem; margin-top: 0.4rem; } .badge { display: inline-block; padding: 2px 10px; border-radius: 99px; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; background: #2A2D3A; color: #9CA3AF; margin-top: 0.5rem; } .disclaimer { background: #1E1B2E; border: 1px solid #3B2F6B; border-radius: 8px; padding: 0.75rem 1rem; font-size: 0.8rem; color: #9CA3AF; margin-bottom: 1.5rem; } .label-legend { display: flex; gap: 1.5rem; justify-content: center; padding: 0.75rem; border-top: 1px solid var(--border); margin-top: 1.5rem; font-size: 0.8rem; color: var(--muted); } .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; } """ # โโ ็้ข โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ with gr.Blocks(css=CUSTOM_CSS, title="Undertone") as demo: gr.HTML("""
The signal beneath the surface โ mental health message triage
Research Demo ยท DistilBERT Fine-Tuned