Bgk Injector SqLi commited on
Commit
9df0966
·
1 Parent(s): b2297ec

Add interactive live demo page

Browse files

- New /demo endpoint with TailwindCSS interface
- Test STT with microphone recording
- Test TTS with text input and audio playback
- Test both translation directions (DYU↔FR)
- Includes example phrases
- Updated homepage with demo link

Files changed (2) hide show
  1. app.py +7 -1
  2. demo.html +353 -0
app.py CHANGED
@@ -160,8 +160,9 @@ def home():
160
  -F "text=Il va pleuvoir aujourd'hui"</pre>
161
  </div>
162
 
163
- <h2>🔗 Documentation interactive</h2>
164
  <p>
 
165
  <a href="/docs">Swagger UI</a> |
166
  <a href="/redoc">ReDoc</a>
167
  </p>
@@ -184,6 +185,11 @@ def home():
184
  </html>
185
  """
186
 
 
 
 
 
 
187
  @app.get("/health")
188
  def health_check():
189
  """Vérifie le statut de l'API et des modèles"""
 
160
  -F "text=Il va pleuvoir aujourd'hui"</pre>
161
  </div>
162
 
163
+ <h2>🔗 Liens utiles</h2>
164
  <p>
165
+ <a href="/demo" style="color: #3b82f6; font-weight: bold;">🎯 Demo Live</a> |
166
  <a href="/docs">Swagger UI</a> |
167
  <a href="/redoc">ReDoc</a>
168
  </p>
 
185
  </html>
186
  """
187
 
188
+ @app.get("/demo", response_class=HTMLResponse)
189
+ def demo_page():
190
+ """Page de démonstration interactive"""
191
+ return Path("demo.html").read_text(encoding="utf-8")
192
+
193
  @app.get("/health")
194
  def health_check():
195
  """Vérifie le statut de l'API et des modèles"""
