File size: 31,522 Bytes
f44ef6f
 
0bc2de3
f44ef6f
0bc2de3
 
 
 
b5c9ccb
0bc2de3
 
 
 
35e0a5d
b5c9ccb
 
 
0f15ab4
b5c9ccb
0bc2de3
 
 
 
c3f5238
0bc2de3
b5c9ccb
 
 
 
 
0bc2de3
c3f5238
0bc2de3
 
35e0a5d
b5c9ccb
 
0bc2de3
 
35e0a5d
b5c9ccb
 
0bc2de3
 
35e0a5d
b5c9ccb
 
0bc2de3
 
35e0a5d
b5c9ccb
 
0bc2de3
 
35e0a5d
b5c9ccb
 
0bc2de3
 
35e0a5d
b5c9ccb
 
0bc2de3
 
35e0a5d
b5c9ccb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0bc2de3
 
3333547
 
 
 
 
0bc2de3
3333547
0bc2de3
 
b5c9ccb
 
 
 
3333547
 
2c73ed4
 
 
35e0a5d
2c73ed4
b5c9ccb
 
2c73ed4
 
35e0a5d
2c73ed4
b5c9ccb
 
2c73ed4
 
35e0a5d
2c73ed4
b5c9ccb
 
2c73ed4
 
35e0a5d
2c73ed4
b5c9ccb
 
2c73ed4
 
35e0a5d
2c73ed4
b5c9ccb
 
2c73ed4
 
35e0a5d
2c73ed4
b5c9ccb
 
2c73ed4
 
35e0a5d
2c73ed4
b5c9ccb
 
 
 
35e0a5d
b5c9ccb
35e0a5d
b5c9ccb
 
 
 
 
 
 
 
 
 
 
 
 
2c73ed4
 
 
 
 
 
 
 
 
 
 
c3f5238
35e0a5d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2c73ed4
0bc2de3
2c73ed4
 
35e0a5d
c3f5238
2c73ed4
 
35e0a5d
2c73ed4
 
35e0a5d
c3f5238
 
35e0a5d
c3f5238
2c73ed4
35e0a5d
c3f5238
2c73ed4
35e0a5d
c3f5238
2c73ed4
35e0a5d
2c73ed4
 
0bc2de3
 
c3f5238
b5c9ccb
 
 
 
c3f5238
2c73ed4
 
 
 
35e0a5d
2c73ed4
 
 
 
 
35e0a5d
2c73ed4
 
35e0a5d
2c73ed4
 
 
 
 
 
 
 
 
 
 
 
 
 
35e0a5d
b5c9ccb
35e0a5d
b5c9ccb
 
35e0a5d
2c73ed4
 
 
 
 
b5c9ccb
2c73ed4
 
 
35e0a5d
2c73ed4
c3f5238
 
 
 
0bc2de3
c3f5238
35e0a5d
0bc2de3
c3f5238
0bc2de3
c3f5238
35e0a5d
2c73ed4
 
35e0a5d
2c73ed4
 
 
35e0a5d
2c73ed4
 
35e0a5d
2c73ed4
 
 
35e0a5d
2c73ed4
 
35e0a5d
2c73ed4
 
 
 
 
 
35e0a5d
2c73ed4
 
35e0a5d
2c73ed4
 
35e0a5d
c3f5238
2c73ed4
0bc2de3
b5c9ccb
 
 
 
35e0a5d
b5c9ccb
 
35e0a5d
b5c9ccb
c3f5238
35e0a5d
c3f5238
b5c9ccb
 
 
 
 
 
 
 
 
35e0a5d
b5c9ccb
35e0a5d
b5c9ccb
 
 
 
 
 
 
 
 
 
 
 
 
35e0a5d
2c73ed4
 
 
 
35e0a5d
2c73ed4
 
35e0a5d
2c73ed4
 
35e0a5d
b5c9ccb
 
 
35e0a5d
b5c9ccb
 
 
 
35e0a5d
2c73ed4
 
 
35e0a5d
2c73ed4
 
35e0a5d
2c73ed4
 
35e0a5d
b5c9ccb
 
35e0a5d
2c73ed4
 
0bc2de3
c3f5238
b5c9ccb
 
 
 
35e0a5d
c3f5238
2c73ed4
35e0a5d
b5c9ccb
2c73ed4
b5c9ccb
35e0a5d
2c73ed4
b5c9ccb
2c73ed4
 
b5c9ccb
2c73ed4
35e0a5d
2c73ed4
 
 
35e0a5d
b5c9ccb
 
 
35e0a5d
b5c9ccb
 
 
35e0a5d
b5c9ccb
 
 
35e0a5d
b5c9ccb
 
 
 
 
 
 
35e0a5d
b5c9ccb
 
 
35e0a5d
c3f5238
b5c9ccb
 
 
35e0a5d
c3f5238
2c73ed4
3333547
35e0a5d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0bc2de3
35e0a5d
3333547
 
35e0a5d
3333547
2c73ed4
 
35e0a5d
c3f5238
0bc2de3
 
 
35e0a5d
2c73ed4
 
 
 
 
 
35e0a5d
c3f5238
2c73ed4
 
 
 
 
 
35e0a5d
2c73ed4
 
35e0a5d
b5c9ccb
2c73ed4
 
35e0a5d
2c73ed4
35e0a5d
2c73ed4
35e0a5d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3333547
35e0a5d
 
 
3333547
0bc2de3
35e0a5d
 
 
 
 
 
bd4c718
35e0a5d
 
 
 
 
 
 
 
bd4c718
 
 
 
 
 
 
35e0a5d
 
 
bd4c718
 
35e0a5d
bd4c718
35e0a5d
 
bd4c718
 
35e0a5d
 
 
bd4c718
 
 
35e0a5d
 
3333547
bd4c718
35e0a5d
 
3333547
 
bd4c718
35e0a5d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3333547
35e0a5d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3333547
35e0a5d
f44ef6f
35e0a5d
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
import subprocess
import sys
import os

# Check if running in a standard environment (not Colab/Jupyter)
# and install packages if needed
if not os.path.exists("/.dockerenv") and not os.path.exists("/kaggle"):
    try:
        import spacy
        import matplotlib
        import gradio
    except ImportError:
        print("Installing required packages...")
        subprocess.check_call([sys.executable, "-m", "pip", "install",
                              "spacy", "matplotlib", "gradio"])
        # Download spaCy model
        subprocess.check_call([sys.executable, "-m", "spacy", "download", "en_core_web_md"])

import spacy
import numpy as np
import matplotlib.pyplot as plt
import gradio as gr
import re
from collections import Counter

print("Setting up spaCy-based emotion analysis model...")

# Load spaCy model
print("Loading spaCy model (this takes just a moment)...")
nlp = spacy.load("en_core_web_md")

# Enhanced emotion categories with carefully selected keywords
EMOTION_CATEGORIES = {
    'joy': [
        'happy', 'joyful', 'delighted', 'excited', 'cheerful',
        'glad', 'elated', 'jubilant', 'overjoyed', 'pleased',
        'ecstatic', 'thrilled', 'euphoric', 'content', 'blissful'
    ],
    'sadness': [
        'sad', 'unhappy', 'depressed', 'disappointed', 'sorrowful',
        'heartbroken', 'melancholy', 'grief', 'somber', 'mournful',
        'gloomy', 'despondent', 'downcast', 'miserable', 'devastated'
    ],
    'anger': [
        'angry', 'furious', 'enraged', 'irritated', 'annoyed',
        'outraged', 'hostile', 'mad', 'infuriated', 'indignant',
        'livid', 'irate', 'fuming', 'seething', 'resentful'
    ],
    'fear': [
        'afraid', 'scared', 'frightened', 'terrified', 'anxious',
        'worried', 'nervous', 'panicked', 'horrified', 'apprehensive',
        'fearful', 'uneasy', 'alarmed', 'dread', 'paranoid'
    ],
    'surprise': [
        'surprised', 'amazed', 'astonished', 'shocked', 'stunned',
        'startled', 'astounded', 'bewildered', 'unexpected', 'awestruck',
        'flabbergasted', 'dumbfounded', 'incredulous', 'perplexed', 'thunderstruck'
    ],
    'love': [
        'loving', 'affectionate', 'fond', 'adoring', 'caring',
        'devoted', 'passionate', 'tender', 'compassionate', 'cherishing',
        'enamored', 'smitten', 'infatuated', 'admiring', 'doting'
    ],
    'sarcasm': [
        'sarcastic', 'ironic', 'mocking', 'cynical', 'satirical',
        'sardonic', 'facetious', 'contemptuous', 'caustic', 'biting',
        'scornful', 'derisive', 'snide', 'taunting', 'wry'
    ],
    'disgust': [
        'disgusted', 'revolted', 'nauseated', 'repulsed', 'sickened',
        'appalled', 'repelled', 'abhorred', 'loathing', 'distaste',
        'aversion', 'revulsion', 'repugnance', 'horrified', 'offended'
    ],
    'anticipation': [
        'anticipating', 'expecting', 'awaiting', 'looking forward', 'hopeful',
        'eager', 'excited', 'impatient', 'prepared', 'ready',
        'vigilant', 'attentive', 'watchful', 'alert', 'expectant'
    ],
    'trust': [
        'trusting', 'confident', 'assured', 'secure', 'certain',
        'reliant', 'faithful', 'believing', 'dependable', 'reliable',
        'credible', 'trustworthy', 'honest', 'loyal', 'devoted'
    ]
}

# Define emotion colors for visualization
EMOTION_COLORS = {
    'joy': '#F1C40F',       # Yellow
    'sadness': '#3498DB',   # Blue
    'anger': '#E74C3C',     # Red
    'fear': '#7D3C98',      # Purple
    'surprise': '#2ECC71',  # Green
    'love': '#E91E63',      # Pink
    'sarcasm': '#FF7F50',   # Coral
    'disgust': '#8E44AD',   # Dark Purple
    'anticipation': '#F39C12', # Orange
    'trust': '#16A085'      # Teal
}

# Common sentiment phrases and expressions for improved detection
EMOTION_PHRASES = {
    'joy': [
        'over the moon', 'on cloud nine', 'couldn\'t be happier',
        'best day ever', 'made my day', 'feeling great',
        'absolutely thrilled', 'jumping for joy', 'bursting with happiness',
        'walking on sunshine', 'flying high', 'tickled pink'
    ],
    'sadness': [
        'broke my heart', 'in tears', 'feel like crying',
        'deeply saddened', 'lost all hope', 'feel empty',
        'devastating news', 'hit hard', 'feel down', 'soul-crushing',
        'falling apart', 'world is ending', 'deeply hurt'
    ],
    'anger': [
        'makes my blood boil', 'fed up with', 'had it with',
        'sick and tired of', 'drives me crazy', 'lost my temper',
        'absolutely furious', 'beyond frustrated', 'driving me up the wall',
        'at my wit\'s end', 'through the roof', 'blow a gasket', 'see red'
    ],
    'fear': [
        'scared to death', 'freaking out', 'keeps me up at night',
        'terrified of', 'living in fear', 'panic attack',
        'nervous wreck', 'can\'t stop worrying', 'break out in a cold sweat',
        'shaking like a leaf', 'scared stiff', 'frozen with fear'
    ],
    'surprise': [
        'can\'t believe', 'took me by surprise', 'out of nowhere',
        'never expected', 'caught off guard', 'mind blown',
        'plot twist', 'jaw dropped', 'knocked my socks off',
        'took my breath away', 'blew me away', 'speechless'
    ],
    'love': [
        'deeply in love', 'means the world to me', 'treasure every moment',
        'hold dear', 'close to my heart', 'forever grateful',
        'truly blessed', 'never felt this way', 'head over heels',
        'madly in love', 'heart skips a beat', 'love with all my heart'
    ],
    'sarcasm': [
        'just what I needed', 'couldn\'t get any better', 'how wonderful',
        'oh great', 'lucky me', 'my favorite part',
        'thrilled to bits', 'way to go', 'thanks for nothing',
        'brilliant job', 'story of my life', 'what a surprise'
    ],
    'disgust': [
        'makes me sick', 'turn my stomach', 'can\'t stand',
        'absolutely disgusting', 'utterly repulsive', 'gross',
        'revolting sight', 'nauseating', 'skin crawl',
        'makes me want to vomit', 'repulsed by', 'can hardly look at'
    ],
    'anticipation': [
        'looking forward to', 'can\'t wait for', 'counting down the days',
        'eagerly awaiting', 'excited about', 'in anticipation of',
        'on the edge of my seat', 'can hardly wait', 'dying to see',
        'marked on my calendar', 'preparing for', 'gearing up for'
    ],
    'trust': [
        'rely on completely', 'trust with my life', 'put my faith in',
        'never let me down', 'count on', 'believe in',
        'have confidence in', 'trustworthy', 'dependable',
        'true to their word', 'rock solid', 'through thick and thin'
    ]
}

# Contextual emotion indicators for better analysis
CONTEXTUAL_INDICATORS = {
    'intensifiers': ['very', 'extremely', 'incredibly', 'absolutely', 'totally', 'completely', 'utterly'],
    'negators': ['not', 'never', 'no', 'none', 'neither', 'nor', 'hardly', 'barely'],
    'hedges': ['somewhat', 'kind of', 'sort of', 'a bit', 'slightly', 'perhaps', 'maybe'],
    'boosters': ['definitely', 'certainly', 'absolutely', 'undoubtedly', 'surely', 'clearly'],
    'punctuation': {'!': 'emphasis', '?': 'question', '...': 'hesitation'}
}

# Emotional verdict categories for intelligently classifying mixed emotions
EMOTION_VERDICT_CATEGORIES = {
    # Single dominant emotions (when over 35%)
    'purely_joyful': {'conditions': [('joy', 0.35)], 'label': 'Purely Joyful', 'description': 'Expressing happiness and positive emotions'},
    'deeply_sad': {'conditions': [('sadness', 0.35)], 'label': 'Deeply Sad', 'description': 'Expressing sadness and negative emotions'},
    'intensely_angry': {'conditions': [('anger', 0.35)], 'label': 'Intensely Angry', 'description': 'Expressing anger and frustration'},
    'primarily_fearful': {'conditions': [('fear', 0.35)], 'label': 'Primarily Fearful', 'description': 'Expressing fear and anxiety'},
    'genuinely_surprised': {'conditions': [('surprise', 0.35)], 'label': 'Genuinely Surprised', 'description': 'Expressing surprise and astonishment'},
    'deeply_loving': {'conditions': [('love', 0.35)], 'label': 'Deeply Loving', 'description': 'Expressing love and affection'},
    'clearly_sarcastic': {'conditions': [('sarcasm', 0.35)], 'label': 'Clearly Sarcastic', 'description': 'Expressing sarcasm and irony'},
    'utterly_disgusted': {'conditions': [('disgust', 0.35)], 'label': 'Utterly Disgusted', 'description': 'Expressing disgust and revulsion'},
    'eagerly_anticipating': {'conditions': [('anticipation', 0.35)], 'label': 'Eagerly Anticipating', 'description': 'Expressing anticipation and eagerness'},
    'firmly_trusting': {'conditions': [('trust', 0.35)], 'label': 'Firmly Trusting', 'description': 'Expressing trust and confidence'},

    # Common emotional combinations
    'bitter_sweet': {
        'conditions': [('joy', 0.2), ('sadness', 0.2)],
        'label': 'Bittersweet',
        'description': 'Mixed feelings of happiness and sadness'
    },
    'anxious_excitement': {
        'conditions': [('anticipation', 0.2), ('fear', 0.2)],
        'label': 'Anxious Excitement',
        'description': 'Mixture of excitement and nervousness'
    },
    'angry_disappointment': {
        'conditions': [('anger', 0.2), ('sadness', 0.2)],
        'label': 'Angry Disappointment',
        'description': 'Disappointment expressed through anger'
    },
    'ironic_amusement': {
        'conditions': [('sarcasm', 0.2), ('joy', 0.15)],
        'label': 'Ironic Amusement',
        'description': 'Finding humor through irony or sarcasm'
    },
    'fearful_anticipation': {
        'conditions': [('fear', 0.2), ('anticipation', 0.2)],
        'label': 'Fearful Anticipation',
        'description': 'Anxiously awaiting something'
    },
    'relieved_surprise': {
        'conditions': [('surprise', 0.2), ('joy', 0.15)],
        'label': 'Relieved Surprise',
        'description': 'Surprise with positive outcome'
    },
    'shocked_disappointment': {
        'conditions': [('surprise', 0.2), ('sadness', 0.15)],
        'label': 'Shocked Disappointment',
        'description': 'Unexpectedly negative outcome'
    },
    'disgusted_anger': {
        'conditions': [('disgust', 0.2), ('anger', 0.2)],
        'label': 'Disgusted Anger',
        'description': 'Angry response to something repulsive'
    },
    'loving_trust': {
        'conditions': [('love', 0.2), ('trust', 0.2)],
        'label': 'Loving Trust',
        'description': 'Deep affection with confidence'
    },
    'sarcastic_frustration': {
        'conditions': [('sarcasm', 0.2), ('anger', 0.15)],
        'label': 'Sarcastic Frustration',
        'description': 'Using sarcasm to express frustration'
    },
    'confused_surprise': {
        'conditions': [('surprise', 0.2), ('fear', 0.15)],
        'label': 'Confused Surprise',
        'description': 'Startled with uncertainty'
    },
    'hopeful_joy': {
        'conditions': [('joy', 0.2), ('anticipation', 0.2)],
        'label': 'Hopeful Joy',
        'description': 'Happy anticipation of something positive'
    },
    'betrayed_trust': {
        'conditions': [('sadness', 0.2), ('trust', 0.15), ('anger', 0.15)],
        'label': 'Betrayed Trust',
        'description': 'Sadness from broken trust'
    },
    'fearful_disgust': {
        'conditions': [('fear', 0.2), ('disgust', 0.2)],
        'label': 'Fearful Disgust',
        'description': 'Fear of something repulsive'
    },

    # Special cases for multiple emotions
    'emotionally_complex': {
        'conditions': ['multiple_over_15'],
        'label': 'Emotionally Complex',
        'description': 'Multiple competing emotions'
    },
    'mildly_emotional': {
        'conditions': ['all_under_20'],
        'label': 'Mildly Emotional',
        'description': 'Low intensity emotional content'
    },
    'predominantly_neutral': {
        'conditions': ['all_under_15'],
        'label': 'Predominantly Neutral',
        'description': 'No strong emotional signals detected'
    }
}

