Compare commits
29 Commits
wave-220-a
...
wave-227-g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0eb4825f7d | ||
|
|
e3e6e3ac54 | ||
|
|
8e37e1c3f4 | ||
|
|
5ed6857e78 | ||
|
|
8d0f0ceee4 | ||
|
|
e94c263624 | ||
|
|
e824e9c03e | ||
|
|
2c9ff7c958 | ||
|
|
8a38661311 | ||
|
|
ad9d3dc376 | ||
|
|
c362e5f77e | ||
|
|
61447aca2a | ||
|
|
d3598d1184 | ||
|
|
260cc8a553 | ||
|
|
3c392a4142 | ||
|
|
bb284e4101 | ||
|
|
f3fb7283bf | ||
|
|
6fd30277fa | ||
|
|
68109fc3f2 | ||
|
|
d9859c93fa | ||
|
|
27ae771f3a | ||
|
|
5aaf0e7f0f | ||
|
|
2d7b488c46 | ||
|
|
412ff8b23b | ||
|
|
4ec7c0bb9e | ||
|
|
98618d0006 | ||
|
|
049296d1aa | ||
|
|
d98131946e | ||
|
|
a705e42253 |
@@ -89,8 +89,18 @@ body.light #theme-toggle::before{content:"\263D"}
|
||||
/* V142-FOOTER-STRIP: body padding to prevent footer overlap */
|
||||
body{padding-bottom:26px}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/css/wevia-portal-consistency.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wevia-portal-banner">
|
||||
<span class="wevia-portal-banner-label">🌐 WEVIA ECOSYSTEM</span>
|
||||
<a href="/all-ia-hub.html" data-portal="hub" class="wevia-portal-banner-link wevia-current">🧠 All-IA Hub</a>
|
||||
<a href="/wevia-master.html" data-portal="master" class="wevia-portal-banner-link">🤖 WEVIA Master</a>
|
||||
<a href="/wevia-orchestrator.html" data-portal="arena" class="wevia-portal-banner-link">🎭 Arena Orchestrator</a>
|
||||
<a href="/weval-technology-platform.html" data-portal="wtp" class="wevia-portal-banner-link">🧭 WTP Hub</a>
|
||||
<span class="wevia-portal-badge-wave">WAVE 221</span>
|
||||
</div>
|
||||
|
||||
<!-- BETON-DOCTRINE-101 dual-dummy (entry point) -->
|
||||
<div id="weval-global-logout" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection"></div>
|
||||
<a id="weval-gl" href="#" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection" tabindex="-1"></a>
|
||||
@@ -1324,5 +1334,6 @@ setInterval(refreshStats,60000);
|
||||
<span style="margin-left:auto;color:#00d4b4;font-size:9px"><a href="/wevia-unified-hub.html" style="color:inherit;text-decoration:none" title="Truth Hub">Truth →</a></span>
|
||||
</div>
|
||||
<script>(function(){var p=window.location.pathname;var pub=["/","/index.html","/wevia.html","/wevia-widget.html","/enterprise-model.html","/wevia","/login","/register.html","/agents-archi.html","/wevia-meeting-rooms.html","/director-center.html","/director-chat.html","/l99-brain.html","/agents-fleet.html","/value-streaming.html","/architecture.html","/openclaw.html","/l99-saas.html","/admin-saas.html","/agents-goodjob.html","/ai-benchmark.html","/oss-discovery.html","/paperclip.html","/agents-3d.html","/agents-alive.html","/agents-enterprise.html","/agents-hd.html","/agents-iso3d.html","/agents-sim.html","/agents-valuechain.html","/avatar-picker.html"];var isPub=pub.indexOf(p)>=0||p.indexOf("/products/")===0||p.indexOf("/solutions/")===0||p.indexOf("/blog/")===0||p.indexOf("/service/")===0||p.indexOf("/marketplace")===0||p.indexOf("/contact")===0||p.indexOf("/tarifs")===0||p.indexOf("/news")===0;if(isPub||document.getElementById("weval-gl"))return;var a=document.createElement("a");a.id="weval-gl";a.href="/logout";a.textContent="Logout";a.style.cssText="position:fixed;top:10px;right:12px;z-index:99990;padding:5px 10px;background:rgba(30,30,50,0.7);color:rgba(200,210,230,0.8);border:1px solid rgba(100,100,140,0.3);border-radius:6px;font:500 11px system-ui,sans-serif;text-decoration:none;opacity:0.6;cursor:pointer;backdrop-filter:blur(6px);transition:all .15s";a.onmouseover=function(){this.style.opacity="1";this.style.background="rgba(239,68,68,0.85)";this.style.color="white"};a.onmouseout=function(){this.style.opacity="0.6";this.style.background="rgba(30,30,50,0.7)";this.style.color="rgba(200,210,230,0.8)"};document.body.appendChild(a)})()</script><script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
|
||||
<script src="/api/weval-feature-tracker.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"agent": "V41_Disk_Monitor",
|
||||
"ts": "2026-04-21T22:30:01+02:00",
|
||||
"disk_pct": 82,
|
||||
"disk_free_gb": 27,
|
||||
"ts": "2026-04-21T23:30:02+02:00",
|
||||
"disk_pct": 83,
|
||||
"disk_free_gb": 26,
|
||||
"growth_per_day_gb": 1.5,
|
||||
"runway_days": 18,
|
||||
"runway_days": 17,
|
||||
"alert": "WARN_runway_under_30d",
|
||||
"action_auto_if_under_7d": "trigger_hetzner_volume_extension_api",
|
||||
"hetzner_volume_size_gb_recommended": 500,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"agent": "V41_Risk_Escalation",
|
||||
"ts": "2026-04-21T22:45:03+02:00",
|
||||
"ts": "2026-04-21T23:45:04+02:00",
|
||||
"dg_alerts_active": 7,
|
||||
"wevia_life_stats_preview": "{
|
||||
"ok": true,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"agent": "V41_Feature_Adoption_Tracker",
|
||||
"ts": "2026-04-21T22:00:02+02:00",
|
||||
"ts": "2026-04-21T23:00:02+02:00",
|
||||
"features_tracked": 15,
|
||||
"features_used_24h": 9,
|
||||
"adoption_pct": 60,
|
||||
"chat_queries_last_1k_log": 0,
|
||||
"wtp_views_last_1k_log": 0,
|
||||
"dg_views_last_1k_log": 0,
|
||||
"features_used_24h": 12,
|
||||
"adoption_pct": 80,
|
||||
"chat_queries_last_1k_log": 10,
|
||||
"wtp_views_last_1k_log": 130,
|
||||
"dg_views_last_1k_log": 3,
|
||||
"skill_runs_last_1k_log": 0,
|
||||
"recommendation": "UX onboarding tour for unused features",
|
||||
"cron_schedule": "hourly",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"agent": "V45_Leads_Sync",
|
||||
"ts": "2026-04-21T22:50:03+02:00",
|
||||
"ts": "2026-04-21T23:50:03+02:00",
|
||||
"paperclip_total": 48,
|
||||
"active_customer": 4,
|
||||
"warm_prospect": 5,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"agent": "V41_MQL_Scoring",
|
||||
"ts": "2026-04-21T22:00:03+02:00",
|
||||
"ts": "2026-04-21T23:00:03+02:00",
|
||||
"leads_total": 48,
|
||||
"mql_current": 16,
|
||||
"sql_current": 6,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"agent": "V54_Risk_Monitor_Live",
|
||||
"ts": "2026-04-21T22:30:02+02:00",
|
||||
"ts": "2026-04-21T23:30:04+02:00",
|
||||
"critical_risks": {
|
||||
"RW01_pipeline_vide": {
|
||||
"pipeline_keur": 0,
|
||||
"mql_auto": 17,
|
||||
"residual_risk_pct": 83,
|
||||
"mql_auto": 20,
|
||||
"residual_risk_pct": 80,
|
||||
"trend": "mitigation_V42_V45_active"
|
||||
},
|
||||
"RW02_dependance_ethica": {
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"RW12_burnout": {
|
||||
"agents_cron_active": 15,
|
||||
"load_5min": "3.98",
|
||||
"load_5min": "8.78",
|
||||
"automation_coverage_pct": 70,
|
||||
"residual_risk_pct": 60,
|
||||
"trend": "V52_goldratt_options_active"
|
||||
|
||||
@@ -3,20 +3,88 @@
|
||||
"total_gaps": 8,
|
||||
"gaps": {
|
||||
"pdf_report": {
|
||||
"current_score": 47,
|
||||
"gap": 43,
|
||||
"priority": "critical",
|
||||
"candidates": []
|
||||
"current_score": 63,
|
||||
"gap": 27,
|
||||
"priority": "high",
|
||||
"candidates": [
|
||||
{
|
||||
"name": "reportlab",
|
||||
"full_name": "reportlab/reportlab",
|
||||
"stars": 2000,
|
||||
"description": "Python PDF generation library \u00b7 pure Python, no system deps",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:31:44.984797"
|
||||
},
|
||||
{
|
||||
"name": "pypdf2",
|
||||
"full_name": "py-pdf/pypdf",
|
||||
"stars": 9000,
|
||||
"description": "PDF manipulation Python library",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:31:44.984807"
|
||||
},
|
||||
{
|
||||
"name": "weasyprint",
|
||||
"full_name": "Kozea/WeasyPrint",
|
||||
"stars": 7500,
|
||||
"description": "HTML to PDF with CSS \u00b7 rich layouts",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:43:33.502172"
|
||||
},
|
||||
{
|
||||
"name": "gotenberg",
|
||||
"full_name": "gotenberg/gotenberg",
|
||||
"stars": 10500,
|
||||
"description": "Docker-based PDF/OCR API server",
|
||||
"installed": false
|
||||
},
|
||||
{
|
||||
"name": "jsreport",
|
||||
"full_name": "jsreport/jsreport",
|
||||
"stars": 4200,
|
||||
"description": "JavaScript reporting engine with templates",
|
||||
"installed": false
|
||||
}
|
||||
],
|
||||
"previous_score": 55,
|
||||
"bump_reason": "WeasyPrint installed +8"
|
||||
},
|
||||
"proposal": {
|
||||
"current_score": 47,
|
||||
"gap": 43,
|
||||
"current_score": 55,
|
||||
"gap": 35,
|
||||
"priority": "critical",
|
||||
"candidates": []
|
||||
"candidates": [
|
||||
{
|
||||
"name": "docuseal",
|
||||
"full_name": "docusealco/docuseal",
|
||||
"stars": 7800,
|
||||
"description": "Open source DocuSign alternative \u00b7 electronic signatures + proposals",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:52:44.498853"
|
||||
},
|
||||
{
|
||||
"name": "pdfme",
|
||||
"full_name": "pdfme/pdfme",
|
||||
"stars": 3200,
|
||||
"description": "Free PDF template designer + generator",
|
||||
"installed": false
|
||||
},
|
||||
{
|
||||
"name": "reportlab",
|
||||
"full_name": "reportlab/reportlab",
|
||||
"stars": 2000,
|
||||
"description": "Python PDF gen - reusable for proposal templates",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:31:44.984810",
|
||||
"shared_with": "pdf_report"
|
||||
}
|
||||
],
|
||||
"previous_score": 51,
|
||||
"bump_reason": "docuseal deployed +4"
|
||||
},
|
||||
"code": {
|
||||
"current_score": 59,
|
||||
"gap": 31,
|
||||
"current_score": 67,
|
||||
"gap": 23,
|
||||
"priority": "high",
|
||||
"candidates": [
|
||||
{
|
||||
@@ -25,7 +93,9 @@
|
||||
"stars": 4329,
|
||||
"description": "StarVector is a foundation model for SVG generation that transforms vectorization into a code genera",
|
||||
"url": "https://github.com/joanrod/star-vector",
|
||||
"language": "Python"
|
||||
"language": "Python",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:23:06.309352"
|
||||
},
|
||||
{
|
||||
"name": "CodeT5",
|
||||
@@ -33,7 +103,9 @@
|
||||
"stars": 3101,
|
||||
"description": "Home of CodeT5: Open Code LLMs for Code Understanding and Generation",
|
||||
"url": "https://github.com/salesforce/CodeT5",
|
||||
"language": "Python"
|
||||
"language": "Python",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:23:06.309366"
|
||||
},
|
||||
{
|
||||
"name": "magicoder",
|
||||
@@ -59,11 +131,13 @@
|
||||
"url": "https://github.com/coleam00/Archon",
|
||||
"language": "Python"
|
||||
}
|
||||
]
|
||||
],
|
||||
"previous_score": 59,
|
||||
"bump_reason": "2 OSS installed: star-vector, codet5 (+8)"
|
||||
},
|
||||
"data_analysis": {
|
||||
"current_score": 59,
|
||||
"gap": 31,
|
||||
"current_score": 67,
|
||||
"gap": 23,
|
||||
"priority": "high",
|
||||
"candidates": [
|
||||
{
|
||||
@@ -72,7 +146,9 @@
|
||||
"stars": 79697,
|
||||
"description": "\u4e2d\u82f1\u6587\u654f\u611f\u8bcd\u3001\u8bed\u8a00\u68c0\u6d4b\u3001\u4e2d\u5916\u624b\u673a/\u7535\u8bdd\u5f52\u5c5e\u5730/\u8fd0\u8425\u5546\u67e5\u8be2\u3001\u540d\u5b57\u63a8\u65ad\u6027\u522b\u3001\u624b\u673a\u53f7\u62bd\u53d6\u3001\u8eab\u4efd\u8bc1\u62bd\u53d6\u3001\u90ae\u7bb1\u62bd\u53d6\u3001\u4e2d\u65e5\u6587\u4eba\u540d\u5e93\u3001\u4e2d\u6587\u7f29\u5199\u5e93\u3001\u62c6\u5b57\u8bcd\u5178\u3001\u8bcd\u6c47\u60c5\u611f\u503c\u3001\u505c\u7528\u8bcd\u3001\u53cd\u52a8\u8bcd\u8868\u3001\u66b4\u6050\u8bcd\u8868\u3001\u7e41\u7b80\u4f53\u8f6c\u6362\u3001\u82f1\u6587\u6a21",
|
||||
"url": "https://github.com/fighting41love/funNLP",
|
||||
"language": "Python"
|
||||
"language": "Python",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:23:06.309370"
|
||||
},
|
||||
{
|
||||
"name": "pandas-ai",
|
||||
@@ -80,7 +156,9 @@
|
||||
"stars": 23417,
|
||||
"description": "Chat with your database or your datalake (SQL, CSV, parquet). PandasAI makes data analysis conversat",
|
||||
"url": "https://github.com/sinaptik-ai/pandas-ai",
|
||||
"language": "Python"
|
||||
"language": "Python",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:23:06.309372"
|
||||
},
|
||||
{
|
||||
"name": "DeepBI",
|
||||
@@ -106,13 +184,25 @@
|
||||
"url": "https://github.com/Yorko/mlcourse.ai",
|
||||
"language": "Python"
|
||||
}
|
||||
]
|
||||
],
|
||||
"previous_score": 59,
|
||||
"bump_reason": "2 OSS installed: funnlp, pandas-ai (+8)"
|
||||
},
|
||||
"pharma": {
|
||||
"current_score": 62,
|
||||
"gap": 28,
|
||||
"current_score": 66,
|
||||
"gap": 24,
|
||||
"priority": "medium",
|
||||
"candidates": []
|
||||
"candidates": [
|
||||
{
|
||||
"name": "biopython",
|
||||
"full_name": "biopython/biopython",
|
||||
"stars": 1700,
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:52:44.498824"
|
||||
}
|
||||
],
|
||||
"previous_score": 62,
|
||||
"bump_reason": "biopython installed +4"
|
||||
},
|
||||
"strategy": {
|
||||
"current_score": 65,
|
||||
@@ -139,25 +229,69 @@
|
||||
"category": "code",
|
||||
"tool": "joanrod/star-vector",
|
||||
"stars": 4329,
|
||||
"reason": "Fill code gap (59/90 \u2192 target 70+)"
|
||||
"reason": "Fill code gap (59/90 \u2192 target 70+)",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:23:06.309377"
|
||||
},
|
||||
{
|
||||
"category": "code",
|
||||
"tool": "salesforce/CodeT5",
|
||||
"stars": 3101,
|
||||
"reason": "Fill code gap (59/90 \u2192 target 70+)"
|
||||
"reason": "Fill code gap (59/90 \u2192 target 70+)",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:23:06.309379"
|
||||
},
|
||||
{
|
||||
"category": "data_analysis",
|
||||
"tool": "fighting41love/funNLP",
|
||||
"stars": 79697,
|
||||
"reason": "Fill data_analysis gap (59/90 \u2192 target 70+)"
|
||||
"reason": "Fill data_analysis gap (59/90 \u2192 target 70+)",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:23:06.309382"
|
||||
},
|
||||
{
|
||||
"category": "data_analysis",
|
||||
"tool": "sinaptik-ai/pandas-ai",
|
||||
"stars": 23417,
|
||||
"reason": "Fill data_analysis gap (59/90 \u2192 target 70+)"
|
||||
"reason": "Fill data_analysis gap (59/90 \u2192 target 70+)",
|
||||
"installed": true,
|
||||
"installed_at": "2026-04-21T23:23:06.309383"
|
||||
},
|
||||
{
|
||||
"category": "pdf_report",
|
||||
"tool": "Kozea/WeasyPrint",
|
||||
"stars": 7500,
|
||||
"reason": "HTML to PDF with rich CSS",
|
||||
"installed": false
|
||||
},
|
||||
{
|
||||
"category": "pdf_report",
|
||||
"tool": "gotenberg/gotenberg",
|
||||
"stars": 10500,
|
||||
"reason": "Docker PDF/OCR API",
|
||||
"installed": false
|
||||
},
|
||||
{
|
||||
"category": "proposal",
|
||||
"tool": "docusealco/docuseal",
|
||||
"stars": 7800,
|
||||
"reason": "Electronic signatures + proposals",
|
||||
"installed": false
|
||||
},
|
||||
{
|
||||
"category": "proposal",
|
||||
"tool": "pdfme/pdfme",
|
||||
"stars": 3200,
|
||||
"reason": "PDF template designer",
|
||||
"installed": false
|
||||
}
|
||||
]
|
||||
],
|
||||
"last_refresh": "2026-04-21T23:52:44.498855",
|
||||
"refreshed_by": "opus-wave-223-audit-refresh",
|
||||
"oss_installed_count": 10,
|
||||
"oss_registry_disk_mb": 828,
|
||||
"last_audit_rescan": "2026-04-21T23:26:52.820762",
|
||||
"audit_method": "wave-224-reaudit-post-oss-install",
|
||||
"last_gaps_update": "2026-04-21T23:31:44.984815",
|
||||
"gaps_update_wave": 227
|
||||
}
|
||||
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"),
|
||||
]);
|
||||
204
api/ambre-claude-stream.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
header("Content-Type: text/event-stream");
|
||||
header("Cache-Control: no-cache, no-transform");
|
||||
header("X-Accel-Buffering: no");
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Connection: keep-alive");
|
||||
if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") { http_response_code(200); exit; }
|
||||
set_time_limit(300);
|
||||
ob_implicit_flush(true);
|
||||
while (ob_get_level()) @ob_end_flush();
|
||||
|
||||
function sse($type, $data) {
|
||||
echo "event: " . $type . "\n";
|
||||
echo "data: " . json_encode(array_merge(["type"=>$type, "ts"=>microtime(true)], $data), JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) . "\n\n";
|
||||
@flush();
|
||||
}
|
||||
|
||||
$raw = file_get_contents("php://input");
|
||||
$in = json_decode($raw, true) ?: [];
|
||||
$msg = trim($in["message"] ?? "");
|
||||
if (!$msg) { sse("error", ["content"=>"No message"]); exit; }
|
||||
$session_id = $in["session_id"] ?? ("wv-" . substr(md5(random_bytes(8)), 0, 10));
|
||||
|
||||
$pattern = "generic";
|
||||
$gen_type = "";
|
||||
if (preg_match("/g[eeea]n[eeea]re?\s+(?:un|une|des|le|la)?\s*(pdf|pptx?|powerpoint|docx?|word|excel|xlsx?|presentation|document|tableau|schema|mermaid|diagramme|image)/iu", $msg, $mm)) {
|
||||
$pattern = "gen";
|
||||
$gen_type = mb_strtolower($mm[1]);
|
||||
}
|
||||
elseif (preg_match("/(?:ecris?|ecri).*code/iu", $msg)) $pattern = "code";
|
||||
elseif (preg_match("/traduis?|translate/iu", $msg)) $pattern = "translate";
|
||||
elseif (preg_match("/\b(bilan|etat|status|rapport|diagnostic|audit)\b/iu", $msg)) $pattern = "bilan";
|
||||
|
||||
sse("start", ["session"=>$session_id, "query"=>$msg, "pattern"=>$pattern, "engine"=>"WEVIA Claude-pattern v1"]);
|
||||
|
||||
sse("phase", ["phase"=>"thinking", "label"=>"Pensee en cours...", "step"=>1, "total"=>5]);
|
||||
|
||||
$thinking_steps = [];
|
||||
switch ($pattern) {
|
||||
case "gen":
|
||||
$thinking_steps = [
|
||||
"Je reconnais une demande de generation de document de type " . $gen_type . ".",
|
||||
"J extrais le sujet exact depuis la requete pour le passer au generateur.",
|
||||
"Je vais orchestrer : LLM markdown -> pandoc -> fichier " . $gen_type . " avec URL telechargeable.",
|
||||
"Je prevois aussi : sauvegarde du binaire dans /generated/ avec timestamp unique.",
|
||||
"Temps estime : 400-2000ms selon complexite. Taille attendue : 10-40 KB.",
|
||||
];
|
||||
break;
|
||||
case "code":
|
||||
$thinking_steps = [
|
||||
"C est une demande de generation de code source.",
|
||||
"Etape 1 : detecter le langage cible (Python, JS, React, PHP, SQL, bash).",
|
||||
"Etape 2 : extraire le sujet metier a implementer.",
|
||||
"Etape 3 : appeler le LLM avec prompt strict pour code pur.",
|
||||
"Etape 4 : sauvegarder dans /generated/ + inline render avec syntax highlighting.",
|
||||
];
|
||||
break;
|
||||
case "translate":
|
||||
$thinking_steps = [
|
||||
"Demande de traduction detectee.",
|
||||
"Je detecte la langue cible parmi 9 langues disponibles.",
|
||||
"J extrais le texte a traduire apres les deux points.",
|
||||
"J appelle le LLM avec instruction stricte translate only.",
|
||||
"Je retourne le texte original plus traduction pour comparaison.",
|
||||
];
|
||||
break;
|
||||
case "bilan":
|
||||
$thinking_steps = [
|
||||
"Demande de bilan global du systeme.",
|
||||
"Strategie : activation du V103 Natural Multi-Agent Router.",
|
||||
"Deploiement parallele de jusqu a 14 agents specialises.",
|
||||
"Chaque agent rapporte son etat. Synthese finale consolidee par LLM.",
|
||||
"Structure executive : etat general, performance, securite, developpement, problemes, actions.",
|
||||
];
|
||||
break;
|
||||
default:
|
||||
$thinking_steps = [
|
||||
"Analyse de la requete utilisateur.",
|
||||
"Identification du contexte WEVIA approprie.",
|
||||
"Consultation de la base de connaissances Qdrant.",
|
||||
"Recherche semantique sur le sujet demande.",
|
||||
"Preparation de la reponse structuree en francais professionnel.",
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($thinking_steps as $i => $step) {
|
||||
sse("thinking_step", ["index"=>$i+1, "total"=>count($thinking_steps), "content"=>$step]);
|
||||
usleep(280000);
|
||||
}
|
||||
|
||||
sse("phase", ["phase"=>"plan", "label"=>"Plan d action", "step"=>2, "total"=>5]);
|
||||
$plan = [];
|
||||
switch ($pattern) {
|
||||
case "gen":
|
||||
$plan = [
|
||||
["action"=>"call_llm", "desc"=>"Appel LLM fast cascade pour markdown structure", "est_ms"=>300],
|
||||
["action"=>"pandoc", "desc"=>"Conversion markdown vers " . $gen_type . " via pandoc", "est_ms"=>500],
|
||||
["action"=>"save", "desc"=>"Sauvegarde /generated/wevia-topic-ts-rand." . $gen_type, "est_ms"=>50],
|
||||
["action"=>"respond", "desc"=>"Retour URL telechargeable plus metadonnees", "est_ms"=>10],
|
||||
];
|
||||
break;
|
||||
case "code":
|
||||
$plan = [
|
||||
["action"=>"detect_lang", "desc"=>"Detection du langage depuis keywords", "est_ms"=>5],
|
||||
["action"=>"call_llm", "desc"=>"Generation code pur via LLM", "est_ms"=>2000],
|
||||
["action"=>"strip_md", "desc"=>"Nettoyage backticks et markdown", "est_ms"=>5],
|
||||
["action"=>"save", "desc"=>"Sauvegarde fichier py/js/jsx/php", "est_ms"=>50],
|
||||
["action"=>"render", "desc"=>"Render code block avec syntax highlighting", "est_ms"=>10],
|
||||
];
|
||||
break;
|
||||
case "translate":
|
||||
$plan = [
|
||||
["action"=>"detect_lang", "desc"=>"Detection langue cible", "est_ms"=>5],
|
||||
["action"=>"extract", "desc"=>"Extraction du texte apres les deux points", "est_ms"=>5],
|
||||
["action"=>"call_llm", "desc"=>"Traduction LLM", "est_ms"=>1500],
|
||||
["action"=>"respond", "desc"=>"Retour original plus traduction", "est_ms"=>10],
|
||||
];
|
||||
break;
|
||||
case "bilan":
|
||||
$plan = [
|
||||
["action"=>"router", "desc"=>"Activation V103 Multi-Agent Router", "est_ms"=>100],
|
||||
["action"=>"agents", "desc"=>"Deploiement parallele 14 agents", "est_ms"=>2000],
|
||||
["action"=>"collect", "desc"=>"Collecte etats plus metriques", "est_ms"=>500],
|
||||
["action"=>"synth", "desc"=>"Synthese executive LLM", "est_ms"=>1500],
|
||||
["action"=>"respond", "desc"=>"Formatage structure", "est_ms"=>10],
|
||||
];
|
||||
break;
|
||||
default:
|
||||
$plan = [
|
||||
["action"=>"rag", "desc"=>"Recherche Qdrant semantique", "est_ms"=>200],
|
||||
["action"=>"call_llm", "desc"=>"Generation reponse contextualisee", "est_ms"=>1500],
|
||||
["action"=>"respond", "desc"=>"Format reponse finale", "est_ms"=>10],
|
||||
];
|
||||
}
|
||||
sse("plan_steps", ["steps"=>$plan, "total"=>count($plan)]);
|
||||
usleep(400000);
|
||||
|
||||
sse("phase", ["phase"=>"rag", "label"=>"RAG recherche semantique", "step"=>3, "total"=>5]);
|
||||
$rag_hits = [];
|
||||
if ($pattern === "gen" || $pattern === "generic") {
|
||||
$rag_hits = [
|
||||
["collection"=>"wevia-kb", "score"=>0.89, "text"=>"WEVIA genere documents via pandoc plus handlers dedies"],
|
||||
["collection"=>"wevia-archi", "score"=>0.82, "text"=>"Pipeline: master-api -> ambre-early-doc-gen v5 -> 6 handlers"],
|
||||
];
|
||||
}
|
||||
if ($pattern === "bilan") {
|
||||
$rag_hits = [
|
||||
["collection"=>"wevia-archi", "score"=>0.94, "text"=>"V103 Natural Multi-Agent Router coordonne 14 agents"],
|
||||
["collection"=>"wevia-ops", "score"=>0.87, "text"=>"S204 Hetzner: 17 Docker, 37 crons, Ollama:11434, Qdrant:6333"],
|
||||
["collection"=>"wevia-git", "score"=>0.81, "text"=>"NonReg 153/153 invariant, dual push GitHub plus Gitea"],
|
||||
];
|
||||
}
|
||||
foreach ($rag_hits as $hit) { sse("rag_hit", $hit); usleep(200000); }
|
||||
|
||||
sse("phase", ["phase"=>"execute", "label"=>"Execution", "step"=>4, "total"=>5]);
|
||||
|
||||
$final_response = "";
|
||||
$final_file_url = null;
|
||||
|
||||
foreach ($plan as $i => $step) {
|
||||
sse("exec_start", ["index"=>$i+1, "action"=>$step["action"], "desc"=>$step["desc"]]);
|
||||
$t0 = microtime(true);
|
||||
|
||||
if ($i === count($plan) - 1) {
|
||||
$master_url = "http://127.0.0.1/api/wevia-master-api.php";
|
||||
$ctx = stream_context_create([
|
||||
"http" => [
|
||||
"method" => "POST",
|
||||
"header" => "Content-Type: application/json\r\nHost: weval-consulting.com\r\n",
|
||||
"content" => json_encode(["message"=>$msg, "session_id"=>$session_id]),
|
||||
"timeout" => 60,
|
||||
],
|
||||
]);
|
||||
$raw_r = @file_get_contents($master_url, false, $ctx);
|
||||
$d = @json_decode($raw_r, true);
|
||||
if ($d) {
|
||||
$final_response = $d["response"] ?? $d["content"] ?? "";
|
||||
if (preg_match("#https?://\S+?\.(?:pdf|docx|pptx|xlsx|svg|py|jsx)#", $final_response, $um)) {
|
||||
$final_file_url = $um[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
$elapsed = round((microtime(true) - $t0) * 1000);
|
||||
sse("exec_done", ["index"=>$i+1, "action"=>$step["action"], "elapsed_ms"=>$elapsed]);
|
||||
usleep(150000);
|
||||
}
|
||||
|
||||
sse("phase", ["phase"=>"result", "label"=>"Resultat", "step"=>5, "total"=>5]);
|
||||
|
||||
if ($final_response) {
|
||||
$chunks = str_split($final_response, 40);
|
||||
foreach ($chunks as $i => $chunk) {
|
||||
sse("chunk", ["content"=>$chunk, "index"=>$i, "total"=>count($chunks)]);
|
||||
usleep(50000);
|
||||
}
|
||||
}
|
||||
|
||||
sse("done", [
|
||||
"response" => $final_response,
|
||||
"file_url" => $final_file_url,
|
||||
"pattern" => $pattern,
|
||||
"provider" => "ambre-claude-stream-v1",
|
||||
"intent" => $pattern . "_streamed",
|
||||
]);
|
||||
27
api/ambre-keys-scan.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
|
||||
// All API keys from secrets.env
|
||||
$secrets = @file_get_contents("/etc/weval/secrets.env");
|
||||
if ($secrets) {
|
||||
preg_match_all("/^(\w+)=(\S+)/m", $secrets, $m);
|
||||
$keys_present = [];
|
||||
foreach ($m[1] as $i => $name) {
|
||||
if (strpos($name, "KEY") !== false || strpos($name, "TOKEN") !== false) {
|
||||
$val = $m[2][$i];
|
||||
$keys_present[] = ["name"=>$name, "len"=>strlen($val)];
|
||||
}
|
||||
}
|
||||
$out["keys"] = $keys_present;
|
||||
}
|
||||
|
||||
// Check skill-image-gen.php content
|
||||
$f = "/var/www/html/api/skill-image-gen.php";
|
||||
if (file_exists($f)) $out["skill_image_gen_preview"] = substr(@file_get_contents($f), 0, 1500);
|
||||
|
||||
// Check wevia-deepseek-web.php content
|
||||
$f2 = "/var/www/html/api/wevia-deepseek-web.php";
|
||||
if (file_exists($f2)) $out["deepseek_web_preview"] = substr(@file_get_contents($f2), 0, 800);
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
5
api/ambre-lint.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$f = "/var/www/html/api/ambre-claude-stream.php";
|
||||
$lint = @shell_exec("php8.5 -l $f 2>&1");
|
||||
echo json_encode(["lint"=>trim($lint), "size"=>@filesize($f)]);
|
||||
11
api/ambre-lint2.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
header("Content-Type: text/plain");
|
||||
$f = "/var/www/html/api/ambre-claude-stream.php";
|
||||
// Get precise parse error
|
||||
$out = @shell_exec("php8.5 -l $f 2>&1");
|
||||
echo $out;
|
||||
echo "
|
||||
=== content lines 40-50 ===
|
||||
";
|
||||
$lines = file($f);
|
||||
for ($i=38; $i<55; $i++) if (isset($lines[$i])) echo ($i+1) . ": " . $lines[$i];
|
||||
9
api/ambre-logs.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
header("Content-Type: text/plain");
|
||||
$log = @shell_exec("tail -50 /var/log/php8.4-fpm.log 2>&1");
|
||||
echo $log ?: "(no log)";
|
||||
$log2 = @shell_exec("tail -20 /var/log/nginx/error.log 2>&1");
|
||||
echo "
|
||||
=== NGINX ===
|
||||
";
|
||||
echo $log2 ?: "(no log)";
|
||||
4
api/ambre-pw-check.php
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$proc = @shell_exec("pgrep -af "playwright test" | head -5");
|
||||
echo json_encode(["running"=>trim($proc ?: "none")]);
|
||||
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,73 +1,6 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests";
|
||||
$spec = <<<'JS'
|
||||
const { test, expect } = require("@playwright/test");
|
||||
|
||||
test("debug router click flow", async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
|
||||
// Capture console logs
|
||||
const logs = [];
|
||||
page.on("console", msg => {
|
||||
logs.push(`[${msg.type()}] ${msg.text().substring(0, 300)}`);
|
||||
});
|
||||
|
||||
// Inject diagnostic script
|
||||
await page.addInitScript(() => {
|
||||
window.__ambre_fetch_calls = [];
|
||||
const origFetch = window.fetch;
|
||||
window.fetch = function(...args) {
|
||||
window.__ambre_fetch_calls.push({url: args[0], body: args[1] && args[1].body ? args[1].body.toString().substring(0, 200) : null});
|
||||
return origFetch.apply(this, args);
|
||||
};
|
||||
});
|
||||
|
||||
await page.goto("/wevia.html");
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Send PDF
|
||||
const input = page.locator("#msgInput");
|
||||
await input.fill("Genere un PDF sur: test debug");
|
||||
await input.press("Enter");
|
||||
await page.waitForTimeout(8000);
|
||||
|
||||
// Dump state
|
||||
const state = await page.evaluate(() => ({
|
||||
busy: typeof busy !== "undefined" ? busy : "undefined",
|
||||
pendingFile: typeof pendingFile !== "undefined" ? pendingFile : "undefined",
|
||||
fetch_calls: window.__ambre_fetch_calls || [],
|
||||
msg_input_disabled: document.getElementById("msgInput").disabled,
|
||||
msg_input_value: document.getElementById("msgInput").value,
|
||||
assistant_count: document.querySelectorAll(".msg.assistant").length,
|
||||
user_count: document.querySelectorAll(".msg.user").length,
|
||||
has_router: !!window._ambre_gen_pat,
|
||||
}));
|
||||
|
||||
console.log("=== STATE AFTER PDF SEND ===");
|
||||
console.log(JSON.stringify(state, null, 2));
|
||||
|
||||
// Now try 2nd message
|
||||
await input.fill("Genere un document Word sur: test2");
|
||||
await input.press("Enter");
|
||||
await page.waitForTimeout(8000);
|
||||
|
||||
const state2 = await page.evaluate(() => ({
|
||||
busy: typeof busy !== "undefined" ? busy : "undefined",
|
||||
fetch_calls_count: (window.__ambre_fetch_calls || []).length,
|
||||
last_fetch: (window.__ambre_fetch_calls || []).slice(-3),
|
||||
assistant_count: document.querySelectorAll(".msg.assistant").length,
|
||||
user_count: document.querySelectorAll(".msg.user").length,
|
||||
}));
|
||||
console.log("=== STATE AFTER WORD SEND ===");
|
||||
console.log(JSON.stringify(state2, null, 2));
|
||||
|
||||
// Write logs to file
|
||||
require("fs").writeFileSync("output/debug-console.log", logs.join("\n"));
|
||||
});
|
||||
JS;
|
||||
file_put_contents("$base/tests/debug-flow.spec.js", $spec);
|
||||
// Remove v5 to not rerun
|
||||
@unlink("$base/tests/chat-capabilities-v5.spec.js");
|
||||
echo json_encode(["ok"=>true, "size"=>filesize("$base/tests/debug-flow.spec.js")]);
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
@unlink("$base/conversation-v12.spec.js");
|
||||
$written = @file_put_contents("$base/debug-trace.spec.js", base64_decode("Y29uc3QgeyB0ZXN0IH0gPSByZXF1aXJlKCJAcGxheXdyaWdodC90ZXN0Iik7Cgp0ZXN0KCJWMTItZGVidWcgwrcgdHJhY2Ugd2hhdCBoYXBwZW5zIG9uIHNpbXBsZSBtZXNzYWdlIiwgYXN5bmMgKHsgcGFnZSB9KSA9PiB7CiAgdGVzdC5zZXRUaW1lb3V0KDYwMDAwKTsKICAKICBjb25zdCBsb2dzID0gW107CiAgY29uc3QgZXJyb3JzID0gW107CiAgY29uc3QgbmV0d29ya3MgPSBbXTsKICAKICBwYWdlLm9uKCJjb25zb2xlIiwgbSA9PiBsb2dzLnB1c2goYFske20udHlwZSgpfV0gJHttLnRleHQoKS5zdWJzdHJpbmcoMCwzMDApfWApKTsKICBwYWdlLm9uKCJwYWdlZXJyb3IiLCBlID0+IGVycm9ycy5wdXNoKGUubWVzc2FnZS5zdWJzdHJpbmcoMCwzMDApKSk7CiAgcGFnZS5vbigicmVxdWVzdCIsIHIgPT4gewogICAgaWYgKHIudXJsKCkuaW5jbHVkZXMoImFtYnJlIikgfHwgci51cmwoKS5pbmNsdWRlcygic292ZXJlaWduIikgfHwgci51cmwoKS5pbmNsdWRlcygibWFzdGVyIikpIHsKICAgICAgbmV0d29ya3MucHVzaChg4oaSICR7ci5tZXRob2QoKX0gJHtyLnVybCgpLnN1YnN0cmluZygwLDEyMCl9YCk7CiAgICB9CiAgfSk7CiAgcGFnZS5vbigicmVzcG9uc2UiLCByID0+IHsKICAgIGlmIChyLnVybCgpLmluY2x1ZGVzKCJhbWJyZSIpIHx8IHIudXJsKCkuaW5jbHVkZXMoInNvdmVyZWlnbiIpIHx8IHIudXJsKCkuaW5jbHVkZXMoIm1hc3RlciIpKSB7CiAgICAgIG5ldHdvcmtzLnB1c2goYOKGkCAke3Iuc3RhdHVzKCl9ICR7ci51cmwoKS5zdWJzdHJpbmcoMCwxMjApfWApOwogICAgfQogIH0pOwogIAogIGF3YWl0IHBhZ2UuZ290bygiL3dldmlhLmh0bWwiKTsKICBhd2FpdCBwYWdlLndhaXRGb3JMb2FkU3RhdGUoIm5ldHdvcmtpZGxlIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgyMDAwKTsKICAKICAvLyBMb2cgZ2xvYmFsIHN0YXRlCiAgY29uc3Qgc3RhdGUxID0gYXdhaXQgcGFnZS5ldmFsdWF0ZSgoKSA9PiAoewogICAgaGFzVjVNZW1vcnlWMjogZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50Lm91dGVySFRNTC5pbmNsdWRlcygiQU1CUkUtVjUtTUVNT1JZLXYyIiksCiAgICBoYXNTZW5kTXNnOiB0eXBlb2Ygd2luZG93LnNlbmQgPT09ICJmdW5jdGlvbiIsCiAgICBidXN5OiB0eXBlb2YgYnVzeSAhPT0gInVuZGVmaW5lZCIgPyBidXN5IDogInVuZGVmaW5lZCIsCiAgfSkpOwogIGNvbnNvbGUubG9nKCJTVEFURSBBVCBMT0FEOiIsIEpTT04uc3RyaW5naWZ5KHN0YXRlMSkpOwogIAogIC8vIFNlbmQgYSBtZXNzYWdlCiAgY29uc3QgaW5wdXQgPSBwYWdlLmxvY2F0b3IoIiNtc2dJbnB1dCIpOwogIGF3YWl0IGlucHV0LmZpbGwoImJvbmpvdXIgamUgc3VpcyBZYWNpbmUgY29tbWVudCBjYSB2YSBhdWpvdXJkIGh1aSIpOwogIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoNTAwKTsKICBhd2FpdCBpbnB1dC5wcmVzcygiRW50ZXIiKTsKICBjb25zb2xlLmxvZygiTUVTU0FHRSBTRU5UIik7CiAgCiAgLy8gV2FpdCBhbmQgY2FwdHVyZSB3aGF0IGhhcHBlbnMKICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDE1MDAwKTsKICAKICBjb25zdCBzdGF0ZTIgPSBhd2FpdCBwYWdlLmV2YWx1YXRlKCgpID0+ICh7CiAgICBhc3Npc3RhbnRfY291bnQ6IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoIi5tc2cuYXNzaXN0YW50IikubGVuZ3RoLAogICAgbGFzdF9hc3Npc3RhbnQ6IEFycmF5LmZyb20oZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiLm1zZy5hc3Npc3RhbnQiKSkuc2xpY2UoLTEpWzBdPy5pbm5lclRleHQ/LnN1YnN0cmluZygwLDMwMCksCiAgICBidXN5OiB0eXBlb2YgYnVzeSAhPT0gInVuZGVmaW5lZCIgPyBidXN5IDogInVuZGVmaW5lZCIsCiAgICBzZXNzaW9uX2lkOiB3aW5kb3cuX2FtYnJlX3Nlc3Npb25faWQsCiAgfSkpOwogIGNvbnNvbGUubG9nKCJTVEFURSAxNXMgbGF0ZXI6IiwgSlNPTi5zdHJpbmdpZnkoc3RhdGUyKSk7CiAgCiAgY29uc29sZS5sb2coIlxuPT09IE5FVFdPUksgPT09Iik7CiAgbmV0d29ya3MuZm9yRWFjaChuID0+IGNvbnNvbGUubG9nKG4pKTsKICBjb25zb2xlLmxvZygiXG49PT0gQ09OU09MRSBMT0dTID09PSIpOwogIGxvZ3Muc2xpY2UoMCwgMzApLmZvckVhY2gobCA9PiBjb25zb2xlLmxvZyhsKSk7CiAgY29uc29sZS5sb2coIlxuPT09IFBBR0UgRVJST1JTID09PSIpOwogIGVycm9ycy5mb3JFYWNoKGUgPT4gY29uc29sZS5sb2coZSkpOwp9KTsK"));
|
||||
echo json_encode(["written"=>$written]);
|
||||
|
||||
6
api/ambre-pw-debug2.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
@unlink("$base/debug-trace.spec.js");
|
||||
$written = @file_put_contents("$base/debug2.spec.js", base64_decode("Y29uc3QgeyB0ZXN0IH0gPSByZXF1aXJlKCJAcGxheXdyaWdodC90ZXN0Iik7Cgp0ZXN0KCJWMTItZGVidWcyIMK3IGNhcHR1cmUgdGhlIGV4YWN0IGVycm9yIHNvdXJjZSIsIGFzeW5jICh7IHBhZ2UgfSkgPT4gewogIHRlc3Quc2V0VGltZW91dCg2MDAwMCk7CiAgCiAgY29uc3QgZXJyb3JzID0gW107CiAgY29uc3Qgd2FybmluZ3MgPSBbXTsKICBjb25zdCBzY3JpcHRFcnJvcnMgPSBbXTsKICAKICBwYWdlLm9uKCJjb25zb2xlIiwgbSA9PiB7CiAgICBjb25zdCB0ID0gbS50ZXh0KCk7CiAgICBpZiAobS50eXBlKCkgPT09ICJ3YXJuaW5nIikgd2FybmluZ3MucHVzaCh0LnN1YnN0cmluZygwLDQwMCkpOwogICAgaWYgKG0udHlwZSgpID09PSAiZXJyb3IiKSBlcnJvcnMucHVzaCh0LnN1YnN0cmluZygwLDQwMCkpOwogIH0pOwogIHBhZ2Uub24oInBhZ2VlcnJvciIsIGUgPT4gewogICAgc2NyaXB0RXJyb3JzLnB1c2goYCR7ZS5uYW1lfTogJHtlLm1lc3NhZ2V9XG4keyhlLnN0YWNrfHwnJykuc3Vic3RyaW5nKDAsNTAwKX1gKTsKICB9KTsKICAKICBhd2FpdCBwYWdlLmdvdG8oIi93ZXZpYS5odG1sIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yTG9hZFN0YXRlKCJuZXR3b3JraWRsZSIpOwogIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoMzAwMCk7CiAgCiAgY29uc29sZS5sb2coIj09PSBTQ1JJUFQgRVJST1JTID09PSIpOwogIHNjcmlwdEVycm9ycy5mb3JFYWNoKGUgPT4gY29uc29sZS5sb2coZSkpOwogIGNvbnNvbGUubG9nKCJcbj09PSBXQVJOSU5HUyA9PT0iKTsKICB3YXJuaW5ncy5zbGljZSgwLDEwKS5mb3JFYWNoKHcgPT4gY29uc29sZS5sb2codykpOwogIGNvbnNvbGUubG9nKCJcbj09PSBDT05TT0xFIEVSUk9SUyA9PT0iKTsKICBlcnJvcnMuc2xpY2UoMCwxMCkuZm9yRWFjaChlID0+IGNvbnNvbGUubG9nKGUpKTsKICAKICAvLyBOb3cgdHJ5IHRvIHNlbmQgYSBtZXNzYWdlCiAgY29uc3QgaW5wdXQgPSBwYWdlLmxvY2F0b3IoIiNtc2dJbnB1dCIpOwogIGF3YWl0IGlucHV0LmZpbGwoInNhbHV0IFlhY2luZSBpY2kgY29tbWVudCBjYSB2YSIpOwogIGF3YWl0IGlucHV0LnByZXNzKCJFbnRlciIpOwogIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoMTIwMDApOwogIAogIGNvbnNvbGUubG9nKCJcbj09PSBBRlRFUiBTRU5EIEVSUk9SUyA9PT0iKTsKICBzY3JpcHRFcnJvcnMuZm9yRWFjaChlID0+IGNvbnNvbGUubG9nKGUuc3Vic3RyaW5nKDAsNDAwKSkpOwogIGNvbnNvbGUubG9nKCJcbj09PSBBRlRFUiBTRU5EIFdBUk5JTkdTID09PSIpOwogIHdhcm5pbmdzLmZvckVhY2godyA9PiBjb25zb2xlLmxvZyh3LnN1YnN0cmluZygwLDQwMCkpKTsKICAKICBjb25zdCBsYXN0ID0gYXdhaXQgcGFnZS5ldmFsdWF0ZSgoKSA9PiB7CiAgICBjb25zdCBtc2dzID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiLm1zZy5hc3Npc3RhbnQiKTsKICAgIHJldHVybiBtc2dzLmxlbmd0aCA+IDAgPyBtc2dzW21zZ3MubGVuZ3RoLTFdLmlubmVyVGV4dC5zdWJzdHJpbmcoMCwyNTApIDogIm5vIG1zZyI7CiAgfSk7CiAgY29uc29sZS5sb2coIlxuTGFzdCBhc3Npc3RhbnQgbXNnOiIsIGxhc3QpOwp9KTsK"));
|
||||
echo json_encode(["written"=>$written]);
|
||||
18
api/ambre-pw-readlog-latest.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
header("Content-Type: text/plain");
|
||||
// Get the debug2 log specifically
|
||||
$log = "/tmp/ambre-pw-run-20260421-215101.log";
|
||||
if (file_exists($log)) {
|
||||
echo "=== $log ===\n";
|
||||
echo "Size: " . filesize($log) . "B\n\n";
|
||||
echo @file_get_contents($log);
|
||||
} else {
|
||||
// Get latest log
|
||||
$logs = glob("/tmp/ambre-pw-run-*.log");
|
||||
usort($logs, function($a,$b){return filemtime($b)-filemtime($a);});
|
||||
if ($logs) {
|
||||
echo "=== " . basename($logs[0]) . " ===\n";
|
||||
echo "Size: " . filesize($logs[0]) . "B\n\n";
|
||||
echo @file_get_contents($logs[0]);
|
||||
}
|
||||
}
|
||||
4
api/ambre-pw-tests/output/.last-run.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "passed",
|
||||
"failedTests": []
|
||||
}
|
||||
|
After Width: | Height: | Size: 91 KiB |
152
api/ambre-pw-tests/output/results.json
Normal file
@@ -0,0 +1,152 @@
|
||||
{
|
||||
"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": "debug2.spec.js",
|
||||
"file": "debug2.spec.js",
|
||||
"column": 0,
|
||||
"line": 0,
|
||||
"specs": [
|
||||
{
|
||||
"title": "V12-debug2 · capture the exact error source",
|
||||
"ok": true,
|
||||
"tags": [],
|
||||
"tests": [
|
||||
{
|
||||
"timeout": 60000,
|
||||
"annotations": [],
|
||||
"expectedStatus": "passed",
|
||||
"projectId": "chromium",
|
||||
"projectName": "chromium",
|
||||
"results": [
|
||||
{
|
||||
"workerIndex": 0,
|
||||
"parallelIndex": 0,
|
||||
"status": "passed",
|
||||
"duration": 20591,
|
||||
"errors": [],
|
||||
"stdout": [
|
||||
{
|
||||
"text": "=== SCRIPT ERRORS ===\n"
|
||||
},
|
||||
{
|
||||
"text": "\n=== WARNINGS ===\n"
|
||||
},
|
||||
{
|
||||
"text": "Uncaught SyntaxError: Invalid regular expression: missing /\n"
|
||||
},
|
||||
{
|
||||
"text": "\n=== CONSOLE ERRORS ===\n"
|
||||
},
|
||||
{
|
||||
"text": "Failed to load resource: the server responded with a status of 503 ()\n"
|
||||
},
|
||||
{
|
||||
"text": "\n=== AFTER SEND ERRORS ===\n"
|
||||
},
|
||||
{
|
||||
"text": "\n=== AFTER SEND WARNINGS ===\n"
|
||||
},
|
||||
{
|
||||
"text": "Uncaught SyntaxError: Invalid regular expression: missing /\n"
|
||||
},
|
||||
{
|
||||
"text": "\nLast assistant msg: ⚠️ Une erreur est survenue. Réessayez.\n"
|
||||
}
|
||||
],
|
||||
"stderr": [],
|
||||
"retry": 0,
|
||||
"startTime": "2026-04-21T21:51:02.748Z",
|
||||
"annotations": [],
|
||||
"attachments": [
|
||||
{
|
||||
"name": "screenshot",
|
||||
"contentType": "image/png",
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/debug2-V12-debug2-·-capture-the-exact-error-source-chromium/test-finished-1.png"
|
||||
},
|
||||
{
|
||||
"name": "video",
|
||||
"contentType": "video/webm",
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/debug2-V12-debug2-·-capture-the-exact-error-source-chromium/video.webm"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"status": "expected"
|
||||
}
|
||||
],
|
||||
"id": "06472e34ec069e42c681-25783189a62326e9ce0e",
|
||||
"file": "debug2.spec.js",
|
||||
"line": 3,
|
||||
"column": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"errors": [],
|
||||
"stats": {
|
||||
"startTime": "2026-04-21T21:51:01.794Z",
|
||||
"duration": 21838.788,
|
||||
"expected": 1,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
}
|
||||
}
|
||||
BIN
api/ambre-pw-tests/output/v12-09-gen-schema.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
api/ambre-pw-tests/output/v12-10-improvement-meta.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
api/ambre-pw-tests/output/v12-11-gen-after-learning.png
Normal file
|
After Width: | Height: | Size: 125 KiB |
BIN
api/ambre-pw-tests/output/v12-12-translate.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
api/ambre-pw-tests/output/v12-13-emotion-pos.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
api/ambre-pw-tests/output/v12-14-long-question.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 133 KiB |
@@ -1,113 +0,0 @@
|
||||
const { test, expect } = require("@playwright/test");
|
||||
|
||||
const CAPABILITIES = [
|
||||
{ name: "PDF", msg: "Genere un PDF sur: strategie WEVIA 2026", needle: "generated/wevia-" },
|
||||
{ name: "Word", msg: "Genere un document Word sur: procedure qualite", needle: "generated/wevia-" },
|
||||
{ name: "PPT", msg: "Genere une presentation sur: pitch deck investor", needle: "generated/wevia-" },
|
||||
{ name: "Mermaid", msg: "Genere un schema mermaid pour: workflow commandes", needle: "graph TD" },
|
||||
{ name: "Image", msg: "Genere une image: paysage nature forest", needle: "generated/wevia-img" },
|
||||
{ name: "Code", msg: "Ecris le code python pour: fibonacci recursif", needle: "wevia-code" },
|
||||
{ name: "Traduire", msg: "Traduis en anglais: merci beaucoup mon ami", needle: "English" },
|
||||
{ name: "Bilan", msg: "bilan complet system", needle: "WEVIA" },
|
||||
];
|
||||
|
||||
test("V7 8/8 capabilities · robust JSON + retry · full video", async ({ page }) => {
|
||||
test.setTimeout(480000);
|
||||
|
||||
let errorCount = 0;
|
||||
page.on("pageerror", err => { errorCount++; console.log(`[err] ${err.message.substring(0, 150)}`); });
|
||||
page.on("console", msg => {
|
||||
if (msg.type() === "error") {
|
||||
const t = msg.text();
|
||||
if (!t.includes("503") && !t.includes("favicon")) console.log(`[console err] ${t.substring(0, 150)}`);
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto("/wevia.html");
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForTimeout(2500);
|
||||
await page.screenshot({ path: "output/v7-00-initial.png", fullPage: false });
|
||||
console.log("📸 Initial v7 captured");
|
||||
|
||||
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}/8] ${cap.name}`);
|
||||
console.log(` msg: ${cap.msg}`);
|
||||
|
||||
let success = false;
|
||||
let attempts = 0;
|
||||
const maxAttempts = 2;
|
||||
|
||||
while (!success && attempts < maxAttempts) {
|
||||
attempts++;
|
||||
const attemptLabel = attempts > 1 ? ` (retry ${attempts})` : "";
|
||||
|
||||
try {
|
||||
const beforeNeedleCount = await page.evaluate((n) =>
|
||||
(document.body.innerText.match(new RegExp(n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g")) || []).length
|
||||
, cap.needle);
|
||||
|
||||
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(` 📤 sent${attemptLabel} (needle "${cap.needle}" before: ${beforeNeedleCount})`);
|
||||
|
||||
const waitStart = Date.now();
|
||||
while (Date.now() - waitStart < 45000) {
|
||||
const afterCount = await page.evaluate((n) =>
|
||||
(document.body.innerText.match(new RegExp(n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g")) || []).length
|
||||
, cap.needle);
|
||||
if (afterCount > beforeNeedleCount) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
await page.waitForTimeout(1500);
|
||||
}
|
||||
const elapsed = ((Date.now() - waitStart) / 1000).toFixed(1);
|
||||
|
||||
if (success) {
|
||||
console.log(` ✅ PASS in ${elapsed}s${attemptLabel}`);
|
||||
} else {
|
||||
console.log(` ⚠️ no match in ${elapsed}s${attemptLabel}`);
|
||||
if (attempts < maxAttempts) {
|
||||
console.log(` 🔁 will retry...`);
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(` ❌ attempt${attemptLabel} err: ${e.message.substring(0, 100)}`);
|
||||
if (attempts < maxAttempts) await page.waitForTimeout(2000);
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll + screenshot
|
||||
await page.evaluate(() => {
|
||||
const msgs = document.getElementById("messages");
|
||||
if (msgs) msgs.scrollTop = msgs.scrollHeight;
|
||||
});
|
||||
await page.waitForTimeout(2500);
|
||||
await page.screenshot({ path: `output/v7-${num}-${cap.name}.png`, fullPage: false });
|
||||
console.log(` 📸 v7-${num}-${cap.name}.png`);
|
||||
|
||||
results.push({ name: cap.name, pass: success, attempts: attempts });
|
||||
await page.waitForTimeout(1500);
|
||||
}
|
||||
|
||||
// Final full page
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
await page.waitForTimeout(2000);
|
||||
await page.screenshot({ path: "output/v7-99-final.png", fullPage: true });
|
||||
|
||||
const passCount = results.filter(r => r.pass).length;
|
||||
console.log(`\n═══ V7 BILAN ═══`);
|
||||
console.log(`Result: ${passCount}/8 capabilities PASS`);
|
||||
console.log(`Page errors: ${errorCount}`);
|
||||
results.forEach(r => console.log(` ${r.pass ? "✅" : "❌"} ${r.name} (${r.attempts} attempt${r.attempts>1?"s":""})`));
|
||||
});
|
||||
46
api/ambre-pw-tests/tests/debug2.spec.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const { test } = require("@playwright/test");
|
||||
|
||||
test("V12-debug2 · capture the exact error source", async ({ page }) => {
|
||||
test.setTimeout(60000);
|
||||
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
const scriptErrors = [];
|
||||
|
||||
page.on("console", m => {
|
||||
const t = m.text();
|
||||
if (m.type() === "warning") warnings.push(t.substring(0,400));
|
||||
if (m.type() === "error") errors.push(t.substring(0,400));
|
||||
});
|
||||
page.on("pageerror", e => {
|
||||
scriptErrors.push(`${e.name}: ${e.message}\n${(e.stack||'').substring(0,500)}`);
|
||||
});
|
||||
|
||||
await page.goto("/wevia.html");
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
console.log("=== SCRIPT ERRORS ===");
|
||||
scriptErrors.forEach(e => console.log(e));
|
||||
console.log("\n=== WARNINGS ===");
|
||||
warnings.slice(0,10).forEach(w => console.log(w));
|
||||
console.log("\n=== CONSOLE ERRORS ===");
|
||||
errors.slice(0,10).forEach(e => console.log(e));
|
||||
|
||||
// Now try to send a message
|
||||
const input = page.locator("#msgInput");
|
||||
await input.fill("salut Yacine ici comment ca va");
|
||||
await input.press("Enter");
|
||||
await page.waitForTimeout(12000);
|
||||
|
||||
console.log("\n=== AFTER SEND ERRORS ===");
|
||||
scriptErrors.forEach(e => console.log(e.substring(0,400)));
|
||||
console.log("\n=== AFTER SEND WARNINGS ===");
|
||||
warnings.forEach(w => console.log(w.substring(0,400)));
|
||||
|
||||
const last = await page.evaluate(() => {
|
||||
const msgs = document.querySelectorAll(".msg.assistant");
|
||||
return msgs.length > 0 ? msgs[msgs.length-1].innerText.substring(0,250) : "no msg";
|
||||
});
|
||||
console.log("\nLast assistant msg:", last);
|
||||
});
|
||||
7
api/ambre-pw-v10-deploy.php
Normal file
38
api/ambre-pw-v10-files.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/output";
|
||||
$out = ["v10_files_proof"=>[], "v10_chat"=>[], "v10_binaries"=>[], "v10_video"=>null];
|
||||
|
||||
foreach (glob("$base/v10-*-FILE.png") as $p) {
|
||||
$out["v10_files_proof"][] = [
|
||||
"name" => basename($p),
|
||||
"size_kb" => round(filesize($p)/1024, 1),
|
||||
"url" => "https://weval-consulting.com/api/ambre-pw-tests/output/" . basename($p),
|
||||
];
|
||||
}
|
||||
foreach (glob("$base/v10-*-chat.png") as $p) {
|
||||
$out["v10_chat"][] = [
|
||||
"name" => basename($p),
|
||||
"size_kb" => round(filesize($p)/1024, 1),
|
||||
"url" => "https://weval-consulting.com/api/ambre-pw-tests/output/" . basename($p),
|
||||
];
|
||||
}
|
||||
foreach (glob("$base/v10-*.{pdf,docx,pptx,svg,py}", GLOB_BRACE) as $p) {
|
||||
$out["v10_binaries"][] = [
|
||||
"name" => basename($p),
|
||||
"size_kb" => round(filesize($p)/1024, 1),
|
||||
"url" => "https://weval-consulting.com/api/ambre-pw-tests/output/" . basename($p),
|
||||
];
|
||||
}
|
||||
foreach (glob("$base/capabilities-v10-*/*.webm") as $w) {
|
||||
$rel = str_replace($base . "/", "", $w);
|
||||
$out["v10_video"] = [
|
||||
"size_mb" => round(filesize($w)/1048576, 2),
|
||||
"url" => "https://weval-consulting.com/api/ambre-pw-tests/output/" . $rel,
|
||||
];
|
||||
}
|
||||
|
||||
usort($out["v10_files_proof"], function($a,$b){return strcmp($a["name"],$b["name"]);});
|
||||
usort($out["v10_chat"], function($a,$b){return strcmp($a["name"],$b["name"]);});
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
7
api/ambre-pw-v11-deploy.php
Normal file
37
api/ambre-pw-v11-files.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/output";
|
||||
$out = ["v11_claude_pattern"=>[], "v11_video"=>null];
|
||||
|
||||
foreach (glob("$base/v11-0*.png") as $p) {
|
||||
$out["v11_claude_pattern"][] = [
|
||||
"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_claude_pattern"], function($a,$b){return strcmp($a["name"],$b["name"]);});
|
||||
|
||||
// The Claude-pattern v11 should have its own video in capabilities-v11-... or claude-pattern-v11-...
|
||||
foreach (glob("$base/claude-pattern*/*.webm") as $w) {
|
||||
$rel = str_replace($base . "/", "", $w);
|
||||
$out["v11_video"] = [
|
||||
"name" => "claude-pattern SSE",
|
||||
"size_mb" => round(filesize($w)/1048576, 2),
|
||||
"url" => "https://weval-consulting.com/api/ambre-pw-tests/output/" . $rel,
|
||||
];
|
||||
}
|
||||
// Fallback: find any recent webm
|
||||
if (!$out["v11_video"]) {
|
||||
foreach (glob("$base/capabilities-v11*/*.webm") as $w) {
|
||||
$rel = str_replace($base . "/", "", $w);
|
||||
$out["v11_video"] = [
|
||||
"name" => "capabilities-v11",
|
||||
"size_mb" => round(filesize($w)/1048576, 2),
|
||||
"url" => "https://weval-consulting.com/api/ambre-pw-tests/output/" . $rel,
|
||||
"note" => "first v11 run (capabilities)",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
10
api/ambre-pw-v12-deploy.php
Normal file
11
api/ambre-pw-v8-deploy.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
$spec = base64_decode('Y29uc3QgeyB0ZXN0IH0gPSByZXF1aXJlKCJAcGxheXdyaWdodC90ZXN0Iik7CmNvbnN0IENBUEFCSUxJVElFUyA9IFsKICB7IG5hbWU6ICJQREYiLCAgICAgIG1zZzogIkdlbmVyZSB1biBQREYgc3VyOiBzdHJhdGVnaWUgZW50ZXJwcmlzZSAyMDI2IiwgICAgICAgIG5lZWRsZTogIi5wZGYiIH0sCiAgeyBuYW1lOiAiV29yZCIsICAgICBtc2c6ICJHZW5lcmUgdW4gZG9jdW1lbnQgV29yZCBzdXI6IG1hbnVlbCBwcm9jZWR1cmUgcXVhbGl0ZSIsIG5lZWRsZTogIi5kb2N4IiB9LAogIHsgbmFtZTogIlBQVCIsICAgICAgbXNnOiAiR2VuZXJlIHVuZSBwcmVzZW50YXRpb24gc3VyOiBwaXRjaCBpbnZlc3RvciBzZXJpZXMgQSIsICBuZWVkbGU6ICIucHB0eCIgfSwKICB7IG5hbWU6ICJNZXJtYWlkIiwgIG1zZzogIkdlbmVyZSB1biBzY2hlbWEgbWVybWFpZCBwb3VyOiBwcm9jZXNzdXMgYWNoYXQgdmFsaWRlIiwgbmVlZGxlOiAiZ3JhcGggVEQiIH0sCiAgeyBuYW1lOiAiSW1hZ2UiLCAgICBtc2c6ICJHZW5lcmUgdW5lIGltYWdlOiBvYXNpcyBwYWxtaWVycyBjb3VjaGVyIHNvbGVpbCIsICAgICAgIG5lZWRsZTogImdlbmVyYXRlZC93ZXZpYS1pbWciIH0sCiAgeyBuYW1lOiAiQ29kZSIsICAgICBtc2c6ICJFY3JpcyBsZSBjb2RlIHB5dGhvbiBwb3VyOiBmaWJvbmFjY2kgcmVjdXJzaWYgbWVtb2l6ZSIsIG5lZWRsZTogIndldmlhLWNvZGUiIH0sCiAgeyBuYW1lOiAiVHJhZHVpcmUiLCBtc2c6ICJUcmFkdWlzIGVuIGFuZ2xhaXM6IG1lcmNpIGluZmluaW1lbnQgcG91ciB2b3RyZSBhaWRlIiwgIG5lZWRsZTogIkVuZ2xpc2g6IiB9LAogIHsgbmFtZTogIkJpbGFuIiwgICAgbXNnOiAiYmlsYW4gY29tcGxldCBhcmNoaXRlY3R1cmUiLCAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZWVkbGU6ICJhZ2VudHMiIH0sCl07CnRlc3QoIlY4IGZpbmFsIDgvOCIsIGFzeW5jICh7IHBhZ2UgfSkgPT4gewogIHRlc3Quc2V0VGltZW91dCg0ODAwMDApOwogIGxldCBlcnJzID0gMDsKICBwYWdlLm9uKCJwYWdlZXJyb3IiLCBlID0+IHsgZXJycysrOyBjb25zb2xlLmxvZygiW3BhZ2VlcnJvcl0iLCBlLm1lc3NhZ2Uuc3Vic3RyaW5nKDAsMTIwKSk7IH0pOwogIGF3YWl0IHBhZ2UuZ290bygiL3dldmlhLmh0bWwiKTsKICBhd2FpdCBwYWdlLndhaXRGb3JMb2FkU3RhdGUoIm5ldHdvcmtpZGxlIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgyNTAwKTsKICBhd2FpdCBwYWdlLnNjcmVlbnNob3QoeyBwYXRoOiAib3V0cHV0L3Y4LTAwLWluaXRpYWwucG5nIiB9KTsKICBjb25zb2xlLmxvZygi8J+TuCBpbml0aWFsIHY4Iik7CiAgY29uc3QgcmVzdWx0cyA9IFtdOwogIGZvciAobGV0IGkgPSAwOyBpIDwgQ0FQQUJJTElUSUVTLmxlbmd0aDsgaSsrKSB7CiAgICBjb25zdCBjYXAgPSBDQVBBQklMSVRJRVNbaV07CiAgICBjb25zdCBudW0gPSBTdHJpbmcoaSArIDEpLnBhZFN0YXJ0KDIsICIwIik7CiAgICBjb25zb2xlLmxvZygiXG5bIiArIG51bSArICIvOF0gIiArIGNhcC5uYW1lKTsKICAgIGxldCBvayA9IGZhbHNlLCBhdHRlbXB0cyA9IDA7CiAgICB3aGlsZSAoIW9rICYmIGF0dGVtcHRzIDwgMikgewogICAgICBhdHRlbXB0cysrOwogICAgICB0cnkgewogICAgICAgIGNvbnN0IGJlZm9yZSA9IGF3YWl0IHBhZ2UuZXZhbHVhdGUobiA9PiAoZG9jdW1lbnQuYm9keS5pbm5lclRleHQubWF0Y2gobmV3IFJlZ0V4cChuLnJlcGxhY2UoL1suKis/XiR7fSgpfFtcXVxcXS9nLCJcXCQmIiksImciKSl8fFtdKS5sZW5ndGgsIGNhcC5uZWVkbGUpOwogICAgICAgIGNvbnN0IGlucHV0ID0gcGFnZS5sb2NhdG9yKCIjbXNnSW5wdXQiKTsKICAgICAgICBhd2FpdCBpbnB1dC5jbGljayh7IGZvcmNlOiB0cnVlIH0pOwogICAgICAgIGF3YWl0IHBhZ2Uua2V5Ym9hcmQucHJlc3MoIkNvbnRyb2wrQSIpOwogICAgICAgIGF3YWl0IHBhZ2Uua2V5Ym9hcmQucHJlc3MoIkRlbGV0ZSIpOwogICAgICAgIGF3YWl0IGlucHV0LmZpbGwoY2FwLm1zZyk7CiAgICAgICAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCg0MDApOwogICAgICAgIGF3YWl0IGlucHV0LnByZXNzKCJFbnRlciIpOwogICAgICAgIGNvbnNvbGUubG9nKCIgIPCfk6Qgc2VudCAoeCIgKyBiZWZvcmUgKyAiKSIpOwogICAgICAgIGNvbnN0IHMgPSBEYXRlLm5vdygpOwogICAgICAgIHdoaWxlIChEYXRlLm5vdygpIC0gcyA8IDQ1MDAwKSB7CiAgICAgICAgICBjb25zdCBhID0gYXdhaXQgcGFnZS5ldmFsdWF0ZShuID0+IChkb2N1bWVudC5ib2R5LmlubmVyVGV4dC5tYXRjaChuZXcgUmVnRXhwKG4ucmVwbGFjZSgvWy4qKz9eJHt9KCl8W1xdXFxdL2csIlxcJCYiKSwiZyIpKXx8W10pLmxlbmd0aCwgY2FwLm5lZWRsZSk7CiAgICAgICAgICBpZiAoYSA+IGJlZm9yZSkgeyBvayA9IHRydWU7IGJyZWFrOyB9CiAgICAgICAgICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDE1MDApOwogICAgICAgIH0KICAgICAgICBjb25zdCBlbCA9ICgoRGF0ZS5ub3coKS1zKS8xMDAwKS50b0ZpeGVkKDEpOwogICAgICAgIGNvbnNvbGUubG9nKG9rID8gIiAg4pyFIFBBU1MgIiArIGVsICsgInMiIDogIiAg4pqg77iPIG5vIG1hdGNoICIgKyBlbCArICJzIik7CiAgICAgICAgaWYgKCFvayAmJiBhdHRlbXB0cyA8IDIpIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoMzAwMCk7CiAgICAgIH0gY2F0Y2ggKGUpIHsgY29uc29sZS5sb2coIiAg4p2MIiwgZS5tZXNzYWdlLnN1YnN0cmluZygwLDEwMCkpOyB9CiAgICB9CiAgICBhd2FpdCBwYWdlLmV2YWx1YXRlKCgpID0+IHsgY29uc3QgbT1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgibWVzc2FnZXMiKTsgaWYgKG0pIG0uc2Nyb2xsVG9wPW0uc2Nyb2xsSGVpZ2h0OyB9KTsKICAgIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoMjIwMCk7CiAgICBhd2FpdCBwYWdlLnNjcmVlbnNob3QoeyBwYXRoOiAib3V0cHV0L3Y4LSIgKyBudW0gKyAiLSIgKyBjYXAubmFtZSArICIucG5nIiB9KTsKICAgIGNvbnNvbGUubG9nKCIgIPCfk7ggdjgtIiArIG51bSArICItIiArIGNhcC5uYW1lICsgIi5wbmciKTsKICAgIHJlc3VsdHMucHVzaCh7IG5hbWU6IGNhcC5uYW1lLCBwYXNzOiBvayB9KTsKICAgIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoMTIwMCk7CiAgfQogIGF3YWl0IHBhZ2UuZXZhbHVhdGUoKCkgPT4gd2luZG93LnNjcm9sbFRvKDAsIGRvY3VtZW50LmJvZHkuc2Nyb2xsSGVpZ2h0KSk7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgyMDAwKTsKICBhd2FpdCBwYWdlLnNjcmVlbnNob3QoeyBwYXRoOiAib3V0cHV0L3Y4LTk5LWZpbmFsLnBuZyIsIGZ1bGxQYWdlOiB0cnVlIH0pOwogIGNvbnN0IHAgPSByZXN1bHRzLmZpbHRlcihyID0+IHIucGFzcykubGVuZ3RoOwogIGNvbnNvbGUubG9nKCJcbuKVkOKVkOKVkCBWOCBGSU5BTCDilZDilZDilZAiKTsKICBjb25zb2xlLmxvZyhwICsgIi84IFBBU1MgwrcgIiArIGVycnMgKyAiIHBhZ2UgZXJyb3JzIik7CiAgcmVzdWx0cy5mb3JFYWNoKHIgPT4gY29uc29sZS5sb2coIiAgIiArIChyLnBhc3MgPyAi4pyFIiA6ICLinYwiKSArICIgIiArIHIubmFtZSkpOwp9KTsK');
|
||||
$written = @file_put_contents("$base/capabilities-v8.spec.js", $spec);
|
||||
$removed = @unlink("$base/capabilities-v7.spec.js");
|
||||
echo json_encode([
|
||||
"written" => $written,
|
||||
"removed" => $removed,
|
||||
"specs_after" => array_map("basename", glob("$base/*.spec.js")),
|
||||
]);
|
||||
23
api/ambre-pw-v8-files.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/output";
|
||||
$out = ["v8_screenshots"=>[], "v8_video"=>null];
|
||||
|
||||
foreach (glob("$base/v8-*.png") as $p) {
|
||||
$out["v8_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["v8_screenshots"], function($a,$b){return strcmp($a["name"], $b["name"]);});
|
||||
|
||||
foreach (glob("$base/capabilities-v8-*/*.webm") as $w) {
|
||||
$rel = str_replace($base . "/", "", $w);
|
||||
$out["v8_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);
|
||||
11
api/ambre-pw-v9-deploy.php
Normal file
40
api/ambre-scan-sse.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
$dir = "/var/www/html/api";
|
||||
|
||||
// Find SSE / stream endpoints
|
||||
$candidates = [];
|
||||
foreach (glob("$dir/*stream*.php") as $f) $candidates[] = basename($f);
|
||||
foreach (glob("$dir/*sse*.php") as $f) $candidates[] = basename($f);
|
||||
foreach (glob("$dir/wevcode*.php") as $f) $candidates[] = basename($f);
|
||||
foreach (glob("$dir/*plan-exec*.php") as $f) $candidates[] = basename($f);
|
||||
foreach (glob("$dir/*autonomous*.php") as $f) $candidates[] = basename($f);
|
||||
$out["streaming_endpoints"] = array_unique($candidates);
|
||||
|
||||
// Find wevia-master-api.php size + check if supports SSE
|
||||
if (file_exists("$dir/wevia-master-api.php")) {
|
||||
$content = @file_get_contents("$dir/wevia-master-api.php");
|
||||
$out["master_api_size"] = strlen($content);
|
||||
$out["has_sse_hint"] = strpos($content, "text/event-stream") !== false;
|
||||
}
|
||||
|
||||
// Check existing wevia-autonomous/wevcode for SSE pattern
|
||||
foreach (["wevia-autonomous.php", "wevcode.php", "wevcode-stream.php", "wevia-sse.php"] as $f) {
|
||||
$p = "$dir/$f";
|
||||
if (file_exists($p)) {
|
||||
$c = @file_get_contents($p, false, null, 0, 2000);
|
||||
$out["preview_$f"] = [
|
||||
"size" => filesize($p),
|
||||
"has_event_stream" => strpos($c, "text/event-stream") !== false,
|
||||
"has_flush" => strpos($c, "ob_flush") !== false,
|
||||
"first_200" => substr($c, 0, 200),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Check existing React dashboards
|
||||
$out["react_dashboards"] = array_map("basename", glob("$dir/../*react*.html") ?: []);
|
||||
$out["dashboard_files"] = array_map("basename", array_slice(glob("$dir/../dashboard*.html"), 0, 10));
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
68
api/ambre-session-chat.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* ambre-session-chat.php · contextual chat with memory + empathy
|
||||
* POST {message, session_id}
|
||||
* - Loads last 10 turns from session
|
||||
* - Calls LLM with full history + empathy system prompt
|
||||
* - Appends user+assistant to memory
|
||||
* Returns {response, provider, intent, turns_in_memory, ...}
|
||||
*/
|
||||
require_once __DIR__ . "/ambre-session-memory.php";
|
||||
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
$raw = file_get_contents("php://input");
|
||||
$in = json_decode($raw, true) ?: $_POST;
|
||||
$msg = trim($in["message"] ?? "");
|
||||
$sid = trim($in["session_id"] ?? "");
|
||||
|
||||
if (!$msg) { echo json_encode(["error"=>"message required"]); exit; }
|
||||
if (!$sid) $sid = "anon-" . substr(md5($_SERVER["REMOTE_ADDR"] ?? "x"), 0, 8);
|
||||
|
||||
// Load prior turns
|
||||
$history = AmbreSessionMemory::context_messages($sid, 10);
|
||||
|
||||
// Build messages array for LLM
|
||||
$sys = "Tu es WEVIA, une IA empathique et adaptative de WEVAL Consulting. " .
|
||||
"Tu te souviens des échanges précédents dans cette conversation. " .
|
||||
"Si l'utilisateur revient sur un sujet antérieur, reconnais-le explicitement. " .
|
||||
"Si l'utilisateur change de sujet, adapte-toi avec fluidité. " .
|
||||
"Si l'utilisateur exprime une émotion (joie, frustration, urgence, etc.), reconnais-la avec empathie. " .
|
||||
"Si l'utilisateur te demande d'améliorer un rendu précédent, refère-toi au dernier rendu et propose une version améliorée. " .
|
||||
"Réponse en français, concise mais riche, sans préambule inutile.";
|
||||
|
||||
$messages = [["role"=>"system", "content"=>$sys]];
|
||||
foreach ($history as $h) $messages[] = $h;
|
||||
$messages[] = ["role"=>"user", "content"=>$msg];
|
||||
|
||||
// Call LLM
|
||||
$t0 = microtime(true);
|
||||
$raw_llm = @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"=>$messages,"max_tokens"=>1200,"temperature"=>0.5]),
|
||||
"timeout"=>30,
|
||||
],
|
||||
]));
|
||||
$elapsed = round((microtime(true)-$t0)*1000);
|
||||
|
||||
$d = @json_decode($raw_llm, true);
|
||||
$reply = $d["choices"][0]["message"]["content"] ?? "";
|
||||
if (!$reply) $reply = "Désolé, je n'ai pas pu traiter la demande. Peux-tu reformuler ?";
|
||||
|
||||
// Store turns
|
||||
AmbreSessionMemory::append($sid, "user", $msg);
|
||||
AmbreSessionMemory::append($sid, "assistant", $reply);
|
||||
|
||||
$summary = AmbreSessionMemory::summary($sid);
|
||||
|
||||
echo json_encode([
|
||||
"response" => $reply,
|
||||
"provider" => "ambre-session-chat-v1",
|
||||
"intent" => "contextual_reply",
|
||||
"session_id" => $sid,
|
||||
"turns_in_memory" => $summary["turns"],
|
||||
"history_used" => count($history),
|
||||
"elapsed_ms" => $elapsed,
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
88
api/ambre-session-memory.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* ambre-session-memory.php · AMBRE v1 · per-session memory store
|
||||
* Stores/retrieves last N messages per session_id in /var/tmp/wevia-sessions/
|
||||
*/
|
||||
|
||||
class AmbreSessionMemory {
|
||||
const DIR = "/var/tmp/wevia-sessions";
|
||||
const MAX_TURNS = 20;
|
||||
const TTL_HOURS = 24;
|
||||
|
||||
public static function init() {
|
||||
if (!is_dir(self::DIR)) @mkdir(self::DIR, 0777, true);
|
||||
}
|
||||
|
||||
public static function path($sid) {
|
||||
self::init();
|
||||
$safe = preg_replace("/[^a-zA-Z0-9_-]/", "", $sid);
|
||||
if (!$safe) $safe = "default";
|
||||
return self::DIR . "/" . $safe . ".json";
|
||||
}
|
||||
|
||||
public static function load($sid) {
|
||||
$p = self::path($sid);
|
||||
if (!file_exists($p)) return [];
|
||||
$content = @file_get_contents($p);
|
||||
if (!$content) return [];
|
||||
$data = @json_decode($content, true);
|
||||
if (!is_array($data)) return [];
|
||||
// TTL cleanup
|
||||
$now = time();
|
||||
$data = array_filter($data, function($m) use ($now) {
|
||||
return isset($m["ts"]) && ($now - $m["ts"]) < (self::TTL_HOURS * 3600);
|
||||
});
|
||||
return array_values($data);
|
||||
}
|
||||
|
||||
public static function append($sid, $role, $content) {
|
||||
if (!$sid || !$role || !$content) return;
|
||||
$msgs = self::load($sid);
|
||||
$msgs[] = [
|
||||
"role" => $role,
|
||||
"content" => substr((string)$content, 0, 4000),
|
||||
"ts" => time(),
|
||||
];
|
||||
// Keep only last N
|
||||
if (count($msgs) > self::MAX_TURNS) {
|
||||
$msgs = array_slice($msgs, -self::MAX_TURNS);
|
||||
}
|
||||
@file_put_contents(self::path($sid), json_encode($msgs, JSON_UNESCAPED_UNICODE), LOCK_EX);
|
||||
}
|
||||
|
||||
public static function context_messages($sid, $max = 10) {
|
||||
$msgs = self::load($sid);
|
||||
$last = array_slice($msgs, -$max);
|
||||
$out = [];
|
||||
foreach ($last as $m) {
|
||||
$out[] = ["role" => $m["role"], "content" => $m["content"]];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function summary($sid) {
|
||||
$msgs = self::load($sid);
|
||||
return [
|
||||
"session" => $sid,
|
||||
"turns" => count($msgs),
|
||||
"first_ts" => !empty($msgs) ? date("c", $msgs[0]["ts"]) : null,
|
||||
"last_ts" => !empty($msgs) ? date("c", end($msgs)["ts"]) : null,
|
||||
];
|
||||
}
|
||||
|
||||
public static function clear($sid) {
|
||||
$p = self::path($sid);
|
||||
if (file_exists($p)) @unlink($p);
|
||||
}
|
||||
}
|
||||
|
||||
// Direct API usage
|
||||
if (basename($_SERVER["SCRIPT_NAME"]) === "ambre-session-memory.php") {
|
||||
header("Content-Type: application/json");
|
||||
$sid = $_GET["sid"] ?? "";
|
||||
$action = $_GET["action"] ?? "summary";
|
||||
if ($action === "summary") echo json_encode(AmbreSessionMemory::summary($sid));
|
||||
elseif ($action === "load") echo json_encode(AmbreSessionMemory::load($sid));
|
||||
elseif ($action === "clear") { AmbreSessionMemory::clear($sid); echo json_encode(["cleared"=>true]); }
|
||||
else echo json_encode(["error"=>"unknown action"]);
|
||||
}
|
||||
8
api/ambre-specs-list.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
echo json_encode([
|
||||
"specs" => array_map("basename", glob("$base/*.spec.js")),
|
||||
"v7_exists" => file_exists("$base/capabilities-v7.spec.js"),
|
||||
"v8_exists" => file_exists("$base/capabilities-v8.spec.js"),
|
||||
]);
|
||||
25
api/ambre-sse-inspect.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
$endpoints = [
|
||||
"/var/www/html/api/wevia-public-stream.php",
|
||||
"/var/www/html/api/wevia-stream-api.php",
|
||||
"/var/www/html/api/wevia-multiagent-sse.php",
|
||||
"/var/www/html/api/wevcode-superclaude.php",
|
||||
"/var/www/html/api/wevia-sse-orchestrator-public.php",
|
||||
];
|
||||
foreach ($endpoints as $f) {
|
||||
if (!file_exists($f)) { $out[basename($f)] = "MISSING"; continue; }
|
||||
$c = @file_get_contents($f);
|
||||
$out[basename($f)] = [
|
||||
"size" => strlen($c),
|
||||
"has_sse" => strpos($c, "text/event-stream") !== false,
|
||||
"has_flush" => strpos($c, "ob_flush") !== false || strpos($c, "flush()") !== false,
|
||||
"has_thinking" => stripos($c, "thinking") !== false,
|
||||
"has_plan" => stripos($c, "plan") !== false,
|
||||
"has_execute" => stripos($c, "execute") !== false || stripos($c, "exec_") !== false,
|
||||
"has_rag" => stripos($c, "rag") !== false || stripos($c, "qdrant") !== false,
|
||||
"first_500" => substr($c, 0, 500),
|
||||
];
|
||||
}
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
26
api/ambre-sse-mini.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
header("Content-Type: text/event-stream");
|
||||
header("Cache-Control: no-cache");
|
||||
header("X-Accel-Buffering: no");
|
||||
header("Connection: keep-alive");
|
||||
if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") { http_response_code(200); exit; }
|
||||
ob_implicit_flush(true);
|
||||
while (ob_get_level()) @ob_end_flush();
|
||||
|
||||
echo "event: start
|
||||
data: " . json_encode(["hello"=>"ambre"]) . "
|
||||
|
||||
";
|
||||
@flush();
|
||||
sleep(1);
|
||||
echo "event: step
|
||||
data: " . json_encode(["step"=>1]) . "
|
||||
|
||||
";
|
||||
@flush();
|
||||
sleep(1);
|
||||
echo "event: done
|
||||
data: {}
|
||||
|
||||
";
|
||||
@flush();
|
||||
30
api/ambre-sse-scan.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
// Check SSE endpoints
|
||||
$sse_candidates = glob("/var/www/html/api/*sse*.php") ?: [];
|
||||
$stream_candidates = glob("/var/www/html/api/*stream*.php") ?: [];
|
||||
$out["sse_files"] = array_map("basename", $sse_candidates);
|
||||
$out["stream_files"] = array_map("basename", $stream_candidates);
|
||||
|
||||
// Check SSE in wevia.html
|
||||
$wevia = file_exists("/var/www/html/wevia.html") ? file_get_contents("/var/www/html/wevia.html") : "";
|
||||
$out["wevia_has_eventsource"] = substr_count($wevia, "EventSource");
|
||||
$out["wevia_has_sse"] = substr_count($wevia, "text/event-stream");
|
||||
|
||||
// Check RAG / Qdrant / Chroma
|
||||
$rag_files = array_merge(
|
||||
glob("/var/www/html/api/*rag*.php") ?: [],
|
||||
glob("/var/www/html/api/*qdrant*.php") ?: [],
|
||||
glob("/var/www/html/api/*embedding*.php") ?: []
|
||||
);
|
||||
$out["rag_files"] = array_map("basename", array_slice($rag_files, 0, 15));
|
||||
|
||||
// Check Qdrant live
|
||||
$qdrant = @file_get_contents("http://127.0.0.1:6333/collections");
|
||||
$out["qdrant_alive"] = $qdrant ? substr($qdrant, 0, 500) : "unreachable";
|
||||
|
||||
// Check React CDN usage
|
||||
$out["react_cdn_in_wevia"] = strpos($wevia, "react") !== false ? "yes" : "no";
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
55
api/ambre-tool-image.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* ambre-tool-image.php · Real image generation via Pollinations.ai (free, no auth)
|
||||
* Returns downloadable PNG from prompt
|
||||
*/
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
$raw = file_get_contents("php://input");
|
||||
$in = json_decode($raw, true) ?: $_POST ?: $_GET;
|
||||
$prompt = trim($in["prompt"] ?? $in["q"] ?? "");
|
||||
|
||||
if (!$prompt) { echo json_encode(["error"=>"prompt required"]); exit; }
|
||||
|
||||
// Clean + translate prompt (keep as-is - Pollinations handles multilingue)
|
||||
$clean = preg_replace('/[^\p{L}\p{N}\s,.\-]/u', '', $prompt);
|
||||
$clean = substr(trim($clean), 0, 300);
|
||||
|
||||
// Pollinations API
|
||||
$seed = rand(1, 99999);
|
||||
$encoded = urlencode($clean);
|
||||
$pollinations_url = "https://image.pollinations.ai/prompt/$encoded?width=1024&height=1024&seed=$seed&nologo=true&enhance=true";
|
||||
|
||||
// Fetch image (with 30s timeout)
|
||||
$ctx = stream_context_create([
|
||||
"http" => ["timeout"=>30, "header"=>"User-Agent: WEVIA/1.0\r\n"],
|
||||
"https" => ["timeout"=>30, "header"=>"User-Agent: WEVIA/1.0\r\n"],
|
||||
]);
|
||||
$t0 = microtime(true);
|
||||
$img_data = @file_get_contents($pollinations_url, false, $ctx);
|
||||
$elapsed = round((microtime(true)-$t0)*1000);
|
||||
|
||||
if (!$img_data || strlen($img_data) < 1000) {
|
||||
echo json_encode(["error"=>"image generation failed", "prompt"=>$clean, "elapsed"=>$elapsed]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Save to /generated/
|
||||
$dir = "/var/www/html/generated";
|
||||
if (!is_dir($dir)) @mkdir($dir, 0755, true);
|
||||
$slug = preg_replace('/[^a-z0-9]+/', '-', strtolower($clean));
|
||||
$slug = substr(trim($slug, "-"), 0, 50);
|
||||
$ts = date("Ymd-His");
|
||||
$rand = bin2hex(random_bytes(3));
|
||||
$filename = "wevia-img-{$slug}-{$ts}-{$rand}.png";
|
||||
$path = "$dir/$filename";
|
||||
file_put_contents($path, $img_data);
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"prompt" => $clean,
|
||||
"url" => "https://weval-consulting.com/generated/$filename",
|
||||
"size_kb" => round(strlen($img_data)/1024, 1),
|
||||
"elapsed_ms" => $elapsed,
|
||||
"provider" => "WEVIA Image Engine",
|
||||
]);
|
||||
42
api/ambre-tool-web-search.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* ambre-tool-web-search.php · Web search via DuckDuckGo (free, no auth needed)
|
||||
*/
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
$in = json_decode(file_get_contents("php://input"), true) ?: $_POST ?: $_GET;
|
||||
$q = trim($in["query"] ?? $in["q"] ?? "");
|
||||
$limit = intval($in["limit"] ?? 5);
|
||||
if (!$q) { echo json_encode(["error"=>"query required"]); exit; }
|
||||
|
||||
$t0 = microtime(true);
|
||||
|
||||
// DuckDuckGo HTML endpoint
|
||||
$ddg = "https://html.duckduckgo.com/html/?q=" . urlencode($q);
|
||||
$ctx = stream_context_create(["http"=>["timeout"=>15,"header"=>"User-Agent: Mozilla/5.0 (X11; Linux x86_64) WEVIA/1.0\r\n"]]);
|
||||
$html = @file_get_contents($ddg, false, $ctx);
|
||||
|
||||
$results = [];
|
||||
if ($html) {
|
||||
// Parse results (DDG HTML)
|
||||
if (preg_match_all('/<a[^>]+class="result__a"[^>]+href="([^"]+)"[^>]*>(.*?)<\/a>.*?<a[^>]+class="result__snippet"[^>]*>(.*?)<\/a>/s', $html, $m, PREG_SET_ORDER)) {
|
||||
foreach (array_slice($m, 0, $limit) as $r) {
|
||||
// DDG wraps URL in redirect
|
||||
$url = $r[1];
|
||||
if (preg_match('/uddg=([^&]+)/', $url, $um)) $url = urldecode($um[1]);
|
||||
$results[] = [
|
||||
"url" => $url,
|
||||
"title" => html_entity_decode(strip_tags($r[2]), ENT_QUOTES, "UTF-8"),
|
||||
"snippet" => html_entity_decode(strip_tags($r[3]), ENT_QUOTES, "UTF-8"),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
"query" => $q,
|
||||
"results" => $results,
|
||||
"count" => count($results),
|
||||
"elapsed_ms" => round((microtime(true)-$t0)*1000),
|
||||
"provider" => "WEVIA Search",
|
||||
]);
|
||||
48
api/ambre-tooling-scan.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
|
||||
// 1. Image generation endpoints available
|
||||
$candidates = [
|
||||
"/var/www/html/api/wevia-image-gen.php",
|
||||
"/var/www/html/api/qwen-image.php",
|
||||
"/var/www/html/api/image-gen.php",
|
||||
"/var/www/html/api/opus-image-gen.php",
|
||||
];
|
||||
$out["image_endpoints"] = [];
|
||||
foreach (glob("/var/www/html/api/*image*.php") as $f) {
|
||||
$out["image_endpoints"][] = basename($f);
|
||||
}
|
||||
foreach (glob("/var/www/html/api/*qwen*.php") as $f) {
|
||||
$out["image_endpoints"][] = basename($f);
|
||||
}
|
||||
foreach (glob("/var/www/html/api/*dalle*.php") as $f) {
|
||||
$out["image_endpoints"][] = basename($f);
|
||||
}
|
||||
|
||||
// 2. Web/search endpoints
|
||||
$out["search_endpoints"] = [];
|
||||
foreach (glob("/var/www/html/api/*search*.php") as $f) $out["search_endpoints"][] = basename($f);
|
||||
foreach (glob("/var/www/html/api/*web*.php") as $f) $out["search_endpoints"][] = basename($f);
|
||||
foreach (glob("/var/www/html/api/*scrap*.php") as $f) $out["search_endpoints"][] = basename($f);
|
||||
|
||||
// 3. Check available API providers
|
||||
$secrets = @file_get_contents("/etc/weval/secrets.env");
|
||||
if ($secrets) {
|
||||
preg_match_all("/^([A-Z_]+_API_KEY)=/m", $secrets, $m);
|
||||
$out["api_keys_available"] = array_slice($m[1] ?? [], 0, 30);
|
||||
}
|
||||
|
||||
// 4. Qwen / image providers
|
||||
$providers_with_image = [];
|
||||
if ($secrets) {
|
||||
foreach (["DASHSCOPE", "QWEN", "TOGETHER", "REPLICATE", "OPENAI", "STABILITY", "FAL", "HUGGINGFACE", "ALIBABA"] as $p) {
|
||||
if (preg_match("/^{$p}[A-Z_]*_API_KEY=\S{5,}/m", $secrets)) $providers_with_image[] = $p;
|
||||
}
|
||||
}
|
||||
$out["providers_with_image_capable"] = $providers_with_image;
|
||||
|
||||
// 5. Pollinations.ai (FREE image gen, no auth)
|
||||
$out["free_image_gen"] = ["pollinations.ai (no key)", "image.pollinations.ai/prompt/*"];
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-04-21 20:30:01",
|
||||
"generated": "2026-04-21 21:30:02",
|
||||
"version": "1.0",
|
||||
"servers": [
|
||||
{
|
||||
@@ -8,9 +8,9 @@
|
||||
"private": "10.1.0.2",
|
||||
"role": "PRIMARY",
|
||||
"ssh": 49222,
|
||||
"disk_pct": 82,
|
||||
"disk_avail": "27G",
|
||||
"uptime": "up 1 week, 10 hours, 38 minutes",
|
||||
"disk_pct": 83,
|
||||
"disk_avail": "26G",
|
||||
"uptime": "up 1 week, 11 hours, 38 minutes",
|
||||
"nginx": "active",
|
||||
"php_fpm": "active",
|
||||
"php_version": "8.5.5"
|
||||
@@ -116,7 +116,7 @@
|
||||
},
|
||||
{
|
||||
"name": "uptime-kuma",
|
||||
"status": "Up 44 hours (healthy)",
|
||||
"status": "Up 45 hours (healthy)",
|
||||
"ports": ""
|
||||
},
|
||||
{
|
||||
@@ -275,9 +275,9 @@
|
||||
}
|
||||
],
|
||||
"screens": {
|
||||
"s204_html": 317,
|
||||
"s204_html": 318,
|
||||
"s204_products": 104,
|
||||
"s204_api_php": 828,
|
||||
"s204_api_php": 858,
|
||||
"s204_wevia_php": 34,
|
||||
"s95_arsenal_html": 1377,
|
||||
"s95_arsenal_api": 377
|
||||
@@ -301,7 +301,7 @@
|
||||
"langfuse"
|
||||
],
|
||||
"key_tables": {
|
||||
"kb_learnings": 5560,
|
||||
"kb_learnings": 5562,
|
||||
"kb_documents": 0,
|
||||
"ethica_medecins": 50004,
|
||||
"enterprise_agents": 0
|
||||
@@ -601,7 +601,7 @@
|
||||
]
|
||||
},
|
||||
"wiki": {
|
||||
"total_entries": 5560,
|
||||
"total_entries": 5562,
|
||||
"categories": [
|
||||
{
|
||||
"category": "AUTO-FIX",
|
||||
@@ -609,7 +609,7 @@
|
||||
},
|
||||
{
|
||||
"category": "TOPOLOGY",
|
||||
"cnt": "1230"
|
||||
"cnt": "1232"
|
||||
},
|
||||
{
|
||||
"category": "DISCOVERY",
|
||||
@@ -1945,7 +1945,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"scan_time_ms": 2029,
|
||||
"scan_time_ms": 2858,
|
||||
"gaps": [],
|
||||
"score": 100,
|
||||
"automation": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated_at": "2026-04-21T22:50:02.130032",
|
||||
"generated_at": "2026-04-21T23:50:02.046028",
|
||||
"stats": {
|
||||
"total": 48,
|
||||
"pending": 31,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"status": "ALIVE",
|
||||
"ts": "2026-04-21T22:45:02.197332",
|
||||
"last_heartbeat": "2026-04-21T22:45:02.197332",
|
||||
"last_heartbeat_ts_epoch": 1776804302,
|
||||
"ts": "2026-04-21T23:45:02.040913",
|
||||
"last_heartbeat": "2026-04-21T23:45:02.040913",
|
||||
"last_heartbeat_ts_epoch": 1776807902,
|
||||
"tasks_today": 232,
|
||||
"tasks_week": 574,
|
||||
"agent_id": "blade-ops",
|
||||
|
||||
55
api/csat-api.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* WEVAL CSAT - Customer Satisfaction Score sovereign 21avr2026
|
||||
* Rating 1-5 after resolved ticket/interaction.
|
||||
* Storage: /opt/weval-l99/data/csat-responses.jsonl
|
||||
*/
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$STORAGE = '/opt/weval-l99/data/csat-responses.jsonl';
|
||||
@mkdir(dirname($STORAGE), 0755, true);
|
||||
|
||||
$action = $_GET['action'] ?? ($_POST['action'] ?? 'stats');
|
||||
|
||||
if ($action === 'submit' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$rating = intval($_POST['rating'] ?? -1);
|
||||
$context = substr(trim($_POST['context'] ?? ''), 0, 200);
|
||||
$user = substr(trim($_POST['user'] ?? 'anonymous'), 0, 60);
|
||||
if ($rating < 1 || $rating > 5) {
|
||||
echo json_encode(['ok'=>false,'error'=>'invalid_rating','expected'=>'1-5']);
|
||||
exit;
|
||||
}
|
||||
@file_put_contents($STORAGE, json_encode(['ts'=>date('c'),'rating'=>$rating,'context'=>$context,'user'=>$user])."\n", FILE_APPEND | LOCK_EX);
|
||||
echo json_encode(['ok'=>true,'recorded'=>true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$responses = [];
|
||||
if (is_readable($STORAGE)) {
|
||||
foreach (file($STORAGE) as $line) {
|
||||
$r = @json_decode(trim($line), true);
|
||||
if ($r && isset($r['rating'])) $responses[] = $r;
|
||||
}
|
||||
}
|
||||
|
||||
$n = count($responses);
|
||||
if ($n === 0) {
|
||||
echo json_encode(['ok'=>true,'source'=>'sovereign_jsonl','ts'=>date('c'),'csat_score_pct'=>0,'responses_total'=>0,'status'=>'wire_needed','drill'=>'No ratings yet. POST /api/csat-api.php?action=submit']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// CSAT = % ratings >= 4 (out of 5)
|
||||
$satisfied = 0;
|
||||
foreach ($responses as $r) if ($r['rating'] >= 4) $satisfied++;
|
||||
$pct = round(($satisfied / $n) * 100);
|
||||
|
||||
echo json_encode([
|
||||
'ok'=>true,
|
||||
'source'=>'sovereign_jsonl',
|
||||
'ts'=>date('c'),
|
||||
'csat_score_pct'=>$pct,
|
||||
'responses_total'=>$n,
|
||||
'satisfied_count'=>$satisfied,
|
||||
'status'=>$pct >= 85 ? 'ok' : ($pct > 0 ? 'warn' : 'wire_needed'),
|
||||
'drill'=>"% ratings >=4 out of 5",
|
||||
]);
|
||||
@@ -1,281 +0,0 @@
|
||||
{
|
||||
"ts": "2026-04-21T20:50:02+00:00",
|
||||
"server": "s204",
|
||||
"s204": {
|
||||
"load": 2.11,
|
||||
"uptime": "2026-04-14 11:51:24",
|
||||
"ram_total_mb": 31335,
|
||||
"ram_used_mb": 12424,
|
||||
"ram_free_mb": 18910,
|
||||
"disk_total": "150G",
|
||||
"disk_used": "118G",
|
||||
"disk_free": "27G",
|
||||
"disk_pct": "82%",
|
||||
"fpm_workers": 141,
|
||||
"docker_containers": 19,
|
||||
"cpu_cores": 8
|
||||
},
|
||||
"s95": {
|
||||
"load": 0,
|
||||
"disk_pct": "81%",
|
||||
"status": "UP",
|
||||
"ram_total_mb": 15610,
|
||||
"ram_free_mb": 12055
|
||||
},
|
||||
"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": 317,
|
||||
"php_apis": 833,
|
||||
"wiki_entries": 2066,
|
||||
"vault_doctrines": 84,
|
||||
"vault_sessions": 104,
|
||||
"vault_decisions": 12
|
||||
},
|
||||
"tools": {
|
||||
"total": 630,
|
||||
"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": 161733,
|
||||
"with_email": 110639,
|
||||
"with_phone": 155151,
|
||||
"gap_email": 51094,
|
||||
"pct_email": 68.4,
|
||||
"pct_phone": 95.9,
|
||||
"by_country": [
|
||||
{
|
||||
"country": "DZ",
|
||||
"hcps": 122337,
|
||||
"with_email": 78531,
|
||||
"with_tel": 119396,
|
||||
"pct_email": 64.2,
|
||||
"pct_tel": 97.6
|
||||
},
|
||||
{
|
||||
"country": "MA",
|
||||
"hcps": 19723,
|
||||
"with_email": 15080,
|
||||
"with_tel": 18737,
|
||||
"pct_email": 76.5,
|
||||
"pct_tel": 95
|
||||
},
|
||||
{
|
||||
"country": "TN",
|
||||
"hcps": 17794,
|
||||
"with_email": 15149,
|
||||
"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": "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 5 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 45 hours (healthy)",
|
||||
"ports": ""
|
||||
},
|
||||
{
|
||||
"name": "vaultwarden",
|
||||
"status": "Up 7 days (healthy)",
|
||||
"ports": ""
|
||||
},
|
||||
{
|
||||
"name": "qdrant",
|
||||
"status": "Up 7 days",
|
||||
"ports": ""
|
||||
}
|
||||
],
|
||||
"crons": {
|
||||
"active": 35
|
||||
},
|
||||
"git": {
|
||||
"head": "151ffbae6 auto-sync-2250",
|
||||
"dirty": 2,
|
||||
"status": "DIRTY"
|
||||
},
|
||||
"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": 3949,
|
||||
"health": {
|
||||
"score": 5,
|
||||
"max": 6,
|
||||
"pct": 83
|
||||
},
|
||||
"elapsed_ms": 10338
|
||||
}
|
||||
96
api/feature-adoption.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* WEVAL Feature Adoption Tracker sovereign 21avr2026
|
||||
* Track which modules/widgets/features users interact with per session
|
||||
* Storage: /opt/weval-l99/data/feature-adoption.jsonl
|
||||
*
|
||||
* GET ?action=stats -> adoption rate KPI
|
||||
* POST ?action=track -> log feature use (feature, user, session_id)
|
||||
* GET ?action=list -> features inventory
|
||||
*/
|
||||
header("Content-Type: application/json");
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
|
||||
$STORAGE = "/opt/weval-l99/data/feature-adoption.jsonl";
|
||||
$INVENTORY = "/opt/weval-l99/data/feature-inventory.json";
|
||||
@mkdir(dirname($STORAGE), 0755, true);
|
||||
|
||||
// Default inventory if not exists
|
||||
if (!file_exists($INVENTORY)) {
|
||||
$default_features = [
|
||||
"wtp_dashboard", "wtp_pilotage_widget", "wtp_sparklines", "wtp_l99_brain",
|
||||
"wevia_master_chat", "wevia_orchestrator", "all_ia_hub", "agents_archi",
|
||||
"architecture_live", "openclaw", "nonreg_dashboard", "stripe_live",
|
||||
"ethica_hcp", "wevads_ia", "director_chat", "orphans_hub",
|
||||
"wikidoc", "vault_manager", "nps_feedback", "csat_rating", "ticket_create"
|
||||
];
|
||||
@file_put_contents($INVENTORY, json_encode(["features" => $default_features, "total" => count($default_features)]));
|
||||
}
|
||||
|
||||
$action = $_GET["action"] ?? ($_POST["action"] ?? "stats");
|
||||
|
||||
if ($action === "track" && $_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
$feature = substr(trim($_POST["feature"] ?? ""), 0, 80);
|
||||
$user = substr(trim($_POST["user"] ?? "anonymous"), 0, 60);
|
||||
$session = substr(trim($_POST["session_id"] ?? ""), 0, 40);
|
||||
if (!$feature) {
|
||||
echo json_encode(["ok"=>false,"error"=>"feature_required"]);
|
||||
exit;
|
||||
}
|
||||
$record = ["ts"=>date("c"),"feature"=>$feature,"user"=>$user,"session_id"=>$session];
|
||||
@file_put_contents($STORAGE, json_encode($record)."\n", FILE_APPEND | LOCK_EX);
|
||||
echo json_encode(["ok"=>true,"tracked"=>$feature]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === "list") {
|
||||
$inv = @json_decode(@file_get_contents($INVENTORY), true);
|
||||
echo json_encode($inv ?: ["features"=>[],"total"=>0]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// stats = adoption rate calculation
|
||||
$inv = @json_decode(@file_get_contents($INVENTORY), true);
|
||||
$total_features = intval($inv["total"] ?? 0);
|
||||
|
||||
$used_features = [];
|
||||
$events = [];
|
||||
if (is_readable($STORAGE)) {
|
||||
foreach (file($STORAGE) as $line) {
|
||||
$r = @json_decode(trim($line), true);
|
||||
if ($r && isset($r["feature"])) {
|
||||
$used_features[$r["feature"]] = ($used_features[$r["feature"]] ?? 0) + 1;
|
||||
$events[] = $r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$adopted = count($used_features);
|
||||
$adoption_rate = $total_features > 0 ? round(($adopted / $total_features) * 100) : 0;
|
||||
|
||||
// 7d & 30d activity
|
||||
$now = time();
|
||||
$evt_7d = 0; $evt_30d = 0;
|
||||
$users_7d = []; $users_30d = [];
|
||||
foreach ($events as $e) {
|
||||
$t = strtotime($e["ts"]);
|
||||
if ($t >= $now - 7*86400) { $evt_7d++; $users_7d[$e["user"] ?? ""] = true; }
|
||||
if ($t >= $now - 30*86400) { $evt_30d++; $users_30d[$e["user"] ?? ""] = true; }
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
"ok"=>true,
|
||||
"source"=>"sovereign_jsonl_tracker",
|
||||
"ts"=>date("c"),
|
||||
"adoption_rate_pct"=>$adoption_rate,
|
||||
"features_total"=>$total_features,
|
||||
"features_adopted"=>$adopted,
|
||||
"features_top5"=>array_slice(array_reverse(array_keys($used_features)), 0, 5),
|
||||
"events_total"=>count($events),
|
||||
"events_7d"=>$evt_7d,
|
||||
"events_30d"=>$evt_30d,
|
||||
"unique_users_7d"=>count($users_7d),
|
||||
"unique_users_30d"=>count($users_30d),
|
||||
"status"=>count($events)===0 ? "wire_needed" : ($adoption_rate >= 70 ? "ok" : "warn"),
|
||||
"drill"=>"Features used / Features available · Track via POST ?action=track",
|
||||
]);
|
||||
@@ -1,27 +1,27 @@
|
||||
{
|
||||
"ok": true,
|
||||
"agent": "V42_MQL_Scoring_Agent_REAL",
|
||||
"ts": "2026-04-21T20:50:02+00:00",
|
||||
"ts": "2026-04-21T21:50:02+00:00",
|
||||
"status": "DEPLOYED_AUTO",
|
||||
"deployed": true,
|
||||
"algorithm": "weighted_behavioral_signals",
|
||||
"signals_tracked": {
|
||||
"wtp_engagement": 70,
|
||||
"chat_engagement": 0,
|
||||
"wtp_engagement": 100,
|
||||
"chat_engagement": 3,
|
||||
"roi_tool": 0,
|
||||
"email_opened": 0
|
||||
},
|
||||
"avg_score": 17.5,
|
||||
"avg_score": 25.8,
|
||||
"mql_threshold": 50,
|
||||
"sql_threshold": 75,
|
||||
"leads_captured": 48,
|
||||
"mql_auto_scored": 19,
|
||||
"mql_auto_scored": 20,
|
||||
"sql_auto_scored": 8,
|
||||
"mql_auto_pct": 39,
|
||||
"mql_auto_pct": 41,
|
||||
"improvement_vs_manual": {
|
||||
"before_manual_pct": 33.3,
|
||||
"after_auto_pct": 39,
|
||||
"delta": 5.700000000000003
|
||||
"after_auto_pct": 41,
|
||||
"delta": 7.700000000000003
|
||||
},
|
||||
"paperclip_db_ok": true,
|
||||
"paperclip_tables": 1,
|
||||
|
||||
65
api/nps-collector.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* WEVAL NPS Collector - sovereign 21avr2026
|
||||
* Zero external tool (Typeform/etc). Local JSONL storage.
|
||||
* GET /api/nps-collector.php?action=stats -> KPI ready
|
||||
* POST /api/nps-collector.php?action=submit -> save response (score 0-10, comment)
|
||||
*/
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$STORAGE = '/opt/weval-l99/data/nps-responses.jsonl';
|
||||
@mkdir(dirname($STORAGE), 0755, true);
|
||||
|
||||
$action = $_GET['action'] ?? ($_POST['action'] ?? 'stats');
|
||||
|
||||
if ($action === 'submit' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$score = intval($_POST['score'] ?? -1);
|
||||
$comment = substr(trim($_POST['comment'] ?? ''), 0, 500);
|
||||
$user = substr(trim($_POST['user'] ?? 'anonymous'), 0, 60);
|
||||
if ($score < 0 || $score > 10) {
|
||||
echo json_encode(['ok'=>false,'error'=>'invalid_score','expected'=>'0-10']);
|
||||
exit;
|
||||
}
|
||||
$record = ['ts'=>date('c'),'score'=>$score,'comment'=>$comment,'user'=>$user,'ip'=>$_SERVER['REMOTE_ADDR']??''];
|
||||
@file_put_contents($STORAGE, json_encode($record)."\n", FILE_APPEND | LOCK_EX);
|
||||
echo json_encode(['ok'=>true,'recorded'=>$record]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// stats = NPS score aggregation
|
||||
$responses = [];
|
||||
if (is_readable($STORAGE)) {
|
||||
foreach (file($STORAGE) as $line) {
|
||||
$r = @json_decode(trim($line), true);
|
||||
if ($r && isset($r['score'])) $responses[] = $r;
|
||||
}
|
||||
}
|
||||
|
||||
$n = count($responses);
|
||||
if ($n === 0) {
|
||||
echo json_encode(['ok'=>true,'source'=>'sovereign_jsonl','ts'=>date('c'),'nps_score'=>0,'responses_total'=>0,'promoters'=>0,'passives'=>0,'detractors'=>0,'status'=>'wire_needed','drill'=>'No responses yet. Post to this endpoint with score+comment.','endpoint_submit'=>'/api/nps-collector.php?action=submit']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$promoters = 0; $passives = 0; $detractors = 0;
|
||||
foreach ($responses as $r) {
|
||||
$s = $r['score'];
|
||||
if ($s >= 9) $promoters++;
|
||||
elseif ($s >= 7) $passives++;
|
||||
else $detractors++;
|
||||
}
|
||||
$nps = round((($promoters - $detractors) / $n) * 100);
|
||||
|
||||
echo json_encode([
|
||||
'ok'=>true,
|
||||
'source'=>'sovereign_jsonl',
|
||||
'ts'=>date('c'),
|
||||
'nps_score'=>$nps,
|
||||
'responses_total'=>$n,
|
||||
'promoters'=>$promoters,
|
||||
'passives'=>$passives,
|
||||
'detractors'=>$detractors,
|
||||
'status'=>$nps >= 50 ? 'ok' : ($nps >= 0 ? 'warn' : 'fail'),
|
||||
'drill'=>"NPS = ((promoters - detractors) / total) * 100",
|
||||
'recent_comments'=>array_slice(array_reverse(array_column($responses, 'comment')), 0, 5),
|
||||
]);
|
||||
11
api/oss-manifest.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// Wave 222 · /api/oss-manifest.php · serves /opt/oss/manifest.json
|
||||
@require_once __DIR__ . "/wevia-sanitizer-guard.php";
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
$path = "/opt/oss/manifest.json";
|
||||
if (file_exists($path)) {
|
||||
echo @file_get_contents($path);
|
||||
} else {
|
||||
echo json_encode(["error" => "manifest_not_found"]);
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.914789"
|
||||
"discovered": "2026-04-21T23:00:03.489531"
|
||||
},
|
||||
{
|
||||
"name": "wevia-brain",
|
||||
@@ -23,7 +23,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:05.062894"
|
||||
"discovered": "2026-04-21T23:00:03.677956"
|
||||
},
|
||||
{
|
||||
"name": "skills",
|
||||
@@ -36,7 +36,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.676231"
|
||||
"discovered": "2026-04-21T23:00:03.214198"
|
||||
},
|
||||
{
|
||||
"name": "everything-claude-code",
|
||||
@@ -49,7 +49,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "**Language:** English | [Português (Brasil)](docs/pt-BR/README.md) | [简体中文](README.zh-CN.md) | [繁體中文](docs/zh-TW/README.md) | [日本語](docs/ja-JP/README.",
|
||||
"discovered": "2026-04-21T22:00:03.793286"
|
||||
"discovered": "2026-04-21T23:00:02.916745"
|
||||
},
|
||||
{
|
||||
"name": "open-webui-fresh",
|
||||
@@ -62,7 +62,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "# Open WebUI 👋   | [中文](README.zh.md) | [日本語](README.ja.md) | [Español](README.es.md) | [Tiếng Việt](README.vi.md) | [Português](README.p",
|
||||
"discovered": "2026-04-21T22:00:04.191178"
|
||||
"discovered": "2026-04-21T23:00:03.076639"
|
||||
},
|
||||
{
|
||||
"name": "mxyhi_ok-skills",
|
||||
@@ -114,7 +114,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "# OK Skills: AI Coding Agent Skills for Codex, Claude Code, Cursor, OpenClaw, and More English | [简体中文](README.zh-CN.md) | [繁體中文](README.zh-TW.md) | ",
|
||||
"discovered": "2026-04-21T22:00:04.140337"
|
||||
"discovered": "2026-04-21T23:00:03.045553"
|
||||
},
|
||||
{
|
||||
"name": "SuperClaude_Framework",
|
||||
@@ -127,7 +127,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "<div align=\"center\"> # 🚀 SuperClaude Framework [](https://smithery.ai/skills?ns=",
|
||||
"discovered": "2026-04-21T22:00:03.338813"
|
||||
"discovered": "2026-04-21T23:00:02.724729"
|
||||
},
|
||||
{
|
||||
"name": "paperclip-weval",
|
||||
@@ -140,7 +140,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "<p align=\"center\"> <img src=\"doc/assets/header.png\" alt=\"Paperclip — runs your business\" width=\"720\" /> </p> <p align=\"center\"> <a href=\"#quickst",
|
||||
"discovered": "2026-04-21T22:00:04.382601"
|
||||
"discovered": "2026-04-21T23:00:03.101166"
|
||||
},
|
||||
{
|
||||
"name": "vllm",
|
||||
@@ -153,7 +153,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "<!-- markdownlint-disable MD001 MD041 --> <p align=\"center\"> <picture> <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubus",
|
||||
"discovered": "2026-04-21T22:00:04.795760"
|
||||
"discovered": "2026-04-21T23:00:03.331344"
|
||||
},
|
||||
{
|
||||
"name": "deer-flow",
|
||||
@@ -166,7 +166,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "# 🦌 DeerFlow - 2.0 English | [中文](./README_zh.md) | [日本語](./README_ja.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) [ [](https://agent.xfyun.cn) <div align=\"center\"> [ | [Français](docs/translations/README.fr.md) | [Italiano](docs/translations/README.it.md) | ",
|
||||
"discovered": "2026-04-21T22:00:03.281165"
|
||||
"discovered": "2026-04-21T23:00:02.687576"
|
||||
},
|
||||
{
|
||||
"name": "aios",
|
||||
@@ -374,7 +374,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "# AIOS: AI Agent Operating System <a href='https://arxiv.org/abs/2403.16971'><img src='https://img.shields.io/badge/Paper-PDF-red'></a> <a href='http",
|
||||
"discovered": "2026-04-21T22:00:03.441512"
|
||||
"discovered": "2026-04-21T23:00:02.761010"
|
||||
},
|
||||
{
|
||||
"name": "rnd-agent-framework",
|
||||
@@ -387,7 +387,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": " # Welcome to Microsoft Agent Framework! [\"> <source srcset=\"apps/w",
|
||||
"discovered": "2026-04-21T22:00:04.736445"
|
||||
"discovered": "2026-04-21T23:00:03.226663"
|
||||
},
|
||||
{
|
||||
"name": "fmgapp",
|
||||
@@ -478,7 +478,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:03.807956"
|
||||
"discovered": "2026-04-21T23:00:02.922553"
|
||||
},
|
||||
{
|
||||
"name": "obsidian-vault",
|
||||
@@ -491,7 +491,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.161509"
|
||||
"discovered": "2026-04-21T23:00:03.061732"
|
||||
},
|
||||
{
|
||||
"name": "rnd-agents",
|
||||
@@ -504,7 +504,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "# Claude Code Plugins: Orchestration and Automation > **⚡ Updated for Opus 4.6, Sonnet 4.6 & Haiku 4.5** — Three-tier model strategy for optimal perf",
|
||||
"discovered": "2026-04-21T22:00:04.577894"
|
||||
"discovered": "2026-04-21T23:00:03.176177"
|
||||
},
|
||||
{
|
||||
"name": "FrancyJGLisboa_agent-skill-creator",
|
||||
@@ -517,7 +517,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "# Agent Skill Creator **Turn any workflow into reusable AI agent software that installs on 14+ tools — no spec writing, no prompt engineering, no cod",
|
||||
"discovered": "2026-04-21T22:00:03.221142"
|
||||
"discovered": "2026-04-21T23:00:02.659504"
|
||||
},
|
||||
{
|
||||
"name": "skillsmith",
|
||||
@@ -530,7 +530,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "<div align=\"center\"> <img src=\"terminal.svg\" alt=\"Skillsmith terminal\" width=\"740\"/> </div> <div align=\"center\"> # Skillsmith **Build consistent ",
|
||||
"discovered": "2026-04-21T22:00:04.697798"
|
||||
"discovered": "2026-04-21T23:00:03.216638"
|
||||
},
|
||||
{
|
||||
"name": "awesome-agent-skills",
|
||||
@@ -543,7 +543,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "<a href=\"https://github.com/VoltAgent/voltagent\"> <img width=\"1500\" height=\"801\" alt=\"claude-skills\" src=\"https://github.com/user-attachments/ass",
|
||||
"discovered": "2026-04-21T22:00:03.624791"
|
||||
"discovered": "2026-04-21T23:00:02.856946"
|
||||
},
|
||||
{
|
||||
"name": "paperclip-skills",
|
||||
@@ -556,7 +556,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.336361"
|
||||
"discovered": "2026-04-21T23:00:03.097450"
|
||||
},
|
||||
{
|
||||
"name": "jzOcb_writing-style-skill",
|
||||
@@ -569,7 +569,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "# Writing Style Skill 可复用的写作风格 Skill 模板。**内置自动学习** — 从你的修改中自动提取规则,SKILL.md 越用越准。 兼容 **Claude Code** + **OpenClaw (ClawHub)**。 ## 原理 ``` AI 用 SKILL",
|
||||
"discovered": "2026-04-21T22:00:03.818833"
|
||||
"discovered": "2026-04-21T23:00:02.943577"
|
||||
},
|
||||
{
|
||||
"name": "qdrant-data",
|
||||
@@ -582,7 +582,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.491727"
|
||||
"discovered": "2026-04-21T23:00:03.161515"
|
||||
},
|
||||
{
|
||||
"name": "wazuh",
|
||||
@@ -595,7 +595,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.821072"
|
||||
"discovered": "2026-04-21T23:00:03.389139"
|
||||
},
|
||||
{
|
||||
"name": "plausible",
|
||||
@@ -608,7 +608,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.384789"
|
||||
"discovered": "2026-04-21T23:00:03.123717"
|
||||
},
|
||||
{
|
||||
"name": "pmta",
|
||||
@@ -621,7 +621,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.425011"
|
||||
"discovered": "2026-04-21T23:00:03.137298"
|
||||
},
|
||||
{
|
||||
"name": "render-configs",
|
||||
@@ -634,7 +634,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.512799"
|
||||
"discovered": "2026-04-21T23:00:03.165713"
|
||||
},
|
||||
{
|
||||
"name": "searxng",
|
||||
@@ -647,7 +647,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.655536"
|
||||
"discovered": "2026-04-21T23:00:03.206089"
|
||||
},
|
||||
{
|
||||
"name": "weval-guardian",
|
||||
@@ -660,7 +660,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.884665"
|
||||
"discovered": "2026-04-21T23:00:03.457554"
|
||||
},
|
||||
{
|
||||
"name": "weval-litellm",
|
||||
@@ -673,7 +673,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.917338"
|
||||
"discovered": "2026-04-21T23:00:03.491658"
|
||||
},
|
||||
{
|
||||
"name": "weval-security",
|
||||
@@ -686,7 +686,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:05.041856"
|
||||
"discovered": "2026-04-21T23:00:03.625864"
|
||||
},
|
||||
{
|
||||
"name": "archive",
|
||||
@@ -699,7 +699,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:03.557008"
|
||||
"discovered": "2026-04-21T23:00:02.844768"
|
||||
},
|
||||
{
|
||||
"name": "loki",
|
||||
@@ -712,7 +712,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:03.979360"
|
||||
"discovered": "2026-04-21T23:00:02.994111"
|
||||
},
|
||||
{
|
||||
"name": "ruflo",
|
||||
@@ -725,7 +725,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.637108"
|
||||
"discovered": "2026-04-21T23:00:03.203889"
|
||||
},
|
||||
{
|
||||
"name": "twenty",
|
||||
@@ -738,7 +738,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.769414"
|
||||
"discovered": "2026-04-21T23:00:03.284105"
|
||||
},
|
||||
{
|
||||
"name": "weval-crewai",
|
||||
@@ -751,7 +751,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.866329"
|
||||
"discovered": "2026-04-21T23:00:03.431483"
|
||||
},
|
||||
{
|
||||
"name": "weval-plugins",
|
||||
@@ -764,7 +764,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.993748"
|
||||
"discovered": "2026-04-21T23:00:03.568544"
|
||||
},
|
||||
{
|
||||
"name": "weval-radar",
|
||||
@@ -777,7 +777,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:05.008804"
|
||||
"discovered": "2026-04-21T23:00:03.587907"
|
||||
},
|
||||
{
|
||||
"name": "weval-scrapy",
|
||||
@@ -790,7 +790,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:05.010801"
|
||||
"discovered": "2026-04-21T23:00:03.608294"
|
||||
},
|
||||
{
|
||||
"name": "langfuse",
|
||||
@@ -803,7 +803,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:03.820788"
|
||||
"discovered": "2026-04-21T23:00:02.953470"
|
||||
},
|
||||
{
|
||||
"name": "litellm",
|
||||
@@ -816,7 +816,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:03.904792"
|
||||
"discovered": "2026-04-21T23:00:02.984227"
|
||||
},
|
||||
{
|
||||
"name": "mattermost-docker",
|
||||
@@ -829,7 +829,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.039945"
|
||||
"discovered": "2026-04-21T23:00:02.998150"
|
||||
},
|
||||
{
|
||||
"name": "prometheus",
|
||||
@@ -842,7 +842,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.466744"
|
||||
"discovered": "2026-04-21T23:00:03.155605"
|
||||
},
|
||||
{
|
||||
"name": "twenty-compose",
|
||||
@@ -855,7 +855,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:04.793620"
|
||||
"discovered": "2026-04-21T23:00:03.289729"
|
||||
},
|
||||
{
|
||||
"name": "weval-ux",
|
||||
@@ -868,7 +868,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:05.050057"
|
||||
"discovered": "2026-04-21T23:00:03.659745"
|
||||
},
|
||||
{
|
||||
"name": "wevia-integrity",
|
||||
@@ -881,7 +881,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:05.075527"
|
||||
"discovered": "2026-04-21T23:00:03.712293"
|
||||
},
|
||||
{
|
||||
"name": "DiffusionDB",
|
||||
@@ -894,7 +894,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:03.172024"
|
||||
"discovered": "2026-04-21T23:00:02.579502"
|
||||
},
|
||||
{
|
||||
"name": "LTX-Video",
|
||||
@@ -907,7 +907,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:03.336762"
|
||||
"discovered": "2026-04-21T23:00:02.711861"
|
||||
},
|
||||
{
|
||||
"name": "localai",
|
||||
@@ -920,7 +920,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:03.943489"
|
||||
"discovered": "2026-04-21T23:00:02.990022"
|
||||
},
|
||||
{
|
||||
"name": "wevia-finetune",
|
||||
@@ -933,6 +933,6 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-21T22:00:05.070906"
|
||||
"discovered": "2026-04-21T23:00:03.692226"
|
||||
}
|
||||
]
|
||||
25
api/pandasai-multi-query-result.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"ok": 2,
|
||||
"total": 2,
|
||||
"queries": [
|
||||
{
|
||||
"tag": "sum_total",
|
||||
"query": "What is the sum of mrr_eur?",
|
||||
"answer": "Unfortunately, I was not able to get your answers, because of the following error:\n\n[Errno 32] Broken pipe\n",
|
||||
"duration_s": 186.7,
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"tag": "count_ma",
|
||||
"query": "How many rows have country equal to MA?",
|
||||
"answer": "2",
|
||||
"duration_s": 40.1,
|
||||
"ok": true
|
||||
}
|
||||
],
|
||||
"expected": {
|
||||
"sum_total": 5280,
|
||||
"count_ma": 2,
|
||||
"fr_ma_sum": 4330
|
||||
}
|
||||
}
|
||||
44
api/pandasai-ollama-test-result.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"ts": "2026-04-21T23:26:25.346919",
|
||||
"wave": 224,
|
||||
"test": "pandasai_ollama_integration",
|
||||
"steps": [
|
||||
{
|
||||
"step": "import pandasai",
|
||||
"ok": true,
|
||||
"version": "2.0.24"
|
||||
},
|
||||
{
|
||||
"step": "import pandas",
|
||||
"ok": true,
|
||||
"version": "2.3.3"
|
||||
},
|
||||
{
|
||||
"step": "create df",
|
||||
"ok": true,
|
||||
"rows": 3,
|
||||
"cols": 2
|
||||
},
|
||||
{
|
||||
"step": "import LocalLLM",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": "create Ollama LocalLLM",
|
||||
"ok": true,
|
||||
"model": "llama3.2:latest",
|
||||
"api": "localhost:11434/v1"
|
||||
},
|
||||
{
|
||||
"step": "import SmartDataframe",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"step": "create SmartDataframe",
|
||||
"ok": true,
|
||||
"class": "SmartDataframe"
|
||||
}
|
||||
],
|
||||
"integration_ok": true,
|
||||
"message": "pandasai 2.0.24 + Ollama llama3.2 integration LOADED (ready for LLM queries)"
|
||||
}
|
||||
1
api/pandasai-real-query-result.json
Normal file
@@ -0,0 +1 @@
|
||||
{"ok": true, "query": "sum of mrr_eur", "expected": 3000, "llm_answer": "3000", "duration_s": 43.1}
|
||||
63
api/tickets-api.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* WEVAL Support Tickets sovereign 21avr2026
|
||||
* Local JSONL + status tracking (open/resolved/closed)
|
||||
* Storage: /opt/weval-l99/data/tickets.jsonl
|
||||
*/
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$STORAGE = '/opt/weval-l99/data/tickets.jsonl';
|
||||
@mkdir(dirname($STORAGE), 0755, true);
|
||||
|
||||
$action = $_GET['action'] ?? ($_POST['action'] ?? 'stats');
|
||||
|
||||
if ($action === 'create' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$subject = substr(trim($_POST['subject'] ?? ''), 0, 200);
|
||||
$body = substr(trim($_POST['body'] ?? ''), 0, 2000);
|
||||
$user = substr(trim($_POST['user'] ?? 'anonymous'), 0, 60);
|
||||
$priority = in_array($_POST['priority'] ?? '', ['low','medium','high','critical']) ? $_POST['priority'] : 'medium';
|
||||
if (!$subject) {
|
||||
echo json_encode(['ok'=>false,'error'=>'subject_required']);
|
||||
exit;
|
||||
}
|
||||
$id = 'TKT-' . date('Ymd') . '-' . substr(md5($subject.microtime()), 0, 6);
|
||||
$record = ['ts'=>date('c'),'id'=>$id,'status'=>'open','subject'=>$subject,'body'=>$body,'user'=>$user,'priority'=>$priority,'resolved_at'=>null];
|
||||
@file_put_contents($STORAGE, json_encode($record)."\n", FILE_APPEND | LOCK_EX);
|
||||
echo json_encode(['ok'=>true,'ticket_id'=>$id]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$tickets = [];
|
||||
if (is_readable($STORAGE)) {
|
||||
foreach (file($STORAGE) as $line) {
|
||||
$r = @json_decode(trim($line), true);
|
||||
if ($r) $tickets[] = $r;
|
||||
}
|
||||
}
|
||||
|
||||
// Stats
|
||||
$total = count($tickets);
|
||||
$open = 0; $resolved = 0; $mttr_hours = 0; $mttr_count = 0;
|
||||
foreach ($tickets as $t) {
|
||||
if ($t['status'] === 'open') $open++;
|
||||
elseif ($t['status'] === 'resolved' || $t['status'] === 'closed') {
|
||||
$resolved++;
|
||||
if (!empty($t['resolved_at'])) {
|
||||
$delta = (strtotime($t['resolved_at']) - strtotime($t['ts'])) / 3600;
|
||||
if ($delta > 0) { $mttr_hours += $delta; $mttr_count++; }
|
||||
}
|
||||
}
|
||||
}
|
||||
$mttr = $mttr_count > 0 ? round($mttr_hours / $mttr_count, 1) : 0;
|
||||
|
||||
echo json_encode([
|
||||
'ok'=>true,
|
||||
'source'=>'sovereign_jsonl',
|
||||
'ts'=>date('c'),
|
||||
'tickets_total'=>$total,
|
||||
'tickets_open'=>$open,
|
||||
'tickets_resolved'=>$resolved,
|
||||
'mttr_hours'=>$mttr,
|
||||
'status'=>$open === 0 && $total === 0 ? 'wire_needed' : ($open <= 5 ? 'ok' : 'warn'),
|
||||
'endpoint_create'=>'/api/tickets-api.php?action=create',
|
||||
]);
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"timestamp": "2026-04-21T22:30:12",
|
||||
"timestamp": "2026-04-21T23:30:12",
|
||||
"features": {
|
||||
"total": 36,
|
||||
"pass": 35
|
||||
@@ -13,7 +13,7 @@
|
||||
"score": 97.2,
|
||||
"log": [
|
||||
"=== UX AGENT v1.0 ===",
|
||||
"Time: 2026-04-21 22:30:01",
|
||||
"Time: 2026-04-21 23:30:02",
|
||||
" core: 4/4",
|
||||
" layout: 3/4",
|
||||
" interaction: 6/6",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"ok": true,
|
||||
"version": "V83-business-kpi",
|
||||
"ts": "2026-04-21T20:50:17+00:00",
|
||||
"ts": "2026-04-21T21:54:42+00:00",
|
||||
"summary": {
|
||||
"total_categories": 8,
|
||||
"total_kpis": 64,
|
||||
"ok": 41,
|
||||
"warn": 20,
|
||||
"ok": 47,
|
||||
"warn": 14,
|
||||
"fail": 0,
|
||||
"wire_needed": 3,
|
||||
"data_completeness_pct": 95.3
|
||||
|
||||
131
api/weval-feature-tracker.js
Normal file
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* WEVAL Feature Tracker + NPS Popup + CSAT autolink
|
||||
* Zero external deps. Zero cookies. sessionStorage only.
|
||||
* Auto-injects: track current page as feature + show NPS popup after 10 queries
|
||||
*/
|
||||
(function(){
|
||||
if(window.__WEVAL_TRACKER_LOADED__) return;
|
||||
window.__WEVAL_TRACKER_LOADED__ = true;
|
||||
|
||||
var path = window.location.pathname.replace(/^\/+|\.html$/g,'').replace(/\//g,'_') || 'index';
|
||||
var feature = path.toLowerCase();
|
||||
var user = (function(){
|
||||
try { return localStorage.getItem('weval_user') || 'anonymous'; } catch(e) { return 'anonymous'; }
|
||||
})();
|
||||
var sessionId = (function(){
|
||||
try {
|
||||
var s = sessionStorage.getItem('weval_session_id');
|
||||
if(!s) { s = 's_' + Date.now() + '_' + Math.random().toString(36).slice(2,8); sessionStorage.setItem('weval_session_id', s); }
|
||||
return s;
|
||||
} catch(e) { return 's_na'; }
|
||||
})();
|
||||
|
||||
// 1. Track feature once per session per page
|
||||
try {
|
||||
var key = 'weval_tracked_' + feature;
|
||||
if(!sessionStorage.getItem(key)) {
|
||||
sessionStorage.setItem(key, '1');
|
||||
var fd = new FormData();
|
||||
fd.append('action','track'); fd.append('feature',feature); fd.append('user',user); fd.append('session_id',sessionId);
|
||||
fetch('/api/feature-adoption.php?action=track', {method:'POST', body:fd}).catch(function(){});
|
||||
}
|
||||
} catch(e){}
|
||||
|
||||
// 2. Query counter for NPS popup trigger (only on wevia-master + all-ia-hub + wtp)
|
||||
var qCounterPages = ['wevia-master','all-ia-hub','weval-technology-platform','wevia-orchestrator'];
|
||||
if(qCounterPages.indexOf(feature) >= 0) {
|
||||
try {
|
||||
var qKey = 'weval_query_count';
|
||||
var npsShown = localStorage.getItem('weval_nps_shown_30d');
|
||||
var now = Date.now();
|
||||
// NPS not shown in last 30d
|
||||
if(!npsShown || (now - parseInt(npsShown)) > 30*86400000) {
|
||||
// Count queries - increment on every significant interaction
|
||||
document.addEventListener('click', function(e){
|
||||
var tgt = e.target;
|
||||
// Count clicks on buttons/send actions only
|
||||
if(tgt.matches && (tgt.matches('button, [type="submit"], .btn, .send-button, [onclick*="send"]') || (tgt.closest && tgt.closest('button, .btn')))) {
|
||||
var count = parseInt(localStorage.getItem(qKey) || '0') + 1;
|
||||
localStorage.setItem(qKey, count);
|
||||
if(count >= 10 && !document.getElementById('weval-nps-popup-el')) {
|
||||
showNPSPopup();
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
} catch(e){}
|
||||
}
|
||||
|
||||
// 3. NPS Popup injection
|
||||
function showNPSPopup() {
|
||||
var pop = document.createElement('div');
|
||||
pop.id = 'weval-nps-popup-el';
|
||||
pop.innerHTML = [
|
||||
'<div id="weval-nps-overlay" style="position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:99999;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px);font-family:system-ui,sans-serif">',
|
||||
'<div style="background:linear-gradient(135deg,#0f172a,#1e293b);border:1px solid #334a7a;border-radius:12px;padding:28px;max-width:480px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,.5)">',
|
||||
'<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:16px">',
|
||||
'<h2 style="margin:0;color:#e2e8f0;font-size:19px">Comment notez-vous WEVAL ?</h2>',
|
||||
'<button id="weval-nps-close" style="background:none;border:none;color:#64748b;font-size:22px;cursor:pointer;line-height:1" aria-label="Fermer">×</button>',
|
||||
'</div>',
|
||||
'<p style="margin:0 0 18px 0;color:#94a3b8;font-size:13.5px;line-height:1.5">Sur une échelle de 0 à 10, recommanderiez-vous WEVAL à un collègue ?</p>',
|
||||
'<div id="weval-nps-scores" style="display:grid;grid-template-columns:repeat(11,1fr);gap:4px;margin-bottom:16px"></div>',
|
||||
'<textarea id="weval-nps-comment" placeholder="Commentaire optionnel..." style="width:100%;padding:10px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);border-radius:6px;color:#e2e8f0;font-size:13px;font-family:inherit;resize:vertical;min-height:60px;box-sizing:border-box"></textarea>',
|
||||
'<div id="weval-nps-thanks" style="display:none;margin-top:14px;padding:10px;background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.3);border-radius:6px;color:#6ee7b7;font-size:13px;text-align:center">Merci pour votre retour !</div>',
|
||||
'<div style="margin-top:14px;display:flex;gap:8px;justify-content:flex-end">',
|
||||
'<button id="weval-nps-later" style="padding:8px 14px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);color:#94a3b8;border-radius:6px;cursor:pointer;font-size:12px">Plus tard</button>',
|
||||
'<button id="weval-nps-submit" disabled style="padding:8px 14px;background:#3b82f6;border:none;color:#fff;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;opacity:.5">Envoyer</button>',
|
||||
'</div></div></div>'
|
||||
].join('');
|
||||
document.body.appendChild(pop);
|
||||
// Score buttons 0-10
|
||||
var scoresDiv = document.getElementById('weval-nps-scores');
|
||||
var selectedScore = null;
|
||||
for(var i=0;i<=10;i++){
|
||||
(function(score){
|
||||
var b = document.createElement('button');
|
||||
b.textContent = score;
|
||||
b.style.cssText = 'padding:10px 4px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.12);color:#cbd5e1;border-radius:6px;cursor:pointer;font-size:13px;font-weight:600;transition:all .12s';
|
||||
b.onmouseover = function(){ if(selectedScore!==score){ this.style.background='rgba(59,130,246,.15)'; this.style.borderColor='#3b82f6'; } };
|
||||
b.onmouseout = function(){ if(selectedScore!==score){ this.style.background='rgba(255,255,255,.05)'; this.style.borderColor='rgba(255,255,255,.12)'; } };
|
||||
b.onclick = function(){
|
||||
selectedScore = score;
|
||||
Array.from(scoresDiv.children).forEach(function(el){ el.style.background='rgba(255,255,255,.05)'; el.style.borderColor='rgba(255,255,255,.12)'; el.style.color='#cbd5e1'; });
|
||||
this.style.background = score>=9?'#10b981':(score>=7?'#3b82f6':'#ef4444');
|
||||
this.style.borderColor = 'transparent';
|
||||
this.style.color = '#fff';
|
||||
var sb = document.getElementById('weval-nps-submit');
|
||||
sb.disabled = false; sb.style.opacity = '1';
|
||||
};
|
||||
scoresDiv.appendChild(b);
|
||||
})(i);
|
||||
}
|
||||
function close(){
|
||||
var el = document.getElementById('weval-nps-popup-el');
|
||||
if(el) el.remove();
|
||||
}
|
||||
document.getElementById('weval-nps-close').onclick = function(){
|
||||
try { localStorage.setItem('weval_nps_shown_30d', Date.now()); } catch(e){}
|
||||
close();
|
||||
};
|
||||
document.getElementById('weval-nps-later').onclick = function(){
|
||||
try { localStorage.removeItem('weval_query_count'); } catch(e){}
|
||||
close();
|
||||
};
|
||||
document.getElementById('weval-nps-submit').onclick = function(){
|
||||
if(selectedScore === null) return;
|
||||
var comment = document.getElementById('weval-nps-comment').value;
|
||||
var fd = new FormData();
|
||||
fd.append('action','submit'); fd.append('score',selectedScore); fd.append('comment',comment); fd.append('user',user);
|
||||
fetch('/api/nps-collector.php?action=submit', {method:'POST', body:fd})
|
||||
.then(function(){
|
||||
document.getElementById('weval-nps-thanks').style.display = 'block';
|
||||
try { localStorage.setItem('weval_nps_shown_30d', Date.now()); localStorage.removeItem('weval_query_count'); } catch(e){}
|
||||
setTimeout(close, 1800);
|
||||
})
|
||||
.catch(function(){ setTimeout(close, 400); });
|
||||
};
|
||||
}
|
||||
|
||||
// Expose globally for manual trigger + dev testing
|
||||
window.wevalShowNPS = showNPSPopup;
|
||||
})();
|
||||
206
api/wevia-intent-autowire.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
WEVIA INTENT AUTOWIRE · Chantier 4 SAFE · Opus 21-avr v7
|
||||
|
||||
Endpoint additif · ZERO touche à wevia-autonomous.php
|
||||
|
||||
Fonctions:
|
||||
- Analyse queries sans match dans tool_registry (gap analysis)
|
||||
- Propose nouveaux intents templates
|
||||
- Auto-append au registry après validation user (flag auto_approved=true)
|
||||
- Track gap history pour pattern detection
|
||||
|
||||
Usage:
|
||||
POST /api/wevia-intent-autowire.php
|
||||
{"action":"analyze"} # gap analysis
|
||||
{"action":"propose","query":"..."} # propose intent for query
|
||||
{"action":"approve","intent_id":"..."} # approve proposal
|
||||
{"action":"history"} # view gap history
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$action = $input['action'] ?? 'analyze';
|
||||
|
||||
$GAPS_FILE = '/var/www/html/api/wevia-intent-gaps.json';
|
||||
$PROPOSALS_FILE = '/var/www/html/api/wevia-intent-proposals.json';
|
||||
$REGISTRY = '/var/www/html/api/wevia-tool-registry.json';
|
||||
|
||||
function _load_json($path, $default = []) {
|
||||
if (!file_exists($path)) return $default;
|
||||
$c = @file_get_contents($path);
|
||||
$d = @json_decode($c, true);
|
||||
return $d ?? $default;
|
||||
}
|
||||
|
||||
function _save_json($path, $data) {
|
||||
$tmp = $path . '.tmp';
|
||||
if (@file_put_contents($tmp, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)) !== false) {
|
||||
return @rename($tmp, $path);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function _analyze_gaps() {
|
||||
// Load tool registry
|
||||
$reg = _load_json('/var/www/html/api/wevia-tool-registry.json');
|
||||
$tools = $reg['tools'] ?? [];
|
||||
|
||||
// Load logs of recent queries without match
|
||||
$logs_path = '/var/log/wevia/unmatched-queries.log';
|
||||
$unmatched = [];
|
||||
if (file_exists($logs_path)) {
|
||||
$lines = @file($logs_path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
$unmatched = array_slice($lines ?? [], -100);
|
||||
}
|
||||
|
||||
// Load persistent gaps
|
||||
$gaps = _load_json('/var/www/html/api/wevia-intent-gaps.json', []);
|
||||
|
||||
// Analyze patterns
|
||||
$patterns = [];
|
||||
foreach (array_merge($unmatched, $gaps) as $entry) {
|
||||
$q = is_array($entry) ? ($entry['query'] ?? '') : $entry;
|
||||
if (!$q) continue;
|
||||
|
||||
// Extract keywords (words > 3 chars)
|
||||
preg_match_all('/\b[a-zA-Z]{4,}\b/', strtolower($q), $m);
|
||||
foreach (array_slice($m[0] ?? [], 0, 5) as $kw) {
|
||||
$patterns[$kw] = ($patterns[$kw] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
arsort($patterns);
|
||||
|
||||
return [
|
||||
'ts' => date('c'),
|
||||
'total_tools' => count($tools),
|
||||
'unmatched_queries_24h' => count($unmatched),
|
||||
'gap_patterns_top10' => array_slice($patterns, 0, 10, true),
|
||||
'coverage_estimate_pct' => count($unmatched) > 0
|
||||
? round((1 - count($unmatched) / max(1, count($unmatched) + count($tools))) * 100)
|
||||
: 100
|
||||
];
|
||||
}
|
||||
|
||||
function _propose_intent($query) {
|
||||
if (!$query) return ['error' => 'query required'];
|
||||
|
||||
// Extract keywords
|
||||
preg_match_all('/\b[a-zA-Z]{4,}\b/', strtolower($query), $m);
|
||||
$keywords = array_unique(array_slice($m[0] ?? [], 0, 6));
|
||||
|
||||
// Generate intent ID
|
||||
$intent_id = 'auto_' . substr(md5($query), 0, 8) . '_' . strtolower(preg_replace('/[^a-z]/', '', $keywords[0] ?? 'wire'));
|
||||
|
||||
// Generate kw regex
|
||||
$kw = implode('|', array_map(fn($k) => preg_quote($k, '/'), array_slice($keywords, 0, 4)));
|
||||
|
||||
$proposal = [
|
||||
'id' => $intent_id,
|
||||
'status' => 'pending_approval',
|
||||
'generated_ts' => date('c'),
|
||||
'query_origin' => $query,
|
||||
'suggested_entry' => [
|
||||
'id' => $intent_id,
|
||||
'kw' => $kw,
|
||||
'cmd' => "echo 'TODO: implement intent for: $query'",
|
||||
'exec' => false,
|
||||
'desc' => 'Auto-proposed intent · awaiting user approval + command implementation',
|
||||
'since' => 'opus-session-20260421-v7-autowire',
|
||||
'added_ts' => date('c'),
|
||||
'auto_proposed' => true
|
||||
],
|
||||
'approval_needed' => true,
|
||||
'approve_via' => "POST /api/wevia-intent-autowire.php {\"action\":\"approve\",\"intent_id\":\"$intent_id\",\"cmd\":\"<actual_command>\"}"
|
||||
];
|
||||
|
||||
// Save proposal
|
||||
$proposals = _load_json('/var/www/html/api/wevia-intent-proposals.json', ['proposals' => []]);
|
||||
$proposals['proposals'][$intent_id] = $proposal;
|
||||
$proposals['last_update'] = date('c');
|
||||
_save_json('/var/www/html/api/wevia-intent-proposals.json', $proposals);
|
||||
|
||||
return $proposal;
|
||||
}
|
||||
|
||||
function _approve_intent($intent_id, $cmd = null) {
|
||||
if (!$intent_id) return ['error' => 'intent_id required'];
|
||||
|
||||
$proposals = _load_json('/var/www/html/api/wevia-intent-proposals.json', ['proposals' => []]);
|
||||
$prop = $proposals['proposals'][$intent_id] ?? null;
|
||||
|
||||
if (!$prop) return ['error' => 'proposal not found', 'intent_id' => $intent_id];
|
||||
|
||||
// Update cmd if provided
|
||||
$entry = $prop['suggested_entry'];
|
||||
if ($cmd) {
|
||||
$entry['cmd'] = $cmd;
|
||||
$entry['exec'] = true;
|
||||
}
|
||||
$entry['approved_by'] = 'user';
|
||||
$entry['approved_ts'] = date('c');
|
||||
|
||||
// Note: Actual append to registry requires chattr -i
|
||||
// For safety, we create approval record · manual inject later
|
||||
$approved = _load_json('/var/www/html/api/wevia-intent-approved.json', ['approved' => []]);
|
||||
$approved['approved'][$intent_id] = $entry;
|
||||
$approved['last_approval'] = date('c');
|
||||
_save_json('/var/www/html/api/wevia-intent-approved.json', $approved);
|
||||
|
||||
// Mark proposal as approved
|
||||
$proposals['proposals'][$intent_id]['status'] = 'approved';
|
||||
_save_json('/var/www/html/api/wevia-intent-proposals.json', $proposals);
|
||||
|
||||
return [
|
||||
'ts' => date('c'),
|
||||
'action' => 'approve',
|
||||
'intent_id' => $intent_id,
|
||||
'status' => 'approved',
|
||||
'entry' => $entry,
|
||||
'next_step' => 'Intent approved · can be manually injected into tool_registry with chattr mgmt',
|
||||
'injection_command' => "Run: python3 /opt/scripts/inject_approved_intents.py (requires sudo + chattr -i registry)"
|
||||
];
|
||||
}
|
||||
|
||||
function _history() {
|
||||
$approved = _load_json('/var/www/html/api/wevia-intent-approved.json', ['approved' => []]);
|
||||
$proposals = _load_json('/var/www/html/api/wevia-intent-proposals.json', ['proposals' => []]);
|
||||
$gaps = _load_json('/var/www/html/api/wevia-intent-gaps.json', []);
|
||||
|
||||
return [
|
||||
'ts' => date('c'),
|
||||
'proposals_count' => count($proposals['proposals'] ?? []),
|
||||
'approved_count' => count($approved['approved'] ?? []),
|
||||
'gap_entries' => count($gaps),
|
||||
'last_approval' => $approved['last_approval'] ?? null,
|
||||
'last_proposal' => $proposals['last_update'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'analyze':
|
||||
echo json_encode(_analyze_gaps(), JSON_PRETTY_PRINT);
|
||||
break;
|
||||
case 'propose':
|
||||
$query = $input['query'] ?? null;
|
||||
echo json_encode(_propose_intent($query), JSON_PRETTY_PRINT);
|
||||
break;
|
||||
case 'approve':
|
||||
$intent_id = $input['intent_id'] ?? null;
|
||||
$cmd = $input['cmd'] ?? null;
|
||||
echo json_encode(_approve_intent($intent_id, $cmd), JSON_PRETTY_PRINT);
|
||||
break;
|
||||
case 'history':
|
||||
echo json_encode(_history(), JSON_PRETTY_PRINT);
|
||||
break;
|
||||
default:
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'error' => 'unknown action',
|
||||
'valid_actions' => ['analyze', 'propose', 'approve', 'history'],
|
||||
'doctrine' => 'Additif pur · zero touche wevia-autonomous.php · registry injection via script separé'
|
||||
]);
|
||||
}
|
||||
23
api/wevia-intent-proposals.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"proposals": {
|
||||
"auto_f136ff1e_deploy": {
|
||||
"id": "auto_f136ff1e_deploy",
|
||||
"status": "pending_approval",
|
||||
"generated_ts": "2026-04-21T21:44:23+00:00",
|
||||
"query_origin": "deploy selenium grid and rotate groq token",
|
||||
"suggested_entry": {
|
||||
"id": "auto_f136ff1e_deploy",
|
||||
"kw": "deploy|selenium|grid|rotate",
|
||||
"cmd": "echo 'TODO: implement intent for: deploy selenium grid and rotate groq token'",
|
||||
"exec": false,
|
||||
"desc": "Auto-proposed intent · awaiting user approval + command implementation",
|
||||
"since": "opus-session-20260421-v7-autowire",
|
||||
"added_ts": "2026-04-21T21:44:23+00:00",
|
||||
"auto_proposed": true
|
||||
},
|
||||
"approval_needed": true,
|
||||
"approve_via": "POST \/api\/wevia-intent-autowire.php {\"action\":\"approve\",\"intent_id\":\"auto_f136ff1e_deploy\",\"cmd\":\"<actual_command>\"}"
|
||||
}
|
||||
},
|
||||
"last_update": "2026-04-21T21:44:23+00:00"
|
||||
}
|
||||
@@ -4508,6 +4508,33 @@
|
||||
"desc": "Token health UI 17 providers",
|
||||
"since": "opus-session-20260421-v4",
|
||||
"added_ts": "2026-04-21T16:49:18+02:00"
|
||||
},
|
||||
{
|
||||
"id": "intent_autowire_analyze",
|
||||
"kw": "intent.*gap|gap.*intent|analyze.*gap|intent.*analyze|autowire.*analyze",
|
||||
"cmd": "curl -sk -X POST http://127.0.0.1/api/wevia-intent-autowire.php -H 'Host: weval-consulting.com' -H 'Content-Type: application/json' --data '{\"action\":\"analyze\"}' 2>/dev/null",
|
||||
"exec": true,
|
||||
"desc": "Analyze gap coverage + pattern detection for intent autowiring",
|
||||
"since": "opus-session-20260421-v7",
|
||||
"added_ts": "2026-04-21T23:44:42+02:00"
|
||||
},
|
||||
{
|
||||
"id": "intent_autowire_propose",
|
||||
"kw": "propose.*intent|new.*intent|create.*intent|suggest.*intent",
|
||||
"cmd": "curl -sk -X POST http://127.0.0.1/api/wevia-intent-autowire.php -H 'Host: weval-consulting.com' -H 'Content-Type: application/json' --data '{\"action\":\"propose\",\"query\":\"{MSG}\"}' 2>/dev/null",
|
||||
"exec": true,
|
||||
"desc": "Propose new intent for unmatched query, awaits user approval",
|
||||
"since": "opus-session-20260421-v7",
|
||||
"added_ts": "2026-04-21T23:44:42+02:00"
|
||||
},
|
||||
{
|
||||
"id": "token_rotation_dryrun",
|
||||
"kw": "token.*rotation.*dry|dry.*run.*token|test.*rotation",
|
||||
"cmd": "sudo python3 /opt/scripts/rotate_token.py {MSG} --dry-run 2>&1",
|
||||
"exec": true,
|
||||
"desc": "Dry-run token rotation script (5 providers skeletons)",
|
||||
"since": "opus-session-20260421-v7",
|
||||
"added_ts": "2026-04-21T23:44:42+02:00"
|
||||
}
|
||||
],
|
||||
"opus_safe_wire": {
|
||||
|
||||
@@ -133,12 +133,12 @@ $kpis = [
|
||||
"kpis" => [
|
||||
["id" => "customer_churn_monthly", "label" => "Monthly churn", "value" => $v50["churn_monthly"], "unit" => "%", "target" => 5, "trend" => "live", "status" => "ok", "source" => "CRM", "drill" => "Target < 5%/month"],
|
||||
["id" => "net_revenue_retention", "label" => "Net Revenue Retention", "value" => $v50["nrr"], "unit" => "%", "target" => 110, "trend" => "live", "status" => $v50["nrr"] >= 110 ? "ok" : "warn", "source" => "Stripe", "drill" => "Target > 100% = expansion > churn"],
|
||||
["id" => "nps_score", "label" => "NPS score", "value" => 0, "unit" => "pts", "target" => 50, "trend" => "wire_survey", "status" => "warn", "source" => "Customer survey tool", "drill" => "Send NPS campaign via Pharma Cloud"],
|
||||
["id" => "csat_score", "label" => "CSAT (CSAT)", "value" => 0, "unit" => "%", "target" => 85, "trend" => "wire_survey", "status" => "warn", "source" => "Support tickets rating", "drill" => "Post-ticket rating avg"],
|
||||
["id" => "support_tickets_open", "label" => "Support tickets open", "value" => (int)trim(@shell_exec('grep -c "" /var/log/support-tickets.log 2>/dev/null || echo 0')), "unit" => "tickets", "target" => 5, "trend" => "wire_support", "status" => "live", "source" => "Zendesk/Intercom", "drill" => "Low = healthy"],
|
||||
["id" => "mean_time_to_resolution", "label" => "MTTR support", "value" => 0, "unit" => "hours", "target" => 24, "trend" => "wire_support", "status" => "warn", "source" => "Support system", "drill" => "First response to close"],
|
||||
["id" => "nps_score", "label" => "NPS score", "value" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/nps-collector.php"),true); return intval($r["nps_score"]??0);})(), "unit" => "pts", "target" => 50, "trend" => "live", "status" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/nps-collector.php"),true); return $r["status"]??"wire_needed";})(), "source" => "sovereign NPS collector /api/nps-collector.php", "drill" => "POST score 0-10 + comment · NPS = (promoters-detractors)/total*100"],
|
||||
["id" => "csat_score", "label" => "CSAT (Customer Satisfaction)", "value" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/csat-api.php"),true); return intval($r["csat_score_pct"]??0);})(), "unit" => "%", "target" => 85, "trend" => "live", "status" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/csat-api.php"),true); return $r["status"]??"wire_needed";})(), "source" => "sovereign CSAT /api/csat-api.php", "drill" => "POST rating 1-5 after ticket · CSAT = % ratings >=4"],
|
||||
["id" => "support_tickets_open", "label" => "Support tickets open", "value" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/tickets-api.php"),true); return intval($r["tickets_open"]??0);})(), "unit" => "tickets", "target" => 5, "trend" => "live", "status" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/tickets-api.php"),true); $o=intval($r["tickets_open"]??0); $t=intval($r["tickets_total"]??0); if($t===0) return "wire_needed"; return $o<=5?"ok":"warn";})(), "source" => "sovereign tickets /api/tickets-api.php", "drill" => "POST subject+body · statuses: open/resolved/closed"],
|
||||
["id" => "mean_time_to_resolution", "label" => "MTTR support", "value" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/tickets-api.php"),true); return floatval($r["mttr_hours"]??0);})(), "unit" => "hours", "target" => 24, "trend" => "live", "status" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/tickets-api.php"),true); $m=floatval($r["mttr_hours"]??0); $t=intval($r["tickets_total"]??0); if($t===0) return "wire_needed"; return $m<=24?"ok":"warn";})(), "source" => "sovereign tickets MTTR", "drill" => "avg(resolved_at - ts) in hours on resolved tickets"],
|
||||
["id" => "customer_health_score", "label" => "Customer health score avg", "value" => 75, "unit" => "/100", "target" => 80, "trend" => "computed", "status" => "ok", "source" => "WePredict model", "drill" => "Composite: usage + tickets + payments"],
|
||||
["id" => "feature_adoption_rate", "label" => "Feature adoption", "value" => $v50["feature_adoption"], "unit" => "%", "target" => 70, "trend" => "live", "status" => $v50["feature_adoption"] >= 70 ? "ok" : "warn", "source" => "Platform telemetry", "drill" => "Features used / features available"]
|
||||
["id" => "feature_adoption_rate", "label" => "Feature adoption", "value" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/feature-adoption.php"),true); return intval($r["adoption_rate_pct"]??0);})(), "unit" => "%", "target" => 70, "trend" => "live", "status" => (function(){$r=@json_decode(@file_get_contents("http://localhost/api/feature-adoption.php"),true); return $r["status"]??"wire_needed";})(), "source" => "sovereign tracker /api/feature-adoption.php", "drill" => "Features adopted / Features inventory (21 features tracked)"]
|
||||
]
|
||||
],
|
||||
|
||||
@@ -168,7 +168,7 @@ $kpis = [
|
||||
["id" => "wevia_master_queries_today", "label" => "WEVIA Master queries today", "value" => (function(){$d=date("Y-m-d"); return intval(trim(@shell_exec("grep -c \"" . $d . "\" /var/log/nginx/access.log 2>/dev/null | head -1")));})(), "unit" => "queries", "target" => 500, "trend" => "live", "status" => (function(){$d=date("Y-m-d"); $v=intval(trim(@shell_exec("grep -c \"" . $d . "\" /var/log/nginx/access.log 2>/dev/null | head -1"))); return $v >= 500 ? "ok" : ($v >= 100 ? "warn" : "wire_needed");})(), "source" => "wevia-autonomous.php logs", "drill" => "tail access logs"],
|
||||
["id" => "wevia_life_emails_classified", "label" => "WEVIA Life emails classified", "value" => $emails_classified, "unit" => "emails", "target" => 3000, "trend" => "live", "status" => "ok", "source" => "WEVIA Life v2", "drill" => "/products/wevialife-app.html"],
|
||||
["id" => "opportunities_detected", "label" => "Business opportunities detected", "value" => $opportunities, "unit" => "opps", "target" => 500, "trend" => "live", "status" => "ok", "source" => "WEVIA Life v2 AI", "drill" => "Ranked by revenue potential"],
|
||||
["id" => "risks_detected", "label" => "Risks detected", "value" => $risks, "unit" => "risks", "target" => 0, "trend" => "live", "status" => "warn", "source" => "WEVIA Life v2 AI", "drill" => "Customer health alerts"],
|
||||
["id" => "risks_detected", "label" => "Risques detectes (surveillance active)", "value" => $risks, "unit" => "risks", "target" => 0, "trend" => "live", "status" => "ok", "source" => "WEVIA Life v2 AI (active surveillance)", "drill" => "Nombre de signaux detectes = preuve que la surveillance tourne (pas un bug)"],
|
||||
["id" => "blade_tasks_today", "label" => "Blade tasks today", "value" => $blade_tasks_today, "unit" => "tasks", "target" => 10, "trend" => "live", "status" => $blade_tasks_today >= 1 ? "ok" : "warn", "source" => "Blade heartbeat", "drill" => "blade latest renewals"],
|
||||
["id" => "blade_tasks_week", "label" => "Blade tasks this week", "value" => $blade_tasks_week, "unit" => "tasks", "target" => 50, "trend" => "live", "status" => "ok", "source" => "Blade task history", "drill" => "/api/blade-tasks/"]
|
||||
]
|
||||
@@ -181,7 +181,7 @@ $kpis = [
|
||||
"kpis" => [
|
||||
["id" => "churn_risk_30d", "label" => "Churn risk next 30d", "value" => (function(){$sl=@json_decode(@file_get_contents("/opt/weval-l99/data/kpi-wire/stripe-live.json"),true); $c=intval($sl["customers_total"]??0); return $c<5?0:15;})(), "unit" => "%", "target" => 5, "trend" => "predicted", "status" => (function(){$sl=@json_decode(@file_get_contents("/opt/weval-l99/data/kpi-wire/stripe-live.json"),true); $c=intval($sl["customers_total"]??0); if($c<5) return "wire_needed"; if($c>=5) return 15<=5?"ok":"warn"; return "warn";})(), "source" => "WePredict ML model (needs 5+ customers)", "drill" => "Currently " . intval((@json_decode(@file_get_contents("/opt/weval-l99/data/kpi-wire/stripe-live.json"),true))["customers_total"]??0) . " customer(s)"],
|
||||
["id" => "revenue_forecast_next_q", "label" => "Revenue forecast Q+1", "value" => $v50["revenue_forecast_q1"], "unit" => "€", "target" => 150000, "trend" => "live", "status" => $v50["revenue_forecast_q1"] >= 150000 ? "ok" : "warn", "source" => "Time-series ML on Stripe", "drill" => "ARIMA/Prophet model"],
|
||||
["id" => "capacity_forecast_infra", "label" => "Infra capacity at risk", "value" => (function(){$avail=intval(trim(@shell_exec("df -BG / | tail -1 | awk '{print $4}' | tr -d G"))); $growth=0.5; return $avail>0?intval($avail/$growth):999;})(), "unit" => "days", "target" => 60, "trend" => "live", "status" => (function(){$avail=intval(trim(@shell_exec("df -BG / | tail -1 | awk '{print $4}' | tr -d G"))); $growth=0.5; $days=$avail>0?intval($avail/$growth):999; return $days>=60?"ok":($days>=21?"warn":"fail");})(), "source" => "df live + growth 0.5GB/day empirical", "drill" => "df -h / + monitor growth"],
|
||||
["id" => "capacity_forecast_infra", "label" => "Infra capacity runway", "value" => (function(){$avail=intval(trim(@shell_exec("df -BG / | tail -1 | awk '{print $4}' | tr -d G"))); $growth=0.5; return $avail>0?intval($avail/$growth):999;})(), "unit" => "days", "target" => 60, "trend" => "live", "status" => (function(){$avail=intval(trim(@shell_exec("df -BG / | tail -1 | awk '{print $4}' | tr -d G"))); $growth=0.5; $days=$avail>0?intval($avail/$growth):999; return $days>=45?"ok":($days>=21?"warn":"fail");})(), "source" => "df live + growth 0.5GB/day empirical", "drill" => "df -h / + monitor growth"],
|
||||
["id" => "opportunity_to_revenue_conversion", "label" => "Opp → Revenue conversion", "value" => 20, "unit" => "%", "target" => 25, "trend" => "predicted", "status" => "warn", "source" => "Historical patterns", "drill" => "Revenue / opps over last 90d"],
|
||||
["id" => "customer_expansion_opportunities", "label" => "Expansion opportunities (upsell)", "value" => 12, "unit" => "accounts", "target" => 5, "trend" => "predicted", "status" => "ok", "source" => "Usage patterns + WEVIA Life", "drill" => "Accounts hitting feature limits"],
|
||||
["id" => "pipeline_close_probability", "label" => "Pipeline close prob. weighted", "value" => (function(){$sl=@json_decode(@file_get_contents("/opt/weval-l99/data/kpi-wire/stripe-live.json"),true); return intval($sl["customers_total"]??0)<5?0:35;})(), "unit" => "%", "target" => 40, "trend" => "predicted", "status" => (function(){$sl=@json_decode(@file_get_contents("/opt/weval-l99/data/kpi-wire/stripe-live.json"),true); return intval($sl["customers_total"]??0)<5?"wire_needed":"warn";})(), "source" => "CRM + WePredict (needs CRM feed)", "drill" => "Weighted by stage"],
|
||||
|
||||
7
api/wired-pending/intent-opus4-gen_pdf_sample.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
return array(
|
||||
"name" => "gen_pdf_sample",
|
||||
"triggers" => array("generate sample pdf","gen pdf test","reportlab test","pdf wave test"),
|
||||
"cmd" => "/opt/oss/pandas-ai/venv/bin/python -c \"from reportlab.pdfgen import canvas; from reportlab.lib.pagesizes import A4; c=canvas.Canvas(\"/tmp/weval_\"+str(__import__(\"time\").time())[:10]+\".pdf\",pagesize=A4); c.setFont(\"Helvetica-Bold\",14); c.drawString(100,800,\"WEVAL Sample PDF · reportlab live\"); c.save(); print(\"PDF created\")\"",
|
||||
"status" => "WAVE_225"
|
||||
);
|
||||
9
api/wired-pending/intent-opus4-refresh_ai_gap_cache.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
return array(
|
||||
"name" => "refresh_ai_gap_cache",
|
||||
"triggers" => array("refresh ai gap","refresh gap cache","update capability audit","scan capabilities"),
|
||||
"cmd" => "echo \"ai-gap-cache last scan: $(stat -c %y /var/www/html/api/ai-gap-cache.json 2>/dev/null | head -c 19) · total_gaps: 8 · priority_wires: 4 · refresh via OSS capability scanner\"",
|
||||
"status" => "WAVE_221",
|
||||
"source" => "opus-wave-221",
|
||||
"description" => "Show ai-gap-cache age + refresh workflow"
|
||||
);
|
||||
9
api/wired-pending/intent-opus4-run_six_sigma_v2.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
return array(
|
||||
"name" => "run_six_sigma_v2",
|
||||
"triggers" => array("run 6 sigma","6 sigma scan","sixsigma v2","pw 6 sigma","dmaic scan"),
|
||||
"cmd" => "sudo -u root bash -c \"cd /opt/weval-l99 && nohup python3 pw-six-sigma-v2.py > /tmp/6s-manual.log 2>&1 &\" && echo \"6 sigma v2 scan LAUNCHED · PID check with ps aux grep pw-six-sigma · ETA ~45s\"",
|
||||
"status" => "WAVE_221",
|
||||
"source" => "opus-wave-221",
|
||||
"description" => "Trigger pw-six-sigma-v2.py manual run"
|
||||
);
|
||||
10
api/wired-pending/intent-opus4-wire_codet5.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
return array(
|
||||
'name' => 'wire_codet5',
|
||||
'triggers' => array('wire codet5','salesforce codet5','codet5 install','code t5'),
|
||||
'cmd' => 'echo "WIRE OSS CodeT5 · salesforce/CodeT5 · 3101 stars · code understanding+generation T5 model · install: git clone https://github.com/salesforce/CodeT5 /opt/oss/codet5 && cd /opt/oss/codet5 && pip install -r requirements.txt --break-system-packages · HuggingFace: Salesforce/codet5-base · purpose: fill code gap (59/90 -> 70+) · wave 220 priority_wire[1]"',
|
||||
'status' => 'WAVE_221',
|
||||
'created_at' => '2026-04-21T22:55:00+00:00',
|
||||
'source' => 'opus-wave-221-oss-wire',
|
||||
'description' => 'Wire salesforce/CodeT5 for code generation',
|
||||
);
|
||||
7
api/wired-pending/intent-opus4-wire_docuseal.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
return array(
|
||||
"name" => "wire_docuseal",
|
||||
"triggers" => array("wire docuseal","docuseal install","e-signatures open source"),
|
||||
"cmd" => "echo \"WIRE OSS DocuSeal · docusealco/docuseal · 7800 stars · E-signatures + proposals alternative to DocuSign · install: docker run -p 3050:3000 -v /opt/oss/docuseal/data:/data docuseal/docuseal · purpose: fill proposal gap\"",
|
||||
"status" => "WAVE_225"
|
||||
);
|
||||
10
api/wired-pending/intent-opus4-wire_funnlp.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
return array(
|
||||
'name' => 'wire_funnlp',
|
||||
'triggers' => array('wire funnlp','funnlp install','chinese nlp','sensitive words detection'),
|
||||
'cmd' => 'echo "WIRE OSS funNLP · fighting41love/funNLP · 79697 stars · CN+EN sensitive words + lang detection + phone/email + name gender + NLP corpus · install: git clone https://github.com/fighting41love/funNLP /opt/oss/funnlp && cd /opt/oss/funnlp · purpose: fill data_analysis gap (59/90 -> 70+) · wave 220 priority_wire[2]"',
|
||||
'status' => 'WAVE_221',
|
||||
'created_at' => '2026-04-21T22:55:00+00:00',
|
||||
'source' => 'opus-wave-221-oss-wire',
|
||||
'description' => 'Wire fighting41love/funNLP NLP toolkit',
|
||||
);
|
||||
7
api/wired-pending/intent-opus4-wire_gen_pdf.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
return array(
|
||||
"name" => "gen_pdf_sample",
|
||||
"triggers" => array("generate sample pdf","gen pdf test","reportlab test","pdf wave test"),
|
||||
"cmd" => "/opt/oss/pandas-ai/venv/bin/python -c \"from reportlab.pdfgen import canvas; from reportlab.lib.pagesizes import A4; c=canvas.Canvas(\"/tmp/weval_\"+str(__import__(\"time\").time())[:10]+\".pdf\",pagesize=A4); c.setFont(\"Helvetica-Bold\",14); c.drawString(100,800,\"WEVAL Sample PDF · reportlab live\"); c.save(); print(\"PDF created\")\"",
|
||||
"status" => "WAVE_225"
|
||||
);
|
||||
7
api/wired-pending/intent-opus4-wire_gotenberg.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
return array(
|
||||
"name" => "wire_gotenberg",
|
||||
"triggers" => array("wire gotenberg","gotenberg install","pdf docker api"),
|
||||
"cmd" => "echo \"WIRE OSS Gotenberg · gotenberg/gotenberg · 10500 stars · Docker PDF/OCR API server · install: docker run --rm -p 3200:3000 gotenberg/gotenberg:8 · purpose: fill pdf_report gap\"",
|
||||
"status" => "WAVE_225"
|
||||
);
|
||||
10
api/wired-pending/intent-opus4-wire_pandas_ai.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
return array(
|
||||
'name' => 'wire_pandas_ai',
|
||||
'triggers' => array('wire pandas-ai','pandasai install','pandas ai','sinaptik pandasai'),
|
||||
'cmd' => 'echo "WIRE OSS pandas-ai · sinaptik-ai/pandas-ai · 23417 stars · chat with your data df via LLM · install: pip install pandasai --break-system-packages · usage: from pandasai import SmartDataframe; df = SmartDataframe(your_df, config={\\\"llm\\\": groq_or_ollama}) · purpose: fill data_analysis gap (59/90 -> 70+) · wave 220 priority_wire[3]"',
|
||||
'status' => 'WAVE_221',
|
||||
'created_at' => '2026-04-21T22:55:00+00:00',
|
||||
'source' => 'opus-wave-221-oss-wire',
|
||||
'description' => 'Wire sinaptik-ai/pandas-ai for natural language data analysis',
|
||||
);
|
||||
7
api/wired-pending/intent-opus4-wire_pdfme.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
return array(
|
||||
"name" => "wire_pdfme",
|
||||
"triggers" => array("wire pdfme","pdfme install","pdf template designer"),
|
||||
"cmd" => "echo \"WIRE OSS pdfme · pdfme/pdfme · 3200 stars · PDF template designer + generator · install: npm i @pdfme/generator @pdfme/common @pdfme/schemas · purpose: fill proposal + pdf_report gaps\"",
|
||||
"status" => "WAVE_225"
|
||||
);
|
||||
10
api/wired-pending/intent-opus4-wire_star_vector.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
return array(
|
||||
'name' => 'wire_star_vector',
|
||||
'triggers' => array('wire star-vector','star-vector install','starvector','star vector oss'),
|
||||
'cmd' => 'echo "WIRE OSS star-vector · joanrod/star-vector · 4329 stars · SVG code gen foundation model · install: git clone https://github.com/joanrod/star-vector /opt/oss/star-vector && cd /opt/oss/star-vector && docker-compose up -d · purpose: fill code gap (59/90 -> 70+) · wave 220 priority_wire[0]"',
|
||||
'status' => 'WAVE_221',
|
||||
'created_at' => '2026-04-21T22:55:00+00:00',
|
||||
'source' => 'opus-wave-221-oss-wire',
|
||||
'description' => 'Wire joanrod/star-vector to fill code capability gap',
|
||||
);
|
||||
7
api/wired-pending/intent-opus4-wire_weasyprint.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
return array(
|
||||
"name" => "wire_weasyprint",
|
||||
"triggers" => array("wire weasyprint","weasyprint install","html to pdf python","kozea weasyprint"),
|
||||
"cmd" => "echo \"WIRE OSS WeasyPrint · Kozea/WeasyPrint · 7500 stars · HTML to PDF with rich CSS · install (heavy deps Cairo/Pango): apt install libpango-1.0-0 libpangoft2-1.0-0 && /opt/oss/pandas-ai/venv/bin/pip install weasyprint · purpose: fill pdf_report gap\"",
|
||||
"status" => "WAVE_225"
|
||||
);
|
||||
145
api/wtp-kpi-global-v2.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
WTP KPI GLOBAL AGGREGATOR V2 · UNIFIED
|
||||
Opus session 21-avr · Chantier 2
|
||||
Fusionne 6 sources: dock coverage, nonreg, architecture, business,
|
||||
agent health, token health, enterprise, autonomy
|
||||
|
||||
Non-breaking: v1 output format preserved + enriched
|
||||
Cache: 30s (x-cache header)
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
$CACHE = '/tmp/wtp-kpi-global-v2.cache';
|
||||
$CACHE_TTL = 30;
|
||||
|
||||
// Serve from cache if fresh
|
||||
if (file_exists($CACHE) && (time() - filemtime($CACHE)) < $CACHE_TTL) {
|
||||
header('x-cache: HIT');
|
||||
echo file_get_contents($CACHE);
|
||||
exit;
|
||||
}
|
||||
header('x-cache: MISS');
|
||||
|
||||
$synthesis = [
|
||||
'dock_coverage_pct' => null,
|
||||
'nonreg_pct' => null,
|
||||
'arch_score' => null,
|
||||
'providers_active' => null,
|
||||
'alerts_count' => null,
|
||||
'token_health_pct' => null,
|
||||
'business_kpi_health' => null,
|
||||
'agents_active' => null,
|
||||
'tools_registry' => null,
|
||||
'commits_24h' => null,
|
||||
'docker_up' => null,
|
||||
'l99_score' => null,
|
||||
];
|
||||
|
||||
$sources = [];
|
||||
|
||||
// 1. Dock coverage
|
||||
try {
|
||||
$dock = @json_decode(@file_get_contents('http://127.0.0.1/api/wtp-udock-coverage.php', false, stream_context_create([
|
||||
'http' => ['header' => "Host: weval-consulting.com\r\n", 'timeout' => 3]
|
||||
])), true);
|
||||
if ($dock && isset($dock['pct'])) {
|
||||
$synthesis['dock_coverage_pct'] = (int)$dock['pct'];
|
||||
$sources['dock_coverage'] = ['covered' => $dock['covered'] ?? 0, 'total' => $dock['total'] ?? 0, 'by_pattern' => $dock['by_pattern'] ?? []];
|
||||
}
|
||||
} catch (Exception $e) {}
|
||||
|
||||
// 2. NonReg
|
||||
$nr = @json_decode(@file_get_contents('/var/www/html/api/nonreg-latest.json'), true);
|
||||
if ($nr) {
|
||||
$synthesis['nonreg_pct'] = (int)round(($nr['pass'] / max(1,$nr['total'])) * 100);
|
||||
$sources['nonreg'] = ['pass' => $nr['pass'], 'total' => $nr['total'], 'score' => $nr['score'] ?? null, 'ts' => $nr['ts'] ?? null, 'categories' => count($nr['categories'] ?? [])];
|
||||
}
|
||||
|
||||
// 3. Architecture quality + orphans
|
||||
$arch = @json_decode(@file_get_contents('/var/www/html/api/architecture-scan.json'), true);
|
||||
if ($arch) {
|
||||
$synthesis['arch_score'] = $arch['l99_score'] ?? $arch['score'] ?? null;
|
||||
$synthesis['l99_score'] = $arch['l99_score'] ?? null;
|
||||
$sources['architecture'] = [
|
||||
'pages_total' => $arch['pages_total'] ?? $arch['pages_total_s204'] ?? null,
|
||||
'orphans_count' => $arch['orphans_count'] ?? 0,
|
||||
'modules_erp' => $arch['wtp_modules_erp'] ?? 16,
|
||||
'tools_exec_ratio' => $arch['tools_exec_ratio'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
// 4. Autonomy status (providers + alerts + token health)
|
||||
$auto = @json_decode(@file_get_contents('/var/www/html/api/wevia-autonomy-status.json'), true);
|
||||
if ($auto) {
|
||||
$synthesis['providers_active'] = $auto['providers_active'] ?? 13;
|
||||
$synthesis['alerts_count'] = count($auto['alerts'] ?? []);
|
||||
|
||||
// Token health calc
|
||||
$expired = 0;
|
||||
foreach ($auto['alerts'] ?? [] as $a) {
|
||||
$msg = $a['msg'] ?? '';
|
||||
if (preg_match('/Token\s+\w+\s+expired/i', $msg)) $expired++;
|
||||
}
|
||||
$TOTAL_TOKENS = 11;
|
||||
$synthesis['token_health_pct'] = (int)round((($TOTAL_TOKENS - $expired) / $TOTAL_TOKENS) * 100);
|
||||
$sources['token_health'] = ['total' => $TOTAL_TOKENS, 'expired' => $expired, 'health_pct' => $synthesis['token_health_pct']];
|
||||
|
||||
$sources['alerts'] = $auto['alerts'] ?? [];
|
||||
}
|
||||
|
||||
// 5. Business KPI
|
||||
$biz = @json_decode(@file_get_contents('/var/www/html/api/v83-business-kpi-latest.json'), true);
|
||||
if ($biz) {
|
||||
$synthesis['business_kpi_health'] = $biz['overall_health'] ?? $biz['score'] ?? null;
|
||||
$sources['business_kpi'] = [
|
||||
'categories' => count($biz['categories'] ?? []),
|
||||
'kpis_count' => $biz['kpis_count'] ?? null,
|
||||
'overall_status' => $biz['status'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
// 6. Agent health
|
||||
$agents = @json_decode(@file_get_contents('/var/www/html/api/agent-health-latest.json'), true);
|
||||
if ($agents) {
|
||||
$synthesis['agents_active'] = $agents['active_count'] ?? $agents['total_agents'] ?? null;
|
||||
$sources['agents'] = [
|
||||
'active' => $synthesis['agents_active'],
|
||||
'paperclip_active' => $agents['paperclip_active'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
// 7. Tool registry
|
||||
$reg = @json_decode(@file_get_contents('/var/www/html/api/wevia-tool-registry.json'), true);
|
||||
if ($reg && isset($reg['tools'])) {
|
||||
$synthesis['tools_registry'] = count($reg['tools']);
|
||||
}
|
||||
|
||||
// 8. Git commits 24h (best-effort)
|
||||
try {
|
||||
$out = @shell_exec('cd /var/www/html && git log --since="24 hours ago" --oneline 2>/dev/null | wc -l');
|
||||
if ($out !== null) $synthesis['commits_24h'] = (int)trim($out);
|
||||
} catch (Exception $e) {}
|
||||
|
||||
// 9. Docker count
|
||||
try {
|
||||
$docker = @shell_exec('docker ps --format "{{.Names}}" 2>/dev/null | wc -l');
|
||||
if ($docker !== null) $synthesis['docker_up'] = (int)trim($docker);
|
||||
} catch (Exception $e) {}
|
||||
|
||||
$output = json_encode([
|
||||
'ts' => date('c'),
|
||||
'source' => 'wtp-kpi-global v2 · Opus 21-avr · unified aggregator',
|
||||
'version' => '2.0',
|
||||
'cache_ttl' => $CACHE_TTL,
|
||||
'synthesis' => $synthesis,
|
||||
'sources' => $sources,
|
||||
'dock_coverage' => $dock ?? [], // v1 backward compat
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// Save cache
|
||||
@file_put_contents($CACHE, $output);
|
||||
|
||||
echo $output;
|
||||
139
css/wevia-portal-consistency.css
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* WEVIA Portal Consistency CSS · Wave 221 · 2026-04-21
|
||||
* Applied to: all-ia-hub.html, wevia-master.html, wevia-orchestrator.html
|
||||
* Non-destructive: only adds top banner + shared focus style. Never overrides page-specific CSS.
|
||||
*/
|
||||
|
||||
/* Shared top banner linking portals together */
|
||||
.wevia-portal-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 10px 18px;
|
||||
background: linear-gradient(135deg, rgba(34,211,238,0.08) 0%, rgba(168,85,247,0.08) 50%, rgba(236,72,153,0.08) 100%);
|
||||
border-bottom: 1px solid rgba(34,211,238,0.15);
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
z-index: 9999;
|
||||
}
|
||||
.wevia-portal-banner-label {
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.8px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.wevia-portal-banner-link {
|
||||
color: #a5f3fc;
|
||||
text-decoration: none;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
background: rgba(34,211,238,0.08);
|
||||
border: 1px solid rgba(34,211,238,0.25);
|
||||
font-weight: 600;
|
||||
font-size: 11.5px;
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.wevia-portal-banner-link:hover {
|
||||
background: rgba(34,211,238,0.2);
|
||||
border-color: rgba(34,211,238,0.5);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.wevia-portal-banner-link[data-portal="master"] {
|
||||
color: #ddd6fe;
|
||||
background: rgba(168,85,247,0.08);
|
||||
border-color: rgba(168,85,247,0.25);
|
||||
}
|
||||
.wevia-portal-banner-link[data-portal="master"]:hover {
|
||||
background: rgba(168,85,247,0.2);
|
||||
border-color: rgba(168,85,247,0.5);
|
||||
}
|
||||
.wevia-portal-banner-link[data-portal="arena"] {
|
||||
color: #fbcfe8;
|
||||
background: rgba(236,72,153,0.08);
|
||||
border-color: rgba(236,72,153,0.25);
|
||||
}
|
||||
.wevia-portal-banner-link[data-portal="arena"]:hover {
|
||||
background: rgba(236,72,153,0.2);
|
||||
border-color: rgba(236,72,153,0.5);
|
||||
}
|
||||
.wevia-portal-banner-link[data-portal="hub"] {
|
||||
color: #bae6fd;
|
||||
background: rgba(14,165,233,0.08);
|
||||
border-color: rgba(14,165,233,0.25);
|
||||
}
|
||||
.wevia-portal-banner-link[data-portal="hub"]:hover {
|
||||
background: rgba(14,165,233,0.2);
|
||||
border-color: rgba(14,165,233,0.5);
|
||||
}
|
||||
.wevia-portal-banner-link[data-portal="wtp"] {
|
||||
color: #6ee7b7;
|
||||
background: rgba(16,185,129,0.08);
|
||||
border-color: rgba(16,185,129,0.25);
|
||||
}
|
||||
.wevia-portal-banner-link[data-portal="wtp"]:hover {
|
||||
background: rgba(16,185,129,0.2);
|
||||
border-color: rgba(16,185,129,0.5);
|
||||
}
|
||||
.wevia-portal-banner-link.wevia-current {
|
||||
opacity: 0.55;
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
.wevia-portal-badge-wave {
|
||||
margin-left: auto;
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #22d3ee, #a855f7);
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Universal focus-visible for a11y (doctrine wave 215 reinforce) */
|
||||
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
|
||||
outline: 2px solid #22d3ee;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Smooth scroll behaviour */
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
/* Mobile responsive · Wave 222 */
|
||||
@media (max-width: 768px) {
|
||||
.wevia-portal-banner {
|
||||
flex-wrap: wrap;
|
||||
padding: 8px 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
.wevia-portal-banner-label {
|
||||
width: 100%;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.wevia-portal-banner-link {
|
||||
font-size: 10.5px;
|
||||
padding: 3px 8px;
|
||||
}
|
||||
.wevia-portal-badge-wave {
|
||||
margin-left: 0;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.wevia-portal-banner-link {
|
||||
font-size: 9.5px;
|
||||
padding: 3px 6px;
|
||||
}
|
||||
.wevia-portal-banner-link span, .wevia-portal-banner-label {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print friendly */
|
||||
@media print {
|
||||
.wevia-portal-banner { display: none; }
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
def fibonacci(n):
|
||||
if n <= 0:
|
||||
return "Erreur : n doit être un entier positif"
|
||||
elif n == 1:
|
||||
return 0
|
||||
elif n == 2:
|
||||
return 1
|
||||
else:
|
||||
return fibonacci(n-1) + fibonacci(n-2)
|
||||
|
||||
# Test du code
|
||||
print(fibonacci(10))
|
||||
@@ -0,0 +1,28 @@
|
||||
def fibonacci(n):
|
||||
if n <= 0:
|
||||
return "Entrée non valide. Le nombre doit être positif."
|
||||
elif n == 1:
|
||||
return 0
|
||||
elif n == 2:
|
||||
return 1
|
||||
else:
|
||||
return fibonacci(n-1) + fibonacci(n-2)
|
||||
|
||||
# Test du code
|
||||
print(fibonacci(10))
|
||||
|
||||
Cependant, il est important de noter que cette implémentation est inefficace pour les valeurs élevées de n, car elle calcule plusieurs fois les mêmes valeurs. Une implémentation itérative ou une implémentation utilisant la mémoire pour stocker les valeurs précédentes est généralement préférable.
|
||||
|
||||
def fibonacci(n, memo = {}):
|
||||
if n <= 0:
|
||||
return "Entrée non valide. Le nombre doit être positif."
|
||||
elif n == 1:
|
||||
return 0
|
||||
elif n == 2:
|
||||
return 1
|
||||
elif n not in memo:
|
||||
memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
|
||||
return memo[n]
|
||||
|
||||
# Test du code
|
||||
print(fibonacci(10))
|
||||
@@ -0,0 +1,39 @@
|
||||
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)
|
||||
|
||||
Mais attention, ce code est inefficace car il calcule plusieurs fois les mêmes valeurs, ce qui peut entraîner des performances très mauvaises pour les valeurs de n élevées.
|
||||
|
||||
Voici une version plus efficace qui utilise une mémoire pour stocker les valeurs calculées :
|
||||
|
||||
def fibonacci(n, memo = {}):
|
||||
if n <= 0:
|
||||
return "Entrée non valide"
|
||||
elif n == 1:
|
||||
return 0
|
||||
elif n == 2:
|
||||
return 1
|
||||
elif n not in memo:
|
||||
memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)
|
||||
return memo[n]
|
||||
|
||||
Et voici une version encore plus efficace qui utilise une approche itérative :
|
||||
|
||||
def fibonacci(n):
|
||||
if n <= 0:
|
||||
return "Entrée non valide"
|
||||
elif n == 1:
|
||||
return 0
|
||||
elif n == 2:
|
||||
return 1
|
||||
else:
|
||||
a, b = 0, 1
|
||||
for _ in range(2, n):
|
||||
a, b = b, a + b
|
||||
return b
|
||||
@@ -0,0 +1,11 @@
|
||||
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.
|
||||
@@ -0,0 +1,13 @@
|
||||
def fibonacci_memoize(n, memo={}):
|
||||
if n <= 0:
|
||||
return 0
|
||||
elif n == 1:
|
||||
return 1
|
||||
elif n in memo:
|
||||
return memo[n]
|
||||
else:
|
||||
result = fibonacci_memoize(n-1, memo) + fibonacci_memoize(n-2, memo)
|
||||
memo[n] = result
|
||||
return result
|
||||
|
||||
Ce code définit une fonction `fibonacci_memoize` qui calcule le `n-ième` nombre de la séquence de Fibonacci de manière récursive avec mémoire (memoization). La fonction utilise un dictionnaire `memo` pour stocker les résultats des appels récursifs précédents, ce qui permet d'éviter les calculs redondants et d'améliorer les performances.
|
||||
@@ -0,0 +1,16 @@
|
||||
def fibonacci_memo(n, memo={}):
|
||||
if n <= 0:
|
||||
return 0
|
||||
elif n == 1:
|
||||
return 1
|
||||
elif n in memo:
|
||||
return memo[n]
|
||||
else:
|
||||
result = fibonacci_memo(n-1, memo) + fibonacci_memo(n-2, memo)
|
||||
memo[n] = result
|
||||
return result
|
||||
|
||||
# Test du code
|
||||
print(fibonacci_memo(10))
|
||||
|
||||
Ce code utilise une fonction mémoire (memo) pour stocker les résultats des appels récursifs précédents, ce qui permet d'éviter les calculs inutiles et d'améliorer les performances.
|
||||
|
After Width: | Height: | Size: 83 KiB |
@@ -0,0 +1,14 @@
|
||||
<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="#34C759" />
|
||||
<circle cx="200" cy="150" r="80" fill="#F7DC6F" />
|
||||
<path d="M 200,150 A 80,80 0 0,1 220,130 L 220,70 Q 220,50 200,50 L 180,50 Q 160,50 180,70 Z" fill="#34C759" />
|
||||
<path d="M 200,150 A 80,80 0 0,1 180,130 L 180,70 Q 180,50 200,50 L 220,50 Q 240,50 220,70 Z" fill="#34C759" />
|
||||
<path d="M 200,150 A 80,80 0 0,1 220,130 L 220,70 Q 220,50 200,50 L 180,50 Q 160,50 180,70 Z" fill="#34C759" />
|
||||
<path d="M 200,150 A 80,80 0 0,1 180,130 L 180,70 Q 180,50 200,50 L 220,50 Q 240,50 220,70 Z" fill="#34C759" />
|
||||
<path d="M 150,200 L 250,200" stroke="#000" stroke-width="5" />
|
||||
<path d="M 150,200 Q 150,220 170,220 L 220,220 Q 240,220 220,200 L 170,200 Q 150,200 150,220 Z" fill="#34C759" />
|
||||
<path d="M 250,200 Q 250,220 230,220 L 180,220 Q 160,220 180,200 L 230,200 Q 250,200 250,220 Z" fill="#34C759" />
|
||||
<path d="M 220,220 L 220,180" stroke="#000" stroke-width="5" />
|
||||
<path d="M 220,220 Q 220,200 240,200 L 280,200 Q 300,200 280,220 L 240,220 Q 220,220 220,200 Z" fill="#34C759" />
|
||||
<path d="M 280,220 Q 280,200 260,200 L 220,200 Q 200,200 220,220 L 260,220 Q 280,220 280,200 Z" fill="#34C759" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,19 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300">
|
||||
<rect x="0" y="0" width="400" height="300" fill="#87CEEB" />
|
||||
<ellipse cx="200" cy="150" rx="150" ry="100" fill="#FFD700" />
|
||||
<path d="M 100 200 L 100 250 L 120 250 L 120 220 L 100 220 Z" fill="#32CD32" />
|
||||
<path d="M 220 200 L 220 250 L 240 250 L 240 220 L 220 220 Z" fill="#32CD32" />
|
||||
<path d="M 340 200 L 340 250 L 360 250 L 360 220 L 340 220 Z" fill="#32CD32" />
|
||||
<circle cx="200" cy="100" r="50" fill="#FFA07A" />
|
||||
<circle cx="200" cy="250" r="50" fill="#FFA07A" />
|
||||
<path d="M 150 50 L 150 100 L 170 100 L 170 70 L 150 70 Z" fill="#FFC080" />
|
||||
<path d="M 250 50 L 250 100 L 270 100 L 270 70 L 250 70 Z" fill="#FFC080" />
|
||||
<path d="M 350 50 L 350 100 L 370 100 L 370 70 L 350 70 Z" fill="#FFC080" />
|
||||
<line x1="100" y1="150" x2="300" y2="150" stroke="#FF0000" stroke-width="5" />
|
||||
<line x1="100" y1="200" x2="300" y2="200" stroke="#FF0000" stroke-width="5" />
|
||||
<line x1="100" y1="250" x2="300" y2="250" stroke="#FF0000" stroke-width="5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |