713 lines
36 KiB
PHP
713 lines
36 KiB
PHP
<?php
|
||
require_once __DIR__.'/weval-brand-guard.php';
|
||
$secrets=[];foreach(file("/etc/weval/secrets.env",2|4) as $l){if(strpos($l,"=")!==false){list($k,$v)=explode("=",$l,2);$secrets[trim($k)]=trim($v," \t\"'");}}
|
||
require_once __DIR__ . '/_secrets.php';
|
||
/**
|
||
* WEDROID Brain API v5 — Ollama-First Sovereign AI Agent
|
||
*
|
||
* Routing: LOCAL (Ollama 80%) → Cerebras (complex 15%) → Groq (fallback 5%)
|
||
* Deployed on S204, calls Ollama on S95 via SSH private key
|
||
*/
|
||
header("Content-Type: application/json");
|
||
header("Access-Control-Allow-Origin: *");
|
||
if($_SERVER['REQUEST_METHOD']==='OPTIONS'){http_response_code(200);exit;}
|
||
|
||
$KEY = "DROID2026";
|
||
$k = $_POST["k"] ?? $_GET["k"] ?? "";
|
||
if($k !== $KEY) { echo json_encode(["error"=>"Unauthorized"]); exit; }
|
||
|
||
require_once __DIR__ . "/wedroid-infra-patterns.php";
|
||
require_once __DIR__ . "/wedroid-chain-executor.php";
|
||
require_once __DIR__ . "/wedroid-git-auto.php";
|
||
require_once __DIR__ . "/wedroid-telegram-alert.php";
|
||
require_once __DIR__ . "/wedroid-scheduler.php";
|
||
require_once __DIR__ . "/wedroid-learning.php";
|
||
$WEDROID_KB = @json_decode(@file_get_contents(__DIR__ . '/wedroid-knowledge-base.json'), true) ?: [];
|
||
|
||
$action = $_POST["action"] ?? $_GET["action"] ?? "chat";
|
||
$message = $_POST["message"] ?? $_GET["message"] ?? "";
|
||
$session = $_POST["session"] ?? "default";
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// PROVIDER CONFIG
|
||
// ═══════════════════════════════════════════════════════
|
||
$OLLAMA_HOST = "localhost"; // S95 via SSH
|
||
$OLLAMA_PORT = 11434;
|
||
$OLLAMA_MODELS = [
|
||
'fast' => 'qwen3:4b', // 2.5GB — fast, simple tasks
|
||
'general' => 'qwen3:8b', // 5.2GB — general purpose
|
||
'code' => 'qwen2.5:7b', // 4.7GB — code generation
|
||
'deep' => 'mistral:latest', // 4.4GB — reasoning
|
||
];
|
||
|
||
$CEREBRAS_KEY = "csk-4wrrhkpr568ry9xx49k9mcynwdx483nx53dd62yh5xedfckh";
|
||
$CEREBRAS_MODEL = "qwen-3-235b-a22b-instruct-2507";
|
||
$CEREBRAS_URL = "https://api.cerebras.ai/v1/chat/completions";
|
||
|
||
$GROQ_KEY = ($secrets["GROQ_KEY"]??"");
|
||
$GROQ_MODEL = "llama-3.3-70b-versatile";
|
||
$GROQ_URL = "https://api.groq.com/openai/v1/chat/completions";
|
||
|
||
// MULTI-ACCOUNT ROTATION
|
||
$CEREBRAS_KEYS = [
|
||
"csk-4wrrhkpr568ry9xx49k9mcynwdx483nx53dd62yh5xedfckh",
|
||
];
|
||
$GROQ_KEYS = [
|
||
($secrets["GROQ_KEY"]??""),
|
||
];
|
||
|
||
// ADDITIONAL PROVIDERS
|
||
$SAMBANOVA_KEY = "9541b2a0-6ddc-4e7d-a957-c348d6119c3f";
|
||
$SAMBANOVA_MODEL = "DeepSeek-V3.1";
|
||
$SAMBANOVA_URL = "https://api.sambanova.ai/v1/chat/completions";
|
||
|
||
$DEEPSEEK_KEY = "sk-a296c24f77a1405d8f80105982991203";
|
||
$DEEPSEEK_MODEL = "deepseek-chat";
|
||
$DEEPSEEK_URL = "https://api.deepseek.com/v1/chat/completions";
|
||
|
||
$GEMINI_KEY = "AIzaSyBt-qfLETGALpibigmgW-o4vlxQZWaxwcE";
|
||
$GEMINI_MODEL = "gemini-2.0-flash";
|
||
$GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=";
|
||
|
||
// MISTRAL
|
||
$MISTRAL_KEY = "0JBySAtEKlM8CKgE3zh8uDYGQVhdMa6M";
|
||
$MISTRAL_MODEL = "mistral-small-latest";
|
||
$MISTRAL_URL = "https://api.mistral.ai/v1/chat/completions";
|
||
|
||
// ALIBABA QWEN (DashScope)
|
||
$DASHSCOPE_KEY = "sk-34db1ad3152443cd86563d1bfc576c30";
|
||
$DASHSCOPE_MODEL = "qwen3.5-plus";
|
||
$DASHSCOPE_URL = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions";
|
||
|
||
// OPENROUTER (free tier)
|
||
$OPENROUTER_KEY = ($secrets["OPENROUTER_KEY"]??"");
|
||
$OPENROUTER_MODEL = "meta-llama/llama-3.3-70b-instruct:free";
|
||
$OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions";
|
||
|
||
// Force provider from frontend
|
||
$force_provider = $_POST["provider"] ?? $_GET["provider"] ?? "auto";
|
||
|
||
|
||
// SSH config for S95
|
||
$SSH_KEY = "/root/.ssh/wevads_key";
|
||
$SSH_USER = "root";
|
||
$SSH_HOST = "95.216.167.89";
|
||
$SSH_PORT = 49222;
|
||
$SSH_CMD = "sudo SSH_AUTH_SOCK= ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o IdentitiesOnly=yes -i $SSH_KEY -p $SSH_PORT $SSH_USER@$SSH_HOST";
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// SESSION MEMORY (simple file-based)
|
||
// ═══════════════════════════════════════════════════════
|
||
$SESSION_DIR = "/tmp/wedroid-sessions";
|
||
@mkdir($SESSION_DIR, 0777, true);
|
||
$session_file = "$SESSION_DIR/$session.json";
|
||
$history = [];
|
||
if(file_exists($session_file)) {
|
||
$history = json_decode(file_get_contents($session_file), true) ?: [];
|
||
// Keep last 10 exchanges
|
||
if(count($history) > 20) $history = array_slice($history, -20);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// PROVIDER ROTATION (multi-account)
|
||
function rotateKey($keys) {
|
||
return $keys[array_rand($keys)];
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// ADDITIONAL PROVIDER FUNCTIONS
|
||
function callSambaNova($msg, $key, $model, $url, $sys) {
|
||
return callOpenAICompat($msg, $key, $model, $url, $sys);
|
||
}
|
||
function callDeepSeek($msg, $key, $model, $url, $sys) {
|
||
return callOpenAICompat($msg, $key, $model, $url, $sys);
|
||
}
|
||
function callOpenAICompat($msg, $key, $model, $url, $sys) {
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_POST => true,
|
||
CURLOPT_TIMEOUT => 60,
|
||
CURLOPT_HTTPHEADER => ["Content-Type: application/json", "Authorization: Bearer $key"],
|
||
CURLOPT_POSTFIELDS => json_encode([
|
||
"model" => $model,
|
||
"messages" => [["role"=>"system","content"=>$sys],["role"=>"user","content"=>$msg]],
|
||
"max_tokens" => 1024,
|
||
"temperature" => 0.7
|
||
])
|
||
]);
|
||
$r = curl_exec($ch); curl_close($ch);
|
||
$d = json_decode($r, true);
|
||
return $d["choices"][0]["message"]["content"] ?? "Error: ".($d["error"]["message"] ?? $r);
|
||
}
|
||
function callGemini($msg, $key, $url, $sys) {
|
||
$ch = curl_init($url . $key);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_POST => true,
|
||
CURLOPT_TIMEOUT => 60,
|
||
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
|
||
CURLOPT_POSTFIELDS => json_encode([
|
||
"contents" => [["parts" => [["text" => "$sys\n\n$msg"]]]],
|
||
"generationConfig" => ["maxOutputTokens" => 1024, "temperature" => 0.7]
|
||
])
|
||
]);
|
||
$r = curl_exec($ch); curl_close($ch);
|
||
$d = json_decode($r, true);
|
||
return $d["candidates"][0]["content"]["parts"][0]["text"] ?? "Error: ".($d["error"]["message"] ?? $r);
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// SYSTEM PROMPT
|
||
// ═══════════════════════════════════════════════════════
|
||
$SYSTEM = "'.WEVAL_BRAND_CONTEXT.'Tu es WEDROID, un agent IA souverain qui gère l'infrastructure WEVAL Consulting.
|
||
|
||
SERVEURS:
|
||
- S204 (204.168.152.13): serveur principal, nginx, PHP, PostgreSQL, Node.js, WEVIA, Ethica
|
||
- S95 (95.216.167.89): Arsenal, Apache, PostgreSQL, Droid CLI, Ollama, bcgapp
|
||
- S151 (151.80.235.110): tracking email, OVH
|
||
- 4× ECS PMTA: serveurs email delivery
|
||
|
||
BASE ETHICA: 50,543 HCPs, 49,594 emails, 3 pays (TN/MA/ALG), 18 marques pharma
|
||
|
||
RÈGLES:
|
||
1. Réponds en français naturel et concis
|
||
2. Si on te demande une action serveur, génère la commande bash et exécute-la
|
||
3. Pour les commandes S95: utilise shell_exec directement
|
||
4. Pour les commandes S204: préfixe avec [S204]
|
||
5. Analyse les résultats et donne un résumé clair
|
||
6. Si une commande échoue, explique pourquoi et propose une alternative
|
||
7. Pour les données Ethica: utilise l'API sur S204 (token ETHICA_API_2026_SECURE)
|
||
|
||
FORMAT RÉPONSE: JSON avec les clés: thinking, response, commands (optionnel), provider
|
||
|
||
COMPÉTENCES WEVIA INTÉGRÉES (16 capacités que TU possèdes):
|
||
1. frontend-design: UI production-grade, bold aesthetics, pas de AI slop
|
||
2. algorithmic-art: Art génératif p5.js, seeded randomness, philosophies computationnelles
|
||
3. canvas-design: Art visuel PDF/PNG, design philosophy → expression visuelle
|
||
4. web-artifacts-builder: React 18 + Tailwind + shadcn/ui, artifacts complexes
|
||
5. skill-creator: Créer/tester/itérer des compétences avec framework eval
|
||
6. brand-guidelines: Charte Anthropic (Dark #141413, Orange #d97757, Poppins+Lora)
|
||
7. doc-coauthoring: Track changes, comments, collaboration docx
|
||
8. pdf: Créer, merger, splitter, OCR, forms, watermarks PDF
|
||
9. docx: Créer/éditer Word avec docx-js, styles, tables, images
|
||
10. pptx: Créer/éditer PowerPoint, slides, layouts
|
||
11. xlsx: Créer/éditer Excel, formules, charts, formatting
|
||
12. internal-comms: Communications internes (emails, memos, annonces)
|
||
13. mcp-builder: Construire serveurs MCP pour intégration outils
|
||
14. theme-factory: Générer thèmes design et palettes couleurs
|
||
15. slack-gif-creator: Créer GIFs animés
|
||
16. product-self-knowledge
|
||
17. grader-agent: Évaluer assertions vs outputs
|
||
18. analyzer-agent: Analyser pourquoi une version bat une autre
|
||
19. comparator-agent: Comparaison A/B aveugle entre outputs
|
||
20. schemas-agent: Structures JSON pour evals: Connaissance produits Anthropic/Claude
|
||
Quand on te demande tes compétences, CITE CETTE LISTE EXACTE. Tu ES WEVIA. Ne dis JAMAIS que tu es Qwen.
|
||
";
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// COMPLEXITY DETECTION
|
||
// ═══════════════════════════════════════════════════════
|
||
function detectComplexity($msg) {
|
||
$lower = mb_strtolower($msg);
|
||
|
||
// SIMPLE → Ollama fast (tinyllama) — direct bash, no AI needed
|
||
$simple_patterns = [
|
||
'/^(status|etat|état)$/i',
|
||
'/^(ls|df|ps|top|uptime|whoami|date|pwd|free)/i',
|
||
'/^(disk|disque|espace)/i',
|
||
'/^(log|error|erreur)/i',
|
||
];
|
||
foreach($simple_patterns as $p) if(preg_match($p, $msg)) return 'direct';
|
||
|
||
// MEDIUM → Ollama general (llama3.1:8b) — needs understanding
|
||
$medium_patterns = [
|
||
'collecte de donnees','collecteur','cron','ethica','contact','hcp','medecin',
|
||
'git','github','push','commit','backup','sauvegarde',
|
||
'test','nonreg','regression','lance','lancer',
|
||
'serveur','server','apache','nginx','php','postgresql',
|
||
'montre','affiche','combien','quel','quels','liste',
|
||
];
|
||
foreach($medium_patterns as $p) if(strpos($lower, $p) !== false) return 'medium';
|
||
|
||
// COMPLEX → Cerebras 235B — multi-step reasoning
|
||
$complex_patterns = [
|
||
'deploy','déploie','deploie','migre','migration',
|
||
'architecture','design','refonte','reconstru',
|
||
'analyse','audit','diagnostic','optimise',
|
||
'crée','créer','constru','génère','genere',
|
||
'compare','benchmark','stratégie','strategie',
|
||
'pourquoi','comment faire','explique en détail',
|
||
'code','script','fonction','classe','module',
|
||
];
|
||
foreach($complex_patterns as $p) if(strpos($lower, $p) !== false) return 'complex';
|
||
|
||
// Default → medium (Ollama general)
|
||
return 'medium';
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// DIRECT EXECUTION — handles 80% of requests (INSTANT)
|
||
// No AI needed — pattern → bash command → formatted response
|
||
// ═══════════════════════════════════════════════════════
|
||
function execDirect($msg) {
|
||
$lower = mb_strtolower(trim($msg));
|
||
$cmd = null;
|
||
$label = null;
|
||
|
||
// ── STATUS & HEALTH ──
|
||
if(preg_match('/^(status|etat|état|comment.*va|sante|santé|health)/i', $msg)) {
|
||
$cmd = 'echo "Disk: $(df -h / | tail -1 | awk \'{print $5}\')" && echo "RAM: $(free -h | awk \'/Mem:/{print $3\"/\"$2}\')" && echo "Load: $(cat /proc/loadavg | cut -d" " -f1-3)" && echo "PHP: $(ps aux | grep php | grep -v grep | wc -l) procs" && echo "PG: $(systemctl is-active postgresql 2>/dev/null)" && echo "Apache: $(systemctl is-active apache2 2>/dev/null)" && echo "Uptime: $(uptime -p)"';
|
||
$label = "État S95";
|
||
}
|
||
// ── ETHICA / HCP / CONTACTS — only data requests, not complex questions ──
|
||
elseif(preg_match('/^(ethica|hcp|contact|medecin)$/i', $msg) ||
|
||
preg_match('/combien.*contact|stats.*ethica|données.*ethica|data.*ethica|nombre.*hcp/i', $msg)) {
|
||
$s204_cmd = 'curl -s --max-time 10 "http://204.168.152.13/api/ethica-api.php?action=dashboard&token=ETHICA_API_2026_SECURE" 2>/dev/null';
|
||
$raw = execOnS95($s204_cmd);
|
||
$data = json_decode($raw, true);
|
||
if($data && isset($data['contacts_total'])) {
|
||
$pays = '';
|
||
if(is_array($data['contacts_by_pays']??null)) {
|
||
foreach($data['contacts_by_pays'] as $p) $pays .= " " . ($p[0]??'') . ": " . number_format($p[1]??0) . "\n";
|
||
}
|
||
$resp = "**Ethica B2B — Données live**\n\n";
|
||
$resp .= "Total HCPs: **" . number_format($data['contacts_total']) . "**\n";
|
||
$resp .= "Actifs: " . number_format($data['contacts_active']??0) . "\n";
|
||
$resp .= "Senders: " . ($data['senders']??0) . " | Sources: " . ($data['sources']??0) . "\n\n";
|
||
if($pays) $resp .= "**Par pays:**\n$pays\n";
|
||
$resp .= "Campagnes: " . ($data['campaigns_total']??0) . " | Envois: " . ($data['sends_total']??0);
|
||
return ["ok"=>true,"provider"=>"direct","complexity"=>"direct","response"=>$resp,"thinking"=>"Requête API Ethica directe"];
|
||
}
|
||
}
|
||
// ── collecte de donnees ──
|
||
elseif(preg_match('/scrap|enrichi/i', $msg)) {
|
||
$cmd = 'echo "collecteurs actifs: $(ps aux | grep "ethica.*php" | grep -v grep | wc -l)" && echo "Total HCPs: $(PGPASSWORD=W3v4l_2026_S3cur3 psql -h 127.0.0.1 -U admin -d adx_system -t -c "SELECT COUNT(*) FROM ethica.medecins_real" 2>/dev/null | tr -d " ")" && echo "Sources:" && PGPASSWORD=W3v4l_2026_S3cur3 psql -h 127.0.0.1 -U admin -d adx_system -t -c "SELECT source, COUNT(*) c FROM ethica.medecins_real GROUP BY source ORDER BY c DESC LIMIT 8" 2>/dev/null';
|
||
$label = "collecteurs Ethica";
|
||
}
|
||
// ── INFRA PATTERNS (auto) ──
|
||
$infra = matchInfraPattern($msg);
|
||
if ($infra && isset($infra['special'])) {
|
||
if ($infra['special']==='auth_test_local') {
|
||
$results = [];
|
||
$ch = curl_init('http://127.0.0.1/api/weval-auth-session.php');
|
||
curl_setopt_array($ch, [CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>5,CURLOPT_POSTFIELDS=>'action=login&user=weval&pass=YacineWeval2026',CURLOPT_HEADER=>true,CURLOPT_HTTPHEADER=>['Host: weval-consulting.com']]);
|
||
$resp = curl_exec($ch); curl_close($ch);
|
||
preg_match('/PHPSESSID=([^;\s]+)/', $resp, $m);
|
||
$cookie = $m[1] ?? '';
|
||
$results[] = 'Session: ' . ($cookie ? 'OK' : 'FAIL');
|
||
$pages = ['/wevia-ia/droid.html','/arsenal-proxy/menu.html','/admin-ia','/wevia-bo','/cyber-monitor','/wevia-fullscreen'];
|
||
foreach ($pages as $p) {
|
||
$ch2 = curl_init('http://127.0.0.1' . $p);
|
||
curl_setopt_array($ch2, [CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>5,CURLOPT_COOKIE=>'PHPSESSID='.$cookie,CURLOPT_HTTPHEADER=>['Host: weval-consulting.com']]);
|
||
$r = curl_exec($ch2); $size = strlen($r); curl_close($ch2);
|
||
$results[] = $p . ': ' . ($size > 5000 ? 'OK ' . $size . 'B' : 'FAIL ' . $size . 'B');
|
||
}
|
||
return ['ok'=>true,'provider'=>'auth-test','complexity'=>'direct','response'=>implode("
|
||
",$results)];
|
||
}
|
||
if ($infra['special']==='heal') { require_once '/var/www/weval/wevia-ia/wevia-self-healing.php'; return ['ok'=>true,'provider'=>'self-heal','complexity'=>'direct','response'=>json_encode(weviaSelfHeal(),JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)]; }
|
||
if ($infra['special']==='git') { return ['ok'=>true,'provider'=>'git-auto','complexity'=>'direct','response'=>json_encode(gitAutoPush($msg),JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)]; }
|
||
if ($infra['special']==='alert') { return ['ok'=>true,'provider'=>'telegram','complexity'=>'direct','response'=>json_encode(telegramAlert($msg),JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)]; }
|
||
if ($infra['special']==='email_check') { $pdo=new PDO('pgsql:host=127.0.0.1;dbname=adx_system','admin',weval_secret('WEVAL_PG_ADMIN_PASS')); $c=$pdo->query("SELECT count(*) as t FROM admin.contact_messages")->fetch(PDO::FETCH_ASSOC); $u=$pdo->query("SELECT count(*) as t FROM admin.users")->fetch(PDO::FETCH_ASSOC); return ['ok'=>true,'provider'=>'email-check','complexity'=>'direct','response'=>"Contacts: ".($c['t']??0)." | Users: ".($u['t']??0)." | Routing: ymahboub+yacineutt"]; }
|
||
}
|
||
if ($infra && isset($infra['cmd'])) { $cmd = $infra['cmd']; $label = $infra['label']; }
|
||
// ── GIT / BACKUP ──
|
||
elseif(preg_match('/git|github|commit/i', $msg) && !preg_match('/push|backup|sauvegarde/i', $msg)) {
|
||
$cmd = 'cd /opt/wevads-arsenal 2>/dev/null && git log --oneline -5 && echo "---" && git status --short | head -10';
|
||
$label = "Git Status";
|
||
}
|
||
elseif(preg_match('/push|backup|sauvegarde/i', $msg)) {
|
||
$cmd = 'cd /opt/wevads-arsenal 2>/dev/null && git add -A && git commit -m "Auto-backup $(date +%Y%m%d-%H%M)" 2>&1 && git push origin master 2>&1 | tail -5 || echo "Rien à committer"';
|
||
$label = "Git Push";
|
||
}
|
||
// ── NONREG / TESTS ──
|
||
elseif(preg_match('/nonreg|test|regression|lance.*test|teste/i', $msg)) {
|
||
$cmd = 'bash /tmp/nr4.sh 2>&1 | tail -20';
|
||
$label = "NonReg Ethica";
|
||
}
|
||
// ── DISK ──
|
||
elseif(preg_match('/disk|disque|espace|storage/i', $msg)) {
|
||
$cmd = 'df -h / && echo "---Top dirs:" && du -sh /opt/* 2>/dev/null | sort -rh | head -8';
|
||
$label = "Espace disque S95";
|
||
}
|
||
// ── LOGS ──
|
||
elseif(preg_match('/log|error|erreur|debug/i', $msg)) {
|
||
$cmd = 'echo "=== Apache ===" && tail -10 /var/log/apache2/error.log 2>/dev/null | tail -5 && echo "=== PHP ===" && tail -5 /var/log/php*.log 2>/dev/null | tail -3';
|
||
$label = "Logs récents";
|
||
}
|
||
// ── CRON ──
|
||
elseif(preg_match('/cron|tache|tâche|schedule|planif/i', $msg)) {
|
||
$cmd = 'crontab -l 2>/dev/null | grep -v "^#" | grep -v "^$" | head -15';
|
||
$label = "Crons actifs";
|
||
}
|
||
// ── PROCESSES ──
|
||
elseif(preg_match('/process|processus|qui tourne|running/i', $msg)) {
|
||
$cmd = 'ps aux --sort=-%mem | head -15';
|
||
$label = "Top processus";
|
||
}
|
||
// ── KILL ──
|
||
elseif(preg_match('/kill|stop|arrêt|arrete|tue\s/i', $msg)) {
|
||
$m = [];
|
||
preg_match('/(?:kill|stop|tue)\s+(\S+)/i', $msg, $m);
|
||
if(!empty($m[1])) {
|
||
$cmd = 'pkill -f "' . $m[1] . '" 2>&1 && echo "' . $m[1] . ' arrêté" || echo "Pas trouvé"';
|
||
} else {
|
||
return ["ok"=>true,"provider"=>"direct","complexity"=>"direct","response"=>"Dis-moi quoi arrêter : **kill [nom_process]**"];
|
||
}
|
||
$label = "Kill process";
|
||
}
|
||
// ── S204 ──
|
||
elseif(preg_match('/s204|serveur principal|server principal|nginx/i', $msg)) {
|
||
$raw = execOnS95('curl -s --max-time 5 http://204.168.152.13/api/wedroid-brain-api.php?k=DROID2026\\&action=status 2>/dev/null');
|
||
$data = json_decode($raw, true);
|
||
$resp = "**S204** — OK\n\nProviders IA:\n";
|
||
foreach(($data['providers']??[]) as $n=>$p) $resp .= " $n: {$p['status']} ({$p['role']})\n";
|
||
return ["ok"=>true,"provider"=>"direct","complexity"=>"direct","response"=>$resp,"thinking"=>"Status S204 via API interne"];
|
||
}
|
||
// ── S151 ──
|
||
elseif(preg_match('/s151|tracking/i', $msg)) {
|
||
$raw = execOnS95('curl -s -o /dev/null -w "%{http_code}" --max-time 5 http://151.80.235.110 2>/dev/null');
|
||
return ["ok"=>true,"provider"=>"direct","complexity"=>"direct","response"=>"**S151 Tracking**: HTTP $raw"];
|
||
}
|
||
// ── DIRECT BASH ──
|
||
elseif(preg_match('/^[\/$]/', $msg)) {
|
||
$cmd = preg_replace('/^[\/$]\s*/', '', $msg);
|
||
$label = "Bash";
|
||
}
|
||
elseif(preg_match('/^(ls|df|ps|top|uptime|whoami|date|pwd|free|cat|head|tail|grep|wc|find|du|ss|curl|systemctl|journalctl|PGPASSWORD)/i', $msg)) {
|
||
$cmd = $msg;
|
||
$label = "Bash";
|
||
}
|
||
|
||
if($cmd) {
|
||
$output = execOnS95($cmd);
|
||
$resp = $label ? "**$label:**\n```\n$output\n```" : "```\n$output\n```";
|
||
return [
|
||
"ok" => true, "provider" => "direct", "complexity" => "direct",
|
||
"response" => $resp,
|
||
"thinking" => "Exécution directe (pas d'IA nécessaire)",
|
||
"commands" => [["cmd" => substr($cmd, 0, 120), "output" => substr($output, 0, 1500), "success" => true]],
|
||
];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// EXECUTE ON S95 (via SSH from S204)
|
||
// ═══════════════════════════════════════════════════════
|
||
function execOnS95($cmd) {
|
||
global $SSH_CMD;
|
||
$escaped = str_replace('"', '\\"', $cmd);
|
||
$full = "$SSH_CMD \"$escaped\" 2>&1";
|
||
$output = shell_exec($full);
|
||
return trim($output ?? "");
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// CALL OLLAMA (on S95 via SSH)
|
||
// ═══════════════════════════════════════════════════════
|
||
function callOllama($messages, $model = 'tinyllama:latest') {
|
||
global $SSH_CMD;
|
||
|
||
// Build SHORT prompt for local model (save tokens = faster)
|
||
$prompt = "'.WEVAL_BRAND_CONTEXT.'Tu es WEDROID, assistant serveur WEVAL. Réponds en français, court et précis.\n\n";
|
||
// Only last 2 messages for local (save tokens)
|
||
$recent = array_slice($messages, -2);
|
||
foreach($recent as $m) {
|
||
$role = $m['role'] === 'user' ? 'Q' : 'R';
|
||
$prompt .= "$role: " . substr($m['content'], 0, 500) . "\n";
|
||
}
|
||
$prompt .= "R:";
|
||
|
||
$json_payload = json_encode([
|
||
"model" => $model,
|
||
"prompt" => $prompt,
|
||
"stream" => false,
|
||
"options" => ["temperature" => 0.2, "num_predict" => 300, "num_ctx" => 2048]
|
||
]);
|
||
|
||
$b64 = base64_encode($json_payload);
|
||
// Timeout 30s for tinyllama, 60s for bigger models
|
||
$timeout = ($model === 'tinyllama:latest' || $model === 'phi:latest') ? 20 : 45;
|
||
$cmd = "echo '$b64' | base64 -d > /tmp/ollama_req.json && curl -s --max-time $timeout http://localhost:11434/api/generate -d @/tmp/ollama_req.json 2>/dev/null";
|
||
|
||
$raw = execOnS95($cmd);
|
||
if(!$raw) return null;
|
||
|
||
$data = json_decode($raw, true);
|
||
return $data['response'] ?? null;
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// CALL CEREBRAS/GROQ (cloud)
|
||
// ═══════════════════════════════════════════════════════
|
||
function callCloud($messages, $provider = 'cerebras') {
|
||
// === SOVEREIGN FIRST: sovereign-api:4000 has ALL 12 provider keys ===
|
||
$sov_ch = curl_init('http://127.0.0.1:4000/v1/chat/completions');
|
||
curl_setopt_array($sov_ch, [
|
||
CURLOPT_POST => true,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 15,
|
||
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
||
CURLOPT_POSTFIELDS => json_encode([
|
||
'model' => 'auto',
|
||
'messages' => $messages,
|
||
'max_tokens' => 1500,
|
||
'temperature' => 0.3,
|
||
])
|
||
]);
|
||
$sov_resp = curl_exec($sov_ch);
|
||
$sov_code = curl_getinfo($sov_ch, CURLINFO_HTTP_CODE);
|
||
curl_close($sov_ch);
|
||
if ($sov_code === 200) {
|
||
$sov_data = json_decode($sov_resp, true);
|
||
if (isset($sov_data['choices'][0]['message']['content'])) {
|
||
return $sov_data['choices'][0]['message']['content'];
|
||
}
|
||
}
|
||
// === Fallback: direct provider calls (original) ===
|
||
|
||
global $CEREBRAS_KEY, $CEREBRAS_MODEL, $CEREBRAS_URL, $GROQ_KEY, $GROQ_MODEL, $GROQ_URL;
|
||
global $MISTRAL_KEY, $MISTRAL_MODEL, $MISTRAL_URL, $SAMBANOVA_KEY, $SAMBANOVA_MODEL, $SAMBANOVA_URL;
|
||
global $DASHSCOPE_KEY, $DASHSCOPE_MODEL, $DASHSCOPE_URL, $OPENROUTER_KEY, $OPENROUTER_MODEL, $OPENROUTER_URL;
|
||
|
||
$providers = [
|
||
'groq' => [$GROQ_URL, $GROQ_KEY, $GROQ_MODEL],
|
||
'cerebras' => [$CEREBRAS_URL, $CEREBRAS_KEY, $CEREBRAS_MODEL],
|
||
'mistral' => [$MISTRAL_URL, $MISTRAL_KEY, $MISTRAL_MODEL],
|
||
'sambanova' => [$SAMBANOVA_URL, $SAMBANOVA_KEY, $SAMBANOVA_MODEL],
|
||
'alibaba' => [$DASHSCOPE_URL, $DASHSCOPE_KEY, $DASHSCOPE_MODEL],
|
||
'openrouter'=> [$OPENROUTER_URL, $OPENROUTER_KEY, $OPENROUTER_MODEL],
|
||
];
|
||
if(!isset($providers[$provider])) $provider = 'groq';
|
||
[$url, $key, $model] = $providers[$provider];
|
||
|
||
if(!$key) return null;
|
||
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_POST => true,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 30,
|
||
CURLOPT_HTTPHEADER => [
|
||
"Authorization: Bearer $key",
|
||
"Content-Type: application/json"
|
||
],
|
||
CURLOPT_POSTFIELDS => json_encode([
|
||
"model" => $model,
|
||
"messages" => $messages,
|
||
"max_tokens" => 1500,
|
||
"temperature" => 0.3,
|
||
])
|
||
]);
|
||
|
||
$resp = curl_exec($ch);
|
||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if($code === 429) return null; // Rate limited → fallback
|
||
if($code !== 200) return null;
|
||
|
||
$data = json_decode($resp, true);
|
||
return $data['choices'][0]['message']['content'] ?? null;
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// AGENTIC LOOP: Think → Plan → Execute → Verify
|
||
// ═══════════════════════════════════════════════════════
|
||
function agenticProcess($message, $provider_response, $complexity) {
|
||
$result = [
|
||
"thinking" => null,
|
||
"response" => $provider_response,
|
||
"commands" => [],
|
||
"verify" => null,
|
||
"followup" => null,
|
||
];
|
||
|
||
// Extract bash commands from AI response
|
||
if(preg_match_all('/```(?:bash|sh)?\s*\n(.*?)\n```/s', $provider_response, $matches)) {
|
||
foreach($matches[1] as $cmd_block) {
|
||
$cmds = array_filter(explode("\n", trim($cmd_block)));
|
||
foreach($cmds as $cmd) {
|
||
$cmd = trim($cmd);
|
||
if(empty($cmd) || $cmd[0] === '#') continue;
|
||
|
||
// Safety check
|
||
if(preg_match('/rm\s+-rf\s+\/[^t]|mkfs|dd\s+if|shutdown|reboot|poweroff/i', $cmd)) {
|
||
$result["commands"][] = ["cmd" => $cmd, "output" => "BLOQUÉ — commande dangereuse", "success" => false];
|
||
continue;
|
||
}
|
||
|
||
// Execute on S95
|
||
$output = execOnS95($cmd);
|
||
$result["commands"][] = [
|
||
"cmd" => $cmd,
|
||
"output" => substr($output, 0, 1000),
|
||
"success" => (strlen($output) > 0 || strpos($output, 'error') === false),
|
||
"description" => ""
|
||
];
|
||
}
|
||
}
|
||
}
|
||
|
||
// Also handle inline commands like: `commande`
|
||
if(empty($result["commands"]) && preg_match_all('/`([^`]+)`/', $provider_response, $inline_matches)) {
|
||
foreach($inline_matches[1] as $cmd) {
|
||
if(preg_match('/^(curl|ls|cat|grep|ps|df|du|wc|head|tail|echo|systemctl|git|php|bash|PGPASSWORD)/', $cmd)) {
|
||
$output = execOnS95($cmd);
|
||
$result["commands"][] = ["cmd" => $cmd, "output" => substr($output, 0, 500), "success" => true];
|
||
}
|
||
}
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// MAIN ROUTER
|
||
// ═══════════════════════════════════════════════════════
|
||
|
||
|
||
// ═══ EXTENDED ACTIONS (wired by Opus) ═══
|
||
if ($action === 'chain') { $r = chainExecute($message); echo json_encode($r); exit; }
|
||
if ($action === 'git') { echo json_encode(['ok'=>true,'result'=>gitAutoPush($message?:'auto')]); exit; }
|
||
if ($action === 'alert') { echo json_encode(telegramAlert($message, $_POST['level']??'info')); exit; }
|
||
if ($action === 'heal') { require_once '/var/www/weval/wevia-ia/wevia-self-healing.php'; echo json_encode(weviaSelfHeal()); exit; }
|
||
if ($action === 'schedule') { echo json_encode(wedroidRunSchedule()); exit; }
|
||
if ($action === 'learn') { wedroidLearn($message, $_POST['output']??'', true, $_POST['context']??''); echo json_encode(['ok'=>true]); exit; }
|
||
if ($action === 'recall') { echo json_encode(['ok'=>true,'knowledge'=>wedroidRecall($message)]); exit; }
|
||
if ($action === 'providers') { require_once '/var/www/weval/wevia-ia/wevia-provider-autoscale.php'; echo json_encode(weviaCheckProviders()); exit; }
|
||
if ($action === 'rollback') { require_once '/var/www/weval/wevia-ia/wevia-rollback.php'; echo json_encode(weviaListVersions($_POST['path']??'')); exit; }
|
||
if ($action === 'email_check') { $pdo=new PDO('pgsql:host=127.0.0.1;dbname=adx_system','admin',weval_secret('WEVAL_PG_ADMIN_PASS')); $c=$pdo->query("SELECT count(*) as t FROM admin.contact_messages")->fetch(PDO::FETCH_ASSOC); $u=$pdo->query("SELECT count(*) as t FROM admin.users")->fetch(PDO::FETCH_ASSOC); echo json_encode(['ok'=>true,'contacts'=>$c['t']??0,'users'=>$u['t']??0,'routing'=>'ymahboub+yacineutt']); exit; }
|
||
if($action === 'status') {
|
||
echo json_encode([
|
||
"ok" => true,
|
||
"version" => "5.0",
|
||
"providers" => [
|
||
"ollama" => ["status" => "active", "models" => array_values($OLLAMA_MODELS), "role" => "primary (80%)"],
|
||
"cerebras" => ["status" => $CEREBRAS_KEY ? "active" : "no_key", "model" => $CEREBRAS_MODEL, "role" => "complex (15%)"],
|
||
"groq" => ["status" => $GROQ_KEY ? "active" : "no_key", "model" => $GROQ_MODEL, "role" => "fast cloud"],
|
||
"mistral" => ["status" => $MISTRAL_KEY ? "active" : "no_key", "model" => $MISTRAL_MODEL, "role" => "multilingual"],
|
||
"sambanova" => ["status" => $SAMBANOVA_KEY ? "active" : "no_key", "model" => $SAMBANOVA_MODEL, "role" => "heavy"],
|
||
"alibaba" => ["status" => $DASHSCOPE_KEY ? "pending" : "no_key", "model" => $DASHSCOPE_MODEL, "role" => "chinese"],
|
||
"openrouter" => ["status" => $OPENROUTER_KEY ? "active" : "no_key", "model" => $OPENROUTER_MODEL, "role" => "free fallback"],
|
||
],
|
||
"routing" => "SOVEREIGN: ollama>groq>cerebras>mistral>sambanova>alibaba>openrouter",
|
||
"session" => $session,
|
||
]);
|
||
exit;
|
||
}
|
||
|
||
if($action === 'chat' && $message) {
|
||
$start = microtime(true);
|
||
$complexity = detectComplexity($message);
|
||
$provider_used = "unknown";
|
||
$response = null;
|
||
|
||
// STEP 1: Try direct execution (no AI)
|
||
$direct = execDirect($message);
|
||
if($direct) {
|
||
$direct["complexity"] = "direct";
|
||
$direct["duration_ms"] = round((microtime(true) - $start) * 1000);
|
||
|
||
// Save to session
|
||
$history[] = ["role" => "user", "content" => $message];
|
||
$history[] = ["role" => "assistant", "content" => $direct["response"]];
|
||
file_put_contents($session_file, json_encode($history));
|
||
|
||
echo json_encode($direct);
|
||
exit;
|
||
}
|
||
|
||
// STEP 2: Build messages with system prompt + history
|
||
$messages = [["role" => "system", "content" => $SYSTEM]];
|
||
foreach(array_slice($history, -6) as $h) {
|
||
$messages[] = $h;
|
||
}
|
||
$messages[] = ["role" => "user", "content" => $message];
|
||
|
||
// STEP 3: Route based on complexity
|
||
// STRATEGY: 90% Direct (instant) → 7% Cerebras (1-3s) → 2% Groq → 1% Ollama (emergency)
|
||
// LOCAL = Direct bash execution (instant, rich, 90% coverage)
|
||
// Ollama on CPU = too slow (17-60s) and low quality — emergency only
|
||
|
||
// === SOVEREIGN CASCADE: Force provider > Ollama > Cloud chain ===
|
||
$cascade = ['groq','cerebras','mistral','sambanova','alibaba','openrouter'];
|
||
|
||
// Force provider from frontend
|
||
if($force_provider && $force_provider !== 'auto') {
|
||
if($force_provider === 'ollama' || $force_provider === 'ollama_local') {
|
||
$m = ($complexity === 'complex') ? 'qwen3:8b' : 'qwen3:4b';
|
||
$response = callCloud($messages, 'cerebras');
|
||
$provider_used = 'cerebras-forced';
|
||
} else {
|
||
$response = callCloud($messages, $force_provider);
|
||
$provider_used = $force_provider;
|
||
}
|
||
}
|
||
|
||
// Auto routing
|
||
if(!$response) {
|
||
if($complexity === 'complex') {
|
||
// Complex: Cerebras 235B first (best reasoning), then cascade
|
||
$cascade = ['cerebras','groq','mistral','sambanova','alibaba','openrouter'];
|
||
} elseif($complexity === 'simple') {
|
||
// Simple: Ollama local first (sovereign), then fast cloud
|
||
$response = callCloud($messages, 'cerebras');
|
||
$provider_used = "cerebras";
|
||
}
|
||
// Medium + fallback: cloud cascade
|
||
if(!$response) {
|
||
foreach($cascade as $cp) {
|
||
$response = callCloud($messages, $cp);
|
||
if($response) { $provider_used = $cp; break; }
|
||
}
|
||
}
|
||
// Ultimate fallback: Ollama heavy
|
||
if(!$response) {
|
||
$response = callCloud($messages, 'groq');
|
||
$provider_used = "groq-fallback";
|
||
}
|
||
}
|
||
|
||
if(!$response) {
|
||
echo json_encode(["ok" => false, "error" => "Tous les providers sont down ou rate-limited"]);
|
||
exit;
|
||
}
|
||
|
||
// FLATTEN: if response is nested JSON from Cerebras, extract inner response
|
||
if (is_string($response) && trim($response) !== "" && trim($response)[0] === "{") {
|
||
$inner = @json_decode(trim($response), true);
|
||
if ($inner && isset($inner["response"])) {
|
||
$response = $inner["response"];
|
||
}
|
||
}
|
||
|
||
// STEP 4: Agentic processing (extract + execute commands)
|
||
$result = agenticProcess($message, $response, $complexity);
|
||
|
||
// STEP 5: Save to session
|
||
$history[] = ["role" => "user", "content" => $message];
|
||
$history[] = ["role" => "assistant", "content" => $response];
|
||
file_put_contents($session_file, json_encode($history));
|
||
|
||
// STEP 6: Return
|
||
echo json_encode([
|
||
"ok" => true,
|
||
"provider" => $provider_used,
|
||
"complexity" => $complexity,
|
||
"response" => $result["response"],
|
||
"thinking" => $result["thinking"],
|
||
"commands" => $result["commands"],
|
||
"verify" => $result["verify"],
|
||
"followup" => $result["followup"],
|
||
"duration_ms" => round((microtime(true) - $start) * 1000),
|
||
"session" => $session,
|
||
]);
|
||
exit;
|
||
}
|
||
|
||
// Default
|
||
echo json_encode(["ok" => true, "version" => "5.0", "actions" => ["chat", "status"]]);
|