isha0110 commited on
Commit
51ff5eb
·
verified ·
1 Parent(s): 5482cb4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +493 -272
app.py CHANGED
@@ -1,357 +1,578 @@
1
- import gradio as gr
2
- import torch
3
- 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("🎭 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_radar=True, 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
- radar = gr.Checkbox(label="Show Radar Chart", value=True)
312
- table = gr.Checkbox(label="Show Table", value=True)
313
- gr.Examples([
314
- ["I'm absolutely thrilled and overjoyed! This is amazing!"],
315
- ["This is unacceptable! I'm furious about this situation!"],
316
- ["I'm terrified about the future. So much uncertainty."],
317
- ["Oh my god! I cannot believe this just happened!"],
318
- ["I feel so empty and sad. Nothing matters anymore."]
319
- ], inputs=[inp], label="💡 Try These Examples")
 
 
 
 
 
 
 
 
 
 
 
320
 
321
- with gr.Column():
322
  gr.Markdown("### 📊 Analysis Results")
323
- clear_res = gr.Button("🗑️ Clear Results", variant="secondary", size="sm")
324
- summ = gr.HTML()
325
- details = gr.HTML()
326
- tbl = gr.HTML()
 
 
327
 
328
  with gr.Tab("📊 Batch Analysis"):
329
- gr.Markdown("### Analyze Multiple Texts\nEnter one text per line")
330
- batch_inp = gr.Textbox(label="Multiple Texts", placeholder="I love this!\nThis is terrible.\nWhat a surprise!", lines=12)
331
- batch_btn = gr.Button("🔍 Analyze All", variant="primary", size="lg")
332
- batch_out = gr.HTML()
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
  with gr.Tab("💾 JSON Export"):
335
- gr.Markdown("### Developer-Friendly JSON Output")
336
- json_out = gr.Code(label="JSON Results", language="json", lines=25)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
 
338
  with gr.Tab("📜 History"):
339
  gr.Markdown("### Recent Predictions")
340
- hist_btn = gr.Button("🔄 Refresh History", variant="secondary")
341
- hist_out = gr.HTML()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
 
343
- 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>")
 
 
 
 
344
 
345
- load_btn.click(load_model, outputs=[status, controls])
346
- analyze.click(predict_emotion, inputs=[inp, radar, table], outputs=[summ, details, tbl, json_out, controls])
347
- inp.submit(predict_emotion, inputs=[inp, radar, table], outputs=[summ, details, tbl, json_out, controls])
348
- clear_res.click(lambda: ("", "", "", "{}", _create_controls_panel()), outputs=[summ, details, tbl, json_out, controls])
349
- batch_btn.click(batch_predict, inputs=[batch_inp], outputs=[batch_out])
350
- hist_btn.click(get_history, outputs=[hist_out])
351
 
352
  return demo
353
 
 
354
  if __name__ == "__main__":
355
  print("🎭 Starting Gradio interface...")
356
  demo = create_interface()
357
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False, show_error=True)
 
 
 
 
 
 
1
+ <div style='display: flex; gap: 8px; margin-top: 10px;'>
2
+ <span style='background: {"#10b98120" if detected else "#f3f4f6"}; color: {"#10b981" if detected else "#9ca3af"}; padding: 4px 10px; border-radius: 6px; font-size: 12px; font-weight: 600;'>
3
+ {("✓ DETECTED" if detected else "○ Not Detected")}
4
+ </span>
5
+ <span style='background: #f3f4f6; padding: 4px 10px; border-radius: 6px; font-size: 12px; color: #6b7280;'>
6
+ Confidence: {intensity}
7
+ </span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  </div>
9
  </div>
