Maheentouqeer1 commited on
Commit
7127aa5
Β·
verified Β·
1 Parent(s): 248463f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +962 -0
app.py ADDED
@@ -0,0 +1,962 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import torch
3
+ import numpy as np
4
+ import requests
5
+ import time
6
+ import re
7
+ from PIL import Image
8
+ from transformers import (
9
+ AutoTokenizer,
10
+ AutoModelForSequenceClassification,
11
+ pipeline,
12
+ )
13
+ from huggingface_hub import hf_hub_download
14
+ import tensorflow as tf
15
+ import plotly.graph_objects as go
16
+ import plotly.express as px
17
+
18
+ # ─────────────────────────────────────────────
19
+ # PAGE CONFIG
20
+ # ─────────────────────────────────────────────
21
+ st.set_page_config(
22
+ page_title="DeepTrace AI",
23
+ page_icon="πŸ”¬",
24
+ layout="wide",
25
+ initial_sidebar_state="collapsed",
26
+ )
27
+
28
+ # ─────────────────────────────────────────────
29
+ # GLOBAL CSS (dark cyber-forensics aesthetic)
30
+ # ─────────────────────────────────────────────
31
+ st.markdown("""
32
+ <style>
33
+ @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');
34
+
35
+ /* ── Root Variables ── */
36
+ :root {
37
+ --bg: #050810;
38
+ --bg2: #0b1120;
39
+ --bg3: #101928;
40
+ --border: #1e2d45;
41
+ --accent: #00d4ff;
42
+ --accent2: #7c3aed;
43
+ --accent3: #10b981;
44
+ --danger: #ef4444;
45
+ --warning: #f59e0b;
46
+ --text: #e2e8f0;
47
+ --muted: #64748b;
48
+ --glow: rgba(0,212,255,0.15);
49
+ }
50
+
51
+ /* ── Base Reset ── */
52
+ html, body, [class*="css"] {
53
+ font-family: 'DM Sans', sans-serif;
54
+ background-color: var(--bg) !important;
55
+ color: var(--text) !important;
56
+ }
57
+
58
+ .stApp { background: var(--bg) !important; }
59
+
60
+ /* ── Hide default Streamlit chrome ── */
61
+ #MainMenu, footer, header { visibility: hidden; }
62
+ .block-container { padding: 0 !important; max-width: 100% !important; }
63
+
64
+ /* ── Scrollbar ── */
65
+ ::-webkit-scrollbar { width: 4px; }
66
+ ::-webkit-scrollbar-track { background: var(--bg2); }
67
+ ::-webkit-scrollbar-thumb { background: var(--accent); border-radius: 2px; }
68
+
69
+ /* ── Animated background grid ── */
70
+ .grid-bg {
71
+ position: fixed; inset: 0; z-index: 0; pointer-events: none;
72
+ background-image:
73
+ linear-gradient(rgba(0,212,255,0.03) 1px, transparent 1px),
74
+ linear-gradient(90deg, rgba(0,212,255,0.03) 1px, transparent 1px);
75
+ background-size: 60px 60px;
76
+ animation: gridScroll 20s linear infinite;
77
+ }
78
+ @keyframes gridScroll {
79
+ 0% { background-position: 0 0; }
80
+ 100% { background-position: 60px 60px; }
81
+ }
82
+
83
+ /* ── Scanline overlay ── */
84
+ .scanlines {
85
+ position: fixed; inset: 0; z-index: 0; pointer-events: none;
86
+ background: repeating-linear-gradient(
87
+ 0deg, transparent, transparent 2px,
88
+ rgba(0,0,0,0.03) 2px, rgba(0,0,0,0.03) 4px
89
+ );
90
+ }
91
+
92
+ /* ── Hero Header ── */
93
+ .hero {
94
+ position: relative; z-index: 10;
95
+ background: linear-gradient(135deg, #050810 0%, #0b1120 50%, #080f1c 100%);
96
+ border-bottom: 1px solid var(--border);
97
+ padding: 2.5rem 3rem 2rem;
98
+ display: flex; align-items: center; gap: 1.5rem;
99
+ overflow: hidden;
100
+ }
101
+ .hero::before {
102
+ content: '';
103
+ position: absolute; top: -50%; right: -10%; width: 500px; height: 500px;
104
+ background: radial-gradient(circle, rgba(0,212,255,0.06) 0%, transparent 70%);
105
+ border-radius: 50%;
106
+ }
107
+ .hero::after {
108
+ content: '';
109
+ position: absolute; bottom: -50%; left: 20%; width: 400px; height: 400px;
110
+ background: radial-gradient(circle, rgba(124,58,237,0.06) 0%, transparent 70%);
111
+ border-radius: 50%;
112
+ }
113
+ .hero-icon {
114
+ font-size: 3rem;
115
+ filter: drop-shadow(0 0 20px var(--accent));
116
+ animation: pulse 3s ease-in-out infinite;
117
+ }
118
+ @keyframes pulse {
119
+ 0%,100% { filter: drop-shadow(0 0 20px var(--accent)); }
120
+ 50% { filter: drop-shadow(0 0 40px var(--accent)); }
121
+ }
122
+ .hero-title {
123
+ font-family: 'Syne', sans-serif;
124
+ font-size: 2.6rem; font-weight: 800; letter-spacing: -0.02em;
125
+ background: linear-gradient(135deg, #00d4ff, #7c3aed);
126
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
127
+ margin: 0; line-height: 1;
128
+ }
129
+ .hero-sub {
130
+ font-family: 'Space Mono', monospace;
131
+ font-size: 0.7rem; color: var(--accent); letter-spacing: 0.2em;
132
+ text-transform: uppercase; margin: 0.4rem 0 0;
133
+ opacity: 0.8;
134
+ }
135
+ .hero-desc {
136
+ font-size: 0.9rem; color: var(--muted); margin: 0; max-width: 500px;
137
+ }
138
+ .status-dot {
139
+ display: inline-block; width: 8px; height: 8px;
140
+ background: var(--accent3); border-radius: 50%;
141
+ margin-right: 0.5rem;
142
+ box-shadow: 0 0 8px var(--accent3);
143
+ animation: blink 2s ease-in-out infinite;
144
+ }
145
+ @keyframes blink {
146
+ 0%,100% { opacity: 1; }
147
+ 50% { opacity: 0.3; }
148
+ }
149
+
150
+ /* ── Nav Tabs ── */
151
+ .nav-wrap {
152
+ position: sticky; top: 0; z-index: 100;
153
+ background: rgba(5,8,16,0.95); backdrop-filter: blur(12px);
154
+ border-bottom: 1px solid var(--border);
155
+ padding: 0 3rem;
156
+ display: flex; gap: 0;
157
+ }
158
+ .stTabs [data-baseweb="tab-list"] {
159
+ background: transparent !important;
160
+ border-bottom: none !important;
161
+ gap: 0 !important;
162
+ padding: 0 !important;
163
+ }
164
+ .stTabs [data-baseweb="tab"] {
165
+ background: transparent !important;
166
+ border: none !important;
167
+ color: var(--muted) !important;
168
+ font-family: 'Space Mono', monospace !important;
169
+ font-size: 0.75rem !important;
170
+ letter-spacing: 0.1em !important;
171
+ text-transform: uppercase !important;
172
+ padding: 1rem 2rem !important;
173
+ border-bottom: 2px solid transparent !important;
174
+ transition: all 0.2s !important;
175
+ }
176
+ .stTabs [data-baseweb="tab"]:hover {
177
+ color: var(--accent) !important;
178
+ background: var(--glow) !important;
179
+ }
180
+ .stTabs [aria-selected="true"] {
181
+ color: var(--accent) !important;
182
+ border-bottom: 2px solid var(--accent) !important;
183
+ background: var(--glow) !important;
184
+ }
185
+ .stTabs [data-baseweb="tab-panel"] { padding: 0 !important; }
186
+
187
+ /* ── Content area ── */
188
+ .content-area { padding: 2rem 3rem; position: relative; z-index: 5; }
189
+
190
+ /* ── Cards ── */
191
+ .card {
192
+ background: var(--bg2);
193
+ border: 1px solid var(--border);
194
+ border-radius: 12px;
195
+ padding: 1.5rem;
196
+ margin-bottom: 1.25rem;
197
+ position: relative; overflow: hidden;
198
+ transition: border-color 0.2s, box-shadow 0.2s;
199
+ }
200
+ .card:hover {
201
+ border-color: rgba(0,212,255,0.3);
202
+ box-shadow: 0 0 20px rgba(0,212,255,0.05);
203
+ }
204
+ .card::before {
205
+ content: '';
206
+ position: absolute; top: 0; left: 0; right: 0; height: 2px;
207
+ background: linear-gradient(90deg, transparent, var(--accent), transparent);
208
+ opacity: 0;
209
+ transition: opacity 0.3s;
210
+ }
211
+ .card:hover::before { opacity: 1; }
212
+
213
+ .card-label {
214
+ font-family: 'Space Mono', monospace;
215
+ font-size: 0.65rem; color: var(--accent); letter-spacing: 0.2em;
216
+ text-transform: uppercase; margin-bottom: 0.75rem;
217
+ display: flex; align-items: center; gap: 0.5rem;
218
+ }
219
+ .card-label::after {
220
+ content: ''; flex: 1; height: 1px; background: var(--border);
221
+ }
222
+
223
+ /* ── Result Verdict ── */
224
+ .verdict-wrap {
225
+ text-align: center; padding: 2rem 1rem;
226
+ background: var(--bg2); border: 1px solid var(--border);
227
+ border-radius: 16px; position: relative; overflow: hidden;
228
+ }
229
+ .verdict-badge {
230
+ display: inline-block;
231
+ font-family: 'Syne', sans-serif;
232
+ font-size: 1.8rem; font-weight: 800;
233
+ padding: 0.5rem 2rem; border-radius: 8px;
234
+ letter-spacing: 0.05em;
235
+ margin-bottom: 0.75rem;
236
+ }
237
+ .verdict-fake {
238
+ background: rgba(239,68,68,0.12);
239
+ border: 1px solid rgba(239,68,68,0.4);
240
+ color: #ef4444;
241
+ box-shadow: 0 0 30px rgba(239,68,68,0.1);
242
+ }
243
+ .verdict-real {
244
+ background: rgba(16,185,129,0.12);
245
+ border: 1px solid rgba(16,185,129,0.4);
246
+ color: #10b981;
247
+ box-shadow: 0 0 30px rgba(16,185,129,0.1);
248
+ }
249
+ .verdict-uncertain {
250
+ background: rgba(245,158,11,0.12);
251
+ border: 1px solid rgba(245,158,11,0.4);
252
+ color: #f59e0b;
253
+ box-shadow: 0 0 30px rgba(245,158,11,0.1);
254
+ }
255
+ .verdict-conf {
256
+ font-family: 'Space Mono', monospace;
257
+ font-size: 0.8rem; color: var(--muted);
258
+ }
259
+ .verdict-conf span {
260
+ color: var(--text); font-size: 1.1rem; font-weight: 700;
261
+ }
262
+
263
+ /* ── Metric boxes ── */
264
+ .metric-row { display: flex; gap: 1rem; flex-wrap: wrap; margin: 1rem 0; }
265
+ .metric-box {
266
+ flex: 1; min-width: 120px;
267
+ background: var(--bg3); border: 1px solid var(--border);
268
+ border-radius: 10px; padding: 1rem;
269
+ text-align: center;
270
+ }
271
+ .metric-val {
272
+ font-family: 'Syne', sans-serif;
273
+ font-size: 1.6rem; font-weight: 800;
274
+ background: linear-gradient(135deg, var(--accent), var(--accent2));
275
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
276
+ display: block;
277
+ }
278
+ .metric-key {
279
+ font-family: 'Space Mono', monospace;
280
+ font-size: 0.6rem; color: var(--muted);
281
+ text-transform: uppercase; letter-spacing: 0.1em;
282
+ }
283
+
284
+ /* ── Tags ── */
285
+ .tag {
286
+ display: inline-block;
287
+ font-family: 'Space Mono', monospace;
288
+ font-size: 0.65rem; letter-spacing: 0.05em;
289
+ padding: 0.25rem 0.75rem; border-radius: 999px;
290
+ margin: 0.25rem;
291
+ }
292
+ .tag-danger { background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3); color: #ef4444; }
293
+ .tag-warn { background: rgba(245,158,11,0.1); border: 1px solid rgba(245,158,11,0.3); color: #f59e0b; }
294
+ .tag-safe { background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.3); color: #10b981; }
295
+ .tag-info { background: rgba(0,212,255,0.1); border: 1px solid rgba(0,212,255,0.3); color: #00d4ff; }
296
+
297
+ /* ── Streamlit overrides ── */
298
+ .stTextArea textarea {
299
+ background: var(--bg3) !important;
300
+ border: 1px solid var(--border) !important;
301
+ color: var(--text) !important;
302
+ font-family: 'DM Sans', sans-serif !important;
303
+ border-radius: 10px !important;
304
+ font-size: 0.9rem !important;
305
+ transition: border-color 0.2s !important;
306
+ }
307
+ .stTextArea textarea:focus {
308
+ border-color: var(--accent) !important;
309
+ box-shadow: 0 0 0 1px var(--accent) !important;
310
+ }
311
+ .stTextArea label {
312
+ color: var(--muted) !important;
313
+ font-family: 'Space Mono', monospace !important;
314
+ font-size: 0.7rem !important;
315
+ text-transform: uppercase !important;
316
+ letter-spacing: 0.1em !important;
317
+ }
318
+
319
+ .stButton > button {
320
+ background: linear-gradient(135deg, #00d4ff22, #7c3aed22) !important;
321
+ color: var(--accent) !important;
322
+ border: 1px solid var(--accent) !important;
323
+ border-radius: 8px !important;
324
+ font-family: 'Space Mono', monospace !important;
325
+ font-size: 0.75rem !important;
326
+ letter-spacing: 0.1em !important;
327
+ text-transform: uppercase !important;
328
+ padding: 0.6rem 2rem !important;
329
+ transition: all 0.2s !important;
330
+ width: 100% !important;
331
+ }
332
+ .stButton > button:hover {
333
+ background: linear-gradient(135deg, #00d4ff33, #7c3aed33) !important;
334
+ box-shadow: 0 0 20px rgba(0,212,255,0.2) !important;
335
+ transform: translateY(-1px) !important;
336
+ }
337
+
338
+ .stFileUploader {
339
+ background: var(--bg3) !important;
340
+ border: 1px dashed var(--border) !important;
341
+ border-radius: 12px !important;
342
+ transition: border-color 0.2s !important;
343
+ }
344
+ .stFileUploader:hover { border-color: var(--accent) !important; }
345
+ .stFileUploader label { color: var(--muted) !important; }
346
+
347
+ /* spinner */
348
+ .stSpinner > div { border-top-color: var(--accent) !important; }
349
+
350
+ /* divider */
351
+ hr { border-color: var(--border) !important; margin: 1.5rem 0 !important; }
352
+
353
+ /* image captions */
354
+ .stImage > div > div { color: var(--muted) !important; font-size: 0.75rem !important; }
355
+
356
+ /* ── Section header ── */
357
+ .section-header {
358
+ font-family: 'Syne', sans-serif;
359
+ font-size: 1.3rem; font-weight: 700;
360
+ color: var(--text); margin: 0 0 0.25rem;
361
+ }
362
+ .section-sub {
363
+ font-size: 0.85rem; color: var(--muted); margin: 0 0 1.5rem;
364
+ }
365
+
366
+ /* ── Signal bars decoration ── */
367
+ .signal { display: flex; align-items: flex-end; gap: 3px; height: 20px; }
368
+ .signal span {
369
+ display: block; width: 4px; border-radius: 2px;
370
+ background: var(--accent);
371
+ animation: signalAnim 1.2s ease-in-out infinite;
372
+ }
373
+ .signal span:nth-child(1) { height: 6px; animation-delay: 0s; }
374
+ .signal span:nth-child(2) { height: 10px; animation-delay: 0.1s; }
375
+ .signal span:nth-child(3) { height: 14px; animation-delay: 0.2s; }
376
+ .signal span:nth-child(4) { height: 10px; animation-delay: 0.3s; }
377
+ .signal span:nth-child(5) { height: 6px; animation-delay: 0.4s; }
378
+ @keyframes signalAnim {
379
+ 0%,100% { opacity: 1; }
380
+ 50% { opacity: 0.3; }
381
+ }
382
+
383
+ /* ── About cards ── */
384
+ .about-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 1rem; }
385
+ .about-card {
386
+ background: var(--bg2); border: 1px solid var(--border);
387
+ border-radius: 12px; padding: 1.5rem; text-align: center;
388
+ transition: all 0.2s;
389
+ }
390
+ .about-card:hover {
391
+ border-color: rgba(0,212,255,0.3);
392
+ transform: translateY(-2px);
393
+ }
394
+ .about-card-icon { font-size: 2rem; margin-bottom: 0.75rem; }
395
+ .about-card-title {
396
+ font-family: 'Syne', sans-serif;
397
+ font-size: 0.95rem; font-weight: 700; color: var(--text);
398
+ margin: 0 0 0.5rem;
399
+ }
400
+ .about-card-desc { font-size: 0.8rem; color: var(--muted); line-height: 1.5; }
401
+
402
+ /* responsive */
403
+ @media (max-width: 768px) {
404
+ .hero { padding: 1.5rem; flex-direction: column; text-align: center; }
405
+ .hero-title { font-size: 1.8rem; }
406
+ .content-area { padding: 1rem; }
407
+ .about-grid { grid-template-columns: 1fr; }
408
+ }
409
+ </style>
410
+
411
+ <div class="grid-bg"></div>
412
+ <div class="scanlines"></div>
413
+ """, unsafe_allow_html=True)
414
+
415
+
416
+ # ─────────────────────────────────────────────
417
+ # MODEL LOADERS
418
+ # ─────────────────────────────────────────────
419
+ @st.cache_resource(show_spinner=False)
420
+ def load_text_model():
421
+ model_name = "hamzab/roberta-fake-news-classification"
422
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
423
+ model = AutoModelForSequenceClassification.from_pretrained(model_name)
424
+ model.eval()
425
+ return tokenizer, model
426
+
427
+
428
+ @st.cache_resource(show_spinner=False)
429
+ def load_image_model():
430
+ path = hf_hub_download(
431
+ repo_id="Muniba930/image-detector",
432
+ filename="image_detector_v2.h5"
433
+ )
434
+ model = tf.keras.models.load_model(path)
435
+ return model
436
+
437
+
438
+ # ─────────────────────────────────────────────
439
+ # PREDICTION FUNCTIONS
440
+ # ─────────────────────────────────────────────
441
+ CLICKBAIT_WORDS = [
442
+ "SHOCKING", "BREAKING", "EXPOSED", "YOU WON'T BELIEVE",
443
+ "UNBELIEVABLE", "MUST SEE", "URGENT", "SECRET", "LEAKED",
444
+ "BANNED", "CENSORED", "THEY DON'T WANT", "EXCLUSIVE"
445
+ ]
446
+
447
+ FEAR_WORDS = [
448
+ "danger", "crisis", "collapse", "attack", "war", "threat",
449
+ "catastrophe", "disaster", "chaos", "doom", "apocalypse",
450
+ "deadly", "terror", "panic", "emergency"
451
+ ]
452
+
453
+
454
+ def predict_news(text, tokenizer, model):
455
+ inputs = tokenizer(
456
+ text, return_tensors="pt",
457
+ truncation=True, padding=True, max_length=512
458
+ )
459
+ with torch.no_grad():
460
+ outputs = model(**inputs)
461
+ probs = torch.nn.functional.softmax(outputs.logits, dim=1)
462
+ pred = torch.argmax(probs).item()
463
+ confidence = torch.max(probs).item() * 100
464
+ label = model.config.id2label[pred]
465
+ return label, confidence, probs[0].tolist()
466
+
467
+
468
+ def manipulation_score(text):
469
+ text_up = text.upper()
470
+ cb_hits = [w for w in CLICKBAIT_WORDS if w in text_up]
471
+ fear_hits = [w for w in FEAR_WORDS if w.lower() in text.lower()]
472
+ exclamations = text.count("!")
473
+ caps_ratio = sum(1 for c in text if c.isupper()) / max(len(text), 1)
474
+
475
+ score = (
476
+ len(cb_hits) * 20 +
477
+ len(fear_hits) * 10 +
478
+ min(exclamations * 5, 20) +
479
+ min(caps_ratio * 100, 20)
480
+ )
481
+ return min(int(score), 100), cb_hits, fear_hits
482
+
483
+
484
+ def predict_image(img, model):
485
+ img_r = img.resize((224, 224)).convert("RGB")
486
+ arr = np.array(img_r, dtype=np.float32) / 255.0
487
+ arr = np.expand_dims(arr, axis=0)
488
+ pred = model.predict(arr, verbose=0)
489
+ conf = float(pred[0][0]) * 100
490
+ # 0 = AI, 1 = Real (based on training)
491
+ if conf < 50:
492
+ return "AI GENERATED", 100 - conf
493
+ else:
494
+ return "REAL IMAGE", conf
495
+
496
+
497
+ # ─────────────────────────────────────────────
498
+ # CHART HELPERS
499
+ # ─────────────────────────────────────────────
500
+ def gauge_chart(value, title, color):
501
+ fig = go.Figure(go.Indicator(
502
+ mode = "gauge+number",
503
+ value = value,
504
+ title = {"text": title, "font": {"family": "Space Mono", "size": 11, "color": "#64748b"}},
505
+ number= {"suffix": "%", "font": {"family": "Syne", "size": 28, "color": "#e2e8f0"}},
506
+ gauge = {
507
+ "axis" : {"range": [0, 100], "tickcolor": "#1e2d45", "tickfont": {"color": "#64748b", "size": 9}},
508
+ "bar" : {"color": color, "thickness": 0.3},
509
+ "bgcolor" : "#0b1120",
510
+ "bordercolor": "#1e2d45",
511
+ "steps" : [
512
+ {"range": [0, 33], "color": "rgba(16,185,129,0.08)"},
513
+ {"range": [33, 66], "color": "rgba(245,158,11,0.08)"},
514
+ {"range": [66,100], "color": "rgba(239,68,68,0.08)"},
515
+ ],
516
+ "threshold" : {"line": {"color": color, "width": 2}, "value": value},
517
+ }
518
+ ))
519
+ fig.update_layout(
520
+ paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
521
+ margin=dict(t=30, b=10, l=20, r=20), height=200,
522
+ )
523
+ return fig
524
+
525
+
526
+ def bar_chart(labels, values, colors):
527
+ fig = go.Figure(go.Bar(
528
+ x=values, y=labels, orientation="h",
529
+ marker_color=colors, marker_line_width=0,
530
+ text=[f"{v:.1f}%" for v in values],
531
+ textfont={"family": "Space Mono", "size": 10, "color": "#e2e8f0"},
532
+ textposition="outside",
533
+ ))
534
+ fig.update_layout(
535
+ paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
536
+ xaxis=dict(range=[0,120], visible=False),
537
+ yaxis=dict(tickfont={"family": "Space Mono", "size": 10, "color": "#64748b"}),
538
+ margin=dict(t=10, b=10, l=10, r=60), height=120,
539
+ showlegend=False,
540
+ )
541
+ return fig
542
+
543
+
544
+ # ─────────────────────────────────────────────
545
+ # HERO
546
+ # ─────────────────────────────────────────────
547
+ st.markdown("""
548
+ <div class="hero">
549
+ <div class="hero-icon">πŸ”¬</div>
550
+ <div>
551
+ <div class="hero-title">DeepTrace AI</div>
552
+ <div class="hero-sub">
553
+ <span class="status-dot"></span>
554
+ Multi-Modal Misinformation Detection System Β· v2.0
555
+ </div>
556
+ <p class="hero-desc">
557
+ Advanced forensic AI for detecting fake news, AI-generated images,
558
+ and emotional manipulation in digital media.
559
+ </p>
560
+ </div>
561
+ <div style="margin-left:auto; display:flex; align-items:center; gap:1rem; flex-wrap:wrap;">
562
+ <div style="text-align:right;">
563
+ <div style="font-family:'Space Mono',monospace; font-size:0.6rem; color:#64748b; letter-spacing:0.1em;">STATUS</div>
564
+ <div style="font-family:'Syne',sans-serif; font-size:0.85rem; color:#10b981; font-weight:700;">● ONLINE</div>
565
+ </div>
566
+ <div class="signal">
567
+ <span></span><span></span><span></span><span></span><span></span>
568
+ </div>
569
+ </div>
570
+ </div>
571
+ """, unsafe_allow_html=True)
572
+
573
+
574
+ # ─────────────────────────────────────────────
575
+ # TABS
576
+ # ─────────────────────────────────────────────
577
+ tab1, tab2, tab3 = st.tabs([
578
+ "πŸ“° Fake News Detector",
579
+ "πŸ–ΌοΈ AI Image Detector",
580
+ "βš™οΈ About & Models",
581
+ ])
582
+
583
+
584
+ # ══════════════════════════════════════════════
585
+ # TAB 1 β€” FAKE NEWS
586
+ # ══════════════════════════════════════════════
587
+ with tab1:
588
+ st.markdown('<div class="content-area">', unsafe_allow_html=True)
589
+
590
+ st.markdown("""
591
+ <div class="section-header">πŸ“° Fake News & Manipulation Detector</div>
592
+ <div class="section-sub">
593
+ Paste a news article or headline. The AI will analyse authenticity,
594
+ emotional manipulation, and clickbait signals.
595
+ </div>
596
+ """, unsafe_allow_html=True)
597
+
598
+ col_in, col_out = st.columns([1, 1], gap="large")
599
+
600
+ with col_in:
601
+ st.markdown('<div class="card"><div class="card-label">πŸ“ Input Text</div>', unsafe_allow_html=True)
602
+ user_text = st.text_area(
603
+ "Article / Headline",
604
+ height=240,
605
+ placeholder="Paste your news article or headline here…\n\nExample:\n'BREAKING: Scientists discover miracle cure that Big Pharma is hiding!'",
606
+ label_visibility="collapsed",
607
+ )
608
+
609
+ # sample buttons
610
+ st.markdown('<div class="card-label" style="margin-top:1rem;">πŸ§ͺ Try Samples</div>', unsafe_allow_html=True)
611
+ s1, s2 = st.columns(2)
612
+ with s1:
613
+ if st.button("Real News Sample"):
614
+ user_text = ("NASA's James Webb Space Telescope has captured the deepest infrared "
615
+ "image of the universe, revealing thousands of galaxies that existed "
616
+ "over 13 billion years ago. The milestone image was released in July 2022.")
617
+ st.session_state["sample_text"] = user_text
618
+ with s2:
619
+ if st.button("Fake News Sample"):
620
+ user_text = ("SHOCKING!! Scientists EXPOSED: drinking lemon water CURES cancer in 30 days! "
621
+ "Big Pharma is HIDING this SECRET β€” SHARE before it gets DELETED!!")
622
+ st.session_state["sample_text"] = user_text
623
+
624
+ if "sample_text" in st.session_state and not user_text:
625
+ user_text = st.session_state["sample_text"]
626
+
627
+ st.markdown('</div>', unsafe_allow_html=True)
628
+
629
+ analyze_clicked = st.button("πŸ” ANALYZE TEXT", key="analyze_btn")
630
+
631
+ with col_out:
632
+ if analyze_clicked and user_text.strip():
633
+ with st.spinner("Loading NLP model…"):
634
+ tokenizer, text_model = load_text_model()
635
+
636
+ with st.spinner("Analysing content…"):
637
+ time.sleep(0.3)
638
+ label, conf, probs_list = predict_news(user_text, tokenizer, text_model)
639
+ manip, cb_hits, fear_hits = manipulation_score(user_text)
640
+ word_count = len(user_text.split())
641
+ sent_count = user_text.count(".") + user_text.count("!") + user_text.count("?")
642
+ excl_count = user_text.count("!")
643
+
644
+ # ── Verdict ──
645
+ is_fake = label.upper() == "FAKE"
646
+ badge_class = "verdict-fake" if is_fake else "verdict-real"
647
+ verdict_icon = "⚠️ FAKE NEWS" if is_fake else "βœ… REAL NEWS"
648
+
649
+ st.markdown(f"""
650
+ <div class="verdict-wrap">
651
+ <div class="verdict-badge {badge_class}">{verdict_icon}</div>
652
+ <div class="verdict-conf">Confidence: <span>{conf:.1f}%</span></div>
653
+ </div>
654
+ """, unsafe_allow_html=True)
655
+
656
+ # ── Gauge row ──
657
+ g1, g2, g3 = st.columns(3)
658
+ with g1:
659
+ fake_prob = probs_list[0] * 100 if len(probs_list) > 0 else conf
660
+ st.plotly_chart(gauge_chart(fake_prob, "FAKE PROB", "#ef4444"), use_container_width=True)
661
+ with g2:
662
+ real_prob = probs_list[1] * 100 if len(probs_list) > 1 else (100 - conf)
663
+ st.plotly_chart(gauge_chart(real_prob, "REAL PROB", "#10b981"), use_container_width=True)
664
+ with g3:
665
+ st.plotly_chart(gauge_chart(manip, "MANIPULATION", "#f59e0b"), use_container_width=True)
666
+
667
+ # ── Metrics ──
668
+ st.markdown(f"""
669
+ <div class="metric-row">
670
+ <div class="metric-box">
671
+ <span class="metric-val">{word_count}</span>
672
+ <span class="metric-key">Words</span>
673
+ </div>
674
+ <div class="metric-box">
675
+ <span class="metric-val">{excl_count}</span>
676
+ <span class="metric-key">Exclamations</span>
677
+ </div>
678
+ <div class="metric-box">
679
+ <span class="metric-val">{len(cb_hits)}</span>
680
+ <span class="metric-key">Clickbait Hits</span>
681
+ </div>
682
+ <div class="metric-box">
683
+ <span class="metric-val">{len(fear_hits)}</span>
684
+ <span class="metric-key">Fear Words</span>
685
+ </div>
686
+ </div>
687
+ """, unsafe_allow_html=True)
688
+
689
+ # ── Tags ──
690
+ if cb_hits or fear_hits:
691
+ st.markdown('<div class="card"><div class="card-label">🚨 Detected Signals</div>', unsafe_allow_html=True)
692
+ tags_html = ""
693
+ for w in cb_hits:
694
+ tags_html += f'<span class="tag tag-danger">πŸ”΄ {w}</span>'
695
+ for w in fear_hits[:6]:
696
+ tags_html += f'<span class="tag tag-warn">🟑 {w}</span>'
697
+ st.markdown(f'<div>{tags_html}</div></div>', unsafe_allow_html=True)
698
+
699
+ # ── Analysis summary ──
700
+ manip_level = "HIGH" if manip > 60 else "MEDIUM" if manip > 30 else "LOW"
701
+ manip_color = "#ef4444" if manip > 60 else "#f59e0b" if manip > 30 else "#10b981"
702
+ st.markdown(f"""
703
+ <div class="card">
704
+ <div class="card-label">🧠 Analysis Summary</div>
705
+ <p style="font-size:0.85rem; color:#94a3b8; line-height:1.7; margin:0;">
706
+ The model classified this content as
707
+ <strong style="color:{'#ef4444' if is_fake else '#10b981'}">
708
+ {'FAKE' if is_fake else 'REAL'}
709
+ </strong>
710
+ with <strong style="color:#e2e8f0">{conf:.1f}%</strong> confidence.<br>
711
+ Manipulation score is
712
+ <strong style="color:{manip_color}">{manip_level} ({manip}%)</strong>
713
+ β€” detected <strong style="color:#e2e8f0">{len(cb_hits)}</strong>
714
+ clickbait keyword(s) and
715
+ <strong style="color:#e2e8f0">{len(fear_hits)}</strong> fear-based word(s).
716
+ {'<br><span style="color:#f59e0b">⚠️ High emotional manipulation detected β€” verify from multiple sources.</span>' if manip > 50 else ''}
717
+ </p>
718
+ </div>
719
+ """, unsafe_allow_html=True)
720
+
721
+ elif analyze_clicked:
722
+ st.warning("Please enter some text to analyse.")
723
+ else:
724
+ st.markdown("""
725
+ <div class="card" style="text-align:center; padding:3rem 1rem; border-style:dashed;">
726
+ <div style="font-size:3rem; margin-bottom:1rem; opacity:0.3;">πŸ“°</div>
727
+ <div style="font-family:'Space Mono',monospace; font-size:0.75rem;
728
+ color:#64748b; letter-spacing:0.1em;">
729
+ AWAITING INPUT
730
+ </div>
731
+ <div style="font-size:0.8rem; color:#475569; margin-top:0.5rem;">
732
+ Paste an article and click Analyze
733
+ </div>
734
+ </div>
735
+ """, unsafe_allow_html=True)
736
+
737
+ st.markdown('</div>', unsafe_allow_html=True)
738
+
739
+
740
+ # ══════════════════════════════════════════════
741
+ # TAB 2 β€” IMAGE DETECTOR
742
+ # ══════════════════════════════════════════════
743
+ with tab2:
744
+ st.markdown('<div class="content-area">', unsafe_allow_html=True)
745
+
746
+ st.markdown("""
747
+ <div class="section-header">πŸ–ΌοΈ AI Image Forensic Analyser</div>
748
+ <div class="section-sub">
749
+ Upload any image. The model will determine whether it was taken by a
750
+ camera or generated by an AI system.
751
+ </div>
752
+ """, unsafe_allow_html=True)
753
+
754
+ col_up, col_res = st.columns([1, 1], gap="large")
755
+
756
+ with col_up:
757
+ st.markdown('<div class="card"><div class="card-label">πŸ“‚ Upload Image</div>', unsafe_allow_html=True)
758
+ uploaded = st.file_uploader(
759
+ "Upload image",
760
+ type=["jpg", "jpeg", "png", "webp"],
761
+ label_visibility="collapsed",
762
+ )
763
+ st.markdown('</div>', unsafe_allow_html=True)
764
+
765
+ if uploaded:
766
+ img = Image.open(uploaded)
767
+ st.image(img, caption="Uploaded Image", use_container_width=True)
768
+
769
+ # Image metadata
770
+ w, h = img.size
771
+ st.markdown(f"""
772
+ <div class="card">
773
+ <div class="card-label">πŸ“ Image Metadata</div>
774
+ <div class="metric-row" style="margin:0;">
775
+ <div class="metric-box">
776
+ <span class="metric-val" style="font-size:1.1rem;">{w}Γ—{h}</span>
777
+ <span class="metric-key">Resolution</span>
778
+ </div>
779
+ <div class="metric-box">
780
+ <span class="metric-val" style="font-size:1.1rem;">{img.mode}</span>
781
+ <span class="metric-key">Color Mode</span>
782
+ </div>
783
+ <div class="metric-box">
784
+ <span class="metric-val" style="font-size:1.1rem;">{uploaded.name.split('.')[-1].upper()}</span>
785
+ <span class="metric-key">Format</span>
786
+ </div>
787
+ </div>
788
+ </div>
789
+ """, unsafe_allow_html=True)
790
+
791
+ scan_clicked = st.button("πŸ”¬ SCAN IMAGE", key="scan_btn", disabled=uploaded is None)
792
+
793
+ with col_res:
794
+ if scan_clicked and uploaded:
795
+ with st.spinner("Loading vision model…"):
796
+ img_model = load_image_model()
797
+
798
+ with st.spinner("Running forensic scan…"):
799
+ time.sleep(0.5)
800
+ verdict, score = predict_image(img, img_model)
801
+
802
+ is_ai = verdict == "AI GENERATED"
803
+ badge_class = "verdict-fake" if is_ai else "verdict-real"
804
+ icon = "πŸ€– AI GENERATED" if is_ai else "πŸ“· REAL IMAGE"
805
+
806
+ st.markdown(f"""
807
+ <div class="verdict-wrap">
808
+ <div class="verdict-badge {badge_class}">{icon}</div>
809
+ <div class="verdict-conf">Detection confidence: <span>{score:.1f}%</span></div>
810
+ </div>
811
+ """, unsafe_allow_html=True)
812
+
813
+ # Gauges
814
+ ai_score = score if is_ai else 100 - score
815
+ real_score = 100 - ai_score
816
+ g1, g2 = st.columns(2)
817
+ with g1:
818
+ st.plotly_chart(gauge_chart(ai_score, "AI PROBABILITY", "#ef4444"), use_container_width=True)
819
+ with g2:
820
+ st.plotly_chart(gauge_chart(real_score, "REAL PROBABILITY", "#10b981"), use_container_width=True)
821
+
822
+ # Confidence bar
823
+ fig_bar = bar_chart(
824
+ ["AI Generated", "Real / Authentic"],
825
+ [ai_score, real_score],
826
+ ["#ef4444" if ai_score > real_score else "#475569",
827
+ "#10b981" if real_score >= ai_score else "#475569"]
828
+ )
829
+ st.markdown('<div class="card"><div class="card-label">πŸ“Š Score Breakdown</div>', unsafe_allow_html=True)
830
+ st.plotly_chart(fig_bar, use_container_width=True)
831
+ st.markdown('</div>', unsafe_allow_html=True)
832
+
833
+ # Summary
834
+ risk = "HIGH" if ai_score > 75 else "MEDIUM" if ai_score > 45 else "LOW"
835
+ risk_col = "#ef4444" if ai_score > 75 else "#f59e0b" if ai_score > 45 else "#10b981"
836
+ st.markdown(f"""
837
+ <div class="card">
838
+ <div class="card-label">🧠 Forensic Summary</div>
839
+ <p style="font-size:0.85rem; color:#94a3b8; line-height:1.7; margin:0;">
840
+ Forensic analysis indicates this image is
841
+ <strong style="color:{'#ef4444' if is_ai else '#10b981'}">
842
+ {'likely AI-generated' if is_ai else 'likely authentic'}
843
+ </strong>
844
+ with <strong style="color:#e2e8f0">{score:.1f}%</strong> confidence.<br>
845
+ AI generation risk level:
846
+ <strong style="color:{risk_col}">{risk}</strong>.
847
+ {'<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>'}
848
+ </p>
849
+ </div>
850
+ """, unsafe_allow_html=True)
851
+
852
+ elif not uploaded:
853
+ st.markdown("""
854
+ <div class="card" style="text-align:center; padding:3rem 1rem; border-style:dashed;">
855
+ <div style="font-size:3rem; margin-bottom:1rem; opacity:0.3;">πŸ–ΌοΈ</div>
856
+ <div style="font-family:'Space Mono',monospace; font-size:0.75rem;
857
+ color:#64748b; letter-spacing:0.1em;">
858
+ NO IMAGE UPLOADED
859
+ </div>
860
+ <div style="font-size:0.8rem; color:#475569; margin-top:0.5rem;">
861
+ Upload an image and click Scan
862
+ </div>
863
+ </div>
864
+ """, unsafe_allow_html=True)
865
+
866
+ st.markdown('</div>', unsafe_allow_html=True)
867
+
868
+
869
+ # ══════════════════════════════════════════════
870
+ # TAB 3 β€” ABOUT
871
+ # ══════════════════════════════════════════════
872
+ with tab3:
873
+ st.markdown('<div class="content-area">', unsafe_allow_html=True)
874
+
875
+ st.markdown("""
876
+ <div class="section-header">βš™οΈ System Architecture & Models</div>
877
+ <div class="section-sub">Technical details about the AI models and pipeline powering DeepTrace AI.</div>
878
+
879
+ <div class="about-grid">
880
+ <div class="about-card">
881
+ <div class="about-card-icon">🧠</div>
882
+ <div class="about-card-title">NLP Engine</div>
883
+ <div class="about-card-desc">
884
+ RoBERTa-based transformer fine-tuned on 72,000+ news articles.
885
+ Classifies content as FAKE or REAL with confidence scoring.
886
+ <br><br>
887
+ <span style="font-family:'Space Mono',monospace; font-size:0.65rem; color:#00d4ff;">
888
+ hamzab/roberta-fake-news-classification
889
+ </span>
890
+ </div>
891
+ </div>
892
+ <div class="about-card">
893
+ <div class="about-card-icon">πŸ‘οΈ</div>
894
+ <div class="about-card-title">Vision Engine</div>
895
+ <div class="about-card-desc">
896
+ EfficientNetB3 transfer learning model fine-tuned on
897
+ AI-generated vs real image datasets. Detects synthetic textures
898
+ and generation artifacts.
899
+ <br><br>
900
+ <span style="font-family:'Space Mono',monospace; font-size:0.65rem; color:#00d4ff;">
901
+ Muniba930/image-detector
902
+ </span>
903
+ </div>
904
+ </div>
905
+ <div class="about-card">
906
+ <div class="about-card-icon">πŸ”</div>
907
+ <div class="about-card-title">Manipulation Analyser</div>
908
+ <div class="about-card-desc">
909
+ Rule-based linguistic analyser detecting clickbait keywords,
910
+ fear-based language, excessive punctuation, and emotional
911
+ manipulation patterns.
912
+ <br><br>
913
+ <span style="font-family:'Space Mono',monospace; font-size:0.65rem; color:#00d4ff;">
914
+ Custom lexical engine
915
+ </span>
916
+ </div>
917
+ </div>
918
+ </div>
919
+ """, unsafe_allow_html=True)
920
+
921
+ st.markdown("<br>", unsafe_allow_html=True)
922
+
923
+ # Tech stack
924
+ st.markdown("""
925
+ <div class="card">
926
+ <div class="card-label">πŸ› οΈ Tech Stack</div>
927
+ <div style="display:flex; flex-wrap:wrap; gap:0.5rem; margin-top:0.5rem;">
928
+ <span class="tag tag-info">Streamlit</span>
929
+ <span class="tag tag-info">Hugging Face</span>
930
+ <span class="tag tag-info">PyTorch</span>
931
+ <span class="tag tag-info">TensorFlow</span>
932
+ <span class="tag tag-info">Transformers</span>
933
+ <span class="tag tag-info">EfficientNetB3</span>
934
+ <span class="tag tag-info">RoBERTa</span>
935
+ <span class="tag tag-info">Plotly</span>
936
+ <span class="tag tag-info">Pillow</span>
937
+ </div>
938
+ </div>
939
+
940
+ <div class="card">
941
+ <div class="card-label">⚠️ Limitations & Disclaimer</div>
942
+ <p style="font-size:0.85rem; color:#94a3b8; line-height:1.8; margin:0;">
943
+ β€’ This tool is for <strong style="color:#e2e8f0">research and educational purposes</strong>
944
+ β€” not a definitive fact-checker.<br>
945
+ β€’ Image model performance is limited by training data diversity;
946
+ very recent AI generators may not be detected.<br>
947
+ β€’ Always cross-reference with trusted news sources before
948
+ drawing conclusions.<br>
949
+ β€’ The manipulation score is heuristic-based and may produce
950
+ false positives on legitimate breaking news.
951
+ </p>
952
+ </div>
953
+ """, unsafe_allow_html=True)
954
+
955
+ st.markdown("""
956
+ <div style="text-align:center; padding:2rem 0; color:#334155;
957
+ font-family:'Space Mono',monospace; font-size:0.65rem; letter-spacing:0.1em;">
958
+ DEEPTRACE AI · MULTI-MODAL MISINFORMATION DETECTION · BUILT WITH ❀️ USING OPEN-SOURCE AI
959
+ </div>
960
+ """, unsafe_allow_html=True)
961
+
962
+ st.markdown('</div>', unsafe_allow_html=True)