isha0110 commited on
Commit
c86b565
·
verified ·
1 Parent(s): 817d658

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +630 -401
app.py CHANGED
@@ -4,484 +4,713 @@ import torch.nn as nn
4
  from transformers import AutoTokenizer, AutoModel
5
  import numpy as np
6
  import os
7
- from typing import Dict, List, Tuple, Optional
8
  import json
9
  from datetime import datetime
10
- from collections import defaultdict
 
 
11
 
12
- print("🌊 Sentiment Analyzer Starting...")
13
 
14
- MODEL_NAME = "roberta-base"
15
- SENTIMENTS = ["anger", "fear", "joy", "sadness", "surprise"]
16
- OPTIMIZED_THRESHOLDS = [0.24722222, 0.61666667, 0.59722222, 0.44166667, 0.46111111]
17
- SEQUENCE_LENGTH = 200
18
- WEIGHTS_FILE = "roberta.pth"
 
 
 
19
 
20
- SENTIMENT_CONFIG = {
 
21
  "anger": {
22
- "icon": "🔥",
23
- "primary": "#ff6b6b",
24
- "secondary": "#ee5a6f",
25
- "bg": "linear-gradient(120deg, #ff6b6b 0%, #c92a2a 100%)",
26
- "label": "Anger Detected",
27
- "levels": ["Annoyed", "Frustrated", "Angry", "Enraged"]
28
  },
29
  "fear": {
30
- "icon": "",
31
- "primary": "#845ef7",
32
- "secondary": "#7048e8",
33
- "bg": "linear-gradient(120deg, #845ef7 0%, #5f3dc4 100%)",
34
- "label": "Fear Detected",
35
- "levels": ["Worried", "Anxious", "Fearful", "Terrified"]
36
  },
37
  "joy": {
38
- "icon": "",
39
- "primary": "#ffd43b",
40
- "secondary": "#fab005",
41
- "bg": "linear-gradient(120deg, #ffd43b 0%, #f59f00 100%)",
42
- "label": "Joy Detected",
43
- "levels": ["Content", "Happy", "Joyful", "Ecstatic"]
44
  },
45
  "sadness": {
46
- "icon": "💧",
47
- "primary": "#4dabf7",
48
- "secondary": "#339af0",
49
- "bg": "linear-gradient(120deg, #4dabf7 0%, #1971c2 100%)",
50
- "label": "Sadness Detected",
51
- "levels": ["Melancholic", "Sad", "Sorrowful", "Devastated"]
52
  },
53
  "surprise": {
54
- "icon": "💫",
55
- "primary": "#ff6bc2",
56
- "secondary": "#e64980",
57
- "bg": "linear-gradient(120deg, #ff6bc2 0%, #c2255c 100%)",
58
- "label": "Surprise Detected",
59
- "levels": ["Curious", "Surprised", "Shocked", "Stunned"]
60
  }
61
  }
62
 
63
- class SentimentModel(nn.Module):
64
- def __init__(self):
 
65
  super().__init__()
66
- self.encoder = AutoModel.from_pretrained(MODEL_NAME)
67
- self.dropout_layer = nn.Dropout(0.35)
68
- self.classifier = nn.Linear(768, 5)
69
-
70
- def forward(self, tokens, mask):
71
- enc_output = self.encoder(input_ids=tokens, attention_mask=mask)
72
- pooled_output = enc_output.pooler_output if hasattr(enc_output, "pooler_output") else enc_output.last_hidden_state[:, 0]
73
- return self.classifier(self.dropout_layer(pooled_output))
74
 
75
- class AnalyzerState:
 
76
  def __init__(self):
77
- self.compute_device = "cuda" if torch.cuda.is_available() else "cpu"
78
- self.neural_net = None
79
- self.text_tokenizer = None
80
- self.is_loaded = False
81
- self.total_analyses = 0
82
- self.analysis_log = []
83
- self.sentiment_counts = defaultdict(int)
84
-
85
- app_state = AnalyzerState()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- def apply_sigmoid(x):
88
- return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
 
 
 
 
 
89
 
90
- def calculate_strength(probability):
91
- if probability >= 0.80: return "Critical", "#fa5252"
92
- elif probability >= 0.60: return "Strong", "#ff8787"
93
- elif probability >= 0.40: return "Moderate", "#ffc078"
94
- elif probability >= 0.20: return "Weak", "#ffe066"
95
- else: return "Minimal", "#d0d0d0"
96
 
