Spaces:
Sleeping
Sleeping
| 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 | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| 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}") | |
| 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) |