RohitManglik commited on
Commit
d71c05d
·
verified ·
1 Parent(s): 815e70f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +534 -285
app.py CHANGED
@@ -1,4 +1,3 @@
1
-
2
  import os
3
  from huggingface_hub import login
4
 
@@ -10,41 +9,29 @@ import torch.nn.functional as F
10
  from transformers import DistilBertTokenizer, DistilBertForSequenceClassification
11
  import whisper
12
  import pickle
 
13
 
14
- # 📁 Load model
15
  model_path = "InfoBayAI/Audio-to-Sentiment_Intelligence_Model"
16
 
17
  tokenizer = DistilBertTokenizer.from_pretrained(
18
  model_path,
19
  token=os.getenv("HF_TOKEN")
20
  )
21
-
22
  model = DistilBertForSequenceClassification.from_pretrained(
23
  model_path,
24
  token=os.getenv("HF_TOKEN")
25
  )
26
 
27
- from huggingface_hub import hf_hub_download
28
-
29
  label_path = hf_hub_download(
30
  repo_id=model_path,
31
  filename="label_encoder.pkl",
32
  token=os.getenv("HF_TOKEN")
33
  )
34
 
35
- tokenizer = DistilBertTokenizer.from_pretrained(model_path)
36
- model = DistilBertForSequenceClassification.from_pretrained(model_path)
37
-
38
- from huggingface_hub import hf_hub_download
39
-
40
- label_path = hf_hub_download(
41
- repo_id="InfoBayAI/Audio-to-Sentiment_Intelligence_Model",
42
- filename="label_encoder.pkl",
43
- token=os.getenv("HF_TOKEN")
44
- )
45
-
46
  with open(label_path, "rb") as f:
47
  encoder = pickle.load(f)
 
48
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
49
  model.to(device)
50
  model.eval()
@@ -53,7 +40,7 @@ model.eval()
53
  whisper_model = whisper.load_model("base")
54
 
55
 
56
- # 🧠 Sentiment prediction
57
  def predict_sentiment(text):
58
  inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True, max_length=128)
59
  inputs = {k: v.to(device) for k, v in inputs.items()}
@@ -66,301 +53,568 @@ def predict_sentiment(text):
66
 
67
  label = encoder.inverse_transform([predicted_class.item()])[0]
68
  confidence = confidence.item() * 100
69
-
70
  return label, confidence
71
 
72
 
73
- # 🔁 Pipeline
74
  def process_audio(audio_file):
75
  if audio_file is None:
76
- return "⚠️ No audio provided. Please upload or record audio first."
77
-
78
- result = whisper_model.transcribe(audio_file, task="translate")
79
- text = result["text"]
80
-
81
- label, confidence = predict_sentiment(text)
82
-
83
- return f"""🎯 Transcription:
84
- {text}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
- 📊 Sentiment: {label}
87
- 🔍 Confidence: {confidence:.2f}%"""
88
 
89
 
