Spaces:
Sleeping
Sleeping
/* Cosmic Chatbot - Scripts JavaScript */ | |
// Attendre que le DOM soit complètement chargé | |
document.addEventListener('DOMContentLoaded', function() { | |
// Éléments DOM | |
const messagesDiv = document.getElementById('messages'); | |
const chatInput = document.getElementById('chatInput'); | |
const sendButton = document.getElementById('sendButton'); | |
const fileInput = document.getElementById('fileInput'); | |
const filePreviewArea = document.getElementById('filePreviewArea'); | |
const themeToggle = document.getElementById('themeToggle'); | |
const featureToggle = document.getElementById('featureToggle'); | |
const featureShowcase = document.getElementById('featureShowcase'); | |
const starsContainer = document.getElementById('starsContainer'); | |
const particlesContainer = document.getElementById('particlesContainer'); | |
// Variables globales | |
let currentTheme = 'dark'; | |
let selectedFile = null; | |
// Initialisation | |
initializeStars(); | |
initializeParticles(); | |
initializeEventListeners(); | |
// Fonctions d'initialisation | |
function initializeStars() { | |
// Créer des étoiles avec des positions et tailles aléatoires | |
for (let i = 0; i < 100; i++) { | |
const star = document.createElement('div'); | |
star.className = 'star'; | |
// Position aléatoire | |
const x = Math.random() * 100; | |
const y = Math.random() * 100; | |
// Taille aléatoire | |
const size = Math.random() * 2 + 1; | |
// Délai d'animation aléatoire | |
const delay = Math.random() * 3; | |
// Appliquer les styles | |
star.style.left = `${x}%`; | |
star.style.top = `${y}%`; | |
star.style.width = `${size}px`; | |
star.style.height = `${size}px`; | |
star.style.animationDelay = `${delay}s`; | |
starsContainer.appendChild(star); | |
} | |
} | |
function initializeParticles() { | |
// Créer des particules flottantes | |
for (let i = 0; i < 15; i++) { | |
const particle = document.createElement('div'); | |
particle.className = 'particle'; | |
// Position aléatoire | |
const x = Math.random() * 100; | |
const y = Math.random() * 100; | |
// Taille aléatoire | |
const size = Math.random() * 50 + 20; | |
// Délai d'animation aléatoire | |
const delay = Math.random() * 5; | |
// Appliquer les styles | |
particle.style.left = `${x}%`; | |
particle.style.top = `${y}%`; | |
particle.style.width = `${size}px`; | |
particle.style.height = `${size}px`; | |
particle.style.animationDelay = `${delay}s`; | |
particlesContainer.appendChild(particle); | |
} | |
} | |
function initializeEventListeners() { | |
// Événement d'envoi de message | |
sendButton.addEventListener('click', processInput); | |
chatInput.addEventListener('keydown', (e) => { | |
if (e.key === 'Enter' && !e.shiftKey) { | |
e.preventDefault(); | |
processInput(); | |
} | |
}); | |
// Événement de changement de thème | |
themeToggle.addEventListener('click', toggleTheme); | |
// Événement d'affichage des fonctionnalités | |
featureToggle.addEventListener('click', toggleFeatureShowcase); | |
// Événement de sélection de fichier | |
fileInput.addEventListener('change', handleFileSelection); | |
// Événement de clic sur une carte de fonctionnalité | |
document.querySelectorAll('.feature-card').forEach(card => { | |
card.addEventListener('click', () => { | |
const feature = card.getAttribute('data-feature'); | |
suggestFeaturePrompt(feature); | |
}); | |
}); | |
} | |
// Fonctions de gestion des événements | |
function toggleTheme() { | |
const body = document.body; | |
const icon = themeToggle.querySelector('i'); | |
if (currentTheme === 'dark') { | |
// Passer au thème clair | |
body.classList.add('light-theme'); | |
icon.className = 'bx bx-sun'; | |
currentTheme = 'light'; | |
} else { | |
// Passer au thème sombre | |
body.classList.remove('light-theme'); | |
icon.className = 'bx bx-moon'; | |
currentTheme = 'dark'; | |
} | |
// Ajouter une classe pour la transition | |
body.classList.add('theme-transition'); | |
// Supprimer la classe après la transition | |
setTimeout(() => { | |
body.classList.remove('theme-transition'); | |
}, 500); | |
} | |
function toggleFeatureShowcase() { | |
featureShowcase.classList.toggle('active'); | |
// Changer l'icône et le texte du bouton | |
if (featureShowcase.classList.contains('active')) { | |
featureToggle.innerHTML = '<i class="bx bx-x"></i> Fermer'; | |
} else { | |
featureToggle.innerHTML = '<i class="bx bx-bulb"></i> Fonctionnalités'; | |
} | |
} | |
function handleFileSelection(e) { | |
const file = e.target.files[0]; | |
if (!file) return; | |
selectedFile = file; | |
// Afficher l'aperçu du fichier | |
filePreviewArea.innerHTML = ''; | |
const preview = document.createElement('div'); | |
preview.className = 'file-preview'; | |
// Déterminer l'icône en fonction du type de fichier | |
let iconClass = 'bx-file'; | |
if (file.type.startsWith('image/')) { | |
iconClass = 'bx-image'; | |
} else if (file.name.endsWith('.pdf')) { | |
iconClass = 'bx-file-pdf'; | |
} else if (file.name.endsWith('.xlsx') || file.name.endsWith('.xls')) { | |
iconClass = 'bx-spreadsheet'; | |
} else if (file.name.endsWith('.docx') || file.name.endsWith('.doc')) { | |
iconClass = 'bx-file-doc'; | |
} | |
preview.innerHTML = ` | |
<div class="file-preview-icon"><i class="bx ${iconClass}"></i></div> | |
<div class="file-preview-name">${file.name}</div> | |
<button class="file-preview-remove"><i class="bx bx-x"></i></button> | |
`; | |
filePreviewArea.appendChild(preview); | |
// Ajouter un événement pour supprimer l'aperçu | |
preview.querySelector('.file-preview-remove').addEventListener('click', () => { | |
filePreviewArea.innerHTML = ''; | |
fileInput.value = ''; | |
selectedFile = null; | |
}); | |
} | |
function suggestFeaturePrompt(feature) { | |
let prompt = ''; | |
switch (feature) { | |
case 'summarize': | |
prompt = 'Pouvez-vous résumer ce document pour moi ?'; | |
break; | |
case 'image-caption': | |
chatInput.value = ''; | |
fileInput.click(); | |
return; | |
case 'qa': | |
prompt = 'Pouvez-vous répondre à cette question : '; | |
break; | |
case 'vqa': | |
prompt = 'Que pouvez-vous me dire sur cette image ?'; | |
fileInput.click(); | |
break; | |
case 'visualization': | |
prompt = 'Générez un graphique à partir de ces données Excel'; | |
fileInput.click(); | |
return; | |
case 'translate': | |
prompt = 'Traduisez ce texte en français : '; | |
break; | |
} | |
chatInput.value = prompt; | |
chatInput.focus(); | |
// Placer le curseur à la fin du texte | |
const len = chatInput.value.length; | |
chatInput.setSelectionRange(len, len); | |
// Fermer le showcase | |
featureShowcase.classList.remove('active'); | |
featureToggle.innerHTML = '<i class="bx bx-bulb"></i> Fonctionnalités'; | |
} | |
// Fonctions de traitement des messages | |
async function processInput() { | |
const text = chatInput.value.trim(); | |
const file = selectedFile; | |
if (!text && !file) return; | |
// Ajouter le message de l'utilisateur | |
if (text) { | |
addMessage(text, true); | |
} | |
if (file) { | |
addMessage(`📄 ${file.name}`, true); | |
filePreviewArea.innerHTML = ''; | |
} | |
// Effacer les entrées | |
chatInput.value = ''; | |
selectedFile = null; | |
// Afficher l'indicateur de frappe | |
showTyping(); | |
try { | |
const formData = new FormData(); | |
if (file) formData.append('file', file); | |
if (text) formData.append('text', text); | |
const response = await fetch('/process', { | |
method: 'POST', | |
body: formData | |
}); | |
if (!response.ok) throw new Error('Erreur serveur'); | |
const data = await response.json(); | |
// Supprimer l'indicateur de frappe | |
document.getElementById('typingIndicator')?.remove(); | |
// Formater la réponse en fonction du type | |
let responseText = data.response; | |
if (data.type === 'visualization_code') { | |
responseText = `Voici le code de visualisation :\n\`\`\`python\n${data.response}\n\`\`\``; | |
} else if (data.type === 'caption' && file && file.type.startsWith('image/')) { | |
// Pour les images, afficher l'image avec la légende | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
const imgPreview = `<img src="${e.target.result}" alt="Image téléchargée" class="image-preview">`; | |
addMessage(`${imgPreview}<p>${responseText}</p>`, false, true); | |
}; | |
reader.readAsDataURL(file); | |
return; | |
} | |
addMessage(responseText); | |
} catch (error) { | |
document.getElementById('typingIndicator')?.remove(); | |
addMessage(`Erreur: ${error.message}`); | |
} | |
} | |
function addMessage(content, isUser = false, isHTML = false) { | |
const msgDiv = document.createElement('div'); | |
msgDiv.className = `message ${isUser ? 'user-message' : 'bot-message'}`; | |
// Ajouter l'avatar | |
const avatar = document.createElement('div'); | |
avatar.className = 'message-avatar'; | |
avatar.innerHTML = isUser ? '<i class="bx bx-user"></i>' : '<i class="bx bx-bot"></i>'; | |
msgDiv.appendChild(avatar); | |
// Ajouter l'heure | |
const time = document.createElement('div'); | |
time.className = 'message-time'; | |
time.textContent = formatTime(new Date()); | |
// Formater le contenu | |
if (isHTML) { | |
// Si le contenu est du HTML (pour les images) | |
msgDiv.innerHTML += content; | |
} else if (content.includes('```')) { | |
// Formater les blocs de code | |
const contentDiv = document.createElement('div'); | |
contentDiv.className = 'markdown-content'; | |
const parts = content.split(/```([\s\S]*?)```/); | |
parts.forEach((part, i) => { | |
if (i % 2 === 1) { | |
// Bloc de code | |
const pre = document.createElement('div'); | |
pre.className = 'code-block'; | |
pre.textContent = part.trim(); | |
// Bouton de copie | |
const copyBtn = document.createElement('button'); | |
copyBtn.className = 'copy-code'; | |
copyBtn.textContent = 'Copier'; | |
copyBtn.addEventListener('click', () => { | |
navigator.clipboard.writeText(part.trim()); | |
copyBtn.textContent = 'Copié !'; | |
setTimeout(() => { | |
copyBtn.textContent = 'Copier'; | |
}, 2000); | |
}); | |
pre.appendChild(copyBtn); | |
contentDiv.appendChild(pre); | |
} else if (part.trim()) { | |
// Texte normal | |
const p = document.createElement('p'); | |
p.textContent = part.trim(); | |
contentDiv.appendChild(p); | |
} | |
}); | |
msgDiv.appendChild(contentDiv); | |
} else { | |
// Texte normal avec formatage Markdown basique | |
const contentDiv = document.createElement('div'); | |
contentDiv.className = 'markdown-content'; | |
// Convertir les listes | |
let formattedContent = content; | |
if (content.includes('\n- ')) { | |
const listItems = content.split('\n- '); | |
formattedContent = listItems[0]; | |
if (listItems.length > 1) { | |
const ul = document.createElement('ul'); | |
for (let i = 1; i < listItems.length; i++) { | |
const li = document.createElement('li'); | |
li.textContent = listItems[i].trim(); | |
ul.appendChild(li); | |
} | |
contentDiv.innerHTML = `<p>${formattedContent}</p>`; | |
contentDiv.appendChild(ul); | |
msgDiv.appendChild(contentDiv); | |
} else { | |
contentDiv.textContent = formattedContent; | |
msgDiv.appendChild(contentDiv); | |
} | |
} else { | |
contentDiv.textContent = formattedContent; | |
msgDiv.appendChild(contentDiv); | |
} | |
} | |
msgDiv.appendChild(time); | |
messagesDiv.appendChild(msgDiv); | |
messagesDiv.scrollTop = messagesDiv.scrollHeight; | |
return msgDiv; | |
} | |
function showTyping() { | |
const typingDiv = document.createElement('div'); | |
typingDiv.className = 'typing'; | |
typingDiv.id = 'typingIndicator'; | |
for (let i = 0; i < 3; i++) { | |
const dot = document.createElement('div'); | |
dot.className = 'typing-dot'; | |
typingDiv.appendChild(dot); | |
} | |
messagesDiv.appendChild(typingDiv); | |
messagesDiv.scrollTop = messagesDiv.scrollHeight; | |
} | |
// Fonctions utilitaires | |
function formatTime(date) { | |
const hours = date.getHours().toString().padStart(2, '0'); | |
const minutes = date.getMinutes().toString().padStart(2, '0'); | |
return `${hours}:${minutes}`; | |
} | |
}); | |