Natwar commited on
Commit
35e0a5d
·
verified ·
1 Parent(s): b5c9ccb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +381 -235
app.py CHANGED
@@ -11,7 +11,7 @@ if not os.path.exists("/.dockerenv") and not os.path.exists("/kaggle"):
11
  import gradio
12
  except ImportError:
13
  print("Installing required packages...")
14
- subprocess.check_call([sys.executable, "-m", "pip", "install",
15
  "spacy", "matplotlib", "gradio"])
16
  # Download spaCy model
17
  subprocess.check_call([sys.executable, "-m", "spacy", "download", "en_core_web_md"])
@@ -32,37 +32,37 @@ nlp = spacy.load("en_core_web_md")
32
  # Enhanced emotion categories with carefully selected keywords
33
  EMOTION_CATEGORIES = {
34
  'joy': [
35
- 'happy', 'joyful', 'delighted', 'excited', 'cheerful',
36
  'glad', 'elated', 'jubilant', 'overjoyed', 'pleased',
37
  'ecstatic', 'thrilled', 'euphoric', 'content', 'blissful'
38
  ],
39
  'sadness': [
40
- 'sad', 'unhappy', 'depressed', 'disappointed', 'sorrowful',
41
  'heartbroken', 'melancholy', 'grief', 'somber', 'mournful',
42
  'gloomy', 'despondent', 'downcast', 'miserable', 'devastated'
43
  ],
44
  'anger': [
45
- 'angry', 'furious', 'enraged', 'irritated', 'annoyed',
46
  'outraged', 'hostile', 'mad', 'infuriated', 'indignant',
47
  'livid', 'irate', 'fuming', 'seething', 'resentful'
48
  ],
49
  'fear': [
50
- 'afraid', 'scared', 'frightened', 'terrified', 'anxious',
51
  'worried', 'nervous', 'panicked', 'horrified', 'apprehensive',
52
  'fearful', 'uneasy', 'alarmed', 'dread', 'paranoid'
53
  ],
54
  'surprise': [
55
- 'surprised', 'amazed', 'astonished', 'shocked', 'stunned',
56
  'startled', 'astounded', 'bewildered', 'unexpected', 'awestruck',
57
  'flabbergasted', 'dumbfounded', 'incredulous', 'perplexed', 'thunderstruck'
58
  ],
59
  'love': [
60
- 'loving', 'affectionate', 'fond', 'adoring', 'caring',
61
  'devoted', 'passionate', 'tender', 'compassionate', 'cherishing',
62
  'enamored', 'smitten', 'infatuated', 'admiring', 'doting'
63
  ],
64
  'sarcasm': [
65
- 'sarcastic', 'ironic', 'mocking', 'cynical', 'satirical',
66
  'sardonic', 'facetious', 'contemptuous', 'caustic', 'biting',
67
  'scornful', 'derisive', 'snide', 'taunting', 'wry'
68
  ],
@@ -100,51 +100,51 @@ EMOTION_COLORS = {
100
  # Common sentiment phrases and expressions for improved detection
101
  EMOTION_PHRASES = {
102
  'joy': [
103
- 'over the moon', 'on cloud nine', 'couldn\'t be happier',
104
  'best day ever', 'made my day', 'feeling great',
105
  'absolutely thrilled', 'jumping for joy', 'bursting with happiness',
106
  'walking on sunshine', 'flying high', 'tickled pink'
107
  ],
108
  'sadness': [
109
- 'broke my heart', 'in tears', 'feel like crying',
110
  'deeply saddened', 'lost all hope', 'feel empty',
111
  'devastating news', 'hit hard', 'feel down', 'soul-crushing',
112
  'falling apart', 'world is ending', 'deeply hurt'
113
  ],
114
  'anger': [
115
- 'makes my blood boil', 'fed up with', 'had it with',
116
  'sick and tired of', 'drives me crazy', 'lost my temper',
117
  'absolutely furious', 'beyond frustrated', 'driving me up the wall',
118
  'at my wit\'s end', 'through the roof', 'blow a gasket', 'see red'
119
  ],
120
  'fear': [
121
- 'scared to death', 'freaking out', 'keeps me up at night',
122
  'terrified of', 'living in fear', 'panic attack',
123
  'nervous wreck', 'can\'t stop worrying', 'break out in a cold sweat',
124
  'shaking like a leaf', 'scared stiff', 'frozen with fear'
125
  ],
126
  'surprise': [
127
- 'can\'t believe', 'took me by surprise', 'out of nowhere',
128
  'never expected', 'caught off guard', 'mind blown',
129
  'plot twist', 'jaw dropped', 'knocked my socks off',
130
  'took my breath away', 'blew me away', 'speechless'
131
  ],
132
  'love': [
133
- 'deeply in love', 'means the world to me', 'treasure every moment',
134
  'hold dear', 'close to my heart', 'forever grateful',
135
  'truly blessed', 'never felt this way', 'head over heels',
136
  'madly in love', 'heart skips a beat', 'love with all my heart'
137
  ],
138
  'sarcasm': [
139
- 'just what I needed', 'couldn\'t get any better', 'how wonderful',
140
  'oh great', 'lucky me', 'my favorite part',
141
  'thrilled to bits', 'way to go', 'thanks for nothing',
142
  'brilliant job', 'story of my life', 'what a surprise'
143
  ],
144
  'disgust': [
145
- 'makes me sick', 'turn my stomach', 'can\'t stand',
146
  'absolutely disgusting', 'utterly repulsive', 'gross',
147
- 'revolting sight', 'nauseating', 'skin crawl',
148
  'makes me want to vomit', 'repulsed by', 'can hardly look at'
149
  ],
150
  'anticipation': [
@@ -170,30 +170,134 @@ CONTEXTUAL_INDICATORS = {
170
  'punctuation': {'!': 'emphasis', '?': 'question', '...': 'hesitation'}
171
  }
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  # Sarcasm patterns with refined detection logic
174
  SARCASM_PATTERNS = [
175
  # Exaggerated positive with negative context
176
  r'(?i)\b(?:so+|really|absolutely|totally|completely)\s+(?:thrilled|excited|happy|delighted)\s+(?:about|with|by)\b.*?(?:terrible|awful|worst|bad)',
177
-
178
  # Classic sarcastic phrases
179
  r'(?i)(?:^|\W)just\s+what\s+(?:I|we)\s+(?:need|wanted|hoped for)\b',
180
  r'(?i)(?:^|\W)how\s+(?:wonderful|nice|great|lovely|exciting)\b.*?(?:\!|\?{2,})',
181
-
182
  # Thanks for nothing pattern
183
  r'(?i)(?:^|\W)thanks\s+for\s+(?:nothing|that|pointing|stating)\b',
184
-
185
  # Quotation marks around positive words (scare quotes)
186
  r'(?i)"(?:great|wonderful|excellent|perfect|amazing)"',
187
-
188
  # Typical sarcastic responses
189
  r'(?i)^(?:yeah|sure|right|oh)\s+(?:right|sure|okay|ok)(?:\W|$)',
190
-
191
  # Exaggerated praise in negative context
192
  r'(?i)\b(?:brilliant|genius|impressive)\b.*?(?:disaster|failure|mess)',
193
-
194
  # Obvious understatements
195
  r'(?i)\b(?:slightly|bit|little)\s+(?:catastrophic|disastrous|terrible|awful)\b',
196
-
197
  # Oh great patterns
198
  r'(?i)(?:^|\W)oh\s+(?:great|wonderful|perfect|fantastic|awesome)(?:\W|$)'
199
  ]
@@ -208,16 +312,16 @@ def detect_phrases(text, emotion_phrases):
208
  """Detect emotion-specific phrases in text"""
209
  text_lower = text.lower()
210
  detected_phrases = {}
211
-
212
  for emotion, phrases in emotion_phrases.items():
213
  found_phrases = []
214
  for phrase in phrases:
215
  if phrase.lower() in text_lower:
216
  found_phrases.append(phrase)
217
-
218
  if found_phrases:
219
  detected_phrases[emotion] = found_phrases
220
-
221
  return detected_phrases
222
 
223
  def detect_contextual_features(text):
@@ -232,12 +336,12 @@ def detect_contextual_features(text):
232
  'ellipses': text.count('...'),
233
  'capitalized_words': len(re.findall(r'\b[A-Z]{2,}\b', text))
234
  }
235
-
236
  doc = nlp(text.lower())
237
-
238
  # Get tokens for counting
239
  tokens = [token.text for token in doc]
240
-
241
  # Count contextual indicators
242
  for indicator_type, words in CONTEXTUAL_INDICATORS.items():
243
  if indicator_type != 'punctuation':
@@ -247,7 +351,7 @@ def detect_contextual_features(text):
247
  features[indicator_type] += 1
248
  else: # Single word
249
  features[indicator_type] += tokens.count(word)
250
-
251
  return features
252
 
253
  def detect_sarcasm_patterns(text):
@@ -255,42 +359,42 @@ def detect_sarcasm_patterns(text):
255
  # Match sarcasm patterns
256
  matches = 0
257
  pattern_matches = []
258
-
259
  for pattern in SARCASM_PATTERNS:
260
  if re.search(pattern, text):
261
  matches += 1
262
  pattern_matches.append(pattern)
263
-
264
  # Get contextual features
265
  features = detect_contextual_features(text)
266
-
267
  # Check for phrases specific to sarcasm
268
  phrases = detect_phrases(text, {'sarcasm': EMOTION_PHRASES['sarcasm']})
269
  sarcasm_phrases = len(phrases.get('sarcasm', []))
270
-
271
  # Calculate raw score based on pattern matches and features
272
  raw_score = (matches * 0.15) + (sarcasm_phrases * 0.2)
273
-
274
  # Adjust based on contextual features
275
  if features['exclamations'] > 1:
276
  raw_score += min(features['exclamations'] * 0.05, 0.2)
277
-
278
  if features['capitalized_words'] > 0:
279
  raw_score += min(features['capitalized_words'] * 0.1, 0.3)
280
-
281
  # Detect positive-negative contrasts
282
  pos_neg_contrast = 0
283
  emotion_phrases = detect_phrases(text, {
284
  'positive': EMOTION_PHRASES['joy'] + EMOTION_PHRASES['love'],
285
  'negative': EMOTION_PHRASES['sadness'] + EMOTION_PHRASES['anger']
286
  })
287
-
288
  if emotion_phrases.get('positive') and emotion_phrases.get('negative'):
289
  pos_neg_contrast = 0.3
290
-
291
  # Add contrast score
292
  raw_score += pos_neg_contrast
293
-
294
  # Normalize to [0, 1]
295
  return min(raw_score, 1.0), pattern_matches
296
 
@@ -298,13 +402,13 @@ def calculate_emotion_similarity(text, emotion_keywords):
298
  """Calculate similarity between text and emotion keywords using spaCy"""
299
  if not text.strip():
300
  return 0.0
301
-
302
  # Process the input text
303
  doc = nlp(text.lower())
304
-
305
  # Get average similarity with emotion keywords
306
  keyword_scores = []
307
-
308
  # Use a subset of keywords for efficiency
309
  for keyword in emotion_keywords[:6]: # Use first 6 keywords for each emotion
310
  keyword_doc = nlp(keyword)
@@ -315,9 +419,9 @@ def calculate_emotion_similarity(text, emotion_keywords):
315
  for keyword_token in keyword_doc:
316
  similarity = token.similarity(keyword_token)
317
  max_similarity = max(max_similarity, similarity)
318
-
319
  keyword_scores.append(max_similarity)
320
-
321
  # Return average of top 3 similarities if we have at least 3 scores
322
  if len(keyword_scores) >= 3:
323
  return sum(sorted(keyword_scores, reverse=True)[:3]) / 3
@@ -331,40 +435,40 @@ def get_emotion_score(text, emotion, keywords):
331
  """Calculate emotion score based on similarity, context, and phrase detection"""
332
  # Get emotion score using spaCy word vectors
333
  similarity_score = calculate_emotion_similarity(text, keywords)
334
-
335
  # Check for emotion-specific phrases
336
  detected_phrases = detect_phrases(text, {emotion: EMOTION_PHRASES[emotion]})
337
  phrase_count = len(detected_phrases.get(emotion, []))
338
  phrase_score = min(phrase_count * 0.2, 0.6) # Cap at 0.6
339
-
340
  # Get contextual features
341
  features = detect_contextual_features(text)
342
-
343
  # Calculate feature-based adjustment
344
  feature_adjustment = 0
345
-
346
  # Search for direct emotion mentions in text
347
  doc = nlp(text.lower())
348
  direct_mention_score = 0
349
-
350
  for token in doc:
351
  if token.lemma_ in keywords:
352
  direct_mention_score += 0.2 # Direct mention of emotion word
353
  break
354
-
355
  # Adjust score based on emotional context
356
  if emotion in ['joy', 'love', 'surprise'] and features['exclamations'] > 0:
357
  feature_adjustment += min(features['exclamations'] * 0.05, 0.2)
358
-
359
  if emotion in ['anger', 'sadness'] and features['negators'] > 0:
360
  feature_adjustment += min(features['negators'] * 0.05, 0.2)
361
-
362
  if emotion == 'fear' and features['intensifiers'] > 0:
363
  feature_adjustment += min(features['intensifiers'] * 0.05, 0.2)
364
-
365
  # Combine scores with appropriate weights
366
  final_score = (similarity_score * 0.5) + (phrase_score * 0.3) + (feature_adjustment * 0.1) + (direct_mention_score * 0.1)
367
-
368
  # Normalize to ensure it's in [0, 1]
369
  return max(0, min(final_score, 1.0)), detected_phrases.get(emotion, [])
370
 
@@ -373,37 +477,37 @@ def analyze_sarcasm(text):
373
  # 1. Keyword similarity for sarcasm words
374
  sarcasm_keywords = EMOTION_CATEGORIES['sarcasm']
375
  similarity_score = calculate_emotion_similarity(text, sarcasm_keywords)
376
-
377
  # 2. Linguistic pattern detection
378
  pattern_score, pattern_matches = detect_sarcasm_patterns(text)
379
-
380
  # 3. Check for semantic incongruity between sentences
381
  incongruity_score = 0
382
  sentences = list(nlp(text).sents)
383
-
384
  if len(sentences) > 1:
385
  # Calculate similarity between adjacent sentences
386
  similarities = []
387
  for i in range(len(sentences) - 1):
388
  sim = sentences[i].similarity(sentences[i+1])
389
  similarities.append(sim)
390
-
391
  # Low similarity between adjacent sentences might indicate sarcasm
392
  if similarities and min(similarities) < 0.5:
393
  incongruity_score = 0.3
394
-
395
  # 4. Check for sarcasm phrases
396
  detected_phrases = detect_phrases(text, {'sarcasm': EMOTION_PHRASES['sarcasm']})
397
  phrase_score = min(len(detected_phrases.get('sarcasm', [])) * 0.2, 0.6)
398
-
399
  # 5. Check for emotional contrast
400
  # (positive words in negative context or vice versa)
401
  doc = nlp(text.lower())
402
-
403
  # Count positive and negative words
404
  pos_count = 0
405
  neg_count = 0
406
-
407
  for token in doc:
408
  if token.is_alpha and not token.is_stop:
409
  # Check against positive and negative emotion keywords
@@ -411,40 +515,98 @@ def analyze_sarcasm(text):
411
  pos_count += 1
412
  if any(token.similarity(nlp(word)) > 0.7 for word in EMOTION_CATEGORIES['sadness'][:5] + EMOTION_CATEGORIES['anger'][:5]):
413
  neg_count += 1
414
-
415
  contrast_score = 0
416
  if pos_count > 0 and neg_count > 0:
417
  contrast_score = min(0.3, pos_count * neg_count * 0.05)
418
-
419
  # Weighted combination of all scores
420
  combined_score = (0.15 * similarity_score) + (0.35 * pattern_score) + \
421
  (0.15 * incongruity_score) + (0.25 * phrase_score) + \
422
  (0.1 * contrast_score)
423
-
424
  # Normalize to [0, 1]
425
  return max(0, min(combined_score, 1.0)), detected_phrases.get('sarcasm', []), pattern_matches
426
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  def analyze_emotions(text):
428
- """Analyze emotions in text using spaCy with robust sarcasm detection"""
429
  if not text or not text.strip():
430
  return None, {"error": "Please enter some text to analyze"}
431
-
432
  try:
433
  # Calculate scores for each emotion with supporting phrases
434
  emotion_data = {}
435
-
436
  # For each standard emotion category (excluding sarcasm)
437
  for emotion, keywords in EMOTION_CATEGORIES.items():
438
  if emotion == 'sarcasm':
439
  continue
440
-
441
  # Use specialized function to get emotion score and supporting phrases
442
  score, phrases = get_emotion_score(text, emotion, keywords)
443
  emotion_data[emotion] = {
444
  'score': score,
445
  'phrases': phrases
446
  }
447
-
448
  # Special handling for sarcasm with multi-method approach
449
  sarcasm_score, sarcasm_phrases, sarcasm_patterns = analyze_sarcasm(text)
450
  emotion_data['sarcasm'] = {
@@ -452,182 +614,166 @@ def analyze_emotions(text):
452
  'phrases': sarcasm_phrases,
453
  'patterns': sarcasm_patterns
454
  }
455
-
456
  # Get contextual features for overall analysis
457
  context_features = detect_contextual_features(text)
458
-
459
  # Apply decision making for final analysis
460
  # 1. Check for dominant emotions by raw scores
461
  emotion_scores = {emotion: data['score'] for emotion, data in emotion_data.items()}
462
-
463
  # 2. Adjust based on contextual evidence
464
- # If we have strong phrase evidence, boost those emotions
465
  for emotion, data in emotion_data.items():
466
- if len(data.get('phrases', [])) >= 2:
467
- emotion_scores[emotion] = emotion_scores[emotion] * 1.2
468
-
469
- # 3. Adjust sarcasm based on specific rules
470
- if emotion_scores['sarcasm'] > 0.6:
471
- # High sarcasm - reduce intensity of other emotions
472
- for emotion in emotion_scores:
473
- if emotion != 'sarcasm':
474
- emotion_scores[emotion] *= 0.8
475
- elif emotion_scores['sarcasm'] > 0.3:
476
- # Moderate sarcasm - keep as complementary emotion
477
- pass
478
- else:
479
- # Low sarcasm - reduce it further to avoid false positives
480
- emotion_scores['sarcasm'] *= 0.7
481
-
482
- # 4. Check for unusually high emotions that might override sarcasm
483
- non_sarcasm_emotions = {e: s for e, s in emotion_scores.items() if e != 'sarcasm'}
484
- max_emotion = max(non_sarcasm_emotions.items(), key=lambda x: x[1]) if non_sarcasm_emotions else (None, 0)
485
-
486
- if max_emotion[1] > 0.7:
487
- # Very strong emotion detected - this could reduce sarcasm
488
- emotion_scores['sarcasm'] *= 0.7
489
-
490
- # 5. Normalize scores to ensure they sum to 1
491
- total_score = sum(emotion_scores.values())
492
- normalized_scores = {emotion: score / total_score for emotion, score in emotion_scores.items()}
493
-
494
- # Sort emotions by score
495
- sorted_emotions = sorted(normalized_scores.items(), key=lambda x: x[1], reverse=True)
496
- emotions, scores = zip(*sorted_emotions)
497
-
498
- # Prepare supporting evidence for each emotion
499
- supporting_evidence = {}
500
- for emotion in emotions:
501
- evidence = []
502
-
503
- # Add detected phrases
504
- if emotion_data[emotion].get('phrases'):
505
- evidence.extend([f'Phrase: "{phrase}"' for phrase in emotion_data[emotion]['phrases']])
506
-
507
- # Add pattern matches for sarcasm
508
- if emotion == 'sarcasm' and emotion_data['sarcasm'].get('patterns'):
509
- evidence.extend([f'Pattern match: sarcastic pattern detected' for _ in emotion_data['sarcasm']['patterns']])
510
-
511
- # Add contextual features as evidence
512
- if emotion == 'joy' and context_features['exclamations'] > 1:
513
- evidence.append(f'Found {context_features["exclamations"]} exclamation marks (!)')
514
-
515
- if emotion == 'anger' and context_features['capitalized_words'] > 0:
516
- evidence.append(f'Found {context_features["capitalized_words"]} capitalized words')
517
-
518
- supporting_evidence[emotion] = evidence[:3] # Limit to top 3 pieces of evidence
519
-
520
- # Create visualization
521
- fig = create_visualization(emotions, scores, text, supporting_evidence)
522
-
523
- # Format output
524
- output = {
525
- "dominant_emotion": emotions[0],
526
- "confidence": f"{scores[0]*100:.1f}%",
527
- "detailed_scores": {emotion: f"{score*100:.1f}%" for emotion, score in zip(emotions, scores)},
528
- "supporting_evidence": supporting_evidence
529
  }
530
-
531
- # Add contextual notes if applicable
532
- if emotions[0] == 'sarcasm' and scores[0] > 0.3:
533
- output["note"] = f"Sarcasm detected with {scores[0]*100:.1f}% confidence."
534
- elif 'sarcasm' in normalized_scores and normalized_scores['sarcasm'] > 0.25:
535
- output["note"] = f"Some sarcastic elements detected alongside {emotions[0]}."
536
-
537
- return fig, output
538
-
539
  except Exception as e:
540
  import traceback
541
- print(f"Error in analyze_emotions: {str(e)}")
542
- print(traceback.format_exc())
543
- return None, {"error": f"Analysis failed: {str(e)}"}
544
-
545
- def create_visualization(emotions, scores, text=None, supporting_evidence=None):
546
- """Create a bar chart visualization of emotion scores with fixed x-axis and evidence"""
547
- fig, ax = plt.subplots(figsize=(12, 7))
548
-
549
- # Use custom colors for the bars
550
- colors = [EMOTION_COLORS.get(emotion, '#1f77b4') for emotion in emotions]
551
-
552
- # Create horizontal bar chart
553
- y_pos = np.arange(len(emotions))
554
- ax.barh(y_pos, [score * 100 for score in scores], color=colors)
555
-
556
- # Set fixed x-axis from 0 to 100
557
- ax.set_xlim(0, 100)
558
- ax.set_xticks(np.arange(0, 101, 10))
559
- ax.set_xlabel('Confidence (%)')
560
-
561
- # Set y-ticks and labels
562
- ax.set_yticks(y_pos)
563
-
564
- # Create custom labels with probability
565
- y_labels = []
566
- for i, emotion in enumerate(emotions):
567
- prob_text = f"{scores[i]*100:.1f}%"
568
- y_labels.append(f"{emotion.capitalize()} ({prob_text})")
569
-
570
- # Add evidence as smaller text
571
- if supporting_evidence and emotion in supporting_evidence and supporting_evidence[emotion]:
572
- evidence_x = 101 # Position just outside the plot area
573
-
574
- for j, evidence in enumerate(supporting_evidence[emotion]):
575
- ax.text(evidence_x, y_pos[i] - 0.15 + (j * 0.3),
576
- evidence,
577
- fontsize=8, color='#555555',
578
- verticalalignment='center')
579
-
580
- ax.set_yticklabels(y_labels)
581
- ax.invert_yaxis() # Labels read top-to-bottom
582
-
583
- # Add value labels to the bars
584
- for i, v in enumerate(scores):
585
- ax.text(v * 100 + 1, i, f"{v*100:.1f}%", va='center')
586
-
587
- # Set title with truncated text if provided
588
- if text:
589
- display_text = text if len(text) < 50 else text[:47] + "..."
590
- ax.set_title(f'Emotion Analysis: "{display_text}"', pad=20)
591
- else:
592
- ax.set_title('spaCy-based Emotion Analysis', pad=20)
593
-
594
  plt.tight_layout()
 
 
 
595
  return fig
596
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
  # Create Gradio interface
598
- demo = gr.Interface(
599
- fn=analyze_emotions,
600
- inputs=gr.Textbox(
601
- lines=4,
602
- placeholder="Enter text to analyze emotions...",
603
- label="Input Text"
604
- ),
605
- outputs=[
606
- gr.Plot(label="Emotion Distribution"),
607
- gr.JSON(label="Analysis Results")
608
- ],
609
- title="🧠 spaCy-based Emotion Analysis",
610
- description="""This app analyzes emotions in text using a fast spaCy-based approach.
611
- It identifies how well the input text aligns with ten emotional categories: joy, sadness, anger, fear, surprise, love, sarcasm, disgust, anticipation, and trust.
612
- The analysis leverages spaCy's word vectors along with phrase detection and linguistic pattern recognition to evaluate emotional content.""",
613
- examples=[
614
- ["I can't wait for the concert tonight! It's going to be amazing!"],
615
- ["The news about the layoffs has left everyone feeling devastated."],
616
- ["I'm absolutely furious about how they handled this situation."],
617
- ["I'm really nervous about the upcoming presentation."],
618
- ["Wow! I didn't expect that plot twist at all!"],
619
- ["I deeply cherish the time we spend together."],
620
- ["Oh great, another meeting that could have been an email. Just what I needed today."],
621
- ["Sure, I'd LOVE to do your work for you. Nothing better than doing two jobs for one salary!"],
622
- ["What a FANTASTIC way to start the day - my car won't start and it's pouring rain!"],
623
- ["This new restaurant is absolutely mind-blowing. The flavors are incredible!"],
624
- ["I'm heartbroken after hearing what happened. I can't believe it."]
625
- ],
626
- allow_flagging="never"
627
- )
 
 
 
 
 
 
 
 
 
 
628
 
629
- # Launch the app
630
  if __name__ == "__main__":
631
- print("Starting Gradio app...")
632
- # Use launch parameters that work well in Hugging Face Spaces
633
- demo.launch(debug=False)
 
 
11
  import gradio
12
  except ImportError:
13
  print("Installing required packages...")
14
+ subprocess.check_call([sys.executable, "-m", "pip", "install",
15
  "spacy", "matplotlib", "gradio"])
16
  # Download spaCy model
17
  subprocess.check_call([sys.executable, "-m", "spacy", "download", "en_core_web_md"])
 
32
  # Enhanced emotion categories with carefully selected keywords
33
  EMOTION_CATEGORIES = {
34
  'joy': [
35
+ 'happy', 'joyful', 'delighted', 'excited', 'cheerful',
36
  'glad', 'elated', 'jubilant', 'overjoyed', 'pleased',
37
  'ecstatic', 'thrilled', 'euphoric', 'content', 'blissful'
38
  ],
39
  'sadness': [
40
+ 'sad', 'unhappy', 'depressed', 'disappointed', 'sorrowful',
41
  'heartbroken', 'melancholy', 'grief', 'somber', 'mournful',
42
  'gloomy', 'despondent', 'downcast', 'miserable', 'devastated'
43
  ],
44
  'anger': [
45
+ 'angry', 'furious', 'enraged', 'irritated', 'annoyed',
46
  'outraged', 'hostile', 'mad', 'infuriated', 'indignant',
47
  'livid', 'irate', 'fuming', 'seething', 'resentful'
48
  ],
49
  'fear': [
50
+ 'afraid', 'scared', 'frightened', 'terrified', 'anxious',
51
  'worried', 'nervous', 'panicked', 'horrified', 'apprehensive',
52
  'fearful', 'uneasy', 'alarmed', 'dread', 'paranoid'
53
  ],
54
  'surprise': [
55
+ 'surprised', 'amazed', 'astonished', 'shocked', 'stunned',
56
  'startled', 'astounded', 'bewildered', 'unexpected', 'awestruck',
57
  'flabbergasted', 'dumbfounded', 'incredulous', 'perplexed', 'thunderstruck'
58
  ],
59
  'love': [
60
+ 'loving', 'affectionate', 'fond', 'adoring', 'caring',
61
  'devoted', 'passionate', 'tender', 'compassionate', 'cherishing',
62
  'enamored', 'smitten', 'infatuated', 'admiring', 'doting'
63
  ],
64
  'sarcasm': [
65
+ 'sarcastic', 'ironic', 'mocking', 'cynical', 'satirical',
66
  'sardonic', 'facetious', 'contemptuous', 'caustic', 'biting',
67
  'scornful', 'derisive', 'snide', 'taunting', 'wry'
68
  ],
 
100
  # Common sentiment phrases and expressions for improved detection
101
  EMOTION_PHRASES = {
102
  'joy': [
103
+ 'over the moon', 'on cloud nine', 'couldn\'t be happier',
104
  'best day ever', 'made my day', 'feeling great',
105
  'absolutely thrilled', 'jumping for joy', 'bursting with happiness',
106
  'walking on sunshine', 'flying high', 'tickled pink'
107
  ],
108
  'sadness': [
109
+ 'broke my heart', 'in tears', 'feel like crying',
110
  'deeply saddened', 'lost all hope', 'feel empty',
111
  'devastating news', 'hit hard', 'feel down', 'soul-crushing',
112
  'falling apart', 'world is ending', 'deeply hurt'
113
  ],
114
  'anger': [
115
+ 'makes my blood boil', 'fed up with', 'had it with',
116
  'sick and tired of', 'drives me crazy', 'lost my temper',
117
  'absolutely furious', 'beyond frustrated', 'driving me up the wall',
118
  'at my wit\'s end', 'through the roof', 'blow a gasket', 'see red'
119
  ],
120
  'fear': [
121
+ 'scared to death', 'freaking out', 'keeps me up at night',
122
  'terrified of', 'living in fear', 'panic attack',
123
  'nervous wreck', 'can\'t stop worrying', 'break out in a cold sweat',
124
  'shaking like a leaf', 'scared stiff', 'frozen with fear'
125
  ],
126
  'surprise': [
127
+ 'can\'t believe', 'took me by surprise', 'out of nowhere',
128
  'never expected', 'caught off guard', 'mind blown',
129
  'plot twist', 'jaw dropped', 'knocked my socks off',
130
  'took my breath away', 'blew me away', 'speechless'
131
  ],
132
  'love': [
133
+ 'deeply in love', 'means the world to me', 'treasure every moment',
134
  'hold dear', 'close to my heart', 'forever grateful',
135
  'truly blessed', 'never felt this way', 'head over heels',
136
  'madly in love', 'heart skips a beat', 'love with all my heart'
137
  ],
138
  'sarcasm': [
139
+ 'just what I needed', 'couldn\'t get any better', 'how wonderful',
140
  'oh great', 'lucky me', 'my favorite part',
141
  'thrilled to bits', 'way to go', 'thanks for nothing',
142
  'brilliant job', 'story of my life', 'what a surprise'
143
  ],
144
  'disgust': [
145
+ 'makes me sick', 'turn my stomach', 'can\'t stand',
146
  'absolutely disgusting', 'utterly repulsive', 'gross',
147
+ 'revolting sight', 'nauseating', 'skin crawl',
148
  'makes me want to vomit', 'repulsed by', 'can hardly look at'
149
  ],
150
  'anticipation': [
 
170
  'punctuation': {'!': 'emphasis', '?': 'question', '...': 'hesitation'}
171
  }
172
 
173
+ # Emotional verdict categories for intelligently classifying mixed emotions
174
+ EMOTION_VERDICT_CATEGORIES = {
175
+ # Single dominant emotions (when over 35%)
176
+ 'purely_joyful': {'conditions': [('joy', 0.35)], 'label': 'Purely Joyful', 'description': 'Expressing happiness and positive emotions'},
177
+ 'deeply_sad': {'conditions': [('sadness', 0.35)], 'label': 'Deeply Sad', 'description': 'Expressing sadness and negative emotions'},
178
+ 'intensely_angry': {'conditions': [('anger', 0.35)], 'label': 'Intensely Angry', 'description': 'Expressing anger and frustration'},
179
+ 'primarily_fearful': {'conditions': [('fear', 0.35)], 'label': 'Primarily Fearful', 'description': 'Expressing fear and anxiety'},
180
+ 'genuinely_surprised': {'conditions': [('surprise', 0.35)], 'label': 'Genuinely Surprised', 'description': 'Expressing surprise and astonishment'},
181
+ 'deeply_loving': {'conditions': [('love', 0.35)], 'label': 'Deeply Loving', 'description': 'Expressing love and affection'},
182
+ 'clearly_sarcastic': {'conditions': [('sarcasm', 0.35)], 'label': 'Clearly Sarcastic', 'description': 'Expressing sarcasm and irony'},
183
+ 'utterly_disgusted': {'conditions': [('disgust', 0.35)], 'label': 'Utterly Disgusted', 'description': 'Expressing disgust and revulsion'},
184
+ 'eagerly_anticipating': {'conditions': [('anticipation', 0.35)], 'label': 'Eagerly Anticipating', 'description': 'Expressing anticipation and eagerness'},
185
+ 'firmly_trusting': {'conditions': [('trust', 0.35)], 'label': 'Firmly Trusting', 'description': 'Expressing trust and confidence'},
186
+
187
+ # Common emotional combinations
188
+ 'bitter_sweet': {
189
+ 'conditions': [('joy', 0.2), ('sadness', 0.2)],
190
+ 'label': 'Bittersweet',
191
+ 'description': 'Mixed feelings of happiness and sadness'
192
+ },
193
+ 'anxious_excitement': {
194
+ 'conditions': [('anticipation', 0.2), ('fear', 0.2)],
195
+ 'label': 'Anxious Excitement',
196
+ 'description': 'Mixture of excitement and nervousness'
197
+ },
198
+ 'angry_disappointment': {
199
+ 'conditions': [('anger', 0.2), ('sadness', 0.2)],
200
+ 'label': 'Angry Disappointment',
201
+ 'description': 'Disappointment expressed through anger'
202
+ },
203
+ 'ironic_amusement': {
204
+ 'conditions': [('sarcasm', 0.2), ('joy', 0.15)],
205
+ 'label': 'Ironic Amusement',
206
+ 'description': 'Finding humor through irony or sarcasm'
207
+ },
208
+ 'fearful_anticipation': {
209
+ 'conditions': [('fear', 0.2), ('anticipation', 0.2)],
210
+ 'label': 'Fearful Anticipation',
211
+ 'description': 'Anxiously awaiting something'
212
+ },
213
+ 'relieved_surprise': {
214
+ 'conditions': [('surprise', 0.2), ('joy', 0.15)],
215
+ 'label': 'Relieved Surprise',
216
+ 'description': 'Surprise with positive outcome'
217
+ },
218
+ 'shocked_disappointment': {
219
+ 'conditions': [('surprise', 0.2), ('sadness', 0.15)],
220
+ 'label': 'Shocked Disappointment',
221
+ 'description': 'Unexpectedly negative outcome'
222
+ },
223
+ 'disgusted_anger': {
224
+ 'conditions': [('disgust', 0.2), ('anger', 0.2)],
225
+ 'label': 'Disgusted Anger',
226
+ 'description': 'Angry response to something repulsive'
227
+ },
228
+ 'loving_trust': {
229
+ 'conditions': [('love', 0.2), ('trust', 0.2)],
230
+ 'label': 'Loving Trust',
231
+ 'description': 'Deep affection with confidence'
232
+ },
233
+ 'sarcastic_frustration': {
234
+ 'conditions': [('sarcasm', 0.2), ('anger', 0.15)],
235
+ 'label': 'Sarcastic Frustration',
236
+ 'description': 'Using sarcasm to express frustration'
237
+ },
238
+ 'confused_surprise': {
239
+ 'conditions': [('surprise', 0.2), ('fear', 0.15)],
240
+ 'label': 'Confused Surprise',
241
+ 'description': 'Startled with uncertainty'
242
+ },
243
+ 'hopeful_joy': {
244
+ 'conditions': [('joy', 0.2), ('anticipation', 0.2)],
245
+ 'label': 'Hopeful Joy',
246
+ 'description': 'Happy anticipation of something positive'
247
+ },
248
+ 'betrayed_trust': {
249
+ 'conditions': [('sadness', 0.2), ('trust', 0.15), ('anger', 0.15)],
250
+ 'label': 'Betrayed Trust',
251
+ 'description': 'Sadness from broken trust'
252
+ },
253
+ 'fearful_disgust': {
254
+ 'conditions': [('fear', 0.2), ('disgust', 0.2)],
255
+ 'label': 'Fearful Disgust',
256
+ 'description': 'Fear of something repulsive'
257
+ },
258
+
259
+ # Special cases for multiple emotions
260
+ 'emotionally_complex': {
261
+ 'conditions': ['multiple_over_15'],
262
+ 'label': 'Emotionally Complex',
263
+ 'description': 'Multiple competing emotions'
264
+ },
265
+ 'mildly_emotional': {
266
+ 'conditions': ['all_under_20'],
267
+ 'label': 'Mildly Emotional',
268
+ 'description': 'Low intensity emotional content'
269
+ },
270
+ 'predominantly_neutral': {
271
+ 'conditions': ['all_under_15'],
272
+ 'label': 'Predominantly Neutral',
273
+ 'description': 'No strong emotional signals detected'
274
+ }
275
+ }
276
+
277
  # Sarcasm patterns with refined detection logic
278
  SARCASM_PATTERNS = [
279
  # Exaggerated positive with negative context
280
  r'(?i)\b(?:so+|really|absolutely|totally|completely)\s+(?:thrilled|excited|happy|delighted)\s+(?:about|with|by)\b.*?(?:terrible|awful|worst|bad)',
281
+
282
  # Classic sarcastic phrases
283
  r'(?i)(?:^|\W)just\s+what\s+(?:I|we)\s+(?:need|wanted|hoped for)\b',
284
  r'(?i)(?:^|\W)how\s+(?:wonderful|nice|great|lovely|exciting)\b.*?(?:\!|\?{2,})',
285
+
286
  # Thanks for nothing pattern
287
  r'(?i)(?:^|\W)thanks\s+for\s+(?:nothing|that|pointing|stating)\b',
288
+
289
  # Quotation marks around positive words (scare quotes)
290
  r'(?i)"(?:great|wonderful|excellent|perfect|amazing)"',
291
+
292
  # Typical sarcastic responses
293
  r'(?i)^(?:yeah|sure|right|oh)\s+(?:right|sure|okay|ok)(?:\W|$)',
294
+
295
  # Exaggerated praise in negative context
296
  r'(?i)\b(?:brilliant|genius|impressive)\b.*?(?:disaster|failure|mess)',
297
+
298
  # Obvious understatements
299
  r'(?i)\b(?:slightly|bit|little)\s+(?:catastrophic|disastrous|terrible|awful)\b',
300
+
301
  # Oh great patterns
302
  r'(?i)(?:^|\W)oh\s+(?:great|wonderful|perfect|fantastic|awesome)(?:\W|$)'
303
  ]
 
312
  """Detect emotion-specific phrases in text"""
313
  text_lower = text.lower()
314
  detected_phrases = {}
315
+
316
  for emotion, phrases in emotion_phrases.items():
317
  found_phrases = []
318
  for phrase in phrases:
319
  if phrase.lower() in text_lower:
320
  found_phrases.append(phrase)
321
+
322
  if found_phrases:
323
  detected_phrases[emotion] = found_phrases
324
+
325
  return detected_phrases
326
 
327
  def detect_contextual_features(text):
 
336
  'ellipses': text.count('...'),
337
  'capitalized_words': len(re.findall(r'\b[A-Z]{2,}\b', text))
338
  }
339
+
340
  doc = nlp(text.lower())
341
+
342
  # Get tokens for counting
343
  tokens = [token.text for token in doc]
344
+
345
  # Count contextual indicators
346
  for indicator_type, words in CONTEXTUAL_INDICATORS.items():
347
  if indicator_type != 'punctuation':
 
351
  features[indicator_type] += 1
352
  else: # Single word
353
  features[indicator_type] += tokens.count(word)
354
+
355
  return features
356
 
357
  def detect_sarcasm_patterns(text):
 
359
  # Match sarcasm patterns
360
  matches = 0
361
  pattern_matches = []
362
+
363
  for pattern in SARCASM_PATTERNS:
364
  if re.search(pattern, text):
365
  matches += 1
366
  pattern_matches.append(pattern)
367
+
368
  # Get contextual features
369
  features = detect_contextual_features(text)
370
+
371
  # Check for phrases specific to sarcasm
372
  phrases = detect_phrases(text, {'sarcasm': EMOTION_PHRASES['sarcasm']})
373
  sarcasm_phrases = len(phrases.get('sarcasm', []))
374
+
375
  # Calculate raw score based on pattern matches and features
376
  raw_score = (matches * 0.15) + (sarcasm_phrases * 0.2)
377
+
378
  # Adjust based on contextual features
379
  if features['exclamations'] > 1:
380
  raw_score += min(features['exclamations'] * 0.05, 0.2)
381
+
382
  if features['capitalized_words'] > 0:
383
  raw_score += min(features['capitalized_words'] * 0.1, 0.3)
384
+
385
  # Detect positive-negative contrasts
386
  pos_neg_contrast = 0
387
  emotion_phrases = detect_phrases(text, {
388
  'positive': EMOTION_PHRASES['joy'] + EMOTION_PHRASES['love'],
389
  'negative': EMOTION_PHRASES['sadness'] + EMOTION_PHRASES['anger']
390
  })
391
+
392
  if emotion_phrases.get('positive') and emotion_phrases.get('negative'):
393
  pos_neg_contrast = 0.3
394
+
395
  # Add contrast score
396
  raw_score += pos_neg_contrast
397
+
398
  # Normalize to [0, 1]
399
  return min(raw_score, 1.0), pattern_matches
400
 
 
402
  """Calculate similarity between text and emotion keywords using spaCy"""
403
  if not text.strip():
404
  return 0.0
405
+
406
  # Process the input text
407
  doc = nlp(text.lower())
408
+
409
  # Get average similarity with emotion keywords
410
  keyword_scores = []
411
+
412
  # Use a subset of keywords for efficiency
413
  for keyword in emotion_keywords[:6]: # Use first 6 keywords for each emotion
414
  keyword_doc = nlp(keyword)
 
419
  for keyword_token in keyword_doc:
420
  similarity = token.similarity(keyword_token)
421
  max_similarity = max(max_similarity, similarity)
422
+
423
  keyword_scores.append(max_similarity)
424
+
425
  # Return average of top 3 similarities if we have at least 3 scores
426
  if len(keyword_scores) >= 3:
427
  return sum(sorted(keyword_scores, reverse=True)[:3]) / 3
 
435
  """Calculate emotion score based on similarity, context, and phrase detection"""
436
  # Get emotion score using spaCy word vectors
437
  similarity_score = calculate_emotion_similarity(text, keywords)
438
+
439
  # Check for emotion-specific phrases
440
  detected_phrases = detect_phrases(text, {emotion: EMOTION_PHRASES[emotion]})
441
  phrase_count = len(detected_phrases.get(emotion, []))
442
  phrase_score = min(phrase_count * 0.2, 0.6) # Cap at 0.6
443
+
444
  # Get contextual features
445
  features = detect_contextual_features(text)
446
+
447
  # Calculate feature-based adjustment
448
  feature_adjustment = 0
449
+
450
  # Search for direct emotion mentions in text
451
  doc = nlp(text.lower())
452
  direct_mention_score = 0
453
+
454
  for token in doc:
455
  if token.lemma_ in keywords:
456
  direct_mention_score += 0.2 # Direct mention of emotion word
457
  break
458
+
459
  # Adjust score based on emotional context
460
  if emotion in ['joy', 'love', 'surprise'] and features['exclamations'] > 0:
461
  feature_adjustment += min(features['exclamations'] * 0.05, 0.2)
462
+
463
  if emotion in ['anger', 'sadness'] and features['negators'] > 0:
464
  feature_adjustment += min(features['negators'] * 0.05, 0.2)
465
+
466
  if emotion == 'fear' and features['intensifiers'] > 0:
467
  feature_adjustment += min(features['intensifiers'] * 0.05, 0.2)
468
+
469
  # Combine scores with appropriate weights
470
  final_score = (similarity_score * 0.5) + (phrase_score * 0.3) + (feature_adjustment * 0.1) + (direct_mention_score * 0.1)
471
+
472
  # Normalize to ensure it's in [0, 1]
473
  return max(0, min(final_score, 1.0)), detected_phrases.get(emotion, [])
474
 
 
477
  # 1. Keyword similarity for sarcasm words
478
  sarcasm_keywords = EMOTION_CATEGORIES['sarcasm']
479
  similarity_score = calculate_emotion_similarity(text, sarcasm_keywords)
480
+
481
  # 2. Linguistic pattern detection
482
  pattern_score, pattern_matches = detect_sarcasm_patterns(text)
483
+
484
  # 3. Check for semantic incongruity between sentences
485
  incongruity_score = 0
486
  sentences = list(nlp(text).sents)
487
+
488
  if len(sentences) > 1:
489
  # Calculate similarity between adjacent sentences
490
  similarities = []
491
  for i in range(len(sentences) - 1):
492
  sim = sentences[i].similarity(sentences[i+1])
493
  similarities.append(sim)
494
+
495
  # Low similarity between adjacent sentences might indicate sarcasm
496
  if similarities and min(similarities) < 0.5:
497
  incongruity_score = 0.3
498
+
499
  # 4. Check for sarcasm phrases
500
  detected_phrases = detect_phrases(text, {'sarcasm': EMOTION_PHRASES['sarcasm']})
501
  phrase_score = min(len(detected_phrases.get('sarcasm', [])) * 0.2, 0.6)
502
+
503
  # 5. Check for emotional contrast
504
  # (positive words in negative context or vice versa)
505
  doc = nlp(text.lower())
506
+
507
  # Count positive and negative words
508
  pos_count = 0
509
  neg_count = 0
510
+
511
  for token in doc:
512
  if token.is_alpha and not token.is_stop:
513
  # Check against positive and negative emotion keywords
 
515
  pos_count += 1
516
  if any(token.similarity(nlp(word)) > 0.7 for word in EMOTION_CATEGORIES['sadness'][:5] + EMOTION_CATEGORIES['anger'][:5]):
517
  neg_count += 1
518
+
519
  contrast_score = 0
520
  if pos_count > 0 and neg_count > 0:
521
  contrast_score = min(0.3, pos_count * neg_count * 0.05)
522
+
523
  # Weighted combination of all scores
524
  combined_score = (0.15 * similarity_score) + (0.35 * pattern_score) + \
525
  (0.15 * incongruity_score) + (0.25 * phrase_score) + \
526
  (0.1 * contrast_score)
527
+
528
  # Normalize to [0, 1]
529
  return max(0, min(combined_score, 1.0)), detected_phrases.get('sarcasm', []), pattern_matches
530
 
531
+ def determine_emotional_verdict(emotion_scores):
532
+ """Determine the emotional verdict based on the emotional profile"""
533
+ # Create a sorted list of emotions by score
534
+ sorted_emotions = sorted(emotion_scores.items(), key=lambda x: x[1], reverse=True)
535
+
536
+ # Count emotions over different thresholds
537
+ emotions_over_35 = [e for e, s in sorted_emotions if s > 0.35]
538
+ emotions_over_20 = [e for e, s in sorted_emotions if s > 0.20]
539
+ emotions_over_15 = [e for e, s in sorted_emotions if s > 0.15]
540
+
541
+ # Check if we have a strong dominant emotion (over 35%)
542
+ if emotions_over_35:
543
+ dominant_emotion = emotions_over_35[0]
544
+ for verdict_key, verdict_info in EMOTION_VERDICT_CATEGORIES.items():
545
+ conditions = verdict_info['conditions']
546
+ # Check single emotion conditions
547
+ if len(conditions) == 1 and isinstance(conditions[0], tuple):
548
+ emotion, threshold = conditions[0]
549
+ if emotion == dominant_emotion and emotion_scores[emotion] >= threshold:
550
+ return verdict_info['label'], verdict_info['description']
551
+
552
+ # Check for emotion combinations
553
+ for verdict_key, verdict_info in EMOTION_VERDICT_CATEGORIES.items():
554
+ conditions = verdict_info['conditions']
555
+
556
+ # Skip single emotion conditions we've already checked
557
+ if len(conditions) == 1 and isinstance(conditions[0], tuple):
558
+ continue
559
+
560
+ # Handle special condition types
561
+ if conditions == ['multiple_over_15'] and len(emotions_over_15) >= 3:
562
+ return verdict_info['label'], verdict_info['description']
563
+
564
+ if conditions == ['all_under_20'] and not emotions_over_20:
565
+ return verdict_info['label'], verdict_info['description']
566
+
567
+ if conditions == ['all_under_15'] and not emotions_over_15:
568
+ return verdict_info['label'], verdict_info['description']
569
+
570
+ # Check standard combination conditions
571
+ if all(emotion_scores.get(emotion, 0) >= threshold for emotion, threshold in conditions):
572
+ return verdict_info['label'], verdict_info['description']
573
+
574
+ # If we've found nothing specific but have some emotions over 15%
575
+ if emotions_over_15:
576
+ if len(emotions_over_15) == 1:
577
+ # Use the single emotion even though it's not super strong
578
+ emotion = emotions_over_15[0]
579
+ return f"Moderately {emotion.capitalize()}", f"Shows some signs of {emotion}"
580
+ else:
581
+ # Create a custom mixed emotion label
582
+ primary = emotions_over_15[0].capitalize()
583
+ secondary = emotions_over_15[1].capitalize()
584
+ return f"{primary} with {secondary}", f"A mix of {primary.lower()} and {secondary.lower()} emotions"
585
+
586
+ # Default fallback
587
+ return "Neutral or Subtle", "No clear emotional signals detected"
588
+
589
  def analyze_emotions(text):
590
+ """Analyze emotions in text using spaCy with robust sarcasm detection and emotional verdict"""
591
  if not text or not text.strip():
592
  return None, {"error": "Please enter some text to analyze"}
593
+
594
  try:
595
  # Calculate scores for each emotion with supporting phrases
596
  emotion_data = {}
597
+
598
  # For each standard emotion category (excluding sarcasm)
599
  for emotion, keywords in EMOTION_CATEGORIES.items():
600
  if emotion == 'sarcasm':
601
  continue
602
+
603
  # Use specialized function to get emotion score and supporting phrases
604
  score, phrases = get_emotion_score(text, emotion, keywords)
605
  emotion_data[emotion] = {
606
  'score': score,
607
  'phrases': phrases
608
  }
609
+
610
  # Special handling for sarcasm with multi-method approach
611
  sarcasm_score, sarcasm_phrases, sarcasm_patterns = analyze_sarcasm(text)
612
  emotion_data['sarcasm'] = {
 
614
  'phrases': sarcasm_phrases,
615
  'patterns': sarcasm_patterns
616
  }
617
+
618
  # Get contextual features for overall analysis
619
  context_features = detect_contextual_features(text)
620
+
621
  # Apply decision making for final analysis
622
  # 1. Check for dominant emotions by raw scores
623
  emotion_scores = {emotion: data['score'] for emotion, data in emotion_data.items()}
624
+
625
  # 2. Adjust based on contextual evidence
626
+ # If we have strong phrase evidence, boost the score slightly
627
  for emotion, data in emotion_data.items():
628
+ if len(data.get('phrases', [])) > 1:
629
+ emotion_scores[emotion] = min(emotion_scores[emotion] * 1.2, 1.0)
630
+
631
+ # 3. Get emotional verdict
632
+ verdict_label, verdict_description = determine_emotional_verdict(emotion_scores)
633
+
634
+ # 4. Normalize scores to percentages
635
+ total_score = sum(emotion_scores.values()) or 1 # Avoid division by zero
636
+ emotion_percentages = {emotion: (score / total_score) * 100 for emotion, score in emotion_scores.items()}
637
+
638
+ # Sort emotions by percentage for display
639
+ sorted_emotions = sorted(emotion_percentages.items(), key=lambda x: x[1], reverse=True)
640
+
641
+ # Prepare result
642
+ result = {
643
+ 'emotion_scores': sorted_emotions,
644
+ 'emotional_verdict': {
645
+ 'label': verdict_label,
646
+ 'description': verdict_description
647
+ },
648
+ 'top_emotions': sorted_emotions[:3],
649
+ 'supporting_evidence': {
650
+ emotion: data.get('phrases', []) for emotion, data in emotion_data.items() if data.get('phrases')
651
+ },
652
+ 'context_features': context_features
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
  }
654
+
655
+ return create_visualization(result), result
656
+
 
 
 
 
 
 
657
  except Exception as e:
658
  import traceback
659
+ error_msg = traceback.format_exc()
660
+ return None, {"error": f"Analysis error: {str(e)}", "details": error_msg}
661
+
662
+ def create_visualization(result):
663
+ """Create visualization of emotion analysis results"""
664
+ # Create figure and axis
665
+ fig, ax = plt.subplots(figsize=(10, 6))
666
+
667
+ # Extract emotion data
668
+ emotions = [e[0] for e in result['emotion_scores']]
669
+ scores = [e[1] for e in result['emotion_scores']]
670
+
671
+ # Get colors
672
+ colors = [EMOTION_COLORS.get(emotion, '#CCCCCC') for emotion in emotions]
673
+
674
+ # Create bar chart
675
+ bars = ax.bar(emotions, scores, color=colors)
676
+
677
+ # Add title and labels
678
+ ax.set_title('Emotion Analysis', fontsize=16, fontweight='bold')
679
+ ax.set_xlabel('Emotions', fontsize=12)
680
+ ax.set_ylabel('Score (%)', fontsize=12)
681
+
682
+ # Add verdict as text annotation
683
+ verdict = result['emotional_verdict']['label']
684
+ description = result['emotional_verdict']['description']
685
+ ax.text(0.5, -0.15, f"Verdict: {verdict}", ha='center', transform=ax.transAxes, fontsize=14, fontweight='bold')
686
+ ax.text(0.5, -0.2, f"{description}", ha='center', transform=ax.transAxes, fontsize=12)
687
+
688
+ # Rotate x-axis labels for readability
689
+ plt.xticks(rotation=45, ha='right')
690
+
691
+ # Add value labels on top of bars
692
+ for bar in bars:
693
+ height = bar.get_height()
694
+ ax.text(bar.get_x() + bar.get_width()/2., height + 0.5,
695
+ f'{height:.1f}%', ha='center', va='bottom', fontsize=10)
696
+
697
+ # Adjust layout
 
 
 
 
 
 
 
 
 
 
 
 
 
 
698
  plt.tight_layout()
699
+ plt.subplots_adjust(bottom=0.25) # Make room for the verdict text
700
+
701
+ # Return figure
702
  return fig
703
 
704
+ def analyze_text(text):
705
+ """Analyze text and return visualization and detailed results"""
706
+ fig, result = analyze_emotions(text)
707
+
708
+ if 'error' in result:
709
+ return None, result['error']
710
+
711
+ # Format the results for display
712
+ verdict = result['emotional_verdict']['label']
713
+ description = result['emotional_verdict']['description']
714
+
715
+ # Create a formatted summary
716
+ summary = f"## Emotional Analysis Results\n\n"
717
+ summary += f"**Verdict:** {verdict}\n\n"
718
+ summary += f"**Description:** {description}\n\n"
719
+
720
+ summary += "### Top Emotions:\n"
721
+ for emotion, score in result['top_emotions']:
722
+ summary += f"- {emotion.capitalize()}: {score:.1f}%\n"
723
+
724
+ if result['supporting_evidence']:
725
+ summary += "\n### Supporting Evidence:\n"
726
+ for emotion, phrases in result['supporting_evidence'].items():
727
+ if phrases:
728
+ summary += f"- **{emotion.capitalize()}**: {', '.join(phrases)}\n"
729
+
730
+ return fig, summary
731
+
732
  # Create Gradio interface
733
+ def create_interface():
734
+ with gr.Blocks(title="Emotional Analysis Tool") as demo:
735
+ gr.Markdown("# Advanced Emotion Analysis")
736
+ gr.Markdown("Enter text to analyze the emotional content and receive a detailed breakdown.")
737
+
738
+ with gr.Row():
739
+ with gr.Column(scale=2):
740
+ text_input = gr.Textbox(
741
+ label="Text to analyze",
742
+ placeholder="Enter text here...",
743
+ lines=10
744
+ )
745
+ analyze_button = gr.Button("Analyze Emotions")
746
+
747
+ with gr.Column(scale=3):
748
+ with gr.Tab("Visualization"):
749
+ plot_output = gr.Plot(label="Emotion Distribution")
750
+ with gr.Tab("Summary"):
751
+ text_output = gr.Markdown(label="Analysis Summary")
752
+
753
+ analyze_button.click(
754
+ fn=analyze_text,
755
+ inputs=text_input,
756
+ outputs=[plot_output, text_output]
757
+ )
758
+
759
+ gr.Markdown("""
760
+ ## About This Tool
761
+
762
+ This advanced emotion analysis tool uses NLP techniques to detect and analyze emotions in text.
763
+ It can identify:
764
+ - 10 different emotions (joy, sadness, anger, fear, surprise, love, sarcasm, disgust, anticipation, trust)
765
+ - Complex emotional combinations
766
+ - Contextual features that affect emotional interpretation
767
+ - Intelligent emotional verdicts for mixed emotion states
768
+
769
+ The model uses word vectors, phrase detection, and contextual analysis for accurate emotion recognition.
770
+ """)
771
+
772
+ return demo
773
 
774
+ # Launch the interface if running directly
775
  if __name__ == "__main__":
776
+ print("Creating Gradio interface...")
777
+ demo = create_interface()
778
+ demo.launch(share=True)
779
+ print("Gradio interface launched!")