demo.html ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr" class="h-full">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Wami - Demo Live</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ </head>
9
+ <body class="h-full bg-gradient-to-br from-slate-900 via-blue-900 to-slate-900">
10
+ <div class="min-h-full">
11
+ <!-- Header -->
12
+ <header class="bg-slate-800/50 backdrop-blur-sm border-b border-slate-700">
13
+ <div class="max-w-7xl mx-auto px-4 py-6 sm:px-6 lg:px-8">
14
+ <div class="flex items-center justify-between">
15
+ <div>
16
+ <h1 class="text-3xl font-bold text-white">🎙️ Wami - Demo Live</h1>
17
+ <p class="text-slate-300 mt-1">API Dioula STT, TTS & Traduction</p>
18
+ </div>
19
+ <a href="/docs" target="_blank" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition">
20
+ 📖 API Docs
21
+ </a>
22
+ </div>
23
+ </div>
24
+ </header>
25
+
26
+ <!-- Main Content -->
27
+ <main class="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8">
28
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
29
+
30
+ <!-- STT Card -->
31
+ <div class="bg-slate-800/50 backdrop-blur-sm rounded-xl shadow-xl p-6 border border-slate-700">
32
+ <h2 class="text-2xl font-bold text-white mb-4 flex items-center">
33
+ <span class="text-3xl mr-2">🎤</span> Speech-to-Text
34
+ </h2>
35
+ <p class="text-slate-300 mb-4">Enregistrez votre voix en Dioula</p>
36
+
37
+ <div class="space-y-4">
38
+ <button id="sttBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-4 px-6 rounded-lg transition transform hover:scale-105 flex items-center justify-center gap-2">
39
+ <span id="sttBtnIcon">🎙️</span>
40
+ <span id="sttBtnText">Cliquer pour enregistrer</span>
41
+ </button>
42
+
43
+ <div id="sttStatus" class="text-sm text-slate-400 text-center min-h-6"></div>
44
+
45
+ <div id="sttResult" class="bg-slate-900/50 rounded-lg p-4 min-h-24 text-white hidden">
46
+ <div class="text-sm text-slate-400 mb-2">Transcription :</div>
47
+ <div id="sttText" class="text-lg"></div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <!-- TTS Card -->
53
+ <div class="bg-slate-800/50 backdrop-blur-sm rounded-xl shadow-xl p-6 border border-slate-700">
54
+ <h2 class="text-2xl font-bold text-white mb-4 flex items-center">
55
+ <span class="text-3xl mr-2">🔊</span> Text-to-Speech
56
+ </h2>
57
+ <p class="text-slate-300 mb-4">Générez un audio en Dioula</p>
58
+
59
+ <div class="space-y-4">
60
+ <textarea id="ttsInput" class="w-full bg-slate-900/50 text-white rounded-lg p-4 border border-slate-600 focus:border-blue-500 focus:ring-2 focus:ring-blue-500 transition" rows="3" placeholder="Tapez du texte en Dioula..."></textarea>
61
+
62
+ <button id="ttsBtn" class="w-full bg-green-600 hover:bg-green-700 text-white font-semibold py-3 px-6 rounded-lg transition transform hover:scale-105">
63
+ 🔊 Générer la voix
64
+ </button>
65
+
66
+ <div id="ttsStatus" class="text-sm text-slate-400 text-center min-h-6"></div>
67
+
68
+ <audio id="ttsAudio" controls class="w-full rounded-lg hidden"></audio>
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Translation DYU→FR Card -->
73
+ <div class="bg-slate-800/50 backdrop-blur-sm rounded-xl shadow-xl p-6 border border-slate-700">
74
+ <h2 class="text-2xl font-bold text-white mb-4 flex items-center">
75
+ <span class="text-3xl mr-2">🇲🇱</span> Dioula → Français
76
+ </h2>
77
+ <p class="text-slate-300 mb-4">Traduisez du Dioula vers le Français</p>
78
+
79
+ <div class="space-y-4">
80
+ <textarea id="dyuInput" class="w-full bg-slate-900/50 text-white rounded-lg p-4 border border-slate-600 focus:border-purple-500 focus:ring-2 focus:ring-purple-500 transition" rows="3" placeholder="Texte en Dioula..."></textarea>
81
+
82
+ <button id="dyuBtn" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-semibold py-3 px-6 rounded-lg transition transform hover:scale-105">
83
+ → Traduire en Français
84
+ </button>
85
+
86
+ <div id="dyuStatus" class="text-sm text-slate-400 text-center min-h-6"></div>
87
+
88
+ <div id="dyuResult" class="bg-slate-900/50 rounded-lg p-4 min-h-20 text-white hidden">
89
+ <div class="text-sm text-slate-400 mb-2">🇫🇷 Traduction française :</div>
90
+ <div id="dyuText" class="text-lg"></div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+
95
+ <!-- Translation FR→DYU Card -->
96
+ <div class="bg-slate-800/50 backdrop-blur-sm rounded-xl shadow-xl p-6 border border-slate-700">
97
+ <h2 class="text-2xl font-bold text-white mb-4 flex items-center">
98
+ <span class="text-3xl mr-2">🇫🇷</span> Français → Dioula
99
+ </h2>
100
+ <p class="text-slate-300 mb-4">Traduisez du Français vers le Dioula</p>
101
+
102
+ <div class="space-y-4">
103
+ <textarea id="frInput" class="w-full bg-slate-900/50 text-white rounded-lg p-4 border border-slate-600 focus:border-orange-500 focus:ring-2 focus:ring-orange-500 transition" rows="3" placeholder="Texte en Français..."></textarea>
104
+
105
+ <button id="frBtn" class="w-full bg-orange-600 hover:bg-orange-700 text-white font-semibold py-3 px-6 rounded-lg transition transform hover:scale-105">
106
+ → Traduire en Dioula
107
+ </button>
108
+
109
+ <div id="frStatus" class="text-sm text-slate-400 text-center min-h-6"></div>
110
+
111
+ <div id="frResult" class="bg-slate-900/50 rounded-lg p-4 min-h-20 text-white hidden">
112
+ <div class="text-sm text-slate-400 mb-2">🇲🇱 Traduction dioula :</div>
113
+ <div id="frText" class="text-lg"></div>
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ </div>
119
+
120
+ <!-- Examples Section -->
121
+ <div class="mt-8 bg-slate-800/50 backdrop-blur-sm rounded-xl shadow-xl p-6 border border-slate-700">
122
+ <h3 class="text-xl font-bold text-white mb-4">💡 Exemples de phrases</h3>
123
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
124
+ <div class="bg-slate-900/50 p-4 rounded-lg">
125
+ <div class="text-sm text-slate-400 mb-1">Dioula:</div>
126
+ <div class="text-white">Sanji bɛna kɛ bi</div>
127
+ <div class="text-sm text-green-400 mt-2">→ Il pleuvra demain</div>
128
+ </div>
129
+ <div class="bg-slate-900/50 p-4 rounded-lg">
130
+ <div class="text-sm text-slate-400 mb-1">Dioula:</div>
131
+ <div class="text-white">Aw ka séné ninnge ma</div>
132
+ <div class="text-sm text-green-400 mt-2">→ Protégez vos récoltes</div>
133
+ </div>
134
+ <div class="bg-slate-900/50 p-4 rounded-lg">
135
+ <div class="text-sm text-slate-400 mb-1">Français:</div>
136
+ <div class="text-white">Bonjour, comment allez-vous?</div>
137
+ <div class="text-sm text-green-400 mt-2">→ I ni sɔgɔma, i ka kɛnɛya?</div>
138
+ </div>
139
+ <div class="bg-slate-900/50 p-4 rounded-lg">
140
+ <div class="text-sm text-slate-400 mb-1">Français:</div>
141
+ <div class="text-white">Le temps est beau aujourd'hui</div>
142
+ <div class="text-sm text-green-400 mt-2">→ Tile ka di bi</div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </main>
147
+ </div>
148
+
149
+ <script>
150
+ // ========== STT ==========
151
+ const sttBtn = document.getElementById('sttBtn');
152
+ const sttBtnIcon = document.getElementById('sttBtnIcon');
153
+ const sttBtnText = document.getElementById('sttBtnText');
154
+ const sttStatus = document.getElementById('sttStatus');
155
+ const sttResult = document.getElementById('sttResult');
156
+ const sttText = document.getElementById('sttText');
157
+
158
+ let mediaRecorder = null;
159
+ let audioChunks = [];
160
+ let isRecording = false;
161
+
162
+ sttBtn.addEventListener('click', async () => {
163
+ if (!isRecording) {
164
+ try {
165
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
166
+ mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
167
+ audioChunks = [];
168
+
169
+ mediaRecorder.ondataavailable = e => {
170
+ if (e.data.size > 0) audioChunks.push(e.data);
171
+ };
172
+
173
+ mediaRecorder.onstop = async () => {
174
+ stream.getTracks().forEach(t => t.stop());
175
+ const blob = new Blob(audioChunks, { type: 'audio/webm' });
176
+ await sendAudioToSTT(blob);
177
+ };
178
+
179
+ mediaRecorder.start();
180
+ isRecording = true;
181
+ sttBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
182
+ sttBtn.classList.add('bg-red-600', 'hover:bg-red-700', 'animate-pulse');
183
+ sttBtnIcon.textContent = '🔴';
184
+ sttBtnText.textContent = 'Enregistrement... Cliquez pour arrêter';
185
+ } catch (err) {
186
+ sttStatus.textContent = '❌ Erreur : accès micro refusé';
187
+ sttStatus.classList.add('text-red-400');
188
+ }
189
+ } else {
190
+ mediaRecorder.stop();
191
+ isRecording = false;
192
+ sttBtn.classList.remove('bg-red-600', 'hover:bg-red-700', 'animate-pulse');
193
+ sttBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
194
+ sttBtnIcon.textContent = '🎙️';
195
+ sttBtnText.textContent = 'Cliquer pour enregistrer';
196
+ }
197
+ });
198
+
199
+ async function sendAudioToSTT(blob) {
200
+ sttStatus.textContent = '⏳ Transcription en cours...';
201
+ sttStatus.classList.remove('text-red-400');
202
+ sttStatus.classList.add('text-blue-400');
203
+ sttResult.classList.add('hidden');
204
+
205
+ const formData = new FormData();
206
+ formData.append('audio', blob, 'recording.wav');
207
+
208
+ try {
209
+ const res = await fetch('/api/stt', { method: 'POST', body: formData });
210
+ const data = await res.json();
211
+
212
+ sttText.textContent = data.transcription || 'Aucun résultat';
213
+ sttResult.classList.remove('hidden');
214
+ sttStatus.textContent = '✅ Transcription terminée';
215
+ sttStatus.classList.remove('text-blue-400');
216
+ sttStatus.classList.add('text-green-400');
217
+ } catch (err) {
218
+ sttStatus.textContent = '❌ Erreur : ' + err.message;
219
+ sttStatus.classList.remove('text-blue-400');
220
+ sttStatus.classList.add('text-red-400');
221
+ }
222
+ }
223
+
224
+ // ========== TTS ==========
225
+ const ttsBtn = document.getElementById('ttsBtn');
226
+ const ttsInput = document.getElementById('ttsInput');
227
+ const ttsStatus = document.getElementById('ttsStatus');
228
+ const ttsAudio = document.getElementById('ttsAudio');
229
+
230
+ ttsBtn.addEventListener('click', async () => {
231
+ const text = ttsInput.value.trim();
232
+ if (!text) {
233
+ ttsStatus.textContent = '⚠️ Veuillez entrer du texte';
234
+ ttsStatus.classList.add('text-yellow-400');
235
+ return;
236
+ }
237
+
238
+ ttsBtn.disabled = true;
239
+ ttsStatus.textContent = '⏳ Génération en cours...';
240
+ ttsStatus.classList.remove('text-yellow-400', 'text-red-400');
241
+ ttsStatus.classList.add('text-blue-400');
242
+ ttsAudio.classList.add('hidden');
243
+
244
+ const formData = new FormData();
245
+ formData.append('text', text);
246
+
247
+ try {
248
+ const res = await fetch('/api/tts', { method: 'POST', body: formData });
249
+ const blob = await res.blob();
250
+ const url = URL.createObjectURL(blob);
251
+
252
+ ttsAudio.src = url;
253
+ ttsAudio.classList.remove('hidden');
254
+ ttsAudio.play();
255
+
256
+ ttsStatus.textContent = '✅ Audio généré';
257
+ ttsStatus.classList.remove('text-blue-400');
258
+ ttsStatus.classList.add('text-green-400');
259
+ } catch (err) {
260
+ ttsStatus.textContent = '❌ Erreur : ' + err.message;
261
+ ttsStatus.classList.remove('text-blue-400');
262
+ ttsStatus.classList.add('text-red-400');
263
+ } finally {
264
+ ttsBtn.disabled = false;
265
+ }
266
+ });
267
+
268
+ // ========== Translation DYU→FR ==========
269
+ const dyuBtn = document.getElementById('dyuBtn');
270
+ const dyuInput = document.getElementById('dyuInput');
271
+ const dyuStatus = document.getElementById('dyuStatus');
272
+ const dyuResult = document.getElementById('dyuResult');
273
+ const dyuText = document.getElementById('dyuText');
274
+
275
+ dyuBtn.addEventListener('click', async () => {
276
+ const text = dyuInput.value.trim();
277
+ if (!text) {
278
+ dyuStatus.textContent = '⚠️ Veuillez entrer du texte';
279
+ dyuStatus.classList.add('text-yellow-400');
280
+ return;
281
+ }
282
+
283
+ dyuBtn.disabled = true;
284
+ dyuStatus.textContent = '⏳ Traduction en cours...';
285
+ dyuStatus.classList.remove('text-yellow-400', 'text-red-400');
286
+ dyuStatus.classList.add('text-blue-400');
287
+ dyuResult.classList.add('hidden');
288
+
289
+ const formData = new FormData();
290
+ formData.append('text', text);
291
+
292
+ try {
293
+ const res = await fetch('/api/translate/dyu-fr', { method: 'POST', body: formData });
294
+ const data = await res.json();
295
+
296
+ dyuText.textContent = data.texte_traduit;
297
+ dyuResult.classList.remove('hidden');
298
+ dyuStatus.textContent = '✅ Traduction terminée';
299
+ dyuStatus.classList.remove('text-blue-400');
300
+ dyuStatus.classList.add('text-green-400');
301
+ } catch (err) {
302
+ dyuStatus.textContent = '❌ Erreur : ' + err.message;
303
+ dyuStatus.classList.remove('text-blue-400');
304
+ dyuStatus.classList.add('text-red-400');
305
+ } finally {
306
+ dyuBtn.disabled = false;
307
+ }
308
+ });
309
+
310
+ // ========== Translation FR→DYU ==========
311
+ const frBtn = document.getElementById('frBtn');
312
+ const frInput = document.getElementById('frInput');
313
+ const frStatus = document.getElementById('frStatus');
314
+ const frResult = document.getElementById('frResult');
315
+ const frText = document.getElementById('frText');
316
+
317
+ frBtn.addEventListener('click', async () => {
318
+ const text = frInput.value.trim();
319
+ if (!text) {
320
+ frStatus.textContent = '⚠️ Veuillez entrer du texte';
321
+ frStatus.classList.add('text-yellow-400');
322
+ return;
323
+ }
324
+
325
+ frBtn.disabled = true;
326
+ frStatus.textContent = '⏳ Traduction en cours...';
327
+ frStatus.classList.remove('text-yellow-400', 'text-red-400');
328
+ frStatus.classList.add('text-blue-400');
329
+ frResult.classList.add('hidden');
330
+
331
+ const formData = new FormData();
332
+ formData.append('text', text);
333
+
334
+ try {
335
+ const res = await fetch('/api/translate/fr-dyu', { method: 'POST', body: formData });
336
+ const data = await res.json();
337
+
338
+ frText.textContent = data.texte_traduit;
339
+ frResult.classList.remove('hidden');
340
+ frStatus.textContent = '✅ Traduction terminée';
341
+ frStatus.classList.remove('text-blue-400');
342
+ frStatus.classList.add('text-green-400');
343
+ } catch (err) {
344
+ frStatus.textContent = '❌ Erreur : ' + err.message;
345
+ frStatus.classList.remove('text-blue-400');
346
+ frStatus.classList.add('text-red-400');
347
+ } finally {
348
+ frBtn.disabled = false;
349
+ }
350
+ });
351
+ </script>
352
+ </body>
353
+ </html>