10
+ """
 
 
 
 
11
 
12
+ def _create_summary_header(probs: np.ndarray, preds: np.ndarray, text: str) -> str:
13
+ """Create comprehensive summary header"""
14
  detected_count = sum(preds)
15
  max_idx = np.argmax(probs)
16
  dominant = EMOTIONS[max_idx]
17
+ dominant_meta = EMOTION_META[dominant]
18
+
19
  detected_emotions = [EMOTIONS[i] for i in range(len(EMOTIONS)) if preds[i] == 1]
20
+ emotion_emojis = " ".join([EMOTION_META[e]["emoji"] for e in detected_emotions]) if detected_emotions else "😐"
21
+
22
  word_count = len(text.split())
23
+ char_count = len(text)
24
+ avg_prob = np.mean(probs)
25
+
26
+ # Sentiment description
27
+ if detected_count == 0:
28
+ sentiment = "Neutral/Unclear"
29
+ sentiment_desc = "No strong emotional signals detected"
30
+ sentiment_color = "#9ca3af"
31
+ elif detected_count == 1:
32
+ sentiment = detected_emotions[0].capitalize()
33
+ sentiment_desc = f"Primarily expressing {detected_emotions[0]}"
34
+ sentiment_color = EMOTION_META[detected_emotions[0]]["color"]
35
+ else:
36
+ sentiment = "Mixed Emotions"
37
+ sentiment_desc = f"Complex emotional expression with {detected_count} emotions"
38
+ sentiment_color = "#6366f1"
39
 
40
+ return f"""
41
+ <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 16px; color: white; margin: 20px 0; box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);'>
42
+ <div style='text-align: center; margin-bottom: 25px;'>
43
+ <div style='font-size: 56px; margin-bottom: 12px;'>{emotion_emojis}</div>
44
+ <h2 style='margin: 0; font-size: 32px; font-weight: 700;'>{sentiment}</h2>
45
+ <p style='margin: 8px 0 0 0; font-size: 16px; opacity: 0.95;'>{sentiment_desc}</p>
46
+ </div>
47
+
48
+ <div style='display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px;'>
49
+ <div style='background: rgba(255,255,255,0.15); padding: 16px; border-radius: 12px; text-align: center; backdrop-filter: blur(10px);'>
50
+ <div style='font-size: 36px; font-weight: bold;'>{detected_count}</div>
51
+ <div style='font-size: 13px; opacity: 0.95; margin-top: 4px;'>Emotions<br>Detected</div>
52
+ </div>
53
+ <div style='background: rgba(255,255,255,0.15); padding: 16px; border-radius: 12px; text-align: center; backdrop-filter: blur(10px);'>
54
+ <div style='font-size: 36px; font-weight: bold;'>{dominant_meta["emoji"]}</div>
55
+ <div style='font-size: 13px; opacity: 0.95; margin-top: 4px;'>Dominant<br>{dominant.capitalize()}</div>
56
+ </div>
57
+ <div style='background: rgba(255,255,255,0.15); padding: 16px; border-radius: 12px; text-align: center; backdrop-filter: blur(10px);'>
58
+ <div style='font-size: 36px; font-weight: bold;'>{probs[max_idx]:.0%}</div>
59
+ <div style='font-size: 13px; opacity: 0.95; margin-top: 4px;'>Max<br>Confidence</div>
60
+ </div>
61
+ <div style='background: rgba(255,255,255,0.15); padding: 16px; border-radius: 12px; text-align: center; backdrop-filter: blur(10px);'>
62
+ <div style='font-size: 36px; font-weight: bold;'>{avg_prob:.0%}</div>
63
+ <div style='font-size: 13px; opacity: 0.95; margin-top: 4px;'>Average<br>Score</div>
64
+ </div>
65
+ <div style='background: rgba(255,255,255,0.15); padding: 16px; border-radius: 12px; text-align: center; backdrop-filter: blur(10px);'>
66
+ <div style='font-size: 36px; font-weight: bold;'>{word_count}</div>
67
+ <div style='font-size: 13px; opacity: 0.95; margin-top: 4px;'>Words<br>Analyzed</div>
68
+ </div>
69
+ </div>
70
  </div>
