diff --git a/api/ambre-autonomous-scan.php b/api/ambre-autonomous-scan.php new file mode 100644 index 000000000..65304cbc4 --- /dev/null +++ b/api/ambre-autonomous-scan.php @@ -0,0 +1,19 @@ + strlen($c), + "has_resolver" => strpos($c, "Resolver") !== false, + "has_multiagent" => strpos($c, "multiagent") !== false || strpos($c, "multi-agent") !== false, + "has_parallel" => strpos($c, "parallel") !== false || strpos($c, "curl_multi") !== false, + "has_plan_exec" => strpos($c, "plan") !== false && strpos($c, "execute") !== false, +]; +// First 500 chars +$out["header"] = substr($c, 0, 500); + +// Quick test if endpoint alive +$test = @file_get_contents("http://127.0.0.1/api/wevia-autonomous.php?test&q=hello", false, stream_context_create(["http"=>["timeout"=>5]])); +$out["test_response"] = substr($test ?? "FAIL", 0, 300); + +echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); diff --git a/api/ambre-multiagent-parallel.php b/api/ambre-multiagent-parallel.php new file mode 100644 index 000000000..060a11517 --- /dev/null +++ b/api/ambre-multiagent-parallel.php @@ -0,0 +1,190 @@ +"goal required"]); + exit; +} + +$t0 = microtime(true); + +// Step 1 · PLAN via LLM (one call, JSON structured) +$plan_sys = "Tu es un planificateur multi-agent. Pour l'objectif donné, génère STRICTEMENT un JSON avec cette structure :\n" . + "{\n" . + " \"objective\": \"\",\n" . + " \"agents\": [\n" . + " {\"role\":\"researcher\", \"task\":\"\", \"tool\":\"\"},\n" . + " ...\n" . + " ]\n" . + "}\n" . + "Maximum $max_agents agents. Chaque agent a un role distinct et une tâche autonome. NE réponds QUE le JSON."; + +$plan_raw = @file_get_contents("http://127.0.0.1:4000/v1/chat/completions", false, stream_context_create([ + "http" => [ + "method" => "POST", + "header" => "Content-Type: application/json\r\n", + "content" => json_encode([ + "model" => "fast", + "messages" => [ + ["role"=>"system","content"=>$plan_sys], + ["role"=>"user","content"=>"Objectif: " . $goal] + ], + "max_tokens" => 800, + "temperature" => 0.3, + ]), + "timeout" => 20, + ], +])); + +$plan_data = @json_decode($plan_raw, true); +$plan_text = $plan_data["choices"][0]["message"]["content"] ?? ""; +$plan_text = preg_replace('/^```(?:json)?\s*/m', '', $plan_text); +$plan_text = preg_replace('/\s*```\s*$/m', '', trim($plan_text)); +$plan = @json_decode($plan_text, true); + +if (!$plan || !isset($plan["agents"]) || !is_array($plan["agents"])) { + echo json_encode(["error"=>"LLM plan invalid", "raw"=>substr($plan_text, 0, 500)]); + exit; +} + +$plan_ms = round((microtime(true)-$t0)*1000); + +// Step 2 · EXECUTE agents en parallèle via curl_multi +$agents = array_slice($plan["agents"], 0, $max_agents); +$mh = curl_multi_init(); +$handles = []; + +$tool_map = [ + "pdf_premium" => ["url"=>"http://127.0.0.1/api/ambre-tool-pdf-premium.php", "body_key"=>"topic"], + "mermaid" => ["url"=>"http://127.0.0.1/api/ambre-tool-mermaid.php", "body_key"=>"topic"], + "web_search" => ["url"=>"http://127.0.0.1/api/ambre-tool-web-search.php", "body_key"=>"query"], + "calc" => ["url"=>"http://127.0.0.1/api/ambre-tool-calc.php", "body_key"=>"expression"], + "kb_search" => ["url"=>"http://127.0.0.1/api/ambre-mermaid-learn.php", "body_key"=>"query"], +]; + +$exec_t0 = microtime(true); +foreach ($agents as $i => $agent) { + $tool = $agent["tool"] ?? "none"; + $task = $agent["task"] ?? ""; + if ($tool === "none" || !isset($tool_map[$tool])) { + // No tool → LLM direct reasoning + $ch = curl_init("http://127.0.0.1:4000/v1/chat/completions"); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_HTTPHEADER => ["Content-Type: application/json"], + CURLOPT_POSTFIELDS => json_encode([ + "model"=>"fast", + "messages"=>[["role"=>"user","content"=>$task]], + "max_tokens"=>400, + ]), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 30, + CURLOPT_CONNECTTIMEOUT => 3, + ]); + } else { + $cfg = $tool_map[$tool]; + $body = [$cfg["body_key"] => $task]; + if ($tool === "kb_search") $body["action"] = "search"; + $ch = curl_init($cfg["url"]); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_HTTPHEADER => ["Content-Type: application/json"], + CURLOPT_POSTFIELDS => json_encode($body), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 45, + CURLOPT_CONNECTTIMEOUT => 3, + ]); + } + curl_multi_add_handle($mh, $ch); + $handles[$i] = ["ch"=>$ch, "agent"=>$agent, "t0"=>microtime(true)]; +} + +// Run all in parallel +$running = null; +do { + curl_multi_exec($mh, $running); + curl_multi_select($mh, 0.5); +} while ($running > 0); + +// Collect results +$results = []; +foreach ($handles as $i => $h) { + $output = curl_multi_getcontent($h["ch"]); + $elapsed_ms = round((microtime(true)-$h["t0"])*1000); + $http_code = curl_getinfo($h["ch"], CURLINFO_HTTP_CODE); + curl_multi_remove_handle($mh, $h["ch"]); + curl_close($h["ch"]); + + // Try to extract meaningful summary + $result_data = @json_decode($output, true); + $summary = ""; + if ($result_data) { + if (isset($result_data["mermaid_code"])) $summary = "Mermaid généré (" . strlen($result_data["mermaid_code"]) . "B)"; + elseif (isset($result_data["url"])) $summary = "PDF: " . $result_data["url"]; + elseif (isset($result_data["choices"][0]["message"]["content"])) $summary = substr($result_data["choices"][0]["message"]["content"], 0, 300); + elseif (isset($result_data["result"])) $summary = (string)$result_data["result"]; + else $summary = substr($output, 0, 200); + } else { + $summary = substr($output ?: "empty", 0, 200); + } + + $results[] = [ + "agent" => $h["agent"]["role"] ?? "agent_$i", + "task" => $h["agent"]["task"] ?? "", + "tool" => $h["agent"]["tool"] ?? "none", + "http" => $http_code, + "elapsed_ms" => $elapsed_ms, + "summary" => $summary, + ]; +} +curl_multi_close($mh); +$exec_ms = round((microtime(true)-$exec_t0)*1000); + +// Step 3 · RECONCILE (synthesis LLM call) +$synth_input = "Objectif: " . $goal . "\n\nRésultats des " . count($results) . " agents:\n"; +foreach ($results as $r) { + $synth_input .= "- " . $r["agent"] . " (" . $r["tool"] . "): " . substr($r["summary"], 0, 300) . "\n"; +} +$synth_input .= "\nSynthétise la réponse finale en français clair et actionnable. Max 200 mots."; + +$synth_raw = @file_get_contents("http://127.0.0.1:4000/v1/chat/completions", false, stream_context_create([ + "http" => [ + "method" => "POST", + "header" => "Content-Type: application/json\r\n", + "content" => json_encode([ + "model" => "fast", + "messages" => [["role"=>"user","content"=>$synth_input]], + "max_tokens" => 500, + ]), + "timeout" => 15, + ], +])); +$synth_data = @json_decode($synth_raw, true); +$reconciled = $synth_data["choices"][0]["message"]["content"] ?? "Synthèse échouée"; + +echo json_encode([ + "ok" => true, + "goal" => $goal, + "plan" => $plan, + "plan_ms" => $plan_ms, + "results" => $results, + "exec_ms" => $exec_ms, + "parallel_speedup" => round(array_sum(array_column($results, "elapsed_ms")) / max($exec_ms, 1), 2), + "reconciled" => trim($reconciled), + "total_ms" => round((microtime(true)-$t0)*1000), + "agents_count" => count($results), + "provider" => "WEVIA MultiAgent Parallel Engine · wave-255", +], JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE); diff --git a/api/ambre-pw-tests/output/.last-run.json b/api/ambre-pw-tests/output/.last-run.json deleted file mode 100644 index cbcc1fbac..000000000 --- a/api/ambre-pw-tests/output/.last-run.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": "passed", - "failedTests": [] -} \ No newline at end of file diff --git a/api/ambre-pw-tests/output/.playwright-artifacts-0/page@88941be8f9124649cc8c90a15b26639c.webm b/api/ambre-pw-tests/output/.playwright-artifacts-0/page@88941be8f9124649cc8c90a15b26639c.webm new file mode 100644 index 000000000..e01c8d28c Binary files /dev/null and b/api/ambre-pw-tests/output/.playwright-artifacts-0/page@88941be8f9124649cc8c90a15b26639c.webm differ diff --git a/api/ambre-pw-tests/output/results.json b/api/ambre-pw-tests/output/results.json deleted file mode 100644 index 04a513dff..000000000 --- a/api/ambre-pw-tests/output/results.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "config": { - "configFile": "/var/www/html/api/ambre-pw-tests/playwright.config.js", - "rootDir": "/var/www/html/api/ambre-pw-tests/tests", - "forbidOnly": false, - "fullyParallel": false, - "globalSetup": null, - "globalTeardown": null, - "globalTimeout": 0, - "grep": {}, - "grepInvert": null, - "maxFailures": 0, - "metadata": { - "actualWorkers": 1 - }, - "preserveOutput": "always", - "projects": [ - { - "outputDir": "/var/www/html/api/ambre-pw-tests/output", - "repeatEach": 1, - "retries": 0, - "metadata": { - "actualWorkers": 1 - }, - "id": "chromium", - "name": "chromium", - "testDir": "/var/www/html/api/ambre-pw-tests/tests", - "testIgnore": [], - "testMatch": [ - "**/*.@(spec|test).?(c|m)[jt]s?(x)" - ], - "timeout": 420000 - } - ], - "quiet": false, - "reporter": [ - [ - "list", - null - ], - [ - "json", - { - "outputFile": "./output/results.json" - } - ] - ], - "reportSlowTests": { - "max": 5, - "threshold": 300000 - }, - "shard": null, - "tags": [], - "updateSnapshots": "missing", - "updateSourceMethod": "patch", - "version": "1.59.1", - "workers": 1, - "webServer": null - }, - "suites": [ - { - "title": "v44-pdf-proof.spec.js", - "file": "v44-pdf-proof.spec.js", - "column": 0, - "line": 0, - "specs": [ - { - "title": "V44 · PROOF · PDF Premium attend fin HI", - "ok": true, - "tags": [], - "tests": [ - { - "timeout": 240000, - "annotations": [], - "expectedStatus": "passed", - "projectId": "chromium", - "projectName": "chromium", - "results": [ - { - "workerIndex": 0, - "parallelIndex": 0, - "status": "passed", - "duration": 34669, - "errors": [], - "stdout": [ - { - "text": "📤 T1: bonjour sent\n" - }, - { - "text": " ✅ T1 done in 1.5s\n" - }, - { - "text": "📤 T2: PDF request sent · waiting...\n" - }, - { - "text": "\n═══ RESULT after 22.6s ═══\n" - }, - { - "text": " ✅ PDF generated: true\n" - }, - { - "text": " URL: https://weval-consulting.com/generated/wevia-pdf-premium-20260422-022905-cdb613.pdf\n" - }, - { - "text": " Text: 📊 PDF Premium généré : Comparaison WEVIA vs OPUS ● 3 sections avec contenu détaillé ● 3 KPI visualisés ● Graphique interactif (Chart.js intégré) ● ~4 pages · 113 KB 📥 Télécharger PDF 🖼 Pré\n" - }, - { - "text": " HTTP HEAD: {\"status\":200,\"size\":\"115701\",\"type\":\"application/pdf\"}\n" - } - ], - "stderr": [], - "retry": 0, - "startTime": "2026-04-22T02:28:32.489Z", - "annotations": [], - "attachments": [ - { - "name": "screenshot", - "contentType": "image/png", - "path": "/var/www/html/api/ambre-pw-tests/output/v44-pdf-proof-V44-·-PROOF-·-PDF-Premium-attend-fin-HI-chromium/test-finished-1.png" - }, - { - "name": "video", - "contentType": "video/webm", - "path": "/var/www/html/api/ambre-pw-tests/output/v44-pdf-proof-V44-·-PROOF-·-PDF-Premium-attend-fin-HI-chromium/video.webm" - } - ] - } - ], - "status": "expected" - } - ], - "id": "a9e3dcbbfa25c1da39a9-56c8db71813f7b096a5e", - "file": "v44-pdf-proof.spec.js", - "line": 3, - "column": 1 - } - ] - } - ], - "errors": [], - "stats": { - "startTime": "2026-04-22T02:28:31.950Z", - "duration": 35394.251000000004, - "expected": 1, - "skipped": 0, - "unexpected": 0, - "flaky": 0 - } -} \ No newline at end of file diff --git a/api/ambre-pw-tests/output/v44-01-hi-done.png b/api/ambre-pw-tests/output/v44-01-hi-done.png deleted file mode 100644 index 42ba0ab25..000000000 Binary files a/api/ambre-pw-tests/output/v44-01-hi-done.png and /dev/null differ diff --git a/api/ambre-pw-tests/output/v44-02-pdf-result.png b/api/ambre-pw-tests/output/v44-02-pdf-result.png deleted file mode 100644 index 2fda02971..000000000 Binary files a/api/ambre-pw-tests/output/v44-02-pdf-result.png and /dev/null differ diff --git a/api/ambre-pw-tests/output/v44-pdf-proof-V44-·-PROOF-·-PDF-Premium-attend-fin-HI-chromium/test-finished-1.png b/api/ambre-pw-tests/output/v44-pdf-proof-V44-·-PROOF-·-PDF-Premium-attend-fin-HI-chromium/test-finished-1.png deleted file mode 100644 index b5a38f28e..000000000 Binary files a/api/ambre-pw-tests/output/v44-pdf-proof-V44-·-PROOF-·-PDF-Premium-attend-fin-HI-chromium/test-finished-1.png and /dev/null differ diff --git a/api/ambre-pw-tests/output/v44-pdf-proof-V44-·-PROOF-·-PDF-Premium-attend-fin-HI-chromium/video.webm b/api/ambre-pw-tests/output/v44-pdf-proof-V44-·-PROOF-·-PDF-Premium-attend-fin-HI-chromium/video.webm deleted file mode 100644 index d0f27b93c..000000000 Binary files a/api/ambre-pw-tests/output/v44-pdf-proof-V44-·-PROOF-·-PDF-Premium-attend-fin-HI-chromium/video.webm and /dev/null differ diff --git a/api/ambre-pw-tests/output/v45-00-start.png b/api/ambre-pw-tests/output/v45-00-start.png new file mode 100644 index 000000000..f71878316 Binary files /dev/null and b/api/ambre-pw-tests/output/v45-00-start.png differ diff --git a/api/ambre-pw-tests/output/v45-01-hi.png b/api/ambre-pw-tests/output/v45-01-hi.png new file mode 100644 index 000000000..a17eeb0c7 Binary files /dev/null and b/api/ambre-pw-tests/output/v45-01-hi.png differ diff --git a/api/ambre-pw-tests/tests/v44-pdf-proof.spec.js b/api/ambre-pw-tests/tests/v44-pdf-proof.spec.js deleted file mode 100644 index 9685aa285..000000000 --- a/api/ambre-pw-tests/tests/v44-pdf-proof.spec.js +++ /dev/null @@ -1,89 +0,0 @@ -const { test } = require("@playwright/test"); - -test("V44 · PROOF · PDF Premium attend fin HI", async ({ page }) => { - test.setTimeout(240000); - - await page.goto("/wevia.html?cb=" + Date.now()); - await page.evaluate(() => { try{sessionStorage.clear(); localStorage.clear();}catch(e){} }); - await page.waitForLoadState("networkidle"); - await page.waitForTimeout(3500); - - // Turn 1: HI - wait for COMPLETE response - const input = page.locator("#msgInput"); - await input.click({force:true}); - await input.fill("bonjour"); - await page.waitForTimeout(400); - - const bc0 = await page.evaluate(() => document.querySelectorAll(".msg.assistant").length); - await input.press("Enter"); - console.log("📤 T1: bonjour sent"); - - // Wait for assistant count to grow AND busy flag to release - const ws1 = Date.now(); - while (Date.now() - ws1 < 90000) { - const s = await page.evaluate((bc) => ({ - cnt: document.querySelectorAll(".msg.assistant").length, - busy: window.busy || false, - disabled: document.getElementById("sendBtn")?.disabled || false, - }), bc0); - if (s.cnt > bc0 && !s.busy && !s.disabled) break; - await page.waitForTimeout(1500); - } - console.log(` ✅ T1 done in ${((Date.now()-ws1)/1000).toFixed(1)}s`); - await page.screenshot({ path: "output/v44-01-hi-done.png" }); - - // Turn 2: PDF request - await input.click({force:true}); - await page.keyboard.press("Control+A"); - await page.keyboard.press("Delete"); - await input.fill("fais moi un pdf premium de comparaison WEVIA versus OPUS avec avantages, inconvenients, couts, performances, integrations"); - await page.waitForTimeout(400); - - const bc1 = await page.evaluate(() => document.querySelectorAll(".msg.assistant").length); - await input.press("Enter"); - console.log("📤 T2: PDF request sent · waiting..."); - - const ws2 = Date.now(); - let pdfFound = false; - let pdfUrl = null; - let lastText = ""; - - while (Date.now() - ws2 < 120000) { - const s = await page.evaluate((bc) => { - const msgs = Array.from(document.querySelectorAll(".msg.assistant")); - const last = msgs.length > bc ? msgs[msgs.length-1] : null; - if (!last) return { cnt: msgs.length, pdf: null, text: "", links: 0 }; - const pdfLink = last.querySelector('a[href*=".pdf"]'); - const links = last.querySelectorAll('a').length; - return { - cnt: msgs.length, - pdf_url: pdfLink ? pdfLink.href : null, - text: (last.querySelector(".bubble")?.innerText || "").substring(0, 400), - links: links, - }; - }, bc1); - if (s.pdf_url) { - pdfFound = true; pdfUrl = s.pdf_url; lastText = s.text; - break; - } - lastText = s.text; - await page.waitForTimeout(2500); - } - - const el = ((Date.now()-ws2)/1000).toFixed(1); - console.log(`\n═══ RESULT after ${el}s ═══`); - console.log(` ✅ PDF generated: ${pdfFound}`); - console.log(` URL: ${pdfUrl}`); - console.log(` Text: ${lastText.substring(0,200).replace(/\n/g,' ')}`); - - await page.screenshot({ path: "output/v44-02-pdf-result.png", fullPage: true }); - - if (pdfFound) { - // Click the PDF link (or just verify) - const resp = await page.evaluate(async (url) => { - const r = await fetch(url, {method:'HEAD'}); - return { status: r.status, size: r.headers.get('content-length'), type: r.headers.get('content-type') }; - }, pdfUrl); - console.log(` HTTP HEAD: ${JSON.stringify(resp)}`); - } -}); diff --git a/api/ambre-pw-tests/tests/v45-multiagent.spec.js b/api/ambre-pw-tests/tests/v45-multiagent.spec.js new file mode 100644 index 000000000..260b33603 --- /dev/null +++ b/api/ambre-pw-tests/tests/v45-multiagent.spec.js @@ -0,0 +1,74 @@ +const { test } = require("@playwright/test"); + +test("V45 · PROOF · Multi-Agent Parallel PDF WEVIA OPUS", async ({ page }) => { + test.setTimeout(240000); + + await page.goto("/wevia.html?cb=" + Date.now()); + await page.evaluate(() => { try{sessionStorage.clear();}catch(e){} }); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(3500); + await page.screenshot({ path: "output/v45-00-start.png" }); + + // Turn 1: bonjour (warm up) + const input = page.locator("#msgInput"); + await input.click({force:true}); + await input.fill("bonjour"); + await input.press("Enter"); + + const ws1 = Date.now(); + while (Date.now() - ws1 < 60000) { + const b = await page.evaluate(() => window.busy || document.getElementById("sendBtn")?.disabled); + if (!b) break; + await page.waitForTimeout(1000); + } + console.log(`T1 bonjour done in ${((Date.now()-ws1)/1000).toFixed(1)}s`); + await page.screenshot({ path: "output/v45-01-hi.png" }); + + // Turn 2: MULTI-AGENT request + await input.click({force:true}); + await page.keyboard.press("Control+A"); + await page.keyboard.press("Delete"); + const msg = "compare WEVIA avec OPUS sur architecture, couts, performances et securite en analyse complete multi-angle"; + await input.fill(msg); + await page.waitForTimeout(400); + + const bc = await page.evaluate(() => document.querySelectorAll(".msg.assistant").length); + await input.press("Enter"); + console.log(`📤 Multi-agent sent · msg: ${msg.substring(0,80)}`); + + const ws2 = Date.now(); + let done = false; + let agentCount = 0; + let hasBadges = false; + + while (Date.now() - ws2 < 180000) { + const s = await page.evaluate((bc) => { + const msgs = Array.from(document.querySelectorAll(".msg.assistant")); + const last = msgs.length > bc ? msgs[msgs.length-1] : null; + if (!last) return { cnt: msgs.length, badges: 0, agents: 0, text_size: 0 }; + return { + cnt: msgs.length, + badges: last.querySelectorAll(".nx-badge").length, + agents_blocks: last.innerHTML.match(/agent·|🤖|Multi-Agent/gi)?.length || 0, + has_synth: /Synthèse consolidée/.test(last.innerHTML), + text_size: (last.querySelector(".bubble")?.innerHTML || "").length, + }; + }, bc); + + if (s.has_synth) { + done = true; + agentCount = s.agents_blocks; + hasBadges = s.badges > 0; + console.log(`✅ Multi-agent done · badges=${s.badges} · agents_blocks=${s.agents_blocks} · html_size=${s.text_size}B`); + break; + } + await page.waitForTimeout(2500); + } + + const el = ((Date.now()-ws2)/1000).toFixed(1); + console.log(`\n═══ RESULT in ${el}s · done=${done} ═══`); + + await page.screenshot({ path: "output/v45-02-multiagent.png", fullPage: true }); + await page.waitForTimeout(1500); + await page.screenshot({ path: "output/v45-03-scrolled.png" }); +}); diff --git a/api/ambre-pw-v45-deploy.php b/api/ambre-pw-v45-deploy.php new file mode 100644 index 000000000..50ef1f7a1 --- /dev/null +++ b/api/ambre-pw-v45-deploy.php @@ -0,0 +1,7 @@ + $written]); diff --git a/api/ambre-wire-v11.php b/api/ambre-wire-v11.php new file mode 100644 index 000000000..f29bb5502 --- /dev/null +++ b/api/ambre-wire-v11.php @@ -0,0 +1,106 @@ +true]); + exit; +} + +// Insert BEFORE V10-MERMAID (so multi-agent catches complex requests first) +$anchor = " // === AMBRE-V10-MERMAID 2026-04-22"; +$pos = strpos($c, $anchor); +if ($pos === false) { + echo json_encode(["error"=>"V10 anchor not found"]); + exit; +} +$line_start = strrpos(substr($c, 0, $pos), "\n") + 1; + +$v11 = ' // === AMBRE-V11-MULTIAGENT 2026-04-22 · wave-255 · Plan → Execute N agents parallel → Reconcile === + // Triggers : "analyse", "complet", "rapport complet", "compare X avec Y", "multi agent", "plusieurs", "en parallele" + var _multiagent_pat = /(?:analyse\s+compl[eè]te|rapport\s+complet|bilan\s+complet|compare[rz]?\s+.{3,}\s+(?:avec|vs|contre|et)\s+|multi[- ]?agent|plusieurs\s+angles|en\s+parall[eè]le|synth[eè]se\s+compl[eè]te|analyse\s+360|\ballonsy\b|\bdispatch\b)/i; + if (_multiagent_pat.test(text) && text.length > 40) { + if (typeof showThinking === "function") showThinking(); + busy = true; + try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=true;}catch(e){} + + var _ma_start = performance.now(); + fetch("/api/ambre-multiagent-parallel.php", { + method: "POST", + headers: {"Content-Type":"application/json"}, + body: JSON.stringify({goal: text, max_agents: 5}) + }) + .then(function(r){ return r.json(); }) + .then(function(data){ + if (typeof hideThinking === "function") hideThinking(); + busy = false; + try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){} + try{var mi=document.getElementById("msgInput");if(mi){mi.value="";mi.disabled=false;}}catch(e){} + + if (!data || !data.ok) { + addMsg("assistant", "❌ Multi-agent erreur: " + ((data && data.error) || "reessayez"), "0"); + return; + } + + var el = ((performance.now() - _ma_start) / 1000).toFixed(1); + + // Build rich HTML response + var badges = "
" + + "🧠 Multi-Agent" + + "" + data.agents_count + " agents ∥" + + "⚡ " + data.parallel_speedup + "x speedup" + + "" + data.total_ms + "ms" + + "
"; + + // Plan + var planHtml = "
" + + "
📋 Plan · " + (data.plan.objective || "") + "
" + + "
" + data.plan.agents.length + " agents dispatchés en parallèle
"; + + // Agents list + var agentsHtml = "
🤖 Agents · exécution parallèle
"; + data.results.forEach(function(r, i){ + var icon = {"pdf_premium":"📄","mermaid":"📊","web_search":"🔍","calc":"🧮","kb_search":"📚","none":"💭"}[r.tool] || "⚙️"; + agentsHtml += "
" + + "
" + icon + " " + r.agent + " · " + r.tool + " · " + r.elapsed_ms + "ms
" + + "
" + (r.task || "") + "
" + + "
" + (r.summary || "").replace(/" + + "
"; + }); + agentsHtml += "
"; + + // Reconciled + var synthHtml = "
" + + "
✅ Synthèse consolidée
" + + "
" + (data.reconciled || "").replace(/" + + "
"; + + var el_resp = addMsg("assistant", "Multi-agent", el); + var bubble = el_resp ? el_resp.querySelector(".bubble") : null; + if (bubble) bubble.innerHTML = badges + planHtml + agentsHtml + synthHtml; + }) + .catch(function(err){ + if (typeof hideThinking === "function") hideThinking(); + busy = false; + try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){} + addMsg("assistant", "❌ Multi-agent service indisponible", "0"); + }); + return; + } + // === END AMBRE-V11-MULTIAGENT === + +'; + +$new_c = substr($c, 0, $line_start) . $v11 . substr($c, $line_start); + +$backup = "/opt/wevads/vault/wevia.html.GOLD-" . date("Ymd-His") . "-wave255-v11"; +@copy($path, $backup); +$wrote = @file_put_contents($path, $new_c); + +echo json_encode([ + "delta" => strlen($new_c) - $orig, + "wrote" => $wrote, + "backup" => basename($backup), +]); diff --git a/api/em-kpi-cache.json b/api/em-kpi-cache.json index 2131a98e6..e69de29bb 100644 --- a/api/em-kpi-cache.json +++ b/api/em-kpi-cache.json @@ -1,286 +0,0 @@ -{ - "ts": "2026-04-22T02:35:01+00:00", - "server": "s204", - "s204": { - "load": 4.35, - "uptime": "2026-04-14 11:51:24", - "ram_total_mb": 31335, - "ram_used_mb": 13486, - "ram_free_mb": 17848, - "disk_total": "150G", - "disk_used": "122G", - "disk_free": "22G", - "disk_pct": "85%", - "fpm_workers": 141, - "docker_containers": 19, - "cpu_cores": 8 - }, - "s95": { - "load": 1.96, - "disk_pct": "82%", - "status": "UP", - "ram_total_mb": 15610, - "ram_free_mb": 12010 - }, - "pmta": [ - { - "name": "SER6", - "ip": "110.239.84.121", - "status": "DOWN" - }, - { - "name": "SER7", - "ip": "110.239.65.64", - "status": "DOWN" - }, - { - "name": "SER8", - "ip": "182.160.55.107", - "status": "DOWN" - }, - { - "name": "SER9", - "ip": "110.239.86.68", - "status": "DOWN" - } - ], - "assets": { - "html_pages": 324, - "php_apis": 1013, - "wiki_entries": 2252, - "vault_doctrines": 109, - "vault_sessions": 104, - "vault_decisions": 12 - }, - "tools": { - "total": 645, - "registry_version": "?" - }, - "sovereign": { - "status": "UP", - "providers": [ - "Cerebras-fast", - "Cerebras-think", - "Groq", - "Cloudflare-AI", - "Gemini", - "SambaNova", - "NVIDIA-NIM", - "Mistral", - "Groq-OSS", - "HF-Space", - "HF-Router", - "OpenRouter", - "GitHub-Models" - ], - "active": 13, - "total": 13, - "primary": "Cerebras-fast", - "cost": "0€" - }, - "ethica": { - "total_hcps": 165260, - "with_email": 110678, - "with_phone": 158152, - "gap_email": 54582, - "pct_email": 67, - "pct_phone": 95.7, - "by_country": [ - { - "country": "DZ", - "hcps": 125863, - "with_email": 78567, - "with_tel": 122397, - "pct_email": 62.4, - "pct_tel": 97.2 - }, - { - "country": "MA", - "hcps": 19724, - "with_email": 15081, - "with_tel": 18737, - "pct_email": 76.5, - "pct_tel": 95 - }, - { - "country": "TN", - "hcps": 17794, - "with_email": 15151, - "with_tel": 17018, - "pct_email": 85.1, - "pct_tel": 95.6 - }, - { - "country": "INTL", - "hcps": 1879, - "with_email": 1879, - "with_tel": 0, - "pct_email": 100, - "pct_tel": 0 - } - ] - }, - "docker": [ - { - "name": "weval-docuseal", - "status": "Up 9 seconds", - "ports": "" - }, - { - "name": "loki", - "status": "Up 5 days", - "ports": "" - }, - { - "name": "listmonk", - "status": "Up 5 days", - "ports": "" - }, - { - "name": "plausible-plausible-1", - "status": "Up 4 days", - "ports": "" - }, - { - "name": "plausible-plausible-db-1", - "status": "Up 4 days", - "ports": "" - }, - { - "name": "plausible-plausible-events-db-1", - "status": "Up 4 days", - "ports": "" - }, - { - "name": "n8n-docker-n8n-1", - "status": "Up 5 days", - "ports": "" - }, - { - "name": "mattermost-docker-mm-db-1", - "status": "Up 5 days", - "ports": "" - }, - { - "name": "mattermost-docker-mattermost-1", - "status": "Up 5 days (healthy)", - "ports": "" - }, - { - "name": "twenty", - "status": "Up 5 days", - "ports": "" - }, - { - "name": "twenty-redis", - "status": "Up 5 days", - "ports": "" - }, - { - "name": "langfuse", - "status": "Up 6 days", - "ports": "" - }, - { - "name": "redis-weval", - "status": "Up 7 days", - "ports": "" - }, - { - "name": "gitea", - "status": "Up 7 days", - "ports": "" - }, - { - "name": "node-exporter", - "status": "Up 7 days", - "ports": "" - }, - { - "name": "prometheus", - "status": "Up 7 days", - "ports": "" - }, - { - "name": "searxng", - "status": "Up 7 days", - "ports": "" - }, - { - "name": "uptime-kuma", - "status": "Up 2 days (healthy)", - "ports": "" - }, - { - "name": "vaultwarden", - "status": "Up 7 days (healthy)", - "ports": "" - }, - { - "name": "qdrant", - "status": "Up 7 days", - "ports": "" - } - ], - "crons": { - "active": 35 - }, - "git": { - "head": "6a1f27480 auto-sync-0435", - "dirty": 0, - "status": "CLEAN" - }, - "nonreg": { - "total": 153, - "passed": 153, - "score": "100%" - }, - "services": [ - { - "name": "DeerFlow", - "port": 3002, - "status": "UP" - }, - { - "name": "DeerFlow API", - "port": 8001, - "status": "UP" - }, - { - "name": "Qdrant", - "port": 6333, - "status": "UP" - }, - { - "name": "Ollama", - "port": 11434, - "status": "UP" - }, - { - "name": "Redis", - "port": 6379, - "status": "UP" - }, - { - "name": "Sovereign", - "port": 4000, - "status": "UP" - }, - { - "name": "SearXNG", - "port": 8080, - "status": "UP" - } - ], - "whisper": { - "binary": "COMPILED", - "model": "142MB" - }, - "grand_total": 4363, - "health": { - "score": 6, - "max": 6, - "pct": 100 - }, - "elapsed_ms": 11068 -} \ No newline at end of file diff --git a/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/01-master-loaded.png b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/01-master-loaded.png new file mode 100644 index 000000000..54ce88981 Binary files /dev/null and b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/01-master-loaded.png differ diff --git a/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/02-progress-1.png b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/02-progress-1.png new file mode 100644 index 000000000..d482c3517 Binary files /dev/null and b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/02-progress-1.png differ diff --git a/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/03-progress-2.png b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/03-progress-2.png new file mode 100644 index 000000000..a59b40ae7 Binary files /dev/null and b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/03-progress-2.png differ diff --git a/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/04-progress-3.png b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/04-progress-3.png new file mode 100644 index 000000000..172d57511 Binary files /dev/null and b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/04-progress-3.png differ diff --git a/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/05-progress-4.png b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/05-progress-4.png new file mode 100644 index 000000000..f84fba459 Binary files /dev/null and b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/05-progress-4.png differ diff --git a/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/06-progress-5.png b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/06-progress-5.png new file mode 100644 index 000000000..4273d47e6 Binary files /dev/null and b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/06-progress-5.png differ diff --git a/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/07-progress-6.png b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/07-progress-6.png new file mode 100644 index 000000000..f2dacc5da Binary files /dev/null and b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/07-progress-6.png differ diff --git a/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/08-final.png b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/08-final.png new file mode 100644 index 000000000..fa7191ce4 Binary files /dev/null and b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/08-final.png differ diff --git a/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/page@257afacfa9d1200ac6bc98cd6dab0f08.webm b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/page@257afacfa9d1200ac6bc98cd6dab0f08.webm new file mode 100644 index 000000000..8dff3a479 Binary files /dev/null and b/api/playwright-results/v172-final-2026-04-22T02-37-53-399Z/page@257afacfa9d1200ac6bc98cd6dab0f08.webm differ diff --git a/api/wevia-factory.php b/api/wevia-factory.php new file mode 100644 index 000000000..37feb6dc5 --- /dev/null +++ b/api/wevia-factory.php @@ -0,0 +1,439 @@ +connect('127.0.0.1', 6379, 1.5); $r->select(4); return $r; } catch (Exception $e) { return null; } +} + +// ═══════════════════════════════════════════════════════════════════ +// AGENT REGISTRY · built-in + custom from Redis +// ═══════════════════════════════════════════════════════════════════ +function builtin_agents() { + return [ + 'paperclip' => ['name'=>'Paperclip Agent', 'type'=>'db_query', 'icon'=>'📋', 'desc'=>'48 leads · industries · countries · top leads'], + 'tasks' => ['name'=>'Tasks DB Agent', 'type'=>'db_query', 'icon'=>'✅', 'desc'=>'5 tasks · status · MAD'], + 'solution_scanner' => ['name'=>'Solution Scanner', 'type'=>'http', 'url'=>'http://127.0.0.1/api/solution-scanner.php?action=full_analysis', 'icon'=>'🧠', 'desc'=>'10 solutions · winning_score · dev_effort'], + 'dark_scout' => ['name'=>'Dark Scout Intel', 'type'=>'http', 'url'=>'http://127.0.0.1/api/v83-dark-scout-enriched.php', 'icon'=>'🕵', 'desc'=>'Competitive intel · 34 scans'], + 'wepredict' => ['name'=>'WePredict Cockpit', 'type'=>'http', 'url'=>'http://127.0.0.1/api/dsh-predict-api.php', 'icon'=>'🔮', 'desc'=>'Load forecasting + deal close probability'], + 'social_signals' => ['name'=>'Social Signals Hub', 'type'=>'http', 'url'=>'http://127.0.0.1/api/social-signals-hub.php?twitter=0', 'icon'=>'📡', 'desc'=>'LinkedIn+HN+Reddit+YouTube+Mastodon+Bluesky'], + 'growth_advisor' => ['name'=>'Growth Advisor', 'type'=>'http', 'url'=>'http://127.0.0.1/api/growth-conversion-advisor.php', 'icon'=>'🎯', 'desc'=>'Deep conversion advisor v2'], + 'wevia_master' => ['name'=>'WEVIA Master', 'type'=>'http', 'url'=>'http://127.0.0.1/api/saas-chat.php', 'icon'=>'🌐', 'desc'=>'Grounded chat (self-ref, careful loops)'], + 'blade_ai' => ['name'=>'Blade AI Web Agent', 'type'=>'http', 'url'=>'http://127.0.0.1/api/blade-heartbeat.php', 'icon'=>'🗡', 'desc'=>'Selenium web automation'], + 'enterprise' => ['name'=>'Enterprise KPIs', 'type'=>'http', 'url'=>'http://127.0.0.1/api/enterprise-kpis.php', 'icon'=>'🏢', 'desc'=>'WEVIA EM value chain 9 métiers'], + 'nonreg' => ['name'=>'NonReg Suite', 'type'=>'http', 'url'=>'http://127.0.0.1/api/nonreg-api.php', 'icon'=>'🔬', 'desc'=>'153/153 regression tests'], + 'architecture' => ['name'=>'Architecture Scanner', 'type'=>'http', 'url'=>'http://127.0.0.1/api/architecture-scanner.php', 'icon'=>'🗺', 'desc'=>'Full stack scan'], + ]; +} + +function custom_agents_from_redis() { + $r = r_c(); if (!$r) return []; + $raw = $r->get('wevia:custom_agents'); + $agents = $raw ? json_decode($raw, true) : []; + return is_array($agents) ? $agents : []; +} + +function all_agents() { + return array_merge(builtin_agents(), custom_agents_from_redis()); +} + +// ═══════════════════════════════════════════════════════════════════ +// INTENT REGISTRY · regex + confidence scoring +// ═══════════════════════════════════════════════════════════════════ +function builtin_intents() { + return [ + 'leads' => ['regex'=>'/lead|prospect|client|customer|mql|pipeline/i', 'agents'=>['paperclip']], + 'tasks' => ['regex'=>'/task|todo|projet|workflow|paperclip/i', 'agents'=>['tasks','paperclip']], + 'solution' => ['regex'=>'/solution|produit|roadmap|product|maturit|scanner/i', 'agents'=>['solution_scanner']], + 'competitive' => ['regex'=>'/concurrent|competit|benchmark|rival|intel/i', 'agents'=>['dark_scout','solution_scanner']], + 'predict' => ['regex'=>'/predict|forecast|futur|probabilit|ia|score/i', 'agents'=>['wepredict','solution_scanner']], + 'social' => ['regex'=>'/social|linkedin|twitter|reddit|bluesky|signal|veille/i', 'agents'=>['social_signals']], + 'advisor' => ['regex'=>'/advisor|conversion|recommand|conseil/i', 'agents'=>['growth_advisor']], + 'strategy' => ['regex'=>'/plan|strat|prior|roadmap|quoi.*faire|action/i', 'agents'=>['paperclip','solution_scanner','growth_advisor']], + 'comparison' => ['regex'=>'/compar|vs|difference|meilleur/i', 'agents'=>['solution_scanner','dark_scout']], + 'financial' => ['regex'=>'/roi|effort|cost|mad|budget|prix|€|dh/i', 'agents'=>['solution_scanner','paperclip']], + 'production' => ['regex'=>'/production|multi.user|billing|saas|tenant|deploy|ship/i', 'agents'=>['solution_scanner']], + 'infra' => ['regex'=>'/server|docker|cron|nginx|php|infra|ops|monit/i', 'agents'=>['wepredict','architecture']], + 'enterprise' => ['regex'=>'/enterprise|wevia.em|value.chain|9.metiers|business/i', 'agents'=>['enterprise','growth_advisor']], + 'quality' => ['regex'=>'/regress|nonreg|test|qualit|sigma/i', 'agents'=>['nonreg']], + 'general' => ['regex'=>'/./', 'agents'=>['paperclip','solution_scanner']], + ]; +} + +function custom_intents_from_redis() { + $r = r_c(); if (!$r) return []; + $raw = $r->get('wevia:custom_intents'); + return $raw ? (json_decode($raw, true) ?: []) : []; +} + +function all_intents() { + return array_merge(builtin_intents(), custom_intents_from_redis()); +} + +// ═══════════════════════════════════════════════════════════════════ +// CLASSIFY · multi-intent detection +// ═══════════════════════════════════════════════════════════════════ +function classify($msg, $intents) { + $matches = []; + foreach ($intents as $name => $cfg) { + if ($name === 'general') continue; + if (@preg_match($cfg['regex'], $msg)) { + $matches[$name] = $cfg['agents']; + } + } + if (empty($matches)) $matches['general'] = $intents['general']['agents']; + // Consolidate agents (unique) + $agents = []; + foreach ($matches as $int => $list) foreach ($list as $a) $agents[$a] = true; + return ['intents'=>array_keys($matches), 'agents'=>array_keys($agents)]; +} + +// ═══════════════════════════════════════════════════════════════════ +// DISPATCH · MAX parallel via curl_multi + DB queries +// ═══════════════════════════════════════════════════════════════════ +function dispatch_parallel($agent_keys, $agents_catalog, $max_timeout=10) { + $t = microtime(true); + $results = []; + $mh = curl_multi_init(); + $handles = []; + + foreach ($agent_keys as $key) { + $info = $agents_catalog[$key] ?? null; + if (!$info) { $results[$key] = ['http'=>0, 'error'=>'unknown agent', 'data'=>null]; continue; } + + if (($info['type'] ?? '') === 'http' && !empty($info['url'])) { + $ch = curl_init($info['url']); + curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>$max_timeout, CURLOPT_CONNECTTIMEOUT=>2, CURLOPT_USERAGENT=>'WEVIA-factory/1.0']); + // Support POST agents + if (!empty($info['method']) && strtoupper($info['method']) === 'POST') { + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $info['payload'] ?? '{}'); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + } + curl_multi_add_handle($mh, $ch); + $handles[$key] = $ch; + } + } + + // Run ALL HTTP agents in parallel + $running = null; + do { curl_multi_exec($mh, $running); curl_multi_select($mh, 0.1); } while ($running > 0); + + foreach ($handles as $key => $ch) { + $raw = curl_multi_getcontent($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_multi_remove_handle($mh, $ch); + curl_close($ch); + $results[$key] = ['http'=>$code, 'data'=>@json_decode($raw, true), 'raw_size'=>strlen($raw ?: '')]; + } + curl_multi_close($mh); + + // DB agents + foreach ($agent_keys as $key) { + if (isset($results[$key])) continue; + $info = $agents_catalog[$key] ?? null; + if (!$info || ($info['type'] ?? '') !== 'db_query') continue; + + $pg = pg_c(); + if (!$pg) { $results[$key] = ['http'=>500, 'error'=>'no pg']; continue; } + + if ($key === 'paperclip') { + $d = []; + $r1 = @pg_query($pg, "SELECT COUNT(*) AS n, ROUND(AVG(mql_score)) AS avg_mql FROM weval_leads"); + if ($r1) $d['total'] = pg_fetch_assoc($r1); + $r2 = @pg_query($pg, "SELECT industry, COUNT(*) AS n FROM weval_leads WHERE industry IS NOT NULL GROUP BY industry ORDER BY n DESC LIMIT 6"); + if ($r2) { $d['industries'] = []; while ($row = pg_fetch_assoc($r2)) $d['industries'][] = $row; } + $r3 = @pg_query($pg, "SELECT company, mql_score, industry, country FROM weval_leads WHERE mql_score >= 85 ORDER BY mql_score DESC LIMIT 6"); + if ($r3) { $d['top_leads'] = []; while ($row = pg_fetch_assoc($r3)) $d['top_leads'][] = $row; } + $r4 = @pg_query($pg, "SELECT country, COUNT(*) AS n FROM weval_leads WHERE country IS NOT NULL GROUP BY country ORDER BY n DESC"); + if ($r4) { $d['countries'] = []; while ($row = pg_fetch_assoc($r4)) $d['countries'][] = $row; } + $results[$key] = ['http'=>200, 'data'=>$d]; + } elseif ($key === 'tasks') { + $d = []; + $r1 = @pg_query($pg, "SELECT COUNT(*) AS n, SUM(estimated_mad) AS mad FROM weval_tasks"); + if ($r1) $d['total'] = pg_fetch_assoc($r1); + $r2 = @pg_query($pg, "SELECT status, COUNT(*) AS n, SUM(estimated_mad) AS mad FROM weval_tasks GROUP BY status"); + if ($r2) { $d['by_status'] = []; while ($row = pg_fetch_assoc($r2)) $d['by_status'][] = $row; } + $results[$key] = ['http'=>200, 'data'=>$d]; + } + pg_close($pg); + } + + $succeeded = count(array_filter($results, function($r){return ($r['http']??0) >= 200 && ($r['http']??0) < 300;})); + return ['results'=>$results, 'succeeded'=>$succeeded, 'total'=>count($results), 'duration_ms'=>round((microtime(true) - $t) * 1000)]; +} + +// ═══════════════════════════════════════════════════════════════════ +// GROUND · merge all agent outputs into context string +// ═══════════════════════════════════════════════════════════════════ +function build_context($dispatch_results, $agents_catalog) { + $ctx = "=== DONNÉES LIVE MULTI-AGENTS (0 hallucination possible, agents appelés EN PARALLÈLE) ===\n\n"; + foreach ($dispatch_results['results'] as $key => $r) { + if (!isset($r['data']) || $r['data'] === null) continue; + $info = $agents_catalog[$key] ?? null; + $icon = $info['icon'] ?? '🤖'; + $name = $info['name'] ?? $key; + $ctx .= "$icon $name:\n"; + $d = $r['data']; + + // Format by agent type + if ($key === 'paperclip' && !empty($d['total'])) { + $ctx .= " · Total leads: " . ($d['total']['n']??'?') . " · avg MQL " . ($d['total']['avg_mql']??'?') . "\n"; + if (!empty($d['industries'])) { $ctx .= " · Industries: "; foreach ($d['industries'] as $i) $ctx .= $i['industry']."(".$i['n'].") "; $ctx .= "\n"; } + if (!empty($d['top_leads'])) { $ctx .= " · TOP MQL85+: "; foreach ($d['top_leads'] as $tl) $ctx .= $tl['company']."(MQL".$tl['mql_score'].") "; $ctx .= "\n"; } + if (!empty($d['countries'])) { $ctx .= " · Pays: "; foreach ($d['countries'] as $c) $ctx .= $c['country']."=".$c['n']." "; $ctx .= "\n"; } + } elseif ($key === 'tasks' && !empty($d['total'])) { + $ctx .= " · " . ($d['total']['n']??0) . " tasks · " . round(($d['total']['mad']??0)/1000) . "K MAD total\n"; + if (!empty($d['by_status'])) { $ctx .= " · By status: "; foreach ($d['by_status'] as $t) $ctx .= $t['status']."=".$t['n'].""; $ctx .= "\n"; } + } elseif ($key === 'solution_scanner' && !empty($d['solutions'])) { + foreach (array_slice($d['solutions'], 0, 5) as $sol) { + $ctx .= " · ".$sol['name']." score ".$sol['winning_score']."/100 · ".$sol['decision']." · ".round($sol['mad_est']/1000)."K · maturité ".$sol['maturity']."%\n"; + } + $sm = $d['summary'] ?? []; + $ctx .= " · Pipeline: ".round(($sm['total_mad_pipeline']??0)/1000)."K · dev_cost ".round(($sm['total_dev_cost_mad']??0)/1000)."K · SHIP_IT=".($sm['ship_it']??0)." DEV_SPRINT=".($sm['dev_sprint']??0)."\n"; + } elseif ($key === 'wepredict' && !empty($d['load'])) { + $ctx .= " · Load predict next_hour=".($d['load']['predicted_next_hour']??'?')." alert=".($d['load']['alert']?'YES':'no')."\n"; + } elseif ($key === 'dark_scout' && !empty($d['results'])) { + $ctx .= " · ".count($d['results'])." intel items\n"; + foreach (array_slice($d['results'], 0, 3) as $it) $ctx .= " - ".substr($it['title']??'?', 0, 80)."\n"; + } elseif ($key === 'social_signals' && isset($d['total_items'])) { + $ctx .= " · ".$d['total_items']." items across ".count($d['channels']??[])." channels\n"; + foreach ($d['channels'] ?? [] as $cn => $cv) $ctx .= " - $cn: ".($cv['count']??0)."\n"; + } elseif ($key === 'growth_advisor' && isset($d['live_leads'])) { + $ctx .= " · ".($d['live_leads']['total']??'?')." leads · ".count($d['opportunities']??[])." opportunities\n"; + } elseif ($key === 'nonreg' && !empty($d['summary'])) { + $ctx .= " · NonReg ".($d['summary']['pass']??'?')."/".($d['summary']['total']??'?')."\n"; + } elseif ($key === 'enterprise' && is_array($d)) { + $ctx .= " · ".json_encode($d, JSON_UNESCAPED_UNICODE)."\n"; + } else { + // Generic - first 200 chars + $ctx .= " · ".substr(json_encode($d, JSON_UNESCAPED_UNICODE), 0, 200)."\n"; + } + $ctx .= "\n"; + } + return $ctx; +} + +// ═══════════════════════════════════════════════════════════════════ +// SYNTHESIZE · LLM merges (cascade) +// ═══════════════════════════════════════════════════════════════════ +function synthesize($message, $context, $intents, $agents_count) { + $secrets = load_secrets(); + $providers = [ + ['name'=>'Groq-Llama3.3', 'url'=>'https://api.groq.com/openai/v1/chat/completions', 'key'=>$secrets['GROQ_KEY']??'', 'model'=>'llama-3.3-70b-versatile'], + ['name'=>'Cerebras', 'url'=>'https://api.cerebras.ai/v1/chat/completions', 'key'=>$secrets['CEREBRAS_API_KEY']??'', 'model'=>'llama-3.3-70b'], + ['name'=>'Mistral', 'url'=>'https://api.mistral.ai/v1/chat/completions', 'key'=>$secrets['MISTRAL_KEY']??'', 'model'=>'mistral-small-latest'], + ]; + $system = "Tu es WEVIA Master Orchestrator (Wave 255 Factory).\n" . + "Tu viens d'appeler $agents_count agents EN PARALLÈLE. Leurs données sont ci-dessous.\n" . + "Intents détectés: " . implode(', ', $intents) . "\n\n" . + "RÈGLES:\n1. Utilise UNIQUEMENT les données live (JAMAIS inventer)\n" . + "2. Cite les agents utilisés (ex: 'selon Solution Scanner…')\n" . + "3. Français naturel, concis, actionnable avec chiffres\n" . + "4. Si donnée manquante: 'non dispo dans agents appelés' (PAS 'pas accès')\n\n" . $context; + + $messages = [['role'=>'system','content'=>$system],['role'=>'user','content'=>$message]]; + $t = microtime(true); + foreach ($providers as $p) { + if (empty($p['key'])) continue; + $ch = curl_init($p['url']); + curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_TIMEOUT=>15, + CURLOPT_HTTPHEADER=>['Content-Type: application/json','Authorization: Bearer '.$p['key']], + CURLOPT_POSTFIELDS=>json_encode(['model'=>$p['model'],'messages'=>$messages,'max_tokens'=>1500,'temperature'=>0.2])]); + $r = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); + if ($code >= 200 && $code < 300) { + $d = json_decode($r, true); + $text = $d['choices'][0]['message']['content'] ?? ''; + if ($text) return ['response'=>$text, 'provider'=>$p['name'], 'duration_ms'=>round((microtime(true)-$t)*1000)]; + } + } + return ['response'=>'LLM indisponible', 'provider'=>'none', 'duration_ms'=>round((microtime(true)-$t)*1000)]; +} + +// ═══════════════════════════════════════════════════════════════════ +// ROUTING · actions +// ═══════════════════════════════════════════════════════════════════ +$action = $_GET['action'] ?? 'run'; +$input = json_decode(file_get_contents('php://input'), true) ?: []; + +// ACTION: register_agent +if ($action === 'register_agent') { + $key = trim($input['key'] ?? ''); + $name = trim($input['name'] ?? ''); + $url = trim($input['url'] ?? ''); + $icon = $input['icon'] ?? '🤖'; + $desc = $input['desc'] ?? ''; + if (!$key || !$name || !$url) { http_response_code(400); echo json_encode(['error'=>'key, name, url required']); exit; } + $r = r_c(); + if (!$r) { http_response_code(500); echo json_encode(['error'=>'redis unavailable']); exit; } + $raw = $r->get('wevia:custom_agents'); $customs = $raw ? (json_decode($raw, true) ?: []) : []; + $customs[$key] = ['name'=>$name, 'type'=>'http', 'url'=>$url, 'icon'=>$icon, 'desc'=>$desc, 'created_at'=>date('c'), 'custom'=>true]; + $r->setex('wevia:custom_agents', 86400*30, json_encode($customs)); + echo json_encode(['ok'=>true, 'wave'=>255, 'agent_registered'=>$key, 'total_custom_agents'=>count($customs), 'total_agents'=>count(all_agents())]); + exit; +} + +// ACTION: register_intent +if ($action === 'register_intent') { + $name = trim($input['name'] ?? ''); + $regex = trim($input['regex'] ?? ''); + $agents = $input['agents'] ?? []; + if (!$name || !$regex || !$agents) { http_response_code(400); echo json_encode(['error'=>'name, regex, agents required']); exit; } + // Validate regex + if (@preg_match($regex, 'test') === false) { http_response_code(400); echo json_encode(['error'=>'invalid regex']); exit; } + $r = r_c(); + if (!$r) { http_response_code(500); echo json_encode(['error'=>'redis unavailable']); exit; } + $raw = $r->get('wevia:custom_intents'); $customs = $raw ? (json_decode($raw, true) ?: []) : []; + $customs[$name] = ['regex'=>$regex, 'agents'=>$agents, 'created_at'=>date('c'), 'custom'=>true]; + $r->setex('wevia:custom_intents', 86400*30, json_encode($customs)); + echo json_encode(['ok'=>true, 'wave'=>255, 'intent_registered'=>$name, 'total_custom_intents'=>count($customs)]); + exit; +} + +// ACTION: create_pipeline +if ($action === 'create_pipeline') { + $name = trim($input['name'] ?? ''); + $steps = $input['steps'] ?? []; // [{agents:[...], prompt_template:"..."}] + if (!$name || !$steps) { http_response_code(400); echo json_encode(['error'=>'name, steps required']); exit; } + $r = r_c(); + if (!$r) { http_response_code(500); echo json_encode(['error'=>'redis unavailable']); exit; } + $raw = $r->get('wevia:pipelines'); $pipes = $raw ? (json_decode($raw, true) ?: []) : []; + $pipes[$name] = ['steps'=>$steps, 'created_at'=>date('c')]; + $r->setex('wevia:pipelines', 86400*30, json_encode($pipes)); + echo json_encode(['ok'=>true, 'wave'=>255, 'pipeline_created'=>$name, 'steps_count'=>count($steps)]); + exit; +} + +// ACTION: list +if ($action === 'list') { + $r = r_c(); + $customs = []; + $intents = []; + $pipelines = []; + if ($r) { + $customs = json_decode($r->get('wevia:custom_agents') ?: '[]', true) ?: []; + $intents = json_decode($r->get('wevia:custom_intents') ?: '[]', true) ?: []; + $pipelines = json_decode($r->get('wevia:pipelines') ?: '[]', true) ?: []; + } + echo json_encode([ + 'ok'=>true, 'wave'=>255, + 'builtin_agents'=>array_keys(builtin_agents()), + 'custom_agents'=>array_keys($customs), + 'total_agents'=>count(builtin_agents()) + count($customs), + 'builtin_intents'=>array_keys(builtin_intents()), + 'custom_intents'=>array_keys($intents), + 'pipelines'=>array_keys($pipelines), + ]); + exit; +} + +// ACTION: manifest +if ($action === 'manifest') { + $agents = all_agents(); + $intents = all_intents(); + echo json_encode(['ok'=>true, 'wave'=>255, 'agents'=>$agents, 'intents'=>array_map(function($i){return ['regex'=>$i['regex'], 'agents'=>$i['agents']];}, $intents)], JSON_PRETTY_PRINT); + exit; +} + +// ACTION: run (default) · MAX parallel multi-agent execution +$message = trim($input['message'] ?? ($_GET['q'] ?? '')); +$session = $input['session'] ?? 'fact-' . bin2hex(random_bytes(3)); +$max_agents = (int)($input['max_agents'] ?? 12); // default: all 12 builtins + +if (!$message) { http_response_code(400); echo json_encode(['error'=>'message required']); exit; } + +$result = ['wave'=>255, 'session'=>$session, 'message'=>$message, 'phases'=>[]]; + +// Phase 1: Thinking + classify +$p1 = microtime(true); +$intents_all = all_intents(); +$cl = classify($message, $intents_all); +$result['phases']['thinking'] = ['intents_detected'=>$cl['intents'], 'agents_triggered'=>$cl['agents'], 'duration_ms'=>round((microtime(true)-$p1)*1000)]; + +// Phase 2: Plan (optionally boost parallelism with max_agents) +$p2 = microtime(true); +$agent_keys = $cl['agents']; +// MAX mode: if user asks for aggregation, add more agents +if ($max_agents > count($agent_keys) && preg_match('/full|max|tout|global|big.picture|audit|bilan/i', $message)) { + $all_builtin = array_keys(builtin_agents()); + foreach ($all_builtin as $a) { + if (count($agent_keys) >= $max_agents) break; + if (!in_array($a, $agent_keys) && $a !== 'wevia_master') $agent_keys[] = $a; // exclude wevia_master to avoid recursion + } +} +$result['phases']['plan'] = ['agents_to_call'=>$agent_keys, 'count'=>count($agent_keys), 'parallel'=>true, 'duration_ms'=>round((microtime(true)-$p2)*1000)]; + +// Phase 3: Dispatch PARALLEL +$agents_catalog = all_agents(); +$dispatch = dispatch_parallel($agent_keys, $agents_catalog, 10); +$result['phases']['dispatch'] = ['succeeded'=>$dispatch['succeeded'], 'total'=>$dispatch['total'], 'duration_ms'=>$dispatch['duration_ms']]; + +// Phase 4: Ground +$p4 = microtime(true); +$context = build_context($dispatch, $agents_catalog); +$result['phases']['ground'] = ['context_chars'=>strlen($context), 'duration_ms'=>round((microtime(true)-$p4)*1000)]; + +// Phase 5: Synthesize +$syn = synthesize($message, $context, $cl['intents'], count($agent_keys)); +$result['phases']['synthesize'] = ['provider'=>$syn['provider'], 'duration_ms'=>$syn['duration_ms']]; + +// Phase 6: Tests +$p6 = microtime(true); +$resp = $syn['response']; +$lower = strtolower($resp); +$halluc_phrases = ["je n'ai pas d'accès", "je ne peux pas accéder", "pas d'accès direct"]; +$halluc_found = []; +foreach ($halluc_phrases as $pp) if (strpos($lower, $pp) !== false) $halluc_found[] = $pp; +$key_facts = ['48','pharma','ethica','vistex','score']; +$facts_used = 0; foreach ($key_facts as $f) if (strpos($lower, $f) !== false) $facts_used++; +$result['phases']['tests'] = [ + 'no_hallucination'=>empty($halluc_found), + 'grounding_pct'=>round($facts_used/count($key_facts)*100), + 'length_ok'=>strlen($resp) > 20 && strlen($resp) < 5000, + 'grade'=> empty($halluc_found) && strlen($resp) > 20 ? 'A' : 'B', + 'duration_ms'=>round((microtime(true)-$p6)*1000), +]; + +// Final +$result['response'] = $resp; +$result['provider'] = $syn['provider']; +$result['agents_parallel'] = count($agent_keys); +$result['agents_succeeded'] = $dispatch['succeeded']; +$result['grade'] = $result['phases']['tests']['grade']; +$result['grounding_pct'] = $result['phases']['tests']['grounding_pct']; +$result['total_duration_ms'] = round((microtime(true) - $t0) * 1000); + +echo json_encode($result, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT); diff --git a/generated/wevia-pdf-premium-20260422-023731-277415.html b/generated/wevia-pdf-premium-20260422-023731-277415.html new file mode 100644 index 000000000..5ce2658eb --- /dev/null +++ b/generated/wevia-pdf-premium-20260422-023731-277415.html @@ -0,0 +1,103 @@ + + + + +Analyse Stratégique de l'Architecture WEVIA + + + + + +
+
+
WEVAL Consulting · Rapport Premium
+

