entropy25 commited on
Commit
35dca1f
·
verified ·
1 Parent(s): e57599e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +258 -110
app.py CHANGED
@@ -8,6 +8,20 @@ from collections import Counter, defaultdict
8
  from sklearn.feature_extraction.text import TfidfVectorizer
9
  import networkx as nx
10
  import re
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  # Load model
13
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
@@ -15,18 +29,23 @@ tokenizer = BertTokenizer.from_pretrained("entropy25/sentimentanalysis")
15
  model = BertForSequenceClassification.from_pretrained("entropy25/sentimentanalysis")
16
  model.to(device)
17
 
18
- # Global storage
19
  history = []
20
 
 
 
 
 
 
 
21
  def clean_text(text):
22
  """Simple text preprocessing"""
23
  text = re.sub(r'[^\w\s]', '', text.lower())
24
  words = text.split()
25
- # Simple stopwords
26
  stopwords = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'have', 'has', 'had', 'will', 'would', 'could', 'should', 'this', 'that', 'these', 'those', 'i', 'you', 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her', 'us', 'them'}
27
  return [w for w in words if w not in stopwords and len(w) > 2]
28
 
29
- def analyze_text(text):
30
  """Core sentiment analysis"""
31
  if not text.strip():
32
  return "Please enter text", None, None, None
@@ -39,30 +58,33 @@ def analyze_text(text):
39
  conf = probs.max()
40
  sentiment = "Positive" if pred == 1 else "Negative"
41
 
42
- # Store in history
43
  history.append({
44
  'text': text[:100],
45
  'full_text': text,
46
  'sentiment': sentiment,
47
  'confidence': conf,
48
  'pos_prob': probs[1],
49
- 'neg_prob': probs[0]
 
50
  })
51
 
 
 
52
  result = f"Sentiment: {sentiment} (Confidence: {conf:.3f})"
53
 
54
  # Generate plots
55
- prob_plot = plot_probs(probs)
56
- gauge_plot = plot_gauge(conf, sentiment)
57
- cloud_plot = plot_wordcloud(text, sentiment)
58
 
59
  return result, prob_plot, gauge_plot, cloud_plot
60
 
61
- def plot_probs(probs):
62
  """Probability bar chart"""
63
  fig, ax = plt.subplots(figsize=(8, 5))
64
  labels = ["Negative", "Positive"]
65
- colors = ['#ff6b6b', '#4ecdc4']
66
 
67
  bars = ax.bar(labels, probs, color=colors, alpha=0.8)
68
  ax.set_title("Sentiment Probabilities", fontweight='bold')
@@ -76,7 +98,7 @@ def plot_probs(probs):
76
  plt.tight_layout()
77
  return fig
78
 
79
- def plot_gauge(conf, sentiment):
80
  """Confidence gauge"""
81
  fig, ax = plt.subplots(figsize=(8, 6))
82
 
@@ -102,28 +124,25 @@ def plot_gauge(conf, sentiment):
102
  plt.tight_layout()
103
  return fig
104
 
105
- def plot_wordcloud(text, sentiment):
106
  """Word cloud visualization"""
107
  if len(text.split()) < 3:
108
  return None
109
 
110
- try:
111
- colormap = 'Greens' if sentiment == 'Positive' else 'Reds'
112
- wc = WordCloud(width=800, height=400, background_color='white',
113
- colormap=colormap, max_words=30).generate(text)
114
-
115
- fig, ax = plt.subplots(figsize=(10, 5))
116
- ax.imshow(wc, interpolation='bilinear')
117
- ax.axis('off')
118
- ax.set_title(f'{sentiment} Word Cloud', fontweight='bold')
119
-
120
- plt.tight_layout()
121
- return fig
122
- except:
123
- return None
124
 
125
- def batch_analysis(reviews):
126
- """Analyze multiple reviews"""
127
  if not reviews.strip():
128
  return None
129
 
@@ -131,8 +150,16 @@ def batch_analysis(reviews):
131
  if len(texts) < 2:
132
  return None
133
 
 
 
 
 
134
  results = []
135
- for text in texts:
 
 
 
 
136
  inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=512).to(device)
137
  with torch.no_grad():
138
  outputs = model(**inputs)
@@ -155,9 +182,12 @@ def batch_analysis(reviews):
155
  'sentiment': sentiment,
156
  'confidence': conf,
157
  'pos_prob': probs[1],
158
- 'neg_prob': probs[0]
 
159
  })
160
 
 
 
161
  # Create visualization
162
  fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 10))
163
 
@@ -199,6 +229,59 @@ def batch_analysis(reviews):
199
  plt.tight_layout()
200
  return fig
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  def keyword_heatmap():
203
  """Keyword sentiment heatmap"""
204
  if len(history) < 3:
@@ -335,55 +418,51 @@ def tfidf_analysis():
335
  if len(pos_texts) < 2 or len(neg_texts) < 2:
336
  return None
337
 
338
- try:
339
- # Positive TF-IDF
340
- vectorizer_pos = TfidfVectorizer(max_features=50, ngram_range=(1, 2))
341
- pos_tfidf = vectorizer_pos.fit_transform(pos_texts)
342
- pos_features = vectorizer_pos.get_feature_names_out()
343
- pos_scores = pos_tfidf.sum(axis=0).A1
344
-
345
- # Negative TF-IDF
346
- vectorizer_neg = TfidfVectorizer(max_features=50, ngram_range=(1, 2))
347
- neg_tfidf = vectorizer_neg.fit_transform(neg_texts)
348
- neg_features = vectorizer_neg.get_feature_names_out()
349
- neg_scores = neg_tfidf.sum(axis=0).A1
350
-
351
- # Top 10 features
352
- pos_top_idx = np.argsort(pos_scores)[-10:][::-1]
353
- neg_top_idx = np.argsort(neg_scores)[-10:][::-1]
354
-
355
- pos_words = [pos_features[i] for i in pos_top_idx]
356
- pos_vals = [pos_scores[i] for i in pos_top_idx]
357
-
358
- neg_words = [neg_features[i] for i in neg_top_idx]
359
- neg_vals = [neg_scores[i] for i in neg_top_idx]
360
-
361
- # Plot
362
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))
363
-
364
- # Positive
365
- bars1 = ax1.barh(pos_words, pos_vals, color='#4ecdc4', alpha=0.8)
366
- ax1.set_title('Positive Keywords (TF-IDF)', fontweight='bold')
367
- ax1.set_xlabel('TF-IDF Score')
368
-
369
- for bar, score in zip(bars1, pos_vals):
370
- ax1.text(bar.get_width() + 0.001, bar.get_y() + bar.get_height()/2,
371
- f'{score:.3f}', va='center', fontsize=9)
372
-
373
- # Negative
374
- bars2 = ax2.barh(neg_words, neg_vals, color='#ff6b6b', alpha=0.8)
375
- ax2.set_title('Negative Keywords (TF-IDF)', fontweight='bold')
376
- ax2.set_xlabel('TF-IDF Score')
377
-
378
- for bar, score in zip(bars2, neg_vals):
379
- ax2.text(bar.get_width() + 0.001, bar.get_y() + bar.get_height()/2,
380
- f'{score:.3f}', va='center', fontsize=9)
381
-
382
- plt.tight_layout()
383
- return fig
384
-
385
- except:
386
- return None
387
 
388
  def plot_history():
389
  """Analysis history visualization"""
@@ -414,10 +493,30 @@ def plot_history():
414
  plt.tight_layout()
415
  return fig
416
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  # Gradio Interface
418
  with gr.Blocks(theme=gr.themes.Soft(), title="Movie Sentiment Analyzer") as demo:
419
- gr.Markdown("# 🎬 Movie Sentiment Analyzer")
420
- gr.Markdown("Advanced sentiment analysis with comprehensive visualizations")
421
 
422
  with gr.Tab("Single Analysis"):
