ai/index.php

243 lines
7.6 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
// chatbot.php — Version sécurisée production + anti prompt injection
session_start();
/* =======================
CONFIG & ENV
======================= */
$env = parse_ini_file(__DIR__ . '/.env');
define('API_KEY', $env['DEESEEK_API_KEY']);
define('SYSTEM_PROMPT', $env['DEEPSEEK_SYSTEM_PROMPT']);
define('CSRF_SECRET', $env['CSRF_SECRET']);
/* =======================
HEADERS SÉCURITÉ
======================= */
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('X-XSS-Protection: 1; mode=block');
/* =======================
SESSION INIT
======================= */
if (!isset($_SESSION['csrf'])) {
$_SESSION['csrf'] = hash_hmac('sha256', session_id(), CSRF_SECRET);
}
if (!isset($_SESSION['message_count'])) {
$_SESSION['message_count'] = 0;
}
/* =======================
HELPERS
======================= */
function isAjax(): bool {
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
}
function sanitizeMessage(string $msg) {
$msg = trim($msg);
if ($msg === '' || mb_strlen($msg) > 500) {
return false;
}
return $msg;
}
/* =======================
ANTI PROMPT INJECTION
======================= */
function isPromptInjection(string $msg): bool {
// regex solide : détecte toutes les tentatives d'instructions systèmes, "ignore", "révèle", "prompt", etc.
$pattern = '/\b(ignore|oublie|révèle|prompt|instructions?|system|agis comme|tu es maintenant|roleplay|administrateur)\b/i';
return preg_match($pattern, $msg) === 1;
}
/* =======================
API CALL
======================= */
function callDeepSeekAPI(string $userMessage): string {
$payload = [
'model' => 'deepseek-chat',
'messages' => [
['role' => 'system', 'content' => SYSTEM_PROMPT],
['role' => 'user', 'content' => $userMessage]
],
'max_tokens' => 800,
'temperature' => 0,
'top_p' => 1
];
$ch = curl_init('https://api.deepseek.com/v1/chat/completions');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . API_KEY
],
CURLOPT_TIMEOUT => 15
]);
$response = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http !== 200 || !$response) {
return 'Erreur du service.';
}
$json = json_decode($response, true);
return $json['choices'][0]['message']['content'] ?? 'Erreur de réponse.';
}
/* =======================
AJAX HANDLER
======================= */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
if (!isAjax()) {
http_response_code(403);
echo json_encode(['error' => 'Requête interdite']);
exit;
}
if (!isset($_POST['csrf']) || $_POST['csrf'] !== $_SESSION['csrf']) {
http_response_code(403);
echo json_encode(['error' => 'CSRF invalide']);
exit;
}
if ($_SESSION['message_count'] >= 1) {
echo json_encode(['error' => 'LIMIT_REACHED']);
exit;
}
$message = sanitizeMessage($_POST['message']);
if ($message === false) {
echo json_encode(['error' => 'Message invalide']);
exit;
}
// anti prompt injection
if (isPromptInjection($message)) {
echo json_encode(['response' => 'Demande à Bouchra.']);
exit;
}
$_SESSION['message_count']++;
$reply = callDeepSeekAPI($message);
echo json_encode([
'response' => $reply
]);
exit;
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Assistant IA</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
body { margin:0; font-family:Inter,sans-serif; background:linear-gradient(135deg,#667eea,#764ba2); height:100vh; display:flex; justify-content:center; align-items:center; }
.chat-wrapper { width:100%; max-width:900px; height:700px; background:#fff; border-radius:24px; display:flex; flex-direction:column; overflow:hidden; }
.chat-header { padding:20px; background:linear-gradient(135deg,#667eea,#764ba2); color:#fff; font-weight:600; }
#chat { flex:1; padding:20px; background:#f8fafc; overflow-y:auto; }
.message { display:flex; margin-bottom:16px; }
.message.user { justify-content:flex-end; }
.message-bubble { max-width:70%; padding:12px 16px; border-radius:14px; font-size:14px; }
.user .message-bubble { background:#667eea; color:#fff; }
.bot .message-bubble { background:#fff; color:#1e293b; }
.input-area { padding:20px; border-top:1px solid #e5e7eb; }
#limitPopup { display:none; margin-bottom:10px; padding:10px; background:#fff3cd; color:#92400e; border-radius:10px; font-size:13px; text-align:center; }
form { display:flex; gap:10px; }
input { flex:1; padding:12px; border-radius:10px; border:1px solid #ccc; }
button { padding:12px 20px; border:none; border-radius:10px; background:#667eea; color:#fff; font-weight:600; cursor:pointer; }
button:disabled { opacity:.6; cursor:not-allowed; }
</style>
</head>
<body>
<div class="chat-wrapper">
<div class="chat-header">Assistant IA</div>
<div id="chat">
<div class="message bot">
<div class="message-bubble">
Bonjour, comment puisje vous aider?
</div>
</div>
</div>
<div class="input-area">
<div id="limitPopup">
Pour plus, prends l'abonnement en demandant à Bouchra
</div>
<form onsubmit="sendMsg();return false;">
<input type="hidden" id="csrf" value="<?= htmlspecialchars($_SESSION['csrf']) ?>">
<input id="userInput" type="text" placeholder="Votre message…" required>
<button id="sendBtn">Envoyer</button>
</form>
</div>
</div>
<script>
const chat = document.getElementById('chat');
const input = document.getElementById('userInput');
const btn = document.getElementById('sendBtn');
const csrf = document.getElementById('csrf').value;
const popup = document.getElementById('limitPopup');
function addMsg(text, isUser) {
const msg = document.createElement('div');
msg.className = 'message ' + (isUser ? 'user' : 'bot');
const bubble = document.createElement('div');
bubble.className = 'message-bubble';
bubble.textContent = text; // 🔒 XSS SAFE
msg.appendChild(bubble);
chat.appendChild(msg);
chat.scrollTop = chat.scrollHeight;
}
async function sendMsg() {
const txt = input.value.trim();
if (!txt) return;
addMsg(txt, true);
input.value = '';
btn.disabled = true;
const res = await fetch('', {
method:'POST',
headers:{
'Content-Type':'application/x-www-form-urlencoded',
'X-Requested-With':'XMLHttpRequest'
},
body:'message='+encodeURIComponent(txt)+'&csrf='+encodeURIComponent(csrf)
});
const data = await res.json();
if (data.error === 'LIMIT_REACHED') {
popup.style.display = 'block';
input.disabled = true;
btn.disabled = true;
addMsg('Accès limité.', false);
return;
}
addMsg(data.response || 'Erreur.', false);
btn.disabled = false;
}
</script>
</body>
</html>