71
+ """
 
72
 
73
+ def _create_comparison_table(probs: np.ndarray, preds: np.ndarray) -> str:
74
+ """Create comparison table"""
75
  rows = ""
76
+ for i, emotion in enumerate(EMOTIONS):
 
77
  meta = EMOTION_META[emotion]
78
+ detected = "✓" if preds[i] else "○"
79
+ color = meta["color"] if preds[i] else "#9ca3af"
80
+
81
+ rows += f"""
82
+ <tr style='border-bottom: 1px solid #e5e7eb;'>
83
+ <td style='padding: 12px; font-weight: 600; color: {color};'>{meta["emoji"]} {emotion.capitalize()}</td>
84
+ <td style='padding: 12px; text-align: center;'>{probs[i]:.3f}</td>
85
+ <td style='padding: 12px; text-align: center;'>{BEST_THRESHOLDS[i]:.3f}</td>
86
+ <td style='padding: 12px; text-align: center; font-size: 18px; color: {color};'>{detected}</td>
87
+ </tr>
88
+ """
89
 
90
+ return f"""
91
+ <div style='background: white; padding: 20px; border-radius: 12px; margin: 20px 0; border: 1px solid #e5e7eb;'>
92
+ <h3 style='margin: 0 0 15px 0; color: #1f2937;'>📊 Detailed Comparison Table</h3>
93
+ <table style='width: 100%; border-collapse: collapse; font-size: 14px;'>
94
+ <thead>
95
+ <tr style='background: #f9fafb; border-bottom: 2px solid #e5e7eb;'>
96
+ <th style='padding: 12px; text-align: left; color: #6b7280; font-weight: 600;'>Emotion</th>
97
+ <th style='padding: 12px; text-align: center; color: #6b7280; font-weight: 600;'>Probability</th>
98
+ <th style='padding: 12px; text-align: center; color: #6b7280; font-weight: 600;'>Threshold</th>
99
+ <th style='padding: 12px; text-align: center; color: #6b7280; font-weight: 600;'>Detected</th>
100
+ </tr>
101
+ </thead>
102
+ <tbody>
103
+ {rows}
104
+ </tbody>
105
+ </table>
106
+ </div>
107
+ """
108
 
109
+ # ========== PREDICTION FUNCTIONS ==========
110
+ def predict_emotion(text: str, show_radar: bool = True, show_table: bool = True) -> Tuple[str, str, str, str, str]:
111
+ """Main prediction function with all outputs"""
112
+ if not state.ready or state.model is None or state.tokenizer is None:
113
+ error = """
114
+ <div style='padding: 40px; text-align: center; background: #fef2f2; border: 3px solid #fca5a5; border-radius: 16px;'>
115
+ <div style='font-size: 64px; margin-bottom: 15px;'>⚠️</div>
116
+ <h2 style='color: #991b1b; margin: 10px 0;'>Model Not Loaded</h2>
117
+ <p style='color: #7f1d1d; font-size: 16px;'>Please click <strong>'🚀 Load Model'</strong> button to initialize.</p>
118
+ </div>
119
+ """
120
+ return error, "", "", "{}", _create_controls_panel()
121
 
122
+ if not text or not text.strip():
123
+ error = """
124
+ <div style='padding: 40px; text-align: center; background: #fefce8; border: 3px solid #fde047; border-radius: 16px;'>
125
+ <div style='font-size: 64px; margin-bottom: 15px;'>✍️</div>
126
+ <h2 style='color: #854d0e; margin: 10px 0;'>No Text Provided</h2>
127
+ <p style='color: #713f12; font-size: 16px;'>Enter text above or try one of the examples below.</p>
128
+ </div>
129
+ """
130
+ return error, "", "", "{}", _create_controls_panel()
131
 
132
  try:
133
+ # Tokenize and predict
134
+ encoding = state.tokenizer(
135
+ text.strip(),
136
+ truncation=True,
137
+ padding="max_length",
138
+ max_length=MAX_LEN,
139
+ return_tensors="pt"
140
+ )
141
+
142
  with torch.no_grad():
143
+ logits = state.model(
144
+ encoding["input_ids"].to(state.device),
145
+ encoding["attention_mask"].to(state.device)
146
+ )
147
  probs = sigmoid(logits.cpu().numpy())[0]
148
  preds = (probs > np.array(BEST_THRESHOLDS)).astype(int)
149
 
150
+ # Update stats
151
  state.predictions_count += 1
152
  for i, emo in enumerate(EMOTIONS):
153
  if preds[i]: state.emotion_stats[emo] += 1
154
 
155
+ state.history.append({
156
+ "text": text[:100],
157
+ "timestamp": datetime.now().isoformat(),
158
+ "detected": sum(preds)
159
+ })
160
+
161
+ # Create visualizations
162
+ summary_html = _create_summary_header(probs, preds, text)
163
 
164
+ # Radar chart
165
+ radar_html = ""
166
+ if show_radar:
167
+ radar_html = f"""
168
+ <div style='background: white; padding: 20px; border-radius: 12px; margin: 20px 0; text-align: center; border: 1px solid #e5e7eb;'>
169
+ <h3 style='margin: 0 0 10px 0; color: #1f2937;'>📡 Emotion Radar Chart</h3>
170
+ {_create_radar_chart_svg(probs)}
171
+ <p style='color: #6b7280; font-size: 13px; margin: 10px 0 0 0;'>Visual representation of emotion intensities</p>
172
+ </div>
173
+ """
174
 
175
+ # Emotion cards with ranking
176
+ ranked_indices = np.argsort(probs)[::-1]
177
+ cards_html = "<div style='background: #f9fafb; padding: 20px; border-radius: 12px;'>"
178
+ cards_html += "<h3 style='color: #1f2937; margin: 0 0 15px 0; font-size: 22px;'>🎯 Detailed Emotion Analysis</h3>"
179
+ for rank, idx in enumerate(ranked_indices, 1):
180
+ cards_html += _create_emotion_card(EMOTIONS[idx], probs[idx], preds[idx] == 1, BEST_THRESHOLDS[idx], rank)
181
+ cards_html += "</div>"
182
 
183
+ # Comparison table
184
+ table_html = _create_comparison_table(probs, preds) if show_table else ""
185
 
186
+ # JSON output
187
+ json_results = {
188
+ "metadata": {
189
+ "timestamp": datetime.now().isoformat(),
190
+ "text_length": len(text),
191
+ "word_count": len(text.split()),
192
+ "model": "roberta-base",
193
+ "prediction_id": state.predictions_count
194
+ },
195
+ "emotions": {
196
+ emo: {
197
+ "probability": round(float(probs[i]), 4),
198
+ "detected": bool(preds[i]),
199
+ "threshold": float(BEST_THRESHOLDS[i]),
200
+ "confidence_level": get_intensity_level(probs[i])[0],
201
+ "rank": int(np.where(ranked_indices == i)[0][0] + 1),
202
+ "intensity_label": get_emotion_intensity_label(emo, probs[i])
203
+ }
204
+ for i, emo in enumerate(EMOTIONS)
205
+ },
206
  "summary": {
207
+ "total_detected": int(sum(preds)),
208
+ "dominant_emotion": EMOTIONS[np.argmax(probs)],
209
+ "dominant_probability": round(float(probs[np.argmax(probs)]), 4),
210
+ "average_probability": round(float(np.mean(probs)), 4),
211
+ "detected_emotions": [EMOTIONS[i] for i in range(len(EMOTIONS)) if preds[i] == 1]
212
  }
213
+ }
214
+
215
+ # Updated controls
216
+ controls_html = _create_controls_panel()
217
+ # Updated controls
218
+ controls_html = _create_controls_panel()
219
+
220
+ return summary_html, radar_html + cards_html, table_html, json.dumps(json_results, indent=2), controls_html
221
 
 
 
222
  except Exception as e:
223
+ error = f"""
224
+ <div style='padding: 40px; text-align: center; background: #fee; border: 3px solid #fca5a5; border-radius: 16px;'>
225
+ <div style='font-size: 64px; margin-bottom: 15px;'>❌</div>
226
+ <h2 style='color: #991b1b; margin: 10px 0;'>Prediction Error</h2>
227
+ <pre style='color: #7f1d1d; text-align: left; padding: 15px; background: #fef2f2; border-radius: 8px; overflow-x: auto;'>{str(e)}</pre>
228
+ </div>
229
+ """
230
+ return error, "", "", "{}", _create_controls_panel()
231
 
232
+ def batch_predict(texts: str) -> str:
233
+ """Batch prediction with rich output"""
234
  if not state.ready:
235
+ return "<div style='padding: 20px; background: #fee2e2; border-radius: 8px;'>⚠️ Model not loaded</div>"
236
 
237
+ lines = [line.strip() for line in texts.split('\n') if line.strip()]
238
  if not lines:
239
+ return "<div style='padding: 20px; background: #fef3c7; border-radius: 8px;'>⚠️ No text provided</div>"
240
 
241
+ html = f"""
242
+ <div style='font-family: system-ui, sans-serif;'>
243
+ <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 12px; color: white; margin-bottom: 20px;'>
244
+ <h2 style='margin: 0; font-size: 24px;'>📊 Batch Analysis Results</h2>
245
+ <p style='margin: 8px 0 0 0; opacity: 0.9;'>Analyzed {len(lines)} text samples</p>
246
+ </div>
247
+ """
248
 
249
+ all_emotions = defaultdict(int)
250
+
251
+ for i, text in enumerate(lines, 1):
252
+ summary, _, _, json_data, _ = predict_emotion(text, show_radar=False, show_table=False)
253
+ try:
254
+ data = json.loads(json_data)
255
+ detected_count = data['summary']['total_detected']
256
+ dominant = data['summary']['dominant_emotion']
257
+ dominant_prob = data['summary']['dominant_probability']
258
+ detected_list = data['summary']['detected_emotions']
259
+
260
+ for emo in detected_list:
261
+ all_emotions[emo] += 1
262
+
263
+ emoji = EMOTION_META[dominant]['emoji']
264
+ color = EMOTION_META[dominant]['color']
265
+
266
+ html += f"""
267
+ <div style='background: white; padding: 18px; margin: 12px 0; border-radius: 10px; border-left: 5px solid {color}; box-shadow: 0 2px 8px rgba(0,0,0,0.1);'>
268
+ <div style='display: flex; justify-content: between; align-items: start; gap: 15px; margin-bottom: 10px;'>
269
+ <div style='font-size: 36px;'>{emoji}</div>
270
+ <div style='flex: 1;'>
271
+ <div style='font-weight: bold; color: #1f2937; font-size: 16px; margin-bottom: 5px;'>Sample #{i}</div>
272
+ <div style='color: #4b5563; font-style: italic; margin-bottom: 10px; line-height: 1.5;'>"{text[:120]}{'...' if len(text) > 120 else ''}"</div>
273
+ <div style='display: flex; gap: 12px; flex-wrap: wrap; font-size: 14px;'>
274
+ <span style='background: #f3f4f6; padding: 6px 12px; border-radius: 6px; color: #374151;'>
275
+ <strong>Dominant:</strong> {emoji} {dominant.capitalize()}
276
+ </span>
277
+ <span style='background: #f3f4f6; padding: 6px 12px; border-radius: 6px; color: #374151;'>
278
+ <strong>Confidence:</strong> {dominant_prob:.1%}
279
+ </span>
280
+ <span style='background: #f3f4f6; padding: 6px 12px; border-radius: 6px; color: #374151;'>
281
+ <strong>Total:</strong> {detected_count}/5
282
+ </span>
283
+ </div>
284
+ {f"<div style='margin-top: 8px; font-size: 13px; color: #6b7280;'><strong>Detected:</strong> {', '.join([e.capitalize() for e in detected_list])}</div>" if detected_list else ""}
285
  </div>
