Ronaldodev commited on
Commit
2d05242
·
verified ·
1 Parent(s): d64e640

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +476 -0
templates/index.html ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>STT Yoruba/Hausa</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+ body {
14
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
15
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
16
+ min-height: 100vh;
17
+ padding: 20px;
18
+ display: flex;
19
+ justify-content: center;
20
+ align-items: center;
21
+ }
22
+ .container {
23
+ background: white;
24
+ border-radius: 20px;
25
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
26
+ max-width: 700px;
27
+ width: 100%;
28
+ padding: 40px;
29
+ }
30
+ h1 {
31
+ text-align: center;
32
+ color: #333;
33
+ margin-bottom: 10px;
34
+ font-size: 2.5em;
35
+ }
36
+ .subtitle {
37
+ text-align: center;
38
+ color: #666;
39
+ margin-bottom: 30px;
40
+ font-size: 1.1em;
41
+ }
42
+ .language-selector {
43
+ display: flex;
44
+ gap: 10px;
45
+ justify-content: center;
46
+ margin-bottom: 30px;
47
+ }
48
+ .lang-btn {
49
+ padding: 15px 40px;
50
+ border: 2px solid #667eea;
51
+ background: white;
52
+ border-radius: 10px;
53
+ cursor: pointer;
54
+ font-size: 16px;
55
+ font-weight: bold;
56
+ transition: all 0.3s;
57
+ display: flex;
58
+ align-items: center;
59
+ gap: 8px;
60
+ }
61
+ .lang-btn:hover {
62
+ transform: translateY(-2px);
63
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
64
+ }
65
+ .lang-btn.active {
66
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
67
+ color: white;
68
+ border-color: transparent;
69
+ }
70
+ .upload-area {
71
+ border: 3px dashed #e0e0e0;
72
+ border-radius: 15px;
73
+ padding: 40px;
74
+ text-align: center;
75
+ margin-bottom: 20px;
76
+ cursor: pointer;
77
+ transition: all 0.3s;
78
+ }
79
+ .upload-area:hover {
80
+ border-color: #667eea;
81
+ background: #f8f9ff;
82
+ }
83
+ .upload-area.dragover {
84
+ border-color: #667eea;
85
+ background: #f0f2ff;
86
+ }
87
+ #fileInput {
88
+ display: none;
89
+ }
90
+ .upload-icon {
91
+ font-size: 4em;
92
+ margin-bottom: 10px;
93
+ }
94
+ .record-section {
95
+ text-align: center;
96
+ margin-bottom: 30px;
97
+ }
98
+ .record-btn {
99
+ padding: 20px 40px;
100
+ font-size: 18px;
101
+ border: none;
102
+ border-radius: 50px;
103
+ cursor: pointer;
104
+ font-weight: bold;
105
+ transition: all 0.3s;
106
+ display: inline-flex;
107
+ align-items: center;
108
+ gap: 10px;
109
+ }
110
+ .record-btn.inactive {
111
+ background: #4CAF50;
112
+ color: white;
113
+ }
114
+ .record-btn.recording {
115
+ background: #f44336;
116
+ color: white;
117
+ animation: pulse 1.5s infinite;
118
+ }
119
+ @keyframes pulse {
120
+ 0%, 100% { transform: scale(1); }
121
+ 50% { transform: scale(1.05); }
122
+ }
123
+ .transcribe-btn {
124
+ width: 100%;
125
+ padding: 15px;
126
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
127
+ color: white;
128
+ border: none;
129
+ border-radius: 10px;
130
+ font-size: 18px;
131
+ font-weight: bold;
132
+ cursor: pointer;
133
+ transition: all 0.3s;
134
+ margin-bottom: 20px;
135
+ }
136
+ .transcribe-btn:hover:not(:disabled) {
137
+ transform: translateY(-2px);
138
+ box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
139
+ }
140
+ .transcribe-btn:disabled {
141
+ background: #ccc;
142
+ cursor: not-allowed;
143
+ }
144
+ .result-box {
145
+ background: #f5f5f5;
146
+ border-radius: 10px;
147
+ padding: 20px;
148
+ margin-top: 20px;
149
+ display: none;
150
+ }
151
+ .result-box.show {
152
+ display: block;
153
+ }
154
+ .result-label {
155
+ font-weight: bold;
156
+ color: #555;
157
+ margin-bottom: 10px;
158
+ }
159
+ .result-text {
160
+ font-size: 18px;
161
+ color: #333;
162
+ line-height: 1.6;
163
+ }
164
+ .loading {
165
+ text-align: center;
166
+ color: #667eea;
167
+ font-weight: bold;
168
+ margin-top: 20px;
169
+ display: none;
170
+ }
171
+ .spinner {
172
+ border: 3px solid #f3f3f3;
173
+ border-top: 3px solid #667eea;
174
+ border-radius: 50%;
175
+ width: 40px;
176
+ height: 40px;
177
+ animation: spin 1s linear infinite;
178
+ margin: 20px auto;
179
+ }
180
+ @keyframes spin {
181
+ 0% { transform: rotate(0deg); }
182
+ 100% { transform: rotate(360deg); }
183
+ }
184
+ .error {
185
+ background: #ffebee;
186
+ color: #c62828;
187
+ padding: 15px;
188
+ border-radius: 10px;
189
+ margin-top: 20px;
190
+ display: none;
191
+ border-left: 4px solid #c62828;
192
+ }
193
+ .success {
194
+ background: #e8f5e9;
195
+ color: #2e7d32;
196
+ padding: 15px;
197
+ border-radius: 10px;
198
+ margin-top: 20px;
199
+ display: none;
200
+ border-left: 4px solid #2e7d32;
201
+ }
202
+ .file-info {
203
+ background: #e3f2fd;
204
+ padding: 15px;
205
+ border-radius: 10px;
206
+ margin-bottom: 20px;
207
+ display: none;
208
+ }
209
+ @media (max-width: 768px) {
210
+ .container {
211
+ padding: 20px;
212
+ }
213
+ h1 {
214
+ font-size: 1.8em;
215
+ }
216
+ .language-selector {
217
+ flex-direction: column;
218
+ }
219
+ }
220
+ </style>
221
+ </head>
222
+ <body>
223
+ <div class="container">
224
+ <h1>🎤 Speech-to-Text</h1>
225
+ <p class="subtitle">Yoruba & Hausa</p>
226
+
227
+ <!-- Sélection de langue -->
228
+ <div class="language-selector">
229
+ <button class="lang-btn active" data-lang="yoruba">
230
+ <span>🇳🇬</span>
231
+ <span>Yoruba</span>
232
+ </button>
233
+ <button class="lang-btn" data-lang="hausa">
234
+ <span>🇳🇬</span>
235
+ <span>Hausa</span>
236
+ </button>
237
+ </div>
238
+
239
+ <!-- Zone d'enregistrement -->
240
+ <div class="record-section">
241
+ <button class="record-btn inactive" id="recordBtn">
242
+ <span id="recordIcon">🎙️</span>
243
+ <span id="recordText">Enregistrer</span>
244
+ </button>
245
+ <div style="margin-top: 10px; color: #666;" id="recordTimer"></div>
246
+ </div>
247
+
248
+ <div style="text-align: center; margin: 20px 0; color: #999;">OU</div>
249
+
250
+ <!-- Zone d'upload -->
251
+ <div class="upload-area" id="uploadArea">
252
+ <div class="upload-icon">📁</div>
253
+ <div><strong>Cliquez pour choisir un fichier</strong></div>
254
+ <div style="margin-top: 5px; color: #999;">ou glissez-déposez un fichier audio</div>
255
+ <div style="margin-top: 10px; color: #999; font-size: 0.9em;">(MP3, WAV, M4A, OGG)</div>
256
+ <input type="file" id="fileInput" accept="audio/*">
257
+ </div>
258
+
259
+ <!-- Info fichier -->
260
+ <div class="file-info" id="fileInfo"></div>
261
+
262
+ <!-- Bouton transcrire -->
263
+ <button class="transcribe-btn" id="transcribeBtn" disabled>
264
+ Transcrire
265
+ </button>
266
+
267
+ <!-- Loading -->
268
+ <div class="loading" id="loading">
269
+ <div class="spinner"></div>
270
+ <p>Transcription en cours...</p>
271
+ </div>
272
+
273
+ <!-- Messages -->
274
+ <div class="error" id="error"></div>
275
+ <div class="success" id="success"></div>
276
+
277
+ <!-- Résultat -->
278
+ <div class="result-box" id="resultBox">
279
+ <div class="result-label">📝 Transcription :</div>
280
+ <div class="result-text" id="resultText"></div>
281
+ </div>
282
+ </div>
283
+
284
+ <script>
285
+ let selectedLanguage = 'yoruba';
286
+ let selectedFile = null;
287
+ let mediaRecorder = null;
288
+ let audioChunks = [];
289
+ let recordingStartTime = null;
290
+ let timerInterval = null;
291
+
292
+ // Éléments DOM
293
+ const langBtns = document.querySelectorAll('.lang-btn');
294
+ const uploadArea = document.getElementById('uploadArea');
295
+ const fileInput = document.getElementById('fileInput');
296
+ const fileInfo = document.getElementById('fileInfo');
297
+ const transcribeBtn = document.getElementById('transcribeBtn');
298
+ const recordBtn = document.getElementById('recordBtn');
299
+ const recordIcon = document.getElementById('recordIcon');
300
+ const recordText = document.getElementById('recordText');
301
+ const recordTimer = document.getElementById('recordTimer');
302
+ const loading = document.getElementById('loading');
303
+ const errorDiv = document.getElementById('error');
304
+ const successDiv = document.getElementById('success');
305
+ const resultBox = document.getElementById('resultBox');
306
+ const resultText = document.getElementById('resultText');
307
+
308
+ // Sélection de langue
309
+ langBtns.forEach(btn => {
310
+ btn.addEventListener('click', function() {
311
+ langBtns.forEach(b => b.classList.remove('active'));
312
+ this.classList.add('active');
313
+ selectedLanguage = this.dataset.lang;
314
+ });
315
+ });
316
+
317
+ // Upload de fichier
318
+ uploadArea.addEventListener('click', () => fileInput.click());
319
+
320
+ uploadArea.addEventListener('dragover', (e) => {
321
+ e.preventDefault();
322
+ uploadArea.classList.add('dragover');
323
+ });
324
+
325
+ uploadArea.addEventListener('dragleave', () => {
326
+ uploadArea.classList.remove('dragover');
327
+ });
328
+
329
+ uploadArea.addEventListener('drop', (e) => {
330
+ e.preventDefault();
331
+ uploadArea.classList.remove('dragover');
332
+ const file = e.dataTransfer.files[0];
333
+ if (file && file.type.startsWith('audio/')) {
334
+ handleFile(file);
335
+ } else {
336
+ showError('Veuillez déposer un fichier audio valide');
337
+ }
338
+ });
339
+
340
+ fileInput.addEventListener('change', (e) => {
341
+ if (e.target.files.length > 0) {
342
+ handleFile(e.target.files[0]);
343
+ }
344
+ });
345
+
346
+ function handleFile(file) {
347
+ selectedFile = file;
348
+ fileInfo.innerHTML = `
349
+ <strong>📄 Fichier sélectionné :</strong><br>
350
+ ${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)
351
+ `;
352
+ fileInfo.style.display = 'block';
353
+ transcribeBtn.disabled = false;
354
+ resultBox.classList.remove('show');
355
+ }
356
+
357
+ // Enregistrement audio
358
+ recordBtn.addEventListener('click', toggleRecording);
359
+
360
+ async function toggleRecording() {
361
+ if (mediaRecorder && mediaRecorder.state === 'recording') {
362
+ // Arrêter l'enregistrement
363
+ mediaRecorder.stop();
364
+ clearInterval(timerInterval);
365
+ recordTimer.textContent = '';
366
+ } else {
367
+ // Démarrer l'enregistrement
368
+ try {
369
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
370
+ mediaRecorder = new MediaRecorder(stream);
371
+ audioChunks = [];
372
+
373
+ mediaRecorder.ondataavailable = (e) => {
374
+ audioChunks.push(e.data);
375
+ };
376
+
377
+ mediaRecorder.onstop = () => {
378
+ const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
379
+ const audioFile = new File([audioBlob], 'recording.wav', { type: 'audio/wav' });
380
+ handleFile(audioFile);
381
+
382
+ // Arrêter le stream
383
+ stream.getTracks().forEach(track => track.stop());
384
+
385
+ // Reset UI
386
+ recordBtn.classList.remove('recording');
387
+ recordBtn.classList.add('inactive');
388
+ recordIcon.textContent = '🎙️';
389
+ recordText.textContent = 'Enregistrer';
390
+ };
391
+
392
+ mediaRecorder.start();
393
+ recordBtn.classList.remove('inactive');
394
+ recordBtn.classList.add('recording');
395
+ recordIcon.textContent = '⏹️';
396
+ recordText.textContent = 'Arrêter';
397
+
398
+ // Timer
399
+ recordingStartTime = Date.now();
400
+ timerInterval = setInterval(updateTimer, 1000);
401
+
402
+ } catch (err) {
403
+ showError('Impossible d\'accéder au microphone');
404
+ console.error(err);
405
+ }
406
+ }
407
+ }
408
+
409
+ function updateTimer() {
410
+ const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
411
+ const minutes = Math.floor(elapsed / 60);
412
+ const seconds = elapsed % 60;
413
+ recordTimer.textContent = `⏱️ ${minutes}:${seconds.toString().padStart(2, '0')}`;
414
+ }
415
+
416
+ // Transcription
417
+ transcribeBtn.addEventListener('click', transcribe);
418
+
419
+ async function transcribe() {
420
+ if (!selectedFile) {
421
+ showError('Aucun fichier sélectionné');
422
+ return;
423
+ }
424
+
425
+ loading.style.display = 'block';
426
+ errorDiv.style.display = 'none';
427
+ successDiv.style.display = 'none';
428
+ resultBox.classList.remove('show');
429
+ transcribeBtn.disabled = true;
430
+
431
+ const formData = new FormData();
432
+ formData.append('audio', selectedFile);
433
+ formData.append('language', selectedLanguage);
434
+
435
+ try {
436
+ const response = await fetch('/stt', {
437
+ method: 'POST',
438
+ body: formData
439
+ });
440
+
441
+ const data = await response.json();
442
+
443
+ if (data.success) {
444
+ resultText.textContent = data.transcription;
445
+ resultBox.classList.add('show');
446
+ showSuccess('✅ Transcription réussie !');
447
+ } else {
448
+ showError(data.error || 'Erreur lors de la transcription');
449
+ }
450
+ } catch (error) {
451
+ showError('Erreur de connexion au serveur');
452
+ console.error(error);
453
+ } finally {
454
+ loading.style.display = 'none';
455
+ transcribeBtn.disabled = false;
456
+ }
457
+ }
458
+
459
+ function showError(message) {
460
+ errorDiv.textContent = '❌ ' + message;
461
+ errorDiv.style.display = 'block';
462
+ setTimeout(() => {
463
+ errorDiv.style.display = 'none';
464
+ }, 5000);
465
+ }
466
+
467
+ function showSuccess(message) {
468
+ successDiv.textContent = message;
469
+ successDiv.style.display = 'block';
470
+ setTimeout(() => {
471
+ successDiv.style.display = 'none';
472
+ }, 3000);
473
+ }
474
+ </script>
475
+ </body>
476
+ </html>