423
  with gr.Row():
@@ -427,38 +526,56 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Movie Sentiment Analyzer") as demo
427
  placeholder="Enter your movie review here...",
428
  lines=5
429
  )
430
- analyze_btn = gr.Button("Analyze", variant="primary", size="lg")
431
 
432
- gr.Examples([
433
- ["The cinematography was stunning, but the plot felt predictable."],
434
- ["A masterpiece! Amazing performances and direction."],
435
- ["Boring movie with terrible acting and weak plot."],
436
- ["Great special effects but cheesy dialogue."],
437
- ["Incredible ending that left me speechless!"]
438
- ], inputs=text_input)
 
 
 
 
 
 
439
 
440
  with gr.Column():
441
- result_output = gr.Textbox(label="Result", lines=2)
442
 
443
  with gr.Row():
444
- prob_plot = gr.Plot(label="Probabilities")
445
  gauge_plot = gr.Plot(label="Confidence Gauge")
446
 
447
- wordcloud_plot = gr.Plot(label="Word Cloud")
448
 
449
  with gr.Tab("Batch Analysis"):
450
  gr.Markdown("### Multiple Reviews Analysis")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
 
452
- batch_input = gr.Textbox(
453
- label="Reviews (one per line)",
454
- placeholder="Review 1...\nReview 2...\nReview 3...",
455
- lines=8
456
- )
457
- batch_btn = gr.Button("Analyze Batch", variant="primary")
458
- batch_plot = gr.Plot(label="Batch Results")
459
 
460
  with gr.Tab("Advanced Analytics"):
461
  gr.Markdown("### Advanced Visualizations")
 
462
 
463
  with gr.Row():
464
  heatmap_btn = gr.Button("Keyword Heatmap", variant="primary")
@@ -467,30 +584,61 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Movie Sentiment Analyzer") as demo
467
 
468
  heatmap_plot = gr.Plot(label="Keyword Sentiment Heatmap")
469
  network_plot = gr.Plot(label="Word Co-occurrence Network")
470
- tfidf_plot = gr.Plot(label="TF-IDF Keywords")
471
-
472
- gr.Markdown("**Status:** All features implemented")
473
 
474
- with gr.Tab("History"):
475
- gr.Markdown("### Analysis History")
476
 
477
  with gr.Row():
478
- refresh_btn = gr.Button("Refresh", variant="secondary")
479
  clear_btn = gr.Button("Clear History", variant="stop")
480
 
481
- history_plot = gr.Plot(label="Historical Trends")
 
 
 
 
 
 
 
 
 
482
 
483
  # Event handlers
484
- analyze_btn.click(analyze_text, inputs=text_input,
485
- outputs=[result_output, prob_plot, gauge_plot, wordcloud_plot])
486
- batch_btn.click(batch_analysis, inputs=batch_input, outputs=batch_plot)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
 
488
  heatmap_btn.click(keyword_heatmap, outputs=heatmap_plot)
489
  network_btn.click(cooccurrence_network, outputs=network_plot)
490
  tfidf_btn.click(tfidf_analysis, outputs=tfidf_plot)
491
 
492
  refresh_btn.click(plot_history, outputs=history_plot)
493
- clear_btn.click(lambda: history.clear(), outputs=None)
 
 
 
 
 
 
 
 
 
 
494
 
495
  demo.launch(share=True)
496
 
 
8
  from sklearn.feature_extraction.text import TfidfVectorizer
9
  import networkx as nx
10
  import re
11
+ import json
12
+ import csv
13
+ import io
14
+ from datetime import datetime
15
+
16
+ # Configuration
17
+ MAX_HISTORY_SIZE = 1000
18
+ BATCH_SIZE_LIMIT = 50
19
+ THEMES = {
20
+ 'default': {'pos': '#4ecdc4', 'neg': '#ff6b6b'},
21
+ 'ocean': {'pos': '#0077be', 'neg': '#ff6b35'},
22
+ 'forest': {'pos': '#228b22', 'neg': '#dc143c'},
23
+ 'sunset': {'pos': '#ff8c00', 'neg': '#8b0000'}
24
+ }
25
 
