MITTop's picture
Upload 4 files
b3cdb20 verified
import gradio as gr
import pickle
import re
from scipy.sparse import hstack
# ── Load models ──────────────────────────────────────────────────────────────
tfidf = pickle.load(open("tfidf.pkl", "rb"))
count_vec = pickle.load(open("count.pkl", "rb"))
xgb = pickle.load(open("xgb.pkl", "rb"))
# ── Core logic ────────────────────────────────────────────────────────────────
def clean(text: str) -> str:
text = text.lower()
text = re.sub(r"[^a-zA-Z]", " ", text)
return text
def predict(news_text: str):
if not news_text or not news_text.strip():
return (
"⚠️ Please enter some news text to analyse.",
"β€”",
"β€”",
)
cleaned = clean(news_text)
t1 = tfidf.transform([cleaned])
t2 = count_vec.transform([cleaned])
features = hstack([t1, t2])
proba = xgb.predict_proba(features)[0]
fake_prob = proba[1]
real_prob = proba[0]
confidence = max(fake_prob, real_prob) * 100
if fake_prob > 0.5:
verdict = "❌ FAKE NEWS DETECTED"
summary = (
f"Our model is **{confidence:.1f}%** confident this article "
f"contains misinformation or fabricated content."
)
conf_label = f"{confidence:.1f}% Fake Confidence"
else:
verdict = "βœ… REAL NEWS VERIFIED"
summary = (
f"Our model is **{confidence:.1f}%** confident this article "
f"is credible and factually grounded."
)
conf_label = f"{confidence:.1f}% Real Confidence"
return verdict, summary, conf_label
# ── Custom CSS β€” dark purple / Cisco-inspired ─────────────────────────────────
CSS = """
/* ── Google Fonts ── */
@import url('https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;600;700&family=Source+Sans+3:wght@300;400;600&display=swap');
/* ── Palette ──
--cisco-navy : #0d1117
--cisco-deep : #12102b
--cisco-purple : #1e1a4a
--cisco-mid : #2d2870
--cisco-accent : #49c5f1 (Cisco cyan)
--cisco-violet : #7b5ea7
--cisco-text : #cdd6f4
*/
body, .gradio-container {
background: #0d1117 !important;
font-family: 'Source Sans 3', sans-serif !important;
color: #cdd6f4 !important;
}
/* ── Animated gradient background ── */
.gradio-container::before {
content: "";
position: fixed;
inset: 0;
z-index: -1;
background:
radial-gradient(ellipse 80% 60% at 20% 10%, rgba(73,197,241,.08) 0%, transparent 60%),
radial-gradient(ellipse 60% 50% at 80% 80%, rgba(123,94,167,.12) 0%, transparent 55%),
radial-gradient(ellipse 100% 80% at 50% 50%, rgba(30,26,74,1) 0%, #0d1117 100%);
animation: bgPulse 10s ease-in-out infinite alternate;
}
@keyframes bgPulse {
from { opacity: .85; }
to { opacity: 1; }
}
/* ── Header / hero ── */
.hero-header {
text-align: center;
padding: 2.4rem 1rem 1rem;
border-bottom: 1px solid rgba(73,197,241,.18);
margin-bottom: 1.8rem;
}
.hero-logo {
display: inline-flex;
align-items: center;
gap: .6rem;
font-family: 'Rajdhani', sans-serif;
font-size: 1rem;
font-weight: 600;
letter-spacing: .18em;
text-transform: uppercase;
color: #49c5f1;
margin-bottom: .7rem;
}
.hero-logo svg { flex-shrink: 0; }
.hero-title {
font-family: 'Rajdhani', sans-serif;
font-size: clamp(2rem, 5vw, 3.2rem);
font-weight: 700;
letter-spacing: .04em;
color: #ffffff;
line-height: 1.1;
margin: 0 0 .45rem;
}
.hero-title span { color: #49c5f1; }
.hero-sub {
font-size: .95rem;
color: #8b9ab8;
max-width: 560px;
margin: 0 auto;
line-height: 1.55;
}
/* ── Panel cards ── */
.panel-card {
background: rgba(30,26,74,.55) !important;
border: 1px solid rgba(73,197,241,.14) !important;
border-radius: 10px !important;
backdrop-filter: blur(12px);
padding: 1.4rem !important;
transition: border-color .3s;
}
.panel-card:hover { border-color: rgba(73,197,241,.35) !important; }
/* ── Textarea ── */
textarea {
background: rgba(13,17,23,.75) !important;
border: 1px solid rgba(73,197,241,.22) !important;
border-radius: 8px !important;
color: #cdd6f4 !important;
font-family: 'Source Sans 3', sans-serif !important;
font-size: .95rem !important;
resize: vertical !important;
transition: border-color .25s, box-shadow .25s !important;
}
textarea:focus {
border-color: #49c5f1 !important;
box-shadow: 0 0 0 3px rgba(73,197,241,.12) !important;
outline: none !important;
}
textarea::placeholder { color: #4a5568 !important; }
/* ── Labels ── */
label span, .block > label {
font-family: 'Rajdhani', sans-serif !important;
font-size: .78rem !important;
font-weight: 600 !important;
letter-spacing: .12em !important;
text-transform: uppercase !important;
color: #49c5f1 !important;
}
/* ── Analyse button ── */
button#analyse-btn, .gr-button-primary {
font-family: 'Rajdhani', sans-serif !important;
font-size: 1.05rem !important;
font-weight: 700 !important;
letter-spacing: .12em !important;
text-transform: uppercase !important;
background: linear-gradient(135deg, #2d2870 0%, #1e1a4a 100%) !important;
border: 1px solid #49c5f1 !important;
color: #49c5f1 !important;
border-radius: 8px !important;
padding: .75rem 2rem !important;
cursor: pointer !important;
transition: background .25s, box-shadow .25s, transform .15s !important;
width: 100% !important;
}
button#analyse-btn:hover, .gr-button-primary:hover {
background: linear-gradient(135deg, #3d37a0 0%, #2d2870 100%) !important;
box-shadow: 0 0 22px rgba(73,197,241,.35) !important;
transform: translateY(-1px) !important;
}
button#analyse-btn:active, .gr-button-primary:active {
transform: translateY(0) !important;
}
/* ── Clear / secondary button ── */
.gr-button-secondary {
font-family: 'Rajdhani', sans-serif !important;
font-size: .88rem !important;
font-weight: 600 !important;
letter-spacing: .1em !important;
text-transform: uppercase !important;
background: transparent !important;
border: 1px solid rgba(73,197,241,.3) !important;
color: #8b9ab8 !important;
border-radius: 8px !important;
transition: border-color .2s, color .2s !important;
}
.gr-button-secondary:hover {
border-color: #49c5f1 !important;
color: #49c5f1 !important;
}
/* ── Verdict output ── */
#verdict-box textarea, #verdict-box input {
font-family: 'Rajdhani', sans-serif !important;
font-size: 1.35rem !important;
font-weight: 700 !important;
text-align: center !important;
letter-spacing: .05em !important;
border-radius: 8px !important;
border: 1px solid rgba(73,197,241,.22) !important;
background: rgba(13,17,23,.6) !important;
color: #ffffff !important;
padding: 1rem !important;
}
/* ── Summary / confidence outputs ── */
#summary-box textarea, #conf-box textarea,
#summary-box input, #conf-box input {
background: rgba(13,17,23,.5) !important;
border: 1px solid rgba(73,197,241,.14) !important;
border-radius: 8px !important;
color: #cdd6f4 !important;
font-size: .92rem !important;
text-align: center !important;
}
/* ── Horizontal divider ── */
hr {
border: none !important;
border-top: 1px solid rgba(73,197,241,.15) !important;
margin: 1.2rem 0 !important;
}
/* ── Footer ── */
.footer-bar {
text-align: center;
padding: 1.2rem 0 .8rem;
font-size: .78rem;
color: #4a5568;
border-top: 1px solid rgba(73,197,241,.1);
margin-top: 2rem;
letter-spacing: .06em;
}
.footer-bar a { color: #49c5f1; text-decoration: none; }
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: #0d1117; }
::-webkit-scrollbar-thumb { background: #2d2870; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #49c5f1; }
"""
# ── Hero HTML block ─────────────────────────────────────────────────────────
HERO_HTML = """
<div class="hero-header">
<div class="hero-logo">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L2 7l10 5 10-5-10-5z" fill="#49c5f1"/>
<path d="M2 17l10 5 10-5" stroke="#49c5f1" stroke-width="1.8" stroke-linecap="round"/>
<path d="M2 12l10 5 10-5" stroke="#7b5ea7" stroke-width="1.8" stroke-linecap="round"/>
</svg>
VeritasAI &nbsp;|&nbsp; Powered by XGBoost + NLP
</div>
<h1 class="hero-title">Fake News <span>Detection</span> Engine</h1>
<p class="hero-sub">
Paste any news article or headline below. Our machine-learning pipeline β€”
TF-IDF Β· CountVectorizer Β· XGBoost β€” will assess its credibility in milliseconds.
</p>
</div>
"""
FOOTER_HTML = """
<div class="footer-bar">
VeritasAI &nbsp;Β·&nbsp; Built with Gradio &amp; deployed on
<a href="https://huggingface.co/spaces" target="_blank">πŸ€— HuggingFace Spaces</a>
&nbsp;Β·&nbsp; Model: XGBoost + TF-IDF/CountVec
</div>
"""
# ── Build Gradio UI ───────────────────────────────────────────────────────────
with gr.Blocks(css=CSS, title="VeritasAI β€” Fake News Detector") as demo:
gr.HTML(HERO_HTML)
with gr.Row():
# ── Left column: input ──────────────────────────────────────────────
with gr.Column(scale=6, elem_classes="panel-card"):
news_input = gr.Textbox(
label="News Article / Headline",
placeholder=(
"Paste your news article, headline, or any text you want to "
"verify…\n\nExample: 'Scientists discover that drinking coffee "
"three times a day cures cancer, says new study.'"
),
lines=12,
max_lines=20,
)
with gr.Row():
clear_btn = gr.ClearButton(
components=[news_input],
value="Clear",
variant="secondary",
scale=1,
)
analyse_btn = gr.Button(
"πŸ” Analyse",
variant="primary",
scale=3,
elem_id="analyse-btn",
)
# ── Right column: results ───────────────────────────────────────────
with gr.Column(scale=4, elem_classes="panel-card"):
gr.Markdown("### πŸ“Š Analysis Results")
verdict_out = gr.Textbox(
label="Verdict",
interactive=False,
elem_id="verdict-box",
)
summary_out = gr.Textbox(
label="Summary",
interactive=False,
lines=3,
elem_id="summary-box",
)
conf_out = gr.Textbox(
label="Confidence Score",
interactive=False,
elem_id="conf-box",
)
gr.HTML("<hr/>")
gr.Markdown(
"""
**How it works**
1. Text is lowercased & cleaned
2. Vectorised with **TF-IDF** + **CountVectorizer**
3. Features are stacked β†’ fed to **XGBoost**
4. Probability threshold: **0.50**
""",
elem_classes="how-it-works",
)
gr.HTML(FOOTER_HTML)
# ── Wire up events ────────────────────────────────────────────────────────
analyse_btn.click(
fn=predict,
inputs=[news_input],
outputs=[verdict_out, summary_out, conf_out],
)
news_input.submit(
fn=predict,
inputs=[news_input],
outputs=[verdict_out, summary_out, conf_out],
)
# ── Entry point ───────────────────────────────────────────────────────────────
if __name__ == "__main__":
demo.launch()