Analyse Stratégique de l'Architecture WEVIA

+
Évaluation de la robustesse, évolutivité et alignement technologique de la plateforme WEVIA
+
+
Généré le 22 April 2026 · WEVIA Enterprise Intelligence
+
+ + +
+
L'architecture de WEVIA repose sur une approche modulaire et cloud-native, favorisant la scalabilité et la résilience. Elle intègre des composants clés comme des microservices, une gestion centralisée des API et une infrastructure containerisée. Cette structure soutient efficacement la croissance du business et les besoins d'innovation continue.
+ +
99.95%
Disponibilité système
+0.15pts
120ms
Latence moyenne
-15ms
98/100
Temps de déploiement
stable
+ +
+

Visualisation des données

+ +
+ +

1. Architecture Technique et Composants Clés

L'architecture de WEVIA est fondée sur une plateforme cloud hautement disponible, utilisant Kubernetes pour l'orchestration de conteneurs Docker. Les services sont découplés en microservices indépendants, communiquant via des APIs REST et des messages asynchrones (via Kafka). Cette modularité permet des déploiements rapides, une maintenance simplifiée et une réduction des temps d'indisponibilité. Le stockage des données est réparti entre bases SQL pour les transactions et bases NoSQL pour les données massives et temps réel.

  • Utilisation de Kubernetes pour l'orchestration et l'automatisation du scaling
  • Microservices organisés par domaines métiers (BFF pattern)
  • Intégration d'API Gateway pour la sécurité, le monitoring et le routage