97
- def get_sentiment_level(sentiment_name, prob):
98
- levels = SENTIMENT_CONFIG[sentiment_name]["levels"]
99
- if prob >= 0.80: return levels[3]
100
- elif prob >= 0.60: return levels[2]
101
- elif prob >= 0.40: return levels[1]
102
- else: return levels[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
- def initialize_model():
105
- try:
106
- app_state.neural_net = SentimentModel()
107
- if os.path.exists(WEIGHTS_FILE):
108
- app_state.neural_net.load_state_dict(torch.load(WEIGHTS_FILE, map_location=app_state.compute_device))
109
- status_msg, emoji, gradient = "Model Loaded Successfully", "✅", "linear-gradient(135deg, #20c997 0%, #12b886 100%)"
110
- else:
111
- status_msg, emoji, gradient = "Model Initialized (Untrained)", "⚠️", "linear-gradient(135deg, #fab005 0%, #fd7e14 100%)"
112
-
113
- app_state.neural_net.to(app_state.compute_device).eval()
114
- app_state.text_tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
115
- app_state.is_loaded = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
- status_html = f"""
118
- <div style='background:{gradient};padding:25px;border-radius:16px;box-shadow:0 8px 32px rgba(0,0,0,0.2);'>
119
- <div style='display:flex;align-items:center;gap:20px;'>
120
- <div style='font-size:52px;'>{emoji}</div>
121
- <div style='color:white;'>
122
- <h2 style='margin:0;font-size:28px;font-weight:700;'>{status_msg}</h2>
123
- <p style='margin:10px 0 0;font-size:16px;opacity:0.95;'>
124
- Runtime: {app_state.compute_device.upper()} Accuracy: 87.2% Ready for Analysis
125
- </p>
126
  </div>
127
  </div>
128
- </div>
129
- """
130
- return status_html, _render_stats_widget()
131
-
132
- except Exception as error:
133
- error_html = f"""
134
- <div style='background:linear-gradient(135deg, #fa5252 0%, #e03131 100%);padding:25px;border-radius:16px;box-shadow:0 8px 32px rgba(0,0,0,0.2);'>
135
- <div style='color:white;'>
136
- <h2 style='margin:0;font-size:24px;'>❌ Initialization Failed</h2>
137
- <p style='margin:10px 0 0;font-size:14px;opacity:0.9;'>{str(error)}</p>
 
 
 
 
 
138
  </div>
139
  </div>
140
- """
141
- return error_html, ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- def _render_stats_widget():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  return f"""
145
- <div style='background:linear-gradient(135deg, #667eea 0%, #764ba2 100%);padding:20px;border-radius:12px;box-shadow:0 4px 20px rgba(102,126,234,0.3);'>
146
- <div style='display:grid;grid-template-columns:repeat(3,1fr);gap:20px;color:white;text-align:center;'>
 
 
 
 
 
147
  <div>
148
- <div style='font-size:32px;font-weight:800;color:#ffd43b;'>{app_state.total_analyses}</div>
149
- <div style='font-size:13px;opacity:0.9;margin-top:5px;'>Total Analyses</div>
 
 
 
 
150
  </div>
151
  <div>
152
- <div style='font-size:32px;font-weight:800;color:#20c997;'>5</div>
153
- <div style='font-size:13px;opacity:0.9;margin-top:5px;'>Sentiment Types</div>
 
 
 
 
154
  </div>
155
  <div>
156
- <div style='font-size:32px;font-weight:800;color:#4dabf7;'>87.2%</div>
157
- <div style='font-size:13px;opacity:0.9;margin-top:5px;'>Accuracy</div>
158
- </div>
159
- </div>
160
- </div>
161
- """
162
-
163
- def _render_sentiment_pulse(sentiment, probability, is_active, threshold, position):
164
- config = SENTIMENT_CONFIG[sentiment]
165
- pct = probability * 100
166
- strength_label, strength_color = calculate_strength(probability)
167
-
168
- position_badges = {1: "1st", 2: "2nd", 3: "3rd", 4: "4th", 5: "5th"}
169
- rank_badge = position_badges.get(position, f"{position}th")
170
-
171
- border_style = f"3px solid {config['primary']}" if is_active else "2px solid #2c3e50"
172
- bg_color = "#2c3e50" if is_active else "#1a1f2e"
173
- shadow_style = f"0 8px 24px {config['primary']}40" if is_active else "0 2px 8px rgba(0,0,0,0.2)"
174
-
175
- pulse_html = f"""
176
- <div style='background:{bg_color};padding:22px;margin:10px 0;border-radius:14px;border:{border_style};box-shadow:{shadow_style};transition:all 0.3s ease;'>
177
- <div style='display:flex;justify-content:space-between;align-items:center;'>
178
- <div style='display:flex;gap:15px;align-items:center;flex:1;'>
179
- <div style='font-size:42px;filter:drop-shadow(0 0 12px {config['primary']});'>{config['icon']}</div>
180
- <div style='flex:1;'>
181
- <div style='display:flex;gap:10px;align-items:center;margin-bottom:5px;'>
182
- <span style='font-weight:700;font-size:22px;color:#ecf0f1;'>{sentiment.upper()}</span>
183
- <span style='background:{config['primary']};color:#000;padding:3px 10px;border-radius:10px;font-size:11px;font-weight:700;'>{rank_badge}</span>
184
- </div>
185
- <div style='font-size:14px;color:#95a5a6;font-weight:500;'>{get_sentiment_level(sentiment, probability)}</div>
186
  </div>
187
- </div>
188
- <div style='text-align:right;margin-left:15px;'>
189
- <div style='font-size:28px;font-weight:800;color:{config['primary']};text-shadow:0 0 12px {config['primary']};'>{pct:.1f}%</div>
190
- <div style='font-size:12px;color:{strength_color};font-weight:600;margin-top:3px;'>{strength_label}</div>
191
  </div>
192
  </div>
193
- <div style='position:relative;background:#1a1f2e;height:32px;border-radius:16px;overflow:hidden;margin-top:12px;border:2px solid #34495e;'>
194
- <div style='position:absolute;height:100%;background:{config['bg']};width:{min(pct,100)}%;border-radius:14px;box-shadow:inset 0 0 15px rgba(255,255,255,0.3);transition:width 0.5s ease;'></div>
195
- </div>
196
  </div>
197
  """
198
- return pulse_html
199
 
200
- def _render_overview_panel(probabilities, predictions, input_text):
201
- active_count = sum(predictions)
202
- primary_idx = np.argmax(probabilities)
203
- dominant_sentiment = SENTIMENTS[primary_idx]
204
- active_sentiments = [SENTIMENTS[idx] for idx in range(len(SENTIMENTS)) if predictions[idx] == 1]
205
- sentiment_icons = " ".join([SENTIMENT_CONFIG[s]["icon"] for s in active_sentiments]) if active_sentiments else "➖"
206
- text_length = len(input_text.split())
207
-
208
- overview_html = f"""
209
- <div style='background:linear-gradient(135deg, #667eea 0%, #764ba2 100%);padding:35px;border-radius:18px;box-shadow:0 10px 40px rgba(102,126,234,0.4);border:3px solid #8b5cf6;'>
210
- <div style='text-align:center;color:white;'>
211
- <div style='font-size:64px;filter:drop-shadow(0 0 20px rgba(255,255,255,0.4));margin-bottom:15px;'>{sentiment_icons}</div>
212
- <h1 style='margin:0;font-size:36px;font-weight:800;text-shadow:0 0 25px rgba(255,255,255,0.3);'>
213
- {active_count} Sentiment Pulse{'s' if active_count!=1 else ''} Active
214
- </h1>
215
- <p style='margin:15px 0 0;font-size:18px;opacity:0.95;'>
216
- Primary: {SENTIMENT_CONFIG[dominant_sentiment]['icon']} {dominant_sentiment.upper()} at {probabilities[primary_idx]:.0%} • {text_length} words analyzed
217
- </p>
218
  </div>
219
- </div>
220
- """
221
- return overview_html
222
-
223
- def _render_data_matrix(probabilities, predictions):
224
- table_rows = ""
225
- for idx in range(5):
226
- sentiment = SENTIMENTS[idx]
227
- config = SENTIMENT_CONFIG[sentiment]
228
- status_icon = "●" if predictions[idx] else "○"
229
- row_color = config['primary'] if predictions[idx] else "#7f8c8d"
230
-
231
- table_rows += f"""
232
- <tr style='border-bottom:1px solid #34495e;'>
233
- <td style='padding:14px;color:{row_color};font-weight:600;'>{config['icon']} {sentiment.upper()}</td>
234
- <td style='padding:14px;text-align:center;color:#ecf0f1;font-family:monospace;'>{probabilities[idx]:.4f}</td>
235
- <td style='padding:14px;text-align:center;color:#95a5a6;font-family:monospace;'>{OPTIMIZED_THRESHOLDS[idx]:.4f}</td>
236
- <td style='padding:14px;text-align:center;color:{row_color};font-size:20px;'>{status_icon}</td>
237
- </tr>
238
  """
 
239
 
240
- matrix_html = f"""
241
- <div style='background:#2c3e50;padding:25px;border-radius:14px;border:2px solid #34495e;margin-top:20px;'>
242
- <h3 style='color:#ecf0f1;margin:0 0 15px 0;font-size:20px;'>📋 Sentiment Data Matrix</h3>
243
- <table style='width:100%;border-collapse:collapse;'>
244
- <thead>
245
- <tr style='background:#1a1f2e;border-bottom:3px solid #667eea;'>
246
- <th style='padding:14px;text-align:left;color:#ecf0f1;font-size:14px;'>Sentiment</th>
247
- <th style='padding:14px;text-align:center;color:#ecf0f1;font-size:14px;'>Score</th>
248
- <th style='padding:14px;text-align:center;color:#ecf0f1;font-size:14px;'>Threshold</th>
249
- <th style='padding:14px;text-align:center;color:#ecf0f1;font-size:14px;'>Status</th>
250
- </tr>
251
- </thead>
252
- <tbody>{table_rows}</tbody>
253
- </table>
254
- </div>
255
- """
256
- return matrix_html
257
-
258
- def analyze_sentiment(text_input, display_matrix=True):
259
- if not app_state.is_loaded:
260
- return "<div style='padding:50px;text-align:center;background:linear-gradient(135deg, #fa5252 0%, #e03131 100%);border-radius:18px;border:3px solid #c92a2a;'><div style='font-size:72px;'>⛔</div><h2 style='color:white;margin:15px 0;'>Model Not Loaded</h2><p style='color:#ffe0e0;'>Please initialize the model first</p></div>", "", "", "{}", _render_stats_widget()
261
-
262
- if not text_input.strip():
263
- return "<div style='padding:50px;text-align:center;background:linear-gradient(135deg, #fab005 0%, #fd7e14 100%);border-radius:18px;border:3px solid #f59f00;'><div style='font-size:72px;'>📝</div><h2 style='color:white;margin:15px 0;'>No Input Detected</h2><p style='color:#fff5e0;'>Please enter text to analyze</p></div>", "", "", "{}", _render_stats_widget()
264
 
265
  try:
266
- tokenized = app_state.text_tokenizer(text_input.strip(), truncation=True, padding="max_length", max_length=SEQUENCE_LENGTH, return_tensors="pt")
267
 
268
- with torch.no_grad():
269
- output_logits = app_state.neural_net(tokenized["input_ids"].to(app_state.compute_device), tokenized["attention_mask"].to(app_state.compute_device))
270
- probabilities = apply_sigmoid(output_logits.cpu().numpy())[0]
271
- predictions = (probabilities > np.array(OPTIMIZED_THRESHOLDS)).astype(int)
272
 
273
- app_state.total_analyses += 1
274
- for idx, sentiment_name in enumerate(SENTIMENTS):
275
- if predictions[idx]:
276
- app_state.sentiment_counts[sentiment_name] += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
- app_state.analysis_log.append({
279
- "snippet": text_input[:100],
280
- "time": datetime.now().isoformat(),
281
- "active": sum(predictions)
282
- })
 
283
 
284
- ranked_indices = np.argsort(probabilities)[::-1]
285
- overview = _render_overview_panel(probabilities, predictions, text_input)
 
 
286
 
287
- pulses = "<div style='background:#2c3e50;padding:25px;border-radius:14px;border:2px solid #34495e;'><h3 style='color:#ecf0f1;margin:0 0 15px 0;font-size:20px;'>🎯 Sentiment Pulse Analysis</h3>"
288
- for rank, idx in enumerate(ranked_indices):
289
- pulses += _render_sentiment_pulse(SENTIMENTS[idx], probabilities[idx], predictions[idx]==1, OPTIMIZED_THRESHOLDS[idx], rank+1)
290
- pulses += "</div>"
291
 
292
- matrix = _render_data_matrix(probabilities, predictions) if display_matrix else ""
 
293
 
294
- json_output = json.dumps({
295
- "sentiments": {SENTIMENTS[idx]: {
296
- "score": round(float(probabilities[idx]), 4),
297
- "active": bool(predictions[idx]),
298
- "rank": int(np.where(ranked_indices == idx)[0][0] + 1),
299
- "level": get_sentiment_level(SENTIMENTS[idx], probabilities[idx])
300
- } for idx in range(5)},
301
- "analysis": {
302
- "active_count": int(sum(predictions)),
303
- "primary": SENTIMENTS[np.argmax(probabilities)],
304
- "primary_score": round(float(probabilities[np.argmax(probabilities)]), 4),
305
- "word_count": len(text_input.split())
306
- }
307
- }, indent=2)
308
 
309
- return overview, pulses, matrix, json_output, _render_stats_widget()
310
-
311
- except Exception as error:
312
- return f"<div style='background:linear-gradient(135deg, #fa5252 0%, #e03131 100%);padding:25px;border-radius:14px;border:3px solid #c92a2a;'><h3 style='color:white;'>⚠️ Analysis Error: {str(error)}</h3></div>", "", "", "{}", _render_stats_widget()
 
 
 
 
313
 
314
- def batch_analysis(multi_text):
315
- if not app_state.is_loaded:
316
- return "<div style='padding:25px;background:linear-gradient(135deg, #fa5252 0%, #e03131 100%);border-radius:14px;border:3px solid #c92a2a;color:white;'>❌ Model not initialized</div>"
317
-
318
- text_lines = [line.strip() for line in multi_text.split('\n') if line.strip()]
319
- if not text_lines:
320
- return "<div style='padding:25px;background:linear-gradient(135deg, #fab005 0%, #fd7e14 100%);border-radius:14px;border:3px solid #f59f00;color:white;'>⚠️ No input provided</div>"
321
-
322
- output_html = f"<div style='background:linear-gradient(135deg, #667eea 0%, #764ba2 100%);padding:25px;border-radius:14px;color:white;margin-bottom:20px;box-shadow:0 8px 32px rgba(102,126,234,0.3);'><h2 style='margin:0;font-size:28px;font-weight:800;'>📊 Batch Analysis Report</h2><p style='margin:12px 0 0;opacity:0.95;font-size:16px;'>{len(text_lines)} samples processed</p></div>"
323
-
324
- for idx, text in enumerate(text_lines, 1):
325
- _, _, _, json_data, _ = analyze_sentiment(text, False)
326
- data = json.loads(json_data)
327
- primary = data['analysis']['primary']
328
- primary_score = data['analysis']['primary_score']
329
- active = data['analysis']['active_count']
330
-
331
- preview = text[:80]
332
- if len(text) > 80:
333
- preview += "..."
334
-
335
- config = SENTIMENT_CONFIG[primary]
336
-
337
- output_html += f"""
338
- <div style='background:#2c3e50;padding:18px;margin:12px 0;border-radius:12px;border-left:5px solid {config['primary']};box-shadow:0 4px 12px rgba(0,0,0,0.2);'>
339
- <div style='display:flex;gap:15px;align-items:start;'>
340
- <div style='font-size:32px;'>{config['icon']}</div>
341
- <div style='flex:1;'>
342
- <div style='color:{config['primary']};font-weight:700;font-size:16px;margin-bottom:8px;'>Sample #{idx}</div>
343
- <div style='color:#ecf0f1;font-style:italic;margin-bottom:10px;line-height:1.5;'>"{preview}"</div>
344
- <div style='display:flex;gap:12px;flex-wrap:wrap;'>
345
- <span style='background:#1a1f2e;padding:6px 14px;border-radius:8px;font-size:13px;color:#95a5a6;'>
346
- Primary: {primary.upper()} ({primary_score:.0%})
347
- </span>
348
- <span style='background:#1a1f2e;padding:6px 14px;border-radius:8px;font-size:13px;color:#95a5a6;'>
349
- Active: {active}/5
350
- </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  </div>
352
  </div>
353
  </div>
354
- </div>
355
- """
 
356
 
357
- return output_html
358
 
359
- def show_analysis_history():
360
- if not app_state.analysis_log:
361
- return "<div style='padding:25px;color:#95a5a6;text-align:center;background:#2c3e50;border-radius:14px;border:2px solid #34495e;'>No analyses performed yet</div>"
 
 
 
362
 
363
- history_html = f"<div style='background:#2c3e50;padding:25px;border-radius:14px;border:2px solid #34495e;'><h3 style='color:#ecf0f1;margin:0 0 15px 0;font-size:20px;'>📚 Analysis History ({len(app_state.analysis_log)} total)</h3>"
 
 
 
 
 
364
 
365
- for idx, record in enumerate(reversed(app_state.analysis_log[-10:]), 1):
366
- timestamp = datetime.fromisoformat(record['time']).strftime('%I:%M:%S %p')
367
- snippet = record['snippet']
368
- if len(record['snippet']) >= 100:
369
- snippet += "..."
370
 
371
- history_html += f"""
372
- <div style='padding:15px;margin:10px 0;background:#1a1f2e;border-radius:10px;border-left:4px solid #667eea;'>
373
- <div style='display:flex;justify-content:space-between;align-items:center;'>
374
- <div style='flex:1;'>
375
- <div style='color:#95a5a6;font-size:13px;margin-bottom:5px;'>{timestamp}</div>
376
- <div style='color:#ecf0f1;'>"{snippet}"</div>
377
- </div>
378
- <span style='background:#667eea;color:white;padding:4px 12px;border-radius:10px;font-size:13px;font-weight:700;margin-left:15px;white-space:nowrap;'>
379
- {record['active']}/5
380
- </span>
381
- </div>
 
 
 
 
 
 
 
 
 
382
  </div>
383
  """
384
 
385
- history_html += "</div>"
386
- return history_html
387
 
388
- def build_interface():
389
- app = gr.Blocks(title="🌊 Sentiment Pulse Analyzer")
390
-
391
- with app:
392
- gr.HTML("""
393
- <div style='text-align:center;padding:50px 30px;background:linear-gradient(135deg, #667eea 0%, #764ba2 100%);border-radius:20px;margin-bottom:30px;box-shadow:0 10px 50px rgba(102,126,234,0.4);border:3px solid #8b5cf6;'>
394
- <div style='font-size:80px;margin-bottom:15px;filter:drop-shadow(0 0 20px rgba(255,255,255,0.3));'>🌊</div>
395
- <h1 style='font-size:52px;margin:0;font-weight:900;color:white;text-shadow:0 0 30px rgba(255,255,255,0.3);'>Sentiment Pulse Analyzer</h1>
396
- <p style='font-size:22px;margin:20px 0;color:white;opacity:0.95;font-weight:500;'>Advanced Multi-Dimensional Sentiment Detection</p>
397
- <p style='font-size:16px;opacity:0.9;color:white;'>RoBERTa Architecture 87.2% Accuracy Real-time Analysis</p>
398
- <div style='margin-top:25px;display:flex;justify-content:center;gap:12px;flex-wrap:wrap;'>
399
- <span style='background:rgba(255,255,255,0.2);padding:10px 18px;border-radius:25px;backdrop-filter:blur(10px);color:white;font-weight:600;'>🔥 Anger</span>
400
- <span style='background:rgba(255,255,255,0.2);padding:10px 18px;border-radius:25px;backdrop-filter:blur(10px);color:white;font-weight:600;'>⚡ Fear</span>
401
- <span style='background:rgba(255,255,255,0.2);padding:10px 18px;border-radius:25px;backdrop-filter:blur(10px);color:white;font-weight:600;'>✨ Joy</span>
402
- <span style='background:rgba(255,255,255,0.2);padding:10px 18px;border-radius:25px;backdrop-filter:blur(10px);color:white;font-weight:600;'>💧 Sadness</span>
403
- <span style='background:rgba(255,255,255,0.2);padding:10px 18px;border-radius:25px;backdrop-filter:blur(10px);color:white;font-weight:600;'>💫 Surprise</span>
404
- </div>
405
  </div>
406
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
 
408
- with gr.Row():
409
- with gr.Column(scale=3):
410
- model_status = gr.HTML("<div style='background:linear-gradient(135deg, #fab005 0%, #fd7e14 100%);padding:25px;border-radius:16px;border:3px solid #f59f00;'><h3 style='color:white;margin:0;font-size:24px;'>⏳ Awaiting Initialization</h3><p style='color:rgba(255,255,255,0.9);margin:10px 0 0;'>Click the button to load the model</p></div>")
411
- with gr.Column(scale=1):
412
- init_button = gr.Button("🚀 Initialize Model", variant="primary", size="lg")
413
 
414
- stats_panel = gr.HTML("")
 
 
 
 
 
 
 
 
415
 
416
- with gr.Tabs():
417
- with gr.Tab("🔍 Single Analysis"):
418
- with gr.Row():
419
- with gr.Column():
420
- text_input = gr.Textbox(label="💬 Input Text for Analysis", placeholder="Enter your text here for sentiment analysis...", lines=10)
421
- with gr.Row():
422
- analyze_btn = gr.Button("⚡ Analyze Sentiment", variant="primary", size="lg")
423
- clear_btn = gr.ClearButton([text_input], value="🗑️ Clear Input", size="lg")
424
- with gr.Row():
425
- show_matrix = gr.Checkbox(label="Display Data Matrix", value=True)
426
- gr.Examples([
427
- ["I'm so excited and thrilled! This is the best day ever!"],
428
- ["I'm extremely frustrated and angry about this terrible situation!"],
429
- ["I'm really scared and worried about what might happen next."],
430
- ["Wow! I can't believe this is actually happening right now!"],
431
- ["I feel completely devastated and heartbroken. Nothing feels right."]
432
- ], inputs=[text_input], label="💡 Sample Inputs")
433
-
434
- with gr.Column():
435
- gr.Markdown("### 📊 Analysis Output")
436
- clear_output_btn = gr.Button("🗑️ Clear Results", variant="secondary", size="sm")
437
- overview_display = gr.HTML()
438
- pulse_display = gr.HTML()
439
- matrix_display = gr.HTML()
440
-
441
- with gr.Tab("🔄 Batch Processing"):
442
- gr.Markdown("### Process Multiple Texts\nEnter one text per line for batch analysis")
443
- batch_input = gr.Textbox(label="Multiple Text Inputs", placeholder="I love this product!\nThis service is terrible.\nWhat an incredible surprise!", lines=12)
444
- batch_analyze_btn = gr.Button("⚡ Process Batch", variant="primary", size="lg")
445
- batch_output = gr.HTML()
446
-
447
- with gr.Tab("💾 JSON Output"):
448
- gr.Markdown("### Structured Data Export")
449
- json_display = gr.Code(label="JSON Results", language="json", lines=25)
450
-
451
- with gr.Tab("📚 History Log"):
452
- gr.Markdown("### Analysis History")
453
- refresh_history_btn = gr.Button("🔄 Refresh History", variant="secondary")
454
- history_display = gr.HTML()
455
 
456
- gr.HTML("""
457
- <div style='margin-top:35px;padding:25px;background:#2c3e50;border-radius:14px;border:2px solid #34495e;'>
458
- <h2 style='color:#ecf0f1;margin-top:0;font-size:24px;'>📖 Technical Specifications</h2>
459
- <p style='color:#ecf0f1;line-height:1.8;margin:10px 0;'>
460
- <strong>Model Architecture:</strong> RoBERTa-base transformer with 125M parameters, fine-tuned for multi-label sentiment classification
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  </p>
462
- <p style='color:#ecf0f1;line-height:1.8;margin:10px 0;'>
463
- <strong>Sentiment Categories:</strong> Anger, Fear, Joy, Sadness, Surprise
464
  </p>
465
- <p style='color:#ecf0f1;line-height:1.8;margin:10px 0;'>
466
- <strong>Model Performance:</strong> 87.2% F1 Score on validation dataset
467
  </p>
468
- <p style='color:#ecf0f1;line-height:1.8;margin:10px 0;'>
469
- <strong>Processing:</strong> Real-time analysis with optimized threshold detection
470
  </p>
471
  </div>
472
- """)
473
-
474
- # Event handlers
475
- init_button.click(initialize_model, outputs=[model_status, stats_panel])
476
- analyze_btn.click(analyze_sentiment, inputs=[text_input, show_matrix], outputs=[overview_display, pulse_display, matrix_display, json_display, stats_panel])
477
- text_input.submit(analyze_sentiment, inputs=[text_input, show_matrix], outputs=[overview_display, pulse_display, matrix_display, json_display, stats_panel])
478
- clear_output_btn.click(lambda: ("", "", "", "{}", _render_stats_widget()), outputs=[overview_display, pulse_display, matrix_display, json_display, stats_panel])
479
- batch_analyze_btn.click(batch_analysis, inputs=[batch_input], outputs=[batch_output])
480
- refresh_history_btn.click(show_analysis_history, outputs=[history_display])
481
-
482
- return app
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
 
484
  if __name__ == "__main__":
485
- print("🌊 Launching Sentiment Pulse Analyzer interface...")
486
- interface = build_interface()
487
- interface.launch(server_name="0.0.0.0", server_port=7860, share=False, show_error=True)
 
 
 
 
 
4
  from transformers import AutoTokenizer, AutoModel
5
  import numpy as np
6
  import os
7
+ from typing import List, Dict, Tuple
8
  import json
9
  from datetime import datetime
10
+ import plotly.graph_objects as go
11
+ from collections import Counter
12
+ import time
13
 
14
+ print("🎭 EmotiScan Initializing...")
15
 
16
+ # Configuration
17
+ CONFIG = {
18
+ "model": "roberta-base",
19
+ "emotions": ["anger", "fear", "joy", "sadness", "surprise"],
20
+ "thresholds": [0.24722222, 0.61666667, 0.59722222, 0.44166667, 0.46111111],
21
+ "max_length": 200,
22
+ "weights_path": "roberta.pth"
23
+ }
24
 
25
+ # Emotion metadata with unique styling
26
+ EMOTION_META = {
27
  "anger": {
28
+ "emoji": "😠", "color": "#E74C3C", "gradient": ["#E74C3C", "#C0392B"],
29
+ "description": "Hostile or irritated state", "intensity_labels": ["Mild", "Moderate", "High", "Extreme"]
 
 
 
 
30
  },
31
  "fear": {
32
+ "emoji": "😨", "color": "#9B59B6", "gradient": ["#9B59B6", "#8E44AD"],
33
+ "description": "Anxiety or apprehension", "intensity_labels": ["Uneasy", "Concerned", "Frightened", "Panicked"]
 
 
 
 
34
  },
35
  "joy": {
36
+ "emoji": "😊", "color": "#F39C12", "gradient": ["#F39C12", "#E67E22"],
37
+ "description": "Positive emotional state", "intensity_labels": ["Pleasant", "Cheerful", "Delighted", "Euphoric"]
 
 
 
 
38
  },
39
  "sadness": {
40
+ "emoji": "😢", "color": "#3498DB", "gradient": ["#3498DB", "#2980B9"],
41
+ "description": "Melancholic emotional tone", "intensity_labels": ["Down", "Unhappy", "Distressed", "Grieving"]
 
 
 
 
42
  },
43
  "surprise": {
44
+ "emoji": "😲", "color": "#E91E63", "gradient": ["#E91E63", "#C2185B"],
45
+ "description": "Unexpected reaction", "intensity_labels": ["Interested", "Intrigued", "Amazed", "Astonished"]
 
 
 
 
46
  }
47
  }
48
 
49
+ class EmotionClassifier(nn.Module):
50
+ """Neural network for emotion classification"""
51
+ def __init__(self, model_name: str, num_labels: int):
52
  super().__init__()
53
+ self.base_model = AutoModel.from_pretrained(model_name)
54
+ self.dropout = nn.Dropout(0.35)
55
+ self.output_layer = nn.Linear(768, num_labels)
56
+
57
+ def forward(self, input_ids, attention_mask):
58
+ outputs = self.base_model(input_ids=input_ids, attention_mask=attention_mask)
59
+ pooled = outputs.pooler_output if hasattr(outputs, "pooler_output") else outputs.last_hidden_state[:, 0]
60
+ return self.output_layer(self.dropout(pooled))
61
 
62
+ class EmotiScanEngine:
63
+ """Core analysis engine"""
64
  def __init__(self):
65
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
66
+ self.model = None
67
+ self.tokenizer = None
68
+ self.ready = False
69
+ self.session_stats = {
70
+ "total_scans": 0,
71
+ "emotion_detections": Counter(),
72
+ "scan_history": [],
73
+ "session_start": datetime.now()
74
+ }
75
+
76
+ def load_model(self) -> Tuple[bool, str]:
77
+ """Initialize model and tokenizer"""
78
+ try:
79
+ self.model = EmotionClassifier(CONFIG["model"], len(CONFIG["emotions"]))
80
+
81
+ if os.path.exists(CONFIG["weights_path"]):
82
+ self.model.load_state_dict(
83
+ torch.load(CONFIG["weights_path"], map_location=self.device)
84
+ )
85
+ status = "✅ Model loaded with trained weights"
86
+ else:
87
+ status = "⚠️ Model initialized without pre-trained weights"
88
+
89
+ self.model.to(self.device).eval()
90
+ self.tokenizer = AutoTokenizer.from_pretrained(CONFIG["model"])
91
+ self.ready = True
92
+
93
+ return True, status
94
+ except Exception as e:
95
+ return False, f"❌ Initialization failed: {str(e)}"
96
+
97
+ def analyze_text(self, text: str) -> Dict:
98
+ """Perform emotion analysis on input text"""
99
+ if not self.ready:
100
+ raise RuntimeError("Model not initialized")
101
+
102
+ if not text.strip():
103
+ raise ValueError("Empty input text")
104
+
105
+ # Tokenize
106
+ encoded = self.tokenizer(
107
+ text.strip(),
108
+ truncation=True,
109
+ padding="max_length",
110
+ max_length=CONFIG["max_length"],
111
+ return_tensors="pt"
112
+ )
113
+
114
+ # Inference
115
+ with torch.no_grad():
116
+ logits = self.model(
117
+ encoded["input_ids"].to(self.device),
118
+ encoded["attention_mask"].to(self.device)
119
+ )
120
+ scores = torch.sigmoid(logits).cpu().numpy()[0]
121
+ predictions = (scores > np.array(CONFIG["thresholds"])).astype(int)
122
+
123
+ # Update statistics
124
+ self.session_stats["total_scans"] += 1
125
+ for idx, emotion in enumerate(CONFIG["emotions"]):
126
+ if predictions[idx]:
127
+ self.session_stats["emotion_detections"][emotion] += 1
128
+
129
+ self.session_stats["scan_history"].append({
130
+ "timestamp": datetime.now().isoformat(),
131
+ "text_preview": text[:80],
132
+ "detected_count": int(predictions.sum())
133
+ })
134
+
135
+ # Build result
136
+ results = {
137
+ "emotions": {},
138
+ "metadata": {
139
+ "text_length": len(text.split()),
140
+ "detected_emotions": int(predictions.sum()),
141
+ "dominant_emotion": None,
142
+ "confidence": 0.0
143
+ }
144
+ }
145
+
146
+ max_score_idx = np.argmax(scores)
147
+ results["metadata"]["dominant_emotion"] = CONFIG["emotions"][max_score_idx]
148
+ results["metadata"]["confidence"] = float(scores[max_score_idx])
149
+
150
+ for idx, emotion in enumerate(CONFIG["emotions"]):
151
+ results["emotions"][emotion] = {
152
+ "score": float(scores[idx]),
153
+ "detected": bool(predictions[idx]),
154
+ "threshold": float(CONFIG["thresholds"][idx]),
155
+ "intensity": self._get_intensity(scores[idx])
156
+ }
157
+
158
+ return results
159
 
160
+ def _get_intensity(self, score: float) -> str:
161
+ """Determine intensity level"""
162
+ if score >= 0.75: return "Very High"
163
+ elif score >= 0.55: return "High"
164
+ elif score >= 0.35: return "Medium"
165
+ elif score >= 0.20: return "Low"
166
+ else: return "Very Low"
167
 
168
+ # Global engine instance
169
+ engine = EmotiScanEngine()
 
 
 
 
170
 
171
+ def create_radar_chart(emotion_scores: Dict) -> go.Figure:
172
+ """Generate radar chart for emotion visualization"""
173
+ emotions = list(emotion_scores.keys())
174
+ scores = [emotion_scores[e]["score"] * 100 for e in emotions]
175
+
176
+ fig = go.Figure()
177
+
178
+ fig.add_trace(go.Scatterpolar(
179
+ r=scores,
180
+ theta=[e.capitalize() for e in emotions],
181
+ fill='toself',
182
+ fillcolor='rgba(52, 152, 219, 0.3)',
183
+ line=dict(color='#3498DB', width=3),
184
+ marker=dict(size=10, color='#2980B9')
185
+ ))
186
+
187
+ fig.update_layout(
188
+ polar=dict(
189
+ radialaxis=dict(
190
+ visible=True,
191
+ range=[0, 100],
192
+ showticklabels=True,
193
+ tickfont=dict(size=11, color="#ECF0F1"),
194
+ gridcolor="#34495E"
195
+ ),
196
+ angularaxis=dict(
197
+ tickfont=dict(size=13, color="#ECF0F1", family="Arial Black")
198
+ ),
199
+ bgcolor="#1A1F2E"
200
+ ),
201
+ showlegend=False,
202
+ paper_bgcolor='rgba(0,0,0,0)',
203
+ plot_bgcolor='rgba(0,0,0,0)',
204
+ height=400,
205
+ margin=dict(l=80, r=80, t=40, b=40)
206
+ )
207
+
208
+ return fig
209
 
210
+ def render_emotion_card(emotion: str, data: Dict, rank: int) -> str:
211
+ """Generate individual emotion card HTML"""
212
+ meta = EMOTION_META[emotion]
213
+ score_pct = data["score"] * 100
214
+ is_detected = data["detected"]
215
+
216
+ border_width = "4px" if is_detected else "2px"
217
+ bg_opacity = "0.15" if is_detected else "0.05"
218
+
219
+ return f"""
220
+ <div style='
221
+ background: linear-gradient(145deg, rgba(26, 31, 46, 0.8), rgba(44, 62, 80, 0.6));
222
+ border: {border_width} solid {meta["color"]};
223
+ border-radius: 16px;
224
+ padding: 20px;
225
+ margin: 12px 0;
226
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
227
+ position: relative;
228
+ overflow: hidden;
229
+ '>
230
+ <div style='
231
+ position: absolute;
232
+ top: 0;
233
+ left: 0;
234
+ right: 0;
235
+ height: 6px;
236
+ background: linear-gradient(90deg, {meta["gradient"][0]}, {meta["gradient"][1]});
237
+ '></div>
238
 
239
+ <div style='display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px;'>
240
+ <div style='display: flex; align-items: center; gap: 12px;'>
241
+ <span style='font-size: 40px;'>{meta["emoji"]}</span>
242
+ <div>
243
+ <h3 style='margin: 0; color: #ECF0F1; font-size: 20px; font-weight: 700;'>
244
+ {emotion.upper()}
245
+ </h3>
246
+ <p style='margin: 4px 0 0; color: #95A5A6; font-size: 12px;'>{meta["description"]}</p>
 
247
  </div>
248
  </div>
249
+ <div style='text-align: right;'>
250
+ <div style='font-size: 32px; font-weight: 900; color: {meta["color"]};'>
251
+ {score_pct:.1f}%
252
+ </div>
253
+ <div style='
254
+ background: {meta["color"]};
255
+ color: white;
256
+ padding: 4px 10px;
257
+ border-radius: 12px;
258
+ font-size: 11px;
259
+ font-weight: 700;
260
+ margin-top: 4px;
261
+ '>
262
+ RANK #{rank}
263
+ </div>
264
  </div>
265
  </div>
266
+
267
+ <div style='
268
+ background: rgba(0, 0, 0, 0.3);
269
+ border-radius: 10px;
270
+ height: 14px;
271
+ overflow: hidden;
272
+ margin-bottom: 10px;
273
+ '>
274
+ <div style='
275
+ height: 100%;
276
+ width: {score_pct}%;
277
+ background: linear-gradient(90deg, {meta["gradient"][0]}, {meta["gradient"][1]});
278
+ border-radius: 10px;
279
+ transition: width 0.6s ease;
280
+ '></div>
281
+ </div>
282
+
283
+ <div style='display: flex; justify-content: space-between; font-size: 12px; color: #BDC3C7;'>
284
+ <span>Intensity: <strong style='color: {meta["color"]};'>{data["intensity"]}</strong></span>
285
+ <span>Status: <strong style='color: {"#2ECC71" if is_detected else "#7F8C8D"};'>
286
+ {"DETECTED" if is_detected else "Below Threshold"}
287
+ </strong></span>
288
+ </div>
289
+ </div>
290
+ """
291
 
292
+ def initialize_system():
293
+ """Initialize the EmotiScan engine"""
294
+ success, message = engine.load_model()
295
+
296
+ status_color = "#2ECC71" if success else "#E74C3C"
297
+ icon = "✅" if success else "❌"
298
+
299
+ status_html = f"""
300
+ <div style='
301
+ background: linear-gradient(135deg, {status_color}15, {status_color}25);
302
+ border: 3px solid {status_color};
303
+ border-radius: 18px;
304
+ padding: 28px;
305
+ text-align: center;
306
+ '>
307
+ <div style='font-size: 64px; margin-bottom: 12px;'>{icon}</div>
308
+ <h2 style='color: #ECF0F1; margin: 0 0 12px 0; font-size: 26px;'>{message}</h2>
309
+ <p style='color: #BDC3C7; margin: 0; font-size: 14px;'>
310
+ Device: {engine.device.type.upper()} | Model: {CONFIG["model"]} | Ready: {"Yes" if success else "No"}
311
+ </p>
312
+ </div>
313
+ """
314
+
315
+ stats_html = generate_stats_panel()
316
+
317
+ return status_html, stats_html
318
+
319
+ def generate_stats_panel() -> str:
320
+ """Create statistics panel"""
321
+ stats = engine.session_stats
322
+ runtime = (datetime.now() - stats["session_start"]).seconds
323
+
324
  return f"""
325
+ <div style='
326
+ background: linear-gradient(135deg, #16A085, #1ABC9C);
327
+ border-radius: 16px;
328
+ padding: 24px;
329
+ margin-top: 20px;
330
+ '>
331
+ <div style='display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; text-align: center;'>
332
  <div>
333
+ <div style='font-size: 36px; font-weight: 900; color: white;'>
334
+ {stats["total_scans"]}
335
+ </div>
336
+ <div style='color: rgba(255,255,255,0.9); font-size: 13px; margin-top: 6px;'>
337
+ Total Analyses
338
+ </div>
339
  </div>
340
  <div>
341
+ <div style='font-size: 36px; font-weight: 900; color: white;'>
342
+ {len(stats["emotion_detections"])}
343
+ </div>
344
+ <div style='color: rgba(255,255,255,0.9); font-size: 13px; margin-top: 6px;'>
345
+ Unique Emotions
346
+ </div>
347
  </div>
348
  <div>
349
+ <div style='font-size: 36px; font-weight: 900; color: white;'>
350
+ {runtime}s
351
+ </div>
352
+ <div style='color: rgba(255,255,255,0.9); font-size: 13px; margin-top: 6px;'>
353
+ Session Time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  </div>
 
 
 
 
355
  </div>
356
  </div>
 
 
 
357
  </div>
358
  """
 
359
 
360
+ def scan_emotion(text: str, show_chart: bool):
361
+ """Main analysis function"""
362
+ if not engine.ready:
363
+ error_html = """
364
+ <div style='padding: 40px; text-align: center; background: linear-gradient(135deg, #E74C3C, #C0392B); border-radius: 18px;'>
365
+ <div style='font-size: 64px;'>⚠️</div>
366
+ <h2 style='color: white; margin: 12px 0;'>System Not Ready</h2>
367
+ <p style='color: rgba(255,255,255,0.9);'>Please initialize the system first</p>
 
 
 
 
 
 
 
 
 
 
368
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  """
370
+ return error_html, "", None, "{}", generate_stats_panel()
371
 
372
+ if not text.strip():
373
+ empty_html = """
374
+ <div style='padding: 40px; text-align: center; background: linear-gradient(135deg, #F39C12, #E67E22); border-radius: 18px;'>
375
+ <div style='font-size: 64px;'>📝</div>
376
+ <h2 style='color: white; margin: 12px 0;'>No Input Provided</h2>
377
+ <p style='color: rgba(255,255,255,0.9);'>Please enter text to analyze</p>
378
+ </div>
379
+ """
380
+ return empty_html, "", None, "{}", generate_stats_panel()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
 
382
  try:
383
+ results = engine.analyze_text(text)
384
 
385
+ # Summary card
386
+ dominant = results["metadata"]["dominant_emotion"]
387
+ confidence = results["metadata"]["confidence"]
388
+ detected_count = results["metadata"]["detected_emotions"]
389
 
390
+ summary_html = f"""
391
+ <div style='
392
+ background: linear-gradient(135deg, #2C3E50, #34495E);
393
+ border: 3px solid #16A085;
394
+ border-radius: 18px;
395
+ padding: 32px;
396
+ text-align: center;
397
+ margin-bottom: 24px;
398
+ '>
399
+ <div style='font-size: 72px; margin-bottom: 16px;'>
400
+ {EMOTION_META[dominant]["emoji"]}
401
+ </div>
402
+ <h2 style='color: #ECF0F1; margin: 0 0 12px 0; font-size: 32px; font-weight: 800;'>
403
+ Dominant Emotion: {dominant.upper()}
404
+ </h2>
405
+ <p style='color: #BDC3C7; font-size: 18px; margin: 0 0 20px 0;'>
406
+ Confidence: {confidence:.1%} | Detected: {detected_count}/{len(CONFIG["emotions"])} emotions
407
+ </p>
408
+ <div style='color: #95A5A6; font-size: 14px;'>
409
+ 📊 {results["metadata"]["text_length"]} words analyzed
410
+ </div>
411
+ </div>
412
+ """
413
 
414
+ # Emotion cards
415
+ sorted_emotions = sorted(
416
+ results["emotions"].items(),
417
+ key=lambda x: x[1]["score"],
418
+ reverse=True
419
+ )
420
 
421
+ cards_html = "<div style='max-width: 900px; margin: 0 auto;'>"
422
+ for rank, (emotion, data) in enumerate(sorted_emotions, 1):
423
+ cards_html += render_emotion_card(emotion, data, rank)
424
+ cards_html += "</div>"
425
 
426
+ # Chart
427
+ chart = create_radar_chart(results["emotions"]) if show_chart else None
 
 
428
 
429
+ # JSON
430
+ json_output = json.dumps(results, indent=2)
431
 
432
+ return summary_html, cards_html, chart, json_output, generate_stats_panel()
 
 
 
 
 
 
 
 
 
 
 
 
 
433
 
434
+ except Exception as e:
435
+ error_html = f"""
436
+ <div style='background: linear-gradient(135deg, #E74C3C, #C0392B); padding: 24px; border-radius: 16px;'>
437
+ <h3 style='color: white; margin: 0;'>❌ Analysis Error</h3>
438
+ <p style='color: rgba(255,255,255,0.9); margin: 10px 0 0;'>{str(e)}</p>
439
+ </div>
440
+ """
441
+ return error_html, "", None, "{}", generate_stats_panel()
442
 
443
+ def batch_scan(texts: str):
444
+ """Batch processing function"""
445
+ if not engine.ready:
446
+ return "<div style='padding: 24px; background: #E74C3C; border-radius: 14px; color: white;'>❌ System not initialized</div>"
447
+
448
+ lines = [l.strip() for l in texts.split('\n') if l.strip()]
449
+ if not lines:
450
+ return "<div style='padding: 24px; background: #F39C12; border-radius: 14px; color: white;'>⚠️ No input provided</div>"
451
+
452
+ output = f"""
453
+ <div style='
454
+ background: linear-gradient(135deg, #16A085, #1ABC9C);
455
+ padding: 28px;
456
+ border-radius: 16px;
457
+ margin-bottom: 24px;
458
+ color: white;
459
+ '>
460
+ <h2 style='margin: 0; font-size: 28px; font-weight: 800;'>📦 Batch Analysis Report</h2>
461
+ <p style='margin: 12px 0 0; font-size: 16px; opacity: 0.95;'>{len(lines)} samples processed</p>
462
+ </div>
463
+ """
464
+
465
+ for idx, text in enumerate(lines, 1):
466
+ try:
467
+ result = engine.analyze_text(text)
468
+ dom = result["metadata"]["dominant_emotion"]
469
+ conf = result["metadata"]["confidence"]
470
+ meta = EMOTION_META[dom]
471
+
472
+ preview = text[:70] + ("..." if len(text) > 70 else "")
473
+
474
+ output += f"""
475
+ <div style='
476
+ background: linear-gradient(145deg, #2C3E50, #34495E);
477
+ border-left: 6px solid {meta["color"]};
478
+ border-radius: 12px;
479
+ padding: 20px;
480
+ margin: 14px 0;
481
+ '>
482
+ <div style='display: flex; gap: 16px; align-items: start;'>
483
+ <div style='font-size: 36px;'>{meta["emoji"]}</div>
484
+ <div style='flex: 1;'>
485
+ <div style='color: {meta["color"]}; font-weight: 700; font-size: 15px; margin-bottom: 8px;'>
486
+ Sample #{idx}
487
+ </div>
488
+ <div style='color: #ECF0F1; font-style: italic; margin-bottom: 12px; line-height: 1.6;'>
489
+ "{preview}"
490
+ </div>
491
+ <div style='display: flex; gap: 10px;'>
492
+ <span style='background: #1A1F2E; padding: 6px 12px; border-radius: 8px; font-size: 12px; color: #95A5A6;'>
493
+ {dom.upper()} ({conf:.0%})
494
+ </span>
495
+ <span style='background: #1A1F2E; padding: 6px 12px; border-radius: 8px; font-size: 12px; color: #95A5A6;'>
496
+ {result["metadata"]["detected_emotions"]}/5 Active
497
+ </span>
498
+ </div>
499
  </div>
500
  </div>
501
  </div>
502
+ """
503
+ except Exception as e:
504
+ output += f"<div style='padding: 16px; background: #E74C3C; border-radius: 10px; color: white; margin: 10px 0;'>Error in sample #{idx}: {str(e)}</div>"
505
 
506
+ return output
507
 
508
+ def show_history():
509
+ """Display scan history"""
510
+ history = engine.session_stats["scan_history"]
511
+
512
+ if not history:
513
+ return "<div style='padding: 24px; color: #95A5A6; text-align: center; background: #2C3E50; border-radius: 14px;'>No scans performed yet</div>"
514
 
515
+ output = f"""
516
+ <div style='background: #2C3E50; padding: 24px; border-radius: 14px;'>
517
+ <h3 style='color: #ECF0F1; margin: 0 0 16px 0; font-size: 20px;'>
518
+ 📜 Scan History ({len(history)} total)
519
+ </h3>
520
+ """
521
 
522
+ for record in reversed(history[-15:]):
523
+ timestamp = datetime.fromisoformat(record['timestamp']).strftime('%H:%M:%S')
 
 
 
524
 
525
+ output += f"""
526
+ <div style='
527
+ background: #1A1F2E;
528
+ padding: 16px;
529
+ margin: 10px 0;
530
+ border-radius: 10px;
531
+ border-left: 4px solid #16A085;
532
+ '>
533
+ <div style='color: #95A5A6; font-size: 12px; margin-bottom: 6px;'>{timestamp}</div>
534
+ <div style='color: #ECF0F1; font-size: 14px; margin-bottom: 8px;'>"{record["text_preview"]}"</div>
535
+ <span style='
536
+ background: #16A085;
537
+ color: white;
538
+ padding: 4px 10px;
539
+ border-radius: 8px;
540
+ font-size: 12px;
541
+ font-weight: 700;
542
+ '>
543
+ {record["detected_count"]}/5 Detected
544
+ </span>
545
  </div>
546
  """
547
 
548
+ output += "</div>"
549
+ return output
550
 
551
+ # Build interface
552
+ with gr.Blocks(title="🎭 EmotiScan", theme=gr.themes.Base()) as app:
553
+ gr.HTML("""
554
+ <div style='
555
+ text-align: center;
556
+ padding: 48px 32px;
557
+ background: linear-gradient(135deg, #16A085 0%, #1ABC9C 50%, #2ECC71 100%);
558
+ border-radius: 24px;
559
+ margin-bottom: 32px;
560
+ box-shadow: 0 12px 48px rgba(22, 160, 133, 0.4);
561
+ '>
562
+ <div style='font-size: 96px; margin-bottom: 16px; filter: drop-shadow(0 0 20px rgba(255,255,255,0.4));'>
563
+ 🎭
 
 
 
 
564
  </div>
565
+ <h1 style='
566
+ font-size: 56px;
567
+ margin: 0;
568
+ font-weight: 900;
569
+ color: white;
570
+ text-shadow: 0 4px 12px rgba(0,0,0,0.3);
571
+ letter-spacing: -1px;
572
+ '>
573
+ EmotiScan
574
+ </h1>
575
+ <p style='font-size: 24px; margin: 16px 0; color: white; opacity: 0.95; font-weight: 600;'>
576
+ Neural Emotion Detection System
577
+ </p>
578
+ <p style='font-size: 15px; opacity: 0.9; color: white; font-weight: 500;'>
579
+ Powered by RoBERTa Transformer • Multi-Label Classification • Real-Time Processing
580
+ </p>
581
+ <div style='margin-top: 28px; display: flex; justify-content: center; gap: 12px; flex-wrap: wrap;'>
582
+ <span style='background: rgba(255,255,255,0.25); padding: 10px 16px; border-radius: 20px; color: white; font-weight: 700; font-size: 14px;'>😠 Anger</span>
583
+ <span style='background: rgba(255,255,255,0.25); padding: 10px 16px; border-radius: 20px; color: white; font-weight: 700; font-size: 14px;'>😨 Fear</span>
584
+ <span style='background: rgba(255,255,255,0.25); padding: 10px 16px; border-radius: 20px; color: white; font-weight: 700; font-size: 14px;'>😊 Joy</span>
585
+ <span style='background: rgba(255,255,255,0.25); padding: 10px 16px; border-radius: 20px; color: white; font-weight: 700; font-size: 14px;'>😢 Sadness</span>
586
+ <span style='background: rgba(255,255,255,0.25); padding: 10px 16px; border-radius: 20px; color: white; font-weight: 700; font-size: 14px;'>😲 Surprise</span>
587
+ </div>
588
+ </div>
589
+ """)
590
+
591
+ with gr.Row():
592
+ with gr.Column(scale=2):
593
+ system_status = gr.HTML("<div style='padding: 28px; text-align: center; background: #34495E; border-radius: 18px; color: white;'><h3>🔄 Awaiting Initialization</h3></div>")
594
+ with gr.Column(scale=1):
595
+ init_btn = gr.Button("🚀 Initialize System", variant="primary", size="lg", scale=1)
596
+
597
+ stats_display = gr.HTML("")
598
+
599
+ with gr.Tabs():
600
+ with gr.Tab("🔬 Single Scan"):
601
+ with gr.Row():
602
+ with gr.Column(scale=1):
603
+ input_text = gr.Textbox(
604
+ label="📄 Text Input",
605
+ placeholder="Enter text for emotion analysis...",
606
+ lines=8
607
+ )
608
+ with gr.Row():
609
+ scan_btn = gr.Button("⚡ Scan Emotions", variant="primary", size="lg")
610
+ clear_btn = gr.ClearButton([input_text], value="🧹 Clear", size="lg")
611
+ show_radar = gr.Checkbox(label="Show Radar Visualization", value=True)
612
+
613
+ gr.Examples([
614
+ ["This is absolutely amazing! I'm so thrilled and excited!"],
615
+ ["I'm furious about this completely unacceptable situation!"],
616
+ ["I'm terrified and extremely worried about the future."],
617
+ ["Wow! I never expected this to happen at all!"],
618
+ ["I'm heartbroken and feel completely devastated."]
619
+ ], inputs=[input_text], label="💡 Example Inputs")
620
+
621
+ with gr.Column(scale=1):
622
+ gr.Markdown("### 📊 Analysis Results")
623
+ summary_output = gr.HTML()
624
+ cards_output = gr.HTML()
625
 
626
+ with gr.Tab("📈 Visualization"):
627
+ radar_chart = gr.Plot(label="Emotion Radar Chart")
 
 
 
628
 
629
+ with gr.Tab("🔄 Batch Processing"):
630
+ gr.Markdown("### Process Multiple Texts\nEnter one text per line")
631
+ batch_input = gr.Textbox(
632
+ label="Batch Input",
633
+ placeholder="Text 1\nText 2\nText 3...",
634
+ lines=10
635
+ )
636
+ batch_btn = gr.Button("⚡ Process Batch", variant="primary", size="lg")
637
+ batch_output = gr.HTML()
638
 
639
+ with gr.Tab("💾 JSON Export"):
640
+ gr.Markdown("### Structured Data Output")
641
+ json_output = gr.Code(label="JSON Results", language="json", lines=20)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
 
643
+ with gr.Tab("📜 History"):
644
+ gr.Markdown("### Scan History Log")
645
+ refresh_btn = gr.Button("🔄 Refresh", variant="secondary")
646
+ history_output = gr.HTML()
647
+
648
+ gr.HTML("""
649
+ <div style='
650
+ margin-top: 32px;
651
+ padding: 28px;
652
+ background: linear-gradient(145deg, #2C3E50, #34495E);
653
+ border-radius: 16px;
654
+ border: 2px solid #16A085;
655
+ '>
656
+ <h2 style='color: #ECF0F1; margin-top: 0; font-size: 26px; font-weight: 700;'>
657
+ 🔬 Technical Specifications
658
+ </h2>
659
+ <div style='color: #BDC3C7; line-height: 1.9;'>
660
+ <p style='margin: 12px 0;'>
661
+ <strong style='color: #16A085;'>Architecture:</strong> RoBERTa-base transformer with 125M parameters,
662
+ fine-tuned for multi-label emotion classification
663
+ </p>
664
+ <p style='margin: 12px 0;'>
665
+ <strong style='color: #16A085;'>Emotion Categories:</strong> Anger, Fear, Joy, Sadness, Surprise
666
  </p>
667
+ <p style='margin: 12px 0;'>
668
+ <strong style='color: #16A085;'>Performance Metrics:</strong> 87.2% F1 Score on validation dataset
669
  </p>
670
+ <p style='margin: 12px 0;'>
671
+ <strong style='color: #16A085;'>Processing Speed:</strong> Real-time inference with optimized threshold detection
672
  </p>
673
+ <p style='margin: 12px 0;'>
674
+ <strong style='color: #16A085;'>Max Sequence Length:</strong> 200 tokens with truncation support
675
  </p>
676
  </div>
677
+ </div>
678
+ """)
679
+
680
+ # Event Handlers
681
+ init_btn.click(
682
+ initialize_system,
683
+ outputs=[system_status, stats_display]
684
+ )
685
+
686
+ scan_btn.click(
687
+ scan_emotion,
688
+ inputs=[input_text, show_radar],
689
+ outputs=[summary_output, cards_output, radar_chart, json_output, stats_display]
690
+ )
691
+
692
+ input_text.submit(
693
+ scan_emotion,
694
+ inputs=[input_text, show_radar],
695
+ outputs=[summary_output, cards_output, radar_chart, json_output, stats_display]
696
+ )
697
+
698
+ batch_btn.click(
699
+ batch_scan,
700
+ inputs=[batch_input],
701
+ outputs=[batch_output]
702
+ )
703
+
704
+ refresh_btn.click(
705
+ show_history,
706
+ outputs=[history_output]
707
+ )
708
 
709
  if __name__ == "__main__":
710
+ print("🎭 Launching EmotiScan interface...")
711
+ app.launch(
712
+ server_name="0.0.0.0",
713
+ server_port=7860,
714
+ share=False,
715
+ show_error=True
716
+ )