90
- # 🎨 UI
91
  APP_CSS = """
92
- /* ── Reset & Base ── */
93
- * { box-sizing: border-box; }
94
-
95
- body, .gradio-container {
96
- background-color: #0b0b0f !important;
97
- font-family: 'Segoe UI', sans-serif;
98
- }
99
-
100
- .gradio-container {
101
- max-width: 1200px !important;
102
- margin: 0 auto !important;
103
- padding: 30px 20px !important;
104
- }
105
-
106
- /* ── Header ── */
107
- h1 {
108
- text-align: center;
109
- color: #ffffff !important;
110
- font-size: 36px !important;
111
- font-weight: 800 !important;
112
- letter-spacing: -0.5px;
113
- margin-bottom: 6px !important;
114
- }
115
-
116
- .subtitle {
117
- text-align: center;
118
- color: #9ca3af;
119
- font-size: 14px;
120
- margin-bottom: 30px;
121
- }
122
-
123
- /* ── Panel headings ── */
124
- h2 {
125
- color: #e2e8f0 !important;
126
- font-size: 18px !important;
127
- font-weight: 600 !important;
128
- margin-bottom: 14px !important;
129
- padding-bottom: 8px !important;
130
- border-bottom: 1px solid #2e2e3a !important;
131
- }
132
-
133
- /* ── Cards / Columns ── */
134
- .input-col, .output-col {
135
- background: #13131f;
136
- border: 1px solid #2a2a38;
137
- border-radius: 18px;
138
- padding: 24px !important;
139
- }
140
-
141
- /* ── Audio widget: give it room so all controls show ── */
142
- .audio-wrap {
143
- background: #1a1a2e;
144
- border: 1px solid #2e2e3a;
145
- border-radius: 14px;
146
- padding: 14px;
147
- margin-bottom: 10px;
148
- min-height: 130px; /* ensures toolbar never gets clipped */
149
- overflow: visible !important;
150
- }
151
-
152
- /* Gradio wraps each audio component in wrappers — make sure nothing clips */
153
- .audio-wrap > div,
154
- .audio-wrap > div > div {
155
- overflow: visible !important;
156
- }
157
-
158
- /* The inner Gradio audio player toolbar row */
159
- .waveform-controls,
160
- [data-testid="waveform-controls"],
161
- .waveform-controls-wrapper {
162
- overflow: visible !important;
163
- flex-wrap: wrap !important;
164
- gap: 6px !important;
165
- }
166
-
167
- /* Volume, speed, trim, download, share, delete — all should be visible */
168
- .waveform-controls button,
169
- .audio-controls button,
170
- [class*="icon-button"],
171
- [class*="control-button"] {
172
- background: #2a2a42 !important;
173
- color: #c4b5fd !important;
174
- border: 1px solid #3f3f5a !important;
175
- border-radius: 8px !important;
176
- width: 34px !important;
177
- height: 34px !important;
178
- min-width: 34px !important;
179
- display: flex !important;
180
- align-items: center !important;
181
- justify-content: center !important;
182
- font-size: 14px !important;
183
- padding: 0 !important;
184
- }
185
-
186
- .waveform-controls button:hover,
187
- .audio-controls button:hover {
188
- background: #3b3b5e !important;
189
- border-color: #7c3aed !important;
190
- }
191
-
192
- /* Playback progress bar */
193
- .waveform-container {
194
- border-radius: 8px;
195
- overflow: hidden;
196
- }
197
-
198
- /* ── Hint text ── */
199
- .hint {
200
- color: #6b7280;
201
- font-size: 12px;
202
- margin-top: 4px;
203
- margin-bottom: 14px;
204
- }
205
-
206
- /* ── Section divider ── */
207
- .divider {
208
- border: none;
209
- border-top: 1px solid #2a2a38;
210
- margin: 20px 0;
211
- }
212
-
213
- /* ── Buttons ── */
214
- button.lg,
215
- button[class*="primary"],
216
- .svelte-cmf5ev {
217
- background: linear-gradient(135deg, #5b21b6, #7c3aed) !important;
218
- color: #ffffff !important;
219
- border: none !important;
220
- border-radius: 12px !important;
221
- font-size: 15px !important;
222
- font-weight: 600 !important;
223
- padding: 12px 20px !important;
224
- width: 100% !important;
225
- cursor: pointer;
226
- transition: opacity 0.2s, transform 0.1s;
227
- margin-bottom: 6px !important;
228
- }
229
-
230
- button.lg:hover,
231
- button[class*="primary"]:hover {
232
- opacity: 0.88 !important;
233
- transform: translateY(-1px);
234
- }
235
-
236
- button.lg:active {
237
- transform: translateY(0px);
238
- }
239
-
240
- /* Clear button — secondary style */
241
- .clear-btn button {
242
- background: #1f1f30 !important;
243
- border: 1px solid #3a3a52 !important;
244
- color: #9ca3af !important;
245
- }
246
-
247
- .clear-btn button:hover {
248
- background: #2a2a40 !important;
249
- color: #e2e8f0 !important;
250
- }
251
-
252
- /* Flag button */
253
- .flag-btn button {
254
- background: linear-gradient(135deg, #7f1d1d, #b91c1c) !important;
255
- margin-top: 10px !important;
256
- }
257
-
258
- /* ── Output textbox ── */
259
- textarea,
260
- .output-col textarea {
261
- background-color: #0f0f1a !important;
262
- color: #e2e8f0 !important;
263
- border: 1px solid #2e2e3a !important;
264
- border-radius: 12px !important;
265
- font-size: 14px !important;
266
- line-height: 1.7 !important;
267
- min-height: 340px !important;
268
- padding: 16px !important;
269
- resize: vertical;
270
- }
271
-
272
- textarea::placeholder {
273
- color: #4b5563 !important;
274
- }
275
-
276
- /* ── Hide Gradio footer branding ── */
277
- footer,
278
- .built-with,
279
- [class*="built-with"],
280
- .footer,
281
- .svelte-1ax1toq,
282
- .gradio-container ~ footer {
283
- display: none !important;
284
- visibility: hidden !important;
285
- height: 0 !important;
286
- overflow: hidden !important;
287
- }
288
-
289
- /* ── Labels ── */
290
- label span,
291
- .label-wrap span {
292
- color: #9ca3af !important;
293
- font-size: 13px !important;
294
- }
295
-
296
- /* ── Recording indicator pulse ── */
297
- @keyframes pulse-red {
298
- 0%, 100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.4); }
299
- 50% { box-shadow: 0 0 0 8px rgba(239,68,68,0); }
300
- }
301
-
302
- /* Record button active state */
303
- [data-testid="record-button"][aria-pressed="true"],
304
- .recording button {
305
- animation: pulse-red 1.2s infinite !important;
306
- background: #dc2626 !important;
307
- border-color: #ef4444 !important;
308
- }
309
- """
310
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  def create_interface():
312
- with gr.Blocks() as interface:
313
 
