Update static/script.js
Browse files- static/script.js +118 -79
static/script.js
CHANGED
@@ -1,78 +1,27 @@
|
|
1 |
const chatbox = document.getElementById('chatbox');
|
2 |
-
const messageInput = document.getElementById('message-input');
|
3 |
const sendButton = document.getElementById('send-button');
|
4 |
|
5 |
-
//
|
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 |
-
//
|
26 |
-
async function askQuestion() {
|
27 |
-
// ... (début de la fonction inchangé) ...
|
28 |
-
|
29 |
-
let currentResponse = ""; // Déclaration initiale ICI
|
30 |
-
|
31 |
-
try {
|
32 |
-
// ... (fetch et initialisation reader/decoder) ...
|
33 |
-
let buffer = '';
|
34 |
-
|
35 |
-
while (true) {
|
36 |
-
const { done, value } = await reader.read();
|
37 |
-
if (done) {
|
38 |
-
console.log("Stream terminé par le serveur.");
|
39 |
-
// Traiter le reste du buffer
|
40 |
-
const result = processSSEBuffer(buffer, assistantMessageDiv, currentResponse); // Passe currentResponse
|
41 |
-
currentResponse = result.updatedResponse; // Met à jour currentResponse
|
42 |
-
break;
|
43 |
-
}
|
44 |
-
|
45 |
-
buffer += decoder.decode(value, { stream: true });
|
46 |
-
// Traite le buffer et met à jour currentResponse
|
47 |
-
const result = processSSEBuffer(buffer, assistantMessageDiv, currentResponse); // Passe currentResponse
|
48 |
-
currentResponse = result.updatedResponse; // Met à jour currentResponse
|
49 |
-
buffer = result.incomplete; // Met à jour le buffer
|
50 |
-
|
51 |
-
// Scroll (déjà fait dans processSSEBuffer)
|
52 |
-
// chatbox.scrollTop = chatbox.scrollHeight;
|
53 |
-
}
|
54 |
-
|
55 |
-
} catch (error) {
|
56 |
-
// ... (gestion d'erreur inchangée) ...
|
57 |
-
} finally {
|
58 |
-
sendButton.disabled = false;
|
59 |
-
}
|
60 |
-
}
|
61 |
-
|
62 |
-
// Fonction pour traiter le buffer SSE et mettre à jour l'UI
|
63 |
-
// Accepte la réponse actuelle et le div cible, retourne la nouvelle réponse accumulée
|
64 |
function processSSEBuffer(buffer, targetDiv, currentResponseAccumulated) {
|
|
|
65 |
let messages = buffer.split('\n\n');
|
66 |
let incomplete = buffer.endsWith('\n\n') ? '' : messages.pop();
|
67 |
-
|
68 |
-
let newResponsePart = ''; // Pour accumuler les NOUVELLES données de ce buffer
|
69 |
|
70 |
messages.forEach(message => {
|
71 |
if (!message.trim()) return;
|
72 |
-
|
73 |
let eventType = 'message';
|
74 |
let dataLines = [];
|
75 |
-
|
76 |
message.split('\n').forEach(line => {
|
77 |
if (line.startsWith('event: ')) {
|
78 |
eventType = line.substring(7).trim();
|
@@ -80,54 +29,144 @@ function processSSEBuffer(buffer, targetDiv, currentResponseAccumulated) {
|
|
80 |
dataLines.push(line.substring(6));
|
81 |
}
|
82 |
});
|
83 |
-
|
84 |
let data = dataLines.join('\n');
|
85 |
-
console.log(
|
86 |
|
87 |
if (eventType === 'message') {
|
88 |
if (targetDiv.querySelector('.thinking')) {
|
89 |
-
|
|
|
90 |
}
|
91 |
-
newResponsePart += data;
|
92 |
-
|
93 |
} else if (eventType === 'end') {
|
94 |
-
console.log("Fin de stream détectée
|
95 |
sendButton.disabled = false;
|
96 |
} else if (eventType === 'error') {
|
97 |
-
console.error("Erreur
|
98 |
-
// Ajoute l'erreur à la fin de la réponse actuelle
|
99 |
currentResponseAccumulated += `<br><strong style="color: #ffaaaa;">خطأ من الخادم: ${data}</strong>`;
|
100 |
sendButton.disabled = false;
|
101 |
}
|
102 |
});
|
103 |
|
104 |
-
// Met à jour l'UI avec la réponse complète (ancienne + nouvelle)
|
105 |
const updatedResponse = currentResponseAccumulated + newResponsePart;
|
|
|
106 |
targetDiv.innerHTML = updatedResponse.replace(/\n/g, '<br>');
|
107 |
-
|
108 |
chatbox.scrollTop = chatbox.scrollHeight;
|
109 |
-
|
110 |
-
}
|
111 |
|
112 |
-
|
113 |
-
function adjustTextareaHeight() {
|
114 |
-
messageInput.style.height = 'auto'; // Réinitialise pour obtenir scrollHeight correct
|
115 |
-
messageInput.style.height = (messageInput.scrollHeight) + 'px'; // Ajuste à la hauteur du contenu
|
116 |
}
|
117 |
|
118 |
-
|
|
|
119 |
|
120 |
|
121 |
-
// ---
|
122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
|
|
|
|
|
124 |
messageInput.addEventListener('keypress', function(e) {
|
125 |
-
// Envoi avec Entrée (sauf si Shift est pressé pour un saut de ligne)
|
126 |
if (e.key === 'Enter' && !e.shiftKey) {
|
127 |
-
e.preventDefault();
|
128 |
askQuestion();
|
129 |
}
|
130 |
});
|
|
|
|
|
131 |
|
132 |
-
|
133 |
-
adjustTextareaHeight();
|
|
|
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) {
|
16 |
+
console.log("Processing buffer:", buffer); // LOG AJOUTÉ
|
17 |
let messages = buffer.split('\n\n');
|
18 |
let incomplete = buffer.endsWith('\n\n') ? '' : messages.pop();
|
19 |
+
let newResponsePart = '';
|
|
|
20 |
|
21 |
messages.forEach(message => {
|
22 |
if (!message.trim()) return;
|
|
|
23 |
let eventType = 'message';
|
24 |
let dataLines = [];
|
|
|
25 |
message.split('\n').forEach(line => {
|
26 |
if (line.startsWith('event: ')) {
|
27 |
eventType = line.substring(7).trim();
|
|
|
29 |
dataLines.push(line.substring(6));
|
30 |
}
|
31 |
});
|
|
|
32 |
let data = dataLines.join('\n');
|
33 |
+
console.log(`--> SSE Event Parsed: ${eventType}, Data: ${data.substring(0, 100)}...`); // LOG AJOUTÉ
|
34 |
|
35 |
if (eventType === 'message') {
|
36 |
if (targetDiv.querySelector('.thinking')) {
|
37 |
+
console.log("Replacing 'thinking' indicator"); // LOG AJOUTÉ
|
38 |
+
targetDiv.innerHTML = '';
|
39 |
}
|
40 |
+
newResponsePart += data;
|
|
|
41 |
} else if (eventType === 'end') {
|
42 |
+
console.log("Fin de stream détectée (event: end)"); // LOG AJOUTÉ
|
43 |
sendButton.disabled = false;
|
44 |
} else if (eventType === 'error') {
|
45 |
+
console.error("Erreur SSE du serveur:", data); // LOG AJOUTÉ
|
|
|
46 |
currentResponseAccumulated += `<br><strong style="color: #ffaaaa;">خطأ من الخادم: ${data}</strong>`;
|
47 |
sendButton.disabled = false;
|
48 |
}
|
49 |
});
|
50 |
|
|
|
51 |
const updatedResponse = currentResponseAccumulated + newResponsePart;
|
52 |
+
// Limiter la fréquence de mise à jour de innerHTML si beaucoup de petits chunks
|
53 |
targetDiv.innerHTML = updatedResponse.replace(/\n/g, '<br>');
|
|
|
54 |
chatbox.scrollTop = chatbox.scrollHeight;
|
55 |
+
// console.log("Updated Response:", updatedResponse.substring(0, 100) + "..."); // Optionnel: peut être très verbeux
|
|
|
56 |
|
57 |
+
return { incomplete: incomplete, updatedResponse: updatedResponse };
|
|
|
|
|
|
|
58 |
}
|
59 |
|
60 |
+
// --- adjustTextareaHeight function (inchangée) ---
|
61 |
+
function adjustTextareaHeight() { /* ... */ }
|
62 |
|
63 |
|
64 |
+
// --- askQuestion function (avec plus de logs) ---
|
65 |
+
async function askQuestion() {
|
66 |
+
console.log("askQuestion called"); // LOG AJOUTÉ
|
67 |
+
const question = messageInput.value.trim();
|
68 |
+
if (!question) {
|
69 |
+
console.log("Question is empty, aborting."); // LOG AJOUTÉ
|
70 |
+
return;
|
71 |
+
}
|
72 |
+
|
73 |
+
addMessage('user', question);
|
74 |
+
messageInput.value = '';
|
75 |
+
sendButton.disabled = true;
|
76 |
+
adjustTextareaHeight(); // Réajuste après vidage
|
77 |
+
console.log("User message added, button disabled."); // LOG AJOUTÉ
|
78 |
+
|
79 |
+
// Crée un placeholder pour la réponse de l'assistant
|
80 |
+
const assistantMessageDiv = document.createElement('div');
|
81 |
+
assistantMessageDiv.classList.add('message', 'assistant-message');
|
82 |
+
assistantMessageDiv.innerHTML = '<span class="thinking">...</span>'; // Indicateur visuel simple
|
83 |
+
chatbox.appendChild(assistantMessageDiv);
|
84 |
+
chatbox.scrollTop = chatbox.scrollHeight;
|
85 |
+
console.log("Assistant placeholder added."); // LOG AJOUTÉ
|
86 |
+
|
87 |
+
let currentResponse = ""; // Déclaration initiale
|
88 |
+
let buffer = ''; // Initialisation buffer
|
89 |
+
const decoder = new TextDecoder(); // Initialisation decoder
|
90 |
+
|
91 |
+
try {
|
92 |
+
console.log("Initiating fetch to /ask"); // LOG AJOUTÉ
|
93 |
+
const response = await fetch('/ask', {
|
94 |
+
method: 'POST',
|
95 |
+
headers: {
|
96 |
+
'Content-Type': 'application/json',
|
97 |
+
'Accept': 'text/event-stream'
|
98 |
+
},
|
99 |
+
body: JSON.stringify({ question: question })
|
100 |
+
});
|
101 |
+
|
102 |
+
console.log(`Fetch response received. Status: ${response.status}, OK: ${response.ok}`); // LOG AJOUTÉ
|
103 |
+
|
104 |
+
if (!response.ok || !response.body) {
|
105 |
+
// Essayer de lire le corps de l'erreur si possible
|
106 |
+
let errorBody = "N/A";
|
107 |
+
try {
|
108 |
+
errorBody = await response.text();
|
109 |
+
} catch (e) {}
|
110 |
+
throw new Error(`Erreur serveur: ${response.status} ${response.statusText}. Body: ${errorBody}`);
|
111 |
+
}
|
112 |
+
|
113 |
+
const reader = response.body.getReader();
|
114 |
+
console.log("ReadableStream reader obtained. Starting read loop."); // LOG AJOUTÉ
|
115 |
+
|
116 |
+
while (true) {
|
117 |
+
console.log("Calling reader.read()..."); // LOG AJOUTÉ
|
118 |
+
const { done, value } = await reader.read();
|
119 |
+
|
120 |
+
if (done) {
|
121 |
+
console.log("Reader finished (done=true). Processing remaining buffer."); // LOG AJOUTÉ
|
122 |
+
// Traiter le reste du buffer
|
123 |
+
const result = processSSEBuffer(buffer, assistantMessageDiv, currentResponse);
|
124 |
+
currentResponse = result.updatedResponse;
|
125 |
+
console.log("Final response after stream ended:", currentResponse.substring(0,100)+"..."); // LOG AJOUTÉ
|
126 |
+
break; // Sort de la boucle
|
127 |
+
}
|
128 |
+
|
129 |
+
// Decode et ajoute au buffer
|
130 |
+
const chunk = decoder.decode(value, { stream: true });
|
131 |
+
console.log("Received chunk:", chunk); // LOG AJOUTÉ (peut être volumineux)
|
132 |
+
buffer += chunk;
|
133 |
+
|
134 |
+
// Traite le buffer et met à jour currentResponse/buffer
|
135 |
+
console.log("Processing buffer with new chunk..."); // LOG AJOUTÉ
|
136 |
+
const result = processSSEBuffer(buffer, assistantMessageDiv, currentResponse);
|
137 |
+
currentResponse = result.updatedResponse;
|
138 |
+
buffer = result.incomplete;
|
139 |
+
console.log("Buffer remaining:", buffer); // LOG AJOUTÉ
|
140 |
+
|
141 |
+
}
|
142 |
+
|
143 |
+
} catch (error) {
|
144 |
+
console.error('Erreur dans askQuestion (fetch ou stream):', error); // LOG AJOUTÉ
|
145 |
+
// Affiche l'erreur dans la bulle de l'assistant
|
146 |
+
if (assistantMessageDiv) {
|
147 |
+
// Append error to existing content if any, otherwise set it
|
148 |
+
assistantMessageDiv.innerHTML += `<br><br><strong style="color: #ffaaaa;">حدث خطأ: ${error.message}</strong>`;
|
149 |
+
chatbox.scrollTop = chatbox.scrollHeight; // Scroll pour voir l'erreur
|
150 |
+
} else {
|
151 |
+
// Fallback si assistantMessageDiv n'existe pas (ne devrait pas arriver ici)
|
152 |
+
addMessage('assistant', `<strong style="color: #ffaaaa;">حدث خطأ: ${error.message}</strong>`);
|
153 |
+
}
|
154 |
+
} finally {
|
155 |
+
console.log("Executing finally block: Enabling button."); // LOG AJOUTÉ
|
156 |
+
sendButton.disabled = false;
|
157 |
+
}
|
158 |
+
}
|
159 |
+
|
160 |
|
161 |
+
// --- Écouteurs d'événements (inchangés) ---
|
162 |
+
sendButton.addEventListener('click', askQuestion);
|
163 |
messageInput.addEventListener('keypress', function(e) {
|
|
|
164 |
if (e.key === 'Enter' && !e.shiftKey) {
|
165 |
+
e.preventDefault();
|
166 |
askQuestion();
|
167 |
}
|
168 |
});
|
169 |
+
messageInput.addEventListener('input', adjustTextareaHeight);
|
170 |
+
adjustTextareaHeight(); // Appel initial
|
171 |
|
172 |
+
console.log("Chat script loaded and listeners attached."); // LOG AJOUTÉ
|
|