From f2832a6300a659ca235970470fc3ae502609f18e Mon Sep 17 00:00:00 2001 From: nicoboy Date: Sat, 3 Jan 2026 14:04:26 +0100 Subject: [PATCH] creation chatbot --- api/chatbot.php | 223 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 api/chatbot.php diff --git a/api/chatbot.php b/api/chatbot.php new file mode 100644 index 0000000..93ea5ad --- /dev/null +++ b/api/chatbot.php @@ -0,0 +1,223 @@ + 'Non autorisé']); + exit; +} + +$user = currentUser(); +$db = Database::getInstance(); + +// Récupérer et valider les données +$id_tentative = filter_input(INPUT_POST, 'id_tentative', FILTER_VALIDATE_INT); +$id_question = filter_input(INPUT_POST, 'id_question', FILTER_VALIDATE_INT); +$message_eleve = trim($_POST['message'] ?? ''); + +if (!$id_tentative || !$id_question || empty($message_eleve)) { + echo json_encode(['error' => 'Données manquantes']); + exit; +} + +// Vérifier que la tentative appartient à l'élève et est en cours +$tentative = $db->fetchOne( + "SELECT id_eleve FROM tentatives_eleves WHERE id_tentative = ? AND statut = 'en_cours'", + [$id_tentative] +); + +if (!$tentative || $tentative['id_eleve'] != $user['id_utilisateur']) { + echo json_encode(['error' => 'Tentative invalide']); + exit; +} + +// Vérifier limite (3 messages max par question) +$usage = $db->fetchOne( + "SELECT messages_count FROM chat_usage_limits WHERE id_tentative = ? AND id_question = ?", + [$id_tentative, $id_question] +); + +$messages_count = $usage['messages_count'] ?? 0; + +if ($messages_count >= 3) { + echo json_encode([ + 'error' => 'Limite atteinte', + 'message' => 'Tu as utilisé toute ton aide pour cette question (3/3). Continue, tu peux le faire ! 💪', + 'remaining' => 0 + ]); + exit; +} + +// Récupérer l'énoncé de la question (SANS la réponse) +$question = $db->fetchOne( + "SELECT q.enonce, q.type_question, e.titre as titre_eval + FROM questions q + JOIN evaluations e ON q.id_evaluation = e.id_evaluation + WHERE q.id_question = ?", + [$id_question] +); + +if (!$question) { + echo json_encode(['error' => 'Question introuvable']); + exit; +} + +// Récupérer ou créer la conversation +$conversation = $db->fetchOne( + "SELECT id FROM chat_conversations + WHERE id_tentative = ? AND id_question = ?", + [$id_tentative, $id_question] +); + +if (!$conversation) { + // Créer nouvelle conversation + $id_conversation = $db->insert( + "INSERT INTO chat_conversations (id_tentative, id_question, id_eleve, modele_utilise) + VALUES (?, ?, ?, 'qwen2-math:1.5b')", + [$id_tentative, $id_question, $user['id_utilisateur']] + ); +} else { + $id_conversation = $conversation['id']; +} + +// Récupérer l'historique de la conversation +$historique = $db->fetchAll( + "SELECT role, message FROM chat_messages + WHERE id_conversation = ? + ORDER BY timestamp ASC", + [$id_conversation] +); + +// Construire le prompt système +$system_prompt = "Tu es un assistant pédagogique en mathématiques pour élèves de collège/lycée. + +RÈGLES ABSOLUES : +- NE JAMAIS donner la réponse finale ou les calculs complets +- Utiliser la méthode socratique : poser des questions pour guider la réflexion +- Donner des INDICES progressifs (formules, méthodes, étapes) +- Encourager l'élève à décomposer le problème lui-même +- Rester positif et encourageant +- Répondre en français simple et clair +- Réponses courtes (max 3-4 lignes) + +STRICTEMENT INTERDIT : +- Résoudre l'exercice complètement +- Donner les chiffres de la réponse finale +- Faire les calculs à la place de l'élève + +CONTEXTE : +Évaluation : {$question['titre_eval']} +Type de question : {$question['type_question']} + +ÉNONCÉ : +{$question['enonce']} + +Aide l'élève avec des questions et indices, PAS avec la réponse."; + +// Construire l'historique pour Ollama +$messages_ollama = []; +foreach ($historique as $msg) { + $messages_ollama[] = [ + 'role' => $msg['role'], + 'content' => $msg['message'] + ]; +} +// Ajouter le nouveau message de l'élève +$messages_ollama[] = [ + 'role' => 'user', + 'content' => $message_eleve +]; + +// Appel à Ollama (qwen2-math:1.5b) +$start_time = microtime(true); + +$ch = curl_init('http://localhost:11434/api/chat'); +curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_TIMEOUT => 30, + CURLOPT_POSTFIELDS => json_encode([ + 'model' => 'qwen2-math:1.5b', + 'messages' => array_merge([ + ['role' => 'system', 'content' => $system_prompt] + ], $messages_ollama), + 'stream' => false, + 'options' => [ + 'temperature' => 0.7, + 'num_predict' => 200, // Réponses courtes + 'top_p' => 0.9 + ] + ]) +]); + +$response = curl_exec($ch); +$curl_error = curl_error($ch); +$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); +curl_close($ch); + +$end_time = microtime(true); +$temps_reponse_ms = round(($end_time - $start_time) * 1000); + +// Gérer les erreurs +if ($curl_error) { + error_log("Erreur Ollama CURL: $curl_error"); + echo json_encode(['error' => 'Service temporairement indisponible (connexion)']); + exit; +} + +if ($http_code !== 200) { + error_log("Erreur Ollama HTTP $http_code: $response"); + echo json_encode(['error' => 'Service temporairement indisponible (HTTP ' . $http_code . ')']); + exit; +} + +$data = json_decode($response, true); + +if (!isset($data['message']['content'])) { + error_log("Réponse Ollama invalide: $response"); + echo json_encode(['error' => 'Erreur de génération']); + exit; +} + +$reponse_ia = trim($data['message']['content']); + +// Sauvegarder le message de l'élève +$db->query( + "INSERT INTO chat_messages (id_conversation, role, message) + VALUES (?, 'user', ?)", + [$id_conversation, $message_eleve] +); + +// Sauvegarder la réponse de l'IA +$db->query( + "INSERT INTO chat_messages (id_conversation, role, message, temps_reponse_ms) + VALUES (?, 'assistant', ?, ?)", + [$id_conversation, $reponse_ia, $temps_reponse_ms] +); + +// Incrémenter le compteur d'utilisation +$db->query( + "INSERT INTO chat_usage_limits (id_tentative, id_question, messages_count) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE messages_count = messages_count + 1", + [$id_tentative, $id_question] +); + +$messages_restants = 2 - $messages_count; + +// Réponse succès +echo json_encode([ + 'success' => true, + 'response' => $reponse_ia, + 'remaining' => $messages_restants, + 'temps_ms' => $temps_reponse_ms +]); \ No newline at end of file