Update static/script.js
Browse files- static/script.js +164 -122
static/script.js
CHANGED
@@ -1,122 +1,164 @@
|
|
1 |
-
const chatbox = document.getElementById('chatbox');
|
2 |
-
const
|
3 |
-
const sendButton = document.getElementById('send-button');
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
//
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
//
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
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();
|