Files changed (1) hide show
  1. src/app.py +209 -147
src/app.py CHANGED
@@ -4,84 +4,72 @@ import pandas as pd
4
  import numpy as np
5
  from pathlib import Path
6
  import sys
7
- import plotly.express as px
8
  import plotly.graph_objects as go
9
  from transformers import BertTokenizer
10
  import nltk
11
 
12
  # Download required NLTK data
13
- try:
14
- nltk.data.find('tokenizers/punkt')
15
- except LookupError:
16
- nltk.download('punkt')
17
- try:
18
- nltk.data.find('corpora/stopwords')
19
- except LookupError:
20
- nltk.download('stopwords')
21
- try:
22
- nltk.data.find('tokenizers/punkt_tab')
23
- except LookupError:
24
- nltk.download('punkt_tab')
25
- try:
26
- nltk.data.find('corpora/wordnet')
27
- except LookupError:
28
- nltk.download('wordnet')
29
 
30
  # Add project root to Python path
31
  project_root = Path(__file__).parent.parent
32
  sys.path.append(str(project_root))
33
 
34
  from src.models.hybrid_model import HybridFakeNewsDetector
35
- from src.config.config import *
36
  from src.data.preprocessor import TextPreprocessor
37
 
38
- # Custom CSS with Poppins font and increased font sizes
39
  st.markdown("""
40
  <style>
41
- /* Import Google Fonts */
42
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
43
 
44
- /* Global Styles */
45
  * {
46
- margin: 0;
47
- padding: 0;
48
  box-sizing: border-box;
49
  }
50
 
51
  .stApp {
52
- font-family: 'Poppins', sans-serif;
53
- background: #f8fafc;
54
  min-height: 100vh;
55
- color: #1a202c;
56
  }
57
 
58
- /* Ensure sidebar is visible */
59
- #MainMenu {visibility: visible;}
60
  footer {visibility: hidden;}
61
  .stDeployButton {display: none;}
62
  header {visibility: hidden;}
63
  .stApp > header {visibility: hidden;}
64
 
65
- /* Container */
66
- .container {
67
  max-width: 1200px;
68
  margin: 0 auto;
69
- padding: 1.5rem;
70
  }
71
 
72
- /* Header */
73
- .header {
74
- padding: 1.5rem 0;
75
  text-align: center;
 
 
76
  }
77
 
78
  .header-title {
79
- font-size: 2.5rem;
80
  font-weight: 700;
81
- color: #1a202c;
82
- display: inline-flex;
83
- align-items: center;
84
- gap: 0.5rem;
85
  }
86
 
87
  /* Hero Section */
@@ -90,6 +78,7 @@ st.markdown("""
90
  align-items: center;
91
  gap: 2rem;
92
  margin-bottom: 2rem;
 
93
  }
94
 
