/** * 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 dynamiquement this.currentQuestionId = this.detectCurrentQuestion(); if (!this.currentQuestionId) { alert('Erreur : impossible de déterminer la question actuelle'); return; } console.log('💬 Ouverture chat - Question:', this.currentQuestionId); // Charger l'aide restante pour cette question this.loadHelpRemaining(); this.modalChat.classList.add('show'); this.chatInput.focus(); } // NOUVELLE MÉTHODE : Détecter la question visible detectCurrentQuestion() { // Méthode 1 : Chercher l'élément de question visible/actif const activeQuestion = document.querySelector('.question.active, .question-container.active, [data-question-active="true"]'); if (activeQuestion && activeQuestion.dataset.questionId) { return parseInt(activeQuestion.dataset.questionId); } // Méthode 2 : Chercher dans les inputs visibles const visibleInputs = document.querySelectorAll('input[name^="reponse_"]:not([style*="display: none"])'); if (visibleInputs.length > 0) { const match = visibleInputs[0].name.match(/reponse_(\d+)/); if (match) { return parseInt(match[1]); } } // Méthode 3 : Index de question actuelle (si ton système utilise un index) if (typeof currentQuestionIndex !== 'undefined' && questionsIds[currentQuestionIndex]) { return questionsIds[currentQuestionIndex]; } // Fallback : première question return questionsIds[0]; } // NOUVELLE MÉTHODE : Charger l'aide restante via AJAX async loadHelpRemaining() { try { const response = await fetch('/mathematiques/api/chatbot_check_limit.php', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams({ id_tentative: this.tentativeId, id_question: this.currentQuestionId }) }); const data = await response.json(); if (data.success) { this.helpRemaining = data.remaining; this.updateHelpBadge(); } } catch (error) { console.error('Erreur chargement limite aide:', error); } } 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(), 10000); // 10s au lieu de 3s } } else { this.ajouterMessage(data.response, 'assistant'); this.helpRemaining = data.remaining; this.updateHelpBadge(); // Afficher info modèle dans console console.log('✅ Réponse IA -', 'Modèle:', data.modele || 'qwen2-math:1.5b', '- Temps:', data.temps_ms, 'ms'); if (this.helpRemaining === 0) { setTimeout(() => { this.ajouterMessage( '⚠️ Tu as utilisé toute ton aide pour cette question (3/3).\n\nLe chat se fermera dans 10 secondes. Continue, tu peux le faire ! 💪', 'system' ); // Bouton fermeture manuelle const btnDiv = document.createElement('div'); btnDiv.className = 'message message-system'; btnDiv.style.textAlign = 'center'; btnDiv.innerHTML = ''; this.chatMessages.appendChild(btnDiv); this.scrollToBottom(); // Fermeture auto après 10s setTimeout(() => this.fermerChat(), 10000); }, 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); } });