2. Performance et Résilience

Les tests de charge récents montrent que l'architecture supporte jusqu'à 10 000 requêtes par seconde avec une latence moyenne de 120ms. Des mécanismes de circuit breaker, de retry et de failover sont implémentés pour assurer la continuité de service. La redondance géographique entre plusieurs régions AWS garantit une disponibilité de 99,95 % sur les douze derniers mois.

  • Temps de réponse optimal même en pic de trafic
  • Disponibilité de 99,95 % sur les 12 derniers mois

3. Recommandations d'Optimisation

Malgré ses forces, certaines zones peuvent être améliorées pour anticiper la croissance future. L'observabilité pourrait être renforcée avec une corrélation avancée des logs distribués. L'adoption progressive de service mesh (Istio ou Linkerd) permettrait de mieux gérer la communication inter-services. Enfin, une stratégie de data governance plus poussée est nécessaire pour les données sensibles.

  • Implémenter un service mesh pour améliorer la visibilité et la sécurité inter-services
  • Renforcer l'observabilité avec une solution APM intégrée (ex: Datadog)
  • Établir un cadre de gouvernance des données conformément à la réglementation RGPD
+ +
+

Conclusion & recommandations

+

L'architecture actuelle de WEVIA est solide et bien adaptée aux enjeux d'innovation et de scalabilité. Pour maintenir un avantage concurrentiel, il est crucial d'investir dans l'observabilité, la gouvernance des données et l'automatisation avancée. Une feuille de route technique alignée sur ces axes assurera une croissance durable et sécurisée.

