truthlens / streamlit_app.py
ReizenO8's picture
Update streamlit_app.py
3c5ebea verified
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)
# ── Load model ───────────────────────────────────────────────
@st.cache_resource
def load_model():
from transformers import pipeline
# Using a well-trained public fake news detection model
classifier = pipeline(
"text-classification",
model="hamzab/roberta-fake-news-classification",
device=-1
)
return classifier
classifier = load_model()
# ── Predict ──────────────────────────────────────────────────
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 ──────────────────────────────────────────────────
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."
}
# ── UI ───────────────────────────────────────────────────────
# Header
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)
# Hero
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)
# Stats
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;">&lt;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)
# Sample buttons
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)
# Input
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)
# ── Result ───────────────────────────────────────────────────
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)
# Metrics
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)
# Signals
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)
# Footer
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)