Spaces:
Sleeping
Sleeping
| import os | |
| import onnx | |
| import gradio as gr | |
| # ---------------- FIX ONNX LOCALE ISSUE ---------------- # | |
| import onnx | |
| def fix_onnx_locale_issue(model_path): | |
| fixed_path = model_path.replace(".onnx", "_fixed.onnx") | |
| if os.path.exists(fixed_path): | |
| return fixed_path | |
| model = onnx.load(model_path) | |
| nodes = model.graph.node | |
| new_nodes = [] | |
| replace_map = {} | |
| # Step 1: find StringNormalizer | |
| for node in nodes: | |
| if node.op_type == "StringNormalizer": | |
| input_name = node.input[0] | |
| output_name = node.output[0] | |
| # map normalized → original input | |
| replace_map[output_name] = input_name | |
| else: | |
| new_nodes.append(node) | |
| # Step 2: fix inputs of remaining nodes | |
| for node in new_nodes: | |
| for i, inp in enumerate(node.input): | |
| if inp in replace_map: | |
| node.input[i] = replace_map[inp] | |
| # Step 3: replace graph nodes | |
| model.graph.ClearField("node") | |
| model.graph.node.extend(new_nodes) | |
| onnx.save(model, fixed_path) | |
| return fixed_path | |
| # ---------------- PATCH MODEL BEFORE IMPORT ---------------- # | |
| MODEL_DIR = "models" | |
| binary_raw = os.path.join(MODEL_DIR, "binary_model.onnx") | |
| category_raw = os.path.join(MODEL_DIR, "category_model.onnx") | |
| # Fix models BEFORE model.py loads them | |
| binary_fixed = fix_onnx_locale_issue(binary_raw) | |
| category_fixed = fix_onnx_locale_issue(category_raw) | |
| # Trick: overwrite original paths so model.py uses fixed ones | |
| os.replace(binary_fixed, binary_raw) | |
| os.replace(category_fixed, category_raw) | |
| # ---------------- IMPORT MODEL ---------------- # | |
| from model import run_model | |
| # ---------------- UI LOGIC ---------------- # | |
| CATEGORIES = { | |
| "phishing": "Phishing", | |
| "job_scam": "Job Scam", | |
| "crypto": "Cryptocurrency Scam", | |
| "adult": "Adult Content", | |
| "giveaway": "Giveaway Scam", | |
| "marketing": "Marketing", | |
| "spam": "Spam", | |
| "normal": "Normal", | |
| } | |
| CATEGORY_ICONS = { | |
| "phishing": "⚠", | |
| "job_scam": "⚠", | |
| "crypto": "⚠", | |
| "adult": "⚠", | |
| "giveaway": "⚠", | |
| "marketing": "◈", | |
| "spam": "⚠", | |
| "normal": "✓", | |
| } | |
| def detect_spam(text): | |
| if not text or not text.strip(): | |
| return "—", "—", 0.0, {}, "Awaiting input" | |
| try: | |
| output = run_model(text) | |
| if not output["ok"]: | |
| return output["error"], "—", 0.0, {}, "Error" | |
| pred = output["result"] | |
| is_spam = pred["is_spam"] | |
| confidence = pred["confidence"] | |
| category = pred["category"] | |
| result_label = "SPAM DETECTED" if is_spam else "CLEAN" | |
| category_label = CATEGORIES.get(category, category) | |
| details = { | |
| "category": category, | |
| "confidence": confidence, | |
| "threshold": pred.get("threshold_used"), | |
| } | |
| summary = f"{'SPAM' if is_spam else 'HAM'} · {category_label} · {confidence:.4f} confidence" | |
| return result_label, category_label, confidence, details, summary | |
| except Exception as e: | |
| return f"Error: {str(e)}", "—", 0.0, {"error": str(e)}, "Crash" | |
| # ================================================================ | |
| # CUSTOM CSS — full dark security dashboard aesthetic | |
| # Fonts: JetBrains Mono (data) + DM Sans (UI) | |
| # ================================================================ | |
| CSS = """ | |
| @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=DM+Sans:wght@300;400;500;600&display=swap'); | |
| /* ── DESIGN TOKENS ─────────────────────────────────────────── */ | |
| :root { | |
| --ss-bg: #080C12; | |
| --ss-surface-1: #0D1320; | |
| --ss-surface-2: #111926; | |
| --ss-border: rgba(255,255,255,0.055); | |
| --ss-border-em: rgba(255,255,255,0.12); | |
| --ss-safe: #0CF2A0; | |
| --ss-safe-dim: rgba(12,242,160,0.12); | |
| --ss-safe-glow: rgba(12,242,160,0.18); | |
| --ss-danger: #FF3D5A; | |
| --ss-danger-dim: rgba(255,61,90,0.12); | |
| --ss-info: #4D9FFF; | |
| --ss-info-dim: rgba(77,159,255,0.10); | |
| --ss-warn: #FFAA00; | |
| --ss-text-1: #EAF0FF; | |
| --ss-text-2: #8090AA; | |
| --ss-text-3: #4A5568; | |
| --ss-mono: 'JetBrains Mono', 'Fira Code', monospace; | |
| --ss-sans: 'DM Sans', system-ui, sans-serif; | |
| --ss-radius-sm: 6px; | |
| --ss-radius-md: 10px; | |
| --ss-radius-lg: 14px; | |
| --ss-transition: 0.18s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| /* ── GLOBAL RESETS ─────────────────────────────────────────── */ | |
| *, *::before, *::after { box-sizing: border-box; } | |
| body, | |
| .gradio-container, | |
| gradio-app { | |
| background: var(--ss-bg) !important; | |
| font-family: var(--ss-sans) !important; | |
| color: var(--ss-text-1) !important; | |
| min-height: 100vh; | |
| } | |
| .gradio-container { | |
| max-width: 960px !important; | |
| margin: 0 auto !important; | |
| padding: 0 20px 80px !important; | |
| background: | |
| radial-gradient(ellipse 70% 40% at 50% -5%, rgba(77,159,255,0.07) 0%, transparent 65%), | |
| radial-gradient(ellipse 50% 30% at 90% 80%, rgba(12,242,160,0.05) 0%, transparent 55%), | |
| var(--ss-bg) !important; | |
| } | |
| footer, | |
| .footer, | |
| .built-with { display: none !important; } | |
| /* Strip all default Gradio card/panel chrome */ | |
| .gr-box, | |
| .gr-form, | |
| .gap, | |
| .contain, | |
| .panel, | |
| .tabs, | |
| .tabitem, | |
| .block { | |
| background: transparent !important; | |
| border: none !important; | |
| box-shadow: none !important; | |
| padding: 0 !important; | |
| } | |
| /* ── CUSTOM HEADER ─────────────────────────────────────────── */ | |
| #ss-header { | |
| position: relative; | |
| text-align: center; | |
| padding: 52px 0 44px; | |
| overflow: hidden; | |
| } | |
| #ss-header::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: 0; left: 50%; | |
| transform: translateX(-50%); | |
| width: 100%; height: 1px; | |
| background: linear-gradient(90deg, | |
| transparent 0%, | |
| var(--ss-border-em) 25%, | |
| var(--ss-safe) 50%, | |
| var(--ss-border-em) 75%, | |
| transparent 100% | |
| ); | |
| opacity: 0.6; | |
| } | |
| .ss-wordmark { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 14px; | |
| margin-bottom: 12px; | |
| } | |
| .ss-shield { | |
| width: 48px; height: 48px; | |
| display: flex; align-items: center; justify-content: center; | |
| background: var(--ss-safe-dim); | |
| border: 1px solid rgba(12,242,160,0.28); | |
| border-radius: 12px; | |
| font-size: 22px; | |
| animation: ss-pulse 3.5s ease-in-out infinite; | |
| } | |
| @keyframes ss-pulse { | |
| 0%, 100% { box-shadow: 0 0 0 0 rgba(12,242,160,0); } | |
| 50% { box-shadow: 0 0 0 8px rgba(12,242,160,0.06); } | |
| } | |
| .ss-title { | |
| font-family: var(--ss-sans); | |
| font-size: 36px; | |
| font-weight: 600; | |
| letter-spacing: -0.5px; | |
| color: var(--ss-text-1); | |
| margin: 0; | |
| line-height: 1; | |
| } | |
| .ss-title span { | |
| color: var(--ss-safe); | |
| } | |
| .ss-tagline { | |
| font-family: var(--ss-mono); | |
| font-size: 11.5px; | |
| letter-spacing: 0.18em; | |
| text-transform: uppercase; | |
| color: var(--ss-text-2); | |
| margin: 10px 0 18px; | |
| } | |
| .ss-badges { | |
| display: inline-flex; | |
| gap: 10px; | |
| align-items: center; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| } | |
| .ss-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 5px 12px; | |
| background: var(--ss-surface-2); | |
| border: 1px solid var(--ss-border-em); | |
| border-radius: 100px; | |
| font-family: var(--ss-mono); | |
| font-size: 10.5px; | |
| letter-spacing: 0.08em; | |
| color: var(--ss-text-2); | |
| } | |
| .ss-badge.live::before { | |
| content: ''; | |
| display: inline-block; | |
| width: 6px; height: 6px; | |
| border-radius: 50%; | |
| background: var(--ss-safe); | |
| animation: ss-blink 2s ease-in-out infinite; | |
| } | |
| @keyframes ss-blink { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.25; } | |
| } | |
| /* ── SECTION LABELS ────────────────────────────────────────── */ | |
| .ss-section-label { | |
| font-family: var(--ss-mono) !important; | |
| font-size: 10px !important; | |
| letter-spacing: 0.18em !important; | |
| text-transform: uppercase !important; | |
| color: var(--ss-text-3) !important; | |
| margin: 28px 0 10px !important; | |
| display: flex !important; | |
| align-items: center !important; | |
| gap: 10px !important; | |
| } | |
| .ss-section-label::after { | |
| content: ''; | |
| flex: 1; | |
| height: 1px; | |
| background: var(--ss-border); | |
| } | |
| /* ── INPUT CARD ────────────────────────────────────────────── */ | |
| #ss-input-card { | |
| background: var(--ss-surface-1) !important; | |
| border: 1px solid var(--ss-border) !important; | |
| border-radius: var(--ss-radius-lg) !important; | |
| padding: 20px !important; | |
| transition: border-color var(--ss-transition) !important; | |
| } | |
| #ss-input-card:focus-within { | |
| border-color: rgba(77,159,255,0.3) !important; | |
| } | |
| /* ── TEXTBOX ───────────────────────────────────────────────── */ | |
| .ss-textarea textarea, | |
| .ss-textarea input[type="text"] { | |
| background: rgba(0, 8, 18, 0.7) !important; | |
| border: 1px solid rgba(77,159,255,0.14) !important; | |
| border-radius: var(--ss-radius-md) !important; | |
| color: var(--ss-text-1) !important; | |
| font-family: var(--ss-mono) !important; | |
| font-size: 13px !important; | |
| line-height: 1.65 !important; | |
| padding: 14px 16px !important; | |
| resize: none !important; | |
| transition: border-color var(--ss-transition), box-shadow var(--ss-transition) !important; | |
| caret-color: var(--ss-safe) !important; | |
| } | |
| .ss-textarea textarea:focus, | |
| .ss-textarea input[type="text"]:focus { | |
| border-color: rgba(77,159,255,0.45) !important; | |
| box-shadow: 0 0 0 3px rgba(77,159,255,0.07) !important; | |
| outline: none !important; | |
| } | |
| .ss-textarea textarea::placeholder { | |
| color: var(--ss-text-3) !important; | |
| font-style: italic; | |
| } | |
| .ss-textarea label, | |
| .ss-textarea label span { | |
| font-family: var(--ss-mono) !important; | |
| font-size: 10px !important; | |
| letter-spacing: 0.16em !important; | |
| text-transform: uppercase !important; | |
| color: var(--ss-text-3) !important; | |
| margin-bottom: 8px !important; | |
| } | |
| /* ── BUTTONS ───────────────────────────────────────────────── */ | |
| #ss-analyze-btn button { | |
| background: var(--ss-safe-dim) !important; | |
| border: 1px solid rgba(12,242,160,0.35) !important; | |
| border-radius: var(--ss-radius-md) !important; | |
| color: var(--ss-safe) !important; | |
| font-family: var(--ss-mono) !important; | |
| font-size: 12px !important; | |
| font-weight: 700 !important; | |
| letter-spacing: 0.18em !important; | |
| text-transform: uppercase !important; | |
| padding: 13px 28px !important; | |
| width: 100% !important; | |
| cursor: pointer !important; | |
| transition: all var(--ss-transition) !important; | |
| } | |
| #ss-analyze-btn button:hover { | |
| background: rgba(12,242,160,0.2) !important; | |
| box-shadow: 0 0 20px rgba(12,242,160,0.14) !important; | |
| transform: translateY(-1px) !important; | |
| } | |
| #ss-analyze-btn button:active { | |
| transform: translateY(0) !important; | |
| } | |
| #ss-clear-btn button { | |
| background: transparent !important; | |
| border: 1px solid var(--ss-border-em) !important; | |
| border-radius: var(--ss-radius-md) !important; | |
| color: var(--ss-text-3) !important; | |
| font-family: var(--ss-mono) !important; | |
| font-size: 10px !important; | |
| letter-spacing: 0.14em !important; | |
| text-transform: uppercase !important; | |
| padding: 9px !important; | |
| width: 100% !important; | |
| cursor: pointer !important; | |
| transition: all var(--ss-transition) !important; | |
| } | |
| #ss-clear-btn button:hover { | |
| border-color: rgba(255,61,90,0.35) !important; | |
| color: var(--ss-danger) !important; | |
| } | |
| /* ── OUTPUT CARDS ──────────────────────────────────────────── */ | |
| .ss-result-card, | |
| .ss-output-card { | |
| background: var(--ss-surface-1) !important; | |
| border: 1px solid var(--ss-border) !important; | |
| border-radius: var(--ss-radius-lg) !important; | |
| padding: 20px !important; | |
| transition: border-color var(--ss-transition) !important; | |
| } | |
| .ss-result-card:hover, | |
| .ss-output-card:hover { | |
| border-color: var(--ss-border-em) !important; | |
| } | |
| /* Output field text */ | |
| .ss-output-field input, | |
| .ss-output-field textarea { | |
| background: rgba(0, 8, 18, 0.6) !important; | |
| border: 1px solid rgba(77,159,255,0.10) !important; | |
| border-radius: var(--ss-radius-sm) !important; | |
| color: var(--ss-info) !important; | |
| font-family: var(--ss-mono) !important; | |
| font-size: 14px !important; | |
| font-weight: 700 !important; | |
| padding: 12px 14px !important; | |
| transition: all var(--ss-transition) !important; | |
| cursor: default !important; | |
| } | |
| .ss-result-field input, | |
| .ss-result-field textarea { | |
| background: rgba(0, 8, 18, 0.6) !important; | |
| border: 1px solid rgba(12,242,160,0.14) !important; | |
| border-radius: var(--ss-radius-sm) !important; | |
| color: var(--ss-safe) !important; | |
| font-family: var(--ss-mono) !important; | |
| font-size: 15px !important; | |
| font-weight: 700 !important; | |
| padding: 13px 14px !important; | |
| letter-spacing: 0.08em !important; | |
| cursor: default !important; | |
| } | |
| .ss-number-field input[type="number"] { | |
| background: rgba(0, 8, 18, 0.6) !important; | |
| border: 1px solid rgba(77,159,255,0.10) !important; | |
| border-radius: var(--ss-radius-sm) !important; | |
| color: var(--ss-info) !important; | |
| font-family: var(--ss-mono) !important; | |
| font-size: 22px !important; | |
| font-weight: 700 !important; | |
| padding: 12px 14px !important; | |
| text-align: center !important; | |
| cursor: default !important; | |
| } | |
| .ss-summary-field input, | |
| .ss-summary-field textarea { | |
| background: var(--ss-surface-2) !important; | |
| border: 1px solid var(--ss-border-em) !important; | |
| border-radius: var(--ss-radius-sm) !important; | |
| color: var(--ss-text-2) !important; | |
| font-family: var(--ss-mono) !important; | |
| font-size: 12px !important; | |
| letter-spacing: 0.06em !important; | |
| padding: 10px 14px !important; | |
| cursor: default !important; | |
| } | |
| /* All output labels */ | |
| .ss-output-field label span, | |
| .ss-result-field label span, | |
| .ss-number-field label span, | |
| .ss-summary-field label span, | |
| .ss-json-field label span { | |
| font-family: var(--ss-mono) !important; | |
| font-size: 10px !important; | |
| letter-spacing: 0.16em !important; | |
| text-transform: uppercase !important; | |
| color: var(--ss-text-3) !important; | |
| margin-bottom: 6px !important; | |
| } | |
| /* ── JSON COMPONENT ────────────────────────────────────────── */ | |
| .ss-json-field .json-component, | |
| .ss-json-field [class*="json"], | |
| .ss-json-field pre { | |
| background: rgba(0, 8, 18, 0.7) !important; | |
| border: 1px solid rgba(77,159,255,0.10) !important; | |
| border-radius: var(--ss-radius-sm) !important; | |
| font-family: var(--ss-mono) !important; | |
| font-size: 12px !important; | |
| color: var(--ss-text-2) !important; | |
| padding: 12px 14px !important; | |
| } | |
| /* ── EXAMPLES ──────────────────────────────────────────────── */ | |
| .gr-samples, | |
| .gr-samples-table { | |
| background: var(--ss-surface-1) !important; | |
| border: 1px solid var(--ss-border) !important; | |
| border-radius: var(--ss-radius-lg) !important; | |
| overflow: hidden !important; | |
| } | |
| .gr-samples thead { | |
| background: rgba(77,159,255,0.05) !important; | |
| border-bottom: 1px solid var(--ss-border-em) !important; | |
| } | |
| .gr-samples th, | |
| .gr-samples-table th { | |
| font-family: var(--ss-mono) !important; | |
| font-size: 9.5px !important; | |
| letter-spacing: 0.18em !important; | |
| text-transform: uppercase !important; | |
| color: var(--ss-text-3) !important; | |
| padding: 10px 16px !important; | |
| font-weight: 400 !important; | |
| } | |
| .gr-samples td, | |
| .gr-samples-table td { | |
| font-family: var(--ss-mono) !important; | |
| font-size: 12px !important; | |
| color: var(--ss-text-2) !important; | |
| padding: 10px 16px !important; | |
| border-bottom: 1px solid var(--ss-border) !important; | |
| cursor: pointer !important; | |
| transition: all var(--ss-transition) !important; | |
| } | |
| .gr-samples tr:hover td, | |
| .gr-samples-table tr:hover td { | |
| color: var(--ss-text-1) !important; | |
| background: rgba(77,159,255,0.04) !important; | |
| } | |
| .gr-samples tr:last-child td, | |
| .gr-samples-table tr:last-child td { | |
| border-bottom: none !important; | |
| } | |
| /* ── SCROLLBAR ─────────────────────────────────────────────── */ | |
| ::-webkit-scrollbar { width: 4px; height: 4px; } | |
| ::-webkit-scrollbar-track { background: transparent; } | |
| ::-webkit-scrollbar-thumb { background: rgba(77,159,255,0.18); border-radius: 4px; } | |
| ::-webkit-scrollbar-thumb:hover{ background: rgba(77,159,255,0.35); } | |
| /* ── DIVIDER ───────────────────────────────────────────────── */ | |
| #ss-divider { | |
| border: none !important; | |
| border-top: 1px solid var(--ss-border) !important; | |
| margin: 12px 0 !important; | |
| } | |
| /* ── MISC CLEANUP ──────────────────────────────────────────── */ | |
| .hide-border input, | |
| .hide-border textarea { | |
| border: none !important; | |
| box-shadow: none !important; | |
| } | |
| /* Gradio number spinner buttons */ | |
| input[type=number]::-webkit-inner-spin-button, | |
| input[type=number]::-webkit-outer-spin-button { | |
| -webkit-appearance: none; | |
| margin: 0; | |
| } | |
| """ | |
| # ================================================================ | |
| # PAGE LOAD JS | |
| # ================================================================ | |
| PAGE_JS = """ | |
| function() { | |
| // Watch result field and tint it spam-red vs safe-green | |
| const observer = new MutationObserver(() => { | |
| const resultInputs = document.querySelectorAll('.ss-result-field input, .ss-result-field textarea'); | |
| resultInputs.forEach(el => { | |
| const v = (el.value || el.innerText || '').trim().toUpperCase(); | |
| if (v.includes('SPAM')) { | |
| el.style.color = '#FF3D5A'; | |
| el.style.borderColor = 'rgba(255,61,90,0.25)'; | |
| } else if (v.includes('CLEAN')) { | |
| el.style.color = '#0CF2A0'; | |
| el.style.borderColor = 'rgba(12,242,160,0.25)'; | |
| } else { | |
| el.style.color = ''; | |
| el.style.borderColor = ''; | |
| } | |
| }); | |
| }); | |
| observer.observe(document.body, { subtree: true, childList: true, characterData: true }); | |
| } | |
| """ | |
| # ================================================================ | |
| # HEADER HTML | |
| # ================================================================ | |
| HEADER_HTML = """ | |
| <div id="ss-header"> | |
| <div class="ss-wordmark"> | |
| <div class="ss-shield">🛡</div> | |
| <h1 class="ss-title">Spam<span>Shield</span></h1> | |
| </div> | |
| <p class="ss-tagline">Multilingual threat detection · ONNX runtime</p> | |
| <div class="ss-badges"> | |
| <span class="ss-badge live">Model online</span> | |
| <span class="ss-badge">PyTorch · ~3M params</span> | |
| <span class="ss-badge">EN · ES · DE · RU · HI</span> | |
| </div> | |
| </div> | |
| """ | |
| EXAMPLES = [ | |
| "Congratulations! You've won a free iPhone. Click here to claim!", | |
| "Hey! How are you doing? Let's meet tomorrow.", | |
| "Your account has been compromised. Verify now.", | |
| "Earn $5000/week from home. No experience needed.", | |
| "Limited offer! Buy now!", | |
| "Free Bitcoin airdrop!", | |
| ] | |
| # ================================================================ | |
| # BUILD UI | |
| # ================================================================ | |
| with gr.Blocks(css=CSS, js=PAGE_JS, title="SpamShield") as demo: | |
| # ── Header ────────────────────────────────────────────────── | |
| gr.HTML(HEADER_HTML) | |
| # ── Input section ─────────────────────────────────────────── | |
| gr.HTML('<p class="ss-section-label">Input</p>') | |
| with gr.Group(elem_id="ss-input-card"): | |
| text_input = gr.Textbox( | |
| label="Message", | |
| lines=5, | |
| placeholder="Paste or type a message to analyze…", | |
| elem_classes=["ss-textarea"], | |
| show_label=True, | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| analyze_btn = gr.Button( | |
| "⟢ Analyze", | |
| variant="primary", | |
| elem_id="ss-analyze-btn", | |
| ) | |
| with gr.Column(scale=1): | |
| clear_btn = gr.Button( | |
| "Clear", | |
| variant="secondary", | |
| elem_id="ss-clear-btn", | |
| ) | |
| # ── Results section ───────────────────────────────────────── | |
| gr.HTML('<p class="ss-section-label">Detection result</p>') | |
| with gr.Row(): | |
| with gr.Column(scale=1, elem_classes=["ss-result-card"]): | |
| result = gr.Textbox( | |
| label="Verdict", | |
| interactive=False, | |
| elem_classes=["ss-result-field"], | |
| ) | |
| category = gr.Textbox( | |
| label="Category", | |
| interactive=False, | |
| elem_classes=["ss-output-field"], | |
| ) | |
| with gr.Column(scale=1, elem_classes=["ss-output-card"]): | |
| confidence = gr.Number( | |
| label="Confidence score", | |
| interactive=False, | |
| elem_classes=["ss-number-field"], | |
| ) | |
| summary = gr.Textbox( | |
| label="Summary", | |
| interactive=False, | |
| elem_classes=["ss-summary-field"], | |
| ) | |
| details = gr.JSON( | |
| label="Raw output", | |
| elem_classes=["ss-json-field"], | |
| ) | |
| # ── Examples ──────────────────────────────────────────────── | |
| gr.HTML('<p class="ss-section-label">Quick examples</p>') | |
| gr.Examples( | |
| examples=EXAMPLES, | |
| inputs=text_input, | |
| outputs=[result, category, confidence, details, summary], | |
| fn=detect_spam, | |
| ) | |
| # ── Event bindings ─────────────────────────────────────────── | |
| analyze_btn.click( | |
| detect_spam, | |
| inputs=text_input, | |
| outputs=[result, category, confidence, details, summary], | |
| ) | |
| clear_btn.click( | |
| lambda: ("—", "—", 0.0, {}, "Awaiting input"), | |
| None, | |
| [result, category, confidence, details, summary], | |
| ).then( | |
| lambda: "", | |
| None, | |
| text_input, | |
| ) | |
| # ================================================================ | |
| # LAUNCH | |
| # ================================================================ | |
| if __name__ == "__main__": | |
| demo.launch( | |
| theme=gr.themes.Base( | |
| primary_hue="emerald", | |
| secondary_hue="blue", | |
| neutral_hue="slate", | |
| font=[gr.themes.GoogleFont("DM Sans"), "system-ui", "sans-serif"], | |
| font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "monospace"], | |
| ), | |
| ssr_mode=False, | |
| ) |