26
  # Load model
27
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
29
  model = BertForSequenceClassification.from_pretrained("entropy25/sentimentanalysis")
30
  model.to(device)
31
 
32
+ # Global storage with size limit
33
  history = []
34
 
35
+ def manage_history_size():
36
+ """Keep history size under limit"""
37
+ global history
38
+ if len(history) > MAX_HISTORY_SIZE:
39
+ history = history[-MAX_HISTORY_SIZE:]
40
+
41
  def clean_text(text):
42
  """Simple text preprocessing"""
43
  text = re.sub(r'[^\w\s]', '', text.lower())
44
  words = text.split()
 
45
  stopwords = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'have', 'has', 'had', 'will', 'would', 'could', 'should', 'this', 'that', 'these', 'those', 'i', 'you', 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her', 'us', 'them'}
46
  return [w for w in words if w not in stopwords and len(w) > 2]
47
 
48
+ def analyze_text(text, theme='default'):
49
  """Core sentiment analysis"""
50
  if not text.strip():
51
  return "Please enter text", None, None, None
 
58
  conf = probs.max()
59
  sentiment = "Positive" if pred == 1 else "Negative"
60
 
61
+ # Store in history with timestamp
62
  history.append({
63
  'text': text[:100],
64
  'full_text': text,
65
  'sentiment': sentiment,
66
  'confidence': conf,
67
  'pos_prob': probs[1],
68
+ 'neg_prob': probs[0],
69
+ 'timestamp': datetime.now().isoformat()
70
  })
71
 
72
+ manage_history_size()
73
+
74
  result = f"Sentiment: {sentiment} (Confidence: {conf:.3f})"
75
 
76
  # Generate plots
77
+ prob_plot = plot_probs(probs, theme)
78
+ gauge_plot = plot_gauge(conf, sentiment, theme)
79
+ cloud_plot = plot_wordcloud(text, sentiment, theme)
80
 
81
  return result, prob_plot, gauge_plot, cloud_plot
82
 
83
+ def plot_probs(probs, theme='default'):
84
  """Probability bar chart"""
85
  fig, ax = plt.subplots(figsize=(8, 5))
86
  labels = ["Negative", "Positive"]
87
+ colors = [THEMES[theme]['neg'], THEMES[theme]['pos']]
88
 
89
  bars = ax.bar(labels, probs, color=colors, alpha=0.8)
90
  ax.set_title("Sentiment Probabilities", fontweight='bold')
 
98
  plt.tight_layout()
99
  return fig
100
 
101
+ def plot_gauge(conf, sentiment, theme='default'):
102
  """Confidence gauge"""
103
  fig, ax = plt.subplots(figsize=(8, 6))
104
 
 
124
  plt.tight_layout()
125
  return fig
126
 
127
+ def plot_wordcloud(text, sentiment, theme='default'):
128
  """Word cloud visualization"""
129
  if len(text.split()) < 3:
130
  return None
131
 
132
+ colormap = 'Greens' if sentiment == 'Positive' else 'Reds'
133
+ wc = WordCloud(width=800, height=400, background_color='white',
134
+ colormap=colormap, max_words=30).generate(text)
135
+
136
+ fig, ax = plt.subplots(figsize=(10, 5))
137
+ ax.imshow(wc, interpolation='bilinear')
138
+ ax.axis('off')
139
+ ax.set_title(f'{sentiment} Word Cloud', fontweight='bold')
140
+
141
+ plt.tight_layout()
142
+ return fig
 
 
 
143
 
144
+ def batch_analysis(reviews, progress=gr.Progress()):
145
+ """Analyze multiple reviews with progress tracking"""
146
  if not reviews.strip():
147
  return None
148
 
 
150
  if len(texts) < 2:
151
  return None
152
 