286
  </div>
287
  </div>
288
+ """
289
+ except Exception as e:
290
+ html += f"<div style='background: #fee; padding: 15px; margin: 10px 0; border-radius: 8px;'>Sample {i}: Error - {str(e)}</div>"
291
 
292
+ # Summary statistics
293
+ html += """
294
+ <div style='background: #f9fafb; padding: 20px; border-radius: 12px; margin-top: 20px; border: 2px solid #e5e7eb;'>
295
+ <h3 style='margin: 0 0 15px 0; color: #1f2937;'>📈 Aggregate Statistics</h3>
296
+ <div style='display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;'>
297
+ """
298
+
299
+ for emotion in EMOTIONS:
300
+ count = all_emotions[emotion]
301
+ pct = (count / len(lines)) * 100 if lines else 0
302
+ meta = EMOTION_META[emotion]
303
+ html += f"""
304
+ <div style='background: white; padding: 15px; border-radius: 8px; text-align: center; border: 2px solid {meta["light_color"]}'>
305
+ <div style='font-size: 32px; margin-bottom: 5px;'>{meta["emoji"]}</div>
306
+ <div style='font-weight: bold; color: {meta["color"]}; font-size: 20px;'>{count}</div>
307
+ <div style='font-size: 12px; color: #6b7280; margin-top: 3px;'>{emotion.capitalize()}</div>
308
+ <div style='font-size: 11px; color: #9ca3af;'>{pct:.0f}% of samples</div>
309
+ </div>
310
+ """
311
+
312
+ html += "</div></div></div>"
313
  return html
314
 
315
+ def get_history() -> str:
316
+ """Get prediction history"""
317
  if not state.history:
318
+ return "<div style='padding: 20px; text-align: center; color: #9ca3af;'>No predictions yet</div>"
319
 
320
+ html = f"""
321
+ <div style='background: white; padding: 20px; border-radius: 12px; border: 1px solid #e5e7eb;'>
322
+ <h3 style='margin: 0 0 15px 0; color: #1f2937;'>📜 Recent Predictions ({len(state.history)})</h3>
323
+ """
324
 
325
+ for i, record in enumerate(reversed(state.history[-10:]), 1):
326
+ time = datetime.fromisoformat(record['timestamp']).strftime('%H:%M:%S')
327
+ html += f"""
328
+ <div style='padding: 12px; margin: 8px 0; background: #f9fafb; border-radius: 8px; border-left: 3px solid #667eea;'>
329
+ <div style='display: flex; justify-content: space-between; align-items: center;'>
330
+ <div style='flex: 1;'>
331
+ <div style='font-size: 13px; color: #6b7280; margin-bottom: 4px;'>{time}</div>
332
+ <div style='color: #374151; font-size: 14px;'>"{record['text']}{'...' if len(record['text']) >= 100 else ''}"</div>
333
+ </div>
334
+ <div style='background: #667eea; color: white; padding: 4px 12px; border-radius: 12px; font-size: 13px; font-weight: bold;'>
335
+ {record['detected']}/5
336
  </div>
 
 
 
