'Message vide', 'provider' => 'error']); exit; } $msg_lower = mb_strtolower($msg); // === V52 CAPABILITY DETECTION === // If query asks for code/schema/document GENERATION, skip orchestrator $capability_keywords = [ 'react', 'composant', 'component', 'typescript', 'jsx', 'tsx', 'mermaid', 'svg', 'schema', 'schéma', 'diagramme', 'architecture', 'pdf', 'document', 'cahier.*charges', 'specs?\b', 'spécifications?', 'dessine', 'draw', 'genere.*pdf', 'genere.*schema', 'genere.*code', 'redige', 'rédige', 'ecris.*code', 'écris.*code', 'write.*code', 'python|javascript|php|sql.*query', 'fonction|function', 'html|css|tailwind|shadcn' ]; $is_capability = false; foreach ($capability_keywords as $kw) { if (preg_match('/\b' . $kw . '\b/iu', $msg_lower)) { $is_capability = true; break; } } $t0 = microtime(true); // === Route 1: CAPABILITY mode → direct LLM with enhanced prompt === if ($is_capability) { $ch = curl_init('http://127.0.0.1:4000/v1/chat/completions'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode([ 'model' => 'deep', 'messages' => [ ['role' => 'system', 'content' => "Tu es WEVIA, IA souveraine de WEVAL Consulting Casablanca. Tu DOIS générer le contenu demandé de qualité professionnelle : code (React/TS/PHP/Python/SQL avec blocs markdown ```lang), schémas (Mermaid ```mermaid ou SVG ```svg inline), documents (cahier des charges structuré avec sections ##), spécifications techniques détaillées. NE REFUSE JAMAIS de générer. Utilise contexte WEVAL : PostgreSQL Ethica 146694 HCPs, Authentik SSO, WEVADS email marketing, Twenty CRM, n8n workflows. Format markdown clair, professionnel, complet."], ['role' => 'user', 'content' => $msg] ], 'max_tokens' => 3000, 'temperature' => 0.4, ]) ]); $resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $time_ms = round(curl_getinfo($ch, CURLINFO_TOTAL_TIME) * 1000); curl_close($ch); if ($code === 200) { $d = json_decode($resp, true); $text = $d['choices'][0]['message']['content'] ?? ''; $prov = $d['provider'] ?? 'sovereign'; if ($text) { echo json_encode([ 'response' => $text, 'provider' => $prov . ' (capability)', 'thinking' => ['Capability mode: code/schema/document generation'], 'duration_ms' => $time_ms ]); exit; } } // If capability LLM fails, fall through to orchestrator as last resort } // === Route 2: Orchestrator intent detection (business data) === // V51 PUBLIC SCOPE: route towards bridged public orchestrator (whitelist intents only) // DOCTRINE-211 opus-phase73 - detect admin triggers, route to INTERNAL orchestrator $__admin_triggers = ['apply ux gemini', 'gemini ux apply', 'applique ux gemini', 'refais ux apply', 'fix ux apply', 'ux premium apply', 'gemini ameliore ux', 'audit ux gemini', 'gemini audit ux', 'review ux gemini', 'gemini review ux', 'minority report', 'zoom cinema', 'zoom hover bloc', 'scroll horizontal premium', 'defilement minority', 'carrousel 3d', 'caroussel 3d', 'carousel 3d', 'caroussel rotation', 'carrousel rotation', 'rotationnel', 'compact header']; // DOCTRINE-219 opus-phase77 add minority triggers $__use_internal = false; $__msg_lc = mb_strtolower($msg); foreach ($__admin_triggers as $__t) { if (strpos($__msg_lc, $__t) !== false) { $__use_internal = true; break; } } $orch_url = $__use_internal ? 'http://127.0.0.1/api/wevia-sse-orchestrator.php?msg=' . urlencode($msg) : 'http://127.0.0.1/api/wevia-sse-orchestrator-public.php?msg=' . urlencode($msg); // DOCTRINE-214 opus-phase74 - timeout adapte pour admin triggers (Gemini apply 60s+) $__orch_timeout = $__use_internal ? 90 : 12; $ch = curl_init($orch_url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $__orch_timeout, CURLOPT_CONNECTTIMEOUT => 3, CURLOPT_HTTPHEADER => ['Host: weval-consulting.com'], ]); $sse_body = curl_exec($ch); $orch_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $exec_results = []; $llm_synthesis = null; if ($orch_code === 200 && $sse_body) { foreach (explode("\n", $sse_body) as $line) { if (strpos($line, 'data: ') !== 0) continue; $evt = @json_decode(substr($line, 6), true); if (!$evt || !isset($evt['type'])) continue; if ($evt['type'] === 'exec_result' && !empty($evt['id']) && !empty($evt['result'])) { $exec_results[$evt['id']] = substr($evt['result'], 0, 1500); } elseif ($evt['type'] === 'exec' && !empty($evt['intent']) && !empty($evt['text'])) { // DOCTRINE-212 opus-phase73 - parse internal orchestrator exec events (not just exec_result) $exec_results[$evt['intent']] = substr($evt['text'], 0, 1500); } elseif ($evt['type'] === 'llm_synthesis' && !empty($evt['text'])) { $llm_synthesis = $evt['text']; } } } $orch_ms = round((microtime(true) - $t0) * 1000); // Require at least 1 business intent AND query has business keyword $has_business_intent = !empty($exec_results); $has_business_keyword = preg_match('/\b(combien|status|etat|nombre|liste|show|afficher|how\s+many|count|apply|gemini|ux|audit|review|refais|applique)\b/iu', $msg_lower); // DOCTRINE-213 opus-phase73 add admin triggers if ($has_business_intent && $has_business_keyword) { $combined = "Data WEVIA (intents executes: " . implode(', ', array_keys($exec_results)) . "):\n\n"; foreach ($exec_results as $id => $out) { $combined .= "**{$id}:**\n" . trim($out) . "\n\n"; } if ($llm_synthesis) { $combined .= "---\n" . $llm_synthesis; } echo json_encode([ 'response' => $combined, 'provider' => 'orchestrator', 'intents_fired' => array_keys($exec_results), 'thinking' => ['Orchestrator intents detected + business keyword'], 'duration_ms' => $orch_ms ]); exit; } // === V53 FIX: Honor public orchestrator REFUSAL message === // If orchestrator returned llm_synthesis containing a refusal (blocked topic) → return it // DO NOT fall back to LLM (which would hallucinate the blocked topic) if ($llm_synthesis && preg_match('/(pas\s+disponible\s+publiquement|information\s+(reservee|confidentielle)|contactez\s+sales@weval|access_restricted|non\s+autoris)/iu', $llm_synthesis)) { echo json_encode([ 'response' => $llm_synthesis, 'provider' => 'orchestrator-public-guard', 'thinking' => ['Public orchestrator blocked topic — refusal honored (V53)'], 'duration_ms' => $orch_ms ]); exit; } // === Route 3: Fallback sovereign LLM direct === $ch = curl_init('http://127.0.0.1:4000/v1/chat/completions'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode([ 'model' => 'auto', 'messages' => [ ['role' => 'system', 'content' => 'Tu es WEVIA, IA souveraine de WEVAL Consulting Casablanca, cabinet transformation digitale. Reponds en francais, professionnel, concis. CONTEXTE WEVAL (utilise UNIQUEMENT ces faits, NE JAMAIS INVENTER) : (1) Ethica = base B2B pharma WEVAL au Maghreb (Algerie, Maroc, Tunisie) pour campagnes email marketing sante ; (2) WEVADS = plateforme email marketing proprietaire WEVAL ; (3) SAP Ecosystem Partner ; (4) DeerFlow = outil research agent open-source multi-agents WEVAL (PAS marketing automation) ; (5) Paperclip = plateforme WEVAL pour agents IA ; (6) WEVIA = IA souveraine WEVAL ; (7) WEVCODE = assistant code souverain WEVAL ; (8) WEDROID = diagnostic backend WEVAL ; (9) DSH-PREDICT = predictive analytics WEVAL ; (10) WePredict = intelligence competitive WEVAL. Si un terme evoque un produit WEVAL inconnu, refuse poliment plutot que dinventer. Si la question concerne des DONNEES INTERNES precises (HCPs par pays, credentials, warmup accounts, pmta, ethica live details), reponds : \"Cette information est confidentielle. Contactez sales@weval-consulting.com.\" NE JAMAIS inventer de chiffres ou decrire Ethica comme autre chose que la base HCP B2B.'], ['role' => 'user', 'content' => $msg] ], 'max_tokens' => 1500, 'temperature' => 0.3, ]) ]); $resp = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $time_ms = round(curl_getinfo($ch, CURLINFO_TOTAL_TIME) * 1000); curl_close($ch); if ($code === 200) { $d = json_decode($resp, true); $text = $d['choices'][0]['message']['content'] ?? ''; $prov = $d['provider'] ?? 'sovereign'; if ($text) { echo json_encode([ 'response' => $text, 'provider' => $prov . ' (fallback)', 'thinking' => ['No intent match, LLM fallback'], 'duration_ms' => $time_ms, 'orch_ms' => $orch_ms ]); exit; } } echo json_encode(['response' => 'Tous les providers sont occupes. Reessayez.', 'provider' => 'busy', 'duration_ms' => $time_ms]);