| import streamlit as st |
| import torch |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification |
| import numpy as np |
| import os |
|
|
| st.set_page_config( |
| page_title="TruthLens β Fake News Detector", |
| page_icon="π", |
| layout="wide", |
| initial_sidebar_state="collapsed" |
| ) |
|
|
| st.markdown(""" |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Outfit:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap'); |
| |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } |
| |
| html, body, [class*="css"], .stApp { |
| background: #080810 !important; |
| color: #e8e8f0 !important; |
| font-family: 'Outfit', sans-serif !important; |
| } |
| |
| .main .block-container { padding: 2rem 2rem 4rem !important; max-width: 900px !important; margin: 0 auto !important; } |
| |
| #MainMenu, footer, header { visibility: hidden; } |
| .stDeployButton { display: none; } |
| |
| h1, h2, h3 { font-family: 'Bebas Neue', sans-serif !important; letter-spacing: 2px !important; } |
| |
| .stTextArea textarea { |
| background: rgba(255,255,255,0.04) !important; |
| border: 1px solid rgba(255,255,255,0.08) !important; |
| border-radius: 14px !important; |
| color: #e8e8f0 !important; |
| font-family: 'Outfit', sans-serif !important; |
| font-size: 15px !important; |
| font-weight: 300 !important; |
| line-height: 1.7 !important; |
| } |
| |
| .stTextArea textarea:focus { |
| border-color: rgba(255,200,0,0.3) !important; |
| box-shadow: none !important; |
| } |
| |
| .stTextArea textarea::placeholder { color: rgba(255,255,255,0.2) !important; } |
| .stTextArea label { display: none !important; } |
| |
| .stButton > button { |
| font-family: 'Bebas Neue', sans-serif !important; |
| letter-spacing: 2px !important; |
| border-radius: 12px !important; |
| border: none !important; |
| transition: all 0.2s !important; |
| } |
| |
| .main-btn .stButton > button { |
| background: #ffc800 !important; |
| color: #080810 !important; |
| width: 100% !important; |
| font-size: 18px !important; |
| padding: 16px !important; |
| } |
| |
| .fake-btn .stButton > button { |
| background: rgba(255,50,80,0.1) !important; |
| color: #ff3250 !important; |
| border: 1px solid rgba(255,50,80,0.2) !important; |
| font-size: 12px !important; |
| width: 100% !important; |
| } |
| |
| .real-btn .stButton > button { |
| background: rgba(0,220,130,0.1) !important; |
| color: #00dc82 !important; |
| border: 1px solid rgba(0,220,130,0.2) !important; |
| font-size: 12px !important; |
| width: 100% !important; |
| } |
| |
| .stButton > button:hover { transform: translateY(-2px) !important; filter: brightness(1.1) !important; } |
| |
| .verdict-fake { |
| background: linear-gradient(135deg, rgba(255,30,60,0.08), rgba(255,30,60,0.03)); |
| border: 1px solid rgba(255,30,60,0.25); |
| border-radius: 20px; padding: 28px; margin: 20px 0; |
| } |
| |
| .verdict-real { |
| background: linear-gradient(135deg, rgba(0,220,130,0.08), rgba(0,220,130,0.03)); |
| border: 1px solid rgba(0,220,130,0.25); |
| border-radius: 20px; padding: 28px; margin: 20px 0; |
| } |
| |
| .verdict-uncertain { |
| background: linear-gradient(135deg, rgba(255,180,0,0.08), rgba(255,180,0,0.03)); |
| border: 1px solid rgba(255,180,0,0.25); |
| border-radius: 20px; padding: 28px; margin: 20px 0; |
| } |
| |
| .big-label { |
| font-family: 'Bebas Neue', sans-serif; |
| font-size: 52px; |
| letter-spacing: 4px; |
| line-height: 1; |
| } |
| |
| .metric-box { |
| background: rgba(255,255,255,0.03); |
| border: 1px solid rgba(255,255,255,0.07); |
| border-radius: 14px; |
| padding: 18px; |
| text-align: center; |
| } |
| |
| .metric-num { |
| font-family: 'Bebas Neue', sans-serif; |
| font-size: 36px; |
| letter-spacing: 1px; |
| line-height: 1; |
| } |
| |
| .metric-lbl { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 9px; |
| color: rgba(255,255,255,0.3); |
| letter-spacing: 2px; |
| text-transform: uppercase; |
| margin-top: 4px; |
| } |
| |
| .signal-box { |
| background: rgba(255,255,255,0.02); |
| border: 1px solid rgba(255,255,255,0.06); |
| border-radius: 14px; |
| padding: 18px; |
| margin-top: 16px; |
| } |
| |
| .sig-row { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: 8px 0; |
| border-bottom: 1px solid rgba(255,255,255,0.04); |
| font-size: 13px; |
| } |
| |
| .sig-row:last-child { border-bottom: none; } |
| |
| .badge-high { color: #ff1e3c; font-family: monospace; font-size: 11px; } |
| .badge-med { color: #ffb400; font-family: monospace; font-size: 11px; } |
| .badge-low { color: #00dc82; font-family: monospace; font-size: 11px; } |
| |
| .footer-txt { |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 11px; |
| color: rgba(255,255,255,0.15); |
| text-align: center; |
| margin-top: 48px; |
| padding-top: 20px; |
| border-top: 1px solid rgba(255,255,255,0.05); |
| } |
| |
| .mono { font-family: 'JetBrains Mono', monospace; } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| |
| @st.cache_resource |
| def load_model(): |
| from transformers import pipeline |
| |
| classifier = pipeline( |
| "text-classification", |
| model="hamzab/roberta-fake-news-classification", |
| device=-1 |
| ) |
| return classifier |
|
|
| classifier = load_model() |
|
|
| |
| def predict(text): |
| result = classifier(text, truncation=True, max_length=512)[0] |
| label_raw = result['label'].upper() |
| score = result['score'] |
|
|
| if 'FAKE' in label_raw or label_raw == 'LABEL_0': |
| label = "FAKE" |
| fake_prob = round(score * 100, 1) |
| real_prob = round((1 - score) * 100, 1) |
| else: |
| label = "REAL" |
| real_prob = round(score * 100, 1) |
| fake_prob = round((1 - score) * 100, 1) |
|
|
| confidence = round(score * 100, 1) |
| risk_score = int(fake_prob) |
| return label, confidence, fake_prob, real_prob, risk_score |
| return label, confidence, round(fake_prob*100,1), round(real_prob*100,1), risk_score |
|
|
| def get_signals(text): |
| lower = text.lower() |
| signals = [] |
| cap = sum(1 for c in text if c.isupper()) / max(len(text),1) |
| signals.append(("Capitalization", f"{cap*100:.0f}%", "HIGH" if cap>0.2 else "LOW")) |
| excl = text.count('!') |
| signals.append(("Exclamation marks", str(excl), "HIGH" if excl>=3 else ("MED" if excl else "LOW"))) |
| sens = ['shocking','breaking','secret','hidden','suppressed','miracle','exposed','bombshell'] |
| found = [w for w in sens if w in lower] |
| signals.append(("Sensational words", str(len(found)), "HIGH" if len(found)>=2 else ("MED" if found else "LOW"))) |
| factual = ['according to','study','research','published','reported','announced'] |
| fc = [w for w in factual if w in lower] |
| signals.append(("Factual language", f"{len(fc)} found", "LOW" if len(fc)>=2 else "HIGH")) |
| return signals |
|
|
| |
| SAMPLES = { |
| "fake1": "SHOCKING: Scientists CONFIRM that drinking lemon juice cures ALL cancers overnight! Big Pharma desperately hiding this miracle cure! Share before it gets deleted!!!", |
| "fake2": "BREAKING: The moon landing was STAGED in Hollywood! Leaked NASA documents CONFIRM what conspiracy theorists have known. Government LYING to us for decades!!!", |
| "real1": "The Intergovernmental Panel on Climate Change released its assessment report indicating global temperatures have risen 1.1 degrees Celsius above pre-industrial levels, compiled by 230 scientists.", |
| "real2": "Apple reported quarterly revenue of $89.5 billion, with its services segment growing 16 percent year-over-year according to the company's official earnings call." |
| } |
|
|
| |
|
|
| |
| st.markdown(""" |
| <div style="display:flex;align-items:center;justify-content:space-between; |
| padding-bottom:20px;border-bottom:1px solid rgba(255,255,255,0.06);margin-bottom:40px;"> |
| <div style="display:flex;align-items:center;gap:14px;"> |
| <div style="width:44px;height:44px;background:#ffc800;border-radius:12px; |
| display:flex;align-items:center;justify-content:center;font-size:22px; |
| box-shadow:0 0 30px rgba(255,200,0,0.3);">π</div> |
| <div style="font-family:'Bebas Neue',sans-serif;font-size:28px;letter-spacing:3px;"> |
| TRUTH<span style="color:#ffc800;">LENS</span> |
| </div> |
| </div> |
| <div style="font-family:'JetBrains Mono',monospace;font-size:10px; |
| color:rgba(255,255,255,0.3);border:1px solid rgba(255,255,255,0.1); |
| padding:4px 12px;border-radius:20px;">v1.0 Β· PORTFOLIO PROJECT</div> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| st.markdown(""" |
| <div style="text-align:center;margin-bottom:40px;"> |
| <div style="display:inline-flex;align-items:center;gap:8px; |
| font-family:'JetBrains Mono',monospace;font-size:11px;color:#ffc800; |
| letter-spacing:3px;text-transform:uppercase;margin-bottom:16px; |
| padding:6px 16px;background:rgba(255,200,0,0.08); |
| border:1px solid rgba(255,200,0,0.2);border-radius:99px;"> |
| β AI-Powered Detection |
| </div> |
| <div style="font-family:'Bebas Neue',sans-serif;font-size:72px; |
| letter-spacing:4px;line-height:0.95;color:#fff;margin-bottom:16px;"> |
| DETECT<br><span style="color:#ffc800;">FAKE</span> <span style="color:rgba(255,255,255,0.2);">/</span> REAL<br>NEWS |
| </div> |
| <p style="font-size:15px;font-weight:300;color:rgba(255,255,255,0.4); |
| max-width:480px;margin:0 auto;line-height:1.7;"> |
| Fine-tuned DistilBERT model trained on 70,000+ articles. |
| Paste any headline or article to instantly analyze it. |
| </p> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| st.markdown(""" |
| <div style="display:flex;align-items:center;justify-content:center;gap:40px; |
| margin-bottom:40px;padding:18px;background:rgba(255,255,255,0.02); |
| border:1px solid rgba(255,255,255,0.05);border-radius:16px;"> |
| <div style="text-align:center;"> |
| <div style="font-family:'Bebas Neue',sans-serif;font-size:28px; |
| letter-spacing:2px;color:#ffc800;">98.9%</div> |
| <div style="font-family:'JetBrains Mono',monospace;font-size:9px; |
| color:rgba(255,255,255,0.3);letter-spacing:2px;text-transform:uppercase;">Accuracy</div> |
| </div> |
| <div style="width:1px;height:32px;background:rgba(255,255,255,0.08);"></div> |
| <div style="text-align:center;"> |
| <div style="font-family:'Bebas Neue',sans-serif;font-size:28px; |
| letter-spacing:2px;color:#ffc800;">70K+</div> |
| <div style="font-family:'JetBrains Mono',monospace;font-size:9px; |
| color:rgba(255,255,255,0.3);letter-spacing:2px;text-transform:uppercase;">Training Articles</div> |
| </div> |
| <div style="width:1px;height:32px;background:rgba(255,255,255,0.08);"></div> |
| <div style="text-align:center;"> |
| <div style="font-family:'Bebas Neue',sans-serif;font-size:28px; |
| letter-spacing:2px;color:#ffc800;">0.989</div> |
| <div style="font-family:'JetBrains Mono',monospace;font-size:9px; |
| color:rgba(255,255,255,0.3);letter-spacing:2px;text-transform:uppercase;">F1 Score</div> |
| </div> |
| <div style="width:1px;height:32px;background:rgba(255,255,255,0.08);"></div> |
| <div style="text-align:center;"> |
| <div style="font-family:'Bebas Neue',sans-serif;font-size:28px; |
| letter-spacing:2px;color:#ffc800;"><1s</div> |
| <div style="font-family:'JetBrains Mono',monospace;font-size:9px; |
| color:rgba(255,255,255,0.3);letter-spacing:2px;text-transform:uppercase;">Inference Time</div> |
| </div> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| st.markdown('<p style="font-family:JetBrains Mono,monospace;font-size:10px;color:rgba(255,255,255,0.3);letter-spacing:2px;text-transform:uppercase;margin-bottom:10px;">Try a sample β</p>', unsafe_allow_html=True) |
|
|
| c1, c2, c3, c4 = st.columns(4) |
| with c1: |
| st.markdown('<div class="fake-btn">', unsafe_allow_html=True) |
| if st.button("π¨ Fake Sample 1"): st.session_state.text = SAMPLES["fake1"] |
| st.markdown('</div>', unsafe_allow_html=True) |
| with c2: |
| st.markdown('<div class="fake-btn">', unsafe_allow_html=True) |
| if st.button("π¨ Fake Sample 2"): st.session_state.text = SAMPLES["fake2"] |
| st.markdown('</div>', unsafe_allow_html=True) |
| with c3: |
| st.markdown('<div class="real-btn">', unsafe_allow_html=True) |
| if st.button("β
Real Sample 1"): st.session_state.text = SAMPLES["real1"] |
| st.markdown('</div>', unsafe_allow_html=True) |
| with c4: |
| st.markdown('<div class="real-btn">', unsafe_allow_html=True) |
| if st.button("β
Real Sample 2"): st.session_state.text = SAMPLES["real2"] |
| st.markdown('</div>', unsafe_allow_html=True) |
|
|
| st.markdown("<div style='height:12px'></div>", unsafe_allow_html=True) |
|
|
| |
| text_input = st.text_area( |
| "input", |
| value=st.session_state.get('text', ''), |
| height=160, |
| placeholder="Paste any news article, headline, or suspicious text here..." |
| ) |
|
|
| char_color = "rgba(255,200,0,0.7)" if len(text_input) > 0 else "rgba(255,255,255,0.2)" |
| st.markdown(f"<p style='font-family:JetBrains Mono,monospace;font-size:11px;color:{char_color};margin-top:6px;letter-spacing:1px;'>{len(text_input)} CHARACTERS</p>", unsafe_allow_html=True) |
|
|
| st.markdown("<div style='height:12px'></div>", unsafe_allow_html=True) |
|
|
| col_btn, _ = st.columns([1, 2]) |
| with col_btn: |
| st.markdown('<div class="main-btn">', unsafe_allow_html=True) |
| analyze = st.button("β‘ ANALYZE NOW", use_container_width=True) |
| st.markdown('</div>', unsafe_allow_html=True) |
|
|
| |
| if analyze: |
| if not text_input.strip(): |
| st.markdown("""<div style='background:rgba(255,30,60,0.08);border:1px solid rgba(255,30,60,0.2); |
| border-radius:12px;padding:14px 18px;margin-top:16px; |
| font-family:JetBrains Mono,monospace;font-size:12px;color:#ff1e3c;'> |
| β Please enter some text to analyze.</div>""", unsafe_allow_html=True) |
| else: |
| with st.spinner("Analyzing with DistilBERT..."): |
| label, confidence, fake_prob, real_prob, risk_score = predict(text_input) |
| signals = get_signals(text_input) |
|
|
| if label == "FAKE": |
| vc, icon, color = "verdict-fake", "β οΈ", "#ff1e3c" |
| verdict_text = "LIKELY FAKE" |
| desc = "High probability of misinformation detected" |
| elif label == "REAL": |
| vc, icon, color = "verdict-real", "β
", "#00dc82" |
| verdict_text = "LIKELY REAL" |
| desc = "Low misinformation indicators detected" |
| else: |
| vc, icon, color = "verdict-uncertain", "π", "#ffb400" |
| verdict_text = "UNCERTAIN" |
| desc = "Mixed signals β verify with trusted sources" |
|
|
| st.markdown(f""" |
| <div class="{vc}"> |
| <div style="font-size:48px;line-height:1;margin-bottom:8px;">{icon}</div> |
| <div class="big-label" style="color:{color};">{verdict_text}</div> |
| <div style="font-family:'JetBrains Mono',monospace;font-size:12px; |
| color:rgba(255,255,255,0.35);margin-top:6px;">{desc}</div> |
| <div style="margin-top:20px;"> |
| <div style="display:flex;justify-content:space-between; |
| font-family:'JetBrains Mono',monospace;font-size:10px; |
| color:rgba(255,255,255,0.25);margin-bottom:6px;"> |
| <span>RISK LEVEL</span><span>{risk_score}/100</span> |
| </div> |
| <div style="height:4px;background:rgba(255,255,255,0.06);border-radius:99px;overflow:hidden;"> |
| <div style="height:100%;width:{risk_score}%;background:linear-gradient(90deg,{color},{color}aa);border-radius:99px;"></div> |
| </div> |
| </div> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| m1, m2, m3 = st.columns(3) |
| with m1: |
| st.markdown(f'<div class="metric-box"><div class="metric-num" style="color:{color};">{confidence:.1f}%</div><div class="metric-lbl">Confidence</div></div>', unsafe_allow_html=True) |
| with m2: |
| st.markdown(f'<div class="metric-box"><div class="metric-num" style="color:#ff1e3c;">{fake_prob:.1f}%</div><div class="metric-lbl">Fake Probability</div></div>', unsafe_allow_html=True) |
| with m3: |
| st.markdown(f'<div class="metric-box"><div class="metric-num" style="color:#00dc82;">{real_prob:.1f}%</div><div class="metric-lbl">Real Probability</div></div>', unsafe_allow_html=True) |
|
|
| |
| sig_rows = "" |
| for name, val, risk in signals: |
| badge = f'<span class="badge-{"high" if risk=="HIGH" else "med" if risk=="MED" else "low"}">{risk}</span>' |
| sig_rows += f'<div class="sig-row"><span>{name}: <span style="color:rgba(255,255,255,0.3);font-family:monospace;font-size:11px;">{val}</span></span>{badge}</div>' |
|
|
| st.markdown(f""" |
| <div class="signal-box"> |
| <div style="font-family:'JetBrains Mono',monospace;font-size:10px; |
| color:rgba(255,255,255,0.3);letter-spacing:2px;text-transform:uppercase; |
| margin-bottom:14px;">Signal Breakdown</div> |
| {sig_rows} |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| st.markdown(""" |
| <div class="footer-txt"> |
| TRUTHLENS v1.0 Β· BUILT BY VISHAL Β· DISTILBERT Β· WELFAKE Β· 98.9% ACCURACY<br> |
| Portfolio Project #1 of 5 Β· MSc AI Engineering Applicant |
| </div> |
| """, unsafe_allow_html=True) |
|
|