Spaces:
Running
Running
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 = '<i class="bi bi-x-circle-fill"></i>'; | |
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', '<div class="overlay" id="navOverlay"></div>'); | |
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 = `<div class="empty-state">${data.error}</div>`; | |
return; | |
} | |
if (!data.chats || data.chats.length === 0) { | |
chatHistoryList.innerHTML = '<div class="empty-state">Aucune conversation sauvegardée</div>'; | |
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 += ` | |
<div class="chat-history-item" data-filename="${chat.filename}"> | |
<div class="icon"><i class="bi bi-chat-dots"></i></div> | |
<div class="details"> | |
<div>Conversation</div> | |
<div class="timestamp">${formattedDate}</div> | |
</div> | |
</div> | |
`; | |
}); | |
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 = '<div class="empty-state">Erreur lors du chargement de l\'historique</div>'; | |
}); | |
} | |
// 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(); | |
}); | |