|
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 |
|
}); |