pranit144 commited on
Commit
4f3952b
·
verified ·
1 Parent(s): 109d651

Upload 6 files

Browse files
Files changed (6) hide show
  1. Dockerfile +17 -0
  2. app.py +32 -0
  3. requirements.txt +5 -0
  4. static/audio.wav +0 -0
  5. temp.wav +0 -0
  6. templates/index.html +652 -0
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ # Create directory structure
11
+ RUN mkdir -p static/images
12
+
13
+ # Expose the port the app runs on
14
+ EXPOSE 7860
15
+
16
+ # Command to run the app
17
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
app.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ import google.generativeai as genai
3
+
4
+ app = Flask(__name__)
5
+
6
+ # Configure your Gemini API key
7
+ genai.configure(api_key = "AIzaSyBoEzi2YrxrGZ2WwqDRCDTG6rbdXTj9yMQ")
8
+
9
+ # Supported languages
10
+ languages = {
11
+ "Hindi": "hi"
12
+ }
13
+
14
+ @app.route('/')
15
+ def index():
16
+ return render_template('index.html', languages=languages)
17
+
18
+ @app.route('/translate', methods=['POST'])
19
+ def translate():
20
+ data = request.get_json()
21
+ text = data.get('text', '')
22
+ target = data.get('target', '')
23
+ try:
24
+ model = genai.GenerativeModel('gemini-2.0-flash')
25
+ prompt = f"Translate the following text {target} and just provide me translated text in hindi langauge no marks should be there only plane text nothing else:\n{text}"
26
+ response = model.generate_content(prompt)
27
+ return jsonify({'translation': response.text})
28
+ except Exception as e:
29
+ return jsonify({'translation': f"Error: {e}"}), 500
30
+
31
+ if __name__ == '__main__':
32
+ app.run(debug=True)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Flask
2
+ SpeechRecognition
3
+ google-generativeai
4
+ pydub
5
+ gunicorn
static/audio.wav ADDED
Binary file (39.2 kB). View file
 
temp.wav ADDED
Binary file (41.1 kB). View file
 
