372 lines
14 KiB
Plaintext
Executable File
372 lines
14 KiB
Plaintext
Executable File
<?php
|
|
header('Content-Type: application/json');
|
|
error_reporting(0);
|
|
|
|
try {
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if (!$input || (!isset($input['message']) && !isset($input['action']))) {
|
|
echo json_encode(['error' => 'Message requis']);
|
|
exit;
|
|
}
|
|
|
|
$message = $input['message'];
|
|
$history = $input['history'] ?? [];
|
|
$provider = $input['provider'] ?? 'cerebras';
|
|
|
|
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123");
|
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
// Handler actions speciales
|
|
if (isset($input["action"])) {
|
|
if ($input["action"] === "list_conversations") {
|
|
$stmt = $pdo->query("SELECT id, title, created_at, updated_at FROM admin.hamid_conversations ORDER BY updated_at DESC LIMIT 20");
|
|
echo json_encode(["conversations" => $stmt->fetchAll(PDO::FETCH_ASSOC)]);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
|
|
// Récupérer config
|
|
$config = [];
|
|
try {
|
|
$stmt = $pdo->query("SELECT config_key, config_value FROM admin.hamid_config");
|
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
|
$config[$row['config_key']] = $row['config_value'];
|
|
}
|
|
} catch (Exception $e) {}
|
|
|
|
// ============================================
|
|
// RECHERCHE KNOWLEDGE BASE - AMÉLIORÉE
|
|
// ============================================
|
|
$kbContext = "";
|
|
$kbCount = 0;
|
|
$kbTitles = [];
|
|
|
|
try {
|
|
// Extraire les mots-clés du message (mots de plus de 3 caractères)
|
|
$words = preg_split('/\s+/', strtolower($message));
|
|
$keywords = array_filter($words, function($w) {
|
|
return strlen($w) > 3 && !in_array($w, ['pour', 'dans', 'avec', 'cette', 'faire', 'comment', 'quoi', 'quel', 'quelle', 'sont', 'est-ce', 'peux', 'veux', 'dois']);
|
|
});
|
|
|
|
if (!empty($keywords)) {
|
|
// Recherche par mots-clés
|
|
$conditions = [];
|
|
$params = [];
|
|
foreach ($keywords as $i => $kw) {
|
|
$conditions[] = "(LOWER(title) LIKE ? OR LOWER(content) LIKE ?)";
|
|
$params[] = '%' . $kw . '%';
|
|
$params[] = '%' . $kw . '%';
|
|
}
|
|
|
|
$sql = "SELECT id, question as title, answer as content FROM admin.commonia_knowledge WHERE " . implode(' OR ', $conditions) . " LIMIT 8";
|
|
$stmt = $pdo->prepare($sql);
|
|
$stmt->execute($params);
|
|
|
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
|
$kbContext .= "\n\n### " . $row['title'] . " ###\n" . mb_substr($row['content'], 0, 500);
|
|
$kbTitles[] = $row['title'];
|
|
$kbCount++;
|
|
}
|
|
}
|
|
|
|
// Si pas de résultats avec mots-clés, recherche générale
|
|
if ($kbCount == 0) {
|
|
$stmt = $pdo->prepare("SELECT id, question as title, answer as content FROM admin.commonia_knowledge WHERE LOWER(content) LIKE ? OR LOWER(question) LIKE ? LIMIT 8");
|
|
$searchTerm = '%' . strtolower(substr($message, 0, 50)) . '%';
|
|
$stmt->execute([$searchTerm, $searchTerm]);
|
|
|
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
|
$kbContext .= "\n\n### " . $row['title'] . " ###\n" . mb_substr($row['content'], 0, 500);
|
|
$kbTitles[] = $row['title'];
|
|
$kbCount++;
|
|
}
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
// Log error but continue
|
|
error_log("KB Search Error: " . $e->getMessage());
|
|
}
|
|
|
|
// ============================================
|
|
// RECHERCHE MÉMOIRE
|
|
// ============================================
|
|
$memContext = "";
|
|
$memCount = 0;
|
|
|
|
try {
|
|
$stmt = $pdo->prepare("SELECT key, value FROM admin.chatbot_memory WHERE LOWER(key) LIKE ? OR LOWER(value) LIKE ? LIMIT 8");
|
|
$searchTerm = '%' . strtolower($message) . '%';
|
|
$stmt->execute([$searchTerm, $searchTerm]);
|
|
|
|
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
|
$memContext .= "\n[" . $row['key'] . "]: " . $row['value'];
|
|
$memCount++;
|
|
}
|
|
} catch (Exception $e) {}
|
|
|
|
// ============================================
|
|
// SYSTEM PROMPT AVEC CONTEXTE KB
|
|
// ============================================
|
|
$systemPrompt = $config['system_prompt'] ?? "Tu es HAMID, un assistant IA expert pour WEVADS, FMGAPP et BCGAPP. Tu aides avec l'architecture système, le développement, l'email marketing, et les questions techniques. Réponds en français de manière claire et professionnelle.";
|
|
|
|
// IMPORTANT: Ajouter le contexte KB au prompt
|
|
if ($kbContext) {
|
|
$systemPrompt .= "\n\n=== KNOWLEDGE BASE (utilise ces informations pour répondre) ===\n" . $kbContext . "\n=== FIN KNOWLEDGE BASE ===";
|
|
}
|
|
|
|
if ($memContext) {
|
|
$systemPrompt .= "\n\n=== MÉMOIRE UTILISATEUR ===\n" . $memContext . "\n=== FIN MÉMOIRE ===";
|
|
}
|
|
|
|
// ============================================
|
|
// PROVIDERS
|
|
// ============================================
|
|
$providers = [
|
|
'cerebras' => [
|
|
'url' => 'https://api.cerebras.ai/v1/chat/completions',
|
|
'key' => $config['cerebras_api_key'] ?? '',
|
|
'model' => 'llama3.1-8b'
|
|
],
|
|
'groq' => [
|
|
'url' => 'https://api.groq.com/openai/v1/chat/completions',
|
|
'key' => $config['groq_api_key'] ?? '',
|
|
'model' => 'llama-3.3-70b-versatile'
|
|
],
|
|
'deepseek' => [
|
|
'url' => 'https://api.deepseek.com/v1/chat/completions',
|
|
'key' => $config['deepseek_api_key'] ?? '',
|
|
'model' => 'deepseek-chat'
|
|
],
|
|
'gemini' => [
|
|
'url' => 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent',
|
|
'key' => $config['gemini_api_key'] ?? '',
|
|
'model' => 'gemini-2.0-flash'
|
|
],
|
|
'chatgpt' => [
|
|
'url' => 'https://api.openai.com/v1/chat/completions',
|
|
'key' => $config['openai_api_key'] ?? '',
|
|
'model' => 'gpt-4o-mini'
|
|
],
|
|
'claude' => [
|
|
'url' => 'https://api.anthropic.com/v1/messages',
|
|
'key' => $config['claude_api_key'] ?? '',
|
|
'model' => 'claude-3-haiku-20240307'
|
|
],
|
|
'hyperbolic' => [
|
|
'url' => 'https://api.hyperbolic.xyz/v1/chat/completions',
|
|
'key' => $config['hyperbolic_api_key'] ?? '',
|
|
'model' => 'meta-llama/Llama-3.3-70B-Instruct'
|
|
],
|
|
'mistral' => [
|
|
'url' => 'https://api.mistral.ai/v1/chat/completions',
|
|
'key' => $config['mistral_api_key'] ?? '',
|
|
'model' => 'mistral-small-latest'
|
|
],
|
|
'cohere' => [
|
|
'url' => 'https://api.cohere.ai/v1/chat',
|
|
'key' => $config['cohere_api_key'] ?? '',
|
|
'model' => 'command-r'
|
|
],
|
|
'sambanova' => [
|
|
'url' => 'https://api.sambanova.ai/v1/chat/completions',
|
|
'key' => $config['sambanova_api_key'] ?? '',
|
|
'model' => 'Meta-Llama-3.1-8B-Instruct'
|
|
],
|
|
'ollama' => [
|
|
'url' => ($config['ollama_url'] ?? 'http://localhost:11434') . '/api/chat',
|
|
'key' => '',
|
|
'model' => $config['ollama_model'] ?? 'llama3'
|
|
],
|
|
'ollama-mini' => [
|
|
'url' => ($config['ollama_url'] ?? 'http://localhost:11434') . '/api/chat',
|
|
'key' => '',
|
|
'model' => 'llama3.2:1b'
|
|
]
|
|
];
|
|
|
|
$providerConfig = $providers[$provider] ?? $providers['cerebras'];
|
|
|
|
if (empty($providerConfig['key']) && !in_array($provider, ['ollama', 'ollama-mini'])) {
|
|
echo json_encode([
|
|
'error' => "Clé API non configurée pour $provider",
|
|
'provider' => $provider,
|
|
'kb_count' => $kbCount,
|
|
'mem_count' => $memCount
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
// Build messages
|
|
$messages = [['role' => 'system', 'content' => $systemPrompt]];
|
|
foreach ($history as $msg) {
|
|
if (isset($msg['role']) && isset($msg['content'])) {
|
|
$messages[] = ['role' => $msg['role'], 'content' => $msg['content']];
|
|
}
|
|
}
|
|
$messages[] = ['role' => 'user', 'content' => $message];
|
|
|
|
// API call
|
|
$ch = curl_init();
|
|
$startTime = microtime(true);
|
|
|
|
if ($provider === 'gemini') {
|
|
$url = $providerConfig['url'] . '?key=' . $providerConfig['key'];
|
|
$geminiMessages = array_filter($messages, fn($m) => $m['role'] !== 'system');
|
|
$payload = [
|
|
'contents' => array_map(function($m) {
|
|
return [
|
|
'role' => $m['role'] === 'assistant' ? 'model' : 'user',
|
|
'parts' => [['text' => $m['content']]]
|
|
];
|
|
}, array_values($geminiMessages)),
|
|
'systemInstruction' => ['parts' => [['text' => $systemPrompt]]]
|
|
];
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_URL => $url,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
|
CURLOPT_POSTFIELDS => json_encode($payload),
|
|
CURLOPT_TIMEOUT => 60
|
|
]);
|
|
} elseif ($provider === 'claude') {
|
|
$claudeMessages = array_values(array_filter($messages, fn($m) => $m['role'] !== 'system'));
|
|
$payload = [
|
|
'model' => $providerConfig['model'],
|
|
'max_tokens' => 4096,
|
|
'system' => $systemPrompt,
|
|
'messages' => $claudeMessages
|
|
];
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_URL => $providerConfig['url'],
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Content-Type: application/json',
|
|
'x-api-key: ' . $providerConfig['key'],
|
|
'anthropic-version: 2023-06-01'
|
|
],
|
|
CURLOPT_POSTFIELDS => json_encode($payload),
|
|
CURLOPT_TIMEOUT => 60
|
|
]);
|
|
} elseif ($provider === 'cohere') {
|
|
$chatHistory = [];
|
|
foreach (array_slice($messages, 1, -1) as $m) {
|
|
$chatHistory[] = [
|
|
'role' => $m['role'] === 'assistant' ? 'CHATBOT' : 'USER',
|
|
'message' => $m['content']
|
|
];
|
|
}
|
|
$payload = [
|
|
'model' => $providerConfig['model'],
|
|
'message' => $message,
|
|
'preamble' => $systemPrompt,
|
|
'chat_history' => $chatHistory
|
|
];
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_URL => $providerConfig['url'],
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Content-Type: application/json',
|
|
'Authorization: Bearer ' . $providerConfig['key']
|
|
],
|
|
CURLOPT_POSTFIELDS => json_encode($payload),
|
|
CURLOPT_TIMEOUT => 60
|
|
]);
|
|
} elseif (in_array($provider, ['ollama', 'ollama-mini'])) {
|
|
$payload = [
|
|
'model' => $providerConfig['model'],
|
|
'messages' => $messages,
|
|
'stream' => false
|
|
];
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_URL => $providerConfig['url'],
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
|
CURLOPT_POSTFIELDS => json_encode($payload),
|
|
CURLOPT_TIMEOUT => 120
|
|
]);
|
|
} else {
|
|
// OpenAI-compatible
|
|
$payload = [
|
|
'model' => $providerConfig['model'],
|
|
'messages' => $messages,
|
|
'max_tokens' => 4096,
|
|
'temperature' => 0.7
|
|
];
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_URL => $providerConfig['url'],
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Content-Type: application/json',
|
|
'Authorization: Bearer ' . $providerConfig['key']
|
|
],
|
|
CURLOPT_POSTFIELDS => json_encode($payload),
|
|
CURLOPT_TIMEOUT => 60
|
|
]);
|
|
}
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$curlError = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
$duration = round((microtime(true) - $startTime) * 1000);
|
|
|
|
if ($curlError) {
|
|
echo json_encode(['error' => "Erreur connexion: $curlError", 'provider' => $provider, 'kb_count' => $kbCount]);
|
|
exit;
|
|
}
|
|
|
|
$data = json_decode($response, true);
|
|
|
|
if ($httpCode !== 200) {
|
|
$errorMsg = $data['error']['message'] ?? $data['error'] ?? $response;
|
|
echo json_encode(['error' => "Erreur API ($httpCode): $errorMsg", 'provider' => $provider, 'kb_count' => $kbCount]);
|
|
exit;
|
|
}
|
|
|
|
// Extract response
|
|
$aiResponse = '';
|
|
if ($provider === 'gemini') {
|
|
$aiResponse = $data['candidates'][0]['content']['parts'][0]['text'] ?? 'Pas de réponse';
|
|
} elseif ($provider === 'claude') {
|
|
$aiResponse = $data['content'][0]['text'] ?? 'Pas de réponse';
|
|
} elseif ($provider === 'cohere') {
|
|
$aiResponse = $data['text'] ?? 'Pas de réponse';
|
|
} elseif (in_array($provider, ['ollama', 'ollama-mini'])) {
|
|
$aiResponse = $data['message']['content'] ?? 'Pas de réponse';
|
|
} else {
|
|
$aiResponse = $data['choices'][0]['message']['content'] ?? 'Pas de réponse';
|
|
}
|
|
|
|
// Files
|
|
$files = [];
|
|
if (preg_match('/\[FILE:([^\]]+)\]/', $aiResponse, $matches)) {
|
|
$fileName = trim($matches[1]);
|
|
$files[] = [
|
|
'name' => $fileName,
|
|
'type' => pathinfo($fileName, PATHINFO_EXTENSION),
|
|
'url' => '/hamid-files/' . $fileName
|
|
];
|
|
}
|
|
|
|
echo json_encode([
|
|
'response' => $aiResponse,
|
|
'provider' => $provider,
|
|
'model' => $providerConfig['model'],
|
|
'duration' => $duration,
|
|
'kb_count' => $kbCount,
|
|
'kb_titles' => $kbTitles,
|
|
'mem_count' => $memCount,
|
|
'files' => $files
|
|
]);
|
|
|
|
} catch (Exception $e) {
|
|
echo json_encode(['error' => 'Erreur serveur: ' . $e->getMessage()]);
|
|
}
|