314
- gr.Markdown("# 🎧 Audio Sentiment Analyzer")
315
- gr.HTML("<div class='subtitle'>DistilBERT + Whisper AI pipeline &nbsp;·&nbsp; Upload or Record Audio</div>")
 
 
 
 
 
316
 
317
- with gr.Row(equal_height=False):
318
 
319
- # ── LEFT: Input Panel ──────────────────────────────
320
- with gr.Column(scale=1, elem_classes="input-col"):
321
  gr.Markdown("## 🎤 Input Panel")
322
 
323
- # Upload section
324
- gr.HTML("<div class='hint'>📁 <b style='color:#c4b5fd'>Upload</b> an audio file (MP3, WAV, M4A…)</div>")
325
  upload_audio = gr.Audio(
326
  sources=["upload"],
327
  type="filepath",
328
  label="Upload Audio File",
329
- elem_classes="audio-wrap",
 
 
 
 
 
 
 
330
  )
331
- upload_btn = gr.Button("🚀 Analyze Uploaded Audio", variant="primary")
332
 
333
- gr.HTML("<hr class='divider'>")
334
 
335
- # Record section
336
- gr.HTML("<div class='hint'>🎙️ <b style='color:#c4b5fd'>Record</b> live using your microphone</div>")
337
  record_audio = gr.Audio(
338
  sources=["microphone"],
339
  type="filepath",
340
- label="Record Audio",
341
- elem_classes="audio-wrap",
 
 
 
 
 
 
342
  )
343
- record_btn = gr.Button("🎙️ Analyze Recorded Audio", variant="primary")
344
 
345
- gr.HTML("<hr class='divider'>")
346
 
347
- with gr.Row(elem_classes="clear-btn"):
348
- clear_btn = gr.Button("🧹 Clear All")
349
 
350
- # ── RIGHT: Output Panel ────────────────────────────
351
- with gr.Column(scale=2, elem_classes="output-col"):
352
  gr.Markdown("## 📊 Result Panel")
353
 
354
  output_text = gr.Textbox(
355
- lines=18,
356
  label="Analysis Result",
357
- placeholder="Results will appear here after analysis...\n\n🎯 Transcription\n📊 Sentiment\n🔍 Confidence",
 
 
 
 
 
 
 
 
358
  )
359
 
360
- with gr.Row(elem_classes="flag-btn"):
361
- clear_result_btn = gr.Button("🧹 Clear Result")
362
 
363
- # ── Event Handlers ──
364
  upload_btn.click(fn=process_audio, inputs=upload_audio, outputs=output_text)
365
  record_btn.click(fn=process_audio, inputs=record_audio, outputs=output_text)
366
 
@@ -369,21 +623,16 @@ def create_interface():
369
  inputs=[],
370
  outputs=[upload_audio, record_audio, output_text]
371
  )
372
-
373
  clear_result_btn.click(
374
- fn=lambda: "🧹 Result is cleared.",
375
  inputs=[],
376
  outputs=output_text
377
  )
378
-
379
  return interface
380
 
381
 
382
- # 🚀 Launch
383
  if __name__ == "__main__":
384
  interface = create_interface()
385
- interface.launch(
386
- share=True,
387
- css=APP_CSS,
388
- theme=gr.themes.Base(),
389
- )
 
 
1
  import os
2
  from huggingface_hub import login
3
 
 
9
  from transformers import DistilBertTokenizer, DistilBertForSequenceClassification
10
  import whisper
11
  import pickle
12
+ from huggingface_hub import hf_hub_download
13
 
14
+ # ── Model Loading ──────────────────────────────────────────
15
  model_path = "InfoBayAI/Audio-to-Sentiment_Intelligence_Model"
16
 
17
  tokenizer = DistilBertTokenizer.from_pretrained(
18
  model_path,
19
  token=os.getenv("HF_TOKEN")
20
  )
 
21
  model = DistilBertForSequenceClassification.from_pretrained(
22
  model_path,
23
  token=os.getenv("HF_TOKEN")
24
  )
25
 
 
 
26
  label_path = hf_hub_download(
27
  repo_id=model_path,
28
  filename="label_encoder.pkl",
29
  token=os.getenv("HF_TOKEN")
30
  )