templates/index.html ADDED
@@ -0,0 +1,652 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>🎙️ Voice Translator </title>
7
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ :root {
11
+ --primary-color: #8c6db0;
12
+ --primary-light: #b995e5;
13
+ --primary-dark: #6a4c91;
14
+ --accent-color: #30b8a8;
15
+ --accent-dark: #228b7f;
16
+ --gradient-start: #9575cd;
17
+ --gradient-end: #7986cb;
18
+ --text-color: #3a3a3a;
19
+ --text-secondary: #6b7280;
20
+ --light-bg: #f8f9fa;
21
+ --card-bg: #ffffff;
22
+ --panel-bg: #f5f7fb;
23
+ --border-radius: 16px;
24
+ --box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
25
+ }
26
+
27
+ body {
28
+ background: linear-gradient(135deg, var(--light-bg) 0%, #edf2f7 100%);
29
+ color: var(--text-color);
30
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
31
+ line-height: 1.6;
32
+ min-height: 100vh;
33
+ overflow-x: hidden;
34
+ }
35
+
36
+ .app-container {
37
+ max-width: 900px;
38
+ margin: 40px auto;
39
+ padding: 0 20px;
40
+ }
41
+
42
+ .translator-card {
43
+ background: var(--card-bg);
44
+ border-radius: var(--border-radius);
45
+ box-shadow: var(--box-shadow);
46
+ padding: 30px;
47
+ margin-bottom: 30px;
48
+ border: none;
49
+ position: relative;
50
+ overflow: hidden;
51
+ }
52
+
53
+ .translator-card::before {
54
+ content: '';
55
+ position: absolute;
56
+ top: 0;
57
+ left: 0;
58
+ right: 0;
59
+ height: 6px;
60
+ background: linear-gradient(90deg, var(--gradient-start), var(--gradient-end), var(--accent-color));
61
+ border-radius: var(--border-radius) var(--border-radius) 0 0;
62
+ }
63
+
64
+ .app-header {
65
+ text-align: center;
66
+ margin-bottom: 30px;
67
+ position: relative;
68
+ }
69
+
70
+ .app-title {
71
+ font-weight: 700;
72
+ margin-bottom: 10px;
73
+ font-size: 2.5rem;
74
+ background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
75
+ -webkit-background-clip: text;
76
+ background-clip: text;
77
+ -webkit-text-fill-color: transparent;
78
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
79
+ }
80
+
81
+ .app-subtitle {
82
+ color: var(--text-secondary);
83
+ font-size: 1.1rem;
84
+ margin-bottom: 0;
85
+ }
86
+
87
+ .language-select-container {
88
+ position: relative;
89
+ margin-bottom: 30px;
90
+ }
91
+
92
+ .language-select {
93
+ background-color: var(--panel-bg);
94
+ border: 2px solid rgba(0, 0, 0, 0.05);
95
+ border-radius: var(--border-radius);
96
+ padding: 14px 20px;
97
+ color: var(--text-color);
98
+ font-size: 1.1rem;
99
+ transition: all 0.3s ease;
100
+ width: 100%;
101
+ appearance: none;
102
+ }
103
+
104
+ .language-select:focus {
105
+ border-color: var(--accent-color);
106
+ box-shadow: 0 0 0 3px rgba(48, 184, 168, 0.25);
107
+ outline: none;
108
+ }
109
+
110
+ .language-select-icon {
111
+ position: absolute;
112
+ right: 15px;
113
+ top: 50%;
114
+ transform: translateY(-50%);
115
+ color: var(--accent-color);
116
+ pointer-events: none;
117
+ }
118
+
119
+ .control-buttons {
120
+ display: flex;
121
+ gap: 20px;
122
+ justify-content: center;
123
+ margin: 25px 0 35px;
124
+ }
125
+
126
+ .btn {
127
+ padding: 14px 30px;
128
+ border-radius: 50px;
129
+ font-weight: 600;
130
+ font-size: 1.1rem;
131
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
132
+ position: relative;
133
+ overflow: hidden;
134
+ z-index: 1;
135
+ }
136
+
137
+ .btn::after {
138
+ content: '';
139
+ position: absolute;
140
+ bottom: -50%;
141
+ left: -10%;
142
+ width: 120%;
143
+ height: 200%;
144
+ background: rgba(255, 255, 255, 0.3);
145
+ border-radius: 40%;
146
+ transform: scale(0);
147
+ transition: transform 0.5s;
148
+ z-index: -1;
149
+ }
150
+
151
+ .btn:hover::after {
152
+ transform: scale(1);
153
+ }
154
+
155
+ .btn:active {
156
+ transform: scale(0.97);
157
+ }
158
+
159
+ .btn-primary {
160
+ background: linear-gradient(135deg, var(--primary-color), var(--gradient-start));
161
+ border: none;
162
+ box-shadow: 0 4px 15px rgba(140, 109, 176, 0.3);
163
+ color: white;
164
+ }
165
+
166
+ .btn-primary:hover {
167
+ box-shadow: 0 6px 20px rgba(140, 109, 176, 0.4);
168
+ background: linear-gradient(135deg, var(--primary-light), var(--gradient-start));
169
+ }
170
+
171
+ .btn-danger {
172
+ background: linear-gradient(135deg, var(--accent-dark), var(--accent-color));
173
+ border: none;
174
+ box-shadow: 0 4px 15px rgba(48, 184, 168, 0.3);
175
+ color: white;
176
+ }
177
+
178
+ .btn-danger:hover {
179
+ box-shadow: 0 6px 20px rgba(48, 184, 168, 0.4);
180
+ background: linear-gradient(135deg, var(--accent-color), #44d6c6);
181
+ }
182
+
183
+ .btn-icon {
184
+ margin-right: 10px;
185
+ font-size: 1.2rem;
186
+ }
187
+
188
+ .text-panel-container {
189
+ position: relative;
190
+ margin-bottom: 30px;
191
+ }
192
+
193
+ .text-panel {
194
+ border-radius: var(--border-radius);
195
+ background-color: var(--panel-bg);
196
+ padding: 25px;
197
+ min-height: 150px;
198
+ margin-top: 10px;
199
+ white-space: pre-wrap;
200
+ border: 2px solid rgba(0, 0, 0, 0.05);
201
+ transition: all 0.3s ease;
202
+ color: var(--text-color);
203
+ font-size: 1.1rem;
204
+ line-height: 1.7;
205
+ position: relative;
206
+ }
207
+
208
+ .text-panel.active {
209
+ border-color: var(--accent-color);
210
+ box-shadow: 0 0 20px rgba(48, 184, 168, 0.15);
211
+ }
212
+
213
+ .text-panel-header {
214
+ display: flex;
215
+ align-items: center;
216
+ margin-bottom: 15px;
217
+ }
218
+
219
+ .text-panel-icon {
220
+ width: 42px;
221
+ height: 42px;
222
+ background: linear-gradient(135deg, var(--primary-light), var(--primary-color));
223
+ border-radius: 50%;
224
+ display: flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ margin-right: 15px;
228
+ color: white;
229
+ font-size: 1.3rem;
230
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
231
+ }
232
+
233
+ .panel-title {
234
+ font-weight: 600;
235
+ font-size: 1.2rem;
236
+ margin: 0;
237
+ letter-spacing: 0.5px;
238
+ color: var(--primary-dark);
239
+ }
240
+
241
+ .footer {
242
+ text-align: center;
243
+ margin-top: 40px;
244
+ color: var(--text-secondary);
245
+ font-size: 0.95rem;
246
+ opacity: 0.8;
247
+ }
248
+
249
+ .pulse-animation {
250
+ animation: pulse 2s infinite;
251
+ }
252
+
253
+ @keyframes pulse {
254
+ 0% {
255
+ box-shadow: 0 0 0 0 rgba(48, 184, 168, 0.4);
256
+ }
257
+ 70% {
258
+ box-shadow: 0 0 0 15px rgba(48, 184, 168, 0);
259
+ }
260
+ 100% {
261
+ box-shadow: 0 0 0 0 rgba(48, 184, 168, 0);
262
+ }
263
+ }
264
+
265
+ .status-indicator {
266
+ display: none;
267
+ align-items: center;
268
+ justify-content: center;
269
+ margin: 20px 0;
270
+ color: var(--accent-color);
271
+ font-weight: 500;
272
+ font-size: 1.1rem;
273
+ letter-spacing: 0.5px;
274
+ }
275
+
276
+ .status-indicator.active {
277
+ display: flex;
278
+ }
279
+
280
+ .audio-wave {
281
+ display: inline-flex;
282
+ align-items: flex-end;
283
+ height: 20px;
284
+ margin-right: 12px;
285
+ }
286
+
287
+ .audio-wave span {
288
+ display: inline-block;
289
+ width: 3px;
290
+ margin-right: 3px;
291
+ background: var(--accent-color);
292
+ animation: wave 1.2s infinite ease-in-out;
293
+ }
294
+
295
+ .audio-wave span:nth-child(1) { height: 8px; animation-delay: 0s; }
296
+ .audio-wave span:nth-child(2) { height: 16px; animation-delay: 0.2s; }
297
+ .audio-wave span:nth-child(3) { height: 12px; animation-delay: 0.3s; }
298
+ .audio-wave span:nth-child(4) { height: 20px; animation-delay: 0.4s; }
299
+ .audio-wave span:nth-child(5) { height: 14px; animation-delay: 0.5s; }
300
+
301
+ @keyframes wave {
302
+ 0%, 100% { transform: scaleY(1); }
303
+ 50% { transform: scaleY(0.5); }
304
+ }
305
+
306
+ .floating-icon {
307
+ position: absolute;
308
+ font-size: 8rem;
309
+ color: rgba(140, 109, 176, 0.07);
310
+ z-index: 0;
311
+ pointer-events: none;
312
+ }
313
+
314
+ .floating-icon.globe {
315
+ bottom: -2rem;
316
+ right: -2rem;
317
+ }
318
+
319
+ .floating-icon.mic {
320
+ top: -1rem;
321
+ left: -1rem;
322
+ transform: rotate(-15deg);
323
+ font-size: 6rem;
324
+ }
325
+
326
+ .translate-animation {
327
+ position: relative;
328
+ }
329
+
330
+ .translate-animation::before {
331
+ content: '';
332
+ position: absolute;
333
+ top: 0;
334
+ left: 0;
335
+ width: 100%;
336
+ height: 100%;
337
+ background: linear-gradient(90deg,
338
+ transparent,
339
+ rgba(48, 184, 168, 0.2),
340
+ transparent);
341
+ transform: translateX(-100%);
342
+ animation: translateShimmer 2s infinite;
343
+ }
344
+
345
+ @keyframes translateShimmer {
346
+ 100% { transform: translateX(100%); }
347
+ }
348
+
349
+ .spinner {
350
+ display: inline-block;
351
+ width: 18px;
352
+ height: 18px;
353
+ border: 3px solid rgba(48, 184, 168, 0.3);
354
+ border-top-color: var(--accent-color);
355
+ border-radius: 50%;
356
+ margin-right: 10px;
357
+ animation: spin 1s ease-in-out infinite;
358
+ }
359
+
360
+ @keyframes spin {
361
+ to { transform: rotate(360deg); }
362
+ }
363
+
364
+ .language-badge {
365
+ position: absolute;
366
+ top: 15px;
367
+ right: 15px;
368
+ background: rgba(48, 184, 168, 0.1);
369
+ color: var(--accent-dark);
370
+ padding: 5px 12px;
371
+ border-radius: 20px;
372
+ font-size: 0.85rem;
373
+ font-weight: 600;
374
+ display: flex;
375
+ align-items: center;
376
+ gap: 5px;
377
+ }
378
+
379
+ /* Responsive adjustments */
380
+ @media (max-width: 768px) {
381
+ .app-title {
382
+ font-size: 2rem;
383
+ }
384
+
385
+ .app-subtitle {
386
+ font-size: 0.95rem;
387
+ }
388
+
389
+ .control-buttons {
390
+ flex-direction: column;
391
+ gap: 15px;
392
+ }
393
+
394
+ .btn {
395
+ width: 100%;
396
+ padding: 12px;
397
+ }
398
+
399
+ .text-panel {
400
+ min-height: 120px;
401
+ padding: 20px;
402
+ font-size: 1rem;
403
+ }
404
+ }
405
+
406
+ /* Light scrollbar */
407
+ ::-webkit-scrollbar {
408
+ width: 6px;
409
+ height: 6px;
410
+ }
411
+
412
+ ::-webkit-scrollbar-track {
413
+ background: var(--panel-bg);
414
+ }
415
+
416
+ ::-webkit-scrollbar-thumb {
417
+ background: var(--primary-light);
418
+ border-radius: 10px;
419
+ }
420
+
421
+ ::-webkit-scrollbar-thumb:hover {
422
+ background: var(--primary-color);
423
+ }
424
+ </style>
425
+ </head>
426
+ <body>
427
+ <div class="app-container">
428
+ <div class="translator-card">
429
+ <div class="floating-icon mic">
430
+ <i class="fas fa-microphone"></i>
431
+ </div>
432
+ <div class="floating-icon globe">
433
+ <i class="fas fa-globe"></i>
434
+ </div>
435
+
436
+ <div class="app-header">
437
+ <h1 class="app-title"><i class="fas fa-microphone-alt"></i> Voice Translator </h1>
438
+ <p class="app-subtitle">Speak naturally. Translate instantly. Connect globally.</p>
439
+ </div>
440
+
441
+ <div class="row">
442
+ <div class="col-md-8 offset-md-2">
443
+ <div class="language-select-container">
444
+ <select id="lang" class="form-select language-select">
445
+ {% for name, code in languages.items() %}
446
+ <option value="{{ code }}">{{ name }}</option>
447
+ {% endfor %}
448
+ </select>
449
+ <div class="language-select-icon">
450
+ <i class="fas fa-language fa-lg"></i>
451
+ </div>
452
+ </div>
453
+ </div>
454
+ </div>
455
+
456
+ <div class="control-buttons">
457
+ <button id="startBtn" class="btn btn-primary">
458
+ <i class="fas fa-play-circle btn-icon"></i> Start Listening
459
+ </button>
460
+ <button id="stopBtn" class="btn btn-danger" disabled>
461
+ <i class="fas fa-stop-circle btn-icon"></i> Stop & Translate
462
+ </button>
463
+ </div>
464
+
465
+ <div class="status-indicator" id="listeningIndicator">
466
+ <div class="audio-wave">
467
+ <span></span>
468
+ <span></span>
469
+ <span></span>
470
+ <span></span>
471
+ <span></span>
472
+ </div>
473
+ Listening to your voice...
474
+ </div>
475
+
476
+ <div class="text-panel-container">
477
+ <div class="text-panel-header">
478
+ <div class="text-panel-icon">
479
+ <i class="fas fa-headphones"></i>
480
+ </div>
481
+ <h5 class="panel-title">Recognized Speech</h5>
482
+ </div>
483
+ <div class="language-badge">
484
+ <i class="fas fa-flag"></i> English
485
+ </div>
486
+ <div id="recognized" class="text-panel"></div>
487
+ </div>
488
+
489
+ <div class="text-panel-container">
490
+ <div class="text-panel-header">
491
+ <div class="text-panel-icon">
492
+ <i class="fas fa-language"></i>
493
+ </div>
494
+ <h5 class="panel-title">Translation</h5>
495
+ </div>
496
+ <div class="language-badge" id="targetLanguageBadge">
497
+ <i class="fas fa-flag"></i> <span id="langDisplay">Hindi</span>
498
+ </div>
499
+ <div id="translated" class="text-panel"></div>
500
+ </div>
501
+ </div>
502
+
503
+ <div class="footer">
504
+ <p>
505
+ <i class="fas fa-magic"></i> Powered by Flask
506
+ <br>
507
+ <small>© 2025 Voice Translator • Speak clearly for optimal recognition</small>
508
+ </p>
509
+ </div>
510
+ </div>
511
+
512
+ <script>
513
+ // Check for browser support
514
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
515
+ if (!SpeechRecognition) {
516
+ alert('Your browser does not support Speech Recognition API.');
517
+ }
518
+
519
+ const recognition = new SpeechRecognition();
520
+ recognition.continuous = true;
521
+ recognition.interimResults = true;
522
+ recognition.lang = 'en-US';
523
+
524
+ const startBtn = document.getElementById('startBtn');
525
+ const stopBtn = document.getElementById('stopBtn');
526
+ const recognizedDiv = document.getElementById('recognized');
527
+ const translatedDiv = document.getElementById('translated');
528
+ const listeningIndicator = document.getElementById('listeningIndicator');
529
+ const langSelect = document.getElementById('lang');
530
+ const langDisplay = document.getElementById('langDisplay');
531
+
532
+ // Set initial language display
533
+ updateLanguageDisplay();
534
+
535
+ let finalTranscript = '';
536
+
537
+ function updateLanguageDisplay() {
538
+ const selectedOption = langSelect.options[langSelect.selectedIndex];
539
+ langDisplay.textContent = selectedOption.text;
540
+ }
541
+
542
+ langSelect.addEventListener('change', updateLanguageDisplay);
543
+
544
+ recognition.onresult = (event) => {
545
+ let interimTranscript = '';
546
+ for (let i = event.resultIndex; i < event.results.length; ++i) {
547
+ const transcript = event.results[i][0].transcript;
548
+ if (event.results[i].isFinal) {
549
+ finalTranscript += transcript + ' ';
550
+ } else {
551
+ interimTranscript += transcript;
552
+ }
553
+ }
554
+ // Display live recognized speech
555
+ recognizedDiv.innerHTML = finalTranscript +
556
+ `<span style="opacity: 0.7;">${interimTranscript}</span>`;
557
+ };
558
+
559
+ recognition.onstart = () => {
560
+ listeningIndicator.classList.add('active');
561
+ recognizedDiv.classList.add('active');
562
+ recognizedDiv.classList.add('pulse-animation');
563
+ };
564
+
565
+ recognition.onend = () => {
566
+ listeningIndicator.classList.remove('active');
567
+ recognizedDiv.classList.remove('pulse-animation');
568
+ };
569
+
570
+ recognition.onerror = (event) => {
571
+ console.error('Speech recognition error', event.error);
572
+ listeningIndicator.classList.remove('active');
573
+ recognizedDiv.classList.remove('pulse-animation');
574
+ recognizedDiv.classList.remove('active');
575
+
576
+ if (event.error === 'no-speech') {
577
+ recognizedDiv.innerHTML = '<span style="color: var(--accent-color);">No speech detected. Please try again.</span>';
578
+ } else {
579
+ recognizedDiv.innerHTML = `<span style="color: var(--accent-color);">Error: ${event.error}. Please try again.</span>`;
580
+ }
581
+ };
582
+
583
+ startBtn.onclick = () => {
584
+ finalTranscript = '';
585
+ recognizedDiv.innerText = '';
586
+ translatedDiv.innerText = '';
587
+ translatedDiv.classList.remove('active');
588
+ recognition.start();
589
+ startBtn.disabled = true;
590
+ stopBtn.disabled = false;
591
+
592
+ // Add animation effects
593
+ startBtn.style.transform = 'scale(0.95)';
594
+ setTimeout(() => startBtn.style.transform = '', 200);
595
+ };
596
+
597
+ stopBtn.onclick = async () => {
598
+ recognition.stop();
599
+ startBtn.disabled = false;
600
+ stopBtn.disabled = true;
601
+ recognizedDiv.classList.remove('active');
602
+
603
+ // Add animation effects
604
+ stopBtn.style.transform = 'scale(0.95)';
605
+ setTimeout(() => stopBtn.style.transform = '', 200);
606
+
607
+ const textToTranslate = finalTranscript.trim();
608
+ if (!textToTranslate) {
609
+ translatedDiv.innerHTML = '<span style="color: var(--accent-color);">No speech detected. Please speak before stopping.</span>';
610
+ return;
611
+ }
612
+
613
+ // Show loading state
614
+ translatedDiv.innerHTML = '<div class="text-center"><span class="spinner"></span> Translating your speech...</div>';
615
+ translatedDiv.classList.add('translate-animation');
616
+
617
+ try {
618
+ // Send text for translation
619
+ const response = await fetch('/translate', {
620
+ method: 'POST',
621
+ headers: { 'Content-Type': 'application/json' },
622
+ body: JSON.stringify({ text: textToTranslate, target: document.getElementById('lang').value })
623
+ });
624
+ const data = await response.json();
625
+
626
+ setTimeout(() => {
627
+ translatedDiv.classList.remove('translate-animation');
628
+ translatedDiv.classList.add('active');
629
+ translatedDiv.innerText = data.translation;
630
+ }, 500);
631
+ } catch (error) {
632
+ translatedDiv.classList.remove('translate-animation');
633
+ translatedDiv.innerHTML = '<span style="color: var(--accent-color);">Error: Could not complete translation. Please try again.</span>';
634
+ }
635
+ };
636
+
637
+ // Add some fancy animations on page load
638
+ document.addEventListener('DOMContentLoaded', () => {
639
+ document.querySelectorAll('.translator-card, .app-title, .app-subtitle, .text-panel-container').forEach((el, i) => {
640
+ el.style.opacity = '0';
641
+ el.style.transform = 'translateY(20px)';
642
+ el.style.transition = `opacity 0.5s ease-out ${i * 0.1}s, transform 0.5s ease-out ${i * 0.1}s`;
643
+
644
+ setTimeout(() => {
645
+ el.style.opacity = '1';
646
+ el.style.transform = 'translateY(0)';
647
+ }, 100);
648
+ });
649
+ });
650
+ </script>
651
+ </body>
652
+ </html>