import os
# Prevent transformers from importing torch (use TF-only loading)
import io
import shutil
import streamlit as st
import torch
import numpy as np
import pandas as pd
import requests
from transformers import AutoTokenizer, AutoModelForSequenceClassification
# -----------------------------
# Styling (purple/blue glass theme)
# -----------------------------
st.set_page_config(page_title="📰 Fake News Dashboard", layout="wide", page_icon="🧠")
CSS = """
"""
st.markdown(CSS, unsafe_allow_html=True)
# -----------------------------
# Load model & tokenizer (PyTorch)
# -----------------------------
model_name="mrm8488/bert-tiny-finetuned-fake-news-detection"
@st.cache_resource
def load_model(model_name="mrm8488/bert-tiny-finetuned-fake-news-detection"):
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
model.eval()
return model, tokenizer
try:
model, tokenizer = load_model()
except Exception as e:
st.title("📰 Fake News Detector")
st.error("Failed to load model/tokenizer. See error below:")
st.code(repr(e))
st.stop()
# -----------------------------
# Prediction & helpers
# -----------------------------
def predict(text, model_arg=None, tokenizer_arg=None, max_length=128, return_attentions=None, return_att=None):
"""
Backwards-compatible predict wrapper:
- accepts either (text, model, tokenizer, ...) or (text, return_att=...) calling styles
- uses module-level `model`/`tokenizer` if none provided
- accepts both `return_attentions` and legacy `return_att` keywords
"""
# resolve model/tokenizer
mdl = model_arg if model_arg is not None else globals().get("model")
tok = tokenizer_arg if tokenizer_arg is not None else globals().get("tokenizer")
if mdl is None or tok is None:
raise RuntimeError("Model/tokenizer not available. Ensure load_model() succeeded.")
# resolve attention flag (support both names)
if return_att is None and return_attentions is None:
ra = False
elif return_att is None:
ra = bool(return_attentions)
else:
ra = bool(return_att)
inputs = tok(text, truncation=True, padding=True, return_tensors="pt", max_length=max_length)
outputs = mdl(**inputs, output_attentions=ra)
probs = torch.softmax(outputs.logits, dim=-1)[0].detach().cpu().numpy()
pred = int(torch.argmax(outputs.logits[0]))
attn = None
if ra and getattr(outputs, "attentions", None) is not None:
try:
attns = outputs.attentions
agg = None
for layer in attns:
arr = layer.detach().cpu().numpy() # (batch, heads, seq, seq)
mean = arr.mean(axis=(0,1)) # (seq, seq)
col = mean[:, 0]
agg = col if agg is None else agg + col
scores = (agg / len(attns)).tolist()
tokens = tok.convert_ids_to_tokens(inputs["input_ids"][0].tolist())
attn = list(zip(tokens, scores))
except Exception:
attn = None
return pred, float(np.max(probs)), float(probs[0]), float(probs[1]), attn
def infer_label_indices(model, tokenizer):
"""
Heuristically infer which class index corresponds to 'FAKE' by scoring
a small calibration set of known fake/real headlines. Stores result in
session_state as 'label_index_for_fake' (0 or 1).
"""
# small, obvious calibration examples
fake_examples = [
"Breaking: NASA confirms aliens landed in Times Square last night",
"Miracle pill cures all diseases, scientists stunned",
"Government to replace currency with pizza next month"
]
real_examples = [
"Stock market closes up 1.5% on positive earnings reports",
"City council approves $50 million infrastructure budget",
"New climate report released by international scientific body"
]
def avg_probs(texts):
probs_acc = np.zeros(2, dtype=float)
n = 0
for t in texts:
try:
inputs = tokenizer(t, truncation=True, padding=True, return_tensors="pt", max_length=128)
outputs = model(**inputs)
p = torch.softmax(outputs.logits, dim=-1)[0].detach().cpu().numpy()
probs_acc += p
n += 1
except Exception:
continue
return probs_acc / max(1, n)
fake_avg = avg_probs(fake_examples)
real_avg = avg_probs(real_examples)
# whichever index has higher mean probability on fake examples -> fake index
fake_index = int(np.argmax(fake_avg))
# store for later use
st.session_state["label_index_for_fake"] = fake_index
st.session_state["label_index_for_real"] = 1 - fake_index
return fake_index
# replace resolve_label_map/human_label_from_pred behavior with auto-detection
def human_label_from_pred(pred, model, invert=False):
"""
Determine REAL/FAKE using model.config.id2label if explicit, otherwise
use an inferred mapping from a small calibration set and cache it.
"""
# 1) try id2label mapping (explicit textual labels)
try:
cfg = getattr(model, "config", None)
if cfg and getattr(cfg, "id2label", None):
id2 = cfg.id2label
v = str(id2.get(pred, "")).lower()
if "fake" in v or "false" in v or "fraud" in v:
res = "FAKE"
elif "real" in v or "true" in v or "legit" in v:
res = "REAL"
else:
# fall back to inference below
raise ValueError("id2label ambiguous")
if invert:
res = "REAL" if res == "FAKE" else "FAKE"
return res
except Exception:
pass
# 2) use cached inference or run inference once
if "label_index_for_fake" not in st.session_state:
try:
infer_label_indices(model, tokenizer)
except Exception:
# last resort numeric default
res = "REAL" if pred == 1 else "FAKE"
if invert:
res = "REAL" if res == "FAKE" else "FAKE"
return res
fake_idx = st.session_state.get("label_index_for_fake", 1)
# map pred -> human label
res = "FAKE" if pred == fake_idx else "REAL"
if invert:
res = "REAL" if res == "FAKE" else "FAKE"
return res
# -----------------------------
# Sidebar navigation
# -----------------------------
st.sidebar.title("Navigation")
page = st.sidebar.radio("", ["Dashboard", "Single Predict", "Batch Predict", "Fetch & Predict", "About"], index=0)
# -----------------------------
# UI layout
# -----------------------------
st.title("📰 Fake News Detection — Dashboard")
max_len = st.sidebar.slider("Max tokens", 64, 512, 128, step=32)
show_attention = st.sidebar.checkbox("Show token importance", value=True)
invert_labels = st.sidebar.checkbox("Invert label mapping", value=False)
# -----------------------------
# Pages
# -----------------------------
if page == "Dashboard":
st.header("Dashboard")
st.markdown("**Examples**")
examples = [
"Breaking: Celebrity endorses miracle cure — doctors shocked",
"Government announces new infrastructure spending plan",
"Study shows chocolate linked with longer life"
]
for ex in examples:
if st.button(f"🔎 {ex}", key=f"ex_{ex[:12]}"):
st.session_state["example_text"] = ex
st.markdown('', unsafe_allow_html=True)
st.markdown('
', unsafe_allow_html=True)
last = st.session_state.get("last_result", None)
if last:
st.markdown("**Last prediction**")
st.write(last["text"])
st.info(f'{last["prediction"]} — confidence {last["confidence"]:.2%}')
else:
st.markdown("**Last prediction**")
st.write("_No predictions yet_")
st.markdown('
', unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
colA, colB = st.columns([2,1])
with colA:
st.markdown('', unsafe_allow_html=True)
example_val = st.session_state.get("example_text", "")
txt = st.text_area("Enter headline or article:", value=example_val, height=160, key="dash_input")
if st.button("Analyze (Dashboard)"):
pred, conf, p0, p1, att = predict(txt, model, tokenizer, max_length=max_len, return_attentions=show_attention)
label = human_label_from_pred(pred, model, invert=invert_labels)
st.write(f"raw probs: index0={p0:.3f}, index1={p1:.3f}")
st.success(f"{label} — confidence {conf:.2%}")
st.session_state["last_result"] = {"text": txt, "prediction": label, "confidence": conf, "att": att}
if att and show_attention:
st.write("Token importance:")
for t,s in att[:60]:
st.markdown(f"{t}", unsafe_allow_html=True)
st.markdown('
', unsafe_allow_html=True)
with colB:
st.markdown('', unsafe_allow_html=True)
st.subheader("Quick actions")
if st.button("Analyze example 1"):
st.session_state["example_text"] = examples[0]
st.markdown("Upload CSV for batch predictions in the Batch page.")
st.markdown('
', unsafe_allow_html=True)
elif page == "Single Predict":
st.header("Single Prediction")
st.markdown('', unsafe_allow_html=True)
# Input + optional NewsAPI key (reuses session key if set elsewhere)
input_text = st.text_area("Paste headline or article:", height=200)
api_key = st.text_input("NewsAPI key (optional — required for online source lookup)", type="password", key="newsapi_key")
# Lookup controls
lookup = st.checkbox("Lookup source online (NewsAPI)", value=False)
source_info = None
if lookup:
st.markdown("Use NewsAPI to find the likely source for this headline.")
if st.button("Find source", key="find_source"):
if not api_key:
st.warning("Enter a NewsAPI key to enable online lookup (get free key at https://newsapi.org).")
else:
try:
with st.spinner("Searching for source..."):
params = {
"qInTitle": input_text,
"apiKey": api_key,
"pageSize": 1,
"sortBy": "relevancy",
}
resp = requests.get("https://newsapi.org/v2/everything", params=params, timeout=10)
resp.raise_for_status()
data = resp.json()
articles = data.get("articles", [])
if articles:
a = articles[0]
source_info = {
"source": a.get("source", {}).get("name", ""),
"url": a.get("url", ""),
"publishedAt": a.get("publishedAt", "")
}
st.success(f"Found source: {source_info['source']}")
st.write(f"[Open article]({source_info['url']})")
else:
st.info("No matching article found for that headline.")
except Exception as e:
st.error(f"Source lookup failed: {e}")
# Analyze / Predict
if st.button("Analyze"):
if input_text.strip() == "":
st.warning("Enter text first.")
else:
with st.spinner("Predicting..."):
pred, conf, p_fake, p_real, att = predict(input_text, return_att=show_attention)
label = human_label_from_pred(pred, model, invert=invert_labels)
st.write(f"raw probs: index0={p_fake:.3f}, index1={p_real:.3f}")
st.success(f"{label} — confidence {conf:.2%}")
# show detected source if available
if source_info:
st.info(f"Detected source: **{source_info['source']}** — [Open article]({source_info['url']})")
if source_info.get("publishedAt"):
st.caption(f"Published at: {source_info['publishedAt']}")
st.metric("P(Real)", f"{p_real:.2%}")
st.metric("P(Fake)", f"{p_fake:.2%}")
st.session_state["last_result"] = {
"text": input_text,
"prediction": label,
"confidence": conf,
"att": att,
"source": source_info
}
if show_attention and att:
st.write("Token importance (top tokens):")
df_att = pd.DataFrame(att, columns=["token", "score"]).head(60)
st.table(df_att)
st.markdown('
', unsafe_allow_html=True)
elif page == "Batch Predict":
st.header("Batch Prediction")
st.markdown('', unsafe_allow_html=True)
uploaded = st.file_uploader("Upload CSV or TXT (one text per line)", type=["csv","txt"])
if uploaded is not None:
raw = uploaded.getvalue()
try:
decoded = raw.decode("utf-8", errors="replace")
lines = [l.strip() for l in decoded.splitlines() if l.strip()]
st.info(f"Parsed {len(lines)} records.")
if st.button("Run batch"):
results = []
progress = st.progress(0)
for i, txt in enumerate(lines):
pred, conf, p0, p1, _ = predict(txt, model, tokenizer, max_length=max_len, return_attentions=False)
mapped = human_label_from_pred(pred, model, invert=invert_labels)
results.append({"text": txt, "prediction": mapped, "confidence": conf, "p_fake": p0, "p_real": p1})
progress.progress((i+1)/len(lines))
df = pd.DataFrame(results); st.dataframe(df, use_container_width=True)
csv_bytes = df.to_csv(index=False).encode("utf-8"); st.download_button("Download CSV", data=csv_bytes, file_name="batch_predictions.csv", mime="text/csv")
if len(results)>0: st.session_state["last_result"] = results[0]
except Exception as e:
st.error(f"Failed to parse file: {e}")
st.markdown('
', unsafe_allow_html=True)
elif page == "Fetch & Predict":
st.header("Fetch & Predict from Web")
st.markdown('', unsafe_allow_html=True)
st.write("Fetch real news headlines from the web and predict if they are fake or real.")
# NewsAPI key input
api_key = st.text_input("NewsAPI key (get free key at https://newsapi.org)", type="password", key="newsapi_key")
col1, col2 = st.columns(2)
with col1:
query = st.text_input("Search query (e.g., 'technology', 'politics')", value="technology")
with col2:
num_articles = st.slider("Number of articles to fetch", 1, 50, 10)
if st.button("Fetch & Analyze"):
if not api_key:
st.error("Please enter a NewsAPI key. Get one free at https://newsapi.org")
else:
try:
with st.spinner("Fetching articles from NewsAPI..."):
url = "https://newsapi.org/v2/everything"
params = {
"q": query,
"apiKey": api_key,
"pageSize": num_articles,
"sortBy": "publishedAt"
}
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if data.get("status") != "ok":
st.error(f"API Error: {data.get('message', 'Unknown error')}")
else:
articles = data.get("articles", [])
st.success(f"Fetched {len(articles)} articles. Analyzing...")
results = []
progress = st.progress(0)
for i, article in enumerate(articles):
title = article.get("title", "")
description = article.get("description", "") or ""
source = article.get("source", {}).get("name", "Unknown")
url_article = article.get("url", "")
# Predict on title + description
text_to_predict = f"{title}. {description}"
if text_to_predict.strip():
pred, conf, p_fake, p_real, _ = predict(text_to_predict, return_att=False)
label = human_label_from_pred(pred, model, invert=invert_labels)
results.append({
"title": title,
"source": source,
"prediction": label,
"confidence": conf,
"p_fake": p_fake,
"p_real": p_real,
"url": url_article
})
progress.progress((i + 1) / len(articles))
# Display results
df = pd.DataFrame(results)
st.subheader(f"Results ({len(results)} articles)")
# Color-code by prediction
def highlight_prediction(row):
if row["prediction"] == "FAKE":
return ["background-color: #ff6b6b"] * len(row)
else:
return ["background-color: #51cf66"] * len(row)
st.dataframe(
df[["title", "source", "prediction", "confidence"]].style.apply(highlight_prediction, axis=1),
use_container_width=True
)
# Download button
csv_bytes = df.to_csv(index=False).encode("utf-8")
st.download_button("Download results CSV", data=csv_bytes, file_name="web_predictions.csv", mime="text/csv")
# Show article links
st.subheader("Articles")
for idx, row in df.iterrows():
emoji = "🔴" if row["prediction"] == "FAKE" else "🟢"
st.write(f"{emoji} [{row['title']}]({row['url']}) — {row['source']}")
st.caption(f"Confidence: {row['confidence']:.2%}")
except requests.exceptions.RequestException as e:
st.error(f"Failed to fetch articles: {e}")
except Exception as e:
st.error(f"Error: {e}")
st.markdown('
', unsafe_allow_html=True)
else: # About
st.header("About")
st.markdown('', unsafe_allow_html=True)
st.markdown("""
**Fake News Detector** — dashboard UI built with Streamlit.
- Uses BERT fine-tuned (mrm8488/bert-tiny-finetuned-fake-news-detection) for classification (TF).
- Sidebar navigation, top taskbar, glass cards, icon buttons.
- Single and batch prediction pages.
""")
st.markdown('
', unsafe_allow_html=True)
st.markdown("---")
st.caption("Built with ❤️ — Streamlit + Transformers. Ensure Streamlit runs in same Python env as installed packages. adeyi bamaiyi. thanks mr steve, thanks torbita. love u guys ")