document.addEventListener('DOMContentLoaded', () => { const chatForm = document.getElementById('chat-form'); const promptInput = document.getElementById('prompt'); const chatMessages = document.getElementById('chat-messages'); const loadingIndicator = document.getElementById('loading-indicator'); const errorMessageDiv = document.getElementById('error-message'); const webSearchToggle = document.getElementById('web_search_toggle'); const fileUpload = document.getElementById('file_upload'); const fileNameSpan = document.getElementById('file-name'); const sendButton = document.getElementById('send-button'); // --- Fonctions Utilitaires --- function scrollToBottom() { // Ajoute un léger délai pour laisser le temps au DOM de se mettre à jour setTimeout(() => { chatMessages.scrollTop = chatMessages.scrollHeight; }, 50); } function showLoading(show) { loadingIndicator.style.display = show ? 'block' : 'none'; sendButton.disabled = show; promptInput.disabled = show; // Optionnel: changer l'apparence du bouton pendant le chargement sendButton.classList.toggle('opacity-50', show); sendButton.classList.toggle('cursor-not-allowed', show); } function displayError(message) { errorMessageDiv.textContent = message; errorMessageDiv.style.display = 'block'; // Cache le message après quelques secondes setTimeout(() => { errorMessageDiv.style.display = 'none'; }, 5000); } function addMessageToChat(role, text) { // Assainir le texte avant de l'insérer (très basique, pour éviter XSS simple) // Pour du Markdown, une bibliothèque comme 'marked' ou 'showdown' serait nécessaire côté client // ou s'assurer que le backend renvoie du HTML sûr. // Ici, on suppose que le backend renvoie du texte simple ou du HTML déjà aseptisé avec | safe const messageDiv = document.createElement('div'); messageDiv.classList.add('flex', role === 'user' ? 'justify-end' : 'justify-start'); const bubbleDiv = document.createElement('div'); bubbleDiv.classList.add('p-3', 'rounded-lg', 'max-w-xs', 'md:max-w-md', 'shadow'); if (role === 'user') { bubbleDiv.classList.add('bg-blue-500', 'text-white', 'rounded-br-none'); } else { // Pour l'assistant, créer une div interne pour le formatage prose si nécessaire bubbleDiv.classList.add('bg-gray-200', 'text-gray-800', 'rounded-bl-none'); const proseDiv = document.createElement('div'); proseDiv.classList.add('prose', 'prose-sm', 'max-w-none'); // IMPORTANT: Si le backend renvoie du HTML, il DOIT être sûr. // Sinon, utiliser textContent pour éviter l'injection de script. proseDiv.innerHTML = text; // Suppose que le HTML reçu est sûr (ex: via Markdown rendu côté serveur) // Si juste du texte : proseDiv.textContent = text; bubbleDiv.appendChild(proseDiv); } // Si c'est un message utilisateur simple (pas de HTML) if(role === 'user'){ const paragraph = document.createElement('p'); paragraph.classList.add('text-sm'); paragraph.textContent = text; // Utiliser textContent pour la sécurité bubbleDiv.appendChild(paragraph); } messageDiv.appendChild(bubbleDiv); chatMessages.appendChild(messageDiv); // Insérer l'indicateur *après* le dernier message pour qu'il soit en bas chatMessages.appendChild(loadingIndicator); scrollToBottom(); } // --- Gestionnaires d'événements --- // Afficher le nom du fichier sélectionné fileUpload.addEventListener('change', () => { if (fileUpload.files.length > 0) { fileNameSpan.textContent = fileUpload.files[0].name; fileNameSpan.title = fileUpload.files[0].name; // Pour le nom complet au survol } else { fileNameSpan.textContent = ''; fileNameSpan.title = ''; } }); // Soumission du formulaire via Fetch API chatForm.addEventListener('submit', async (e) => { e.preventDefault(); // Empêche le rechargement de la page const prompt = promptInput.value.trim(); const file = fileUpload.files[0]; const useWebSearch = webSearchToggle.checked; if (!prompt && !file) { displayError("Veuillez entrer un message ou sélectionner un fichier."); return; } // Cacher les erreurs précédentes errorMessageDiv.style.display = 'none'; // Afficher le message utilisateur immédiatement let userMessageText = prompt; if (file) { userMessageText = `[Fichier: ${file.name}]\n\n${prompt}`; } addMessageToChat('user', userMessageText); // Préparer les données du formulaire pour l'envoi (y compris le fichier) const formData = new FormData(); formData.append('prompt', prompt); formData.append('web_search', useWebSearch); if (file) { formData.append('file', file); } // Afficher le chargement et désactiver les entrées showLoading(true); promptInput.value = ''; // Vider l'input après l'envoi fileUpload.value = ''; // Réinitialiser l'input fichier fileNameSpan.textContent = ''; // Vider le nom du fichier affiché try { const response = await fetch("{{ url_for('chat_api') }}", { // Appel vers la nouvelle route API method: 'POST', body: formData, // Pas besoin de 'Content-Type', le navigateur le définit pour FormData }); if (!response.ok) { // Essayer de lire l'erreur JSON si possible let errorMsg = `Erreur HTTP: ${response.status}`; try { const errorData = await response.json(); errorMsg = errorData.error || errorMsg; } catch (jsonError) { // Ignorer si la réponse n'est pas du JSON } throw new Error(errorMsg); } const data = await response.json(); if (data.success && data.message) { // IMPORTANT: S'assurer que data.message est du HTML sûr ou l'assainir ici addMessageToChat('assistant', data.message); } else if (data.error) { displayError(data.error); // Optionnel: supprimer le message utilisateur si l'IA n'a pas pu répondre // (peut être déroutant pour l'utilisateur) } } catch (error) { console.error("Erreur lors de l'envoi du message:", error); displayError(error.message || "Une erreur inconnue est survenue."); // Optionnel: supprimer le message utilisateur en cas d'erreur réseau grave } finally { // Cacher le chargement et réactiver les entrées showLoading(false); promptInput.focus(); // Remettre le focus sur l'input } }); // Scroll initial vers le bas au chargement de la page scrollToBottom(); promptInput.focus(); // Focus sur l'input au chargement });