| import gradio as gr |
| from transformers import pipeline |
| import time |
|
|
| |
| |
| |
| MODEL_NAME = "ENTUM-AI/roberta-clickbait-classifier" |
|
|
| print(f"Loading model: {MODEL_NAME}...") |
| try: |
| classifier = pipeline("text-classification", model=MODEL_NAME) |
| print("Model loaded successfully!") |
| except Exception as e: |
| print(f"Error loading model: {e}") |
| classifier = None |
|
|
|
|
| |
| |
| |
| def predict_single(text): |
| """Classify a single headline.""" |
| if not text or not text.strip(): |
| return create_empty_result() |
|
|
| if classifier is None: |
| return create_error_result() |
|
|
| start = time.time() |
| result = classifier(text.strip())[0] |
| elapsed = (time.time() - start) * 1000 |
|
|
| label = result["label"] |
| score = result["score"] |
| is_clickbait = label == "Clickbait" |
|
|
| return create_result_html(text.strip(), is_clickbait, score, elapsed) |
|
|
|
|
| def predict_batch(texts): |
| """Classify multiple headlines (one per line).""" |
| if not texts or not texts.strip(): |
| return "<p style='color:#94a3b8; text-align:center;'>Enter headlines, one per line.</p>" |
|
|
| if classifier is None: |
| return create_error_result() |
|
|
| lines = [line.strip() for line in texts.strip().split("\n") if line.strip()] |
| if not lines: |
| return "<p style='color:#94a3b8; text-align:center;'>No valid headlines found.</p>" |
|
|
| start = time.time() |
| results = classifier(lines) |
| elapsed = (time.time() - start) * 1000 |
|
|
| html_parts = [] |
| clickbait_count = 0 |
| for text, res in zip(lines, results): |
| is_clickbait = res["label"] == "Clickbait" |
| score = res["score"] |
| if is_clickbait: |
| clickbait_count += 1 |
|
|
| color = "#dc2626" if is_clickbait else "#16a34a" |
| bg = "#fef2f2" if is_clickbait else "#f0fdf4" |
| icon = "π¨" if is_clickbait else "β
" |
| label_text = "CLICKBAIT" if is_clickbait else "LEGIT" |
| bar_width = int(score * 100) |
|
|
| html_parts.append(f""" |
| <div style=" |
| background: {bg}; |
| border: 1px solid {color}22; |
| border-left: 4px solid {color}; |
| border-radius: 12px; |
| padding: 16px 20px; |
| margin-bottom: 10px; |
| "> |
| <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;"> |
| <span style="color:#1e293b; font-size:14px; flex:1; margin-right:12px;">{icon} {text}</span> |
| <span style=" |
| background: {color}15; |
| color: {color}; |
| padding: 4px 12px; |
| border-radius: 20px; |
| font-size: 12px; |
| font-weight: 700; |
| letter-spacing: 0.5px; |
| white-space: nowrap; |
| ">{label_text} {score:.0%}</span> |
| </div> |
| <div style="background:#e2e8f0; border-radius:6px; height:6px; overflow:hidden;"> |
| <div style="width:{bar_width}%; height:100%; background:linear-gradient(90deg, {color}aa, {color}); border-radius:6px;"></div> |
| </div> |
| </div> |
| """) |
|
|
| summary_color = "#dc2626" if clickbait_count > len(lines) / 2 else "#16a34a" |
| summary_bg = "#fef2f2" if clickbait_count > len(lines) / 2 else "#f0fdf4" |
| summary = f""" |
| <div style=" |
| background: {summary_bg}; |
| border: 1px solid {summary_color}22; |
| border-radius: 14px; |
| padding: 18px 24px; |
| margin-bottom: 16px; |
| text-align: center; |
| "> |
| <span style="color:#64748b; font-size:12px; text-transform:uppercase; letter-spacing:1px;">Batch Analysis</span> |
| <div style="color:#0f172a; font-size:24px; font-weight:800; margin:6px 0;"> |
| {clickbait_count} / {len(lines)} Clickbait |
| </div> |
| <span style="color:#64748b; font-size:13px;">Processed in {elapsed:.0f}ms</span> |
| </div> |
| """ |
|
|
| return summary + "\n".join(html_parts) |
|
|
|
|
| |
| |
| |
| def create_result_html(text, is_clickbait, score, elapsed_ms): |
| if is_clickbait: |
| main_color = "#dc2626" |
| gradient = "linear-gradient(135deg, #fee2e2, #fecaca, #fca5a5)" |
| icon = "π¨" |
| label = "CLICKBAIT DETECTED" |
| subtitle = "This headline uses manipulative patterns to attract clicks." |
| text_color = "#991b1b" |
| else: |
| main_color = "#16a34a" |
| gradient = "linear-gradient(135deg, #dcfce7, #bbf7d0, #86efac)" |
| icon = "β
" |
| label = "LEGITIMATE NEWS" |
| subtitle = "This headline appears to be genuine and informative." |
| text_color = "#166534" |
|
|
| confidence_pct = int(score * 100) |
|
|
| return f""" |
| <div style="font-family: 'Inter', 'Segoe UI', sans-serif;"> |
| <div style=" |
| background: {gradient}; |
| border-radius: 20px; |
| padding: 32px; |
| text-align: center; |
| margin-bottom: 20px; |
| border: 1px solid {main_color}22; |
| box-shadow: 0 4px 24px {main_color}15; |
| "> |
| <div style="font-size: 48px; margin-bottom: 8px;">{icon}</div> |
| <div style=" |
| color: {text_color}; |
| font-size: 22px; |
| font-weight: 800; |
| letter-spacing: 2px; |
| margin-bottom: 6px; |
| ">{label}</div> |
| <div style="color: {text_color}99; font-size: 14px;">{subtitle}</div> |
| </div> |
| |
| <div style=" |
| background: #ffffff; |
| border: 1px solid #e2e8f0; |
| border-radius: 16px; |
| padding: 24px; |
| box-shadow: 0 1px 3px rgba(0,0,0,0.06); |
| "> |
| <div style="margin-bottom: 16px;"> |
| <span style="color: #64748b; font-size: 11px; text-transform: uppercase; letter-spacing: 1px;">Analyzed Headline</span> |
| <div style="color: #1e293b; font-size: 15px; margin-top: 6px; font-style: italic;">"{text}"</div> |
| </div> |
| |
| <div style="margin-bottom: 16px;"> |
| <div style="display: flex; justify-content: space-between; margin-bottom: 6px;"> |
| <span style="color: #64748b; font-size: 12px; text-transform: uppercase; letter-spacing: 1px;">Confidence</span> |
| <span style="color: {main_color}; font-weight: 700; font-size: 18px;">{confidence_pct}%</span> |
| </div> |
| <div style="background: #f1f5f9; border-radius: 8px; height: 10px; overflow: hidden;"> |
| <div style=" |
| width: {confidence_pct}%; |
| height: 100%; |
| background: linear-gradient(90deg, {main_color}aa, {main_color}); |
| border-radius: 8px; |
| "></div> |
| </div> |
| </div> |
| |
| <div style=" |
| display: flex; |
| justify-content: center; |
| gap: 24px; |
| padding-top: 12px; |
| border-top: 1px solid #f1f5f9; |
| "> |
| <div style="text-align: center;"> |
| <span style="color: #94a3b8; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px;">Model</span> |
| <div style="color: #6366f1; font-size: 13px; font-weight: 600; margin-top: 2px;">RoBERTa</div> |
| </div> |
| <div style="text-align: center;"> |
| <span style="color: #94a3b8; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px;">Latency</span> |
| <div style="color: #0891b2; font-size: 13px; font-weight: 600; margin-top: 2px;">{elapsed_ms:.0f}ms</div> |
| </div> |
| <div style="text-align: center;"> |
| <span style="color: #94a3b8; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px;">Tokens</span> |
| <div style="color: #d97706; font-size: 13px; font-weight: 600; margin-top: 2px;">β€128</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| """ |
|
|
|
|
| def create_empty_result(): |
| return """ |
| <div style=" |
| text-align: center; |
| padding: 60px 24px; |
| color: #94a3b8; |
| "> |
| <div style="font-size: 48px; margin-bottom: 12px;">π</div> |
| <div style="font-size: 16px; font-weight: 600; color: #475569;">Awaiting Input</div> |
| <div style="font-size: 13px; margin-top: 4px;">Enter a headline above and click <b>Analyze</b></div> |
| </div> |
| """ |
|
|
|
|
| def create_error_result(): |
| return """ |
| <div style=" |
| text-align: center; |
| padding: 40px 24px; |
| background: #fef2f2; |
| border-radius: 16px; |
| border: 1px solid #fecaca; |
| "> |
| <div style="font-size: 36px; margin-bottom: 8px;">β οΈ</div> |
| <div style="color: #dc2626; font-size: 15px; font-weight: 600;">Model Not Available</div> |
| <div style="color: #64748b; font-size: 13px; margin-top: 4px;">Please wait while the model loads or try refreshing.</div> |
| </div> |
| """ |
|
|
|
|
| |
| |
| |
| CUSTOM_CSS = """ |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap'); |
| |
| * { font-family: 'Inter', 'Segoe UI', sans-serif !important; } |
| |
| .gradio-container { |
| max-width: 960px !important; |
| margin: 0 auto !important; |
| background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 50%, #e2e8f0 100%) !important; |
| } |
| |
| .main-header { |
| text-align: center; |
| padding: 40px 20px 20px; |
| } |
| |
| .main-header h1 { |
| background: linear-gradient(135deg, #6366f1, #8b5cf6, #a855f7); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| font-size: 2.5rem !important; |
| font-weight: 800 !important; |
| margin-bottom: 8px !important; |
| letter-spacing: -0.5px; |
| } |
| |
| .main-header p { |
| color: #64748b !important; |
| font-size: 15px !important; |
| } |
| |
| .model-badge { |
| display: inline-block; |
| background: linear-gradient(135deg, #ede9fe, #e0e7ff); |
| border: 1px solid #c7d2fe; |
| color: #4f46e5 !important; |
| padding: 6px 16px; |
| border-radius: 24px; |
| font-size: 13px !important; |
| font-weight: 600; |
| letter-spacing: 0.5px; |
| margin-top: 12px; |
| } |
| |
| footer { display: none !important; } |
| |
| .tab-nav button { |
| color: #64748b !important; |
| font-weight: 600 !important; |
| font-size: 14px !important; |
| } |
| |
| .tab-nav button.selected { |
| color: #6366f1 !important; |
| border-color: #6366f1 !important; |
| } |
| """ |
|
|
|
|
| |
| |
| |
| with gr.Blocks( |
| css=CUSTOM_CSS, |
| title="RoBERTa Clickbait Detector", |
| theme=gr.themes.Soft( |
| primary_hue="indigo", |
| secondary_hue="violet", |
| neutral_hue="slate", |
| ), |
| ) as demo: |
|
|
| |
| gr.HTML(""" |
| <div class="main-header"> |
| <h1>π― Clickbait Detector</h1> |
| <p>AI-powered headline analysis built on <b>RoBERTa</b> (125M parameters)</p> |
| <span class="model-badge">π§ ENTUM-AI / roberta-clickbait-classifier</span> |
| </div> |
| """) |
|
|
| with gr.Tabs(): |
| |
| with gr.Tab("π Single Analysis"): |
| with gr.Row(): |
| with gr.Column(scale=3): |
| single_input = gr.Textbox( |
| label="Headline", |
| placeholder="e.g. You Won't BELIEVE What This Celebrity Did Next!", |
| lines=2, |
| max_lines=3, |
| ) |
| single_btn = gr.Button("β‘ Analyze", variant="primary", size="lg") |
| with gr.Column(scale=4): |
| single_output = gr.HTML(value=create_empty_result()) |
|
|
| gr.Examples( |
| examples=[ |
| ["You Won't BELIEVE What This Celebrity Did Next! π±"], |
| ["Federal Reserve raises interest rates by 0.25 percentage points"], |
| ["10 Shocking Secrets Your Doctor Doesn't Want You to Know"], |
| ["Apple reports Q3 revenue of $81.4 billion, up 2% year over year"], |
| ["This Simple Trick Will Make You a Millionaire Overnight!"], |
| ["The European Central Bank holds interest rates unchanged at 4.5%"], |
| ["SHOCKING: She walked into the room and what happened next changed everything"], |
| ["NASA successfully launches Artemis II mission to lunar orbit"], |
| ], |
| inputs=single_input, |
| label="π Try these examples", |
| ) |
|
|
| single_btn.click(fn=predict_single, inputs=single_input, outputs=single_output) |
| single_input.submit(fn=predict_single, inputs=single_input, outputs=single_output) |
|
|
| |
| with gr.Tab("π Batch Analysis"): |
| gr.Markdown("Paste multiple headlines β **one per line** β for batch classification.") |
| with gr.Row(): |
| with gr.Column(scale=2): |
| batch_input = gr.Textbox( |
| label="Headlines (one per line)", |
| placeholder="Headline 1\nHeadline 2\nHeadline 3", |
| lines=8, |
| max_lines=20, |
| ) |
| batch_btn = gr.Button("β‘ Analyze All", variant="primary", size="lg") |
| with gr.Column(scale=3): |
| batch_output = gr.HTML( |
| value="<p style='color:#94a3b8; text-align:center; padding:40px;'>Results will appear here.</p>" |
| ) |
|
|
| batch_btn.click(fn=predict_batch, inputs=batch_input, outputs=batch_output) |
|
|
| |
| with gr.Tab("βΉοΈ About"): |
| gr.HTML(""" |
| <div style=" |
| background: #ffffff; |
| border: 1px solid #e2e8f0; |
| border-radius: 20px; |
| padding: 36px; |
| color: #1e293b; |
| box-shadow: 0 1px 3px rgba(0,0,0,0.06); |
| "> |
| <h2 style=" |
| background: linear-gradient(135deg, #6366f1, #8b5cf6); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| font-size: 24px; |
| margin-bottom: 24px; |
| ">About This Model</h2> |
| |
| <table style="width:100%; border-collapse:separate; border-spacing:0 8px;"> |
| <tr> |
| <td style="color:#64748b; padding:8px 16px; font-size:13px; width:35%;">Architecture</td> |
| <td style="color:#1e293b; padding:8px 16px; font-size:14px; font-weight:600;">RoBERTa-base (125M parameters)</td> |
| </tr> |
| <tr> |
| <td style="color:#64748b; padding:8px 16px; font-size:13px;">Task</td> |
| <td style="color:#1e293b; padding:8px 16px; font-size:14px; font-weight:600;">Binary Classification (Clickbait / Non-Clickbait)</td> |
| </tr> |
| <tr> |
| <td style="color:#64748b; padding:8px 16px; font-size:13px;">Language</td> |
| <td style="color:#1e293b; padding:8px 16px; font-size:14px; font-weight:600;">English</td> |
| </tr> |
| <tr> |
| <td style="color:#64748b; padding:8px 16px; font-size:13px;">Max Input</td> |
| <td style="color:#1e293b; padding:8px 16px; font-size:14px; font-weight:600;">128 tokens</td> |
| </tr> |
| <tr> |
| <td style="color:#64748b; padding:8px 16px; font-size:13px;">License</td> |
| <td style="color:#1e293b; padding:8px 16px; font-size:14px; font-weight:600;">Apache 2.0</td> |
| </tr> |
| </table> |
| |
| <h3 style="color:#6366f1; margin-top:28px; margin-bottom:12px; font-size:16px;">π Training Data</h3> |
| <p style="color:#475569; font-size:13px; line-height:1.7;"> |
| Trained on ~48K samples from three combined & deduplicated English datasets: |
| <b style="color:#4f46e5;">christinacdl/Clickbait_New</b>, |
| <b style="color:#4f46e5;">marksverdhei/clickbait_title_classification</b>, and |
| <b style="color:#4f46e5;">contemmcm/clickbait</b>. |
| </p> |
| |
| <h3 style="color:#6366f1; margin-top:28px; margin-bottom:12px; font-size:16px;">π Python API</h3> |
| <pre style=" |
| background:#f8fafc; |
| border:1px solid #e2e8f0; |
| border-radius:12px; |
| padding:20px; |
| color:#1e293b; |
| font-size:13px; |
| overflow-x:auto; |
| font-family: 'Fira Code', 'Cascadia Code', monospace !important; |
| "><span style="color:#6366f1">from</span> transformers <span style="color:#6366f1">import</span> pipeline |
| |
| classifier = pipeline(<span style="color:#d97706">"text-classification"</span>, |
| model=<span style="color:#d97706">"ENTUM-AI/roberta-clickbait-classifier"</span>) |
| |
| result = classifier(<span style="color:#d97706">"You Won't BELIEVE What Happened!"</span>) |
| <span style="color:#94a3b8"># [{'label': 'Clickbait', 'score': 0.99}]</span></pre> |
| |
| <h3 style="color:#6366f1; margin-top:28px; margin-bottom:12px; font-size:16px;">π‘ Use Cases</h3> |
| <div style="display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-top:12px;"> |
| <div style="background:#f8fafc; border:1px solid #e2e8f0; padding:14px; border-radius:10px; font-size:13px;"> |
| <span style="font-size:18px;">π°</span><br> |
| <b style="color:#1e293b;">News Aggregators</b><br> |
| <span style="color:#64748b;">Filter low-quality clickbait articles</span> |
| </div> |
| <div style="background:#f8fafc; border:1px solid #e2e8f0; padding:14px; border-radius:10px; font-size:13px;"> |
| <span style="font-size:18px;">π</span><br> |
| <b style="color:#1e293b;">Social Media</b><br> |
| <span style="color:#64748b;">Content moderation & feed quality</span> |
| </div> |
| <div style="background:#f8fafc; border:1px solid #e2e8f0; padding:14px; border-radius:10px; font-size:13px;"> |
| <span style="font-size:18px;">π</span><br> |
| <b style="color:#1e293b;">Browser Extensions</b><br> |
| <span style="color:#64748b;">Warn users about misleading headlines</span> |
| </div> |
| <div style="background:#f8fafc; border:1px solid #e2e8f0; padding:14px; border-radius:10px; font-size:13px;"> |
| <span style="font-size:18px;">π§</span><br> |
| <b style="color:#1e293b;">Email Filters</b><br> |
| <span style="color:#64748b;">Detect clickbait-style subject lines</span> |
| </div> |
| </div> |
| </div> |
| """) |
|
|
|
|
| |
| demo.launch() |
|
|