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'); const offlineIndicator = document.getElementById('offlineIndicator'); // 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 isOffline = !navigator.onLine; // Vérifier l'état de la connexion au chargement // Gestion de l'état de la connexion pour PWA function updateOnlineStatus() { isOffline = !navigator.onLine; if (isOffline) { // L'utilisateur est hors ligne offlineIndicator.classList.add('visible'); // Désactiver temporairement le bouton d'envoi sendButton.disabled = true; sendButton.title = "Hors ligne - Reconnectez-vous pour envoyer des messages"; } else { // L'utilisateur est en ligne offlineIndicator.classList.remove('visible'); // Réactiver le bouton d'envoi sendButton.disabled = false; sendButton.title = "Envoyer"; } } // Installer les événements pour détecter les changements de connectivité window.addEventListener('online', updateOnlineStatus); window.addEventListener('offline', updateOnlineStatus); // Appliquer l'état initial updateOnlineStatus(); 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() { Swal.fire({ icon: 'info', title: 'En cours de développement', text: 'Cette fonctionnalité sera disponible prochainement.' }); 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) { Swal.fire({ icon: 'error', title: 'Erreur', text: data.error }); return; } if (data.history) { // Clear current chat clearChatContent(); // 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); Swal.fire({ icon: 'error', title: 'Erreur', text: 'Erreur lors du chargement de la conversation.' }); }); } // Save current chat saveCurrentChatButton.addEventListener('click', function() { if (chatHistory.length <= 1) { Swal.fire({ icon: 'info', title: 'Information', text: '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) { Swal.fire({ icon: 'error', title: 'Erreur', text: data.error }); } else { Swal.fire({ icon: 'success', title: 'Sauvegardé', text: 'Conversation sauvegardée avec succès !', timer: 1500, showConfirmButton: false }); loadChatHistoryList(); } }) .catch(error => { console.error('Error saving chat:', error); Swal.fire({ icon: 'error', title: 'Erreur', text: '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) { // Clear chat immediately without confirmation clearChatContent(); return; } if (chatHistory.length > 0) { Swal.fire({ title: 'Êtes-vous sûr ?', text: 'Voulez-vous effacer toute la conversation ?', icon: 'warning', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', confirmButtonText: 'Oui, effacer', cancelButtonText: 'Annuler' }).then((result) => { if (result.isConfirmed) { clearChatContent(); } }); } } // Helper function to clear chat content function clearChatContent() { // 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] : []; // Show welcome container again if (welcomeContainer) { welcomeContainer.style.display = 'flex'; conversationStarted = false; } // 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); // Maintenant on passe toutes les images } 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 const imagesToSend = selectedImagesData.length > 0 ? selectedImagesData : null; sendToAPI(message, loadingElement, imagesToSend); // 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 multiple images to chat window function addUserImageMessage(message, imagesData) { // Hide welcome container if visible (first message) if (!conversationStarted && welcomeContainer) { welcomeContainer.style.display = 'none'; conversationStarted = true; } const messageElement = userImageMessageTemplate.content.cloneNode(true); // Handle multiple images const imageContainer = messageElement.querySelector('.image-container'); // If imagesData is an array, add all images if (Array.isArray(imagesData)) { // Clear existing image elements imageContainer.innerHTML = ''; // Add each image imagesData.forEach(imgData => { const img = document.createElement('img'); img.className = 'chat-image'; img.src = imgData; img.alt = 'Chat image'; imageContainer.appendChild(img); }); } else if (imagesData) { // Single image case const imageElement = messageElement.querySelector('.chat-image'); imageElement.src = imagesData; } // 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 || 'Images envoyées' // 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; } // Add copy button functionality const copyButton = messageElement.querySelector('.copy-button'); if (copyButton) { copyButton.addEventListener('click', function() { // Get the text to copy (original message, not HTML) navigator.clipboard.writeText(message).then(() => { // Show success notification with SweetAlert2 Swal.fire({ toast: true, position: 'top-end', icon: 'success', title: 'Texte copié !', showConfirmButton: false, timer: 1500 }); }).catch(err => { console.error('Erreur lors de la copie :', err); Swal.fire({ toast: true, position: 'top-end', icon: 'error', title: 'Erreur lors de la copie', showConfirmButton: false, timer: 1500 }); }); }); } 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) { // If imageData is an array of images, send them all 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 different error message based on online/offline status if (!navigator.onLine) { // Custom error message for offline mode addErrorMessage('Vous êtes actuellement hors ligne. Connectez-vous à Internet pour discuter avec Mariam AI.', message, imageData); // Update the status indicator updateOnlineStatus(); } else { // Generic error for other connection issues 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(); });