31
 
 
 
 
 
 
 
 
 
 
 
 
32
  with open(label_path, "rb") as f:
33
  encoder = pickle.load(f)
34
+
35
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
36
  model.to(device)
37
  model.eval()
 
40
  whisper_model = whisper.load_model("base")
41
 
42
 
43
+ # ── Sentiment Prediction ───────────────────────────────────
44
  def predict_sentiment(text):
45
  inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True, max_length=128)
46
  inputs = {k: v.to(device) for k, v in inputs.items()}
 
53
 
54
  label = encoder.inverse_transform([predicted_class.item()])[0]
55
  confidence = confidence.item() * 100
 
56
  return label, confidence
57
 
58
 
59
+ # ── Main Pipeline ──────────────────────────────────────────
60
  def process_audio(audio_file):
61
  if audio_file is None:
62
+ return "⚠️ No audio provided.\n\nPlease upload a file or record audio using your microphone, then click Analyze."
63
+
64
+ try:
65
+ # Transcribe full audio — do NOT pass trim timestamps
66
+ result = whisper_model.transcribe(audio_file, task="translate")
67
+ text = result["text"].strip()
68
+
69
+ if not text:
70
+ return "⚠️ Could not transcribe any speech from the audio.\n\nPlease ensure the audio contains clear speech."
71
+
72
+ label, confidence = predict_sentiment(text)
73
+
74
+ # Confidence bar (ASCII)
75
+ filled = int(confidence / 5)
76
+ bar = "█" * filled + "░" * (20 - filled)
77
+
78
+ sentiment_emoji = {
79
+ "positive": "😊", "negative": "😞", "neutral": "😐",
80
+ "happy": "😄", "sad": "😢", "angry": "😠", "fear": "😨",
81
+ "surprise": "😲", "disgust": "🤢"
82
+ }.get(label.lower(), "🎯")
83
+
84
+ return (
85
+ f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
86
+ f" 🎯 TRANSCRIPTION\n"
87
+ f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
88
+ f"{text}\n\n"
89
+ f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
90
+ f" {sentiment_emoji} SENTIMENT ANALYSIS\n"
91
+ f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
92
+ f" Sentiment : {label.upper()}\n"
93
+ f" Confidence : {confidence:.1f}%\n\n"
94
+ f" [{bar}]\n"
95
+ )
96
 
97
+ except Exception as e:
98
+ return f"❌ Error during processing:\n\n{str(e)}\n\nPlease try again with a different audio file."
99
 
100
 
