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}`; | |
| } | |
| }); | |