153
+ # Apply batch size limit
154
+ if len(texts) > BATCH_SIZE_LIMIT:
155
+ texts = texts[:BATCH_SIZE_LIMIT]
156
+
157
  results = []
158
+
159
+ for i, text in enumerate(texts):
160
+ progress((i + 1) / len(texts), f"Processing review {i + 1}/{len(texts)}")
161
+
162
+ # Process in smaller GPU batches
163
  inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=512).to(device)
164
  with torch.no_grad():
165
  outputs = model(**inputs)
 
182
  'sentiment': sentiment,
183
  'confidence': conf,
184
  'pos_prob': probs[1],
185
+ 'neg_prob': probs[0],
186
+ 'timestamp': datetime.now().isoformat()
187
  })
188
 
189
+ manage_history_size()
190
+
191
  # Create visualization
192
  fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 10))
193
 
 
229
  plt.tight_layout()
230
  return fig
231
 
232
+ def process_uploaded_file(file):
233
+ """Process uploaded CSV/TXT file for batch analysis"""
234
+ if file is None:
235
+ return ""
236
+
237
+ content = file.read().decode('utf-8')
238
+
239
+ # Handle CSV format
240
+ if file.name.endswith('.csv'):
241
+ lines = content.split('\n')
242
+ # Assume text is in first column or look for 'review' column
243
+ if ',' in content:
244
+ reviews = []
245
+ reader = csv.reader(lines)
246
+ headers = next(reader, None)
247
+ if headers and any('review' in h.lower() for h in headers):
248
+ review_idx = next(i for i, h in enumerate(headers) if 'review' in h.lower())
249
+ for row in reader:
250
+ if len(row) > review_idx:
251
+ reviews.append(row[review_idx])
252
+ else:
253
+ for row in reader:
254
+ if row:
255
+ reviews.append(row[0])
256
+ return '\n'.join(reviews)
257
+
258
+ # Handle plain text
259
+ return content
260
+
261
+ def export_history_csv():
262
+ """Export history to CSV"""
263
+ if not history:
264
+ return None
265
+
266
+ output = io.StringIO()
267
+ writer = csv.writer(output)
268
+ writer.writerow(['Timestamp', 'Text', 'Sentiment', 'Confidence', 'Positive_Prob', 'Negative_Prob'])
269
+
270
+ for entry in history:
271
+ writer.writerow([
272
+ entry['timestamp'], entry['text'], entry['sentiment'],
273
+ entry['confidence'], entry['pos_prob'], entry['neg_prob']
274
+ ])
275
+
276
+ return output.getvalue()
277
+
278
+ def export_history_json():
279
+ """Export history to JSON"""
280
+ if not history:
281
+ return None
282
+
283
+ return json.dumps(history, indent=2)
284
+
285
  def keyword_heatmap():
286
  """Keyword sentiment heatmap"""
287
  if len(history) < 3:
 
418
  if len(pos_texts) < 2 or len(neg_texts) < 2:
419
  return None
420
 
