Trabis commited on
Commit
1ccf000
·
verified ·
1 Parent(s): a53bbe1

Update static/script.js

Browse files
Files changed (1) hide show
  1. static/script.js +156 -110
static/script.js CHANGED
@@ -1,15 +1,15 @@
1
- const chatbox = document.getElementById('chatbox');
2
- const messageInput = document.getElementById('message-input');
3
- const sendButton = document.getElementById('send-button');
4
-
5
- // --- addMessage function (inchangée) ---
6
- function addMessage(role, text) {
7
- const messageDiv = document.createElement('div');
8
- messageDiv.classList.add('message', role === 'user' ? 'user-message' : 'assistant-message');
9
- messageDiv.innerHTML = text.replace(/\n/g, '<br>');
10
- chatbox.appendChild(messageDiv);
11
- chatbox.scrollTop = chatbox.scrollHeight;
12
- }
13
 
14
  // // --- processSSEBuffer function (inchangée - version Méthode 1) ---
15
  // function processSSEBuffer(buffer, targetDiv, currentResponseAccumulated) {
@@ -171,59 +171,85 @@ function addMessage(role, text) {
171
 
172
  // console.log("Chat script loaded and listeners attached."); // LOG AJOUTÉ
173
 
174
- // --- processSSEBuffer (simplifié pour retourner juste la NOUVELLE partie de texte) ---
175
- function processSSEBuffer(buffer) {
176
- console.log("Processing buffer:", buffer);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  let messages = buffer.split('\n\n');
178
- let incomplete = buffer.endsWith('\n\n') ? '' : messages.pop();
179
- let responseParts = []; // Stocke les parties de données reçues dans ce buffer
180
- let streamEnded = false;
181
- let streamError = null;
182
 
183
  messages.forEach(message => {
184
  if (!message.trim()) return;
185
- let eventType = 'message';
 
186
  let dataLines = [];
 
187
  message.split('\n').forEach(line => {
188
  if (line.startsWith('event: ')) {
189
  eventType = line.substring(7).trim();
190
  } else if (line.startsWith('data: ')) {
191
- dataLines.push(line.substring(6));
192
  }
 
193
  });
194
- let data = dataLines.join('\n');
195
- console.log(`--> SSE Event Parsed: ${eventType}, Data: ${data.substring(0, 100)}...`);
196
-
197
- if (eventType === 'message') {
198
- responseParts.push(data); // Ajoute la donnée reçue
199
- } else if (eventType === 'end') {
200
- console.log("Fin de stream détectée (event: end)");
201
- streamEnded = true;
202
- } else if (eventType === 'error') {
203
- console.error("Erreur SSE du serveur:", data);
204
- streamError = data; // Stocke le message d'erreur
205
  }
206
  });
207
 
208
- // Retourne les nouvelles parties de texte, le statut de fin/erreur, et le buffer restant
209
- return {
210
- newParts: responseParts.join(''), // Concatène toutes les parties reçues dans cet appel
211
- ended: streamEnded,
212
- error: streamError,
213
- incomplete: incomplete
214
- };
215
  }
216
 
217
- // --- askQuestion (modifié pour gérer le rendu) ---
 
 
 
 
 
 
 
218
  async function askQuestion() {
219
  console.log("askQuestion called");
220
  const question = messageInput.value.trim();
221
- // ... (vérification question vide, ajout message user, désactivation bouton) ...
 
 
 
222
 
223
- // Crée un placeholder
 
 
 
 
 
 
224
  const assistantMessageDiv = document.createElement('div');
225
  assistantMessageDiv.classList.add('message', 'assistant-message');
226
- // On pourrait utiliser textContent ici pour l'indicateur, plus sûr
227
  const thinkingSpan = document.createElement('span');
228
  thinkingSpan.className = 'thinking';
229
  thinkingSpan.textContent = '...';
@@ -232,98 +258,118 @@ async function askQuestion() {
232
  chatbox.scrollTop = chatbox.scrollHeight;
233
  console.log("Assistant placeholder added.");
234
 
235
- let buffer = '';
236
  const decoder = new TextDecoder();
237
- let isFirstChunk = true; // Pour remplacer le "..."
238
- let finalHTMLContent = ""; // Accumule le contenu HTML final
239
 
240
  try {
241
  console.log("Initiating fetch to /ask");
242
- const response = await fetch('/ask', { /* ... headers, body ... */ });
 
 
 
 
243
  console.log(`Fetch response received. Status: ${response.status}, OK: ${response.ok}`);
244
 
245
  if (!response.ok || !response.body) {
246
- // ... (gestion erreur fetch) ...
247
- throw new Error(`Erreur serveur: ${response.status} ${response.statusText}.`);
248
  }
249
 
250
  const reader = response.body.getReader();
251
  console.log("ReadableStream reader obtained. Starting read loop.");
252
 
 
253
  while (true) {
254
  const { done, value } = await reader.read();
255
 
256
- if (done) {
257
- console.log("Reader finished (done=true). Processing remaining buffer.");
258
- const result = processSSEBuffer(buffer); // Traite le reste
259
- if (result.newParts) {
260
- // Convertit la dernière partie en HTML et l'ajoute
261
- finalHTMLContent += result.newParts.replace(/\n/g, '<br>');
262
- }
263
- if (result.error) {
264
- finalHTMLContent += `<br><strong style="color: #ffaaaa;">خطأ من الخادم: ${result.error}</strong>`;
265
- }
266
- // Met à jour l'UI une dernière fois
267
- assistantMessageDiv.innerHTML = finalHTMLContent;
268
- chatbox.scrollTop = chatbox.scrollHeight; // Scroll final
269
- console.log("Final HTML content set.");
270
- break; // Sort de la boucle
271
- }
272
 
273
- buffer += decoder.decode(value, { stream: true });
274
- // console.log("Received chunk, buffer:", buffer); // Peut être TROP verbeux
275
-
276
- // Traite le buffer pour obtenir les messages SSE complets
277
- const result = processSSEBuffer(buffer);
278
- buffer = result.incomplete; // Garde la partie incomplète
279
-
280
- // Si on a reçu de nouvelles données de texte
281
- if (result.newParts) {
282
- if (isFirstChunk) {
283
- // Remplace le "..." par le premier contenu reçu
284
- finalHTMLContent = result.newParts.replace(/\n/g, '<br>');
285
- assistantMessageDiv.innerHTML = finalHTMLContent; // Première mise à jour
286
- isFirstChunk = false; // Ne plus effacer le "..."
287
- } else {
288
- // Ajoute les nouvelles parties à la fin
289
- finalHTMLContent += result.newParts.replace(/\n/g, '<br>');
290
- assistantMessageDiv.innerHTML = finalHTMLContent; // Met à jour l'UI
291
- }
292
- chatbox.scrollTop = chatbox.scrollHeight; // Scroll pendant le stream
293
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
 
295
- // Si on a reçu une erreur spécifique
296
- if (result.error) {
297
- finalHTMLContent += `<br><strong style="color: #ffaaaa;">خطأ من الخادم: ${result.error}</strong>`;
298
- assistantMessageDiv.innerHTML = finalHTMLContent; // Affiche l'erreur
299
- chatbox.scrollTop = chatbox.scrollHeight;
300
- // On pourrait vouloir arrêter le stream ici, mais on laisse le serveur décider avec 'done' ou 'event: end'
 
 
 
 
 
301
  }
302
-
303
- // Si le serveur signale la fin explicitement via event: end
304
- if (result.ended) {
305
- console.log("Server sent end event, exiting loop.");
306
- // Le 'break' se fera au prochain 'done=true' ou on peut forcer ici:
307
- // break; // Décommenter si nécessaire
308
- }
309
- }
310
 
311
  } catch (error) {
312
  console.error('Erreur dans askQuestion (fetch ou stream):', error);
313
- if (assistantMessageDiv) {
314
- assistantMessageDiv.innerHTML = finalHTMLContent + `<br><br><strong style="color: #ffaaaa;">حدث خطأ في الاتصال: ${error.message}</strong>`; // Ajoute l'erreur JS/Network
315
- chatbox.scrollTop = chatbox.scrollHeight;
316
- } else { addMessage('assistant', `<strong style="color: #ffaaaa;">حدث خطأ في الاتصال: ${error.message}</strong>`); }
 
 
 
 
 
 
317
  } finally {
318
- console.log("Executing finally block: Enabling button.");
319
- // On ne réactive le bouton que si le stream n'a pas été explicitement terminé par event: end
320
- // (car processSSEBuffer peut l'avoir déjà réactivé)
321
- // Solution simple: toujours réactiver ici, même si redondant.
322
  sendButton.disabled = false;
323
  }
324
  }
325
 
 
326
  // --- Écouteurs d'événements (inchangés) ---
327
- // ...
 
 
 
 
 
 
 
 
328
 
329
- console.log("Chat script loaded and listeners attached.");
 
1
+ // const chatbox = document.getElementById('chatbox');
2
+ // const messageInput = document.getElementById('message-input');
3
+ // const sendButton = document.getElementById('send-button');
4
+
5
+ // // --- addMessage function (inchangée) ---
6
+ // function addMessage(role, text) {
7
+ // const messageDiv = document.createElement('div');
8
+ // messageDiv.classList.add('message', role === 'user' ? 'user-message' : 'assistant-message');
9
+ // messageDiv.innerHTML = text.replace(/\n/g, '<br>');
10
+ // chatbox.appendChild(messageDiv);
11
+ // chatbox.scrollTop = chatbox.scrollHeight;
12
+ // }
13
 
14
  // // --- processSSEBuffer function (inchangée - version Méthode 1) ---
15
  // function processSSEBuffer(buffer, targetDiv, currentResponseAccumulated) {
 
171
 
172
  // console.log("Chat script loaded and listeners attached."); // LOG AJOUTÉ
173
 
174
+
175
+
176
+ const chatbox = document.getElementById('chatbox');
177
+ const messageInput = document.getElementById('message-input');
178
+ const sendButton = document.getElementById('send-button');
179
+
180
+ // --- addMessage function (inchangée) ---
181
+ function addMessage(role, text) {
182
+ const messageDiv = document.createElement('div');
183
+ messageDiv.classList.add('message', role === 'user' ? 'user-message' : 'assistant-message');
184
+ // Remplacer les sauts de ligne pour l'affichage HTML
185
+ messageDiv.innerHTML = text.replace(/\n/g, '<br>');
186
+ chatbox.appendChild(messageDiv);
187
+ chatbox.scrollTop = chatbox.scrollHeight;
188
+ }
189
+
190
+ // --- Fonction pour parser les événements SSE (simplifiée) ---
191
+ // Retourne les événements trouvés et le buffer restant
192
+ function parseSSEEvents(buffer) {
193
+ const events = [];
194
  let messages = buffer.split('\n\n');
195
+ let incomplete = buffer.endsWith('\n\n') ? '' : messages.pop(); // Garde la partie incomplète
 
 
 
196
 
197
  messages.forEach(message => {
198
  if (!message.trim()) return;
199
+ let eventType = 'message'; // Type par défaut
200
+ let data = ''; // Données associées à l'événement (peut être multi-lignes)
201
  let dataLines = [];
202
+
203
  message.split('\n').forEach(line => {
204
  if (line.startsWith('event: ')) {
205
  eventType = line.substring(7).trim();
206
  } else if (line.startsWith('data: ')) {
207
+ dataLines.push(line.substring(6)); // Stocke les lignes de données
208
  }
209
+ // Ignorer les lignes id: et : (commentaires)
210
  });
211
+
212
+ data = dataLines.join('\n'); // Reconstitue les données multi-lignes
213
+
214
+ // On ne stocke QUE les événements spéciaux ici (pas 'message')
215
+ if (eventType !== 'message') {
216
+ console.log(`--> Parsed Special SSE Event: ${eventType}, Data: ${data.substring(0, 100)}...`);
217
+ events.push({ type: eventType, data: data });
218
+ } else {
219
+ // On pourrait logger les messages data ici si besoin de débogage
220
+ // console.log(`--> Parsed SSE Data Message (ignored by event processor): ${data.substring(0,100)}...`);
 
221
  }
222
  });
223
 
224
+ return { events: events, incomplete: incomplete };
 
 
 
 
 
 
225
  }
226
 
227
+
228
+ // --- adjustTextareaHeight function (inchangée) ---
229
+ function adjustTextareaHeight() {
230
+ messageInput.style.height = 'auto'; // Réinitialise pour obtenir scrollHeight correct
231
+ messageInput.style.height = (messageInput.scrollHeight) + 'px'; // Ajuste à la hauteur du contenu
232
+ }
233
+
234
+ // --- askQuestion function (MODIFIÉE pour le streaming UI direct) ---
235
  async function askQuestion() {
236
  console.log("askQuestion called");
237
  const question = messageInput.value.trim();
238
+ if (!question) {
239
+ console.log("Question is empty, aborting.");
240
+ return;
241
+ }
242
 
243
+ addMessage('user', question);
244
+ messageInput.value = '';
245
+ sendButton.disabled = true;
246
+ adjustTextareaHeight(); // Réajuste après vidage
247
+ console.log("User message added, button disabled.");
248
+
249
+ // Crée un placeholder pour la réponse de l'assistant
250
  const assistantMessageDiv = document.createElement('div');
251
  assistantMessageDiv.classList.add('message', 'assistant-message');
252
+ // Utiliser textContent pour l'indicateur est plus sûr
253
  const thinkingSpan = document.createElement('span');
254
  thinkingSpan.className = 'thinking';
255
  thinkingSpan.textContent = '...';
 
258
  chatbox.scrollTop = chatbox.scrollHeight;
259
  console.log("Assistant placeholder added.");
260
 
261
+ let buffer = ''; // Buffer pour détecter les événements SSE spéciaux
262
  const decoder = new TextDecoder();
263
+ let isFirstChunk = true; // Pour savoir quand remplacer le "..."
 
264
 
265
  try {
266
  console.log("Initiating fetch to /ask");
267
+ const response = await fetch('/ask', {
268
+ method: 'POST',
269
+ headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' },
270
+ body: JSON.stringify({ question: question })
271
+ });
272
  console.log(`Fetch response received. Status: ${response.status}, OK: ${response.ok}`);
273
 
274
  if (!response.ok || !response.body) {
275
+ let errorBody = "N/A"; try { errorBody = await response.text(); } catch (e) {}
276
+ throw new Error(`Erreur serveur: ${response.status} ${response.statusText}. Body: ${errorBody}`);
277
  }
278
 
279
  const reader = response.body.getReader();
280
  console.log("ReadableStream reader obtained. Starting read loop.");
281
 
282
+ // Boucle principale de lecture du stream
283
  while (true) {
284
  const { done, value } = await reader.read();
285
 
286
+ // Traiter le chunk reçu SI value n'est pas vide
287
+ if (value) {
288
+ const rawChunk = decoder.decode(value, { stream: true });
289
+ console.log("Received raw chunk:", rawChunk); // Log pour débogage
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
+ // --- Mise à jour UI Immédiate ---
292
+ // Nettoyer l'indicateur "..." au premier chunk reçu
293
+ if (isFirstChunk && assistantMessageDiv.querySelector('.thinking')) {
294
+ assistantMessageDiv.innerHTML = ''; // Vide le contenu (enlève le span '...')
295
+ isFirstChunk = false;
296
+ }
297
+ // Ajoute le nouveau morceau de texte (converti en HTML)
298
+ // Note: innerHTML peut être lent si les chunks sont très petits et nombreux.
299
+ // textContent serait plus rapide mais ne gère pas <br>.
300
+ assistantMessageDiv.innerHTML += rawChunk.replace(/\n/g, '<br>');
301
+ chatbox.scrollTop = chatbox.scrollHeight; // Scroll pendant l'ajout
302
+ // --- Fin Mise à jour UI Immédiate ---
303
+
304
+ // Ajoute le chunk au buffer pour détecter les événements spéciaux
305
+ buffer += rawChunk;
306
+
307
+ // --- Traitement des Événements Spéciaux ---
308
+ const eventResult = parseSSEEvents(buffer);
309
+ buffer = eventResult.incomplete; // Garde la partie non traitée du buffer
310
+
311
+ for (const event of eventResult.events) {
312
+ if (event.type === 'end') {
313
+ console.log("Fin de stream détectée (event: end)");
314
+ sendButton.disabled = false; // Réactive le bouton
315
+ // On pourrait vouloir sortir de la boucle ici si 'done' tarde
316
+ // mais il vaut mieux attendre 'done = true' normalement.
317
+ } else if (event.type === 'error') {
318
+ console.error("Erreur signalée par le serveur (event: error):", event.data);
319
+ // Ajoute l'erreur à la fin
320
+ assistantMessageDiv.innerHTML += `<br><strong style="color: #ffaaaa;">خطأ من الخادم: ${event.data.replace(/\n/g, '<br>')}</strong>`;
321
+ chatbox.scrollTop = chatbox.scrollHeight;
322
+ sendButton.disabled = false;
323
+ // Considérer de sortir de la boucle ici ?
324
+ }
325
+ // Gérer d'autres événements si nécessaire
326
+ }
327
+ // --- Fin Traitement Événements ---
328
+ } // Fin if(value)
329
 
330
+ // Si le stream est terminé (signalé par le reader)
331
+ if (done) {
332
+ console.log("Reader finished (done=true). Stream ended.");
333
+ // Traiter une dernière fois le buffer pour les événements ?
334
+ // Normalement pas nécessaire si les événements finissent par \n\n
335
+ if (buffer.trim()){
336
+ console.log("Processing final buffer content:", buffer);
337
+ const finalEventResult = parseSSEEvents(buffer);
338
+ for (const event of finalEventResult.events) { /* Gérer comme ci-dessus */ }
339
+ }
340
+ break; // Sortir de la boucle while
341
  }
342
+ } // Fin while(true)
 
 
 
 
 
 
 
343
 
344
  } catch (error) {
345
  console.error('Erreur dans askQuestion (fetch ou stream):', error);
346
+ if (assistantMessageDiv) {
347
+ // Supprimer l'indicateur "..." s'il est encore
348
+ const thinkingSpan = assistantMessageDiv.querySelector('.thinking');
349
+ if (thinkingSpan) thinkingSpan.remove();
350
+ // Afficher l'erreur JS/Network
351
+ assistantMessageDiv.innerHTML += `<br><br><strong style="color: #ffaaaa;">حدث خطأ في الاتصال: ${error.message}</strong>`;
352
+ chatbox.scrollTop = chatbox.scrollHeight;
353
+ } else {
354
+ addMessage('assistant', `<strong style="color: #ffaaaa;">حدث خطأ في الاتصال: ${error.message}</strong>`);
355
+ }
356
  } finally {
357
+ console.log("Executing finally block.");
358
+ // Le bouton peut déjà avoir été réactivé par event: end/error, mais sécurité :
 
 
359
  sendButton.disabled = false;
360
  }
361
  }
362
 
363
+
364
  // --- Écouteurs d'événements (inchangés) ---
365
+ sendButton.addEventListener('click', askQuestion);
366
+ messageInput.addEventListener('keypress', function(e) {
367
+ if (e.key === 'Enter' && !e.shiftKey) {
368
+ e.preventDefault();
369
+ askQuestion();
370
+ }
371
+ });
372
+ messageInput.addEventListener('input', adjustTextareaHeight);
373
+ adjustTextareaHeight(); // Appel initial
374
 
375
+ console.log("Chat script loaded and listeners attached. Streaming UI enabled.");