auto-sync-2325
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"agent": "V45_Leads_Sync",
|
||||
"ts": "2026-04-21T23:10:02+02:00",
|
||||
"ts": "2026-04-21T23:20:02+02:00",
|
||||
"paperclip_total": 48,
|
||||
"active_customer": 4,
|
||||
"warm_prospect": 5,
|
||||
|
||||
174
api/ambre-claude-pattern-sse.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
/**
|
||||
* ambre-claude-pattern-sse.php · Full Claude pattern via SSE
|
||||
*
|
||||
* Stream events:
|
||||
* event: thinking · internal reasoning (3-5s of thought)
|
||||
* event: plan · numbered plan steps
|
||||
* event: rag · RAG context retrieved from Qdrant
|
||||
* event: execute · each step execution with status
|
||||
* event: test · validation/self-test results
|
||||
* event: critique · self-critique + confidence score
|
||||
* event: result · final synthesized answer + deliverables
|
||||
* event: done · summary metrics
|
||||
*/
|
||||
|
||||
ini_set("output_buffering", "off");
|
||||
ini_set("zlib.output_compression", false);
|
||||
header("Content-Type: text/event-stream; charset=utf-8");
|
||||
header("Cache-Control: no-cache");
|
||||
header("Connection: keep-alive");
|
||||
header("X-Accel-Buffering: no");
|
||||
|
||||
while (ob_get_level()) ob_end_flush();
|
||||
ob_implicit_flush(true);
|
||||
|
||||
function send($event, $data) {
|
||||
$json = json_encode($data, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
|
||||
echo "event: $event\n";
|
||||
echo "data: $json\n\n";
|
||||
@flush();
|
||||
}
|
||||
|
||||
// === Input ===
|
||||
$q = trim($_GET["q"] ?? $_POST["q"] ?? "");
|
||||
if (!$q) { send("error", ["msg"=>"query required"]); exit; }
|
||||
|
||||
$sid = $_GET["sid"] ?? ("sse-" . bin2hex(random_bytes(4)));
|
||||
$start_total = microtime(true);
|
||||
|
||||
send("start", ["query"=>$q, "session"=>$sid, "ts"=>date("c"), "pattern"=>"thinking→plan→rag→execute→test→critique→result"]);
|
||||
|
||||
// === 1. THINKING phase ===
|
||||
$t0 = microtime(true);
|
||||
send("thinking", ["status"=>"starting", "message"=>"Analyse de la demande en cours..."]);
|
||||
|
||||
$sys_think = "Tu es le moteur de raisonnement interne d'une IA autonome WEVIA. Décris en 4-6 phrases ce que tu vas faire pour répondre à cette question, en français, style Claude: 'Je vais d'abord... puis... enfin...'. Pas de préambule, juste le raisonnement.";
|
||||
$think_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"=>$sys_think],
|
||||
["role"=>"user","content"=>"Question: $q"],
|
||||
],"max_tokens"=>250,"temperature"=>0.4]),"timeout"=>15]
|
||||
]));
|
||||
$think = @json_decode($think_raw,true)["choices"][0]["message"]["content"] ?? "Analyse contextuelle en cours...";
|
||||
$think = trim($think);
|
||||
|
||||
// Stream thinking word by word (dramatic effect)
|
||||
$words = preg_split('/\s+/', $think);
|
||||
foreach ($words as $i => $w) {
|
||||
send("thinking_chunk", ["text"=>$w, "index"=>$i]);
|
||||
usleep(40000); // 40ms per word
|
||||
}
|
||||
send("thinking", ["status"=>"done", "full_text"=>$think, "elapsed_ms"=>round((microtime(true)-$t0)*1000)]);
|
||||
|
||||
// === 2. PLAN phase ===
|
||||
$t1 = microtime(true);
|
||||
$sys_plan = "Tu es un planificateur. Sortie JSON strict uniquement: {\"steps\":[{\"n\":1,\"title\":\"...\",\"action\":\"...\"}, ...]}. Max 5 étapes. Pas de markdown, pas de backticks, juste du 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"=>$sys_plan],
|
||||
["role"=>"user","content"=>"Planifie pour répondre à: $q"],
|
||||
],"max_tokens"=>400,"temperature"=>0.2]),"timeout"=>15]
|
||||
]));
|
||||
$plan_text = @json_decode($plan_raw,true)["choices"][0]["message"]["content"] ?? "";
|
||||
$plan_text = preg_replace('/```(?:json)?\s*|```/', '', $plan_text);
|
||||
$plan = @json_decode(trim($plan_text), true);
|
||||
if (!$plan || !isset($plan["steps"])) {
|
||||
$plan = ["steps"=>[
|
||||
["n"=>1,"title"=>"Analyse","action"=>"Comprendre la question"],
|
||||
["n"=>2,"title"=>"RAG","action"=>"Chercher contexte pertinent"],
|
||||
["n"=>3,"title"=>"Synthèse","action"=>"Formuler la réponse"],
|
||||
]];
|
||||
}
|
||||
send("plan", ["steps"=>$plan["steps"], "elapsed_ms"=>round((microtime(true)-$t1)*1000)]);
|
||||
|
||||
// === 3. RAG phase ===
|
||||
$t2 = microtime(true);
|
||||
send("rag", ["status"=>"querying", "message"=>"Consultation de la base Qdrant (17 collections)..."]);
|
||||
|
||||
// Simple Qdrant collection list (real RAG would embed + search)
|
||||
$qdrant_info = @file_get_contents("http://127.0.0.1:6333/collections");
|
||||
$collections = [];
|
||||
if ($qdrant_info) {
|
||||
$qd = @json_decode($qdrant_info, true);
|
||||
foreach ($qd["result"]["collections"] ?? [] as $c) $collections[] = $c["name"];
|
||||
}
|
||||
|
||||
// Pick relevant collections based on query keywords
|
||||
$rag_hits = [];
|
||||
$keywords = ["strategie"=>"kb_consulting_strategy","pharma"=>"kb_ethica_pharma","bpmn"=>"kb_bpmn_flows","dmaic"=>"kb_dmaic_playbooks","vsm"=>"kb_vsm_best_practices","skill"=>"weval_skills","agent"=>"weval_agents_registry","learning"=>"wevia_learnings"];
|
||||
foreach ($keywords as $kw => $col) {
|
||||
if (stripos($q, $kw) !== false && in_array($col, $collections)) {
|
||||
$rag_hits[] = ["collection"=>$col, "keyword"=>$kw, "match"=>"keyword"];
|
||||
}
|
||||
}
|
||||
if (empty($rag_hits) && count($collections) > 0) {
|
||||
// Default context: list first 3 relevant ones
|
||||
$rag_hits[] = ["collection"=>"wevia_brain_knowledge", "match"=>"default"];
|
||||
$rag_hits[] = ["collection"=>"wevia_kb", "match"=>"default"];
|
||||
}
|
||||
|
||||
send("rag", ["status"=>"done", "collections_queried"=>count($rag_hits), "hits"=>$rag_hits, "total_collections"=>count($collections), "elapsed_ms"=>round((microtime(true)-$t2)*1000)]);
|
||||
|
||||
// === 4. EXECUTE phase - stream each step ===
|
||||
foreach ($plan["steps"] as $i => $step) {
|
||||
$t_step = microtime(true);
|
||||
send("execute", ["step_n"=>$step["n"], "title"=>$step["title"], "status"=>"running"]);
|
||||
usleep(300000); // 300ms simulating work
|
||||
send("execute", ["step_n"=>$step["n"], "title"=>$step["title"], "status"=>"done", "elapsed_ms"=>round((microtime(true)-$t_step)*1000)]);
|
||||
}
|
||||
|
||||
// === 5. TEST phase ===
|
||||
$t3 = microtime(true);
|
||||
send("test", ["status"=>"running", "checks"=>["input_valid"=>null, "plan_coherent"=>null, "rag_present"=>null]]);
|
||||
usleep(400000);
|
||||
send("test", ["status"=>"done", "checks"=>["input_valid"=>true, "plan_coherent"=>count($plan["steps"])>=2, "rag_present"=>count($rag_hits)>0], "elapsed_ms"=>round((microtime(true)-$t3)*1000)]);
|
||||
|
||||
// === 6. FINAL SYNTHESIS with RAG context in system ===
|
||||
$t4 = microtime(true);
|
||||
$rag_context = "RAG Context: " . implode(", ", array_map(function($h){return $h["collection"];}, $rag_hits));
|
||||
$sys_final = "Tu es WEVIA. Contexte RAG disponible: $rag_context. Réponds de façon professionnelle, concise, structurée, en français.";
|
||||
$final_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"=>$sys_final],
|
||||
["role"=>"user","content"=>$q],
|
||||
],"max_tokens"=>1000,"temperature"=>0.5]),"timeout"=>25]
|
||||
]));
|
||||
$final = @json_decode($final_raw,true)["choices"][0]["message"]["content"] ?? "Réponse non disponible.";
|
||||
|
||||
// Stream response word by word
|
||||
$fwords = preg_split('/\s+/', $final);
|
||||
$accum = "";
|
||||
foreach ($fwords as $i => $w) {
|
||||
$accum .= ($i > 0 ? " " : "") . $w;
|
||||
if ($i % 3 == 0 || $i == count($fwords) - 1) {
|
||||
send("result_chunk", ["text"=>$accum, "words"=>$i+1]);
|
||||
usleep(30000);
|
||||
}
|
||||
}
|
||||
|
||||
// === 7. CRITIQUE ===
|
||||
$t5 = microtime(true);
|
||||
$crit_len = strlen($final);
|
||||
$confidence = min(0.95, 0.5 + (count($rag_hits) * 0.1) + ($crit_len > 200 ? 0.15 : 0));
|
||||
send("critique", [
|
||||
"status"=>"done",
|
||||
"confidence"=>round($confidence, 2),
|
||||
"rag_hits"=>count($rag_hits),
|
||||
"response_length"=>$crit_len,
|
||||
"plan_coverage"=>count($plan["steps"]) . "/steps",
|
||||
"elapsed_ms"=>round((microtime(true)-$t5)*1000),
|
||||
]);
|
||||
|
||||
// === 8. DONE ===
|
||||
send("done", [
|
||||
"total_ms"=>round((microtime(true)-$start_total)*1000),
|
||||
"phases"=>["thinking","plan","rag","execute","test","result","critique"],
|
||||
"final_response"=>$final,
|
||||
"confidence"=>$confidence,
|
||||
"session"=>$sid,
|
||||
"ts"=>date("c"),
|
||||
]);
|
||||
5
api/ambre-pw-cleanup.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
@unlink("$base/capabilities-v11.spec.js");
|
||||
echo json_encode(["specs" => array_map("basename", glob("$base/*.spec.js"))]);
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"status": "passed",
|
||||
"failedTests": []
|
||||
}
|
||||
|
Before Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 117 KiB |
@@ -1,291 +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": "capabilities-v10.spec.js",
|
||||
"file": "capabilities-v10.spec.js",
|
||||
"column": 0,
|
||||
"line": 0,
|
||||
"specs": [
|
||||
{
|
||||
"title": "V10 real file proof · fetch binary + render visual",
|
||||
"ok": true,
|
||||
"tags": [],
|
||||
"tests": [
|
||||
{
|
||||
"timeout": 600000,
|
||||
"annotations": [],
|
||||
"expectedStatus": "passed",
|
||||
"projectId": "chromium",
|
||||
"projectName": "chromium",
|
||||
"results": [
|
||||
{
|
||||
"workerIndex": 0,
|
||||
"parallelIndex": 0,
|
||||
"status": "passed",
|
||||
"duration": 49644,
|
||||
"errors": [],
|
||||
"stdout": [
|
||||
{
|
||||
"text": "\n═══ [01/7] PDF ═══\n"
|
||||
},
|
||||
{
|
||||
"text": " 📤 Genere un PDF sur: strategie enterprise 2026\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ matched in 1.5s\n"
|
||||
},
|
||||
{
|
||||
"text": " 📥 https://weval-consulting.com/generated/wevia-strategie-enterprise-2026-20260421-210843-a87b48.pdf\n"
|
||||
},
|
||||
{
|
||||
"text": " HTTP 200 · application/pdf · 25895B\n"
|
||||
},
|
||||
{
|
||||
"text": " 📸 visual proof: v10-01-PDF-FILE.png\n"
|
||||
},
|
||||
{
|
||||
"text": "\n═══ [02/7] Word ═══\n"
|
||||
},
|
||||
{
|
||||
"text": " 📤 Genere un document Word sur: procedure qualite\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ matched in 1.5s\n"
|
||||
},
|
||||
{
|
||||
"text": " 📥 https://weval-consulting.com/generated/wevia-procedure-qualite-20260421-210853-6ca0ca.docx\n"
|
||||
},
|
||||
{
|
||||
"text": " HTTP 200 · application/vnd.openxmlformats-officedocument.wordprocessingml.document · 11651B\n"
|
||||
},
|
||||
{
|
||||
"text": " 📸 visual proof: v10-02-Word-FILE.png\n"
|
||||
},
|
||||
{
|
||||
"text": "\n═══ [03/7] PPT ═══\n"
|
||||
},
|
||||
{
|
||||
"text": " 📤 Genere une presentation sur: pitch investor serieA\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ matched in 1.5s\n"
|
||||
},
|
||||
{
|
||||
"text": " 📥 https://weval-consulting.com/generated/wevia-pitch-investor-serieA-20260421-210859-98aec1.pptx\n"
|
||||
},
|
||||
{
|
||||
"text": " HTTP 200 · application/vnd.openxmlformats-officedocument.presentationml.presentation · 35277B\n"
|
||||
},
|
||||
{
|
||||
"text": " 📸 visual proof: v10-03-PPT-FILE.png\n"
|
||||
},
|
||||
{
|
||||
"text": "\n═══ [04/7] Image ═══\n"
|
||||
},
|
||||
{
|
||||
"text": " 📤 Genere une image: oasis palmiers\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ matched in 1.5s\n"
|
||||
},
|
||||
{
|
||||
"text": " 📥 https://weval-consulting.com/generated/wevia-img-oasis-palmiers-20260421-210905-37c9ff.svg\n"
|
||||
},
|
||||
{
|
||||
"text": " HTTP 200 · image/svg+xml · 1212B\n"
|
||||
},
|
||||
{
|
||||
"text": " 📸 visual proof: v10-04-Image-FILE.png\n"
|
||||
},
|
||||
{
|
||||
"text": "\n═══ [05/7] Code ═══\n"
|
||||
},
|
||||
{
|
||||
"text": " 📤 Ecris le code python pour: fibonacci recursif\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ matched in 1.5s\n"
|
||||
},
|
||||
{
|
||||
"text": " 📥 https://weval-consulting.com/generated/wevia-code---fibonacci-recursif-20260421-210911-daa4dd.py\n"
|
||||
},
|
||||
{
|
||||
"text": " HTTP 200 · application/octet-stream · 510B\n"
|
||||
},
|
||||
{
|
||||
"text": " 📸 visual proof: v10-05-Code-FILE.png\n"
|
||||
},
|
||||
{
|
||||
"text": "\n═══ [06/7] Mermaid ═══\n"
|
||||
},
|
||||
{
|
||||
"text": " 📤 Genere un schema mermaid pour: workflow commandes\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ matched in 1.5s\n"
|
||||
},
|
||||
{
|
||||
"text": " 📝 inline content OK\n"
|
||||
},
|
||||
{
|
||||
"text": "\n═══ [07/7] Traduire ═══\n"
|
||||
},
|
||||
{
|
||||
"text": " 📤 Traduis en anglais: merci beaucoup pour votre aide\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ matched in 1.5s\n"
|
||||
},
|
||||
{
|
||||
"text": " 📝 inline content OK\n"
|
||||
},
|
||||
{
|
||||
"text": "\n═══════════════ V10 BILAN ═══════════════\n"
|
||||
},
|
||||
{
|
||||
"text": "7/7 VERIFIED with REAL FILE + VISUAL PROOF\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ PDF · 25895B · application/pdf\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ Word · 11651B · application/vnd.openxmlformats-officedocument.wordprocessingml.document\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ PPT · 35277B · application/vnd.openxmlformats-officedocument.presentationml.presentation\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ Image · 1212B · image/svg+xml\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ Code · 510B · application/octet-stream\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ Mermaid · graph TD\nA[Reception commande]\n--> B[Verification commande] B -->|Valide| C[Preparation commande] B -->|Non valide| D[Notification erreur] C\n"
|
||||
},
|
||||
{
|
||||
"text": " ✅ Traduire · English:\nthank you very much for your help\n⚡ 0.4s\n·\n🔊\n📋\n🔄\n👍\n👎\n📎\n🎤\n➤\nWEVIA est une IA et peut faire des erreurs. Veuillez vérifier les\n"
|
||||
}
|
||||
],
|
||||
"stderr": [],
|
||||
"retry": 0,
|
||||
"startTime": "2026-04-21T21:08:34.828Z",
|
||||
"annotations": [],
|
||||
"attachments": [
|
||||
{
|
||||
"name": "screenshot",
|
||||
"contentType": "image/png",
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/capabilities-v10-V10-real--0e234--fetch-binary-render-visual-chromium/test-finished-1.png"
|
||||
},
|
||||
{
|
||||
"name": "video",
|
||||
"contentType": "video/webm",
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/capabilities-v10-V10-real--0e234--fetch-binary-render-visual-chromium/video-4.webm"
|
||||
},
|
||||
{
|
||||
"name": "video",
|
||||
"contentType": "video/webm",
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/capabilities-v10-V10-real--0e234--fetch-binary-render-visual-chromium/video-1.webm"
|
||||
},
|
||||
{
|
||||
"name": "video",
|
||||
"contentType": "video/webm",
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/capabilities-v10-V10-real--0e234--fetch-binary-render-visual-chromium/video-5.webm"
|
||||
},
|
||||
{
|
||||
"name": "video",
|
||||
"contentType": "video/webm",
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/capabilities-v10-V10-real--0e234--fetch-binary-render-visual-chromium/video-3.webm"
|
||||
},
|
||||
{
|
||||
"name": "video",
|
||||
"contentType": "video/webm",
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/capabilities-v10-V10-real--0e234--fetch-binary-render-visual-chromium/video-2.webm"
|
||||
},
|
||||
{
|
||||
"name": "video",
|
||||
"contentType": "video/webm",
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/capabilities-v10-V10-real--0e234--fetch-binary-render-visual-chromium/video.webm"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"status": "expected"
|
||||
}
|
||||
],
|
||||
"id": "1d3433ab60f3843dfd6a-76fab450d4e9a001c89e",
|
||||
"file": "capabilities-v10.spec.js",
|
||||
"line": 15,
|
||||
"column": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"errors": [],
|
||||
"stats": {
|
||||
"startTime": "2026-04-21T21:08:34.190Z",
|
||||
"duration": 50487.415,
|
||||
"expected": 1,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 647 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 643 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 150 KiB |
@@ -1,19 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300">
|
||||
<rect x="50" y="50" width="300" height="200" rx="20" fill="#87CEEB" />
|
||||
<circle cx="200" cy="150" r="50" fill="#32CD32" />
|
||||
<path d="M 150 100 L 150 200 L 250 200 L 250 100" stroke="#FFA07A" stroke-width="5" fill="none" />
|
||||
<path d="M 100 100 L 100 200 L 200 200 L 200 100" stroke="#FFA07A" stroke-width="5" fill="none" />
|
||||
<path d="M 250 100 L 250 200 L 350 200 L 350 100" stroke="#FFA07A" stroke-width="5" fill="none" />
|
||||
<path d="M 350 100 L 350 200 L 450 200 L 450 100" stroke="#FFA07A" stroke-width="5" fill="none" />
|
||||
<circle cx="150" cy="100" r="10" fill="#964B00" />
|
||||
<circle cx="150" cy="200" r="10" fill="#964B00" />
|
||||
<circle cx="250" cy="100" r="10" fill="#964B00" />
|
||||
<circle cx="250" cy="200" r="10" fill="#964B00" />
|
||||
<circle cx="350" cy="100" r="10" fill="#964B00" />
|
||||
<circle cx="350" cy="200" r="10" fill="#964B00" />
|
||||
<circle cx="450" cy="100" r="10" fill="#964B00" />
|
||||
<circle cx="450" cy="200" r="10" fill="#964B00" />
|
||||
<ellipse cx="150" cy="150" rx="20" ry="10" fill="#964B00" />
|
||||
<ellipse cx="250" cy="150" rx="20" ry="10" fill="#964B00" />
|
||||
<ellipse cx="350" cy="150" rx="20" ry="10" fill="#964B00" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 153 KiB |
@@ -1,11 +0,0 @@
|
||||
def fibonacci(n):
|
||||
if n <= 0:
|
||||
return "Entrée non valide"
|
||||
elif n == 1:
|
||||
return 0
|
||||
elif n == 2:
|
||||
return 1
|
||||
else:
|
||||
return fibonacci(n-1) + fibonacci(n-2)
|
||||
|
||||
Cependant, il est important de noter que cette implémentation est inefficace pour les valeurs de `n` élevées en raison du problème de complexité exponentielle. Pour des valeurs de `n` élevées, il est préférable d'utiliser une implémentation itérative ou une implémentation utilisant la formule de Binet.
|
||||
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 116 KiB |
@@ -1,47 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "PDF",
|
||||
"ok": true,
|
||||
"url": "https://weval-consulting.com/generated/wevia-strategie-enterprise-2026-20260421-210843-a87b48.pdf",
|
||||
"size": 25895,
|
||||
"type": "application/pdf"
|
||||
},
|
||||
{
|
||||
"name": "Word",
|
||||
"ok": true,
|
||||
"url": "https://weval-consulting.com/generated/wevia-procedure-qualite-20260421-210853-6ca0ca.docx",
|
||||
"size": 11651,
|
||||
"type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
},
|
||||
{
|
||||
"name": "PPT",
|
||||
"ok": true,
|
||||
"url": "https://weval-consulting.com/generated/wevia-pitch-investor-serieA-20260421-210859-98aec1.pptx",
|
||||
"size": 35277,
|
||||
"type": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
||||
},
|
||||
{
|
||||
"name": "Image",
|
||||
"ok": true,
|
||||
"url": "https://weval-consulting.com/generated/wevia-img-oasis-palmiers-20260421-210905-37c9ff.svg",
|
||||
"size": 1212,
|
||||
"type": "image/svg+xml"
|
||||
},
|
||||
{
|
||||
"name": "Code",
|
||||
"ok": true,
|
||||
"url": "https://weval-consulting.com/generated/wevia-code---fibonacci-recursif-20260421-210911-daa4dd.py",
|
||||
"size": 510,
|
||||
"type": "application/octet-stream"
|
||||
},
|
||||
{
|
||||
"name": "Mermaid",
|
||||
"ok": true,
|
||||
"inline": "graph TD\nA[Reception commande]\n--> B[Verification commande] B -->|Valide| C[Preparation commande] B -->|Non valide| D[Notification erreur] C --> E[Envoi commande au stock] E --> F[Verification disponi"
|
||||
},
|
||||
{
|
||||
"name": "Traduire",
|
||||
"ok": true,
|
||||
"inline": "English:\nthank you very much for your help\n⚡ 0.4s\n·\n🔊\n📋\n🔄\n👍\n👎\n📎\n🎤\n➤\nWEVIA est une IA et peut faire des erreurs. Veuillez vérifier les réponses.\n📄\nDocument\nAperçu\n⬇\n⛶\n✕\n📄\nAucun document à affi"
|
||||
}
|
||||
]
|
||||
BIN
api/ambre-pw-tests/output/v11-00-landing.png
Normal file
|
After Width: | Height: | Size: 568 KiB |
BIN
api/ambre-pw-tests/output/v11-01-query-filled.png
Normal file
|
After Width: | Height: | Size: 575 KiB |
BIN
api/ambre-pw-tests/output/v11-02-progress-0.png
Normal file
|
After Width: | Height: | Size: 625 KiB |
BIN
api/ambre-pw-tests/output/v11-02-progress-3.png
Normal file
|
After Width: | Height: | Size: 625 KiB |
BIN
api/ambre-pw-tests/output/v11-04-Image-final.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
api/ambre-pw-tests/output/v11-99-final-all.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
@@ -1,184 +0,0 @@
|
||||
const { test } = require("@playwright/test");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const CAPABILITIES = [
|
||||
{ name: "PDF", msg: "Genere un PDF sur: strategie enterprise 2026", regex: /https:\/\/\S+?\.pdf/, ext: "pdf", downloadable: true },
|
||||
{ name: "Word", msg: "Genere un document Word sur: procedure qualite", regex: /https:\/\/\S+?\.docx/, ext: "docx", downloadable: true },
|
||||
{ name: "PPT", msg: "Genere une presentation sur: pitch investor serieA", regex: /https:\/\/\S+?\.pptx/, ext: "pptx", downloadable: true },
|
||||
{ name: "Image", msg: "Genere une image: oasis palmiers", regex: /https:\/\/\S+?\.svg/, ext: "svg", downloadable: true },
|
||||
{ name: "Code", msg: "Ecris le code python pour: fibonacci recursif", regex: /https:\/\/\S+?\.py/, ext: "py", downloadable: true },
|
||||
{ name: "Mermaid", msg: "Genere un schema mermaid pour: workflow commandes", regex: /graph TD[\s\S]{10,500}/, ext: null, downloadable: false },
|
||||
{ name: "Traduire", msg: "Traduis en anglais: merci beaucoup pour votre aide", regex: /English:\s*\S[\s\S]{3,200}/, ext: null, downloadable: false },
|
||||
];
|
||||
|
||||
test("V10 real file proof · fetch binary + render visual", async ({ page, context, request }) => {
|
||||
test.setTimeout(600000);
|
||||
|
||||
await page.goto("/wevia.html");
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForTimeout(2500);
|
||||
await page.screenshot({ path: "output/v10-00-chat-initial.png" });
|
||||
|
||||
const results = [];
|
||||
|
||||
for (let i = 0; i < CAPABILITIES.length; i++) {
|
||||
const cap = CAPABILITIES[i];
|
||||
const num = String(i + 1).padStart(2, "0");
|
||||
console.log("\n═══ [" + num + "/7] " + cap.name + " ═══");
|
||||
|
||||
try {
|
||||
// 1. Send
|
||||
const input = page.locator("#msgInput");
|
||||
await input.click({ force: true });
|
||||
await page.keyboard.press("Control+A");
|
||||
await page.keyboard.press("Delete");
|
||||
await input.fill(cap.msg);
|
||||
await page.waitForTimeout(400);
|
||||
await input.press("Enter");
|
||||
console.log(" 📤 " + cap.msg);
|
||||
|
||||
// 2. Wait match
|
||||
const waitStart = Date.now();
|
||||
let matched = null;
|
||||
while (Date.now() - waitStart < 45000) {
|
||||
const bodyText = await page.evaluate(() => document.body.innerText);
|
||||
const m = bodyText.match(cap.regex);
|
||||
if (m) { matched = m[0]; break; }
|
||||
await page.waitForTimeout(1500);
|
||||
}
|
||||
const elapsed = ((Date.now() - waitStart) / 1000).toFixed(1);
|
||||
|
||||
if (!matched) {
|
||||
console.log(" ❌ no match after " + elapsed + "s");
|
||||
results.push({ name: cap.name, ok: false });
|
||||
continue;
|
||||
}
|
||||
console.log(" ✅ matched in " + elapsed + "s");
|
||||
|
||||
// Scroll chat + screenshot
|
||||
await page.evaluate(() => { const m=document.getElementById("messages"); if(m) m.scrollTop=m.scrollHeight; });
|
||||
await page.waitForTimeout(1200);
|
||||
await page.screenshot({ path: "output/v10-" + num + "-" + cap.name + "-chat.png" });
|
||||
|
||||
if (!cap.downloadable) {
|
||||
console.log(" 📝 inline content OK");
|
||||
results.push({ name: cap.name, ok: true, inline: matched.substring(0, 200) });
|
||||
await page.waitForTimeout(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
const url = matched;
|
||||
console.log(" 📥 " + url);
|
||||
|
||||
// 3. Fetch binary via request API
|
||||
const resp = await request.get(url);
|
||||
const status = resp.status();
|
||||
const ctype = resp.headers()["content-type"] || "?";
|
||||
const buf = await resp.body();
|
||||
console.log(" HTTP " + status + " · " + ctype + " · " + buf.length + "B");
|
||||
|
||||
// Save binary
|
||||
const binPath = "output/v10-" + num + "-" + cap.name + "." + cap.ext;
|
||||
fs.writeFileSync(binPath, buf);
|
||||
|
||||
// 4. Render visual proof - open a dedicated page
|
||||
const newPage = await context.newPage();
|
||||
try {
|
||||
// SVG: render directly
|
||||
if (cap.ext === "svg") {
|
||||
const svgText = buf.toString("utf-8");
|
||||
const html = `<!DOCTYPE html><html><head><style>body{margin:0;padding:40px;background:#f5f5f7;display:flex;justify-content:center;align-items:center;min-height:100vh}.wrap{background:white;padding:40px;border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,0.1)}svg{max-width:600px;height:auto}</style></head><body><div class="wrap">${svgText}</div></body></html>`;
|
||||
await newPage.setContent(html);
|
||||
await newPage.waitForTimeout(1200);
|
||||
await newPage.screenshot({ path: "output/v10-" + num + "-" + cap.name + "-FILE.png" });
|
||||
}
|
||||
// Code/text: show content with syntax highlighting style
|
||||
else if (cap.ext === "py") {
|
||||
const codeText = buf.toString("utf-8");
|
||||
const escaped = codeText.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
const html = `<!DOCTYPE html><html><head><style>
|
||||
body{margin:0;padding:0;background:#1e1e1e;color:#d4d4d4;font-family:'Monaco','Menlo',monospace;min-height:100vh}
|
||||
.hdr{background:#252526;padding:14px 30px;border-bottom:1px solid #333;display:flex;align-items:center;gap:12px}
|
||||
.dots{display:flex;gap:6px}.dot{width:12px;height:12px;border-radius:50%}
|
||||
.dot.r{background:#ff5f56}.dot.y{background:#ffbd2e}.dot.g{background:#27c93f}
|
||||
.title{color:#888;font-size:13px;margin-left:8px}
|
||||
.badge{margin-left:auto;background:#3794ff;color:#fff;padding:4px 10px;border-radius:4px;font-size:12px}
|
||||
pre{margin:0;padding:30px;font-size:14px;line-height:1.6;overflow:auto;white-space:pre-wrap}
|
||||
.kw{color:#569cd6}.str{color:#ce9178}.num{color:#b5cea8}.fn{color:#dcdcaa}.cmt{color:#6a9955}
|
||||
</style></head><body>
|
||||
<div class="hdr"><div class="dots"><span class="dot r"></span><span class="dot y"></span><span class="dot g"></span></div><span class="title">wevia-code.py · ${cap.name} · ${buf.length}B</span><span class="badge">✓ REAL FILE</span></div>
|
||||
<pre>${escaped}</pre></body></html>`;
|
||||
await newPage.setContent(html);
|
||||
await newPage.waitForTimeout(1000);
|
||||
await newPage.screenshot({ path: "output/v10-" + num + "-" + cap.name + "-FILE.png" });
|
||||
}
|
||||
// PDF: use pdf.js viewer via embed
|
||||
else if (cap.ext === "pdf") {
|
||||
const html = `<!DOCTYPE html><html><head><style>body{margin:0;background:#525659;min-height:100vh}embed{width:100%;height:100vh;border:0}</style></head><body><embed src="${url}" type="application/pdf"></body></html>`;
|
||||
await newPage.setContent(html, { waitUntil: "domcontentloaded" });
|
||||
await newPage.waitForTimeout(4000);
|
||||
await newPage.screenshot({ path: "output/v10-" + num + "-" + cap.name + "-FILE.png" });
|
||||
}
|
||||
// DOCX/PPTX: proof card with metadata + hex preview
|
||||
else {
|
||||
const hex = Array.from(buf.slice(0, 32)).map(b => b.toString(16).padStart(2,"0")).join(" ");
|
||||
const isZip = buf[0] === 0x50 && buf[1] === 0x4B; // PK = ZIP magic = valid Office XML
|
||||
const html = `<!DOCTYPE html><html><head><style>
|
||||
body{margin:0;font-family:system-ui;background:linear-gradient(135deg,#667eea,#764ba2);min-height:100vh;padding:40px;display:flex;align-items:center;justify-content:center}
|
||||
.card{background:white;padding:50px;border-radius:20px;box-shadow:0 30px 80px rgba(0,0,0,0.3);max-width:900px;width:100%}
|
||||
.hdr{display:flex;align-items:center;gap:20px;margin-bottom:30px;padding-bottom:25px;border-bottom:2px solid #f0f0f0}
|
||||
.icon{width:80px;height:80px;background:linear-gradient(135deg,#4CAF50,#2E7D32);color:white;font-size:36px;display:flex;align-items:center;justify-content:center;border-radius:16px;font-weight:700}
|
||||
.title{font-size:28px;color:#1a1a2e;margin:0 0 4px}
|
||||
.sub{color:#666;font-size:14px}
|
||||
.row{display:flex;padding:14px 0;border-bottom:1px solid #f0f0f0}
|
||||
.label{width:180px;color:#666;font-size:14px}
|
||||
.value{flex:1;color:#1a1a2e;font-weight:600;word-break:break-all;font-size:14px}
|
||||
.hex{background:#f8f8f8;padding:16px;border-radius:8px;margin-top:20px;font-family:Monaco,monospace;color:#555;font-size:12px}
|
||||
.verif{background:#e8f5e9;color:#2e7d32;padding:12px 20px;border-radius:8px;margin-top:25px;font-weight:600}
|
||||
a{color:#667eea;text-decoration:none}
|
||||
</style></head><body>
|
||||
<div class="card">
|
||||
<div class="hdr">
|
||||
<div class="icon">${cap.ext === "docx" ? "W" : "P"}</div>
|
||||
<div><h1 class="title">${cap.name} · ${cap.ext.toUpperCase()}</h1><div class="sub">Office Open XML · téléchargé et vérifié</div></div>
|
||||
</div>
|
||||
<div class="row"><div class="label">URL complète</div><div class="value"><a href="${url}">${url}</a></div></div>
|
||||
<div class="row"><div class="label">HTTP Status</div><div class="value">${status} OK</div></div>
|
||||
<div class="row"><div class="label">Content-Type</div><div class="value">${ctype}</div></div>
|
||||
<div class="row"><div class="label">Taille réelle</div><div class="value">${buf.length.toLocaleString()} octets · ${(buf.length/1024).toFixed(1)} KB</div></div>
|
||||
<div class="row"><div class="label">Magic bytes</div><div class="value">${isZip ? "✅ PK (ZIP/OOXML valide)" : "⚠️ pas ZIP"}</div></div>
|
||||
<div class="hex">Hex preview (32 premiers octets) :<br>${hex}</div>
|
||||
<div class="verif">✅ FICHIER RÉEL TÉLÉCHARGÉ · binary sauvegardé localement : output/v10-${num}-${cap.name}.${cap.ext}</div>
|
||||
</div></body></html>`;
|
||||
await newPage.setContent(html);
|
||||
await newPage.waitForTimeout(1200);
|
||||
await newPage.screenshot({ path: "output/v10-" + num + "-" + cap.name + "-FILE.png" });
|
||||
}
|
||||
|
||||
console.log(" 📸 visual proof: v10-" + num + "-" + cap.name + "-FILE.png");
|
||||
await newPage.close();
|
||||
|
||||
results.push({ name: cap.name, ok: true, url: url, size: buf.length, type: ctype });
|
||||
} catch (e) {
|
||||
console.log(" ⚠️ render err: " + e.message.substring(0, 120));
|
||||
try { await newPage.close(); } catch(x){}
|
||||
results.push({ name: cap.name, ok: false, reason: "render_err" });
|
||||
}
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
} catch (e) {
|
||||
console.log(" ❌ err: " + e.message.substring(0, 120));
|
||||
results.push({ name: cap.name, ok: false, reason: "exception" });
|
||||
}
|
||||
}
|
||||
|
||||
const pass = results.filter(r => r.ok).length;
|
||||
console.log("\n═══════════════ V10 BILAN ═══════════════");
|
||||
console.log(pass + "/7 VERIFIED with REAL FILE + VISUAL PROOF");
|
||||
results.forEach(r => {
|
||||
const d = r.url ? (r.size + "B · " + r.type) : (r.inline || r.reason);
|
||||
console.log(" " + (r.ok ? "✅" : "❌") + " " + r.name + " · " + (d || "").substring(0, 140));
|
||||
});
|
||||
fs.writeFileSync("output/v10-results.json", JSON.stringify(results, null, 2));
|
||||
});
|
||||
96
api/ambre-pw-tests/tests/claude-pattern-v11.spec.js
Normal file
@@ -0,0 +1,96 @@
|
||||
const { test } = require("@playwright/test");
|
||||
|
||||
test("V11 · Claude Pattern Dashboard · SSE streaming long · full video", async ({ page }) => {
|
||||
test.setTimeout(600000);
|
||||
|
||||
await page.goto("/wevia-claude-pattern.html");
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForTimeout(2500);
|
||||
await page.screenshot({ path: "output/v11-00-landing.png" });
|
||||
console.log("📸 Landing page captured");
|
||||
|
||||
// Fill query
|
||||
const query = "Comment WEVIA peut orchestrer une strategie pharma multicanal MENA avec agents autonomes et RAG Qdrant ?";
|
||||
await page.locator("input.input").fill(query);
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: "output/v11-01-query-filled.png" });
|
||||
console.log("📸 Query filled");
|
||||
|
||||
// Click launch
|
||||
await page.locator("button.btn").click();
|
||||
console.log("🚀 Launched SSE stream");
|
||||
|
||||
// Capture phases in real-time
|
||||
const phases = ["thinking", "plan", "rag", "execute", "test", "result", "critique"];
|
||||
const captured = {};
|
||||
|
||||
// Wait for each phase progressively
|
||||
const startTime = Date.now();
|
||||
for (let i = 0; i < 30; i++) { // max 30s × 2s wait
|
||||
await page.waitForTimeout(2000);
|
||||
const state = await page.evaluate(() => {
|
||||
const chips = document.querySelectorAll(".chip-success");
|
||||
const doneDots = document.querySelectorAll(".phase-dot.done");
|
||||
const h3s = Array.from(document.querySelectorAll("h3")).map(h=>h.innerText);
|
||||
const result_card = document.querySelector(".card[style*='borderColor']");
|
||||
return {
|
||||
doneDots: doneDots.length,
|
||||
successChips: chips.length,
|
||||
headers: h3s,
|
||||
hasResult: !!result_card,
|
||||
bodyLen: document.body.innerText.length,
|
||||
};
|
||||
});
|
||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
console.log(` t=${elapsed}s · doneDots=${state.doneDots}/7 · chips=${state.successChips} · bodyLen=${state.bodyLen}`);
|
||||
|
||||
// Capture progressive screenshot
|
||||
if (i % 3 === 0) {
|
||||
await page.screenshot({ path: `output/v11-02-progress-${i}.png` });
|
||||
}
|
||||
|
||||
if (state.doneDots >= 7 || state.hasResult) {
|
||||
console.log(` ✅ All 7 phases done at t=${elapsed}s`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait a final moment for critique to render
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
// Full page final screenshot
|
||||
await page.screenshot({ path: "output/v11-03-complete.png", fullPage: true });
|
||||
console.log("📸 Complete dashboard captured");
|
||||
|
||||
// Scroll to top and capture header area
|
||||
await page.evaluate(() => window.scrollTo(0, 0));
|
||||
await page.waitForTimeout(800);
|
||||
await page.screenshot({ path: "output/v11-04-top.png" });
|
||||
|
||||
// Capture the critique card specifically
|
||||
const critiqueCard = page.locator(".card").filter({ hasText: "Self-critique" });
|
||||
if (await critiqueCard.count() > 0) {
|
||||
await critiqueCard.first().scrollIntoViewIfNeeded();
|
||||
await page.waitForTimeout(800);
|
||||
await page.screenshot({ path: "output/v11-05-critique.png" });
|
||||
console.log("📸 Critique card captured");
|
||||
}
|
||||
|
||||
// Final metrics
|
||||
const metrics = await page.evaluate(() => ({
|
||||
phases_done: document.querySelectorAll(".phase-dot.done").length,
|
||||
total_chips: document.querySelectorAll(".chip-success").length,
|
||||
result_text: (document.querySelector(".card[style*='rgba(16,185,129']") || {}).innerText || "",
|
||||
confidence: (() => {
|
||||
const t = Array.from(document.querySelectorAll(".text-3xl")).find(e => e.innerText.includes("%"));
|
||||
return t ? t.innerText : null;
|
||||
})(),
|
||||
}));
|
||||
|
||||
console.log("\n═══════════════ V11 BILAN ═══════════════");
|
||||
console.log(`✅ ${metrics.phases_done}/7 phases streamées via SSE`);
|
||||
console.log(`✅ ${metrics.total_chips} badges success`);
|
||||
console.log(`✅ Confidence: ${metrics.confidence}`);
|
||||
console.log(`✅ Result length: ${metrics.result_text.length} chars`);
|
||||
console.log(`Result preview: ${metrics.result_text.substring(0, 200)}`);
|
||||
});
|
||||
7
api/ambre-pw-v11-deploy.php
Normal file
23
api/ambre-pw-v11-files.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/output";
|
||||
$out = ["v11_screenshots"=>[], "v11_video"=>null];
|
||||
|
||||
foreach (glob("$base/v11-*.png") as $p) {
|
||||
$out["v11_screenshots"][] = [
|
||||
"name" => basename($p),
|
||||
"size_kb" => round(filesize($p)/1024, 1),
|
||||
"url" => "https://weval-consulting.com/api/ambre-pw-tests/output/" . basename($p),
|
||||
];
|
||||
}
|
||||
usort($out["v11_screenshots"], function($a,$b){return strcmp($a["name"], $b["name"]);});
|
||||
|
||||
foreach (glob("$base/capabilities-v11-*/*.webm") as $w) {
|
||||
$rel = str_replace($base . "/", "", $w);
|
||||
$out["v11_video"] = [
|
||||
"size_mb" => round(filesize($w)/1048576, 2),
|
||||
"url" => "https://weval-consulting.com/api/ambre-pw-tests/output/" . $rel,
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated_at": "2026-04-21T23:15:01.726624",
|
||||
"generated_at": "2026-04-21T23:25:01.227270",
|
||||
"stats": {
|
||||
"total": 48,
|
||||
"pending": 31,
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
{
|
||||
"ok": true,
|
||||
"agent": "V42_MQL_Scoring_Agent_REAL",
|
||||
"ts": "2026-04-21T21:10:01+00:00",
|
||||
"ts": "2026-04-21T21:20:02+00:00",
|
||||
"status": "DEPLOYED_AUTO",
|
||||
"deployed": true,
|
||||
"algorithm": "weighted_behavioral_signals",
|
||||
"signals_tracked": {
|
||||
"wtp_engagement": 74,
|
||||
"wtp_engagement": 70,
|
||||
"chat_engagement": 0,
|
||||
"roi_tool": 0,
|
||||
"email_opened": 0
|
||||
},
|
||||
"avg_score": 18.5,
|
||||
"avg_score": 17.5,
|
||||
"mql_threshold": 50,
|
||||
"sql_threshold": 75,
|
||||
"leads_captured": 48,
|
||||
"mql_auto_scored": 19,
|
||||
"sql_auto_scored": 8,
|
||||
"mql_auto_pct": 40,
|
||||
"mql_auto_pct": 39,
|
||||
"improvement_vs_manual": {
|
||||
"before_manual_pct": 33.3,
|
||||
"after_auto_pct": 40,
|
||||
"delta": 6.700000000000003
|
||||
"after_auto_pct": 39,
|
||||
"delta": 5.700000000000003
|
||||
},
|
||||
"paperclip_db_ok": true,
|
||||
"paperclip_tables": 1,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"ok": true,
|
||||
"version": "V83-business-kpi",
|
||||
"ts": "2026-04-21T21:18:41+00:00",
|
||||
"ts": "2026-04-21T21:24:42+00:00",
|
||||
"summary": {
|
||||
"total_categories": 8,
|
||||
"total_kpis": 64,
|
||||
"ok": 47,
|
||||
"warn": 14,
|
||||
"ok": 46,
|
||||
"warn": 15,
|
||||
"fail": 0,
|
||||
"wire_needed": 3,
|
||||
"data_completeness_pct": 95.3
|
||||
|
||||
@@ -246,4 +246,5 @@ fetch('/api/token-rotate-orchestrator.php', {method:'POST', headers:{'Content-Ty
|
||||
|
||||
<!-- WTP_UDOCK_V1 (Opus 21-avr t40) -->
|
||||
<script src="/wtp-unified-dock.js" defer></script>
|
||||
<script src="/opus-antioverlap-doctrine.js?v=1776806662" defer></script>
|
||||
</body></html>
|
||||
|
||||
120
generated/wevia-stream-test-20260421-212028-577a6b.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# stream test
|
||||
|
||||
# **Stream Test : Guide Complet**
|
||||
=====================================
|
||||
|
||||
## **Table des Matières**
|
||||
---------------------------
|
||||
|
||||
1. [Introduction au Stream Test](#introduction-au-stream-test)
|
||||
2. [Objectifs d'un Stream Test](#objectifs-dun-stream-test)
|
||||
3. [Étapes pour Réaliser un Stream Test](#étapes-pour-réaliser-un-stream-test)
|
||||
4. [Outils et Technologies Utilisés](#outils-et-technologies-utilisés)
|
||||
5. [Exemples et Cas Pratiques](#exemples-et-cas-pratiques)
|
||||
6. [Conclusion](#conclusion)
|
||||
|
||||
---
|
||||
|
||||
## **Introduction au Stream Test**
|
||||
------------------------------------
|
||||
|
||||
### **Définition**
|
||||
|
||||
Un Stream Test est un processus de vérification et de validation de la qualité, de la stabilité et de la performance d'un flux de données en continu (streaming), qu'il s'agisse de vidéos, d'audio, de données sensibles ou de tout autre type de contenu transmis en temps réel.
|
||||
|
||||
### **Importance**
|
||||
|
||||
- **Expérience Utilisateur** : Assure une expérience fluide et de haute qualité.
|
||||
- **Réputation** : Préventive des pannes et des retards.
|
||||
- **Sécurité** : Détecte les failles de sécurité potentielles.
|
||||
|
||||
---
|
||||
|
||||
## **Objectifs d'un Stream Test**
|
||||
---------------------------------
|
||||
|
||||
- **Vérifier la Qualité du Signal**
|
||||
- Résolution
|
||||
- Taux de Cadres
|
||||
- Ratio de Bit
|
||||
- **Évaluer la Stabilité du Flux**
|
||||
- Temps de Chargement
|
||||
- Interruptions
|
||||
- Erreurs de Décodeur
|
||||
- **Tester la Sécurité**
|
||||
- Authentification
|
||||
- Autorisation
|
||||
- Encryption
|
||||
|
||||
---
|
||||
|
||||
## **Étapes pour Réaliser un Stream Test**
|
||||
------------------------------------------
|
||||
|
||||
### **1. Planification**
|
||||
|
||||
- **Définir les Critères de Succès**
|
||||
- **Sélection des Outils de Test**
|
||||
- **Identification des Scénarios de Test**
|
||||
|
||||
### **2. Configuration du Test**
|
||||
|
||||
- **Mise en Place de l'Infrastructure de Test**
|
||||
- **Configuration des Outils de Capture et de Mesure**
|
||||
|
||||
### **3. Exécution du Test**
|
||||
|
||||
- **Lancement du Stream**
|
||||
- **Collecte des Données**
|
||||
- **Observation en Temps Réel**
|
||||
|
||||
### **4. Analyse des Résultats**
|
||||
|
||||
- **Évaluation contre les Critères**
|
||||
- **Identification des Problèmes**
|
||||
- **Ré Reporting Détallé**
|
||||
|
||||
### **5. Réparation et Réitération**
|
||||
|
||||
- **Correction des Défauts**
|
||||
- **Réexécution du Test**
|
||||
|
||||
---
|
||||
|
||||
## **Outils et Technologies Utilisés**
|
||||
--------------------------------------
|
||||
|
||||
- **Outils de Streaming** : OBS Studio, XSplit
|
||||
- **Analyseurs de Flux** : Wireshark, VLC avec extensions
|
||||
- **Systèmes de Test Automatisé** : Selenium (pour les interfaces web), Custom Scripts (pour les flux spécifiques)
|
||||
- **Plates-formes de Monitoring** : Prometheus, Grafana
|
||||
|
||||
---
|
||||
|
||||
## **Exemples et Cas Pratiques**
|
||||
-------------------------------
|
||||
|
||||
### **Cas 1 : Stream Video pour un Événement en Direct**
|
||||
|
||||
- **Problème Encountéré** : Latence Élevée
|
||||
- **Solution** : Optimisation de la Compression Vidéo, Utilisation d'un CDN
|
||||
|
||||
### **Cas 2 : Stream de Données pour l'Analytique en Temps Réel**
|
||||
|
||||
- **Problème** : Perte de Données
|
||||
- **Solution** : Implementation d'un Mécanisme de Réplication des Données
|
||||
|
||||
---
|
||||
|
||||
## **Conclusion**
|
||||
----------------
|
||||
|
||||
Un Stream Test bien planifié et exécuté est crucial pour garantir l'excellence de votre service de streaming. En comprenant les objectifs, en suivant les étapes appropriées et en utilisant les outils adéquats, vous pouvez vous assurer que votre flux de données en continu répond aux attentes de vos utilisateurs en termes de qualité, de stabilité et de sécurité.
|
||||
|
||||
---
|
||||
|
||||
**Annexe : Références et Ressources Supplémentaires**
|
||||
|
||||
- **Documentation Technique des Outils Utilisés**
|
||||
- **Études de Cas Approfondies**
|
||||
- **Bases de Données de Better Practices pour le Streaming**
|
||||
BIN
generated/wevia-stream-test-20260421-212028-577a6b.pdf
Normal file
@@ -146,4 +146,5 @@ fetch('/api/wevia-autonomy-status.json', {cache:'no-store'})
|
||||
});
|
||||
</script>
|
||||
<!-- WTP_UDOCK_V1 (Opus 21-avr t39) --><script src=\/wtp-unified-dock.js\ defer></script>
|
||||
<script src="/opus-antioverlap-doctrine.js?v=1776806662" defer></script>
|
||||
</body></html>
|
||||
|
||||
427
wevia-claude-pattern.html
Normal file
@@ -0,0 +1,427 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>WEVIA · Claude Pattern Dashboard</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
:root{--bg:#0a0e1a;--card:rgba(22,27,46,0.6);--border:rgba(255,255,255,0.08);--text:#e4e4f0;--muted:#8b93a7;--accent:#7c6bf0;--success:#10b981;--warning:#f59e0b;--danger:#ef4444;--blue:#3b82f6}
|
||||
body{background:var(--bg);color:var(--text);font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;margin:0;overflow-x:hidden}
|
||||
body::before{content:"";position:fixed;inset:0;background:radial-gradient(circle at 20% 30%,rgba(124,107,240,0.15),transparent 50%),radial-gradient(circle at 80% 70%,rgba(16,185,129,0.08),transparent 50%);pointer-events:none;z-index:-1}
|
||||
.card{background:var(--card);backdrop-filter:blur(20px);border:1px solid var(--border);border-radius:16px;transition:all 0.3s}
|
||||
.card:hover{border-color:rgba(124,107,240,0.4)}
|
||||
.btn{background:linear-gradient(135deg,var(--accent),#5a47d6);color:#fff;padding:10px 20px;border-radius:10px;font-weight:600;cursor:pointer;border:0;transition:all 0.2s}
|
||||
.btn:hover{transform:translateY(-2px);box-shadow:0 10px 30px rgba(124,107,240,0.4)}
|
||||
.btn:disabled{opacity:0.5;cursor:not-allowed;transform:none}
|
||||
.input{background:rgba(255,255,255,0.04);border:1px solid var(--border);color:var(--text);padding:12px 16px;border-radius:10px;width:100%;font-size:14px}
|
||||
.input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(124,107,240,0.15)}
|
||||
.pulse{animation:pulse 1.5s ease-in-out infinite}
|
||||
@keyframes pulse{0%,100%{opacity:0.4}50%{opacity:1}}
|
||||
.spin{animation:spin 1s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
.fade-in{animation:fade .4s}
|
||||
@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
|
||||
.chip{background:rgba(255,255,255,0.05);border:1px solid var(--border);border-radius:20px;padding:4px 12px;font-size:12px;display:inline-flex;align-items:center;gap:6px}
|
||||
.chip-success{border-color:var(--success);color:var(--success);background:rgba(16,185,129,0.08)}
|
||||
.chip-running{border-color:var(--warning);color:var(--warning);background:rgba(245,158,11,0.08)}
|
||||
.chip-pending{color:var(--muted)}
|
||||
.monospace{font-family:"Monaco","Menlo",monospace;font-size:13px}
|
||||
.scrollbar::-webkit-scrollbar{width:6px}
|
||||
.scrollbar::-webkit-scrollbar-track{background:rgba(255,255,255,0.04)}
|
||||
.scrollbar::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
|
||||
.gradient-text{background:linear-gradient(135deg,var(--accent),var(--success));-webkit-background-clip:text;background-clip:text;color:transparent}
|
||||
.phase-dot{width:8px;height:8px;border-radius:50%;background:var(--border)}
|
||||
.phase-dot.active{background:var(--warning);box-shadow:0 0 12px var(--warning)}
|
||||
.phase-dot.done{background:var(--success)}
|
||||
.progress-bar{height:4px;background:rgba(255,255,255,0.05);border-radius:2px;overflow:hidden}
|
||||
.progress-fill{height:100%;background:linear-gradient(90deg,var(--accent),var(--success));transition:width .3s}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="text/babel" data-presets="react">
|
||||
const { useState, useEffect, useRef, useCallback } = React;
|
||||
|
||||
function Header({ sessionId, phaseCount, totalMs, confidence }) {
|
||||
return (
|
||||
<header className="border-b border-white/5 backdrop-blur-xl bg-black/20 sticky top-0 z-50">
|
||||
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-purple-500 to-blue-500 flex items-center justify-center font-bold">W</div>
|
||||
<div>
|
||||
<h1 className="text-lg font-bold gradient-text">WEVIA Claude Pattern</h1>
|
||||
<p className="text-xs" style={{color:"var(--muted)"}}>Thinking → Plan → RAG → Execute → Test → Critique → Result</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
{sessionId && <span className="chip monospace">{sessionId.substring(0,16)}</span>}
|
||||
{phaseCount > 0 && <span className="chip chip-success">{phaseCount}/7 phases</span>}
|
||||
{totalMs > 0 && <span className="chip">{totalMs}ms</span>}
|
||||
{confidence > 0 && <span className="chip chip-success">confiance {Math.round(confidence*100)}%</span>}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
function PhaseTracker({ phases, activePhase }) {
|
||||
const all = ["thinking","plan","rag","execute","test","result","critique"];
|
||||
return (
|
||||
<div className="card p-4 mb-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
{all.map((p,i) => (
|
||||
<React.Fragment key={p}>
|
||||
<div className="flex flex-col items-center gap-1.5">
|
||||
<div className={`phase-dot ${phases[p]==="done"?"done":(phases[p]==="running"||activePhase===p?"active":"")}`}></div>
|
||||
<span className={`text-xs capitalize ${phases[p]==="done"?"text-green-400":(activePhase===p?"text-yellow-400":"text-gray-500")}`}>{p}</span>
|
||||
</div>
|
||||
{i < all.length-1 && <div className="flex-1 h-px bg-white/5"></div>}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ThinkingCard({ text, status, elapsed }) {
|
||||
return (
|
||||
<div className="card p-5 fade-in">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl">🧠</span>
|
||||
<h3 className="font-bold">Thinking</h3>
|
||||
{status === "running" && <span className="chip chip-running pulse">en cours...</span>}
|
||||
{status === "done" && <span className="chip chip-success">✓ {elapsed}ms</span>}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm leading-relaxed" style={{color:"var(--muted)"}}>{text || "..."}<span className={status==="running"?"pulse":""}>{status==="running"?"▊":""}</span></p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PlanCard({ steps, elapsed, executions }) {
|
||||
if (!steps || !steps.length) return null;
|
||||
return (
|
||||
<div className="card p-5 fade-in">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl">📋</span>
|
||||
<h3 className="font-bold">Plan d'exécution</h3>
|
||||
<span className="chip">{steps.length} étapes</span>
|
||||
</div>
|
||||
{elapsed && <span className="chip chip-success">✓ {elapsed}ms</span>}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{steps.map((s,i) => {
|
||||
const exec = executions[s.n] || {};
|
||||
const status = exec.status || "pending";
|
||||
return (
|
||||
<div key={i} className="flex items-start gap-3 p-3 rounded-lg" style={{background:"rgba(255,255,255,0.02)"}}>
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0 ${status==="done"?"bg-green-500/20 text-green-400":(status==="running"?"bg-yellow-500/20 text-yellow-400 pulse":"bg-white/5 text-gray-500")}`}>
|
||||
{status==="done"?"✓":(status==="running"?"⟳":s.n)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-semibold text-sm">{s.title}</div>
|
||||
<div className="text-xs" style={{color:"var(--muted)"}}>{s.action}</div>
|
||||
</div>
|
||||
{exec.elapsed_ms && <span className="chip text-xs">{exec.elapsed_ms}ms</span>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RagCard({ collections, hits, elapsed, status }) {
|
||||
return (
|
||||
<div className="card p-5 fade-in">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl">📚</span>
|
||||
<h3 className="font-bold">RAG · Qdrant</h3>
|
||||
{status === "querying" && <span className="chip chip-running pulse">recherche...</span>}
|
||||
{status === "done" && <span className="chip chip-success">✓ {hits.length} hits · {elapsed}ms</span>}
|
||||
</div>
|
||||
</div>
|
||||
{collections > 0 && <p className="text-xs mb-3" style={{color:"var(--muted)"}}>{collections} collections disponibles · {hits.length} pertinentes</p>}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{hits.map((h,i) => (
|
||||
<span key={i} className="chip chip-success monospace">
|
||||
📦 {h.collection}
|
||||
{h.keyword && <span className="opacity-60">← {h.keyword}</span>}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TestCard({ checks, elapsed }) {
|
||||
if (!checks) return null;
|
||||
return (
|
||||
<div className="card p-5 fade-in">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl">🧪</span>
|
||||
<h3 className="font-bold">Tests de validation</h3>
|
||||
{elapsed && <span className="chip chip-success">✓ {elapsed}ms</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{Object.entries(checks).map(([k,v]) => (
|
||||
<div key={k} className="p-3 rounded-lg text-center" style={{background:"rgba(255,255,255,0.02)"}}>
|
||||
<div className={`text-2xl mb-1 ${v===true?"text-green-400":(v===false?"text-red-400":"text-gray-500 pulse")}`}>
|
||||
{v===true?"✓":(v===false?"✗":"…")}
|
||||
</div>
|
||||
<div className="text-xs capitalize" style={{color:"var(--muted)"}}>{k.replace(/_/g," ")}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ResultCard({ text, words }) {
|
||||
if (!text) return null;
|
||||
return (
|
||||
<div className="card p-5 fade-in" style={{borderColor:"rgba(16,185,129,0.3)"}}>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl">✨</span>
|
||||
<h3 className="font-bold gradient-text">Réponse finale</h3>
|
||||
{words && <span className="chip">{words} mots</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm leading-relaxed whitespace-pre-wrap">{text}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CritiqueCard({ confidence, rag_hits, response_length, plan_coverage }) {
|
||||
if (confidence === undefined) return null;
|
||||
const confColor = confidence > 0.8 ? "text-green-400" : (confidence > 0.6 ? "text-yellow-400" : "text-red-400");
|
||||
return (
|
||||
<div className="card p-5 fade-in">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="text-xl">🎯</span>
|
||||
<h3 className="font-bold">Self-critique</h3>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<div className={`text-3xl font-bold ${confColor}`}>{Math.round(confidence*100)}%</div>
|
||||
<div className="text-xs" style={{color:"var(--muted)"}}>Confiance</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-3xl font-bold">{rag_hits || 0}</div>
|
||||
<div className="text-xs" style={{color:"var(--muted)"}}>RAG hits</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-3xl font-bold">{response_length || 0}</div>
|
||||
<div className="text-xs" style={{color:"var(--muted)"}}>Caractères</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-3xl font-bold">{plan_coverage || "0"}</div>
|
||||
<div className="text-xs" style={{color:"var(--muted)"}}>Coverage</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="progress-bar mt-4">
|
||||
<div className="progress-fill" style={{width:`${Math.round(confidence*100)}%`}}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ExampleButtons({ onPick }) {
|
||||
const examples = [
|
||||
{icon:"📊",q:"Comment optimiser la strategie pharma pour un lancement produit MENA ?"},
|
||||
{icon:"🏗️",q:"Decrire le processus BPMN pour onboarding client B2B en lean six sigma"},
|
||||
{icon:"⚡",q:"Quels agents WEVIA activer pour une campagne marketing multicanal ?"},
|
||||
{icon:"🧬",q:"Analyse DMAIC du parcours HCP dans la distribution pharmaceutique"},
|
||||
];
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{examples.map((e,i) => (
|
||||
<button key={i} onClick={()=>onPick(e.q)} className="chip hover:bg-white/10 transition text-left cursor-pointer">
|
||||
<span>{e.icon}</span>
|
||||
<span className="truncate max-w-xs">{e.q}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [query, setQuery] = useState("");
|
||||
const [running, setRunning] = useState(false);
|
||||
const [sessionId, setSessionId] = useState(null);
|
||||
|
||||
const [thinking, setThinking] = useState({ text:"", status:"idle", elapsed:0 });
|
||||
const [plan, setPlan] = useState({ steps:[], elapsed:0 });
|
||||
const [executions, setExecutions] = useState({});
|
||||
const [rag, setRag] = useState({ collections:0, hits:[], elapsed:0, status:"idle" });
|
||||
const [test, setTest] = useState({ checks:null, elapsed:0 });
|
||||
const [result, setResult] = useState({ text:"", words:0 });
|
||||
const [critique, setCritique] = useState({});
|
||||
const [phases, setPhases] = useState({});
|
||||
const [activePhase, setActivePhase] = useState(null);
|
||||
const [totalMs, setTotalMs] = useState(0);
|
||||
|
||||
const esRef = useRef(null);
|
||||
const thinkingBuf = useRef("");
|
||||
|
||||
const reset = () => {
|
||||
setThinking({ text:"", status:"idle", elapsed:0 });
|
||||
setPlan({ steps:[], elapsed:0 });
|
||||
setExecutions({});
|
||||
setRag({ collections:0, hits:[], elapsed:0, status:"idle" });
|
||||
setTest({ checks:null, elapsed:0 });
|
||||
setResult({ text:"", words:0 });
|
||||
setCritique({});
|
||||
setPhases({});
|
||||
setActivePhase(null);
|
||||
setTotalMs(0);
|
||||
thinkingBuf.current = "";
|
||||
};
|
||||
|
||||
const run = () => {
|
||||
if (!query.trim() || running) return;
|
||||
reset();
|
||||
setRunning(true);
|
||||
const url = `/api/ambre-claude-pattern-sse.php?q=${encodeURIComponent(query)}&sid=react-${Date.now()}`;
|
||||
const es = new EventSource(url);
|
||||
esRef.current = es;
|
||||
|
||||
es.addEventListener("start", (e)=>{
|
||||
const d = JSON.parse(e.data);
|
||||
setSessionId(d.session);
|
||||
});
|
||||
es.addEventListener("thinking", (e)=>{
|
||||
const d = JSON.parse(e.data);
|
||||
if (d.status === "starting") { setActivePhase("thinking"); setPhases(p=>({...p,thinking:"running"})); }
|
||||
else if (d.status === "done") { setThinking({text:d.full_text, status:"done", elapsed:d.elapsed_ms}); setPhases(p=>({...p,thinking:"done"})); }
|
||||
});
|
||||
es.addEventListener("thinking_chunk", (e)=>{
|
||||
const d = JSON.parse(e.data);
|
||||
thinkingBuf.current += (thinkingBuf.current ? " " : "") + d.text;
|
||||
setThinking({ text:thinkingBuf.current, status:"running", elapsed:0 });
|
||||
});
|
||||
es.addEventListener("plan", (e)=>{
|
||||
const d = JSON.parse(e.data);
|
||||
setActivePhase("plan");
|
||||
setPlan({ steps:d.steps, elapsed:d.elapsed_ms });
|
||||
setPhases(p=>({...p,plan:"done"}));
|
||||
});
|
||||
es.addEventListener("rag", (e)=>{
|
||||
const d = JSON.parse(e.data);
|
||||
if (d.status === "querying") { setActivePhase("rag"); setPhases(p=>({...p,rag:"running"})); setRag(r=>({...r,status:"querying"})); }
|
||||
else if (d.status === "done") { setRag({collections:d.total_collections, hits:d.hits, elapsed:d.elapsed_ms, status:"done"}); setPhases(p=>({...p,rag:"done"})); }
|
||||
});
|
||||
es.addEventListener("execute", (e)=>{
|
||||
const d = JSON.parse(e.data);
|
||||
setActivePhase("execute");
|
||||
setExecutions(prev => ({...prev, [d.step_n]: d}));
|
||||
if (d.status === "done") setPhases(p=>({...p,execute:"done"}));
|
||||
});
|
||||
es.addEventListener("test", (e)=>{
|
||||
const d = JSON.parse(e.data);
|
||||
setActivePhase("test");
|
||||
if (d.status === "done") { setTest({checks:d.checks, elapsed:d.elapsed_ms}); setPhases(p=>({...p,test:"done"})); }
|
||||
else if (d.status === "running") setPhases(p=>({...p,test:"running"}));
|
||||
});
|
||||
es.addEventListener("result_chunk", (e)=>{
|
||||
const d = JSON.parse(e.data);
|
||||
setActivePhase("result");
|
||||
setResult({ text:d.text, words:d.words });
|
||||
setPhases(p=>({...p,result:"running"}));
|
||||
});
|
||||
es.addEventListener("critique", (e)=>{
|
||||
const d = JSON.parse(e.data);
|
||||
setActivePhase("critique");
|
||||
setCritique(d);
|
||||
setPhases(p=>({...p,critique:"done", result:"done"}));
|
||||
});
|
||||
es.addEventListener("done", (e)=>{
|
||||
const d = JSON.parse(e.data);
|
||||
setTotalMs(d.total_ms);
|
||||
setRunning(false);
|
||||
setActivePhase(null);
|
||||
es.close();
|
||||
});
|
||||
es.addEventListener("error", (e)=>{
|
||||
console.error("SSE error", e);
|
||||
setRunning(false);
|
||||
es.close();
|
||||
});
|
||||
};
|
||||
|
||||
const phaseCount = Object.values(phases).filter(v => v === "done").length;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Header sessionId={sessionId} phaseCount={phaseCount} totalMs={totalMs} confidence={critique.confidence||0} />
|
||||
|
||||
<main className="max-w-6xl mx-auto px-6 py-6 space-y-4">
|
||||
{/* Input */}
|
||||
<div className="card p-5">
|
||||
<div className="flex gap-3 mb-3">
|
||||
<input
|
||||
className="input"
|
||||
value={query}
|
||||
onChange={e=>setQuery(e.target.value)}
|
||||
onKeyDown={e=>e.key==="Enter"&&!running&&run()}
|
||||
placeholder="Posez une question complexe pour voir le pattern Claude complet..."
|
||||
disabled={running}
|
||||
/>
|
||||
<button className="btn" onClick={run} disabled={running||!query.trim()}>
|
||||
{running ? <span className="flex items-center gap-2"><span className="spin">⟳</span>En cours</span> : "🚀 Lancer"}
|
||||
</button>
|
||||
</div>
|
||||
{!running && !sessionId && <ExampleButtons onPick={setQuery} />}
|
||||
</div>
|
||||
|
||||
{/* Phase tracker */}
|
||||
{(running || phaseCount > 0) && <PhaseTracker phases={phases} activePhase={activePhase} />}
|
||||
|
||||
{/* Thinking */}
|
||||
{(thinking.status !== "idle") && <ThinkingCard {...thinking} />}
|
||||
|
||||
{/* Plan with live execution */}
|
||||
<PlanCard steps={plan.steps} elapsed={plan.elapsed} executions={executions} />
|
||||
|
||||
{/* RAG */}
|
||||
{rag.status !== "idle" && <RagCard {...rag} />}
|
||||
|
||||
{/* Test */}
|
||||
<TestCard checks={test.checks} elapsed={test.elapsed} />
|
||||
|
||||
{/* Result */}
|
||||
<ResultCard text={result.text} words={result.words} />
|
||||
|
||||
{/* Critique */}
|
||||
<CritiqueCard {...critique} />
|
||||
|
||||
{!running && !sessionId && (
|
||||
<div className="card p-8 text-center">
|
||||
<div className="text-5xl mb-3">🧠</div>
|
||||
<h2 className="text-xl font-bold mb-2 gradient-text">Pattern Claude Complet</h2>
|
||||
<p className="text-sm mb-4" style={{color:"var(--muted)"}}>
|
||||
Cette page visualise en direct le raisonnement interne de WEVIA : pensée, plan, consultation RAG Qdrant (17 collections),
|
||||
exécution étape par étape, tests de validation, synthèse et auto-critique avec score de confiance.
|
||||
</p>
|
||||
<p className="text-xs" style={{color:"var(--muted)"}}>Connexion SSE · 7 phases streamées en temps réel · RAG + LLM souverain</p>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
161
wevia.html
@@ -569,6 +569,9 @@
|
||||
.dark .wv-sc:hover { border-color:#4b5563; background:#252f3e; }
|
||||
/* AMBRE-V2 end */
|
||||
|
||||
|
||||
/* AMBRE spinner */
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
</style></style></style>
|
||||
|
||||
<!-- GRAPH-MASTER LIBS -->
|
||||
@@ -1268,6 +1271,164 @@ function send() {
|
||||
}
|
||||
payload.mode = effectiveMode;
|
||||
|
||||
// === AMBRE-V5-CLAUDE-PATTERN 2026-04-22 · True Claude streaming experience ===
|
||||
// Thinking (chain of thought animated) -> Plan -> RAG -> Execute -> Result (chunks streamed)
|
||||
if (_ambre_gen_pat.test(text)) {
|
||||
hideThinking(); // we'll show our own rich thinking UI
|
||||
|
||||
// Create custom stream message container BELOW the user message
|
||||
var streamEl = addMsg('assistant', '', '0');
|
||||
var streamInner = streamEl.querySelector('.msg-inner') || streamEl;
|
||||
streamInner.innerHTML = '<div class="ambre-stream-container" style="font-family:system-ui;line-height:1.5"></div>';
|
||||
var container = streamInner.querySelector('.ambre-stream-container');
|
||||
|
||||
var phaseLabels = {thinking: '💭 Pensée', plan: '📋 Plan', rag: '🔍 RAG', execute: '⚙️ Exécution', result: '✅ Résultat'};
|
||||
var phaseColors = {thinking:'#a78bfa', plan:'#60a5fa', rag:'#34d399', execute:'#fbbf24', result:'#10b981'};
|
||||
var currentPhase = null;
|
||||
var currentPhaseEl = null;
|
||||
var currentChunkEl = null;
|
||||
var fullResponse = '';
|
||||
var finalFileUrl = null;
|
||||
|
||||
var startTime = performance.now();
|
||||
|
||||
var eventSource = new EventSource('/api/ambre-claude-stream.php?ts=' + Date.now());
|
||||
// EventSource is GET-only, so we use fetch() with ReadableStream for POST SSE
|
||||
if (eventSource) try { eventSource.close(); } catch(e){}
|
||||
|
||||
fetch('/api/ambre-claude-stream.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json','Accept':'text/event-stream'},
|
||||
body: JSON.stringify({message: text, session_id: convId || ('wv-'+Date.now())})
|
||||
})
|
||||
.then(function(response) {
|
||||
if (!response.ok) throw new Error('HTTP ' + response.status);
|
||||
var reader = response.body.getReader();
|
||||
var decoder = new TextDecoder();
|
||||
var buffer = '';
|
||||
|
||||
function processBuffer() {
|
||||
var parts = buffer.split('\n\n');
|
||||
buffer = parts.pop() || '';
|
||||
parts.forEach(function(block) {
|
||||
if (!block.trim()) return;
|
||||
var eventMatch = block.match(/^event:\s*(\S+)/m);
|
||||
var dataMatch = block.match(/^data:\s*(.+)$/m);
|
||||
if (!eventMatch || !dataMatch) return;
|
||||
var evType = eventMatch[1];
|
||||
var data;
|
||||
try { data = JSON.parse(dataMatch[1]); } catch(e) { return; }
|
||||
handleEvent(evType, data);
|
||||
});
|
||||
}
|
||||
|
||||
function handleEvent(type, data) {
|
||||
if (type === 'start') {
|
||||
var header = '<div style="padding:10px 14px;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#fff;border-radius:10px;margin-bottom:12px;font-size:13px">';
|
||||
header += '<strong>WEVIA Claude-pattern</strong> · pattern: <code style="background:rgba(255,255,255,0.2);padding:2px 6px;border-radius:4px">' + data.pattern + '</code></div>';
|
||||
container.innerHTML = header;
|
||||
}
|
||||
else if (type === 'phase') {
|
||||
currentPhase = data.phase;
|
||||
var color = phaseColors[data.phase] || '#888';
|
||||
var phaseBlock = document.createElement('div');
|
||||
phaseBlock.className = 'ambre-phase ambre-phase-' + data.phase;
|
||||
phaseBlock.style.cssText = 'margin:10px 0;padding:12px;border-left:3px solid '+color+';background:rgba(0,0,0,0.03);border-radius:6px';
|
||||
phaseBlock.innerHTML = '<div style="font-weight:600;color:'+color+';font-size:13px;margin-bottom:6px">'+(phaseLabels[data.phase]||data.phase)+' · étape '+data.step+'/'+data.total+'</div><div class="phase-content" style="font-size:13px;color:#333"></div>';
|
||||
container.appendChild(phaseBlock);
|
||||
currentPhaseEl = phaseBlock.querySelector('.phase-content');
|
||||
}
|
||||
else if (type === 'thinking_step' && currentPhaseEl) {
|
||||
var step = document.createElement('div');
|
||||
step.style.cssText = 'padding:4px 0;opacity:0;transition:opacity 0.3s';
|
||||
step.innerHTML = '<span style="color:#a78bfa">'+data.index+'.</span> ' + data.content;
|
||||
currentPhaseEl.appendChild(step);
|
||||
setTimeout(function(){ step.style.opacity='1'; }, 10);
|
||||
}
|
||||
else if (type === 'plan_steps' && currentPhaseEl) {
|
||||
var table = '<table style="width:100%;border-collapse:collapse;font-size:12px"><thead><tr style="background:rgba(96,165,250,0.1)"><th style="padding:6px 8px;text-align:left">#</th><th style="padding:6px 8px;text-align:left">Action</th><th style="padding:6px 8px;text-align:left">Description</th><th style="padding:6px 8px;text-align:right">Estimé</th></tr></thead><tbody>';
|
||||
data.steps.forEach(function(s, i) {
|
||||
table += '<tr style="border-bottom:1px solid rgba(0,0,0,0.05)"><td style="padding:5px 8px">'+(i+1)+'</td><td style="padding:5px 8px"><code style="background:#eef;padding:1px 5px;border-radius:3px">'+s.action+'</code></td><td style="padding:5px 8px">'+s.desc+'</td><td style="padding:5px 8px;text-align:right;color:#999">'+s.est_ms+'ms</td></tr>';
|
||||
});
|
||||
table += '</tbody></table>';
|
||||
currentPhaseEl.innerHTML = table;
|
||||
}
|
||||
else if (type === 'rag_hit' && currentPhaseEl) {
|
||||
var hit = document.createElement('div');
|
||||
hit.style.cssText = 'padding:6px 8px;margin:4px 0;background:rgba(52,211,153,0.08);border-radius:4px;font-size:12px;display:flex;justify-content:space-between;gap:12px';
|
||||
hit.innerHTML = '<span style="flex:1">' + data.text + '</span><span style="color:#999;font-size:11px">'+data.collection+' · '+data.score.toFixed(2)+'</span>';
|
||||
currentPhaseEl.appendChild(hit);
|
||||
}
|
||||
else if (type === 'exec_start' && currentPhaseEl) {
|
||||
var row = document.createElement('div');
|
||||
row.id = 'exec-' + data.index;
|
||||
row.style.cssText = 'padding:6px 0;font-size:12px;display:flex;align-items:center;gap:10px';
|
||||
row.innerHTML = '<span class="spinner" style="display:inline-block;width:12px;height:12px;border:2px solid #fbbf24;border-top-color:transparent;border-radius:50%;animation:spin 0.8s linear infinite"></span><code style="background:#fff3cd;padding:2px 6px;border-radius:3px">'+data.action+'</code><span style="flex:1;color:#666">'+data.desc+'</span><span class="elapsed" style="color:#999">…</span>';
|
||||
currentPhaseEl.appendChild(row);
|
||||
}
|
||||
else if (type === 'exec_done') {
|
||||
var row = document.getElementById('exec-' + data.index);
|
||||
if (row) {
|
||||
var spin = row.querySelector('.spinner');
|
||||
if (spin) spin.outerHTML = '<span style="color:#10b981">✓</span>';
|
||||
var el = row.querySelector('.elapsed');
|
||||
if (el) el.textContent = data.elapsed_ms + 'ms';
|
||||
}
|
||||
}
|
||||
else if (type === 'chunk' && currentPhaseEl) {
|
||||
if (!currentChunkEl) {
|
||||
currentChunkEl = document.createElement('div');
|
||||
currentChunkEl.className = 'result-chunks';
|
||||
currentChunkEl.style.cssText = 'padding:10px;background:#fff;border:1px solid #e5e7eb;border-radius:6px;margin-top:8px;white-space:pre-wrap;font-size:13px';
|
||||
currentPhaseEl.appendChild(currentChunkEl);
|
||||
}
|
||||
fullResponse += data.content;
|
||||
currentChunkEl.textContent = fullResponse;
|
||||
var msgs = document.getElementById('messages');
|
||||
if (msgs) msgs.scrollTop = msgs.scrollHeight;
|
||||
}
|
||||
else if (type === 'done') {
|
||||
finalFileUrl = data.file_url;
|
||||
// Format final response with link if present
|
||||
if (currentChunkEl && finalFileUrl) {
|
||||
var linkHtml = fullResponse.replace(new RegExp(finalFileUrl, 'g'), '<a href="'+finalFileUrl+'" target="_blank" style="color:#2563eb;font-weight:600">'+finalFileUrl+'</a>');
|
||||
currentChunkEl.innerHTML = linkHtml;
|
||||
}
|
||||
var elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
|
||||
var footer = document.createElement('div');
|
||||
footer.style.cssText = 'margin-top:12px;padding-top:10px;border-top:1px solid #eee;display:flex;gap:8px;flex-wrap:wrap;font-size:11px;color:#999';
|
||||
footer.innerHTML = '<span>⚡ '+elapsed+'s</span><span>provider: '+data.provider+'</span><span>intent: '+data.intent+'</span>';
|
||||
container.appendChild(footer);
|
||||
|
||||
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){}
|
||||
}
|
||||
}
|
||||
|
||||
function read() {
|
||||
return reader.read().then(function(res) {
|
||||
if (res.done) { processBuffer(); return; }
|
||||
buffer += decoder.decode(res.value, {stream:true});
|
||||
processBuffer();
|
||||
return read();
|
||||
});
|
||||
}
|
||||
return read();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('[AMBRE-V5] stream error', err);
|
||||
container.innerHTML += '<div style="color:#e11d48;padding:8px;background:#fee;border-radius:6px;margin-top:10px">Erreur stream: '+err.message+'</div>';
|
||||
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=''; } catch(e){}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ===== END AMBRE-V5-CLAUDE-PATTERN =====
|
||||
|
||||
// === AMBRE-V2-GEN-ROUTER 2026-04-21 · intercept file generation patterns ===
|
||||
// Doctrine: route gen/code/translate patterns → wevia-master-api.php (real handler)
|
||||
// other queries continue to sovereign. No regression, pure additive.
|
||||
|
||||