# Sarcasm patterns with refined detection logic
SARCASM_PATTERNS = [
    # Exaggerated positive with negative context
    r'(?i)\b(?:so+|really|absolutely|totally|completely)\s+(?:thrilled|excited|happy|delighted)\s+(?:about|with|by)\b.*?(?:terrible|awful|worst|bad)',

    # Classic sarcastic phrases
    r'(?i)(?:^|\W)just\s+what\s+(?:I|we)\s+(?:need|wanted|hoped for)\b',
    r'(?i)(?:^|\W)how\s+(?:wonderful|nice|great|lovely|exciting)\b.*?(?:\!|\?{2,})',

    # Thanks for nothing pattern
    r'(?i)(?:^|\W)thanks\s+for\s+(?:nothing|that|pointing|stating)\b',

    # Quotation marks around positive words (scare quotes)
    r'(?i)"(?:great|wonderful|excellent|perfect|amazing)"',

    # Typical sarcastic responses
    r'(?i)^(?:yeah|sure|right|oh)\s+(?:right|sure|okay|ok)(?:\W|$)',

    # Exaggerated praise in negative context
    r'(?i)\b(?:brilliant|genius|impressive)\b.*?(?:disaster|failure|mess)',

    # Obvious understatements
    r'(?i)\b(?:slightly|bit|little)\s+(?:catastrophic|disastrous|terrible|awful)\b',

    # Oh great patterns
    r'(?i)(?:^|\W)oh\s+(?:great|wonderful|perfect|fantastic|awesome)(?:\W|$)'
]

def tokenize_and_clean(text):
    """Tokenize text using spaCy"""
    doc = nlp(text.lower().strip())
    # Return only alphabetic tokens
    return [token.text for token in doc if token.is_alpha]

def detect_phrases(text, emotion_phrases):
    """Detect emotion-specific phrases in text"""
    text_lower = text.lower()
    detected_phrases = {}

    for emotion, phrases in emotion_phrases.items():
        found_phrases = []
        for phrase in phrases:
            if phrase.lower() in text_lower:
                found_phrases.append(phrase)

        if found_phrases:
            detected_phrases[emotion] = found_phrases

    return detected_phrases

def detect_contextual_features(text):
    """Detect contextual features in text that may influence emotion"""
    features = {
        'intensifiers': 0,
        'negators': 0,
        'hedges': 0,
        'boosters': 0,
        'exclamations': text.count('!'),
        'questions': text.count('?'),
        'ellipses': text.count('...'),
        'capitalized_words': len(re.findall(r'\b[A-Z]{2,}\b', text))
    }

    doc = nlp(text.lower())

    # Get tokens for counting
    tokens = [token.text for token in doc]

    # Count contextual indicators
    for indicator_type, words in CONTEXTUAL_INDICATORS.items():
        if indicator_type != 'punctuation':
            for word in words:
                if ' ' in word:  # Multi-word phrase
                    if word in text.lower():
                        features[indicator_type] += 1
                else:  # Single word
                    features[indicator_type] += tokens.count(word)

    return features

def detect_sarcasm_patterns(text):
    """Detect linguistic patterns of sarcasm in text with context awareness"""
    # Match sarcasm patterns
    matches = 0
    pattern_matches = []

    for pattern in SARCASM_PATTERNS:
        if re.search(pattern, text):
            matches += 1
            pattern_matches.append(pattern)

    # Get contextual features
    features = detect_contextual_features(text)

    # Check for phrases specific to sarcasm
    phrases = detect_phrases(text, {'sarcasm': EMOTION_PHRASES['sarcasm']})
    sarcasm_phrases = len(phrases.get('sarcasm', []))

    # Calculate raw score based on pattern matches and features
    raw_score = (matches * 0.15) + (sarcasm_phrases * 0.2)

    # Adjust based on contextual features
    if features['exclamations'] > 1:
        raw_score += min(features['exclamations'] * 0.05, 0.2)

    if features['capitalized_words'] > 0:
        raw_score += min(features['capitalized_words'] * 0.1, 0.3)

    # Detect positive-negative contrasts
    pos_neg_contrast = 0
    emotion_phrases = detect_phrases(text, {
        'positive': EMOTION_PHRASES['joy'] + EMOTION_PHRASES['love'],
        'negative': EMOTION_PHRASES['sadness'] + EMOTION_PHRASES['anger']
    })

    if emotion_phrases.get('positive') and emotion_phrases.get('negative'):
        pos_neg_contrast = 0.3

    # Add contrast score
    raw_score += pos_neg_contrast

    # Normalize to [0, 1]
    return min(raw_score, 1.0), pattern_matches

