document.addEventListener('DOMContentLoaded', function() { // DOM Elements const chatWindow = document.getElementById('chatWindow'); const userInput = document.getElementById('userInput'); const sendButton = document.getElementById('sendButton'); const clearButton = document.getElementById('clearButton'); const clearButtonNav = document.getElementById('clearButtonNav'); const imageInput = document.getElementById('imageInput'); const uploadImageButton = document.getElementById('uploadImageButton'); const imagePreviewArea = document.getElementById('imagePreviewArea'); const imagePreviewContainer = document.getElementById('imagePreviewContainer'); const chatHistoryList = document.getElementById('chatHistoryList'); const saveCurrentChatButton = document.getElementById('saveCurrentChatButton'); const settingsButton = document.getElementById('settingsButtonNav'); const toggleNavButton = document.getElementById('toggleNavButton'); const closeSideNavButton = document.getElementById('closeSideNavButton'); const sideNav = document.getElementById('sideNav'); const mainContent = document.querySelector('.main-content'); const welcomeContainer = document.querySelector('.welcome-container'); const suggestionBubbles = document.querySelectorAll('.suggestion-bubble'); // Templates const loadingTemplate = document.getElementById('loadingTemplate'); const userMessageTemplate = document.getElementById('userMessageTemplate'); const userImageMessageTemplate = document.getElementById('userImageMessageTemplate'); const botMessageTemplate = document.getElementById('botMessageTemplate'); const errorMessageTemplate = document.getElementById('errorMessageTemplate'); // State variables let chatHistory = []; let selectedImagesData = []; // Pour stocker plusieurs images let conversationStarted = false; // --- IMAGE HANDLING --- // Upload image button click uploadImageButton.addEventListener('click', function() { imageInput.click(); }); // Image input change - support for multiple images imageInput.addEventListener('change', function(e) { const files = Array.from(e.target.files); if (!files.length) return; // Clear previous image container imagePreviewContainer.innerHTML = ''; selectedImagesData = []; // Process each file files.forEach(file => { // Check file type if (!file.type.startsWith('image/')) { alert('Veuillez sélectionner uniquement des fichiers image valides.'); return; } // Check file size (max 5MB) if (file.size > 5 * 1024 * 1024) { alert('La taille de chaque image ne doit pas dépasser 5 Mo.'); return; } const reader = new FileReader(); reader.onload = function(event) { // Create preview container const previewContainer = document.createElement('div'); previewContainer.className = 'image-preview-container'; // Create image preview const imgPreview = document.createElement('img'); imgPreview.src = event.target.result; imgPreview.alt = 'Image preview'; previewContainer.appendChild(imgPreview); // Create remove button const removeBtn = document.createElement('button'); removeBtn.className = 'remove-image-button'; removeBtn.innerHTML = ''; previewContainer.appendChild(removeBtn); // Add preview to container imagePreviewContainer.appendChild(previewContainer); // Store image data const imageData = event.target.result; selectedImagesData.push(imageData); // Add event listener to remove button removeBtn.addEventListener('click', () => { // Remove this specific image const index = selectedImagesData.indexOf(imageData); if (index > -1) { selectedImagesData.splice(index, 1); } previewContainer.remove(); // Hide preview area if no images left if (selectedImagesData.length === 0) { imagePreviewArea.classList.add('hidden'); imageInput.value = ''; } }); }; reader.readAsDataURL(file); }); // Show preview area imagePreviewArea.classList.remove('hidden'); // Focus on input for caption userInput.focus(); }); // --- SIDE NAVIGATION HANDLING --- // Toggle side navigation toggleNavButton.addEventListener('click', function() { sideNav.classList.add('active'); document.body.insertAdjacentHTML('beforeend', ''); const overlay = document.getElementById('navOverlay'); overlay.classList.add('active'); loadChatHistoryList(); // Close side nav when clicking overlay overlay.addEventListener('click', closeSideNav); }); // Close side navigation closeSideNavButton.addEventListener('click', closeSideNav); function closeSideNav() { sideNav.classList.remove('active'); const overlay = document.getElementById('navOverlay'); if (overlay) { overlay.remove(); } } // Clear chat from nav button if (clearButtonNav) { clearButtonNav.addEventListener('click', function() { clearChat(true); // With confirmation closeSideNav(); }); } // Settings button in side nav if (settingsButtonNav) { settingsButtonNav.addEventListener('click', function() { alert('Fonctionnalité en cours de développement'); closeSideNav(); }); } // Load chat history list function loadChatHistoryList() { fetch('/api/load-chats') .then(response => response.json()) .then(data => { if (data.error) { chatHistoryList.innerHTML = `
${data.error}
`; return; } if (!data.chats || data.chats.length === 0) { chatHistoryList.innerHTML = '
Aucune conversation sauvegardée
'; return; } // Create history items let historyHTML = ''; data.chats.forEach(chat => { // Format timestamp (YYYYMMDD_HHMMSS to readable format) const timestamp = chat.timestamp; const year = timestamp.substring(0, 4); const month = timestamp.substring(4, 6); const day = timestamp.substring(6, 8); const hour = timestamp.substring(9, 11); const minute = timestamp.substring(11, 13); const formattedDate = `${day}/${month}/${year} ${hour}:${minute}`; historyHTML += `
Conversation
${formattedDate}
`; }); chatHistoryList.innerHTML = historyHTML; // Add click event to history items const historyItems = chatHistoryList.querySelectorAll('.chat-history-item'); historyItems.forEach(item => { item.addEventListener('click', function() { const filename = this.getAttribute('data-filename'); loadChatHistory(filename); toggleHistoryDropdown(); }); }); }) .catch(error => { console.error('Error loading chat history:', error); chatHistoryList.innerHTML = '
Erreur lors du chargement de l\'historique
'; }); } // Load specific chat history function loadChatHistory(filename) { fetch(`/api/load-chat/${filename}`) .then(response => response.json()) .then(data => { if (data.error) { alert(data.error); return; } if (data.history) { // Clear current chat clearChat(false); // No confirmation needed // Load history chatHistory = data.history; // Display messages data.history.forEach(msg => { if (msg.sender === 'user') { const messageElement = userMessageTemplate.content.cloneNode(true); messageElement.querySelector('p').textContent = msg.text; chatWindow.appendChild(messageElement); } else { const messageElement = botMessageTemplate.content.cloneNode(true); const messageParagraph = messageElement.querySelector('p'); if (window.marked) { messageParagraph.innerHTML = marked.parse(msg.text); } else { messageParagraph.textContent = msg.text; } chatWindow.appendChild(messageElement); if (window.Prism) { const codeBlocks = messageParagraph.querySelectorAll('pre code'); codeBlocks.forEach(block => { Prism.highlightElement(block); }); } } }); // Scroll to bottom scrollToBottom(); } }) .catch(error => { console.error('Error loading chat:', error); alert('Erreur lors du chargement de la conversation.'); }); } // Save current chat saveCurrentChatButton.addEventListener('click', function() { if (chatHistory.length <= 1) { alert('Aucune conversation à sauvegarder. Veuillez d\'abord discuter avec le chatbot.'); return; } fetch('/api/save-chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ history: chatHistory }) }) .then(response => response.json()) .then(data => { if (data.error) { alert(data.error); } else { alert('Conversation sauvegardée avec succès !'); loadChatHistoryList(); } }) .catch(error => { console.error('Error saving chat:', error); alert('Erreur lors de la sauvegarde de la conversation.'); }); }); // --- TEXT INPUT HANDLING --- // Auto-resize textarea based on content userInput.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px'; // Reset height if empty if (this.value === '') { this.style.height = ''; } }); // Send message when Enter key is pressed (without shift) userInput.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); // Send message when send button is clicked sendButton.addEventListener('click', sendMessage); // Clear chat history when clear button is clicked if (clearButton) { clearButton.addEventListener('click', function() { clearChat(true); // With confirmation }); } // --- CHAT FUNCTIONS --- // Function to clear the chat history function clearChat(withConfirmation = true) { if (!withConfirmation || (chatHistory.length > 0 && confirm('Êtes-vous sûr de vouloir effacer toute la conversation ?'))) { // Clear the chat window except for the welcome message while (chatWindow.childElementCount > 1) { chatWindow.removeChild(chatWindow.lastChild); } // Reset chat history but keep the welcome message const welcomeMsg = chatHistory[0]; chatHistory = welcomeMsg ? [welcomeMsg] : []; // Focus on input field userInput.focus(); } } // Function to send message function sendMessage() { const message = userInput.value.trim(); // Require either text or images if (message === '' && selectedImagesData.length === 0) return; if (selectedImagesData.length > 0) { // Add user message with images to UI addUserImageMessage(message, selectedImagesData[0]); // Pour l'instant afficher seulement la première image dans l'UI } else { // Add user text message to UI addUserMessage(message); } // Clear input field and reset height userInput.value = ''; userInput.style.height = ''; // Show loading indicator const loadingElement = addLoadingIndicator(); // Send message to API sendToAPI(message, loadingElement, selectedImagesData.length > 0 ? selectedImagesData[0] : null); // Clear image previews if any if (selectedImagesData.length > 0) { imagePreviewContainer.innerHTML = ''; imagePreviewArea.classList.add('hidden'); selectedImagesData = []; imageInput.value = ''; } } // Add user message to chat window function addUserMessage(message) { // Hide welcome container if visible (first message) if (!conversationStarted && welcomeContainer) { welcomeContainer.style.display = 'none'; conversationStarted = true; } const messageElement = userMessageTemplate.content.cloneNode(true); messageElement.querySelector('p').textContent = message; chatWindow.appendChild(messageElement); // Add to chat history chatHistory.push({ sender: 'user', text: message }); // Scroll to bottom scrollToBottom(); } // Add user message with image to chat window function addUserImageMessage(message, imageData) { // Hide welcome container if visible (first message) if (!conversationStarted && welcomeContainer) { welcomeContainer.style.display = 'none'; conversationStarted = true; } const messageElement = userImageMessageTemplate.content.cloneNode(true); // Set image source const imageElement = messageElement.querySelector('.chat-image'); imageElement.src = imageData; // Set message text if any const textElement = messageElement.querySelector('p'); if (message) { textElement.textContent = message; } else { textElement.style.display = 'none'; // Hide text element if no message } chatWindow.appendChild(messageElement); // Add to chat history (we don't add the image to history, just the text) chatHistory.push({ sender: 'user', text: message || 'Image envoyée' // Default text if no message provided }); // Scroll to bottom scrollToBottom(); } // Add bot message to chat window with markdown support function addBotMessage(message) { // Hide welcome container if visible (first message) if (!conversationStarted && welcomeContainer) { welcomeContainer.style.display = 'none'; conversationStarted = true; } const messageElement = botMessageTemplate.content.cloneNode(true); const messageParagraph = messageElement.querySelector('p'); // Use the marked library to parse Markdown if available if (window.marked) { messageParagraph.innerHTML = marked.parse(message); } else { messageParagraph.textContent = message; } chatWindow.appendChild(messageElement); // Add syntax highlighting to code blocks if Prism is available if (window.Prism) { const codeBlocks = messageParagraph.querySelectorAll('pre code'); codeBlocks.forEach(block => { Prism.highlightElement(block); }); } // Add to chat history chatHistory.push({ sender: 'bot', text: message }); // Scroll to bottom scrollToBottom(); } // Add loading indicator to chat window function addLoadingIndicator() { const loadingElement = loadingTemplate.content.cloneNode(true); chatWindow.appendChild(loadingElement); // Scroll to bottom scrollToBottom(); // Return the loading element so it can be removed later return chatWindow.lastElementChild; } // Add error message to chat window function addErrorMessage(error, retryMessage, retryImage) { const errorElement = errorMessageTemplate.content.cloneNode(true); errorElement.querySelector('p').textContent = error; // Add retry functionality if a message to retry is provided if (retryMessage || retryImage) { const retryButton = errorElement.querySelector('.retry-button'); retryButton.addEventListener('click', function() { // Remove the error message this.closest('.message-container').remove(); // Show loading indicator const loadingElement = addLoadingIndicator(); // Retry sending the message sendToAPI(retryMessage, loadingElement, retryImage); }); } else { // Hide retry button if no retry message errorElement.querySelector('.retry-button').style.display = 'none'; } chatWindow.appendChild(errorElement); // Scroll to bottom scrollToBottom(); } // Send message to API function sendToAPI(message, loadingElement, imageData = null) { // Disable input while processing userInput.disabled = true; sendButton.disabled = true; uploadImageButton.disabled = true; if (clearButton) clearButton.disabled = true; // Prepare request data const requestData = { message: message, history: chatHistory }; // Add image data if provided if (imageData) { requestData.image = imageData; } fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData) }) .then(response => { if (!response.ok) { throw new Error(`Server responded with status: ${response.status}`); } return response.json(); }) .then(data => { // Remove loading indicator if (loadingElement) { loadingElement.remove(); } // Check for error if (data.error) { addErrorMessage(data.error, message, imageData); } else { // Add bot response addBotMessage(data.response); } }) .catch(error => { console.error('Error:', error); // Remove loading indicator if (loadingElement) { loadingElement.remove(); } // Add error message addErrorMessage('Désolé, il y a eu un problème de connexion avec le serveur. Veuillez réessayer.', message, imageData); }) .finally(() => { // Re-enable input userInput.disabled = false; sendButton.disabled = false; uploadImageButton.disabled = false; if (clearButton) clearButton.disabled = false; userInput.focus(); }); } // Scroll chat window to bottom function scrollToBottom() { chatWindow.scrollTop = chatWindow.scrollHeight; } // Setup suggestion bubbles click handlers if (suggestionBubbles) { suggestionBubbles.forEach(bubble => { bubble.addEventListener('click', function() { const prompt = this.getAttribute('data-prompt'); if (prompt) { userInput.value = prompt; sendMessage(); } }); }); } // Initial focus on input field userInput.focus(); });