421
+ # Positive TF-IDF
422
+ vectorizer_pos = TfidfVectorizer(max_features=50, ngram_range=(1, 2))
423
+ pos_tfidf = vectorizer_pos.fit_transform(pos_texts)
424
+ pos_features = vectorizer_pos.get_feature_names_out()
425
+ pos_scores = pos_tfidf.sum(axis=0).A1
426
+
427
+ # Negative TF-IDF
428
+ vectorizer_neg = TfidfVectorizer(max_features=50, ngram_range=(1, 2))
429
+ neg_tfidf = vectorizer_neg.fit_transform(neg_texts)
430
+ neg_features = vectorizer_neg.get_feature_names_out()
431
+ neg_scores = neg_tfidf.sum(axis=0).A1
432
+
433
+ # Top 10 features
434
+ pos_top_idx = np.argsort(pos_scores)[-10:][::-1]
435
+ neg_top_idx = np.argsort(neg_scores)[-10:][::-1]
436
+
437
+ pos_words = [pos_features[i] for i in pos_top_idx]
438
+ pos_vals = [pos_scores[i] for i in pos_top_idx]
439
+
440
+ neg_words = [neg_features[i] for i in neg_top_idx]
441
+ neg_vals = [neg_scores[i] for i in neg_top_idx]
442
+
443
+ # Plot
444
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))
445
+
446
+ # Positive
447
+ bars1 = ax1.barh(pos_words, pos_vals, color='#4ecdc4', alpha=0.8)
448
+ ax1.set_title('Positive Keywords (TF-IDF)', fontweight='bold')
449
+ ax1.set_xlabel('TF-IDF Score')
450
+
451
+ for bar, score in zip(bars1, pos_vals):
452
+ ax1.text(bar.get_width() + 0.001, bar.get_y() + bar.get_height()/2,
453
+ f'{score:.3f}', va='center', fontsize=9)
454
+
455
+ # Negative
456
+ bars2 = ax2.barh(neg_words, neg_vals, color='#ff6b6b', alpha=0.8)
457
+ ax2.set_title('Negative Keywords (TF-IDF)', fontweight='bold')
458
+ ax2.set_xlabel('TF-IDF Score')
459
+
460
+ for bar, score in zip(bars2, neg_vals):
461
+ ax2.text(bar.get_width() + 0.001, bar.get_y() + bar.get_height()/2,
462
+ f'{score:.3f}', va='center', fontsize=9)
463
+
464
+ plt.tight_layout()
465
+ return fig
 
 
 
 
466
 
467
  def plot_history():
468
  """Analysis history visualization"""
 
493
  plt.tight_layout()
494
  return fig
495
 
496
+ def clear_history():
497
+ """Clear analysis history"""
498
+ global history
499
+ history.clear()
500
+ return "History cleared successfully"
501
+
502
+ # Enhanced example data
503
+ EXAMPLE_REVIEWS = [
504
+ ["The cinematography was stunning, but the plot felt predictable and the dialogue was weak."],
505
+ ["A masterpiece of filmmaking! Amazing performances, brilliant direction, and unforgettable moments."],
506
+ ["Boring movie with terrible acting, weak plot, and poor character development throughout."],
507
+ ["Great special effects and action sequences, but the story was confusing and hard to follow."],
508
+ ["Incredible ending that left me speechless! One of the best films I've ever seen."],
509
+ ["The movie started strong but became repetitive and lost my interest halfway through."],
510
+ ["Outstanding soundtrack and beautiful visuals, though the pacing was somewhat slow."],
511
+ ["Disappointing sequel that failed to capture the magic of the original film."],
512
+ ["Brilliant writing and exceptional acting make this a must-watch drama."],
513
+ ["Generic blockbuster with predictable twists and forgettable characters."]
514
+ ]
515
+
516
  # Gradio Interface
517
  with gr.Blocks(theme=gr.themes.Soft(), title="Movie Sentiment Analyzer") as demo:
518
+ gr.Markdown("# 🎬 Enhanced Movie Sentiment Analyzer")
519
+ gr.Markdown("Advanced sentiment analysis with comprehensive visualizations and data export capabilities")
520
 
521
  with gr.Tab("Single Analysis"):
522
  with gr.Row():
 
526
  placeholder="Enter your movie review here...",
527
  lines=5
528
  )
 
529
 
530
+ with gr.Row():
531
+ analyze_btn = gr.Button("Analyze", variant="primary", size="lg")
532
+ theme_selector = gr.Dropdown(
533
+ choices=list(THEMES.keys()),
534
+ value="default",
535
+ label="Color Theme"
536
+ )
537
+
538
+ gr.Examples(
539
+ examples=EXAMPLE_REVIEWS,
540
+ inputs=text_input,
541
+ label="Example Reviews"
542
+ )
543
 
544
  with gr.Column():
545
+ result_output = gr.Textbox(label="Analysis Result", lines=2)
546
 
547
  with gr.Row():
