Trabis commited on
Commit
2893f3c
·
verified ·
1 Parent(s): 1c437f6

Update static/script.js

Browse files
Files changed (1) hide show
  1. static/script.js +164 -122
static/script.js CHANGED
@@ -1,122 +1,164 @@
1
- const chatbox = document.getElementById('chatbox');
2
- const questionInput = document.getElementById('question-input');
3
- const sendButton = document.getElementById('send-button');
4
-
5
- let messageHistory = []; // Pour garder une trace simple
6
-
7
- function addMessage(role, text) {
8
- const messageDiv = document.createElement('div');
9
- messageDiv.classList.add('message', role === 'user' ? 'user-message' : 'assistant-message');
10
- // Remplace les sauts de ligne par <br> pour l'affichage HTML
11
- messageDiv.innerHTML = text.replace(/\n/g, '<br>');
12
- chatbox.appendChild(messageDiv);
13
- chatbox.scrollTop = chatbox.scrollHeight; // Auto-scroll
14
- }
15
-
16
- async function askQuestion() {
17
- const question = questionInput.value.trim();
18
- if (!question) return;
19
-
20
- addMessage('user', question);
21
- questionInput.value = '';
22
- sendButton.disabled = true; // Désactive pendant la réponse
23
-
24
- // Crée un placeholder pour la réponse de l'assistant
25
- const assistantMessageDiv = document.createElement('div');
26
- assistantMessageDiv.classList.add('message', 'assistant-message');
27
- assistantMessageDiv.innerHTML = "جارٍ التفكير..."; // Indicateur
28
- chatbox.appendChild(assistantMessageDiv);
29
- chatbox.scrollTop = chatbox.scrollHeight;
30
-
31
- let currentResponse = ""; // Pour accumuler la réponse streamée
32
-
33
- try {
34
- // Utilisation de EventSource pour gérer le streaming SSE
35
- const eventSource = new EventSource('/ask', {
36
- method: 'POST', // EventSource ne supporte que GET, on doit tricher ou utiliser fetch + ReadableStream
37
- headers: {
38
- 'Content-Type': 'application/json',
39
- },
40
- body: JSON.stringify({ question: question })
41
- });
42
-
43
- // -- Alternative avec Fetch API pour POST et streaming --
44
- const response = await fetch('/ask', {
45
- method: 'POST',
46
- headers: {
47
- 'Content-Type': 'application/json',
48
- 'Accept': 'text/event-stream' // Indique qu'on attend un stream
49
- },
50
- body: JSON.stringify({ question: question })
51
- });
52
-
53
- if (!response.ok || !response.body) {
54
- throw new Error(`Erreur serveur: ${response.statusText}`);
55
- }
56
-
57
- const reader = response.body.getReader();
58
- const decoder = new TextDecoder();
59
-
60
- while (true) {
61
- const { done, value } = await reader.read();
62
- if (done) break;
63
-
64
- const chunk = decoder.decode(value, { stream: true });
65
- // Traiter les données SSE reçues (peut contenir plusieurs messages)
66
- const lines = chunk.split('\n\n');
67
- lines.forEach(line => {
68
- if (line.startsWith('data: ')) {
69
- const data = line.substring(6).trim(); // Enlève "data: "
70
- if (data) {
71
- currentResponse += data.replace(/\\n/g,'\n'); // Reconstitue les sauts de ligne si nécessaire
72
- // Met à jour le contenu du div de l'assistant
73
- assistantMessageDiv.innerHTML = currentResponse.replace(/\n/g, '<br>');
74
- chatbox.scrollTop = chatbox.scrollHeight; // Scroll pendant le stream
75
- }
76
- }
77
- });
78
- }
79
- // --- Fin de l'alternative Fetch API ---
80
-
81
-
82
- // Gestion des événements SSE (si EventSource fonctionnait avec POST)
83
- /*
84
- eventSource.onmessage = function(event) {
85
- currentResponse += event.data + "\n"; // Ajoute les données reçues
86
- // Met à jour le contenu du div de l'assistant
87
- assistantMessageDiv.innerHTML = currentResponse.replace(/\n/g, '<br>');
88
- chatbox.scrollTop = chatbox.scrollHeight; // Scroll pendant le stream
89
- };
90
-
91
- eventSource.onerror = function(err) {
92
- console.error("Erreur EventSource:", err);
93
- assistantMessageDiv.innerHTML += "<br>Erreur de connexion stream.";
94
- eventSource.close(); // Ferme la connexion en cas d'erreur
95
- sendButton.disabled = false;
96
- };
97
-
98
- // On pourrait avoir besoin d'un signal de fin explicite du serveur
99
- // eventSource.addEventListener('end', function() {
100
- // eventSource.close();
101
- // sendButton.disabled = false;
102
- // });
103
- */
104
-
105
-
106
- } catch (error) {
107
- console.error('Erreur lors de la requête:', error);
108
- assistantMessageDiv.innerHTML += `<br>Erreur: ${error.message}`;
109
- } finally {
110
- // Réactive le bouton une fois le stream terminé ou en erreur (nécessite une gestion de fin de stream fiable)
111
- // Avec fetch, la fin est détectée par `done` dans la boucle reader.read()
112
- sendButton.disabled = false;
113
- }
114
- }
115
-
116
- sendButton.addEventListener('click', askQuestion);
117
- questionInput.addEventListener('keypress', function(e) {
118
- if (e.key === 'Enter' && !e.shiftKey) { // Submit avec Entrée (sans Shift)
119
- e.preventDefault(); // Empêche le saut de ligne par défaut
120
- askQuestion();
121
- }
122
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const chatbox = document.getElementById('chatbox');
2
+ const messageInput = document.getElementById('message-input'); // Renommé pour clarté
3
+ const sendButton = document.getElementById('send-button');
4
+
5
+ // Fonction pour ajouter un message au chatbox
6
+ function addMessage(role, text) {
7
+ const messageDiv = document.createElement('div');
8
+ messageDiv.classList.add('message', role === 'user' ? 'user-message' : 'assistant-message');
9
+
10
+ // IMPORTANT: Pour afficher le texte brut et éviter les injections XSS
11
+ // Utilisez textContent au lieu de innerHTML si vous n'avez pas besoin d'interpréter le HTML reçu
12
+ // messageDiv.textContent = text;
13
+
14
+ // SI vous avez besoin d'interpréter le HTML (ex: <br> pour les sauts de ligne, ou si le LLM génère du HTML/Markdown simple)
15
+ // Soyez prudent avec le contenu généré par le LLM si vous utilisez innerHTML.
16
+ // Une bibliothèque de Markdown comme 'marked.js' serait plus sûre pour rendre le Markdown.
17
+ // Pour une simple substitution de saut de ligne:
18
+ messageDiv.innerHTML = text.replace(/\n/g, '<br>');
19
+
20
+ chatbox.appendChild(messageDiv);
21
+ // Scroll vers le bas pour voir le nouveau message
22
+ chatbox.scrollTop = chatbox.scrollHeight;
23
+ }
24
+
25
+ // Fonction pour envoyer la question via l'API SSE
26
+ async function askQuestion() {
27
+ const question = messageInput.value.trim();
28
+ if (!question) return;
29
+
30
+ addMessage('user', question);
31
+ messageInput.value = ''; // Vide la zone de saisie
32
+ sendButton.disabled = true; // Désactive le bouton pendant l'attente
33
+ messageInput.style.height = 'auto'; // Réinitialise la hauteur (pour auto-ajustement si implémenté)
34
+
35
+
36
+ // Crée un placeholder pour la réponse de l'assistant
37
+ const assistantMessageDiv = document.createElement('div');
38
+ assistantMessageDiv.classList.add('message', 'assistant-message');
39
+ assistantMessageDiv.innerHTML = '<span class="thinking">...</span>'; // Indicateur visuel simple
40
+ chatbox.appendChild(assistantMessageDiv);
41
+ chatbox.scrollTop = chatbox.scrollHeight;
42
+
43
+ let currentResponse = ""; // Pour accumuler la réponse streamée
44
+
45
+ try {
46
+ const response = await fetch('/ask', {
47
+ method: 'POST',
48
+ headers: {
49
+ 'Content-Type': 'application/json',
50
+ 'Accept': 'text/event-stream'
51
+ },
52
+ body: JSON.stringify({ question: question })
53
+ });
54
+
55
+ if (!response.ok || !response.body) {
56
+ throw new Error(`Erreur serveur: ${response.status} ${response.statusText}`);
57
+ }
58
+
59
+ const reader = response.body.getReader();
60
+ const decoder = new TextDecoder();
61
+ let buffer = ''; // Buffer pour gérer les chunks SSE potentiellement fragmentés
62
+
63
+ while (true) {
64
+ const { done, value } = await reader.read();
65
+ if (done) {
66
+ console.log("Stream terminé par le serveur.");
67
+ // Traiter le reste du buffer au cas où
68
+ processSSEBuffer(buffer, assistantMessageDiv);
69
+ break; // Sort de la boucle
70
+ }
71
+
72
+ buffer += decoder.decode(value, { stream: true });
73
+ // Traite le buffer pour extraire les messages SSE complets
74
+ buffer = processSSEBuffer(buffer, assistantMessageDiv);
75
+
76
+ // Met à jour le contenu du div de l'assistant (déjà fait dans processSSEBuffer)
77
+ chatbox.scrollTop = chatbox.scrollHeight; // Scroll pendant le stream
78
+ }
79
+
80
+ } catch (error) {
81
+ console.error('Erreur lors de la requête ou du stream:', error);
82
+ // Affiche l'erreur dans la bulle de l'assistant
83
+ if (assistantMessageDiv) {
84
+ assistantMessageDiv.innerHTML += `<br><br><strong style="color: #ffaaaa;">حدث خطأ: ${error.message}</strong>`;
85
+ } else {
86
+ addMessage('assistant', `<strong style="color: #ffaaaa;">حدث خطأ: ${error.message}</strong>`);
87
+ }
88
+ } finally {
89
+ sendButton.disabled = false; // Réactive le bouton à la fin ou en cas d'erreur
90
+ // Optionnel: redonner le focus à la zone de saisie
91
+ // messageInput.focus();
92
+ }
93
+ }
94
+
95
+ // Fonction pour traiter le buffer SSE et mettre à jour l'UI
96
+ function processSSEBuffer(buffer, targetDiv) {
97
+ let messages = buffer.split('\n\n');
98
+ let incomplete = buffer.endsWith('\n\n') ? '' : messages.pop(); // Garde la partie incomplète pour le prochain chunk
99
+
100
+ messages.forEach(message => {
101
+ if (!message.trim()) return; // Ignore les messages vides
102
+
103
+ let eventType = 'message'; // Type par défaut
104
+ let dataLines = [];
105
+
106
+ message.split('\n').forEach(line => {
107
+ if (line.startsWith('event: ')) {
108
+ eventType = line.substring(7).trim();
109
+ } else if (line.startsWith('data: ')) {
110
+ dataLines.push(line.substring(6)); // Ne pas .trim() ici pour préserver les espaces initiaux
111
+ }
112
+ // Ignorer les lignes 'id:' ou ':' (commentaires) pour l'instant
113
+ });
114
+
115
+ let data = dataLines.join('\n'); // Rejoint les lignes de données avec des sauts de ligne
116
+
117
+ console.log(`SSE Event: ${eventType}, Data: ${data.substring(0, 50)}...`); // Log de débogage
118
+
119
+ if (eventType === 'message') {
120
+ // Si la réponse initiale est l'indicateur '...', la remplace
121
+ if (targetDiv.querySelector('.thinking')) {
122
+ targetDiv.innerHTML = ''; // Efface le "..."
123
+ }
124
+ // Ajoute les nouvelles données
125
+ // Utiliser innerHTML avec précaution, idéalement nettoyer ou utiliser textContent si possible
126
+ currentResponse += data;
127
+ targetDiv.innerHTML = currentResponse.replace(/\n/g, '<br>'); // Met à jour avec la réponse accumulée
128
+
129
+ } else if (eventType === 'end') {
130
+ console.log("Fin de stream détectée par événement 'end'");
131
+ sendButton.disabled = false; // Réactive explicitement le bouton
132
+ } else if (eventType === 'error') {
133
+ console.error("Erreur signalée par le serveur via SSE:", data);
134
+ targetDiv.innerHTML += `<br><strong style="color: #ffaaaa;">خطأ من الخادم: ${data}</strong>`;
135
+ sendButton.disabled = false;
136
+ }
137
+ // Gérer d'autres types d'événements si nécessaire
138
+ });
139
+ chatbox.scrollTop = chatbox.scrollHeight; // Scroll après traitement
140
+ return incomplete; // Retourne la partie non traitée du buffer
141
+ }
142
+
143
+ // --- Auto-ajustement hauteur Textarea (Optionnel mais recommandé) ---
144
+ function adjustTextareaHeight() {
145
+ messageInput.style.height = 'auto'; // Réinitialise pour obtenir scrollHeight correct
146
+ messageInput.style.height = (messageInput.scrollHeight) + 'px'; // Ajuste à la hauteur du contenu
147
+ }
148
+
149
+ messageInput.addEventListener('input', adjustTextareaHeight);
150
+
151
+
152
+ // --- Écouteurs d'événements ---
153
+ sendButton.addEventListener('click', askQuestion);
154
+
155
+ messageInput.addEventListener('keypress', function(e) {
156
+ // Envoi avec Entrée (sauf si Shift est pressé pour un saut de ligne)
157
+ if (e.key === 'Enter' && !e.shiftKey) {
158
+ e.preventDefault(); // Empêche le saut de ligne dans le textarea
159
+ askQuestion();
160
+ }
161
+ });
162
+
163
+ // Ajuste la hauteur initiale au cas où il y aurait du texte pré-rempli (peu probable ici)
164
+ adjustTextareaHeight();