101
+ # ── CSS ────────────────────────────────────────────────────
102
  APP_CSS = """
103
+ /* ━━━━ GOOGLE FONTS ━━━━ */
104
+ @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Syne:wght@400;600;700;800&display=swap');
105
+
106
+ /* ━━━━ RESET & ROOT ━━━━ */
107
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
108
+
109
+ :root {
110
+ --bg-base: #080810;
111
+ --bg-surface: #0f0f1c;
112
+ --bg-card: #13132a;
113
+ --bg-element: #1a1a35;
114
+ --border: #252540;
115
+ --border-glow: #4f3fbf;
116
+ --accent: #7c5cfc;
117
+ --accent-light: #a78bfa;
118
+ --accent-dim: #3d2d8a;
119
+ --text-primary: #e8e4ff;
120
+ --text-muted: #7870a8;
121
+ --text-dim: #3d3a6e;
122
+ --success: #34d399;
123
+ --danger: #f87171;
124
+ --font-display: 'Syne', sans-serif;
125
+ --font-mono: 'Space Mono', monospace;
126
+ --radius-sm: 8px;
127
+ --radius-md: 14px;
128
+ --radius-lg: 20px;
129
+ --shadow-glow: 0 0 30px rgba(124, 92, 252, 0.15);
130
+ }
131
+
132
+ /* ━━━━ BODY & CONTAINER ━━━━ */
133
+ body,
134
+ .gradio-container,
135
+ .gradio-container > * {
136
+ background-color: var(--bg-base) !important;
137
+ font-family: var(--font-display) !important;
138
+ color: var(--text-primary) !important;
139
+ }
140
+
141
+ .gradio-container {
142
+ max-width: 1400px !important;
143
+ width: 100% !important;
144
+ min-height: 100vh !important;
145
+ margin: 0 auto !important;
146
+ padding: 32px 24px 60px !important;
147
+ }
148
+
149
+ /* ━━━━ HEADER ━━━━ */
150
+ .app-header {
151
+ text-align: center;
152
+ padding: 40px 0 32px;
153
+ }
154
+
155
+ .app-header h1 {
156
+ font-family: var(--font-display) !important;
157
+ font-size: clamp(28px, 4vw, 48px) !important;
158
+ font-weight: 800 !important;
159
+ color: var(--text-primary) !important;
160
+ letter-spacing: -1px !important;
161
+ line-height: 1.1 !important;
162
+ margin-bottom: 12px !important;
163
+ }
164
+
165
+ .app-header h1 span { color: var(--accent-light); }
166
+
167
+ .app-subtitle {
168
+ color: var(--text-muted);
169
+ font-family: var(--font-mono);
170
+ font-size: 13px;
171
+ letter-spacing: 0.5px;
172
+ padding: 6px 16px;
173
+ border: 1px solid var(--border);
174
+ border-radius: 100px;
175
+ display: inline-block;
176
+ background: var(--bg-surface);
177
+ }
178
+
179
+ /* ━━━━ LAYOUT ROW ━━━━ */
180
+ .main-row {
181
+ gap: 20px !important;
182
+ align-items: stretch !important;
183
+ }
184
+
185
+ /* ━━━━ PANELS ━━━━ */
186
+ .panel-input,
187
+ .panel-output {
188
+ background: var(--bg-card) !important;
189
+ border: 1px solid var(--border) !important;
190
+ border-radius: var(--radius-lg) !important;
191
+ padding: 28px !important;
192
+ box-shadow: var(--shadow-glow) !important;
193
+ display: flex !important;
194
+ flex-direction: column !important;
195
+ gap: 0 !important;
196
+ }
197
+
198
+ .panel-input { flex: 1 !important; min-width: 360px !important; }
199
+ .panel-output { flex: 1.6 !important; min-width: 420px !important; }
200
+
201
+ /* ━━━━ SECTION HEADINGS ━━━━ */
202
+ .panel-input h2,
203
+ .panel-output h2 {
204
+ font-family: var(--font-display) !important;
205
+ font-size: 15px !important;
206
+ font-weight: 700 !important;
207
+ color: var(--text-muted) !important;
208
+ text-transform: uppercase !important;
209
+ letter-spacing: 2px !important;
210
+ margin-bottom: 20px !important;
211
+ padding-bottom: 12px !important;
212
+ border-bottom: 1px solid var(--border) !important;
213
+ }
214
+
215
+ /* ━━━━ HINT LABELS ━━━━ */
216
+ .hint-label {
217
+ font-family: var(--font-mono);
218
+ font-size: 11px;
219
+ color: var(--text-muted);
220
+ letter-spacing: 0.5px;
221
+ margin-bottom: 8px;
222
+ margin-top: 4px;
223
+ }
224
+
225
+ /* ━━━━ AUDIO COMPONENTS — THE CRITICAL FIX ━━━━ */
226
+
227
+ /* Outer wrapper Gradio creates */
228
+ .audio-component-wrap {
229
+ width: 100% !important;
230
+ margin-bottom: 16px !important;
231
+ }
232
+
233
+ /* Kill ALL overflow:hidden on audio internals */
234
+ .audio-component-wrap *,
235
+ [data-testid="audio"],
236
+ [data-testid="audio"] *,
237
+ .waveform-container,
238
+ .waveform-container *,
239
+ .waveform-controls,
240
+ .waveform-controls * {
241
+ overflow: visible !important;
242
+ }
243
+
244
+ /* The main audio block */
245
+ [data-testid="audio"],
246
+ .gr-audio,
247
+ .audio-wrap > div {
248
+ background: var(--bg-element) !important;
249
+ border: 1px solid var(--border) !important;
250
+ border-radius: var(--radius-md) !important;
251
+ padding: 0 !important;
252
+ width: 100% !important;
253
+ overflow: visible !important;
254
+ }
255
+
256
+ /* Waveform canvas area — give it proper height */
257
+ .waveform-container canvas,
258
+ [class*="waveform"] canvas {
259
+ width: 100% !important;
260
+ min-height: 80px !important;
261
+ display: block !important;
262
+ border-radius: var(--radius-sm) var(--radius-sm) 0 0 !important;
263
+ }
264
+
265
+ /* Controls toolbar row — MUST NOT clip */
266
+ .waveform-controls,
267
+ [class*="controls"],
268
+ .audio-controls,
269
+ [data-testid="waveform-controls"] {
270
+ display: flex !important;
271
+ flex-direction: row !important;
272
+ flex-wrap: nowrap !important;
273
+ align-items: center !important;
274
+ gap: 6px !important;
275
+ padding: 10px 12px !important;
276
+ background: var(--bg-card) !important;
277
+ border-top: 1px solid var(--border) !important;
278
+ border-radius: 0 0 var(--radius-md) var(--radius-md) !important;
279
+ width: 100% !important;
280
+ min-height: 52px !important;
281
+ overflow: visible !important;
282
+ }
283
+
284
+ /* Time labels */
285
+ .waveform-controls span,
286
+ [class*="time"],
287
+ [class*="timestamp"] {
288
+ font-family: var(--font-mono) !important;
289
+ font-size: 11px !important;
290
+ color: var(--text-muted) !important;
291
+ white-space: nowrap !important;
292
+ flex-shrink: 0 !important;
293
+ }
294
+
295
+ /* ALL icon/control buttons in audio toolbar */
296
+ .waveform-controls button,
297
+ .audio-controls button,
298
+ [data-testid="audio"] button,
299
+ [class*="icon-button"],
300
+ [class*="control-button"],
301
+ [class*="audio-button"] {
302
+ background: var(--bg-surface) !important;
303
+ border: 1px solid var(--border) !important;
304
+ color: var(--accent-light) !important;
305
+ border-radius: var(--radius-sm) !important;
306
+ width: 32px !important;
307
+ height: 32px !important;
308
+ min-width: 32px !important;
309
+ min-height: 32px !important;
310
+ max-width: 32px !important;
311
+ max-height: 32px !important;
312
+ padding: 0 !important;
313
+ display: flex !important;
314
+ align-items: center !important;
315
+ justify-content: center !important;
316
+ cursor: pointer !important;
317
+ flex-shrink: 0 !important;
318
+ transition: background 0.15s, border-color 0.15s !important;
319
+ }
320
+
321
+ .waveform-controls button:hover,
322
+ .audio-controls button:hover,
323
+ [data-testid="audio"] button:hover {
324
+ background: var(--accent-dim) !important;
325
+ border-color: var(--accent) !important;
326
+ }
327
+
328
+ /* SVG icons inside buttons */
329
+ .waveform-controls button svg,
330
+ .audio-controls button svg,
331
+ [data-testid="audio"] button svg {
332
+ width: 14px !important;
333
+ height: 14px !important;
334
+ fill: currentColor !important;
335
+ stroke: currentColor !important;
336
+ flex-shrink: 0 !important;
337
+ }
338
+
339
+ /* Play button — slightly larger accent */
340
+ [data-testid="play-pause-button"],
341
+ [aria-label="Play"],
342
+ [aria-label="Pause"] {
343
+ background: var(--accent) !important;
344
+ border-color: var(--accent) !important;
345
+ color: #fff !important;
346
+ width: 36px !important;
347
+ height: 36px !important;
348
+ min-width: 36px !important;
349
+ max-width: 36px !important;
350
+ border-radius: 50% !important;
351
+ }
352
+
353
+ /* Speed badge */
354
+ [class*="speed"],
355
+ [aria-label*="speed"],
356
+ [aria-label*="Speed"] {
357
+ font-family: var(--font-mono) !important;
358
+ font-size: 10px !important;
359
+ min-width: 36px !important;
360
+ max-width: 36px !important;
361
+ padding: 0 4px !important;
362
+ letter-spacing: 0 !important;
363
+ }
364
+
365
+ /* Upload drag area */
366
+ [data-testid="audio"] .upload-container,
367
+ [data-testid="audio"] .empty-state,
368
+ .audio-component-wrap .upload-container {
369
+ background: var(--bg-element) !important;
370
+ border: 2px dashed var(--border-glow) !important;
371
+ border-radius: var(--radius-md) !important;
372
+ min-height: 100px !important;
373
+ display: flex !important;
374
+ flex-direction: column !important;
375
+ align-items: center !important;
376
+ justify-content: center !important;
377
+ color: var(--text-muted) !important;
378
+ font-family: var(--font-mono) !important;
379
+ font-size: 12px !important;
380
+ cursor: pointer !important;
381
+ transition: border-color 0.2s, background 0.2s !important;
382
+ }
383
+
384
+ .audio-component-wrap .upload-container:hover {
385
+ border-color: var(--accent) !important;
386
+ background: rgba(124, 92, 252, 0.06) !important;
387
+ }
388
+
389
+ /* ━━━━ DIVIDER ━━━━ */
390
+ .section-divider {
391
+ border: none !important;
392
+ border-top: 1px solid var(--border) !important;
393
+ margin: 22px 0 !important;
394
+ }
395
+
396
+ /* ━━━━ ANALYZE BUTTONS ━━━━ */
397
+ .btn-analyze {
398
+ width: 100% !important;
399
+ padding: 13px 20px !important;
400
+ font-family: var(--font-display) !important;
401
+ font-size: 14px !important;
402
+ font-weight: 700 !important;
403
+ letter-spacing: 0.5px !important;
404
+ color: #fff !important;
405
+ background: linear-gradient(135deg, var(--accent-dim), var(--accent)) !important;
406
+ border: none !important;
407
+ border-radius: var(--radius-md) !important;
408
+ cursor: pointer !important;
409
+ transition: opacity 0.2s, transform 0.1s, box-shadow 0.2s !important;
410
+ box-shadow: 0 4px 20px rgba(124, 92, 252, 0.3) !important;
411
+ margin-bottom: 4px !important;
412
+ }
413
+
414
+ /* Catch all Gradio primary button variants */
415
+ button.lg,
416
+ button[class*="primary"],
417
+ .svelte-cmf5ev,
418
+ [data-testid*="analyze"] {
419
+ background: linear-gradient(135deg, var(--accent-dim), var(--accent)) !important;
420
+ color: #fff !important;
421
+ border: none !important;
422
+ border-radius: var(--radius-md) !important;
423
+ font-family: var(--font-display) !important;
424
+ font-size: 14px !important;
425
+ font-weight: 700 !important;
426
+ padding: 13px 20px !important;
427
+ width: 100% !important;
428
+ cursor: pointer !important;
429
+ box-shadow: 0 4px 20px rgba(124, 92, 252, 0.3) !important;
430
+ transition: opacity 0.2s, transform 0.1s !important;
431
+ margin-bottom: 4px !important;
432
+ }
433
+
434
+ button.lg:hover,
435
+ button[class*="primary"]:hover {
436
+ opacity: 0.88 !important;
437
+ transform: translateY(-1px) !important;
438
+ box-shadow: 0 6px 28px rgba(124, 92, 252, 0.45) !important;
439
+ }
440
+
441
+ button.lg:active { transform: translateY(0) !important; }
442
+
443
+ /* Secondary / clear buttons */
444
+ .btn-secondary button,
445
+ button.secondary,
446
+ button[class*="secondary"] {
447
+ background: var(--bg-element) !important;
448
+ border: 1px solid var(--border) !important;
449
+ color: var(--text-muted) !important;
450
+ font-family: var(--font-display) !important;
451
+ font-size: 13px !important;
452
+ font-weight: 600 !important;
453
+ border-radius: var(--radius-md) !important;
454
+ padding: 10px 20px !important;
455
+ width: 100% !important;
456
+ cursor: pointer !important;
457
+ transition: background 0.2s, color 0.2s !important;
458
+ }
459
+
460
+ .btn-secondary button:hover { background: var(--bg-surface) !important; color: var(--text-primary) !important; }
461
+
462
+ /* ━━━━ OUTPUT TEXTBOX ━━━━ */
463
+ .panel-output textarea,
464
+ .panel-output .gr-textbox textarea,
465
+ textarea {
466
+ background: var(--bg-surface) !important;
467
+ color: var(--text-primary) !important;
468
+ border: 1px solid var(--border) !important;
469
+ border-radius: var(--radius-md) !important;
470
+ font-family: var(--font-mono) !important;
471
+ font-size: 13px !important;
472
+ line-height: 1.8 !important;
473
+ min-height: 420px !important;
474
+ max-height: 70vh !important;
475
+ padding: 20px !important;
476
+ resize: vertical !important;
477
+ width: 100% !important;
478
+ transition: border-color 0.2s !important;
479
+ }
480
+
481
+ textarea:focus {
482
+ outline: none !important;
483
+ border-color: var(--accent) !important;
484
+ box-shadow: 0 0 0 3px rgba(124, 92, 252, 0.12) !important;
485
+ }
486
+
487
+ textarea::placeholder { color: var(--text-dim) !important; }
488
+
489
+ /* ━━━━ LABELS ━━━━ */
490
+ label span,
491
+ .label-wrap span,
492
+ .gr-textbox label span,
493
+ [class*="label"] {
494
+ font-family: var(--font-mono) !important;
495
+ font-size: 11px !important;
496
+ color: var(--text-muted) !important;
497
+ text-transform: uppercase !important;
498
+ letter-spacing: 1px !important;
499
+ }
500
+
501
+ /* ━━━━ SCROLLBARS ━━━━ */
502
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
503
+ ::-webkit-scrollbar-track { background: var(--bg-base); }
504
+ ::-webkit-scrollbar-thumb { background: var(--border-glow); border-radius: 4px; }
505
+
506
+ /* ━━━━ HIDE GRADIO FOOTER ━━━━ */
507
+ footer,
508
+ .built-with,
509
+ [class*="built-with"],
510
+ .footer,
511
+ .svelte-1ax1toq,
512
+ .gradio-container ~ footer {
513
+ display: none !important;
514
+ visibility: hidden !important;
515
+ height: 0 !important;
516
+ overflow: hidden !important;
517
+ }
518
+
519
+ /* ━━━━ RECORDING PULSE ━━━━ */
520
+ @keyframes pulse-rec {
521
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.5); }
522
+ 50% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
523
+ }
524
+
525
+ [data-testid="record-button"][aria-pressed="true"],
526
+ .recording button {
527
+ animation: pulse-rec 1.2s infinite !important;
528
+ background: #dc2626 !important;
529
+ border-color: #ef4444 !important;
530
+ }
531
+
532
+ /* ━━━━ RESPONSIVE ━━━━ */
533
+ @media (max-width: 800px) {
534
+ .gradio-container { padding: 16px 12px 40px !important; }
535
+ .panel-input, .panel-output { min-width: 100% !important; padding: 18px !important; }
536
+ }
537
+ """
538
+
539
+
540
+ # ── Interface ──────────────────────────────────────────────
541
  def create_interface():