548
+ prob_plot = gr.Plot(label="Sentiment Probabilities")
549
  gauge_plot = gr.Plot(label="Confidence Gauge")
550
 
551
+ wordcloud_plot = gr.Plot(label="Word Cloud Visualization")
552
 
553
  with gr.Tab("Batch Analysis"):
554
  gr.Markdown("### Multiple Reviews Analysis")
555
+ gr.Markdown(f"**Note:** Limited to {BATCH_SIZE_LIMIT} reviews per batch for optimal performance")
556
+
557
+ with gr.Row():
558
+ with gr.Column():
559
+ file_upload = gr.File(
560
+ label="Upload CSV/TXT File",
561
+ file_types=[".csv", ".txt"],
562
+ type="binary"
563
+ )
564
+ batch_input = gr.Textbox(
565
+ label="Reviews (one per line)",
566
+ placeholder="Review 1...\nReview 2...\nReview 3...",
567
+ lines=8
568
+ )
569
+
570
+ with gr.Column():
571
+ load_file_btn = gr.Button("Load File", variant="secondary")
572
+ batch_btn = gr.Button("Analyze Batch", variant="primary")
573
 
574
+ batch_plot = gr.Plot(label="Batch Analysis Results")
 
 
 
 
 
 
575
 
576
  with gr.Tab("Advanced Analytics"):
577
  gr.Markdown("### Advanced Visualizations")
578
+ gr.Markdown("**Requirements:** Minimum analysis history needed for each visualization")
579
 
580
  with gr.Row():
581
  heatmap_btn = gr.Button("Keyword Heatmap", variant="primary")
 
584
 
585
  heatmap_plot = gr.Plot(label="Keyword Sentiment Heatmap")
586
  network_plot = gr.Plot(label="Word Co-occurrence Network")
587
+ tfidf_plot = gr.Plot(label="TF-IDF Keywords Comparison")
 
 
588
 
589
+ with gr.Tab("History & Export"):
590
+ gr.Markdown("### Analysis History & Data Export")
591
 
592
  with gr.Row():
593
+ refresh_btn = gr.Button("Refresh History", variant="secondary")
594
  clear_btn = gr.Button("Clear History", variant="stop")
595
 
596
+ with gr.Row():
597
+ export_csv_btn = gr.Button("Export CSV", variant="secondary")
598
+ export_json_btn = gr.Button("Export JSON", variant="secondary")
599
+
600
+ with gr.Row():
601
+ csv_download = gr.File(label="CSV Download", visible=False)
602
+ json_download = gr.File(label="JSON Download", visible=False)
603
+
604
+ history_status = gr.Textbox(label="Status", interactive=False)
605
+ history_plot = gr.Plot(label="Historical Analysis Trends")
606
 
607
  # Event handlers
608
+ analyze_btn.click(
609
+ analyze_text,
610
+ inputs=[text_input, theme_selector],
611
+ outputs=[result_output, prob_plot, gauge_plot, wordcloud_plot]
612
+ )
613
+
614
+ load_file_btn.click(
615
+ process_uploaded_file,
616
+ inputs=file_upload,
617
+ outputs=batch_input
618
+ )
619
+
620
+ batch_btn.click(
621
+ batch_analysis,
622
+ inputs=batch_input,
623
+ outputs=batch_plot
624
+ )
625
 
626
  heatmap_btn.click(keyword_heatmap, outputs=heatmap_plot)
627
  network_btn.click(cooccurrence_network, outputs=network_plot)
628
  tfidf_btn.click(tfidf_analysis, outputs=tfidf_plot)
629
 
630
  refresh_btn.click(plot_history, outputs=history_plot)
631
+ clear_btn.click(clear_history, outputs=history_status)
632
+
633
+ export_csv_btn.click(
634
+ export_history_csv,
635
+ outputs=gr.File(label="history.csv")
636
+ )
637
+
638
+ export_json_btn.click(
639
+ export_history_json,
640
+ outputs=gr.File(label="history.json")
641
+ )
642
 
643
  demo.launch(share=True)
644