DeepTrace-AI / app.py
syeda-Rija20's picture
Update app.py
25f5b89 verified
import streamlit as st
import torch
import numpy as np
import requests
import time
import re
from PIL import Image
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
pipeline,
)
from huggingface_hub import hf_hub_download
import tensorflow as tf
import plotly.graph_objects as go
import plotly.express as px
# ─────────────────────────────────────────────
# PAGE CONFIG
# ─────────────────────────────────────────────
st.set_page_config(
page_title="DeepTrace AI",
page_icon="πŸ”¬",
layout="wide",
initial_sidebar_state="collapsed",
)
# ─────────────────────────────────────────────
# GLOBAL CSS (dark cyber-forensics aesthetic)
# ─────────────────────────────────────────────
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Syne:wght@400;600;800&family=DM+Sans:wght@300;400;500&display=swap');
/* ── Root Variables ── */
:root {
--bg: #050810;
--bg2: #0b1120;
--bg3: #101928;
--border: #1e2d45;
--accent: #00d4ff;
--accent2: #7c3aed;
--accent3: #10b981;
--danger: #ef4444;
--warning: #f59e0b;
--text: #e2e8f0;
--muted: #64748b;
--glow: rgba(0,212,255,0.15);
}
/* ── Base Reset ── */
html, body, [class*="css"] {
font-family: 'DM Sans', sans-serif;
background-color: var(--bg) !important;
color: var(--text) !important;
}
.stApp { background: var(--bg) !important; }
/* ── Hide default Streamlit chrome ── */
#MainMenu, footer, header { visibility: hidden; }
.block-container { padding: 0 !important; max-width: 100% !important; }
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: var(--bg2); }
::-webkit-scrollbar-thumb { background: var(--accent); border-radius: 2px; }
/* ── Animated background grid ── */
.grid-bg {
position: fixed; inset: 0; z-index: 0; pointer-events: none;
background-image:
linear-gradient(rgba(0,212,255,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0,212,255,0.03) 1px, transparent 1px);
background-size: 60px 60px;
animation: gridScroll 20s linear infinite;
}
@keyframes gridScroll {
0% { background-position: 0 0; }
100% { background-position: 60px 60px; }
}
/* ── Scanline overlay ── */
.scanlines {
position: fixed; inset: 0; z-index: 0; pointer-events: none;
background: repeating-linear-gradient(
0deg, transparent, transparent 2px,
rgba(0,0,0,0.03) 2px, rgba(0,0,0,0.03) 4px
);
}
/* ── Hero Header ── */
.hero {
position: relative; z-index: 10;
background: linear-gradient(135deg, #050810 0%, #0b1120 50%, #080f1c 100%);
border-bottom: 1px solid var(--border);
padding: 2.5rem 3rem 2rem;
display: flex; align-items: center; gap: 1.5rem;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute; top: -50%; right: -10%; width: 500px; height: 500px;
background: radial-gradient(circle, rgba(0,212,255,0.06) 0%, transparent 70%);
border-radius: 50%;
}
.hero::after {
content: '';
position: absolute; bottom: -50%; left: 20%; width: 400px; height: 400px;
background: radial-gradient(circle, rgba(124,58,237,0.06) 0%, transparent 70%);
border-radius: 50%;
}
.hero-icon {
font-size: 3rem;
filter: drop-shadow(0 0 20px var(--accent));
animation: pulse 3s ease-in-out infinite;
}
@keyframes pulse {
0%,100% { filter: drop-shadow(0 0 20px var(--accent)); }
50% { filter: drop-shadow(0 0 40px var(--accent)); }
}
.hero-title {
font-family: 'Syne', sans-serif;
font-size: 2.6rem; font-weight: 800; letter-spacing: -0.02em;
background: linear-gradient(135deg, #00d4ff, #7c3aed);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
margin: 0; line-height: 1;
}
.hero-sub {
font-family: 'Space Mono', monospace;
font-size: 0.7rem; color: var(--accent); letter-spacing: 0.2em;
text-transform: uppercase; margin: 0.4rem 0 0;
opacity: 0.8;
}
.hero-desc {
font-size: 0.9rem; color: var(--muted); margin: 0; max-width: 500px;
}
.status-dot {
display: inline-block; width: 8px; height: 8px;
background: var(--accent3); border-radius: 50%;
margin-right: 0.5rem;
box-shadow: 0 0 8px var(--accent3);
animation: blink 2s ease-in-out infinite;
}
@keyframes blink {
0%,100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* ── Nav Tabs ── */
.nav-wrap {
position: sticky; top: 0; z-index: 100;
background: rgba(5,8,16,0.95); backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
padding: 0 3rem;
display: flex; gap: 0;
}
.stTabs [data-baseweb="tab-list"] {
background: transparent !important;
border-bottom: none !important;
gap: 0 !important;
padding: 0 !important;
}
.stTabs [data-baseweb="tab"] {
background: transparent !important;
border: none !important;
color: var(--muted) !important;
font-family: 'Space Mono', monospace !important;
font-size: 0.75rem !important;
letter-spacing: 0.1em !important;
text-transform: uppercase !important;
padding: 1rem 2rem !important;
border-bottom: 2px solid transparent !important;
transition: all 0.2s !important;
}
.stTabs [data-baseweb="tab"]:hover {
color: var(--accent) !important;
background: var(--glow) !important;
}
.stTabs [aria-selected="true"] {
color: var(--accent) !important;
border-bottom: 2px solid var(--accent) !important;
background: var(--glow) !important;
}
.stTabs [data-baseweb="tab-panel"] { padding: 0 !important; }
/* ── Content area ── */
.content-area { padding: 2rem 3rem; position: relative; z-index: 5; }
/* ── Cards ── */
.card {
background: var(--bg2);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.25rem;
position: relative; overflow: hidden;
transition: border-color 0.2s, box-shadow 0.2s;
}
.card:hover {
border-color: rgba(0,212,255,0.3);
box-shadow: 0 0 20px rgba(0,212,255,0.05);
}
.card::before {
content: '';
position: absolute; top: 0; left: 0; right: 0; height: 2px;
background: linear-gradient(90deg, transparent, var(--accent), transparent);
opacity: 0;
transition: opacity 0.3s;
}
.card:hover::before { opacity: 1; }
.card-label {
font-family: 'Space Mono', monospace;
font-size: 0.65rem; color: var(--accent); letter-spacing: 0.2em;
text-transform: uppercase; margin-bottom: 0.75rem;
display: flex; align-items: center; gap: 0.5rem;
}
.card-label::after {
content: ''; flex: 1; height: 1px; background: var(--border);
}
/* ── Result Verdict ── */
.verdict-wrap {
text-align: center; padding: 2rem 1rem;
background: var(--bg2); border: 1px solid var(--border);
border-radius: 16px; position: relative; overflow: hidden;
}
.verdict-badge {
display: inline-block;
font-family: 'Syne', sans-serif;
font-size: 1.8rem; font-weight: 800;
padding: 0.5rem 2rem; border-radius: 8px;
letter-spacing: 0.05em;
margin-bottom: 0.75rem;
}
.verdict-fake {
background: rgba(239,68,68,0.12);
border: 1px solid rgba(239,68,68,0.4);
color: #ef4444;
box-shadow: 0 0 30px rgba(239,68,68,0.1);
}
.verdict-real {
background: rgba(16,185,129,0.12);
border: 1px solid rgba(16,185,129,0.4);
color: #10b981;
box-shadow: 0 0 30px rgba(16,185,129,0.1);
}
.verdict-uncertain {
background: rgba(245,158,11,0.12);
border: 1px solid rgba(245,158,11,0.4);
color: #f59e0b;
box-shadow: 0 0 30px rgba(245,158,11,0.1);
}
.verdict-conf {
font-family: 'Space Mono', monospace;
font-size: 0.8rem; color: var(--muted);
}
.verdict-conf span {
color: var(--text); font-size: 1.1rem; font-weight: 700;
}
/* ── Metric boxes ── */
.metric-row { display: flex; gap: 1rem; flex-wrap: wrap; margin: 1rem 0; }
.metric-box {
flex: 1; min-width: 120px;
background: var(--bg3); border: 1px solid var(--border);
border-radius: 10px; padding: 1rem;
text-align: center;
}
.metric-val {
font-family: 'Syne', sans-serif;
font-size: 1.6rem; font-weight: 800;
background: linear-gradient(135deg, var(--accent), var(--accent2));
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
display: block;
}
.metric-key {
font-family: 'Space Mono', monospace;
font-size: 0.6rem; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.1em;
}
/* ── Tags ── */
.tag {
display: inline-block;
font-family: 'Space Mono', monospace;
font-size: 0.65rem; letter-spacing: 0.05em;
padding: 0.25rem 0.75rem; border-radius: 999px;
margin: 0.25rem;
}
.tag-danger { background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3); color: #ef4444; }
.tag-warn { background: rgba(245,158,11,0.1); border: 1px solid rgba(245,158,11,0.3); color: #f59e0b; }
.tag-safe { background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.3); color: #10b981; }
.tag-info { background: rgba(0,212,255,0.1); border: 1px solid rgba(0,212,255,0.3); color: #00d4ff; }
/* ── Streamlit overrides ── */
.stTextArea textarea {
background: var(--bg3) !important;
border: 1px solid var(--border) !important;
color: var(--text) !important;
font-family: 'DM Sans', sans-serif !important;
border-radius: 10px !important;
font-size: 0.9rem !important;
transition: border-color 0.2s !important;
}
.stTextArea textarea:focus {
border-color: var(--accent) !important;
box-shadow: 0 0 0 1px var(--accent) !important;
}
.stTextArea label {
color: var(--muted) !important;
font-family: 'Space Mono', monospace !important;
font-size: 0.7rem !important;
text-transform: uppercase !important;
letter-spacing: 0.1em !important;
}
.stButton > button {
background: linear-gradient(135deg, #00d4ff22, #7c3aed22) !important;
color: var(--accent) !important;
border: 1px solid var(--accent) !important;
border-radius: 8px !important;
font-family: 'Space Mono', monospace !important;
font-size: 0.75rem !important;
letter-spacing: 0.1em !important;
text-transform: uppercase !important;
padding: 0.6rem 2rem !important;
transition: all 0.2s !important;
width: 100% !important;
}
.stButton > button:hover {
background: linear-gradient(135deg, #00d4ff33, #7c3aed33) !important;
box-shadow: 0 0 20px rgba(0,212,255,0.2) !important;
transform: translateY(-1px) !important;
}
.stFileUploader {
background: var(--bg3) !important;
border: 1px dashed var(--border) !important;
border-radius: 12px !important;
transition: border-color 0.2s !important;
}
.stFileUploader:hover { border-color: var(--accent) !important; }
.stFileUploader label { color: var(--muted) !important; }
/* spinner */
.stSpinner > div { border-top-color: var(--accent) !important; }
/* divider */
hr { border-color: var(--border) !important; margin: 1.5rem 0 !important; }
/* image captions */
.stImage > div > div { color: var(--muted) !important; font-size: 0.75rem !important; }
/* ── Section header ── */
.section-header {
font-family: 'Syne', sans-serif;
font-size: 1.3rem; font-weight: 700;
color: var(--text); margin: 0 0 0.25rem;
}
.section-sub {
font-size: 0.85rem; color: var(--muted); margin: 0 0 1.5rem;
}
/* ── Signal bars decoration ── */
.signal { display: flex; align-items: flex-end; gap: 3px; height: 20px; }
.signal span {
display: block; width: 4px; border-radius: 2px;
background: var(--accent);
animation: signalAnim 1.2s ease-in-out infinite;
}
.signal span:nth-child(1) { height: 6px; animation-delay: 0s; }
.signal span:nth-child(2) { height: 10px; animation-delay: 0.1s; }
.signal span:nth-child(3) { height: 14px; animation-delay: 0.2s; }
.signal span:nth-child(4) { height: 10px; animation-delay: 0.3s; }
.signal span:nth-child(5) { height: 6px; animation-delay: 0.4s; }
@keyframes signalAnim {
0%,100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* ── About cards ── */
.about-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 1rem; }
.about-card {
background: var(--bg2); border: 1px solid var(--border);
border-radius: 12px; padding: 1.5rem; text-align: center;
transition: all 0.2s;
}
.about-card:hover {
border-color: rgba(0,212,255,0.3);
transform: translateY(-2px);
}
.about-card-icon { font-size: 2rem; margin-bottom: 0.75rem; }
.about-card-title {
font-family: 'Syne', sans-serif;
font-size: 0.95rem; font-weight: 700; color: var(--text);
margin: 0 0 0.5rem;
}
.about-card-desc { font-size: 0.8rem; color: var(--muted); line-height: 1.5; }
/* responsive */
@media (max-width: 768px) {
.hero { padding: 1.5rem; flex-direction: column; text-align: center; }
.hero-title { font-size: 1.8rem; }
.content-area { padding: 1rem; }
.about-grid { grid-template-columns: 1fr; }
}
</style>
<div class="grid-bg"></div>
<div class="scanlines"></div>
""", unsafe_allow_html=True)
# ─────────────────────────────────────────────
# MODEL LOADERS
# ─────────────────────────────────────────────
@st.cache_resource(show_spinner=False)
def load_text_model():
model_name = "hamzab/roberta-fake-news-classification"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)
model.eval()
return tokenizer, model
# @st.cache_resource(show_spinner=False)
# def load_image_model():
# import os
# import keras # Keras 3 explicit import
# filenames_to_try = [
# "image_detector_v2.keras",
# "image_detector_v2.h5",
# ]
# path = None
# for fname in filenames_to_try:
# try:
# path = hf_hub_download(
# repo_id="syeda-Rija20/image-detector",
# filename=fname
# )
# break
# except Exception:
# continue
# if path is None:
# raise RuntimeError(
# "Could not download image model from HuggingFace Hub. "
# "Check that 'syeda-Rija20/image-detector' is public and the file exists."
# )
# # Try keras 3 native load first, then tf.keras fallback
# try:
# model = keras.saving.load_model(path, compile=False)
# except Exception:
# try:
# model = tf.keras.models.load_model(path, compile=False)
# except Exception as e:
# raise RuntimeError(f"Failed to load image model: {e}")
@st.cache_resource(show_spinner=False)
def load_image_model():
import keras
import json
config_path = hf_hub_download(
repo_id="syeda-Rija20/image-detector",
filename="model_config.json"
)
weights_path = hf_hub_download(
repo_id="syeda-Rija20/image-detector",
filename="image_detector_clean.weights.h5"
)
with open(config_path) as f:
config = f.read()
model = keras.models.model_from_json(config)
model.load_weights(weights_path)
return model
# ─────────────────────────────────────────────
# PREDICTION FUNCTIONS
# ─────────────────────────────────────────────
CLICKBAIT_WORDS = [
"SHOCKING", "BREAKING", "EXPOSED", "YOU WON'T BELIEVE",
"UNBELIEVABLE", "MUST SEE", "URGENT", "SECRET", "LEAKED",
"BANNED", "CENSORED", "THEY DON'T WANT", "EXCLUSIVE"
]
FEAR_WORDS = [
"danger", "crisis", "collapse", "attack", "war", "threat",
"catastrophe", "disaster", "chaos", "doom", "apocalypse",
"deadly", "terror", "panic", "emergency"
]
def predict_news(text, tokenizer, model):
inputs = tokenizer(
text, return_tensors="pt",
truncation=True, padding=True, max_length=512
)
with torch.no_grad():
outputs = model(**inputs)
probs = torch.nn.functional.softmax(outputs.logits, dim=1)
pred = torch.argmax(probs).item()
confidence = torch.max(probs).item() * 100
label = model.config.id2label[pred]
return label, confidence, probs[0].tolist()
def manipulation_score(text):
text_up = text.upper()
cb_hits = [w for w in CLICKBAIT_WORDS if w in text_up]
fear_hits = [w for w in FEAR_WORDS if w.lower() in text.lower()]
exclamations = text.count("!")
caps_ratio = sum(1 for c in text if c.isupper()) / max(len(text), 1)
score = (
len(cb_hits) * 20 +
len(fear_hits) * 10 +
min(exclamations * 5, 20) +
min(caps_ratio * 100, 20)
)
return min(int(score), 100), cb_hits, fear_hits
def predict_image(img, model):
img_r = img.resize((224, 224)).convert("RGB")
arr = np.array(img_r, dtype=np.float32) / 255.0
arr = np.expand_dims(arr, axis=0)
pred = model.predict(arr, verbose=0)
conf = float(pred[0][0]) * 100
# 0 = AI, 1 = Real (based on training)
if conf < 50:
return "AI GENERATED", 100 - conf
else:
return "REAL IMAGE", conf
# ─────────────────────────────────────────────
# CHART HELPERS
# ─────────────────────────────────────────────
def gauge_chart(value, title, color):
fig = go.Figure(go.Indicator(
mode = "gauge+number",
value = value,
title = {"text": title, "font": {"family": "Space Mono", "size": 11, "color": "#64748b"}},
number= {"suffix": "%", "font": {"family": "Syne", "size": 28, "color": "#e2e8f0"}},
gauge = {
"axis" : {"range": [0, 100], "tickcolor": "#1e2d45", "tickfont": {"color": "#64748b", "size": 9}},
"bar" : {"color": color, "thickness": 0.3},
"bgcolor" : "#0b1120",
"bordercolor": "#1e2d45",
"steps" : [
{"range": [0, 33], "color": "rgba(16,185,129,0.08)"},
{"range": [33, 66], "color": "rgba(245,158,11,0.08)"},
{"range": [66,100], "color": "rgba(239,68,68,0.08)"},
],
"threshold" : {"line": {"color": color, "width": 2}, "value": value},
}
))
fig.update_layout(
paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
margin=dict(t=30, b=10, l=20, r=20), height=200,
)
return fig
def bar_chart(labels, values, colors):
fig = go.Figure(go.Bar(
x=values, y=labels, orientation="h",
marker_color=colors, marker_line_width=0,
text=[f"{v:.1f}%" for v in values],
textfont={"family": "Space Mono", "size": 10, "color": "#e2e8f0"},
textposition="outside",
))
fig.update_layout(
paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
xaxis=dict(range=[0,120], visible=False),
yaxis=dict(tickfont={"family": "Space Mono", "size": 10, "color": "#64748b"}),
margin=dict(t=10, b=10, l=10, r=60), height=120,
showlegend=False,
)
return fig
# ─────────────────────────────────────────────
# HERO
# ─────────────────────────────────────────────
st.markdown("""
<div class="hero">
<div class="hero-icon">πŸ”¬</div>
<div>
<div class="hero-title">DeepTrace AI</div>
<div class="hero-sub">
<span class="status-dot"></span>
Multi-Modal Misinformation Detection System Β· v2.0
</div>
<p class="hero-desc">
Advanced forensic AI for detecting fake news, AI-generated images,
and emotional manipulation in digital media.
</p>
</div>
<div style="margin-left:auto; display:flex; align-items:center; gap:1rem; flex-wrap:wrap;">
<div style="text-align:right;">
<div style="font-family:'Space Mono',monospace; font-size:0.6rem; color:#64748b; letter-spacing:0.1em;">STATUS</div>
<div style="font-family:'Syne',sans-serif; font-size:0.85rem; color:#10b981; font-weight:700;">● ONLINE</div>
</div>
<div class="signal">
<span></span><span></span><span></span><span></span><span></span>
</div>
</div>
</div>
""", unsafe_allow_html=True)
# ─────────────────────────────────────────────
# TABS
# ─────────────────────────────────────────────
tab1, tab2, tab3 = st.tabs([
"πŸ“° Fake News Detector",
"πŸ–ΌοΈ AI Image Detector",
"βš™οΈ About & Models",
])
# ══════════════════════════════════════════════
# TAB 1 β€” FAKE NEWS
# ══════════════════════════════════════════════
with tab1:
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown("""
<div class="section-header">πŸ“° Fake News & Manipulation Detector</div>
<div class="section-sub">
Paste a news article or headline. The AI will analyse authenticity,
emotional manipulation, and clickbait signals.
</div>
""", unsafe_allow_html=True)
col_in, col_out = st.columns([1, 1], gap="large")
with col_in:
# Init session state
if "news_text" not in st.session_state:
st.session_state["news_text"] = ""
# Sample buttons BEFORE text area so state is set on rerun
st.markdown('<div class="card-label">πŸ§ͺ Try Samples</div>', unsafe_allow_html=True)
s1, s2 = st.columns(2)
with s1:
if st.button("βœ… Real News", key="real_sample"):
st.session_state["news_text"] = (
"NASA's James Webb Space Telescope has captured the deepest infrared "
"image of the universe ever taken, revealing thousands of galaxies that "
"existed over 13 billion years ago. The image was released in July 2022 "
"and marks a major milestone in space exploration and astrophysics."
)
st.rerun()
with s2:
if st.button("⚠️ Fake News", key="fake_sample"):
st.session_state["news_text"] = (
"SHOCKING!! Scientists EXPOSED: drinking hot lemon water CURES cancer in "
"30 days! Big Pharma has been HIDING this SECRET for decades to protect "
"their profits. SHARE before it gets DELETED!! You WON'T BELIEVE what "
"they don't want you to know!!"
)
st.rerun()
st.markdown('<div class="card"><div class="card-label">πŸ“ Input Text</div>', unsafe_allow_html=True)
user_text = st.text_area(
"Article / Headline",
value=st.session_state["news_text"],
height=220,
placeholder="Paste your news article or headline here…",
label_visibility="collapsed",
key="news_textarea",
)
st.session_state["news_text"] = user_text
st.markdown('</div>', unsafe_allow_html=True)
analyze_clicked = st.button("πŸ” ANALYZE TEXT", key="analyze_btn")
with col_out:
if analyze_clicked and user_text.strip():
with st.spinner("Loading NLP model…"):
tokenizer, text_model = load_text_model()
with st.spinner("Analysing content…"):
time.sleep(0.3)
label, conf, probs_list = predict_news(user_text, tokenizer, text_model)
manip, cb_hits, fear_hits = manipulation_score(user_text)
word_count = len(user_text.split())
sent_count = user_text.count(".") + user_text.count("!") + user_text.count("?")
excl_count = user_text.count("!")
# ── Verdict ──
is_fake = label.upper() == "FAKE"
badge_class = "verdict-fake" if is_fake else "verdict-real"
verdict_icon = "⚠️ FAKE NEWS" if is_fake else "βœ… REAL NEWS"
st.markdown(f"""
<div class="verdict-wrap">
<div class="verdict-badge {badge_class}">{verdict_icon}</div>
<div class="verdict-conf">Confidence: <span>{conf:.1f}%</span></div>
</div>
""", unsafe_allow_html=True)
# ── Gauge row ──
g1, g2, g3 = st.columns(3)
with g1:
fake_prob = probs_list[0] * 100 if len(probs_list) > 0 else conf
st.plotly_chart(gauge_chart(fake_prob, "FAKE PROB", "#ef4444"), use_column_width=True)
with g2:
real_prob = probs_list[1] * 100 if len(probs_list) > 1 else (100 - conf)
st.plotly_chart(gauge_chart(real_prob, "REAL PROB", "#10b981"), use_column_width=True)
with g3:
st.plotly_chart(gauge_chart(manip, "MANIPULATION", "#f59e0b"), use_column_width=True)
# ── Metrics ──
st.markdown(f"""
<div class="metric-row">
<div class="metric-box">
<span class="metric-val">{word_count}</span>
<span class="metric-key">Words</span>
</div>
<div class="metric-box">
<span class="metric-val">{excl_count}</span>
<span class="metric-key">Exclamations</span>
</div>
<div class="metric-box">
<span class="metric-val">{len(cb_hits)}</span>
<span class="metric-key">Clickbait Hits</span>
</div>
<div class="metric-box">
<span class="metric-val">{len(fear_hits)}</span>
<span class="metric-key">Fear Words</span>
</div>
</div>
""", unsafe_allow_html=True)
# ── Tags ──
if cb_hits or fear_hits:
st.markdown('<div class="card"><div class="card-label">🚨 Detected Signals</div>', unsafe_allow_html=True)
tags_html = ""
for w in cb_hits:
tags_html += f'<span class="tag tag-danger">πŸ”΄ {w}</span>'
for w in fear_hits[:6]:
tags_html += f'<span class="tag tag-warn">🟑 {w}</span>'
st.markdown(f'<div>{tags_html}</div></div>', unsafe_allow_html=True)
# ── Analysis summary ──
manip_level = "HIGH" if manip > 60 else "MEDIUM" if manip > 30 else "LOW"
manip_color = "#ef4444" if manip > 60 else "#f59e0b" if manip > 30 else "#10b981"
st.markdown(f"""
<div class="card">
<div class="card-label">🧠 Analysis Summary</div>
<p style="font-size:0.85rem; color:#94a3b8; line-height:1.7; margin:0;">
The model classified this content as
<strong style="color:{'#ef4444' if is_fake else '#10b981'}">
{'FAKE' if is_fake else 'REAL'}
</strong>
with <strong style="color:#e2e8f0">{conf:.1f}%</strong> confidence.<br>
Manipulation score is
<strong style="color:{manip_color}">{manip_level} ({manip}%)</strong>
β€” detected <strong style="color:#e2e8f0">{len(cb_hits)}</strong>
clickbait keyword(s) and
<strong style="color:#e2e8f0">{len(fear_hits)}</strong> fear-based word(s).
{'<br><span style="color:#f59e0b">⚠️ High emotional manipulation detected β€” verify from multiple sources.</span>' if manip > 50 else ''}
</p>
</div>
""", unsafe_allow_html=True)
elif analyze_clicked:
st.warning("Please enter some text to analyse.")
else:
st.markdown("""
<div class="card" style="text-align:center; padding:3rem 1rem; border-style:dashed;">
<div style="font-size:3rem; margin-bottom:1rem; opacity:0.3;">πŸ“°</div>
<div style="font-family:'Space Mono',monospace; font-size:0.75rem;
color:#64748b; letter-spacing:0.1em;">
AWAITING INPUT
</div>
<div style="font-size:0.8rem; color:#475569; margin-top:0.5rem;">
Paste an article and click Analyze
</div>
</div>
""", unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# ══════════════════════════════════════════════
# TAB 2 β€” IMAGE DETECTOR
# ══════════════════════════════════════════════
with tab2:
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown("""
<div class="section-header">πŸ–ΌοΈ AI Image Forensic Analyser</div>
<div class="section-sub">
Upload any image. The model will determine whether it was taken by a
camera or generated by an AI system.
</div>
""", unsafe_allow_html=True)
col_up, col_res = st.columns([1, 1], gap="large")
with col_up:
st.markdown('<div class="card"><div class="card-label">πŸ“‚ Upload Image</div>', unsafe_allow_html=True)
uploaded = st.file_uploader(
"Upload image",
type=["jpg", "jpeg", "png", "webp"],
label_visibility="collapsed",
)
st.markdown('</div>', unsafe_allow_html=True)
if uploaded:
img = Image.open(uploaded)
st.image(img, caption="Uploaded Image", use_column_width=True)
# Image metadata
w, h = img.size
st.markdown(f"""
<div class="card">
<div class="card-label">πŸ“ Image Metadata</div>
<div class="metric-row" style="margin:0;">
<div class="metric-box">
<span class="metric-val" style="font-size:1.1rem;">{w}Γ—{h}</span>
<span class="metric-key">Resolution</span>
</div>
<div class="metric-box">
<span class="metric-val" style="font-size:1.1rem;">{img.mode}</span>
<span class="metric-key">Color Mode</span>
</div>
<div class="metric-box">
<span class="metric-val" style="font-size:1.1rem;">{uploaded.name.split('.')[-1].upper()}</span>
<span class="metric-key">Format</span>
</div>
</div>
</div>
""", unsafe_allow_html=True)
scan_clicked = st.button("πŸ”¬ SCAN IMAGE", key="scan_btn", disabled=uploaded is None)
with col_res:
if scan_clicked and uploaded:
with st.spinner("Loading vision model…"):
img_model = load_image_model()
with st.spinner("Running forensic scan…"):
time.sleep(0.5)
verdict, score = predict_image(img, img_model)
is_ai = verdict == "AI GENERATED"
badge_class = "verdict-fake" if is_ai else "verdict-real"
icon = "πŸ€– AI GENERATED" if is_ai else "πŸ“· REAL IMAGE"
st.markdown(f"""
<div class="verdict-wrap">
<div class="verdict-badge {badge_class}">{icon}</div>
<div class="verdict-conf">Detection confidence: <span>{score:.1f}%</span></div>
</div>
""", unsafe_allow_html=True)
# Gauges
ai_score = score if is_ai else 100 - score
real_score = 100 - ai_score
g1, g2 = st.columns(2)
with g1:
st.plotly_chart(gauge_chart(ai_score, "AI PROBABILITY", "#ef4444"), use_column_width=True)
with g2:
st.plotly_chart(gauge_chart(real_score, "REAL PROBABILITY", "#10b981"), use_column_width=True)
# Confidence bar
fig_bar = bar_chart(
["AI Generated", "Real / Authentic"],
[ai_score, real_score],
["#ef4444" if ai_score > real_score else "#475569",
"#10b981" if real_score >= ai_score else "#475569"]
)
st.markdown('<div class="card"><div class="card-label">πŸ“Š Score Breakdown</div>', unsafe_allow_html=True)
st.plotly_chart(fig_bar, use_column_width=True)
st.markdown('</div>', unsafe_allow_html=True)
# Summary
risk = "HIGH" if ai_score > 75 else "MEDIUM" if ai_score > 45 else "LOW"
risk_col = "#ef4444" if ai_score > 75 else "#f59e0b" if ai_score > 45 else "#10b981"
st.markdown(f"""
<div class="card">
<div class="card-label">🧠 Forensic Summary</div>
<p style="font-size:0.85rem; color:#94a3b8; line-height:1.7; margin:0;">
Forensic analysis indicates this image is
<strong style="color:{'#ef4444' if is_ai else '#10b981'}">
{'likely AI-generated' if is_ai else 'likely authentic'}
</strong>
with <strong style="color:#e2e8f0">{score:.1f}%</strong> confidence.<br>
AI generation risk level:
<strong style="color:{risk_col}">{risk}</strong>.
{'<br><span style="color:#f59e0b">⚠️ Do not use this image as evidence without further verification.</span>' if is_ai else '<br><span style="color:#10b981">βœ… Image appears to be from a real camera source.</span>'}
</p>
</div>
""", unsafe_allow_html=True)
elif not uploaded:
st.markdown("""
<div class="card" style="text-align:center; padding:3rem 1rem; border-style:dashed;">
<div style="font-size:3rem; margin-bottom:1rem; opacity:0.3;">πŸ–ΌοΈ</div>
<div style="font-family:'Space Mono',monospace; font-size:0.75rem;
color:#64748b; letter-spacing:0.1em;">
NO IMAGE UPLOADED
</div>
<div style="font-size:0.8rem; color:#475569; margin-top:0.5rem;">
Upload an image and click Scan
</div>
</div>
""", unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# ══════════════════════════════════════════════
# TAB 3 β€” ABOUT
# ══════════════════════════════════════════════
with tab3:
st.markdown('<div class="content-area">', unsafe_allow_html=True)
st.markdown("""
<div class="section-header">βš™οΈ System Architecture & Models</div>
<div class="section-sub">Technical details about the AI models and pipeline powering DeepTrace AI.</div>
<div class="about-grid">
<div class="about-card">
<div class="about-card-icon">🧠</div>
<div class="about-card-title">NLP Engine</div>
<div class="about-card-desc">
RoBERTa-based transformer fine-tuned on 72,000+ news articles.
Classifies content as FAKE or REAL with confidence scoring.
<br><br>
<span style="font-family:'Space Mono',monospace; font-size:0.65rem; color:#00d4ff;">
hamzab/roberta-fake-news-classification
</span>
</div>
</div>
<div class="about-card">
<div class="about-card-icon">πŸ‘οΈ</div>
<div class="about-card-title">Vision Engine</div>
<div class="about-card-desc">
EfficientNetB3 transfer learning model fine-tuned on
AI-generated vs real image datasets. Detects synthetic textures
and generation artifacts.
<br><br>
<span style="font-family:'Space Mono',monospace; font-size:0.65rem; color:#00d4ff;">
Muniba930/image-detector
</span>
</div>
</div>
<div class="about-card">
<div class="about-card-icon">πŸ”</div>
<div class="about-card-title">Manipulation Analyser</div>
<div class="about-card-desc">
Rule-based linguistic analyser detecting clickbait keywords,
fear-based language, excessive punctuation, and emotional
manipulation patterns.
<br><br>
<span style="font-family:'Space Mono',monospace; font-size:0.65rem; color:#00d4ff;">
Custom lexical engine
</span>
</div>
</div>
</div>
""", unsafe_allow_html=True)
st.markdown("<br>", unsafe_allow_html=True)
# Tech stack
st.markdown("""
<div class="card">
<div class="card-label">πŸ› οΈ Tech Stack</div>
<div style="display:flex; flex-wrap:wrap; gap:0.5rem; margin-top:0.5rem;">
<span class="tag tag-info">Streamlit</span>
<span class="tag tag-info">Hugging Face</span>
<span class="tag tag-info">PyTorch</span>
<span class="tag tag-info">TensorFlow</span>
<span class="tag tag-info">Transformers</span>
<span class="tag tag-info">EfficientNetB3</span>
<span class="tag tag-info">RoBERTa</span>
<span class="tag tag-info">Plotly</span>
<span class="tag tag-info">Pillow</span>
</div>
</div>
<div class="card">
<div class="card-label">⚠️ Limitations & Disclaimer</div>
<p style="font-size:0.85rem; color:#94a3b8; line-height:1.8; margin:0;">
β€’ This tool is for <strong style="color:#e2e8f0">research and educational purposes</strong>
β€” not a definitive fact-checker.<br>
β€’ Image model performance is limited by training data diversity;
very recent AI generators may not be detected.<br>
β€’ Always cross-reference with trusted news sources before
drawing conclusions.<br>
β€’ The manipulation score is heuristic-based and may produce
false positives on legitimate breaking news.
</p>
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div style="text-align:center; padding:2rem 0; color:#334155;
font-family:'Space Mono',monospace; font-size:0.65rem; letter-spacing:0.1em;">
DEEPTRACE AI · MULTI-MODAL MISINFORMATION DETECTION · BUILT WITH ❀️ USING OPEN-SOURCE AI
</div>
""", unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)