542
+ with gr.Blocks(css=APP_CSS, theme=gr.themes.Base()) as interface:
543
 
544
+ # Header
545
+ gr.HTML("""
546
+ <div class="app-header">
547
+ <h1>🎧 Audio Sentiment <span>Analyzer</span></h1>
548
+ <div class="app-subtitle">DistilBERT · Whisper · Multi-language · Real-time analysis</div>
549
+ </div>
550
+ """)
551
 
552
+ with gr.Row(elem_classes="main-row", equal_height=False):
553
 
554
+ # ── INPUT PANEL ──────────────────────────────
555
+ with gr.Column(scale=1, elem_classes="panel-input"):
556
  gr.Markdown("## 🎤 Input Panel")
557
 
558
+ # Upload
559
+ gr.HTML("<div class='hint-label'>📁 Upload audio file MP3, WAV, M4A, OGG, FLAC</div>")
560
  upload_audio = gr.Audio(
561
  sources=["upload"],
562
  type="filepath",
563
  label="Upload Audio File",
564
+ elem_classes="audio-component-wrap",
565
+ show_download_button=False, # removes download clutter
566
+ editable=False, # ← DISABLES trim entirely
567
+ )
568
+ upload_btn = gr.Button(
569
+ "🚀 Analyze Uploaded Audio",
570
+ variant="primary",
571
+ elem_classes="btn-analyze"
572
  )
 
