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