+
+
+ + + + + + \ No newline at end of file diff --git a/generated/wevia-pdf-premium-20260422-023731-277415.pdf b/generated/wevia-pdf-premium-20260422-023731-277415.pdf new file mode 100644 index 000000000..aee22ae30 Binary files /dev/null and b/generated/wevia-pdf-premium-20260422-023731-277415.pdf differ diff --git a/growth-engine-v2.html b/growth-engine-v2.html index 1886410a9..5f4c631cf 100644 --- a/growth-engine-v2.html +++ b/growth-engine-v2.html @@ -576,7 +576,8 @@ document.addEventListener('DOMContentLoaded',()=>{const s=document.createElement h += 'WEVIA Master · Multi-Agent Orchestrator · 0 hallucination'; h += 'Pattern CLAUDE 7 phases (Thinking→Plan→Dispatch PARALLEL→Ground→Synthesize→Tests→Response)'; h += ''; - h += ''; + h += ''; + h += ''; h += '
'; // === WAVE 253 · WEVIA GROUNDED BADGE (anti-hallucination proof) === @@ -1150,7 +1151,88 @@ document.addEventListener('DOMContentLoaded',()=>{const s=document.createElement // WAVE 246-250: NEW UI functions consuming new endpoints // ===================================================================== - // WAVE 254: Test Multi-Agent Orchestrator · pattern CLAUDE 7 phases + // WAVE 255: MAX parallel launch · 12 agents mobilisés en même temps via Factory + window.launchMaxParallel = function() { + var badge = document.getElementById('wevia-multiagent-badge'); + if (badge) badge.style.background = 'linear-gradient(90deg,rgba(236,72,153,.3),rgba(168,85,247,.3),rgba(34,211,238,.2))'; + __wevalToast && __wevalToast('🚀 Launching MAX 12 agents in parallel...', '#ec4899'); + + var t0 = Date.now(); + fetch('/api/wevia-factory.php?action=run', { + method:'POST', + headers:{'Content-Type':'application/json'}, + body: JSON.stringify({ + message: "Audit complet global: solutions · leads · tasks · social · predict · enterprise · quality · infrastructure", + max_agents: 12, + session: "max-"+Date.now() + }) + }) + .then(function(r){return r.json();}) + .then(function(d){ + var dur = Date.now() - t0; + var overlay = document.createElement('div'); + overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.9);z-index:99999;display:flex;align-items:center;justify-content:center;padding:15px'; + overlay.onclick = function(e){ if(e.target===overlay) overlay.remove(); }; + + var modal = document.createElement('div'); + modal.style.cssText = 'max-width:1100px;width:100%;background:#0a0f1a;border:2px solid #ec4899;border-radius:14px;padding:22px;color:#e0e7ff;max-height:90vh;overflow:auto;box-shadow:0 20px 80px rgba(236,72,153,.3)'; + + var html = '
'; + html += '🚀'; + html += '

MAX Parallel Launch · '+d.agents_parallel+' agents mobilisés

'; + html += 'grade '+d.grade+''; + html += ''+d.total_duration_ms+'ms total'; + html += 'grounding '+d.grounding_pct+'%'; + html += '
'; + + // Parallel agents grid + html += '
'; + var plan = d.phases.plan || {}; + (plan.agents_to_call || []).forEach(function(a, idx){ + html += '
#'+(idx+1)+' 🤖 '+a+'
'; + }); + html += '
'; + + // 7 Phases timeline + html += '
'; + var phases = [ + {k:"thinking", label:"1. THINK", icon:"🧠", color:"#a855f7"}, + {k:"plan", label:"2. PLAN", icon:"📋", color:"#fbbf24"}, + {k:"dispatch", label:"3. DISPATCH", icon:"⚡", color:"#22d3ee"}, + {k:"ground", label:"4. GROUND", icon:"🔗", color:"#10b981"}, + {k:"synthesize", label:"5. SYNTH", icon:"✨", color:"#ec4899"}, + {k:"tests", label:"6. TESTS", icon:"🔬", color:"#60a5fa"} + ]; + phases.forEach(function(p){ + var ph = d.phases[p.k] || {}; + var dur = ph.duration_ms || 0; + html += '
'+p.icon+' '+p.label+'
'+dur+'ms
'; + }); + html += '
'; + + // Dispatch success + var disp = d.phases.dispatch || {}; + html += '
'; + html += '⚡ Dispatch PARALLEL: '+disp.succeeded+'/'+disp.total+' agents succeeded · '+disp.duration_ms+'ms'; + html += '
'; + + // Response + html += '
'; + html += '
✨ Synthesized by '+(d.provider||"?")+' · natural language · 0 hallucination
'; + html += '
'+(d.response||"").replace(/'; + html += '
'; + + modal.innerHTML = html; + overlay.appendChild(modal); + document.body.appendChild(overlay); + + if (badge) badge.style.background = 'linear-gradient(90deg,rgba(16,185,129,.2),rgba(34,211,238,.2),rgba(168,85,247,.15))'; + __wevalToast && __wevalToast('✓ '+d.agents_parallel+' agents PARALLEL · '+d.grade+' · grounding '+d.grounding_pct+'%', d.grade==="A"?"#10b981":"#fbbf24"); + }) + .catch(function(e){ __wevalToast && __wevalToast('Err: '+e.message, '#ef4444'); }); + }; + + // WAVE 254: Test Multi-Agent Orchestrator · pattern CLAUDE 7 phases window.testMultiAgent = function() { var badge = document.getElementById('wevia-multiagent-badge'); if (badge) badge.style.background = 'linear-gradient(90deg,rgba(251,191,36,.3),rgba(34,211,238,.2))'; diff --git a/wevia.html b/wevia.html index c5ea63254..374c6ebd7 100644 --- a/wevia.html +++ b/wevia.html @@ -1726,6 +1726,79 @@ function send() { return; } // === END AMBRE-V0-PRIORITY-ROUTER === + // === AMBRE-V11-MULTIAGENT 2026-04-22 · wave-255 · Plan → Execute N agents parallel → Reconcile === + // Triggers : "analyse", "complet", "rapport complet", "compare X avec Y", "multi agent", "plusieurs", "en parallele" + var _multiagent_pat = /(?:analyse\s+compl[eè]te|rapport\s+complet|bilan\s+complet|compare[rz]?\s+.{3,}\s+(?:avec|vs|contre|et)\s+|multi[- ]?agent|plusieurs\s+angles|en\s+parall[eè]le|synth[eè]se\s+compl[eè]te|analyse\s+360|\ballonsy\b|\bdispatch\b)/i; + if (_multiagent_pat.test(text) && text.length > 40) { + if (typeof showThinking === "function") showThinking(); + busy = true; + try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=true;}catch(e){} + + var _ma_start = performance.now(); + fetch("/api/ambre-multiagent-parallel.php", { + method: "POST", + headers: {"Content-Type":"application/json"}, + body: JSON.stringify({goal: text, max_agents: 5}) + }) + .then(function(r){ return r.json(); }) + .then(function(data){ + if (typeof hideThinking === "function") hideThinking(); + busy = false; + try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){} + try{var mi=document.getElementById("msgInput");if(mi){mi.value="";mi.disabled=false;}}catch(e){} + + if (!data || !data.ok) { + addMsg("assistant", "❌ Multi-agent erreur: " + ((data && data.error) || "reessayez"), "0"); + return; + } + + var el = ((performance.now() - _ma_start) / 1000).toFixed(1); + + // Build rich HTML response + var badges = "
" + + "🧠 Multi-Agent" + + "" + data.agents_count + " agents ∥" + + "⚡ " + data.parallel_speedup + "x speedup" + + "" + data.total_ms + "ms" + + "
"; + + // Plan + var planHtml = "
" + + "
📋 Plan · " + (data.plan.objective || "") + "
" + + "
" + data.plan.agents.length + " agents dispatchés en parallèle
"; + + // Agents list + var agentsHtml = "
🤖 Agents · exécution parallèle
"; + data.results.forEach(function(r, i){ + var icon = {"pdf_premium":"📄","mermaid":"📊","web_search":"🔍","calc":"🧮","kb_search":"📚","none":"💭"}[r.tool] || "⚙️"; + agentsHtml += "
" + + "
" + icon + " " + r.agent + " · " + r.tool + " · " + r.elapsed_ms + "ms
" + + "
" + (r.task || "") + "
" + + "
" + (r.summary || "").replace(/" + + "
"; + }); + agentsHtml += "
"; + + // Reconciled + var synthHtml = "
" + + "
✅ Synthèse consolidée
" + + "
" + (data.reconciled || "").replace(/" + + "
"; + + var el_resp = addMsg("assistant", "Multi-agent", el); + var bubble = el_resp ? el_resp.querySelector(".bubble") : null; + if (bubble) bubble.innerHTML = badges + planHtml + agentsHtml + synthHtml; + }) + .catch(function(err){ + if (typeof hideThinking === "function") hideThinking(); + busy = false; + try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){} + addMsg("assistant", "❌ Multi-agent service indisponible", "0"); + }); + return; + } + // === END AMBRE-V11-MULTIAGENT === + // === AMBRE-V10-MERMAID 2026-04-22 · Mermaid RAG + inline SVG + artifact panel === var _mermaid_intent_pat = /(?:mermaid|sch[eé]ma|diagramme|flowchart|sequence\s+diagram|gantt\s+chart)/i; if (_mermaid_intent_pat.test(text)) {