def calculate_emotion_similarity(text, emotion_keywords):
    """Calculate similarity between text and emotion keywords using spaCy"""
    if not text.strip():
        return 0.0

    # Process the input text
    doc = nlp(text.lower())

    # Get average similarity with emotion keywords
    keyword_scores = []

    # Use a subset of keywords for efficiency
    for keyword in emotion_keywords[:6]:  # Use first 6 keywords for each emotion
        keyword_doc = nlp(keyword)
        # Calculate maximum similarity between any token in text and the keyword
        max_similarity = 0
        for token in doc:
            if token.is_alpha and not token.is_stop:
                for keyword_token in keyword_doc:
                    similarity = token.similarity(keyword_token)
                    max_similarity = max(max_similarity, similarity)

        keyword_scores.append(max_similarity)

    # Return average of top 3 similarities if we have at least 3 scores
    if len(keyword_scores) >= 3:
        return sum(sorted(keyword_scores, reverse=True)[:3]) / 3
    # Otherwise return average of all scores
    elif keyword_scores:
        return sum(keyword_scores) / len(keyword_scores)
    else:
        return 0.0

def get_emotion_score(text, emotion, keywords):
    """Calculate emotion score based on similarity, context, and phrase detection"""
    # Get emotion score using spaCy word vectors
    similarity_score = calculate_emotion_similarity(text, keywords)

    # Check for emotion-specific phrases
    detected_phrases = detect_phrases(text, {emotion: EMOTION_PHRASES[emotion]})
    phrase_count = len(detected_phrases.get(emotion, []))
    phrase_score = min(phrase_count * 0.2, 0.6)  # Cap at 0.6

    # Get contextual features
    features = detect_contextual_features(text)

    # Calculate feature-based adjustment
    feature_adjustment = 0

    # Search for direct emotion mentions in text
    doc = nlp(text.lower())
    direct_mention_score = 0

    for token in doc:
        if token.lemma_ in keywords:
            direct_mention_score += 0.2  # Direct mention of emotion word
            break

    # Adjust score based on emotional context
    if emotion in ['joy', 'love', 'surprise'] and features['exclamations'] > 0:
        feature_adjustment += min(features['exclamations'] * 0.05, 0.2)

    if emotion in ['anger', 'sadness'] and features['negators'] > 0:
        feature_adjustment += min(features['negators'] * 0.05, 0.2)

    if emotion == 'fear' and features['intensifiers'] > 0:
        feature_adjustment += min(features['intensifiers'] * 0.05, 0.2)

    # Combine scores with appropriate weights
    final_score = (similarity_score * 0.5) + (phrase_score * 0.3) + (feature_adjustment * 0.1) + (direct_mention_score * 0.1)

    # Normalize to ensure it's in [0, 1]
    return max(0, min(final_score, 1.0)), detected_phrases.get(emotion, [])

def analyze_sarcasm(text):
    """Specialized analysis for sarcasm detection using spaCy and pattern matching"""
    # 1. Keyword similarity for sarcasm words
    sarcasm_keywords = EMOTION_CATEGORIES['sarcasm']
    similarity_score = calculate_emotion_similarity(text, sarcasm_keywords)

    # 2. Linguistic pattern detection
    pattern_score, pattern_matches = detect_sarcasm_patterns(text)

    # 3. Check for semantic incongruity between sentences
    incongruity_score = 0
    sentences = list(nlp(text).sents)

    if len(sentences) > 1:
        # Calculate similarity between adjacent sentences
        similarities = []
        for i in range(len(sentences) - 1):
            sim = sentences[i].similarity(sentences[i+1])
            similarities.append(sim)

        # Low similarity between adjacent sentences might indicate sarcasm
        if similarities and min(similarities) < 0.5:
            incongruity_score = 0.3

    # 4. Check for sarcasm phrases
    detected_phrases = detect_phrases(text, {'sarcasm': EMOTION_PHRASES['sarcasm']})
    phrase_score = min(len(detected_phrases.get('sarcasm', [])) * 0.2, 0.6)

    # 5. Check for emotional contrast
    # (positive words in negative context or vice versa)
    doc = nlp(text.lower())

    # Count positive and negative words
    pos_count = 0
    neg_count = 0

    for token in doc:
        if token.is_alpha and not token.is_stop:
            # Check against positive and negative emotion keywords
            if any(token.similarity(nlp(word)) > 0.7 for word in EMOTION_CATEGORIES['joy'][:5]):
                pos_count += 1
            if any(token.similarity(nlp(word)) > 0.7 for word in EMOTION_CATEGORIES['sadness'][:5] + EMOTION_CATEGORIES['anger'][:5]):
                neg_count += 1

    contrast_score = 0
    if pos_count > 0 and neg_count > 0:
        contrast_score = min(0.3, pos_count * neg_count * 0.05)

    # Weighted combination of all scores
    combined_score = (0.15 * similarity_score) + (0.35 * pattern_score) + \
                    (0.15 * incongruity_score) + (0.25 * phrase_score) + \
                    (0.1 * contrast_score)

    # Normalize to [0, 1]
    return max(0, min(combined_score, 1.0)), detected_phrases.get('sarcasm', []), pattern_matches

