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

update app.py

Browse files
Files changed (1) hide show
  1. app.py +383 -252
app.py CHANGED
@@ -9,348 +9,479 @@ import json
9
  from datetime import datetime
10
  from collections import defaultdict
11
 
12
- print("🎭 Emotion Classifier Starting...")
13
 
14
  MODEL_NAME = "roberta-base"
15
- EMOTIONS = ["anger", "fear", "joy", "sadness", "surprise"]
16
- BEST_THRESHOLDS = [0.24722222, 0.61666667, 0.59722222, 0.44166667, 0.46111111]
17
- MAX_LEN = 200
18
- MODEL_PATH = "roberta.pth"
19
 
20
- EMOTION_META = {
21
- "anger": {"emoji": "😠", "color": "#ef4444", "gradient": "linear-gradient(135deg, #dc2626 0%, #991b1b 100%)", "glow": "0 0 20px rgba(239, 68, 68, 0.5)", "description": "Frustration, irritation, or rage", "intensity_labels": ["Mild irritation", "Moderate anger", "Strong anger", "Intense rage"]},
22
- "fear": {"emoji": "😨", "color": "#a78bfa", "gradient": "linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%)", "glow": "0 0 20px rgba(139, 92, 246, 0.5)", "description": "Anxiety, worry, or terror", "intensity_labels": ["Slight concern", "Moderate fear", "Strong fear", "Extreme terror"]},
23
- "joy": {"emoji": "😊", "color": "#fbbf24", "gradient": "linear-gradient(135deg, #f59e0b 0%, #d97706 100%)", "glow": "0 0 20px rgba(251, 191, 36, 0.5)", "description": "Happiness, excitement, or delight", "intensity_labels": ["Mild pleasure", "Moderate happiness", "Strong joy", "Intense euphoria"]},
24
- "sadness": {"emoji": "😢", "color": "#60a5fa", "gradient": "linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)", "glow": "0 0 20px rgba(59, 130, 246, 0.5)", "description": "Sorrow, grief, or disappointment", "intensity_labels": ["Mild sadness", "Moderate sorrow", "Strong grief", "Deep despair"]},
25
- "surprise": {"emoji": "😲", "color": "#f472b6", "gradient": "linear-gradient(135deg, #ec4899 0%, #be185d 100%)", "glow": "0 0 20px rgba(236, 72, 153, 0.5)", "description": "Astonishment, shock, or amazement", "intensity_labels": ["Mild surprise", "Moderate shock", "Strong astonishment", "Complete disbelief"]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
 
28
- class RobertaEmotion(nn.Module):
29
  def __init__(self):
30
  super().__init__()
31
- self.backbone = AutoModel.from_pretrained(MODEL_NAME)
32
- self.dropout = nn.Dropout(0.35)
33
- self.head = nn.Linear(768, 5)
34
 
35
- def forward(self, input_ids, attention_mask):
36
- outputs = self.backbone(input_ids=input_ids, attention_mask=attention_mask)
37
- pooled = outputs.pooler_output if hasattr(outputs, "pooler_output") else outputs.last_hidden_state[:, 0]
38
- return self.head(self.dropout(pooled))
39
 
40
- class ModelState:
41
  def __init__(self):
42
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
43
- self.model = None
44
- self.tokenizer = None
45
- self.ready = False
46
- self.predictions_count = 0
47
- self.history = []
48
- self.emotion_stats = defaultdict(int)
49
 
50
- state = ModelState()
51
 
52
- def sigmoid(x):
53
  return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
54
 
55
- def get_intensity_level(prob):
56
- if prob >= 0.85: return "Very High", "#10b981"
57
- elif prob >= 0.70: return "High", "#60a5fa"
58
- elif prob >= 0.50: return "Moderate", "#fbbf24"
59
- elif prob >= 0.30: return "Low", "#9ca3af"
60
- else: return "Very Low", "#6b7280"
61
 
62
- def get_emotion_intensity_label(emotion, prob):
63
- labels = EMOTION_META[emotion]["intensity_labels"]
64
- if prob >= 0.85: return labels[3]
65
- elif prob >= 0.65: return labels[2]
66
- elif prob >= 0.45: return labels[1]
67
- else: return labels[0]
68
 
69
- def load_model():
70
  try:
71
- state.model = RobertaEmotion()
72
- if os.path.exists(MODEL_PATH):
73
- state.model.load_state_dict(torch.load(MODEL_PATH, map_location=state.device))
74
- status, icon, bg, border = "success", "✅", "#065f46", "#10b981"
75
  else:
76
- status, icon, bg, border = "warning", "⚠️", "#78350f", "#f59e0b"
77
- state.model.to(state.device).eval()
78
- state.tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
79
- state.ready = True
80
- html = f"<div style='background:{bg};padding:20px;border-radius:12px;border:2px solid {border};box-shadow:{border} 0 0 30px;'><div style='display:flex;gap:15px;align-items:center;'><div style='font-size:48px;'>{icon}</div><div style='color:#f9fafb;'><h3 style='margin:0;'>Model Ready</h3><p style='margin:8px 0 0;color:#d1d5db;'>Device: {state.device.upper()} | F1: 0.872</p></div></div></div>"
81
- return html, _create_controls_panel()
82
- except Exception as e:
83
- return f"<div style='background:#7f1d1d;padding:20px;border-radius:12px;border:2px solid #ef4444;'><h3 style='color:#fecaca;'>Error: {e}</h3></div>", ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- def _create_controls_panel():
86
- return f"<div style='background:linear-gradient(135deg,#4c1d95 0%,#5b21b6 100%);padding:15px;border-radius:10px;color:white;box-shadow:0 0 30px rgba(139,92,246,0.4);'><div style='display:flex;justify-content:space-around;flex-wrap:wrap;gap:15px;'><div style='text-align:center;'><div style='font-size:28px;font-weight:bold;color:#fbbf24;'>{state.predictions_count}</div><div style='font-size:12px;'>Predictions</div></div><div style='text-align:center;'><div style='font-size:28px;font-weight:bold;color:#10b981;'>5</div><div style='font-size:12px;'>Classes</div></div><div style='text-align:center;'><div style='font-size:28px;font-weight:bold;color:#60a5fa;'>0.872</div><div style='font-size:12px;'>F1 Score</div></div></div></div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- def _create_emotion_card(emotion, prob, detected, threshold, rank):
89
- meta = EMOTION_META[emotion]
90
- prob_pct = prob * 100
91
- intensity, intensity_color = get_intensity_level(prob)
92
- medals = {1: "🥇", 2: "🥈", 3: "🥉"}
93
- rank_display = medals.get(rank, f"#{rank}")
94
- border = f"2px solid {meta['color']}" if detected else "2px solid #374151"
95
- bg = "#1f2937" if detected else "#111827"
96
- shadow = f"{meta['glow']}, 0 6px 12px rgba(0,0,0,0.4)" if detected else "0 2px 4px rgba(0,0,0,0.3)"
97
 
98
- card_html = f"""<div style='background:{bg};padding:18px;margin:12px 0;border-radius:12px;border:{border};box-shadow:{shadow};'>
99
- <div style='display:flex;justify-content:space-between;align-items:center;'>
100
- <div style='display:flex;gap:12px;align-items:center;'>
101
- <span style='font-size:36px;filter:drop-shadow(0 0 10px {meta['color']});'>{meta['emoji']}</span>
102
- <div>
103
- <div style='display:flex;gap:8px;align-items:center;'>
104
- <span style='font-weight:bold;font-size:20px;color:#f9fafb;'>{emotion.capitalize()}</span>
105
- <span style='background:{meta['color']};color:#000;padding:2px 8px;border-radius:12px;font-size:12px;font-weight:bold;'>{rank_display}</span>
 
 
 
 
 
 
 
 
 
 
106
  </div>
107
- <div style='font-size:13px;color:#9ca3af;'>{get_emotion_intensity_label(emotion, prob)}</div>
 
 
 
108
  </div>
109
  </div>
110
- <div style='text-align:right;'>
111
- <div style='font-size:24px;font-weight:bold;color:{meta['color']};text-shadow:0 0 10px {meta['color']};'>{prob_pct:.1f}%</div>
112
- <div style='font-size:11px;color:{intensity_color};'>{intensity}</div>
113
  </div>
114
  </div>
115
- <div style='position:relative;background:#0f172a;height:28px;border-radius:14px;overflow:hidden;margin:10px 0;border:1px solid #1e293b;'>
116
- <div style='position:absolute;height:100%;background:{meta['gradient']};width:{min(prob_pct,100)}%;border-radius:14px;box-shadow:inset 0 0 20px rgba(255,255,255,0.2);'></div>
117
- </div>
118
- </div>"""
119
- return card_html
120
 
121
- def _create_summary_header(probs, preds, text):
122
- detected_count = sum(preds)
123
- max_idx = np.argmax(probs)
124
- dominant = EMOTIONS[max_idx]
125
- detected_emotions = [EMOTIONS[i] for i in range(len(EMOTIONS)) if preds[i] == 1]
126
- emojis = " ".join([EMOTION_META[e]["emoji"] for e in detected_emotions]) if detected_emotions else "😐"
127
- word_count = len(text.split())
128
 
129
- summary_html = f"""<div style='background:linear-gradient(135deg,#4c1d95 0%,#5b21b6 100%);padding:30px;border-radius:16px;color:white;box-shadow:0 0 40px rgba(139,92,246,0.5);border:2px solid #7c3aed;'>
130
- <div style='text-align:center;'>
131
- <div style='font-size:56px;filter:drop-shadow(0 0 15px rgba(255,255,255,0.3));'>{emojis}</div>
132
- <h2 style='margin:10px 0;font-size:32px;text-shadow:0 0 20px rgba(255,255,255,0.3);'>{detected_count} Emotion{'s' if detected_count!=1 else ''} Detected</h2>
133
- <p style='opacity:0.95;'>Dominant: {EMOTION_META[dominant]['emoji']} {dominant.capitalize()} ({probs[max_idx]:.0%}) | Words: {word_count}</p>
 
 
 
 
 
 
134
  </div>
135
- </div>"""
136
- return summary_html
137
 
138
- def _create_comparison_table(probs, preds):
139
- rows = ""
140
- for i in range(5):
141
- emotion = EMOTIONS[i]
142
- meta = EMOTION_META[emotion]
143
- detected_mark = "" if preds[i] else "○"
144
- color = meta['color'] if preds[i] else "#6b7280"
145
- rows += f"""<tr style='border-bottom:1px solid #374151;'>
146
- <td style='padding:12px;color:{color};'>{meta['emoji']} {emotion.capitalize()}</td>
147
- <td style='padding:12px;text-align:center;color:#d1d5db;'>{probs[i]:.3f}</td>
148
- <td style='padding:12px;text-align:center;color:#9ca3af;'>{BEST_THRESHOLDS[i]:.3f}</td>
149
- <td style='padding:12px;text-align:center;color:{color};font-size:18px;'>{detected_mark}</td>
150
- </tr>"""
 
 
 
151
 
152
- table_html = f"""<div style='background:#1f2937;padding:20px;border-radius:12px;border:2px solid #374151;margin-top:20px;'>
153
- <h3 style='color:#f9fafb;margin-top:0;'>📊 Comparison Table</h3>
154
- <table style='width:100%;border-collapse:collapse;'>
155
- <thead>
156
- <tr style='background:#111827;border-bottom:2px solid #4b5563;'>
157
- <th style='padding:12px;text-align:left;color:#d1d5db;'>Emotion</th>
158
- <th style='padding:12px;text-align:center;color:#d1d5db;'>Probability</th>
159
- <th style='padding:12px;text-align:center;color:#d1d5db;'>Threshold</th>
160
- <th style='padding:12px;text-align:center;color:#d1d5db;'>Detected</th>
161
- </tr>
162
- </thead>
163
- <tbody>{rows}</tbody>
164
- </table>
165
- </div>"""
166
- return table_html
 
 
167
 
168
- def predict_emotion(text, show_table=True):
169
- if not state.ready:
170
- return "<div style='padding:40px;text-align:center;background:#7f1d1d;border:2px solid #ef4444;border-radius:16px;'><div style='font-size:64px;'>⚠️</div><h2 style='color:#fecaca;'>Model Not Loaded</h2><p style='color:#fca5a5;'>Click Load Model button</p></div>", "", "", "{}", _create_controls_panel()
171
 
172
- if not text.strip():
173
- return "<div style='padding:40px;text-align:center;background:#78350f;border:2px solid #f59e0b;border-radius:16px;'><div style='font-size:64px;'>✍️</div><h2 style='color:#fef3c7;'>No Text Provided</h2><p style='color:#fde68a;'>Enter text above</p></div>", "", "", "{}", _create_controls_panel()
174
 
175
  try:
176
- encoding = state.tokenizer(text.strip(), truncation=True, padding="max_length", max_length=MAX_LEN, return_tensors="pt")
 
177
  with torch.no_grad():
178
- logits = state.model(encoding["input_ids"].to(state.device), encoding["attention_mask"].to(state.device))
179
- probs = sigmoid(logits.cpu().numpy())[0]
180
- preds = (probs > np.array(BEST_THRESHOLDS)).astype(int)
181
 
182
- state.predictions_count += 1
183
- for i, emo in enumerate(EMOTIONS):
184
- if preds[i]: state.emotion_stats[emo] += 1
 
185
 
186
- state.history.append({"text": text[:100], "timestamp": datetime.now().isoformat(), "detected": sum(preds)})
 
 
 
 
187
 
188
- ranked = np.argsort(probs)[::-1]
189
- summary = _create_summary_header(probs, preds, text)
190
 
191
- cards = "<div style='background:#111827;padding:20px;border-radius:12px;border:2px solid #374151;'><h3 style='color:#f9fafb;margin-top:0;'>🎯 Detailed Analysis</h3>"
192
- for r, i in enumerate(ranked):
193
- cards += _create_emotion_card(EMOTIONS[i], probs[i], preds[i]==1, BEST_THRESHOLDS[i], r+1)
194
- cards += "</div>"
195
 
196
- table = _create_comparison_table(probs, preds) if show_table else ""
197
 
198
- json_out = json.dumps({
199
- "emotions": {EMOTIONS[i]: {
200
- "probability": round(float(probs[i]), 4),
201
- "detected": bool(preds[i]),
202
- "rank": int(np.where(ranked == i)[0][0] + 1),
203
- "intensity": get_emotion_intensity_label(EMOTIONS[i], probs[i])
204
- } for i in range(5)},
205
- "summary": {
206
- "detected": int(sum(preds)),
207
- "dominant": EMOTIONS[np.argmax(probs)],
208
- "dominant_prob": round(float(probs[np.argmax(probs)]), 4),
209
- "word_count": len(text.split())
210
  }
211
  }, indent=2)
212
 
213
- return summary, cards, table, json_out, _create_controls_panel()
214
 
215
- except Exception as e:
216
- return f"<div style='background:#7f1d1d;padding:20px;border-radius:12px;border:2px solid #ef4444;'><h3 style='color:#fecaca;'>Error: {str(e)}</h3></div>", "", "", "{}", _create_controls_panel()
217
 
218
- def batch_predict(texts):
219
- if not state.ready:
220
- return "<div style='padding:20px;background:#7f1d1d;border-radius:12px;border:2px solid #ef4444;color:#fecaca;'>⚠️ Model not loaded</div>"
221
 
222
- lines = [l.strip() for l in texts.split('\n') if l.strip()]
223
- if not lines:
224
- return "<div style='padding:20px;background:#78350f;border-radius:12px;border:2px solid #f59e0b;color:#fef3c7;'>⚠️ No text provided</div>"
225
 
226
- html = f"<div style='background:linear-gradient(135deg,#4c1d95 0%,#5b21b6 100%);padding:20px;border-radius:12px;color:white;margin-bottom:20px;box-shadow:0 0 30px rgba(139,92,246,0.4);'><h2 style='margin:0;'>📊 Batch Results</h2><p style='margin:8px 0 0;opacity:0.9;'>Analyzed {len(lines)} samples</p></div>"
227
 
228
- for i, txt in enumerate(lines, 1):
229
- s, _, _, j, _ = predict_emotion(txt, False, False)
230
- data = json.loads(j)
231
- dom = data['summary']['dominant']
232
- dom_prob = data['summary']['dominant_prob']
233
- det_count = data['summary']['detected']
234
 
235
- txt_preview = txt[:100]
236
- if len(txt) > 100:
237
- txt_preview += "..."
238
 
239
- color = EMOTION_META[dom]['color']
240
- emoji = EMOTION_META[dom]['emoji']
241
 
242
- html += f"""<div style='background:#1f2937;padding:15px;margin:10px 0;border-radius:10px;border-left:4px solid {color};'>
243
- <div style='display:flex;justify-content:space-between;align-items:start;gap:10px;'>
244
- <div style='font-size:24px;'>{emoji}</div>
 
245
  <div style='flex:1;'>
246
- <strong style='color:{color};'>Sample #{i}</strong>
247
- <div style='color:#d1d5db;font-style:italic;margin:5px 0;'>"{txt_preview}"</div>
248
- <div style='display:flex;gap:10px;flex-wrap:wrap;margin-top:8px;'>
249
- <span style='background:#111827;padding:4px 10px;border-radius:6px;font-size:13px;color:#9ca3af;'>
250
- Dominant: {dom.capitalize()} ({dom_prob:.0%})
251
  </span>
252
- <span style='background:#111827;padding:4px 10px;border-radius:6px;font-size:13px;color:#9ca3af;'>
253
- Detected: {det_count}/5
254
  </span>
255
  </div>
256
  </div>
257
  </div>
258
- </div>"""
 
259
 
260
- return html
261
 
262
- def get_history():
263
- if not state.history:
264
- return "<div style='padding:20px;color:#9ca3af;text-align:center;background:#1f2937;border-radius:12px;border:2px solid #374151;'>No predictions yet</div>"
265
 
266
- html = f"<div style='background:#1f2937;padding:20px;border-radius:12px;border:2px solid #374151;'><h3 style='color:#f9fafb;margin-top:0;'>📜 Recent Predictions ({len(state.history)})</h3>"
267
 
268
- for i, rec in enumerate(reversed(state.history[-10:]), 1):
269
- time = datetime.fromisoformat(rec['timestamp']).strftime('%H:%M:%S')
270
- txt_preview = rec['text']
271
- if len(rec['text']) >= 100:
272
- txt_preview += "..."
273
 
274
- html += f"""<div style='padding:12px;margin:8px 0;background:#111827;border-radius:8px;border-left:3px solid #8b5cf6;'>
 
275
  <div style='display:flex;justify-content:space-between;align-items:center;'>
276
- <div style='color:#d1d5db;flex:1;'>
277
- <span style='color:#9ca3af;font-size:12px;'>{time}</span> - "{txt_preview}"
 
278
  </div>
279
- <span style='background:#7c3aed;color:white;padding:2px 8px;border-radius:12px;font-size:12px;font-weight:bold;margin-left:10px;'>
280
- {rec['detected']}/5
281
  </span>
282
  </div>
283
- </div>"""
 
284
 
285
- html += "</div>"
286
- return html
287
 
288
- def create_interface():
289
- demo = gr.Blocks(title="🎭 Emotion Classifier")
290
 
291
- with demo:
292
- gr.HTML("<div style='text-align:center;padding:40px 20px;background:linear-gradient(135deg,#4c1d95 0%,#5b21b6 100%);border-radius:20px;margin-bottom:25px;color:white;box-shadow:0 0 40px rgba(139,92,246,0.5);'><div style='font-size:72px;margin-bottom:10px;'>🎭</div><h1 style='font-size:48px;margin:0;font-weight:800;'>Emotion Classifier</h1><p style='font-size:20px;margin:15px 0;opacity:0.95;'>Advanced Multi-Label Emotion Detection</p><p style='font-size:15px;opacity:0.85;'>RoBERTa-base • F1: 0.872 • 5 Classes</p><div style='margin-top:20px;display:flex;justify-content:center;gap:15px;flex-wrap:wrap;'><span style='background:rgba(0,0,0,0.3);padding:8px 16px;border-radius:20px;backdrop-filter:blur(10px);'>😠 Anger</span><span style='background:rgba(0,0,0,0.3);padding:8px 16px;border-radius:20px;backdrop-filter:blur(10px);'>😨 Fear</span><span style='background:rgba(0,0,0,0.3);padding:8px 16px;border-radius:20px;backdrop-filter:blur(10px);'>😊 Joy</span><span style='background:rgba(0,0,0,0.3);padding:8px 16px;border-radius:20px;backdrop-filter:blur(10px);'>😢 Sadness</span><span style='background:rgba(0,0,0,0.3);padding:8px 16px;border-radius:20px;backdrop-filter:blur(10px);'>😲 Surprise</span></div></div>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
  with gr.Row():
295
  with gr.Column(scale=3):
296
- status = gr.HTML("<div style='background:#78350f;padding:20px;border-radius:12px;border:2px solid #f59e0b;'><h3 style='color:#fef3c7;margin:0;'>⏳ Model Not Loaded</h3><p style='color:#fde68a;margin:8px 0 0;'>Click Load Model to start</p></div>")
297
  with gr.Column(scale=1):
298
- load_btn = gr.Button("🚀 Load Model", variant="primary", size="lg")
299
 
300
- controls = gr.HTML("")
301
 
302
  with gr.Tabs():
303
- with gr.Tab("📝 Single Analysis"):
304
  with gr.Row():
305
  with gr.Column():
306
- inp = gr.Textbox(label="💬 Enter Text to Analyze", placeholder="Type your text here...", lines=10)
307
  with gr.Row():
308
- analyze = gr.Button("🔍 Analyze Emotions", variant="primary", size="lg")
309
- clear = gr.ClearButton([inp], value="🗑️ Clear", size="lg")
310
  with gr.Row():
311
- table = gr.Checkbox(label="Show Table", value=True)
312
  gr.Examples([
313
- ["I'm absolutely thrilled and overjoyed! This is amazing!"],
314
- ["This is unacceptable! I'm furious about this situation!"],
315
- ["I'm terrified about the future. So much uncertainty."],
316
- ["Oh my god! I cannot believe this just happened!"],
317
- ["I feel so empty and sad. Nothing matters anymore."]
318
- ], inputs=[inp], label="💡 Try These Examples")
319
 
320
  with gr.Column():
321
- gr.Markdown("### 📊 Analysis Results")
322
- clear_res = gr.Button("🗑️ Clear Results", variant="secondary", size="sm")
323
- summ = gr.HTML()
324
- details = gr.HTML()
325
- tbl = gr.HTML()
326
 
327
- with gr.Tab("📊 Batch Analysis"):
328
- gr.Markdown("### Analyze Multiple Texts\nEnter one text per line")
329
- batch_inp = gr.Textbox(label="Multiple Texts", placeholder="I love this!\nThis is terrible.\nWhat a surprise!", lines=12)
330
- batch_btn = gr.Button("🔍 Analyze All", variant="primary", size="lg")
331
- batch_out = gr.HTML()
332
 
333
- with gr.Tab("💾 JSON Export"):
334
- gr.Markdown("### Developer-Friendly JSON Output")
335
- json_out = gr.Code(label="JSON Results", language="json", lines=25)
336
 
337
- with gr.Tab("📜 History"):
338
- gr.Markdown("### Recent Predictions")
339
- hist_btn = gr.Button("🔄 Refresh History", variant="secondary")
340
- hist_out = gr.HTML()
341
 
342
- gr.HTML("<div style='margin-top:30px;padding:20px;background:#1f2937;border-radius:12px;border:2px solid #374151;'><h2 style='color:#f9fafb;margin-top:0;'>📚 Model Documentation</h2><p style='color:#d1d5db;line-height:1.8;'><strong>Architecture:</strong> RoBERTa-base (125M parameters) fine-tuned for multi-label emotion classification</p><p style='color:#d1d5db;line-height:1.8;'><strong>Classes:</strong> Anger, Fear, Joy, Sadness, Surprise</p><p style='color:#d1d5db;line-height:1.8;'><strong>Performance:</strong> F1 Score 0.872 on validation set</p></div>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
 
344
- load_btn.click(load_model, outputs=[status, controls])
345
- analyze.click(predict_emotion, inputs=[inp, table], outputs=[summ, details, tbl, json_out, controls])
346
- inp.submit(predict_emotion, inputs=[inp, table], outputs=[summ, details, tbl, json_out, controls])
347
- clear_res.click(lambda: ("", "", "", "{}", _create_controls_panel()), outputs=[summ, details, tbl, json_out, controls])
348
- batch_btn.click(batch_predict, inputs=[batch_inp], outputs=[batch_out])
349
- hist_btn.click(get_history, outputs=[hist_out])
 
350
 
351
- return demo
352
 
353
  if __name__ == "__main__":
354
- print("🎭 Starting Gradio interface...")
355
- demo = create_interface()
356
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False, show_error=True)
 
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)