|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
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'); |
|
|
|
|
|
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'); |
|
|
|
|
|
let chatHistory = []; |
|
let isOffline = !navigator.onLine; |
|
|
|
|
|
function updateOnlineStatus() { |
|
isOffline = !navigator.onLine; |
|
|
|
if (isOffline) { |
|
|
|
offlineIndicator.classList.add('visible'); |
|
|
|
sendButton.disabled = true; |
|
sendButton.title = "Hors ligne - Reconnectez-vous pour envoyer des messages"; |
|
} else { |
|
|
|
offlineIndicator.classList.remove('visible'); |
|
|
|
sendButton.disabled = false; |
|
sendButton.title = "Envoyer"; |
|
} |
|
} |
|
|
|
|
|
window.addEventListener('online', updateOnlineStatus); |
|
window.addEventListener('offline', updateOnlineStatus); |
|
|
|
|
|
updateOnlineStatus(); |
|
let selectedImagesData = []; |
|
let conversationStarted = false; |
|
|
|
|
|
|
|
|
|
uploadImageButton.addEventListener('click', function() { |
|
imageInput.click(); |
|
}); |
|
|
|
|
|
imageInput.addEventListener('change', function(e) { |
|
const files = Array.from(e.target.files); |
|
if (!files.length) return; |
|
|
|
|
|
imagePreviewContainer.innerHTML = ''; |
|
selectedImagesData = []; |
|
|
|
|
|
files.forEach(file => { |
|
|
|
if (!file.type.startsWith('image/')) { |
|
alert('Veuillez sélectionner uniquement des fichiers image valides.'); |
|
return; |
|
} |
|
|
|
|
|
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) { |
|
|
|
const previewContainer = document.createElement('div'); |
|
previewContainer.className = 'image-preview-container'; |
|
|
|
|
|
const imgPreview = document.createElement('img'); |
|
imgPreview.src = event.target.result; |
|
imgPreview.alt = 'Image preview'; |
|
previewContainer.appendChild(imgPreview); |
|
|
|
|
|
const removeBtn = document.createElement('button'); |
|
removeBtn.className = 'remove-image-button'; |
|
removeBtn.innerHTML = '<i class="bi bi-x-circle-fill"></i>'; |
|
previewContainer.appendChild(removeBtn); |
|
|
|
|
|
imagePreviewContainer.appendChild(previewContainer); |
|
|
|
|
|
const imageData = event.target.result; |
|
selectedImagesData.push(imageData); |
|
|
|
|
|
removeBtn.addEventListener('click', () => { |
|
|
|
const index = selectedImagesData.indexOf(imageData); |
|
if (index > -1) { |
|
selectedImagesData.splice(index, 1); |
|
} |
|
previewContainer.remove(); |
|
|
|
|
|
if (selectedImagesData.length === 0) { |
|
imagePreviewArea.classList.add('hidden'); |
|
imageInput.value = ''; |
|
} |
|
}); |
|
}; |
|
reader.readAsDataURL(file); |
|
}); |
|
|
|
|
|
imagePreviewArea.classList.remove('hidden'); |
|
|
|
|
|
userInput.focus(); |
|
}); |
|
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
overlay.addEventListener('click', closeSideNav); |
|
}); |
|
|
|
|
|
closeSideNavButton.addEventListener('click', closeSideNav); |
|
|
|
function closeSideNav() { |
|
sideNav.classList.remove('active'); |
|
const overlay = document.getElementById('navOverlay'); |
|
if (overlay) { |
|
overlay.remove(); |
|
} |
|
} |
|
|
|
|
|
if (clearButtonNav) { |
|
clearButtonNav.addEventListener('click', function() { |
|
clearChat(true); |
|
closeSideNav(); |
|
}); |
|
} |
|
|
|
|
|
if (settingsButtonNav) { |
|
settingsButtonNav.addEventListener('click', function() { |
|
Swal.fire({ |
|
icon: 'info', |
|
title: 'En cours de développement', |
|
text: 'Cette fonctionnalité sera disponible prochainement.' |
|
}); |
|
closeSideNav(); |
|
}); |
|
} |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
let historyHTML = ''; |
|
data.chats.forEach(chat => { |
|
|
|
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; |
|
|
|
|
|
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>'; |
|
}); |
|
} |
|
|
|
|
|
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) { |
|
|
|
clearChatContent(); |
|
|
|
|
|
chatHistory = data.history; |
|
|
|
|
|
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); |
|
}); |
|
} |
|
} |
|
}); |
|
|
|
|
|
scrollToBottom(); |
|
} |
|
}) |
|
.catch(error => { |
|
console.error('Error loading chat:', error); |
|
Swal.fire({ |
|
icon: 'error', |
|
title: 'Erreur', |
|
text: 'Erreur lors du chargement de la conversation.' |
|
}); |
|
}); |
|
} |
|
|
|
|
|
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.' |
|
}); |
|
}); |
|
}); |
|
|
|
|
|
|
|
|
|
userInput.addEventListener('input', function() { |
|
this.style.height = 'auto'; |
|
this.style.height = (this.scrollHeight) + 'px'; |
|
|
|
if (this.value === '') { |
|
this.style.height = ''; |
|
} |
|
}); |
|
|
|
|
|
userInput.addEventListener('keydown', function(e) { |
|
if (e.key === 'Enter' && !e.shiftKey) { |
|
e.preventDefault(); |
|
sendMessage(); |
|
} |
|
}); |
|
|
|
|
|
sendButton.addEventListener('click', sendMessage); |
|
|
|
|
|
if (clearButton) { |
|
clearButton.addEventListener('click', function() { |
|
clearChat(true); |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
function clearChat(withConfirmation = true) { |
|
if (!withConfirmation) { |
|
|
|
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(); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
|
|
function clearChatContent() { |
|
|
|
while (chatWindow.childElementCount > 1) { |
|
chatWindow.removeChild(chatWindow.lastChild); |
|
} |
|
|
|
|
|
const welcomeMsg = chatHistory[0]; |
|
chatHistory = welcomeMsg ? [welcomeMsg] : []; |
|
|
|
|
|
if (welcomeContainer) { |
|
welcomeContainer.style.display = 'flex'; |
|
conversationStarted = false; |
|
} |
|
|
|
|
|
userInput.focus(); |
|
} |
|
|
|
|
|
function sendMessage() { |
|
const message = userInput.value.trim(); |
|
|
|
|
|
if (message === '' && selectedImagesData.length === 0) return; |
|
|
|
if (selectedImagesData.length > 0) { |
|
|
|
addUserImageMessage(message, selectedImagesData); |
|
} else { |
|
|
|
addUserMessage(message); |
|
} |
|
|
|
|
|
userInput.value = ''; |
|
userInput.style.height = ''; |
|
|
|
|
|
const loadingElement = addLoadingIndicator(); |
|
|
|
|
|
const imagesToSend = selectedImagesData.length > 0 ? selectedImagesData : null; |
|
sendToAPI(message, loadingElement, imagesToSend); |
|
|
|
|
|
if (selectedImagesData.length > 0) { |
|
imagePreviewContainer.innerHTML = ''; |
|
imagePreviewArea.classList.add('hidden'); |
|
selectedImagesData = []; |
|
imageInput.value = ''; |
|
} |
|
} |
|
|
|
|
|
function addUserMessage(message) { |
|
|
|
if (!conversationStarted && welcomeContainer) { |
|
welcomeContainer.style.display = 'none'; |
|
conversationStarted = true; |
|
} |
|
|
|
const messageElement = userMessageTemplate.content.cloneNode(true); |
|
messageElement.querySelector('p').textContent = message; |
|
chatWindow.appendChild(messageElement); |
|
|
|
|
|
chatHistory.push({ |
|
sender: 'user', |
|
text: message |
|
}); |
|
|
|
|
|
scrollToBottom(); |
|
} |
|
|
|
|
|
function addUserImageMessage(message, imagesData) { |
|
|
|
if (!conversationStarted && welcomeContainer) { |
|
welcomeContainer.style.display = 'none'; |
|
conversationStarted = true; |
|
} |
|
|
|
const messageElement = userImageMessageTemplate.content.cloneNode(true); |
|
|
|
|
|
const imageContainer = messageElement.querySelector('.image-container'); |
|
|
|
|
|
if (Array.isArray(imagesData)) { |
|
|
|
imageContainer.innerHTML = ''; |
|
|
|
|
|
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) { |
|
|
|
const imageElement = messageElement.querySelector('.chat-image'); |
|
imageElement.src = imagesData; |
|
} |
|
|
|
|
|
const textElement = messageElement.querySelector('p'); |
|
if (message) { |
|
textElement.textContent = message; |
|
} else { |
|
textElement.style.display = 'none'; |
|
} |
|
|
|
chatWindow.appendChild(messageElement); |
|
|
|
|
|
chatHistory.push({ |
|
sender: 'user', |
|
text: message || 'Images envoyées' |
|
}); |
|
|
|
|
|
scrollToBottom(); |
|
} |
|
|
|
|
|
function addBotMessage(message) { |
|
|
|
if (!conversationStarted && welcomeContainer) { |
|
welcomeContainer.style.display = 'none'; |
|
conversationStarted = true; |
|
} |
|
|
|
const messageElement = botMessageTemplate.content.cloneNode(true); |
|
const messageParagraph = messageElement.querySelector('p'); |
|
|
|
|
|
if (window.marked) { |
|
messageParagraph.innerHTML = marked.parse(message); |
|
} else { |
|
messageParagraph.textContent = message; |
|
} |
|
|
|
|
|
const copyButton = messageElement.querySelector('.copy-button'); |
|
if (copyButton) { |
|
copyButton.addEventListener('click', function() { |
|
|
|
navigator.clipboard.writeText(message).then(() => { |
|
|
|
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); |
|
|
|
|
|
if (window.Prism) { |
|
const codeBlocks = messageParagraph.querySelectorAll('pre code'); |
|
codeBlocks.forEach(block => { |
|
Prism.highlightElement(block); |
|
}); |
|
} |
|
|
|
|
|
chatHistory.push({ |
|
sender: 'bot', |
|
text: message |
|
}); |
|
|
|
|
|
scrollToBottom(); |
|
} |
|
|
|
|
|
function addLoadingIndicator() { |
|
const loadingElement = loadingTemplate.content.cloneNode(true); |
|
chatWindow.appendChild(loadingElement); |
|
|
|
|
|
scrollToBottom(); |
|
|
|
|
|
return chatWindow.lastElementChild; |
|
} |
|
|
|
|
|
function addErrorMessage(error, retryMessage, retryImage) { |
|
const errorElement = errorMessageTemplate.content.cloneNode(true); |
|
errorElement.querySelector('p').textContent = error; |
|
|
|
|
|
if (retryMessage || retryImage) { |
|
const retryButton = errorElement.querySelector('.retry-button'); |
|
retryButton.addEventListener('click', function() { |
|
|
|
this.closest('.message-container').remove(); |
|
|
|
|
|
const loadingElement = addLoadingIndicator(); |
|
|
|
|
|
sendToAPI(retryMessage, loadingElement, retryImage); |
|
}); |
|
} else { |
|
|
|
errorElement.querySelector('.retry-button').style.display = 'none'; |
|
} |
|
|
|
chatWindow.appendChild(errorElement); |
|
|
|
|
|
scrollToBottom(); |
|
} |
|
|
|
|
|
function sendToAPI(message, loadingElement, imageData = null) { |
|
|
|
userInput.disabled = true; |
|
sendButton.disabled = true; |
|
uploadImageButton.disabled = true; |
|
if (clearButton) clearButton.disabled = true; |
|
|
|
|
|
const requestData = { |
|
message: message, |
|
history: chatHistory |
|
}; |
|
|
|
|
|
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 => { |
|
|
|
if (loadingElement) { |
|
loadingElement.remove(); |
|
} |
|
|
|
|
|
if (data.error) { |
|
addErrorMessage(data.error, message, imageData); |
|
} else { |
|
|
|
addBotMessage(data.response); |
|
} |
|
}) |
|
.catch(error => { |
|
console.error('Error:', error); |
|
|
|
|
|
if (loadingElement) { |
|
loadingElement.remove(); |
|
} |
|
|
|
|
|
if (!navigator.onLine) { |
|
|
|
addErrorMessage('Vous êtes actuellement hors ligne. Connectez-vous à Internet pour discuter avec Mariam AI.', message, imageData); |
|
|
|
updateOnlineStatus(); |
|
} else { |
|
|
|
addErrorMessage('Désolé, il y a eu un problème de connexion avec le serveur. Veuillez réessayer.', message, imageData); |
|
} |
|
}) |
|
.finally(() => { |
|
|
|
userInput.disabled = false; |
|
sendButton.disabled = false; |
|
uploadImageButton.disabled = false; |
|
if (clearButton) clearButton.disabled = false; |
|
userInput.focus(); |
|
}); |
|
} |
|
|
|
|
|
function scrollToBottom() { |
|
chatWindow.scrollTop = chatWindow.scrollHeight; |
|
} |
|
|
|
|
|
if (suggestionBubbles) { |
|
suggestionBubbles.forEach(bubble => { |
|
bubble.addEventListener('click', function() { |
|
const prompt = this.getAttribute('data-prompt'); |
|
if (prompt) { |
|
userInput.value = prompt; |
|
sendMessage(); |
|
} |
|
}); |
|
}); |
|
} |
|
|
|
|
|
userInput.focus(); |
|
}); |
|
|