Update templates/index.html
Browse files- templates/index.html +78 -26
templates/index.html
CHANGED
@@ -12,7 +12,6 @@
|
|
12 |
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🤖</text></svg>">
|
13 |
|
14 |
<style>
|
15 |
-
/* Assurer la hauteur minimale et permettre le scroll de la page sur mobile */
|
16 |
html, body {
|
17 |
min-height: 100vh;
|
18 |
margin: 0;
|
@@ -34,7 +33,6 @@
|
|
34 |
overflow-y: auto;
|
35 |
min-height: 0;
|
36 |
}
|
37 |
-
/* Styles de la scrollbar */
|
38 |
::-webkit-scrollbar {
|
39 |
width: 8px;
|
40 |
}
|
@@ -88,13 +86,40 @@
|
|
88 |
color: #1d4ed8;
|
89 |
text-decoration-color: #60a5fa;
|
90 |
}
|
91 |
-
/* Chargement initial de l'historique */
|
92 |
#history-loading {
|
93 |
padding: 20px;
|
94 |
text-align: center;
|
95 |
color: #6b7280;
|
96 |
font-style: italic;
|
97 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
</style>
|
99 |
</head>
|
100 |
<body class="bg-gray-100 flex flex-col min-h-screen">
|
@@ -138,7 +163,7 @@
|
|
138 |
<p id="error-text">Le message d'erreur détaillé ira ici.</p>
|
139 |
</div>
|
140 |
|
141 |
-
<!-- Barre d'options
|
142 |
<div class="bg-gray-50 border-t border-gray-200 px-4 py-2 flex-shrink-0">
|
143 |
<div class="flex items-center justify-between text-sm">
|
144 |
<label for="web_search_toggle" class="flex items-center space-x-2 cursor-pointer text-gray-600 hover:text-gray-800 select-none" title="Activer/Désactiver la recherche web pour le prochain message">
|
@@ -162,6 +187,8 @@
|
|
162 |
</button>
|
163 |
</div>
|
164 |
</div>
|
|
|
|
|
165 |
</div>
|
166 |
|
167 |
<!-- Formulaire d'entrée du message -->
|
@@ -180,7 +207,6 @@
|
|
180 |
<!-- Script JavaScript pour l'interaction -->
|
181 |
<script>
|
182 |
document.addEventListener('DOMContentLoaded', () => {
|
183 |
-
// Références aux éléments du DOM
|
184 |
const chatForm = document.getElementById('chat-form');
|
185 |
const promptInput = document.getElementById('prompt');
|
186 |
const chatMessages = document.getElementById('chat-messages');
|
@@ -192,10 +218,10 @@
|
|
192 |
const fileUpload = document.getElementById('file_upload');
|
193 |
const fileNameSpan = document.getElementById('file-name');
|
194 |
const clearFileButton = document.getElementById('clear-file');
|
|
|
195 |
const sendButton = document.getElementById('send-button');
|
196 |
const clearForm = document.getElementById('clear-form');
|
197 |
|
198 |
-
// Endpoints de l'API Backend
|
199 |
const API_CHAT_ENDPOINT = '/api/chat';
|
200 |
const API_HISTORY_ENDPOINT = '/api/history';
|
201 |
const CLEAR_ENDPOINT = '/clear';
|
@@ -230,7 +256,7 @@
|
|
230 |
function addMessageToChat(role, text, isHtml = false) {
|
231 |
errorMessageDiv.style.display = 'none';
|
232 |
const messageWrapper = document.createElement('div');
|
233 |
-
messageWrapper.classList.add('flex', role === 'user' ? 'justify-end' : 'justify-start', 'mb-4');
|
234 |
|
235 |
const bubbleDiv = document.createElement('div');
|
236 |
bubbleDiv.classList.add('p-3', 'rounded-lg', 'max-w-[85%]', 'sm:max-w-[75%]', 'shadow-md', 'relative');
|
@@ -251,11 +277,38 @@
|
|
251 |
proseDiv.textContent = text;
|
252 |
}
|
253 |
bubbleDiv.appendChild(proseDiv);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
254 |
}
|
255 |
|
256 |
messageWrapper.appendChild(bubbleDiv);
|
257 |
chatMessages.insertBefore(messageWrapper, loadingIndicator);
|
258 |
-
|
259 |
if (historyLoadingIndicator.parentNode !== chatMessages) {
|
260 |
scrollToBottom();
|
261 |
}
|
@@ -308,6 +361,7 @@
|
|
308 |
fileNameSpan.textContent = '';
|
309 |
fileNameSpan.title = '';
|
310 |
clearFileButton.style.display = 'none';
|
|
|
311 |
}
|
312 |
|
313 |
fileUpload.addEventListener('change', () => {
|
@@ -317,6 +371,16 @@
|
|
317 |
fileNameSpan.textContent = name.length > 20 ? name.substring(0, 17) + '...' : name;
|
318 |
fileNameSpan.title = name;
|
319 |
clearFileButton.style.display = 'inline-block';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
320 |
} else {
|
321 |
clearFileInput();
|
322 |
}
|
@@ -331,31 +395,26 @@
|
|
331 |
const prompt = promptInput.value.trim();
|
332 |
const file = fileUpload.files[0];
|
333 |
const useWebSearch = webSearchToggle.checked;
|
334 |
-
|
335 |
if (!prompt && !file) {
|
336 |
displayError("Veuillez entrer un message ou sélectionner un fichier.");
|
337 |
promptInput.focus();
|
338 |
return;
|
339 |
}
|
340 |
-
|
341 |
errorMessageDiv.style.display = 'none';
|
342 |
let userMessageText = prompt;
|
343 |
if (file) {
|
344 |
userMessageText = prompt ? `[${file.name}] ${prompt}` : `[${file.name}]`;
|
345 |
}
|
346 |
addMessageToChat('user', userMessageText);
|
347 |
-
|
348 |
const formData = new FormData();
|
349 |
formData.append('prompt', prompt);
|
350 |
formData.append('web_search', useWebSearch);
|
351 |
if (file) {
|
352 |
formData.append('file', file);
|
353 |
}
|
354 |
-
|
355 |
showLoading(true);
|
356 |
promptInput.value = '';
|
357 |
clearFileInput();
|
358 |
-
|
359 |
try {
|
360 |
const response = await fetch(API_CHAT_ENDPOINT, {
|
361 |
method: 'POST',
|
@@ -385,20 +444,13 @@
|
|
385 |
e.target.querySelector('button').textContent = '...';
|
386 |
e.target.querySelector('button').disabled = true;
|
387 |
try {
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
'X-Requested-With': 'XMLHttpRequest'
|
395 |
-
}
|
396 |
-
});
|
397 |
const data = await response.json();
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
if (response.ok && data.success) {
|
403 |
chatMessages.innerHTML = '';
|
404 |
chatMessages.appendChild(loadingIndicator);
|
|
|
12 |
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🤖</text></svg>">
|
13 |
|
14 |
<style>
|
|
|
15 |
html, body {
|
16 |
min-height: 100vh;
|
17 |
margin: 0;
|
|
|
33 |
overflow-y: auto;
|
34 |
min-height: 0;
|
35 |
}
|
|
|
36 |
::-webkit-scrollbar {
|
37 |
width: 8px;
|
38 |
}
|
|
|
86 |
color: #1d4ed8;
|
87 |
text-decoration-color: #60a5fa;
|
88 |
}
|
|
|
89 |
#history-loading {
|
90 |
padding: 20px;
|
91 |
text-align: center;
|
92 |
color: #6b7280;
|
93 |
font-style: italic;
|
94 |
}
|
95 |
+
/* Zone de prévisualisation des images */
|
96 |
+
#file-preview {
|
97 |
+
margin-top: 8px;
|
98 |
+
}
|
99 |
+
#file-preview img {
|
100 |
+
max-width: 100%;
|
101 |
+
max-height: 150px;
|
102 |
+
border: 1px solid #ddd;
|
103 |
+
border-radius: 4px;
|
104 |
+
padding: 4px;
|
105 |
+
}
|
106 |
+
/* Bouton de copie (positionné en haut à droite de la bulle de réponse) */
|
107 |
+
.copy-btn {
|
108 |
+
position: absolute;
|
109 |
+
top: 4px;
|
110 |
+
right: 4px;
|
111 |
+
background: rgba(255, 255, 255, 0.8);
|
112 |
+
border: none;
|
113 |
+
border-radius: 4px;
|
114 |
+
padding: 2px 4px;
|
115 |
+
cursor: pointer;
|
116 |
+
font-size: 0.75rem;
|
117 |
+
display: none;
|
118 |
+
}
|
119 |
+
/* Afficher le bouton au survol de la bulle */
|
120 |
+
.message-wrapper:hover .copy-btn {
|
121 |
+
display: block;
|
122 |
+
}
|
123 |
</style>
|
124 |
</head>
|
125 |
<body class="bg-gray-100 flex flex-col min-h-screen">
|
|
|
163 |
<p id="error-text">Le message d'erreur détaillé ira ici.</p>
|
164 |
</div>
|
165 |
|
166 |
+
<!-- Barre d'options, d'upload et de prévisualisation -->
|
167 |
<div class="bg-gray-50 border-t border-gray-200 px-4 py-2 flex-shrink-0">
|
168 |
<div class="flex items-center justify-between text-sm">
|
169 |
<label for="web_search_toggle" class="flex items-center space-x-2 cursor-pointer text-gray-600 hover:text-gray-800 select-none" title="Activer/Désactiver la recherche web pour le prochain message">
|
|
|
187 |
</button>
|
188 |
</div>
|
189 |
</div>
|
190 |
+
<!-- Zone de prévisualisation des images -->
|
191 |
+
<div id="file-preview"></div>
|
192 |
</div>
|
193 |
|
194 |
<!-- Formulaire d'entrée du message -->
|
|
|
207 |
<!-- Script JavaScript pour l'interaction -->
|
208 |
<script>
|
209 |
document.addEventListener('DOMContentLoaded', () => {
|
|
|
210 |
const chatForm = document.getElementById('chat-form');
|
211 |
const promptInput = document.getElementById('prompt');
|
212 |
const chatMessages = document.getElementById('chat-messages');
|
|
|
218 |
const fileUpload = document.getElementById('file_upload');
|
219 |
const fileNameSpan = document.getElementById('file-name');
|
220 |
const clearFileButton = document.getElementById('clear-file');
|
221 |
+
const filePreview = document.getElementById('file-preview');
|
222 |
const sendButton = document.getElementById('send-button');
|
223 |
const clearForm = document.getElementById('clear-form');
|
224 |
|
|
|
225 |
const API_CHAT_ENDPOINT = '/api/chat';
|
226 |
const API_HISTORY_ENDPOINT = '/api/history';
|
227 |
const CLEAR_ENDPOINT = '/clear';
|
|
|
256 |
function addMessageToChat(role, text, isHtml = false) {
|
257 |
errorMessageDiv.style.display = 'none';
|
258 |
const messageWrapper = document.createElement('div');
|
259 |
+
messageWrapper.classList.add('message-wrapper', 'flex', role === 'user' ? 'justify-end' : 'justify-start', 'mb-4');
|
260 |
|
261 |
const bubbleDiv = document.createElement('div');
|
262 |
bubbleDiv.classList.add('p-3', 'rounded-lg', 'max-w-[85%]', 'sm:max-w-[75%]', 'shadow-md', 'relative');
|
|
|
277 |
proseDiv.textContent = text;
|
278 |
}
|
279 |
bubbleDiv.appendChild(proseDiv);
|
280 |
+
|
281 |
+
// Ajout du bouton de copie
|
282 |
+
const copyBtn = document.createElement('button');
|
283 |
+
copyBtn.textContent = 'Copier';
|
284 |
+
copyBtn.classList.add('copy-btn');
|
285 |
+
copyBtn.title = "Copier la réponse";
|
286 |
+
copyBtn.addEventListener('click', (e) => {
|
287 |
+
e.stopPropagation();
|
288 |
+
e.preventDefault();
|
289 |
+
// Copier le texte sans le bouton
|
290 |
+
let textToCopy = '';
|
291 |
+
if (isHtml) {
|
292 |
+
textToCopy = proseDiv.innerText;
|
293 |
+
} else {
|
294 |
+
textToCopy = text;
|
295 |
+
}
|
296 |
+
navigator.clipboard.writeText(textToCopy)
|
297 |
+
.then(() => {
|
298 |
+
copyBtn.textContent = 'Copié';
|
299 |
+
setTimeout(() => {
|
300 |
+
copyBtn.textContent = 'Copier';
|
301 |
+
}, 2000);
|
302 |
+
})
|
303 |
+
.catch(err => {
|
304 |
+
console.error('Erreur lors de la copie :', err);
|
305 |
+
});
|
306 |
+
});
|
307 |
+
bubbleDiv.appendChild(copyBtn);
|
308 |
}
|
309 |
|
310 |
messageWrapper.appendChild(bubbleDiv);
|
311 |
chatMessages.insertBefore(messageWrapper, loadingIndicator);
|
|
|
312 |
if (historyLoadingIndicator.parentNode !== chatMessages) {
|
313 |
scrollToBottom();
|
314 |
}
|
|
|
361 |
fileNameSpan.textContent = '';
|
362 |
fileNameSpan.title = '';
|
363 |
clearFileButton.style.display = 'none';
|
364 |
+
filePreview.innerHTML = '';
|
365 |
}
|
366 |
|
367 |
fileUpload.addEventListener('change', () => {
|
|
|
371 |
fileNameSpan.textContent = name.length > 20 ? name.substring(0, 17) + '...' : name;
|
372 |
fileNameSpan.title = name;
|
373 |
clearFileButton.style.display = 'inline-block';
|
374 |
+
// Si le fichier est une image, afficher la prévisualisation
|
375 |
+
if (file.type.startsWith('image/')) {
|
376 |
+
const reader = new FileReader();
|
377 |
+
reader.onload = (e) => {
|
378 |
+
filePreview.innerHTML = `<img src="${e.target.result}" alt="Prévisualisation de l'image">`;
|
379 |
+
};
|
380 |
+
reader.readAsDataURL(file);
|
381 |
+
} else {
|
382 |
+
filePreview.innerHTML = '';
|
383 |
+
}
|
384 |
} else {
|
385 |
clearFileInput();
|
386 |
}
|
|
|
395 |
const prompt = promptInput.value.trim();
|
396 |
const file = fileUpload.files[0];
|
397 |
const useWebSearch = webSearchToggle.checked;
|
|
|
398 |
if (!prompt && !file) {
|
399 |
displayError("Veuillez entrer un message ou sélectionner un fichier.");
|
400 |
promptInput.focus();
|
401 |
return;
|
402 |
}
|
|
|
403 |
errorMessageDiv.style.display = 'none';
|
404 |
let userMessageText = prompt;
|
405 |
if (file) {
|
406 |
userMessageText = prompt ? `[${file.name}] ${prompt}` : `[${file.name}]`;
|
407 |
}
|
408 |
addMessageToChat('user', userMessageText);
|
|
|
409 |
const formData = new FormData();
|
410 |
formData.append('prompt', prompt);
|
411 |
formData.append('web_search', useWebSearch);
|
412 |
if (file) {
|
413 |
formData.append('file', file);
|
414 |
}
|
|
|
415 |
showLoading(true);
|
416 |
promptInput.value = '';
|
417 |
clearFileInput();
|
|
|
418 |
try {
|
419 |
const response = await fetch(API_CHAT_ENDPOINT, {
|
420 |
method: 'POST',
|
|
|
444 |
e.target.querySelector('button').textContent = '...';
|
445 |
e.target.querySelector('button').disabled = true;
|
446 |
try {
|
447 |
+
const response = await fetch(CLEAR_ENDPOINT, {
|
448 |
+
method: 'POST',
|
449 |
+
headers: {
|
450 |
+
'X-Requested-With': 'XMLHttpRequest'
|
451 |
+
}
|
452 |
+
});
|
|
|
|
|
|
|
453 |
const data = await response.json();
|
|
|
|
|
|
|
|
|
454 |
if (response.ok && data.success) {
|
455 |
chatMessages.innerHTML = '';
|
456 |
chatMessages.appendChild(loadingIndicator);
|