entropy25 commited on
Commit
4e25610
·
verified ·
1 Parent(s): 60292a4

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +574 -0
app.py ADDED
@@ -0,0 +1,574 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import gradio as gr
3
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
4
+ import plotly.graph_objects as go
5
+ import plotly.express as px
6
+ from plotly.subplots import make_subplots
7
+ import numpy as np
8
+ from wordcloud import WordCloud
9
+ from collections import Counter, defaultdict
10
+ import re
11
+ import json
12
+ import csv
13
+ import io
14
+ import tempfile
15
+ from datetime import datetime
16
+ import logging
17
+ from functools import lru_cache
18
+ from dataclasses import dataclass
19
+ from typing import List, Dict, Optional, Tuple
20
+ import nltk
21
+ from nltk.corpus import stopwords
22
+ import langdetect
23
+ import pandas as pd
24
+
25
+ # Configuration
26
+ @dataclass
27
+ class Config:
28
+ MAX_HISTORY_SIZE: int = 500
29
+ BATCH_SIZE_LIMIT: int = 30
30
+ MAX_TEXT_LENGTH: int = 512
31
+ CACHE_SIZE: int = 64
32
+
33
+ # Supported languages and models
34
+ SUPPORTED_LANGUAGES = {
35
+ 'auto': 'Auto Detect',
36
+ 'en': 'English',
37
+ 'zh': 'Chinese',
38
+ 'es': 'Spanish',
39
+ 'fr': 'French',
40
+ 'de': 'German'
41
+ }
42
+
43
+ MODELS = {
44
+ 'en': "cardiffnlp/twitter-roberta-base-sentiment-latest",
45
+ 'multilingual': "cardiffnlp/twitter-xlm-roberta-base-sentiment"
46
+ }
47
+
48
+ # Color themes
49
+ THEMES = {
50
+ 'default': {'pos': '#4CAF50', 'neg': '#F44336', 'neu': '#FF9800'},
51
+ 'ocean': {'pos': '#0077BE', 'neg': '#FF6B35', 'neu': '#00BCD4'},
52
+ 'dark': {'pos': '#66BB6A', 'neg': '#EF5350', 'neu': '#FFA726'},
53
+ 'rainbow': {'pos': '#9C27B0', 'neg': '#E91E63', 'neu': '#FF5722'}
54
+ }
55
+
56
+ config = Config()
57
+
58
+ # Logging setup
59
+ logging.basicConfig(level=logging.INFO)
60
+ logger = logging.getLogger(__name__)
61
+
62
+ # Initialize NLTK
63
+ try:
64
+ nltk.download('stopwords', quiet=True)
65
+ nltk.download('punkt', quiet=True)
66
+ STOP_WORDS = set(stopwords.words('english'))
67
+ except:
68
+ STOP_WORDS = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'}
69
+
70
+ class ModelManager:
71
+ """Manages multiple language models"""
72
+ def __init__(self):
73
+ self.models = {}
74
+ self.tokenizers = {}
75
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
76
+ self._load_default_model()
77
+
78
+ def _load_default_model(self):
79
+ """Load the default English model"""
80
+ try:
81
+ model_name = config.MODELS['multilingual'] # Use multilingual as default
82
+ self.tokenizers['default'] = AutoTokenizer.from_pretrained(model_name)
83
+ self.models['default'] = AutoModelForSequenceClassification.from_pretrained(model_name)
84
+ self.models['default'].to(self.device)
85
+ logger.info(f"Default model loaded: {model_name}")
86
+ except Exception as e:
87
+ logger.error(f"Failed to load default model: {e}")
88
+ raise
89
+
90
+ def get_model(self, language='en'):
91
+ """Get model for specific language"""
92
+ if language in ['en', 'auto'] or language not in config.SUPPORTED_LANGUAGES:
93
+ return self.models['default'], self.tokenizers['default']
94
+ return self.models['default'], self.tokenizers['default'] # Use multilingual for all
95
+
96
+ @staticmethod
97
+ def detect_language(text: str) -> str:
98
+ """Detect text language"""
99
+ try:
100
+ detected = langdetect.detect(text)
101
+ return detected if detected in config.SUPPORTED_LANGUAGES else 'en'
102
+ except:
103
+ return 'en'
104
+
105
+ model_manager = ModelManager()
106
+
107
+ class HistoryManager:
108
+ """Manages analysis history"""
109
+ def __init__(self):
110
+ self._history = []
111
+
112
+ def add_entry(self, entry: Dict):
113
+ self._history.append(entry)
114
+ if len(self._history) > config.MAX_HISTORY_SIZE:
115
+ self._history = self._history[-config.MAX_HISTORY_SIZE:]
116
+
117
+ def get_history(self) -> List[Dict]:
118
+ return self._history.copy()
119
+
120
+ def clear(self) -> int:
121
+ count = len(self._history)
122
+ self._history.clear()
123
+ return count
124
+
125
+ def get_stats(self) -> Dict:
126
+ if not self._history:
127
+ return {}
128
+
129
+ sentiments = [item['sentiment'] for item in self._history]
130
+ confidences = [item['confidence'] for item in self._history]
131
+
132
+ return {
133
+ 'total_analyses': len(self._history),
134
+ 'positive_count': sentiments.count('Positive'),
135
+ 'negative_count': sentiments.count('Negative'),
136
+ 'avg_confidence': np.mean(confidences),
137
+ 'languages_detected': len(set(item.get('language', 'en') for item in self._history))
138
+ }
139
+
140
+ history_manager = HistoryManager()
141
+
142
+ class TextProcessor:
143
+ """Enhanced text processing"""
144
+
145
+ @staticmethod
146
+ @lru_cache(maxsize=config.CACHE_SIZE)
147
+ def clean_text(text: str, remove_punctuation: bool = True, remove_numbers: bool = False) -> str:
148
+ """Clean text with options"""
149
+ text = text.lower().strip()
150
+
151
+ if remove_numbers:
152
+ text = re.sub(r'\d+', '', text)
153
+
154
+ if remove_punctuation:
155
+ text = re.sub(r'[^\w\s]', '', text)
156
+
157
+ words = text.split()
158
+ cleaned_words = [w for w in words if w not in STOP_WORDS and len(w) > 2]
159
+ return ' '.join(cleaned_words)
160
+
161
+ @staticmethod
162
+ def extract_keywords(text: str, top_k: int = 5) -> List[str]:
163
+ """Extract key words from text"""
164
+ cleaned = TextProcessor.clean_text(text)
165
+ words = cleaned.split()
166
+ word_freq = Counter(words)
167
+ return [word for word, _ in word_freq.most_common(top_k)]
168
+
169
+ class SentimentAnalyzer:
170
+ """Enhanced sentiment analysis"""
171
+
172
+ @staticmethod
173
+ def analyze_text(text: str, language: str = 'auto', preprocessing_options: Dict = None) -> Dict:
174
+ """Analyze single text with language support"""
175
+ if not text.strip():
176
+ raise ValueError("Empty text provided")
177
+
178
+ # Detect language if auto
179
+ if language == 'auto':
180
+ detected_lang = model_manager.detect_language(text)
181
+ else:
182
+ detected_lang = language
183
+
184
+ # Get appropriate model
185
+ model, tokenizer = model_manager.get_model(detected_lang)
186
+
187
+ # Preprocessing options
188
+ options = preprocessing_options or {}
189
+ processed_text = text
190
+ if options.get('clean_text', False):
191
+ processed_text = TextProcessor.clean_text(
192
+ text,
193
+ options.get('remove_punctuation', True),
194
+ options.get('remove_numbers', False)
195
+ )
196
+
197
+ try:
198
+ # Tokenize and analyze
199
+ inputs = tokenizer(processed_text, return_tensors="pt", padding=True,
200
+ truncation=True, max_length=config.MAX_TEXT_LENGTH).to(model_manager.device)
201
+
202
+ with torch.no_grad():
203
+ outputs = model(**inputs)
204
+ probs = torch.nn.functional.softmax(outputs.logits, dim=-1).cpu().numpy()[0]
205
+
206
+ # Handle different model outputs
207
+ if len(probs) == 3: # negative, neutral, positive
208
+ sentiment_idx = np.argmax(probs)
209
+ sentiment_labels = ['Negative', 'Neutral', 'Positive']
210
+ sentiment = sentiment_labels[sentiment_idx]
211
+ confidence = float(probs[sentiment_idx])
212
+
213
+ result = {
214
+ 'sentiment': sentiment,
215
+ 'confidence': confidence,
216
+ 'neg_prob': float(probs[0]),
217
+ 'neu_prob': float(probs[1]),
218
+ 'pos_prob': float(probs[2]),
219
+ 'has_neutral': True
220
+ }
221
+ else: # negative, positive
222
+ pred = np.argmax(probs)
223
+ sentiment = "Positive" if pred == 1 else "Negative"
224
+ confidence = float(probs[pred])
225
+
226
+ result = {
227
+ 'sentiment': sentiment,
228
+ 'confidence': confidence,
229
+ 'neg_prob': float(probs[0]),
230
+ 'pos_prob': float(probs[1]),
231
+ 'neu_prob': 0.0,
232
+ 'has_neutral': False
233
+ }
234
+
235
+ # Add metadata
236
+ result.update({
237
+ 'language': detected_lang,
238
+ 'keywords': TextProcessor.extract_keywords(text),
239
+ 'word_count': len(text.split()),
240
+ 'char_count': len(text)
241
+ })
242
+
243
+ return result
244
+
245
+ except Exception as e:
246
+ logger.error(f"Analysis failed: {e}")
247
+ raise
248
+
249
+ class PlotlyVisualizer:
250
+ """Enhanced visualizations with Plotly"""
251
+
252
+ @staticmethod
253
+ def create_sentiment_gauge(result: Dict, theme: str = 'default') -> go.Figure:
254
+ """Create an animated sentiment gauge"""
255
+ colors = config.THEMES[theme]
256
+
257
+ if result['has_neutral']:
258
+ # Three-way gauge
259
+ fig = go.Figure(go.Indicator(
260
+ mode = "gauge+number+delta",
261
+ value = result['pos_prob'] * 100,
262
+ domain = {'x': [0, 1], 'y': [0, 1]},
263
+ title = {'text': f"Sentiment: {result['sentiment']}"},
264
+ delta = {'reference': 50},
265
+ gauge = {
266
+ 'axis': {'range': [None, 100]},
267
+ 'bar': {'color': colors['pos'] if result['sentiment'] == 'Positive' else colors['neg']},
268
+ 'steps': [
269
+ {'range': [0, 33], 'color': colors['neg']},
270
+ {'range': [33, 67], 'color': colors['neu']},
271
+ {'range': [67, 100], 'color': colors['pos']}
272
+ ],
273
+ 'threshold': {
274
+ 'line': {'color': "red", 'width': 4},
275
+ 'thickness': 0.75,
276
+ 'value': 90
277
+ }
278
+ }
279
+ ))
280
+ else:
281
+ # Two-way gauge
282
+ fig = go.Figure(go.Indicator(
283
+ mode = "gauge+number",
284
+ value = result['confidence'] * 100,
285
+ domain = {'x': [0, 1], 'y': [0, 1]},
286
+ title = {'text': f"Confidence: {result['sentiment']}"},
287
+ gauge = {
288
+ 'axis': {'range': [None, 100]},
289
+ 'bar': {'color': colors['pos'] if result['sentiment'] == 'Positive' else colors['neg']},
290
+ 'steps': [
291
+ {'range': [0, 50], 'color': "lightgray"},
292
+ {'range': [50, 100], 'color': "gray"}
293
+ ]
294
+ }
295
+ ))
296
+
297
+ fig.update_layout(height=400, font={'size': 16})
298
+ return fig
299
+
300
+ @staticmethod
301
+ def create_probability_bars(result: Dict, theme: str = 'default') -> go.Figure:
302
+ """Create probability bar chart"""
303
+ colors = config.THEMES[theme]
304
+
305
+ if result['has_neutral']:
306
+ labels = ['Negative', 'Neutral', 'Positive']
307
+ values = [result['neg_prob'], result['neu_prob'], result['pos_prob']]
308
+ bar_colors = [colors['neg'], colors['neu'], colors['pos']]
309
+ else:
310
+ labels = ['Negative', 'Positive']
311
+ values = [result['neg_prob'], result['pos_prob']]
312
+ bar_colors = [colors['neg'], colors['pos']]
313
+
314
+ fig = go.Figure(data=[
315
+ go.Bar(x=labels, y=values, marker_color=bar_colors, text=[f'{v:.3f}' for v in values])
316
+ ])
317
+
318
+ fig.update_traces(texttemplate='%{text}', textposition='outside')
319
+ fig.update_layout(
320
+ title="Sentiment Probabilities",
321
+ yaxis_title="Probability",
322
+ height=400,
323
+ showlegend=False
324
+ )
325
+
326
+ return fig
327
+
328
+ @staticmethod
329
+ def create_history_dashboard(history: List[Dict]) -> go.Figure:
330
+ """Create comprehensive history dashboard"""
331
+ if len(history) < 2:
332
+ return go.Figure()
333
+
334
+ # Create subplots
335
+ fig = make_subplots(
336
+ rows=2, cols=2,
337
+ subplot_titles=['Sentiment Timeline', 'Confidence Distribution',
338
+ 'Language Distribution', 'Sentiment Summary'],
339
+ specs=[[{"secondary_y": False}, {"secondary_y": False}],
340
+ [{"type": "pie"}, {"type": "bar"}]]
341
+ )
342
+
343
+ # Extract data
344
+ indices = list(range(len(history)))
345
+ pos_probs = [item['pos_prob'] for item in history]
346
+ confidences = [item['confidence'] for item in history]
347
+ sentiments = [item['sentiment'] for item in history]
348
+ languages = [item.get('language', 'en') for item in history]
349
+
350
+ # Sentiment timeline
351
+ colors = ['#4CAF50' if s == 'Positive' else '#F44336' for s in sentiments]
352
+ fig.add_trace(
353
+ go.Scatter(x=indices, y=pos_probs, mode='lines+markers',
354
+ marker=dict(color=colors, size=8),
355
+ name='Positive Probability'),
356
+ row=1, col=1
357
+ )
358
+
359
+ # Confidence distribution
360
+ fig.add_trace(
361
+ go.Histogram(x=confidences, nbinsx=10, name='Confidence'),
362
+ row=1, col=2
363
+ )
364
+
365
+ # Language distribution
366
+ lang_counts = Counter(languages)
367
+ fig.add_trace(
368
+ go.Pie(labels=list(lang_counts.keys()), values=list(lang_counts.values()),
369
+ name="Languages"),
370
+ row=2, col=1
371
+ )
372
+
373
+ # Sentiment summary
374
+ sent_counts = Counter(sentiments)
375
+ fig.add_trace(
376
+ go.Bar(x=list(sent_counts.keys()), y=list(sent_counts.values()),
377
+ marker_color=['#4CAF50' if k == 'Positive' else '#F44336' for k in sent_counts.keys()]),
378
+ row=2, col=2
379
+ )
380
+
381
+ fig.update_layout(height=800, showlegend=False)
382
+ return fig
383
+
384
+ # Main application functions
385
+ def analyze_single_text(text: str, language: str, theme: str, clean_text: bool,
386
+ remove_punct: bool, remove_nums: bool):
387
+ """Enhanced single text analysis"""
388
+ try:
389
+ if not text.strip():
390
+ return "Please enter text", None, None, "No analysis performed"
391
+
392
+ preprocessing_options = {
393
+ 'clean_text': clean_text,
394
+ 'remove_punctuation': remove_punct,
395
+ 'remove_numbers': remove_nums
396
+ }
397
+
398
+ result = SentimentAnalyzer.analyze_text(text, language, preprocessing_options)
399
+
400
+ # Add to history
401
+ history_entry = {
402
+ 'text': text[:100] + '...' if len(text) > 100 else text,
403
+ 'full_text': text,
404
+ 'sentiment': result['sentiment'],
405
+ 'confidence': result['confidence'],
406
+ 'pos_prob': result['pos_prob'],
407
+ 'neg_prob': result['neg_prob'],
408
+ 'neu_prob': result.get('neu_prob', 0),
409
+ 'language': result['language'],
410
+ 'timestamp': datetime.now().isoformat()
411
+ }
412
+ history_manager.add_entry(history_entry)
413
+
414
+ # Create visualizations
415
+ gauge_fig = PlotlyVisualizer.create_sentiment_gauge(result, theme)
416
+ bars_fig = PlotlyVisualizer.create_probability_bars(result, theme)
417
+
418
+ # Create info text
419
+ info_text = f"""
420
+ **Analysis Results:**
421
+ - **Sentiment:** {result['sentiment']} ({result['confidence']:.3f} confidence)
422
+ - **Language:** {result['language'].upper()}
423
+ - **Keywords:** {', '.join(result['keywords'])}
424
+ - **Stats:** {result['word_count']} words, {result['char_count']} characters
425
+ """
426
+
427
+ return info_text, gauge_fig, bars_fig, "Analysis completed successfully"
428
+
429
+ except Exception as e:
430
+ logger.error(f"Analysis failed: {e}")
431
+ return f"Error: {str(e)}", None, None, "Analysis failed"
432
+
433
+ def get_history_stats():
434
+ """Get history statistics"""
435
+ stats = history_manager.get_stats()
436
+ if not stats:
437
+ return "No analysis history available"
438
+
439
+ return f"""
440
+ **History Statistics:**
441
+ - Total Analyses: {stats['total_analyses']}
442
+ - Positive: {stats['positive_count']} | Negative: {stats['negative_count']}
443
+ - Average Confidence: {stats['avg_confidence']:.3f}
444
+ - Languages Detected: {stats['languages_detected']}
445
+ """
446
+
447
+ def plot_history_dashboard():
448
+ """Create history dashboard"""
449
+ history = history_manager.get_history()
450
+ if len(history) < 2:
451
+ return None, "Need at least 2 analyses for dashboard"
452
+
453
+ fig = PlotlyVisualizer.create_history_dashboard(history)
454
+ return fig, f"Dashboard showing {len(history)} analyses"
455
+
456
+ def export_history_excel():
457
+ """Export history to Excel"""
458
+ history = history_manager.get_history()
459
+ if not history:
460
+ return None, "No history to export"
461
+
462
+ try:
463
+ df = pd.DataFrame(history)
464
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.xlsx')
465
+ df.to_excel(temp_file.name, index=False)
466
+ return temp_file.name, f"Exported {len(history)} entries to Excel"
467
+ except Exception as e:
468
+ return None, f"Export failed: {str(e)}"
469
+
470
+ def clear_all_history():
471
+ """Clear analysis history"""
472
+ count = history_manager.clear()
473
+ return f"Cleared {count} entries from history"
474
+
475
+ # Sample data
476
+ SAMPLE_TEXTS = [
477
+ ["Amazing movie with incredible acting and stunning visuals!"],
478
+ ["Terrible film, waste of time and money."],
479
+ ["The movie was okay, nothing special but not bad either."],
480
+ ["¡Excelente película! Me encantó la historia."], # Spanish
481
+ ["这部电影很棒,我非常喜欢!"], # Chinese
482
+ ]
483
+
484
+ # Gradio Interface
485
+ with gr.Blocks(theme=gr.themes.Soft(), title="Advanced Sentiment Analyzer") as demo:
486
+ gr.Markdown("# 🎭 Advanced Multilingual Sentiment Analyzer")
487
+ gr.Markdown("Analyze sentiment with multiple languages, themes, and advanced visualizations")
488
+
489
+ with gr.Tab("📝 Single Analysis"):
490
+ with gr.Row():
491
+ with gr.Column(scale=2):
492
+ text_input = gr.Textbox(
493
+ label="Text to Analyze",
494
+ placeholder="Enter your text here... (supports multiple languages)",
495
+ lines=4
496
+ )
497
+
498
+ with gr.Row():
499
+ language_select = gr.Dropdown(
500
+ choices=list(config.SUPPORTED_LANGUAGES.items()),
501
+ value='auto',
502
+ label="Language"
503
+ )
504
+ theme_select = gr.Dropdown(
505
+ choices=list(config.THEMES.keys()),
506
+ value='default',
507
+ label="Theme"
508
+ )
509
+
510
+ with gr.Row():
511
+ clean_text = gr.Checkbox(label="Clean Text", value=False)
512
+ remove_punct = gr.Checkbox(label="Remove Punctuation", value=True)
513
+ remove_nums = gr.Checkbox(label="Remove Numbers", value=False)
514
+
515
+ analyze_btn = gr.Button("🔍 Analyze", variant="primary", size="lg")
516
+
517
+ gr.Examples(
518
+ examples=SAMPLE_TEXTS,
519
+ inputs=text_input,
520
+ label="Sample Texts (Multiple Languages)"
521
+ )
522
+
523
+ with gr.Column(scale=1):
524
+ result_info = gr.Markdown("Enter text and click Analyze")
525
+
526
+ with gr.Row():
527
+ gauge_plot = gr.Plot(label="Sentiment Gauge")
528
+ bars_plot = gr.Plot(label="Probability Distribution")
529
+
530
+ status_output = gr.Textbox(label="Status", interactive=False)
531
+
532
+ with gr.Tab("📊 History & Analytics"):
533
+ with gr.Row():
534
+ stats_btn = gr.Button("📈 Get Statistics")
535
+ dashboard_btn = gr.Button("📊 View Dashboard")
536
+ clear_btn = gr.Button("🗑️ Clear History", variant="stop")
537
+
538
+ with gr.Row():
539
+ export_excel_btn = gr.Button("📁 Export Excel")
540
+
541
+ stats_output = gr.Markdown("Click 'Get Statistics' to view analysis history")
542
+ dashboard_plot = gr.Plot(label="Analytics Dashboard")
543
+ excel_file = gr.File(label="Download Excel Report")
544
+ history_status = gr.Textbox(label="Status", interactive=False)
545
+
546
+ # Event handlers
547
+ analyze_btn.click(
548
+ analyze_single_text,
549
+ inputs=[text_input, language_select, theme_select, clean_text, remove_punct, remove_nums],
550
+ outputs=[result_info, gauge_plot, bars_plot, status_output]
551
+ )
552
+
553
+ stats_btn.click(
554
+ get_history_stats,
555
+ outputs=stats_output
556
+ )
557
+
558
+ dashboard_btn.click(
559
+ plot_history_dashboard,
560
+ outputs=[dashboard_plot, history_status]
561
+ )
562
+
563
+ export_excel_btn.click(
564
+ export_history_excel,
565
+ outputs=[excel_file, history_status]
566
+ )
567
+
568
+ clear_btn.click(
569
+ clear_all_history,
570
+ outputs=history_status
571
+ )
572
+
573
+ if __name__ == "__main__":
574
+ demo.launch(share=True)