Files
webval/api/chatbot.php
2026-01-04 10:26:53 +01:00

219 lines
6.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* API Chatbot IA - MVP
* Modèle : qwen2-math:1.5b via Ollama local
*/
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../config/database.php';
header('Content-Type: application/json');
// Vérifier authentification élève
if (!isLoggedIn() || !isEleve()) {
echo json_encode(['error' => '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;
}
$enonce_question = $question['enonce'];
$titre_eval = $question['titre_eval'];
$type_question = $question['type_question'];
// 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]
);
$system_prompt = "Tu es un assistant pédagogique en mathématiques. Ton rôle est d'AIDER l'élève à COMPRENDRE, PAS de donner la réponse.
RÈGLES ABSOLUES :
- JAMAIS donner la réponse finale ou résoudre l'exercice
- Poser des QUESTIONS pour guider la réflexion
- Donner des INDICES progressifs
- Rappeler les formules/théorèmes pertinents
- Encourager l'élève à décomposer le problème
UTILISE LA NOTATION MATHÉMATIQUE :
- Fractions : 1/2 (sera converti automatiquement)
- Puissances : x^2, 10^3
- Symboles : × (multiplication), ÷ (division)
- Tu peux aussi utiliser LaTeX entre dollars : \$\\frac{1}{2}\$
ÉNONCÉ DE LA QUESTION (NE CONTIENT PAS LA RÉPONSE) :
" . $enonce_question;
// 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
]);