def determine_emotional_verdict(emotion_scores):
    """Determine the emotional verdict based on the emotional profile"""
    # Create a sorted list of emotions by score
    sorted_emotions = sorted(emotion_scores.items(), key=lambda x: x[1], reverse=True)

    # Count emotions over different thresholds
    emotions_over_35 = [e for e, s in sorted_emotions if s > 0.35]
    emotions_over_20 = [e for e, s in sorted_emotions if s > 0.20]
    emotions_over_15 = [e for e, s in sorted_emotions if s > 0.15]

    # Check if we have a strong dominant emotion (over 35%)
    if emotions_over_35:
        dominant_emotion = emotions_over_35[0]
        for verdict_key, verdict_info in EMOTION_VERDICT_CATEGORIES.items():
            conditions = verdict_info['conditions']
            # Check single emotion conditions
            if len(conditions) == 1 and isinstance(conditions[0], tuple):
                emotion, threshold = conditions[0]
                if emotion == dominant_emotion and emotion_scores[emotion] >= threshold:
                    return verdict_info['label'], verdict_info['description']

    # Check for emotion combinations
    for verdict_key, verdict_info in EMOTION_VERDICT_CATEGORIES.items():
        conditions = verdict_info['conditions']

        # Skip single emotion conditions we've already checked
        if len(conditions) == 1 and isinstance(conditions[0], tuple):
            continue

        # Handle special condition types
        if conditions == ['multiple_over_15'] and len(emotions_over_15) >= 3:
            return verdict_info['label'], verdict_info['description']

        if conditions == ['all_under_20'] and not emotions_over_20:
            return verdict_info['label'], verdict_info['description']

        if conditions == ['all_under_15'] and not emotions_over_15:
            return verdict_info['label'], verdict_info['description']

        # Check standard combination conditions
        if all(emotion_scores.get(emotion, 0) >= threshold for emotion, threshold in conditions):
            return verdict_info['label'], verdict_info['description']

    # If we've found nothing specific but have some emotions over 15%
    if emotions_over_15:
        if len(emotions_over_15) == 1:
            # Use the single emotion even though it's not super strong
            emotion = emotions_over_15[0]
            return f"Moderately {emotion.capitalize()}", f"Shows some signs of {emotion}"
        else:
            # Create a custom mixed emotion label
            primary = emotions_over_15[0].capitalize()
            secondary = emotions_over_15[1].capitalize()
            return f"{primary} with {secondary}", f"A mix of {primary.lower()} and {secondary.lower()} emotions"

    # Default fallback
    return "Neutral or Subtle", "No clear emotional signals detected"

def analyze_emotions(text):
    """Analyze emotions in text using spaCy with robust sarcasm detection and emotional verdict"""
    if not text or not text.strip():
        return None, {"error": "Please enter some text to analyze"}

    try:
        # Calculate scores for each emotion with supporting phrases
        emotion_data = {}

        # For each standard emotion category (excluding sarcasm)
        for emotion, keywords in EMOTION_CATEGORIES.items():
            if emotion == 'sarcasm':
                continue

            # Use specialized function to get emotion score and supporting phrases
            score, phrases = get_emotion_score(text, emotion, keywords)
            emotion_data[emotion] = {
                'score': score,
                'phrases': phrases
            }

        # Special handling for sarcasm with multi-method approach
        sarcasm_score, sarcasm_phrases, sarcasm_patterns = analyze_sarcasm(text)
        emotion_data['sarcasm'] = {
            'score': sarcasm_score,
            'phrases': sarcasm_phrases,
            'patterns': sarcasm_patterns
        }

        # Get contextual features for overall analysis
        context_features = detect_contextual_features(text)

        # Apply decision making for final analysis
        # 1. Check for dominant emotions by raw scores
        emotion_scores = {emotion: data['score'] for emotion, data in emotion_data.items()}

        # 2. Adjust based on contextual evidence
        # If we have strong phrase evidence, boost the score slightly
        for emotion, data in emotion_data.items():
            if len(data.get('phrases', [])) > 1:
                emotion_scores[emotion] = min(emotion_scores[emotion] * 1.2, 1.0)

        # 3. Get emotional verdict
        verdict_label, verdict_description = determine_emotional_verdict(emotion_scores)

        # 4. Normalize scores to percentages
        total_score = sum(emotion_scores.values()) or 1  # Avoid division by zero
        emotion_percentages = {emotion: (score / total_score) * 100 for emotion, score in emotion_scores.items()}

        # Sort emotions by percentage for display
        sorted_emotions = sorted(emotion_percentages.items(), key=lambda x: x[1], reverse=True)

        # Prepare result
        result = {
            'emotion_scores': sorted_emotions,
            'emotional_verdict': {
                'label': verdict_label,
                'description': verdict_description
            },
            'top_emotions': sorted_emotions[:3],
            'supporting_evidence': {
                emotion: data.get('phrases', []) for emotion, data in emotion_data.items() if data.get('phrases')
            },
            'context_features': context_features
        }

        return create_visualization(result), result

    except Exception as e:
        import traceback
        error_msg = traceback.format_exc()
        return None, {"error": f"Analysis error: {str(e)}", "details": error_msg}