95
  .hero-left {
@@ -112,16 +101,16 @@ st.markdown("""
112
  }
113
 
114
  .hero-title {
115
- font-size: 3rem;
116
  font-weight: 700;
117
- color: #1a202c;
118
  margin-bottom: 0.5rem;
119
  }
120
 
121
  .hero-text {
122
- font-size: 1.2rem;
123
- color: #4a5568;
124
- line-height: 1.5;
125
  max-width: 450px;
126
  }
127
 
@@ -129,19 +118,20 @@ st.markdown("""
129
  .about-section {
130
  margin-bottom: 2rem;
131
  text-align: center;
 
132
  }
133
 
134
  .about-title {
135
- font-size: 2.2rem;
136
  font-weight: 600;
137
- color: #1a202c;
138
  margin-bottom: 0.5rem;
139
  }
140
 
141
  .about-text {
142
- font-size: 1.1rem;
143
- color: #4a5568;
144
- line-height: 1.5;
145
  max-width: 600px;
146
  margin: 0 auto;
147
  }
@@ -156,8 +146,7 @@ st.markdown("""
156
  border-radius: 8px !important;
157
  border: 1px solid #d1d5db !important;
158
  padding: 1rem !important;
159
- font-size: 1.1rem !important;
160
- font-family: 'Poppins', sans-serif !important;
161
  background: #ffffff !important;
162
  min-height: 150px !important;
163
  transition: all 0.2s ease !important;
@@ -179,12 +168,12 @@ st.markdown("""
179
  color: white !important;
180
  border-radius: 8px !important;
181
  padding: 0.75rem 2rem !important;
182
- font-size: 1.1rem !important;
183
  font-weight: 600 !important;
184
- font-family: 'Poppins', sans-serif !important;
185
  transition: all 0.2s ease !important;
186
  border: none !important;
187
  width: 100% !important;
 
188
  }
189
 
190
  .stButton > button:hover {
@@ -197,6 +186,9 @@ st.markdown("""
197
  margin-top: 1rem;
198
  padding: 1rem;
199
  border-radius: 8px;
 
 
 
200
  }
201
 
202
  .result-card {
@@ -218,7 +210,7 @@ st.markdown("""
218
 
219
  .prediction-badge {
220
  font-weight: 600;
221
- font-size: 1.1rem;
222
  margin-bottom: 0.5rem;
223
  display: flex;
224
  align-items: center;
@@ -228,7 +220,7 @@ st.markdown("""
228
  .confidence-score {
229
  font-weight: 600;
230
  margin-left: auto;
231
- font-size: 1.1rem;
232
  }
233
 
234
  /* Chart Containers */
@@ -236,78 +228,149 @@ st.markdown("""
236
  padding: 1rem;
237
  border-radius: 8px;
238
  margin: 1rem 0;
 
 
 
239
  }
240
 
241
- /* Sidebar Styling */
242
- .stSidebar {
243
- background: #ffffff;
244
- border-right: 1px solid #e5e7eb;
 
 
 
245
  }
246
 
247
- .stSidebar .sidebar-content {
248
- padding: 1rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  }
250
  </style>
251
  """, unsafe_allow_html=True)
252
 
253
  @st.cache_resource
254
- def load_model_and_tokenizer():
255
  """Load the model and tokenizer (cached)."""
256
- model = HybridFakeNewsDetector(
257
- bert_model_name=BERT_MODEL_NAME,
258
- lstm_hidden_size=LSTM_HIDDEN_SIZE,
259
- lstm_num_layers=LSTM_NUM_LAYERS,
260
- dropout_rate=DROPOUT_RATE
261
- )
262
- state_dict = torch.load(SAVED_MODELS_DIR / "final_model.pt", map_location=torch.device('cpu'))
263
- model_state_dict = model.state_dict()
264
- filtered_state_dict = {k: v for k, v in state_dict.items() if k in model_state_dict}
265
- model.load_state_dict(filtered_state_dict, strict=False)
266
- model.eval()
267
- tokenizer = BertTokenizer.from_pretrained(BERT_MODEL_NAME)
268
- return model, tokenizer
 
 
 
 
 
 
 
 
269
 
270
  @st.cache_resource
271
- def get_preprocessor():
272
  """Get the text preprocessor (cached)."""
273
- return TextPreprocessor()
 
 
 
 
274
 
275
- def predict_news(text):
276
  """Predict if the given news is fake or real."""
277
  model, tokenizer = load_model_and_tokenizer()
 
 
278
  preprocessor = get_preprocessor()
279
- processed_text = preprocessor.preprocess_text(text)
280
- encoding = tokenizer.encode_plus(
281
- processed_text,
282
- add_special_tokens=True,
283
- max_length=MAX_SEQUENCE_LENGTH,
284
- padding='max_length',
285
- truncation=True,
286
- return_attention_mask=True,
287
- return_tensors='pt'
288
- )
289
- with torch.no_grad():
290
- outputs = model(
291
- encoding['input_ids'],
292
- encoding['attention_mask']
293
  )
294
- probabilities = torch.softmax(outputs['logits'], dim=1)
295
- prediction = torch.argmax(outputs['logits'], dim=1)
296
- attention_weights = outputs['attention_weights']
297
- attention_weights_np = attention_weights[0].cpu().numpy()
298
- return {
299
- 'prediction': prediction.item(),
300
- 'label': 'FAKE' if prediction.item() == 1 else 'REAL',
301
- 'confidence': torch.max(probabilities, dim=1)[0].item(),
302
- 'probabilities': {
303
- 'REAL': probabilities[0][0].item(),
304
- 'FAKE': probabilities[0][1].item()
305
- },
306
- 'attention_weights': attention_weights_np
307
- }
308
-
309
- def plot_confidence(probabilities):
 
 
 
 
 
 
 
 
310
  """Plot prediction confidence with simplified styling."""
 
 
311
  fig = go.Figure(data=[
312
  go.Bar(
313
  x=list(probabilities.keys()),
@@ -330,8 +393,10 @@ def plot_confidence(probabilities):
330
  )
331
  return fig
332
 
333
- def plot_attention(text, attention_weights):
334
  """Plot attention weights with simplified styling."""
 
 
335
  tokens = text.split()[:20]
336
  attention_weights = attention_weights[:len(tokens)]
337
  if isinstance(attention_weights, (list, np.ndarray)):
@@ -358,47 +423,43 @@ def plot_attention(text, attention_weights):
358
  return fig
359
 
360
  def main():
 
 
361
 
362
- # Header
363
  st.markdown("""
364
- <div class="header">
365
- <div class="container">
366
- <h1 class="header-title">πŸ›‘οΈ TruthCheck</h1>
367
- </div>
368
  </div>
369
  """, unsafe_allow_html=True)
370
 
371
  # Hero Section
372
  st.markdown("""
373
- <div class="container">
374
- <div class="hero">
375
- <div class="hero-left">
376
- <h2 class="hero-title">Instant Fake News Detection</h2>
377
- <p class="hero-text">
378
- Verify news articles with our AI-powered tool, driven by BERT and BiLSTM for fast and accurate authenticity analysis.
379
- </p>
380
- </div>
381
- <div class="hero-right">
382
- <img src="https://images.unsplash.com/photo-1593642532973-d31b97d0fad2?ixlib=rb-4.0.3&auto=format&fit=crop&w=500&q=80" alt="Fake News Detector" onerror="this.src='https://via.placeholder.com/500x300.png?text=Fake+News+Detector'">
383
- </div>
384
  </div>
385
  </div>
386
  """, unsafe_allow_html=True)
387
 
388
  # About Section
389
  st.markdown("""
390
- <div class="container">
391
- <div class="about-section">
392
- <h2 class="about-title">About TruthCheck</h2>
393
- <p class="about-text">
394
- TruthCheck uses a hybrid BERT-BiLSTM model to detect fake news with high accuracy. Paste an article below for instant analysis.
395
- </p>
396
- </div>
397
  </div>
398
  """, unsafe_allow_html=True)
399
 
400
  # Input Section
401
- st.markdown('<div class="container"><div class="input-container">', unsafe_allow_html=True)
402
  news_text = st.text_area(
403
  "Analyze a News Article",
404
  height=150,
@@ -408,18 +469,16 @@ def main():
408
  st.markdown('</div>', unsafe_allow_html=True)
409
 
410
  # Analyze Button
411
- st.markdown('<div class="container">', unsafe_allow_html=True)
412
  col1, col2, col3 = st.columns([1, 2, 1])
413
  with col2:
414
  analyze_button = st.button("πŸ” Analyze Now", key="analyze_button")
415
- st.markdown('</div>', unsafe_allow_html=True)
416
 
417
  if analyze_button:
418
  if news_text and len(news_text.strip()) > 10:
419
  with st.spinner("Analyzing article..."):
420
- try:
421
- result = predict_news(news_text)
422
- st.markdown('<div class="container"><div class="results-container">', unsafe_allow_html=True)
423
 
424
  # Prediction Result
425
  col1, col2 = st.columns([1, 1], gap="medium")
@@ -428,14 +487,14 @@ def main():
428
  st.markdown(f'''
429
  <div class="result-card fake-news">
430
  <div class="prediction-badge">🚨 Fake News Detected <span class="confidence-score">{result["confidence"]:.1%}</span></div>
431
- <p>Our AI has identified this content as likely misinformation based on linguistic patterns and content analysis.</p>
432
  </div>
433
  ''', unsafe_allow_html=True)
434
  else:
435
  st.markdown(f'''
436
  <div class="result-card real-news">
437
  <div class="prediction-badge">βœ… Authentic News <span class="confidence-score">{result["confidence"]:.1%}</span></div>
438
- <p>This content appears to be legitimate based on professional writing style and factual consistency.</p>
439
  </div>
440
  ''', unsafe_allow_html=True)
441
 
@@ -447,15 +506,18 @@ def main():
447
  # Attention Analysis
448
  st.markdown('<div class="chart-container">', unsafe_allow_html=True)
449
  st.plotly_chart(plot_attention(news_text, result['attention_weights']), use_container_width=True)
450
- st.markdown('</div></div></div>', unsafe_allow_html=True)
451
- except Exception as e:
452
- st.markdown('<div class="container">', unsafe_allow_html=True)
453
- st.error(f"Error: {str(e)}. Please try again or contact support.")
454
- st.markdown('</div>', unsafe_allow_html=True)
455
  else:
456
- st.markdown('<div class="container">', unsafe_allow_html=True)
457
  st.error("Please enter a news article (at least 10 words) for analysis.")
458
- st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
459
 
460
  if __name__ == "__main__":
461
  main()
 
4
  import numpy as np
5
  from pathlib import Path
6
  import sys
 
7
  import plotly.graph_objects as go
8
  from transformers import BertTokenizer
9
  import nltk
10
 
11
  # Download required NLTK data
12
+ nltk_data = {
13
+ 'tokenizers/punkt': 'punkt',
14
+ 'corpora/stopwords': 'stopwords',
15
+ 'tokenizers/punkt_tab': 'punkt_tab',
16
+ 'corpora/wordnet': 'wordnet'
17
+ }
18
+ for resource, package in nltk_data.items():
19
+ try:
20
+ nltk.data.find(resource)
21
+ except LookupError:
22
+ nltk.download(package)
 
 
 
 
 
23
 
24
  # Add project root to Python path
25
  project_root = Path(__file__).parent.parent
26
  sys.path.append(str(project_root))
27
 
28
  from src.models.hybrid_model import HybridFakeNewsDetector
29
+ from src.config.config import BERT_MODEL_NAME, LSTM_HIDDEN_SIZE, LSTM_NUM_LAYERS, DROPOUT_RATE, SAVED_MODELS_DIR, MAX_SEQUENCE_LENGTH
30
  from src.data.preprocessor import TextPreprocessor
31
 
32
+ # Custom CSS with Poppins font
33
  st.markdown("""
34
  <style>
 
35
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
36
 
 
37
  * {
38
+ font-family: 'Poppins', sans-serif !important;
 
39
  box-sizing: border-box;
40
  }
41
 
42
  .stApp {
43
+ background: #ffffff;
 
44
  min-height: 100vh;
45
+ color: #1f2a44;
46
  }
47
 
48
+ #MainMenu {visibility: hidden;}
 
49
  footer {visibility: hidden;}
50
  .stDeployButton {display: none;}
51
  header {visibility: hidden;}
52
  .stApp > header {visibility: hidden;}
53
 
54
+ /* Main Container */
55
+ .main-container {
56
  max-width: 1200px;
57
  margin: 0 auto;
58
+ padding: 1rem 2rem;
59
  }
60
 
61
+ /* Header Section */
62
+ .header-section {
 
63
  text-align: center;
64
+ margin-bottom: 2.5rem;
65
+ padding: 1.5rem 0;
66
  }
67
 
68
  .header-title {
69
+ font-size: 2.25rem;
70
  font-weight: 700;
71
+ color: #1f2a44;
72
+ margin: 0;
 
 
73
  }
74
 
75
  /* Hero Section */
 
78
  align-items: center;
79
  gap: 2rem;
80
  margin-bottom: 2rem;
81
+ padding: 0 1rem;
82
  }
83
 
84
  .hero-left {
 
101
  }
102
 
103
  .hero-title {
104
+ font-size: 2.5rem;
105
  font-weight: 700;
106
+ color: #1f2a44;
107
  margin-bottom: 0.5rem;
108
  }
109
 
110
  .hero-text {
111
+ font-size: 1rem;
112
+ color: #6b7280;
113
+ line-height: 1.6;
114
  max-width: 450px;
115
  }
116
 
 
118
  .about-section {
119
  margin-bottom: 2rem;
120
  text-align: center;
121
+ padding: 0 1rem;
122
  }
123
 
124
  .about-title {
125
+ font-size: 1.75rem;
126
  font-weight: 600;
127
+ color: #1f2a44;
128
  margin-bottom: 0.5rem;
129
  }
130
 
131
  .about-text {
132
+ font-size: 0.95rem;
133
+ color: #6b7280;
134
+ line-height: 1.6;
135
  max-width: 600px;
136
  margin: 0 auto;
137
  }
 
146
  border-radius: 8px !important;
147
  border: 1px solid #d1d5db !important;
148
  padding: 1rem !important;
149
+ font-size: 1rem !important;
 
150
  background: #ffffff !important;
151
  min-height: 150px !important;
152
  transition: all 0.2s ease !important;
 
168
  color: white !important;
169
  border-radius: 8px !important;
170
  padding: 0.75rem 2rem !important;
171
+ font-size: 1rem !important;
172
  font-weight: 600 !important;
 
173
  transition: all 0.2s ease !important;
174
  border: none !important;
175
  width: 100% !important;
176
+ max-width: 300px;
177
  }
178
 
179
  .stButton > button:hover {
 
186
  margin-top: 1rem;
187
  padding: 1rem;
188
  border-radius: 8px;
189
+ max-width: 1200px;
190
+ margin-left: auto;
191
+ margin-right: auto;
192
  }
193
 
194
  .result-card {
 
210
 
211
  .prediction-badge {
212
  font-weight: 600;
213
+ font-size: 1rem;
214
  margin-bottom: 0.5rem;
215
  display: flex;
216
  align-items: center;
 
220
  .confidence-score {
221
  font-weight: 600;
222
  margin-left: auto;
223
+ font-size: 1rem;
224
  }
225
 
226
  /* Chart Containers */
 
228
  padding: 1rem;
229
  border-radius: 8px;
230
  margin: 1rem 0;
231
+ max-width: 1200px;
232
+ margin-left: auto;
233
+ margin-right: auto;
234
  }
235
 
236
+ /* Footer */
237
+ .footer {
238
+ border-top: 1px solid #e5e7eb;
239
+ padding: 1.5rem 0;
240
+ text-align: center;
241
+ max-width: 1200px;
242
+ margin: 2rem auto 0;
243
  }
244
 
245
+ /* Responsive Design */
246
+ @media (max-width: 1024px) {
247
+ .hero {
248
+ flex-direction: column;
249
+ text-align: center;
250
+ }
251
+ .hero-right img {
252
+ max-width: 80%;
253
+ }
254
+ }
255
+
256
+ @media (max-width: 768px) {
257
+ .header-title {
258
+ font-size: 1.75rem;
259
+ }
260
+ .hero-title {
261
+ font-size: 2rem;
262
+ }
263
+ .hero-text {
264
+ font-size: 0.9rem;
265
+ }
266
+ .about-title {
267
+ font-size: 1.5rem;
268
+ }
269
+ .about-text {
270
+ font-size: 0.9rem;
271
+ }
272
+ }
273
+
274
+ @media (max-width: 480px) {
275
+ .header-title {
276
+ font-size: 1.5rem;
277
+ }
278
+ .hero-title {
279
+ font-size: 1.75rem;
280
+ }
281
+ .hero-text {
282
+ font-size: 0.85rem;
283
+ }
284
+ .about-title {
285
+ font-size: 1.25rem;
286
+ }
287
+ .about-text {
288
+ font-size: 0.85rem;
289
+ }
290
  }
291
  </style>
292
  """, unsafe_allow_html=True)
293
 
294
  @st.cache_resource
295
+ def load_model_and_tokenizer() -> tuple[HybridFakeNewsDetector, BertTokenizer] | tuple[None, None]:
296
  """Load the model and tokenizer (cached)."""
297
+ try:
298
+ model = HybridFakeNewsDetector(
299
+ bert_model_name=BERT_MODEL_NAME,
300
+ lstm_hidden_size=LSTM_HIDDEN_SIZE,
301
+ lstm_num_layers=LSTM_NUM_LAYERS,
302
+ dropout_rate=DROPOUT_RATE
303
+ )
304
+ model_path = SAVED_MODELS_DIR / "final_model.pt"
305
+ if not model_path.exists():
306
+ st.error("Model file not found. Please ensure 'final_model.pt' is in the models/saved directory.")
307
+ return None, None
308
+ state_dict = torch.load(model_path, map_location=torch.device('cpu'))
309
+ model_state_dict = model.state_dict()
310
+ filtered_state_dict = {k: v for k, v in state_dict.items() if k in model_state_dict}
311
+ model.load_state_dict(filtered_state_dict, strict=False)
312
+ model.eval()
313
+ tokenizer = BertTokenizer.from_pretrained(BERT_MODEL_NAME)
314
+ return model, tokenizer
315
+ except Exception as e:
316
+ st.error(f"Error loading model or tokenizer: {str(e)}")
317
+ return None, None
318
 
319
  @st.cache_resource
320
+ def get_preprocessor() -> TextPreprocessor | None:
321
  """Get the text preprocessor (cached)."""
322
+ try:
323
+ return TextPreprocessor()
324
+ except Exception as e:
325
+ st.error(f"Error initializing preprocessor: {str(e)}")
326
+ return None
327
 
328
+ def predict_news(text: str) -> dict | None:
329
  """Predict if the given news is fake or real."""
330
  model, tokenizer = load_model_and_tokenizer()
331
+ if model is None or tokenizer is None:
332
+ return None
333
  preprocessor = get_preprocessor()
334
+ if preprocessor is None:
335
+ return None
336
+ try:
337
+ processed_text = preprocessor.preprocess_text(text)
338
+ encoding = tokenizer.encode_plus(
339
+ processed_text,
340
+ add_special_tokens=True,
341
+ max_length=MAX_SEQUENCE_LENGTH,
342
+ padding='max_length',
343
+ truncation=True,
344
+ return_attention_mask=True,
345
+ return_tensors='pt'
 
 
346
  )
347
+ with torch.no_grad():
348
+ outputs = model(
349
+ encoding['input_ids'],
350
+ encoding['attention_mask']
351
+ )
352
+ probabilities = torch.softmax(outputs['logits'], dim=1)
353
+ prediction = torch.argmax(outputs['logits'], dim=1)
354
+ attention_weights = outputs.get('attention_weights', torch.zeros(1))
355
+ attention_weights_np = attention_weights[0].cpu().numpy()
356
+ return {
357
+ 'prediction': prediction.item(),
358
+ 'label': 'FAKE' if prediction.item() == 1 else 'REAL',
359
+ 'confidence': torch.max(probabilities, dim=1)[0].item(),
360
+ 'probabilities': {
361
+ 'REAL': probabilities[0][0].item(),
362
+ 'FAKE': probabilities[0][1].item()
363
+ },
364
+ 'attention_weights': attention_weights_np
365
+ }
366
+ except Exception as e:
367
+ st.error(f"Prediction error: {str(e)}")
368
+ return None
369
+
370
+ def plot_confidence(probabilities: dict) -> go.Figure:
371
  """Plot prediction confidence with simplified styling."""
372
+ if not probabilities or not isinstance(probabilities, dict):
373
+ return go.Figure()
374
  fig = go.Figure(data=[
375
  go.Bar(
376
  x=list(probabilities.keys()),
 
393
  )
394
  return fig
395
 
396
+ def plot_attention(text: str, attention_weights: np.ndarray) -> go.Figure:
397
  """Plot attention weights with simplified styling."""
398
+ if not text or not attention_weights.size:
399
+ return go.Figure()
400
  tokens = text.split()[:20]
401
  attention_weights = attention_weights[:len(tokens)]
402
  if isinstance(attention_weights, (list, np.ndarray)):
 
423
  return fig
424
 
425
  def main():
426
+ # Main Container
427
+ st.markdown('<div class="main-container">', unsafe_allow_html=True)
428
 
429
+ # Header Section
430
  st.markdown("""
431
+ <div class="header-section">
432
+ <h1 class="header-title">πŸ›‘οΈ TruthCheck - Advanced Fake News Detector</h1>
 
 
433
  </div>
434
  """, unsafe_allow_html=True)
435
 
436
  # Hero Section
437
  st.markdown("""
438
+ <div class="hero">
439
+ <div class="hero-left">
440
+ <h2 class="hero-title">Instant Fake News Detection</h2>
441
+ <p class="hero-text">
442
+ Verify news articles with our AI-powered tool, driven by advanced BERT and BiLSTM models for accurate authenticity analysis.
443
+ </p>
444
+ </div>
445
+ <div class="hero-right">
446
+ <img src="https://images.pexels.com/photos/267350/pexels-photo-267350.jpeg?auto=compress&cs=tinysrgb&w=500" alt="Fake News Illustration" onerror="this.src='https://via.placeholder.com/500x300.png?text=Fake+News+Illustration'">
 
 
447
  </div>
448
  </div>
449
  """, unsafe_allow_html=True)
450
 
451
  # About Section
452
  st.markdown("""
453
+ <div class="about-section">
454
+ <h2 class="about-title">About TruthCheck</h2>
455
+ <p class="about-text">
456
+ TruthCheck harnesses a hybrid BERT-BiLSTM model to detect fake news with high precision. Simply paste an article below to analyze its authenticity instantly.
457
+ </p>
 
 
458
  </div>
459
  """, unsafe_allow_html=True)
460
 
461
  # Input Section
462
+ st.markdown('<div class="input-container">', unsafe_allow_html=True)
463
  news_text = st.text_area(
464
  "Analyze a News Article",
465
  height=150,
 
469
  st.markdown('</div>', unsafe_allow_html=True)
470
 
471
  # Analyze Button
 
472
  col1, col2, col3 = st.columns([1, 2, 1])
473
  with col2:
474
  analyze_button = st.button("πŸ” Analyze Now", key="analyze_button")
 
475
 
476
  if analyze_button:
477
  if news_text and len(news_text.strip()) > 10:
478
  with st.spinner("Analyzing article..."):
479
+ result = predict_news(news_text)
480
+ if result:
481
+ st.markdown('<div class="results-container">', unsafe_allow_html=True)
482
 
483
  # Prediction Result
484
  col1, col2 = st.columns([1, 1], gap="medium")
 
487
  st.markdown(f'''
488
  <div class="result-card fake-news">
489
  <div class="prediction-badge">🚨 Fake News Detected <span class="confidence-score">{result["confidence"]:.1%}</span></div>
490
+ <p>Our AI has identified this content as likely misinformation based on linguistic patterns and context.</p>
491
  </div>
492
  ''', unsafe_allow_html=True)
493
  else:
494
  st.markdown(f'''
495
  <div class="result-card real-news">
496
  <div class="prediction-badge">βœ… Authentic News <span class="confidence-score">{result["confidence"]:.1%}</span></div>
497
+ <p>This content appears legitimate based on professional writing style and factual consistency.</p>
498
  </div>
499
  ''', unsafe_allow_html=True)
500
 
 
506
  # Attention Analysis
507
  st.markdown('<div class="chart-container">', unsafe_allow_html=True)
508
  st.plotly_chart(plot_attention(news_text, result['attention_weights']), use_container_width=True)
509
+ st.markdown('</div></div>', unsafe_allow_html=True)
 
 
 
 
510
  else:
 
511
  st.error("Please enter a news article (at least 10 words) for analysis.")
512
+
513
+ # Footer
514
+ st.markdown("---")
515
+ st.markdown(
516
+ '<p style="text-align: center; font-weight: 600; font-size: 16px;">πŸ’» Developed with ❀️ using Streamlit | Β© 2025</p>',
517
+ unsafe_allow_html=True
518
+ )
519
+
520
+ st.markdown('</div>', unsafe_allow_html=True) # Close main-container
521
 
522
  if __name__ == "__main__":
523
  main()