Bgk Injector SqLi commited on
Commit
d0b5038
·
1 Parent(s): 5e76f00

Add WebSocket demo page

Browse files

- New /ws-demo endpoint for WebSocket demonstration
- Interactive UI for testing WebSocket pipeline
- Real-time progress updates visualization
- Audio recording and base64 encoding
- TailwindCSS interface matching demo page
- Updated homepage with WebSocket demo link

Files changed (2) hide show
  1. app.py +6 -0
  2. websocket_example.html +262 -0
app.py CHANGED
@@ -178,6 +178,7 @@ ws.send(JSON.stringify({
178
  <h2>🔗 Liens utiles</h2>
179
  <p>
180
  <a href="/demo" style="color: #3b82f6; font-weight: bold;">🎯 Demo Live</a> |
 
181
  <a href="/docs">Swagger UI</a> |
182
  <a href="/redoc">ReDoc</a>
183
  </p>
@@ -205,6 +206,11 @@ def demo_page():
205
  """Page de démonstration interactive"""
206
  return Path("demo.html").read_text(encoding="utf-8")
207
 
 
 
 
 
 
208
  @app.get("/health")
209
  def health_check():
210
  """Vérifie le statut de l'API et des modèles"""
 
178
  <h2>🔗 Liens utiles</h2>
179
  <p>
180
  <a href="/demo" style="color: #3b82f6; font-weight: bold;">🎯 Demo Live</a> |
181
+ <a href="/ws-demo" style="color: #f59e0b; font-weight: bold;">🔄 WebSocket Demo</a> |
182
  <a href="/docs">Swagger UI</a> |
183
  <a href="/redoc">ReDoc</a>
184
  </p>
 
206
  """Page de démonstration interactive"""
207
  return Path("demo.html").read_text(encoding="utf-8")
208
 
209
+ @app.get("/ws-demo", response_class=HTMLResponse)
210
+ def websocket_demo_page():
211
+ """Page de démonstration WebSocket pipeline"""
212
+ return Path("websocket_example.html").read_text(encoding="utf-8")
213
+
214
  @app.get("/health")
215
  def health_check():
216
  """Vérifie le statut de l'API et des modèles"""
websocket_example.html ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>WebSocket Pipeline - Exemple</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ </head>
9
+ <body class="bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 min-h-screen p-8">
10
+ <div class="max-w-4xl mx-auto">
11
+ <h1 class="text-4xl font-bold text-white mb-2">🔄 WebSocket Pipeline</h1>
12
+ <p class="text-slate-300 mb-8">Pipeline temps réel : Audio → STT → Traduction</p>
13
+
14
+ <div class="grid gap-6">
15
+ <!-- Controls -->
16
+ <div class="bg-slate-800/50 backdrop-blur-sm rounded-xl p-6 border border-slate-700">
17
+ <h2 class="text-xl font-bold text-white mb-4">📡 Contrôles</h2>
18
+
19
+ <div class="space-y-4">
20
+ <div>
21
+ <label class="text-slate-300 mb-2 block">Langue cible :</label>
22
+ <select id="targetLang" class="w-full bg-slate-900 text-white rounded-lg px-4 py-2 border border-slate-600">
23
+ <option value="fr">Français</option>
24
+ <option value="dyu">Dioula</option>
25
+ </select>
26
+ </div>
27
+
28
+ <div class="flex items-center gap-2">
29
+ <input type="checkbox" id="includeTts" class="w-5 h-5">
30
+ <label for="includeTts" class="text-slate-300">Générer l'audio (TTS)</label>
31
+ </div>
32
+
33
+ <button id="recordBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-4 rounded-lg transition">
34
+ 🎙️ Enregistrer et envoyer
35
+ </button>
36
+
37
+ <button id="connectBtn" class="w-full bg-green-600 hover:bg-green-700 text-white font-semibold py-2 rounded-lg transition">
38
+ 🔌 Connecter WebSocket
39
+ </button>
40
+ </div>
41
+ </div>
42
+
43
+ <!-- Status -->
44
+ <div class="bg-slate-800/50 backdrop-blur-sm rounded-xl p-6 border border-slate-700">
45
+ <h2 class="text-xl font-bold text-white mb-4">📊 Statut</h2>
46
+ <div id="status" class="space-y-2">
47
+ <div class="text-slate-400">WebSocket: <span id="wsStatus" class="text-red-400">Déconnecté</span></div>
48
+ <div class="text-slate-400">Étape: <span id="currentStep" class="text-slate-300">-</span></div>
49
+ </div>
50
+ </div>
51
+
52
+ <!-- Progress Log -->
53
+ <div class="bg-slate-800/50 backdrop-blur-sm rounded-xl p-6 border border-slate-700">
54
+ <h2 class="text-xl font-bold text-white mb-4">📝 Journal</h2>
55
+ <div id="log" class="bg-slate-900/50 rounded-lg p-4 h-48 overflow-y-auto font-mono text-sm text-green-400"></div>
56
+ </div>
57
+
58
+ <!-- Results -->
59
+ <div class="bg-slate-800/50 backdrop-blur-sm rounded-xl p-6 border border-slate-700">
60
+ <h2 class="text-xl font-bold text-white mb-4">✨ Résultats</h2>
61
+
62
+ <div class="space-y-4">
63
+ <div id="transcriptionDiv" class="hidden">
64
+ <div class="text-sm text-slate-400 mb-1">Transcription (Dioula) :</div>
65
+ <div id="transcription" class="bg-slate-900/50 rounded-lg p-4 text-white"></div>
66
+ </div>
67
+
68
+ <div id="traductionDiv" class="hidden">
69
+ <div class="text-sm text-slate-400 mb-1">Traduction :</div>
70
+ <div id="traduction" class="bg-slate-900/50 rounded-lg p-4 text-white"></div>
71
+ </div>
72
+
73
+ <div id="audioDiv" class="hidden">
74
+ <div class="text-sm text-slate-400 mb-2">Audio généré :</div>
75
+ <audio id="audioResult" controls class="w-full"></audio>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+
82
+ <script>
83
+ let ws = null;
84
+ let mediaRecorder = null;
85
+ let audioChunks = [];
86
+
87
+ const log = document.getElementById('log');
88
+ const wsStatus = document.getElementById('wsStatus');
89
+ const currentStep = document.getElementById('currentStep');
90
+ const recordBtn = document.getElementById('recordBtn');
91
+ const connectBtn = document.getElementById('connectBtn');
92
+
93
+ function addLog(message, type = 'info') {
94
+ const time = new Date().toLocaleTimeString();
95
+ const color = type === 'error' ? 'text-red-400' : type === 'success' ? 'text-green-400' : 'text-blue-400';
96
+ log.innerHTML += `<div class="${color}">[${time}] ${message}</div>`;
97
+ log.scrollTop = log.scrollHeight;
98
+ }
99
+
100
+ // Connect WebSocket
101
+ connectBtn.addEventListener('click', () => {
102
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
103
+ const wsUrl = `${protocol}//${window.location.host}/ws/pipeline`;
104
+
105
+ ws = new WebSocket(wsUrl);
106
+
107
+ ws.onopen = () => {
108
+ wsStatus.textContent = 'Connecté';
109
+ wsStatus.className = 'text-green-400';
110
+ addLog('WebSocket connecté', 'success');
111
+ connectBtn.disabled = true;
112
+ connectBtn.textContent = '✅ Connecté';
113
+ };
114
+
115
+ ws.onmessage = (event) => {
116
+ const data = JSON.parse(event.data);
117
+ handleMessage(data);
118
+ };
119
+
120
+ ws.onerror = (error) => {
121
+ addLog('Erreur WebSocket: ' + error, 'error');
122
+ };
123
+
124
+ ws.onclose = () => {
125
+ wsStatus.textContent = 'Déconnecté';
126
+ wsStatus.className = 'text-red-400';
127
+ addLog('WebSocket déconnecté', 'error');
128
+ connectBtn.disabled = false;
129
+ connectBtn.textContent = '🔌 Reconnecter';
130
+ };
131
+ });
132
+
133
+ function handleMessage(data) {
134
+ if (data.error) {
135
+ addLog('❌ Erreur: ' + data.error, 'error');
136
+ currentStep.textContent = 'Erreur';
137
+ return;
138
+ }
139
+
140
+ if (data.status === 'processing') {
141
+ currentStep.textContent = data.step;
142
+ if (data.message) {
143
+ addLog(data.message);
144
+ }
145
+
146
+ if (data.transcription) {
147
+ document.getElementById('transcription').textContent = data.transcription;
148
+ document.getElementById('transcriptionDiv').classList.remove('hidden');
149
+ addLog('✅ Transcription: ' + data.transcription, 'success');
150
+ }
151
+
152
+ if (data.traduction) {
153
+ document.getElementById('traduction').textContent = data.traduction;
154
+ document.getElementById('traductionDiv').classList.remove('hidden');
155
+ addLog('✅ Traduction: ' + data.traduction, 'success');
156
+ }
157
+ }
158
+
159
+ if (data.status === 'done') {
160
+ currentStep.textContent = 'Terminé ✅';
161
+ addLog('✨ Pipeline terminé', 'success');
162
+
163
+ if (data.audio_base64) {
164
+ const audioBlob = base64ToBlob(data.audio_base64, 'audio/wav');
165
+ const audioUrl = URL.createObjectURL(audioBlob);
166
+ document.getElementById('audioResult').src = audioUrl;
167
+ document.getElementById('audioDiv').classList.remove('hidden');
168
+ addLog('🔊 Audio généré', 'success');
169
+ }
170
+ }
171
+ }
172
+
173
+ // Record and send
174
+ recordBtn.addEventListener('click', async () => {
175
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
176
+ alert('WebSocket non connecté. Cliquez sur "Connecter WebSocket" d\'abord.');
177
+ return;
178
+ }
179
+
180
+ if (!mediaRecorder || mediaRecorder.state === 'inactive') {
181
+ // Start recording
182
+ try {
183
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
184
+ mediaRecorder = new MediaRecorder(stream);
185
+ audioChunks = [];
186
+
187
+ mediaRecorder.ondataavailable = (e) => {
188
+ if (e.data.size > 0) audioChunks.push(e.data);
189
+ };
190
+
191
+ mediaRecorder.onstop = async () => {
192
+ stream.getTracks().forEach(t => t.stop());
193
+ const blob = new Blob(audioChunks, { type: 'audio/webm' });
194
+ await sendAudioToWebSocket(blob);
195
+ };
196
+
197
+ mediaRecorder.start();
198
+ recordBtn.textContent = '⏹️ Arrêter et envoyer';
199
+ recordBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
200
+ recordBtn.classList.add('bg-red-600', 'hover:bg-red-700', 'animate-pulse');
201
+ addLog('🎙️ Enregistrement en cours...');
202
+ } catch (err) {
203
+ addLog('❌ Erreur micro: ' + err.message, 'error');
204
+ }
205
+ } else {
206
+ // Stop recording
207
+ mediaRecorder.stop();
208
+ recordBtn.textContent = '🎙️ Enregistrer et envoyer';
209
+ recordBtn.classList.remove('bg-red-600', 'hover:bg-red-700', 'animate-pulse');
210
+ recordBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
211
+ }
212
+ });
213
+
214
+ async function sendAudioToWebSocket(blob) {
215
+ addLog('📤 Envoi de l\'audio...');
216
+
217
+ // Convert blob to base64
218
+ const base64 = await blobToBase64(blob);
219
+
220
+ const message = {
221
+ action: 'process',
222
+ audio: base64.split(',')[1], // Remove data:audio/webm;base64,
223
+ target_lang: document.getElementById('targetLang').value,
224
+ include_tts: document.getElementById('includeTts').checked
225
+ };
226
+
227
+ ws.send(JSON.stringify(message));
228
+ addLog('✅ Audio envoyé', 'success');
229
+
230
+ // Clear previous results
231
+ document.getElementById('transcriptionDiv').classList.add('hidden');
232
+ document.getElementById('traductionDiv').classList.add('hidden');
233
+ document.getElementById('audioDiv').classList.add('hidden');
234
+ currentStep.textContent = 'En traitement...';
235
+ }
236
+
237
+ function blobToBase64(blob) {
238
+ return new Promise((resolve, reject) => {
239
+ const reader = new FileReader();
240
+ reader.onloadend = () => resolve(reader.result);
241
+ reader.onerror = reject;
242
+ reader.readAsDataURL(blob);
243
+ });
244
+ }
245
+
246
+ function base64ToBlob(base64, mimeType) {
247
+ const byteCharacters = atob(base64);
248
+ const byteNumbers = new Array(byteCharacters.length);
249
+ for (let i = 0; i < byteCharacters.length; i++) {
250
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
251
+ }
252
+ const byteArray = new Uint8Array(byteNumbers);
253
+ return new Blob([byteArray], { type: mimeType });
254
+ }
255
+
256
+ // Auto-connect on load
257
+ window.addEventListener('load', () => {
258
+ addLog('Page chargée. Cliquez sur "Connecter WebSocket" pour commencer.');
259
+ });
260
+ </script>
261
+ </body>
262
+ </html>