From f14ffdf35d09ab57e804ed860c177634ad1e5baf Mon Sep 17 00:00:00 2001 From: nicoboy Date: Sat, 3 Jan 2026 14:12:35 +0100 Subject: [PATCH] ajout chatbot.css et .js --- assets/css/chatbot.css | 311 +++++++++++++++++++++++++++++++++++++++++ assets/js/chatbot.js | 199 ++++++++++++++++++++++++++ 2 files changed, 510 insertions(+) create mode 100644 assets/css/chatbot.css create mode 100644 assets/js/chatbot.js diff --git a/assets/css/chatbot.css b/assets/css/chatbot.css new file mode 100644 index 0000000..e283aaf --- /dev/null +++ b/assets/css/chatbot.css @@ -0,0 +1,311 @@ +/* ==================================== + CHATBOT IA - STYLES + ==================================== */ + +/* Bouton flottant */ +.chat-bubble { + position: fixed; + bottom: 80px; + right: 20px; + z-index: 1000; +} + +.btn-chat { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + padding: 15px 25px; + border-radius: 50px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 8px; +} + +.btn-chat:hover { + transform: translateY(-3px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6); +} + +.btn-chat:active { + transform: translateY(-1px); +} + +.help-badge { + background: rgba(255, 255, 255, 0.3); + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; +} + +/* Modal chat */ +#modal-chat { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: none; + z-index: 2000; + align-items: center; + justify-content: center; + animation: fadeIn 0.2s ease; +} + +#modal-chat.show { + display: flex; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.chat-container { + background: white; + width: 90%; + max-width: 500px; + height: 600px; + max-height: 80vh; + border-radius: 15px; + display: flex; + flex-direction: column; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); + animation: slideUp 0.3s ease; +} + +@keyframes slideUp { + from { + transform: translateY(50px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.chat-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 20px; + border-radius: 15px 15px 0 0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.chat-header h3 { + margin: 0; + font-size: 18px; + font-weight: 600; +} + +.btn-close-chat { + background: none; + border: none; + color: white; + font-size: 28px; + cursor: pointer; + padding: 0; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: background 0.2s; +} + +.btn-close-chat:hover { + background: rgba(255, 255, 255, 0.2); +} + +.chat-messages { + flex: 1; + padding: 20px; + overflow-y: auto; + background: #f5f5f5; +} + +.chat-messages::-webkit-scrollbar { + width: 6px; +} + +.chat-messages::-webkit-scrollbar-track { + background: #f1f1f1; +} + +.chat-messages::-webkit-scrollbar-thumb { + background: #667eea; + border-radius: 3px; +} + +.message { + margin-bottom: 15px; + padding: 12px 15px; + border-radius: 12px; + max-width: 85%; + animation: messageAppear 0.3s ease; + word-wrap: break-word; +} + +@keyframes messageAppear { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.message-user { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + margin-left: auto; + text-align: right; + border-bottom-right-radius: 4px; +} + +.message-assistant { + background: white; + color: #333; + border: 1px solid #e0e0e0; + margin-right: auto; + border-bottom-left-radius: 4px; +} + +.message-assistant::before { + content: "🤖 "; + font-size: 14px; +} + +.message-system { + background: #e3f2fd; + color: #1976d2; + text-align: center; + font-size: 14px; + padding: 10px; + margin: 10px auto; + border-radius: 8px; + max-width: 90%; +} + +.chat-loading { + text-align: center; + padding: 15px; + color: #999; + font-style: italic; +} + +.chat-loading::before { + content: "💭 "; +} + +.typing-indicator { + display: inline-flex; + gap: 4px; + padding: 10px 15px; + background: white; + border-radius: 12px; + border: 1px solid #e0e0e0; +} + +.typing-dot { + width: 8px; + height: 8px; + background: #667eea; + border-radius: 50%; + animation: typing 1.4s infinite; +} + +.typing-dot:nth-child(2) { + animation-delay: 0.2s; +} + +.typing-dot:nth-child(3) { + animation-delay: 0.4s; +} + +@keyframes typing { + 0%, 60%, 100% { + transform: translateY(0); + opacity: 0.5; + } + 30% { + transform: translateY(-10px); + opacity: 1; + } +} + +.chat-input-zone { + padding: 15px; + border-top: 1px solid #e0e0e0; + background: white; + border-radius: 0 0 15px 15px; +} + +.chat-input-zone textarea { + width: 100%; + padding: 12px; + border: 2px solid #e0e0e0; + border-radius: 8px; + resize: none; + font-family: inherit; + font-size: 14px; + transition: border-color 0.2s; +} + +.chat-input-zone textarea:focus { + outline: none; + border-color: #667eea; +} + +.chat-input-zone button { + margin-top: 10px; + width: 100%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + padding: 12px; + border-radius: 8px; + font-weight: 600; + font-size: 14px; + cursor: pointer; + transition: all 0.2s; +} + +.chat-input-zone button:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); +} + +.chat-input-zone button:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Responsive */ +@media (max-width: 600px) { + .chat-container { + width: 100%; + height: 100%; + max-height: 100%; + border-radius: 0; + } + + .chat-header { + border-radius: 0; + } + + .chat-input-zone { + border-radius: 0; + } +} \ No newline at end of file diff --git a/assets/js/chatbot.js b/assets/js/chatbot.js new file mode 100644 index 0000000..9d3578c --- /dev/null +++ b/assets/js/chatbot.js @@ -0,0 +1,199 @@ +/** + * CHATBOT IA - Client JavaScript + */ + +class ChatbotIA { + constructor(tentativeId) { + this.tentativeId = tentativeId; + this.currentQuestionId = null; + this.helpRemaining = 3; + this.isLoading = false; + + this.initElements(); + this.attachEvents(); + } + + initElements() { + this.btnOpenChat = document.getElementById('btn-open-chat'); + this.modalChat = document.getElementById('modal-chat'); + this.btnCloseChat = document.getElementById('btn-close-chat'); + this.chatMessages = document.getElementById('chat-messages'); + this.chatInput = document.getElementById('chat-input'); + this.btnSend = document.getElementById('btn-send'); + this.helpRemainingSpan = document.getElementById('help-remaining'); + } + + attachEvents() { + // Ouvrir chat + this.btnOpenChat?.addEventListener('click', () => this.ouvrirChat()); + + // Fermer chat + this.btnCloseChat?.addEventListener('click', () => this.fermerChat()); + + // Clic en dehors du modal + this.modalChat?.addEventListener('click', (e) => { + if (e.target === this.modalChat) this.fermerChat(); + }); + + // Envoyer message + this.btnSend?.addEventListener('click', () => this.envoyerMessage()); + + // Enter pour envoyer (Shift+Enter pour nouvelle ligne) + this.chatInput?.addEventListener('keypress', (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + this.envoyerMessage(); + } + }); + } + + ouvrirChat() { + // Récupérer l'ID de la question actuelle + this.currentQuestionId = this.getCurrentQuestionId(); + + if (!this.currentQuestionId) { + alert('Erreur : impossible de déterminer la question actuelle'); + return; + } + + this.modalChat.classList.add('show'); + this.chatInput.focus(); + } + + fermerChat() { + this.modalChat.classList.remove('show'); + } + + getCurrentQuestionId() { + // À adapter selon ton code de passer_evaluation.php + // Option 1 : Variable globale JS + if (typeof currentQuestionId !== 'undefined') { + return currentQuestionId; + } + + // Option 2 : Data attribute + const questionElement = document.querySelector('[data-question-id]'); + if (questionElement) { + return questionElement.dataset.questionId; + } + + // Option 3 : Input hidden + const hiddenInput = document.querySelector('input[name="id_question"]'); + if (hiddenInput) { + return hiddenInput.value; + } + + return null; + } + + async envoyerMessage() { + const message = this.chatInput.value.trim(); + + if (!message || this.isLoading) return; + + // Afficher message utilisateur + this.ajouterMessage(message, 'user'); + this.chatInput.value = ''; + + // Loading + this.isLoading = true; + this.btnSend.disabled = true; + this.afficherTypingIndicator(); + + try { + const response = await fetch('/mathematiques/api/chatbot.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: new URLSearchParams({ + id_tentative: this.tentativeId, + id_question: this.currentQuestionId, + message: message + }) + }); + + const data = await response.json(); + + // Retirer typing indicator + this.retirerTypingIndicator(); + + if (data.error) { + this.ajouterMessage(data.message || data.error, 'system'); + + if (data.remaining === 0) { + this.helpRemaining = 0; + this.updateHelpBadge(); + setTimeout(() => this.fermerChat(), 3000); + } + } else { + this.ajouterMessage(data.response, 'assistant'); + this.helpRemaining = data.remaining; + this.updateHelpBadge(); + + if (this.helpRemaining === 0) { + setTimeout(() => { + this.ajouterMessage( + 'Tu as utilisé toute ton aide pour cette question (3/3). Continue, tu peux le faire ! 💪', + 'system' + ); + setTimeout(() => this.fermerChat(), 3000); + }, 500); + } + } + } catch (error) { + console.error('Erreur chatbot:', error); + this.retirerTypingIndicator(); + this.ajouterMessage('❌ Erreur de connexion au serveur.', 'system'); + } finally { + this.isLoading = false; + this.btnSend.disabled = false; + this.chatInput.focus(); + } + } + + ajouterMessage(texte, type) { + const msgDiv = document.createElement('div'); + msgDiv.className = `message message-${type}`; + msgDiv.textContent = texte; + this.chatMessages.appendChild(msgDiv); + this.scrollToBottom(); + } + + afficherTypingIndicator() { + const typingDiv = document.createElement('div'); + typingDiv.className = 'typing-indicator'; + typingDiv.id = 'typing-indicator'; + typingDiv.innerHTML = ` + + + + `; + this.chatMessages.appendChild(typingDiv); + this.scrollToBottom(); + } + + retirerTypingIndicator() { + document.getElementById('typing-indicator')?.remove(); + } + + scrollToBottom() { + this.chatMessages.scrollTop = this.chatMessages.scrollHeight; + } + + updateHelpBadge() { + if (this.helpRemainingSpan) { + this.helpRemainingSpan.textContent = this.helpRemaining; + } + } +} + +// Initialisation automatique si tentativeId existe +document.addEventListener('DOMContentLoaded', () => { + // À adapter : récupérer tentativeId de ton code + const tentativeId = document.querySelector('[data-tentative-id]')?.dataset.tentativeId; + + if (tentativeId) { + window.chatbot = new ChatbotIA(tentativeId); + } +}); \ No newline at end of file