Chatm / templates /index.html
Docfile's picture
Update templates/index.html
25ac863 verified
raw
history blame
7.49 kB
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
});