337
  </div>
338
+ </div>
339
+ """
340
 
341
  html += "</div>"
342
  return html
343
 
344
+ def export_results(json_str: str) -> str:
345
+ """Export results as downloadable JSON"""
346
+ return json_str
347
+
348
+ # ========== GRADIO UI ==========
349
  def create_interface():
350
+ with gr.Blocks(title="🎭 Emotion Classifier") as demo:
351
+
352
+ gr.HTML("""
353
+ <div style='text-align: center; padding: 40px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; margin-bottom: 25px; color: white; box-shadow: 0 10px 40px rgba(102, 126, 234, 0.3);'>
354
+ <div style='font-size: 72px; margin-bottom: 10px;'>🎭</div>
355
+ <h1 style='font-size: 48px; margin: 0; font-weight: 800; letter-spacing: -1px;'>Emotion Classifier</h1>
356
+ <p style='font-size: 20px; margin: 15px 0 8px 0; opacity: 0.95; font-weight: 500;'>Advanced Multi-Label Emotion Detection</p>
357
+ <p style='font-size: 15px; margin: 0; opacity: 0.85;'>Powered by RoBERTa-base • F1 Score: 0.872 • 5 Emotion Classes</p>
358
+ <div style='margin-top: 20px; display: flex; justify-content: center; gap: 15px; flex-wrap: wrap;'>
359
+ <span style='background: rgba(255,255,255,0.2); padding: 8px 16px; border-radius: 20px; font-size: 14px; backdrop-filter: blur(10px);'>😠 Anger</span>
360
+ <span style='background: rgba(255,255,255,0.2); padding: 8px 16px; border-radius: 20px; font-size: 14px; backdrop-filter: blur(10px);'>😨 Fear</span>
361
+ <span style='background: rgba(255,255,255,0.2); padding: 8px 16px; border-radius: 20px; font-size: 14px; backdrop-filter: blur(10px);'>😊 Joy</span>
362
+ <span style='background: rgba(255,255,255,0.2); padding: 8px 16px; border-radius: 20px; font-size: 14px; backdrop-filter: blur(10px);'>😢 Sadness</span>
363
+ <span style='background: rgba(255,255,255,0.2); padding: 8px 16px; border-radius: 20px; font-size: 14px; backdrop-filter: blur(10px);'>😲 Surprise</span>
364
+ </div>
365
+ </div>
366
+ """)
367
 
368
+ # Status and Controls
369
  with gr.Row():
370
  with gr.Column(scale=3):
371
+ status_display = gr.HTML("""
372
+ <div style='background: #fef3c7; padding: 20px; border-radius: 12px; border-left: 5px solid #f59e0b;'>
373
+ <h3 style='margin: 0 0 8px 0; color: #92400e;'>⏳ Model Status: Not Loaded</h3>
374
+ <p style='margin: 0; color: #78350f;'>Click the <strong>'🚀 Load Model'</strong> button to initialize the classifier.</p>
375
+ </div>
376
+ """)
377
  with gr.Column(scale=1):
378
  load_btn = gr.Button("🚀 Load Model", variant="primary", size="lg")
379
 
380
+ controls_panel = gr.HTML("")
381
 
382
+ gr.HTML("<hr style='margin: 30px 0; border: none; border-top: 3px solid #e5e7eb;'>")
383
+
384
+ # Main Tabs
385
  with gr.Tabs():
386
  with gr.Tab("📝 Single Analysis"):
387
  with gr.Row():
388
+ with gr.Column(scale=1):
389
+ input_text = gr.Textbox(
390
+ label="💬 Enter Text to Analyze",
391
+ placeholder="Type your text here... \n\nExample: I'm incredibly excited about this opportunity, but I'm also feeling a bit nervous about the upcoming challenges!",
392
+ lines=10,
393
+ max_lines=20
394
+ )
395
+
396
  with gr.Row():
397
+ analyze_btn = gr.Button("🔍 Analyze Emotions", variant="primary", size="lg")
398
+ clear_input_btn = gr.ClearButton([input_text], value="🗑️ Clear Input", size="lg")
399
+
400
  with gr.Row():
401
+ show_radar_check = gr.Checkbox(label="Show Radar Chart", value=True)
402
+ show_table_check = gr.Checkbox(label="Show Comparison Table", value=True)
403
+
404
+ gr.Markdown("### 💡 Try These Example Texts")
405
+ gr.Examples(
406
+ examples=[
407
+ ["I'm absolutely thrilled and overjoyed! This is the best news I've received in years!"],
408
+ ["This is completely unacceptable! I'm furious about how this situation was handled!"],
409
+ ["I'm terrified about the future. The uncertainty is overwhelming and I can't stop worrying."],
410
+ ["Oh my god! I cannot believe this just happened! This is absolutely shocking!"],
411
+ ["I feel so empty and sad. Nothing seems to matter anymore and I just want to cry."],
412
+ ["I'm excited about the new job, but I'm also really anxious about moving to a new city."],
413
+ ["The concert was amazing! But now I'm sad it's over and I'll miss my friends."],
414
+ ["I'm angry at myself for making such a stupid mistake. I should have known better!"],
415
+ ["What a wonderful surprise! I never expected this and I'm so grateful and happy!"],
416
+ ["This project terrifies me. I'm worried I'll fail, but I'm also determined to succeed."]
417
+ ],
418
+ inputs=[input_text],
419
+ label="Click to try"
420
+ )
421
 
422
+ with gr.Column(scale=1):
423
  gr.Markdown("### 📊 Analysis Results")
424
+
425
+ clear_results_btn = gr.Button("🗑️ Clear Results", variant="secondary", size="sm")
426
+
427
+ summary_html = gr.HTML(label="Summary")
428
+ details_html = gr.HTML(label="Detailed Analysis")
429
+ table_html = gr.HTML(label="Comparison")
430
 
431
  with gr.Tab("📊 Batch Analysis"):
432
+ gr.Markdown("""
433
+ ### Analyze Multiple Texts Simultaneously
434
+ Enter one text per line to analyze multiple samples at once. Perfect for:
435
+ - Customer feedback analysis
436
+ - Social media monitoring
437
+ - Survey responses
438
+ - Product reviews
439
+ """)
440
+
441
+ batch_input = gr.Textbox(
442
+ label="Enter Multiple Texts (one per line)",
443
+ placeholder="I love this product!\nThis service is terrible.\nWhat an amazing surprise!\nFeeling very sad today.",
444
+ lines=12
445
+ )
446
+
447
+ batch_btn = gr.Button("🔍 Analyze All Texts", variant="primary", size="lg")
448
+ batch_output = gr.HTML(label="Batch Results")
449
 
450
  with gr.Tab("💾 JSON Export"):
451
+ gr.Markdown("""
452
+ ### Developer-Friendly JSON Output
453
+ Get structured data for integration with your applications, APIs, or data pipelines.
454
+ """)
455
+
456
+ json_output = gr.Code(
457
+ label="JSON Results (automatically updated)",
458
+ language="json",
459
+ lines=25
460
+ )
461
+
462
+ gr.Markdown("""
463
+ **JSON Structure includes:**
464
+ - Metadata (timestamp, text stats, model info)
465
+ - Individual emotion scores with confidence levels
466
+ - Rankings and intensity labels
467
+ - Summary statistics
468
+ - Detected emotions list
469
+ """)
470
 
471
  with gr.Tab("📜 History"):
472
  gr.Markdown("### Recent Predictions")
473
+ history_btn = gr.Button("🔄 Refresh History", variant="secondary")
474
+ history_output = gr.HTML()
475
+
476
+ # Footer Info
477
+ gr.HTML("""
478
+ <div style='margin-top: 40px; padding: 30px; background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%); border-radius: 16px; border: 2px solid #e5e7eb;'>
479
+ <h2 style='color: #1f2937; margin-top: 0; font-size: 24px;'>📚 Model Documentation</h2>
480
+
481
+ <div style='display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; margin: 20px 0;'>
482
+ <div style='background: white; padding: 20px; border-radius: 12px; border-left: 4px solid #667eea;'>
483
+ <h3 style='margin: 0 0 10px 0; color: #667eea;'>🏗️ Architecture</h3>
484
+ <ul style='margin: 0; padding-left: 20px; color: #4b5563; line-height: 1.8;'>
485
+ <li><strong>Base Model:</strong> RoBERTa-base</li>
486
+ <li><strong>Parameters:</strong> 125M</li>
487
+ <li><strong>Classifier:</strong> Custom head with dropout</li>
488
+ <li><strong>Training:</strong> Fine-tuned on emotion corpus</li>
489
+ </ul>
490
+ </div>
491
+
492
+ <div style='background: white; padding: 20px; border-radius: 12px; border-left: 4px solid #10b981;'>
493
+ <h3 style='margin: 0 0 10px 0; color: #10b981;'>📊 Performance</h3>
494
+ <ul style='margin: 0; padding-left: 20px; color: #4b5563; line-height: 1.8;'>
495
+ <li><strong>F1 Score:</strong> 0.872 (validation)</li>
496
+ <li><strong>Task:</strong> Multi-label classification</li>
497
+ <li><strong>Classes:</strong> 5 emotions</li>
498
+ <li><strong>Thresholds:</strong> Individually optimized</li>
499
+ </ul>
500
+ </div>
501
+
502
+ <div style='background: white; padding: 20px; border-radius: 12px; border-left: 4px solid #f59e0b;'>
503
+ <h3 style='margin: 0 0 10px 0; color: #f59e0b;'>🎯 Capabilities</h3>
504
+ <ul style='margin: 0; padding-left: 20px; color: #4b5563; line-height: 1.8;'>
505
+ <li><strong>Multi-label:</strong> Detect multiple emotions</li>
506
+ <li><strong>Intensity:</strong> Confidence scoring</li>
507
+ <li><strong>Speed:</strong> Real-time inference</li>
508
+ <li><strong>Batch:</strong> Analyze multiple texts</li>
509
+ </ul>
510
+ </div>
511
+ </div>
512
+
513
+ <div style='background: white; padding: 20px; border-radius: 12px; margin-top: 20px;'>
514
+ <h3 style='margin: 0 0 15px 0; color: #1f2937;'>🔬 How It Works</h3>
515
+ <div style='color: #4b5563; line-height: 1.8;'>
516
+ <ol style='margin: 0; padding-left: 20px;'>
517
+ <li><strong>Tokenization:</strong> Text is converted to subword tokens using RoBERTa's byte-pair encoding</li>
518
+ <li><strong>Encoding:</strong> Tokens are embedded and processed through 12 transformer layers</li>
519
+ <li><strong>Classification:</strong> Pooled representation passes through dropout and linear layer</li>
520
+ <li><strong>Thresholding:</strong> Sigmoid probabilities compared against optimized per-class thresholds</li>
521
+ <li><strong>Output:</strong> Binary predictions for each emotion with confidence scores</li>
522
+ </ol>
523
+ </div>
524
+ </div>
525
+
526
+ <div style='margin-top: 20px; padding: 15px; background: #eff6ff; border-radius: 8px; border-left: 4px solid #3b82f6;'>
527
+ <strong style='color: #1e40af;'>💡 Tip:</strong>
528
+ <span style='color: #1e3a8a;'>The model performs best on clear, expressive text. Longer texts (20+ words) generally yield more accurate results.</span>
529
+ </div>
530
+ </div>
531
+ """)
532
+
533
+ # Event Handlers
534
+ load_btn.click(
535
+ fn=load_model,
536
+ outputs=[status_display, controls_panel]
537
+ )
538
+
539
+ analyze_btn.click(
540
+ fn=predict_emotion,
541
+ inputs=[input_text, show_radar_check, show_table_check],
542
+ outputs=[summary_html, details_html, table_html, json_output, controls_panel]
543
+ )
544
+
545
+ input_text.submit(
546
+ fn=predict_emotion,
547
+ inputs=[input_text, show_radar_check, show_table_check],
548
+ outputs=[summary_html, details_html, table_html, json_output, controls_panel]
549
+ )
550
+
551
+ clear_results_btn.click(
552
+ fn=lambda: ("", "", "", "{}", _create_controls_panel()),
553
+ outputs=[summary_html, details_html, table_html, json_output, controls_panel]
554
+ )
555
 
556
+ batch_btn.click(
557
+ fn=batch_predict,
558
+ inputs=[batch_input],
559
+ outputs=[batch_output]
560
+ )
561
 
562
+ history_btn.click(
563
+ fn=get_history,
564
+ outputs=[history_output]
565
+ )
 
 
566
 
567
  return demo
568
 
569
+ # ========== MAIN ==========
570
  if __name__ == "__main__":
571
  print("🎭 Starting Gradio interface...")
572
  demo = create_interface()
573
+ demo.launch(
574
+ server_name="0.0.0.0",
575
+ server_port=7860,
576
+ share=False,
577
+ show_error=True
578
+ )