573
 
574
+ gr.HTML("<hr class='section-divider'>")
575
 
576
+ # Record
577
+ gr.HTML("<div class='hint-label'>🎙️ Record live from microphone</div>")
578
  record_audio = gr.Audio(
579
  sources=["microphone"],
580
  type="filepath",
581
+ label="Microphone Recording",
582
+ elem_classes="audio-component-wrap",
583
+ editable=False, # ← DISABLES trim entirely
584
+ )
585
+ record_btn = gr.Button(
586
+ "🎙️ Analyze Recorded Audio",
587
+ variant="primary",
588
+ elem_classes="btn-analyze"
589
  )
 
590
 
591
+ gr.HTML("<hr class='section-divider'>")
592
 
593
+ with gr.Row(elem_classes="btn-secondary"):
594
+ clear_btn = gr.Button("🧹 Clear All", variant="secondary")
595
 
596
+ # ── OUTPUT PANEL ──────────────────────────────
597
+ with gr.Column(scale=2, elem_classes="panel-output"):
598
  gr.Markdown("## 📊 Result Panel")
599
 
600
  output_text = gr.Textbox(
601
+ lines=22,
602
  label="Analysis Result",
603
+ placeholder=(
604
+ "Results will appear here after analysis...\n\n"
605
+ " 🎯 Transcription\n"
606
+ " 📊 Sentiment label\n"
607
+ " 🔍 Confidence score\n"
608
+ " ▓▓▓ Confidence bar"
609
+ ),
610
+ show_copy_button=True,
611
+ interactive=False,
612
  )
613
 
614
+ with gr.Row(elem_classes="btn-secondary"):
615
+ clear_result_btn = gr.Button("🧹 Clear Result", variant="secondary")
616
 
617
+ # ── Events ──────────────────────────────────────
618
  upload_btn.click(fn=process_audio, inputs=upload_audio, outputs=output_text)
619
  record_btn.click(fn=process_audio, inputs=record_audio, outputs=output_text)
620
 
 
623
  inputs=[],
624
  outputs=[upload_audio, record_audio, output_text]
625
  )
 
626
  clear_result_btn.click(
627
+ fn=lambda: "",
628
  inputs=[],
629
  outputs=output_text
630
  )
631
+
632
  return interface
633
 
634
 
635
+ # ── Launch ─────────────────────────────────────────────────
636
  if __name__ == "__main__":
637
  interface = create_interface()
638
+ interface.launch(share=True)