def create_visualization(result):
    """Create visualization of emotion analysis results"""
    # Create figure and axis
    fig, ax = plt.subplots(figsize=(12, 8))

    # Extract emotion data
    emotions = [e[0] for e in result['emotion_scores']]
    scores = [e[1] for e in result['emotion_scores']]

    # Get colors
    colors = [EMOTION_COLORS.get(emotion, '#CCCCCC') for emotion in emotions]

    # Create horizontal bar chart
    bars = ax.barh(emotions, scores, color=colors)

    # Set x-axis to a static 100%
    ax.set_xlim(0, 100)
    ax.set_xticks(range(0, 101, 10))
    ax.set_xticklabels([f'{i}%' for i in range(0, 101, 10)])

    # Add title and labels
    ax.set_title('Emotion Analysis', fontsize=16, fontweight='bold')
    ax.set_ylabel('Emotions', fontsize=12)
    ax.set_xlabel('Score (%)', fontsize=12)

    # Add verdict as text annotation below the chart
    verdict = result['emotional_verdict']['label']
    description = result['emotional_verdict']['description']
    ax.text(0.5, -0.2, f"Verdict: {verdict}", ha='center', transform=ax.transAxes, fontsize=14, fontweight='bold')
    ax.text(0.5, -0.27, f"{description}", ha='center', transform=ax.transAxes, fontsize=12)

    # Add value labels on top of bars
    for bar in bars:
        width = bar.get_width()
        ax.text(width + 0.5, bar.get_y() + bar.get_height() / 2,
                f'{width:.1f}%', ha='left', va='center', fontsize=10)

    # Adjust layout
    plt.tight_layout()
    plt.subplots_adjust(bottom=0.3)  # Make room for the verdict text

    # Return figure
    return fig


def analyze_text(text):
    """Analyze text and return visualization and detailed results"""
    fig, result = analyze_emotions(text)

    if 'error' in result:
        return None, result['error']

    # Format the results for display
    verdict = result['emotional_verdict']['label']
    description = result['emotional_verdict']['description']

    # Create a formatted summary
    summary = f"## Emotional Analysis Results\n\n"
    summary += f"**Verdict:** {verdict}\n\n"
    summary += f"**Description:** {description}\n\n"

    summary += "### Top Emotions:\n"
    for emotion, score in result['top_emotions']:
        summary += f"- {emotion.capitalize()}: {score:.1f}%\n"

    if result['supporting_evidence']:
        summary += "\n### Supporting Evidence:\n"
        for emotion, phrases in result['supporting_evidence'].items():
            if phrases:
                summary += f"- **{emotion.capitalize()}**: {', '.join(phrases)}\n"

    return fig, summary

# Create Gradio interface
def create_interface():
    with gr.Blocks(title="Emotional Analysis Tool") as demo:
        gr.Markdown("# Advanced Emotion Analysis")
        gr.Markdown("Enter text to analyze the emotional content and receive a detailed breakdown.")

        with gr.Row():
            with gr.Column(scale=2):
                text_input = gr.Textbox(
                    label="Text to analyze",
                    placeholder="Enter text here...",
                    lines=10
                )
                analyze_button = gr.Button("Analyze Emotions")

            with gr.Column(scale=3):
                with gr.Tab("Visualization"):
                    plot_output = gr.Plot(label="Emotion Distribution")
                with gr.Tab("Summary"):
                    text_output = gr.Markdown(label="Analysis Summary")

        analyze_button.click(
            fn=analyze_text,
            inputs=text_input,
            outputs=[plot_output, text_output]
        )

        gr.Markdown("""
        ## About This Tool

        This advanced emotion analysis tool uses NLP techniques to detect and analyze emotions in text.
        It can identify:
        - 10 different emotions (joy, sadness, anger, fear, surprise, love, sarcasm, disgust, anticipation, trust)
        - Complex emotional combinations
        - Contextual features that affect emotional interpretation
        - Intelligent emotional verdicts for mixed emotion states

        The model uses word vectors, phrase detection, and contextual analysis for accurate emotion recognition.
        """)

    return demo

# Launch the interface if running directly
if __name__ == "__main__":
    print("Creating Gradio interface...")
    demo = create_interface()
    demo.launch(share=True)
    print("Gradio interface launched!")