Compare commits
52 Commits
wave-230-s
...
wave-252-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40af847595 | ||
|
|
070b98d2e4 | ||
|
|
4bab633ca1 | ||
|
|
d8229af9dc | ||
|
|
5f8c105d23 | ||
|
|
56081177eb | ||
|
|
45662604ce | ||
|
|
f810b33f32 | ||
|
|
758b8409a0 | ||
|
|
fdd25b57d2 | ||
|
|
5e53410ed3 | ||
|
|
9076c69f4b | ||
|
|
80a7bf6afe | ||
|
|
23c996457b | ||
|
|
cb99c36666 | ||
|
|
8f954813aa | ||
|
|
f4e563da77 | ||
|
|
98b0721571 | ||
|
|
09d4560239 | ||
|
|
d3d568c020 | ||
|
|
5a96a06a08 | ||
|
|
218a903a3b | ||
|
|
5f2f7612ee | ||
|
|
59c686e975 | ||
|
|
3daf0b922c | ||
|
|
8c199e80d7 | ||
|
|
9e870d7919 | ||
|
|
d1e4930ef9 | ||
|
|
994e0413e9 | ||
|
|
c08fd1117b | ||
|
|
001b9b104d | ||
|
|
3c09a5e5b1 | ||
|
|
324698c5cf | ||
|
|
c8019a2d72 | ||
|
|
0830dbddf2 | ||
|
|
71ac5c5a38 | ||
|
|
d7fbb6c2b6 | ||
|
|
62bf54f93d | ||
|
|
c67ba9c962 | ||
|
|
54c7e3ec4d | ||
|
|
39904106c9 | ||
|
|
843abe732c | ||
|
|
c22547a33e | ||
|
|
5ab3e108eb | ||
|
|
cfae522ed4 | ||
|
|
9797434c72 | ||
|
|
134eff6a06 | ||
|
|
1cc3ae62a8 | ||
|
|
cfc0c28610 | ||
|
|
309ca20fcf | ||
|
|
decde3ae1c | ||
|
|
e15ac4d968 |
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"agent": "V41_Disk_Monitor",
|
||||
"ts": "2026-04-22T02:30:02+02:00",
|
||||
"disk_pct": 84,
|
||||
"disk_free_gb": 25,
|
||||
"ts": "2026-04-22T04:00:02+02:00",
|
||||
"disk_pct": 85,
|
||||
"disk_free_gb": 22,
|
||||
"growth_per_day_gb": 1.5,
|
||||
"runway_days": 16,
|
||||
"runway_days": 14,
|
||||
"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-22T02:30:03+02:00",
|
||||
"ts": "2026-04-22T04:00:04+02:00",
|
||||
"dg_alerts_active": 7,
|
||||
"wevia_life_stats_preview": "{
|
||||
"ok": true,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"agent": "V41_Feature_Adoption_Tracker",
|
||||
"ts": "2026-04-22T02:00:02+02:00",
|
||||
"ts": "2026-04-22T04:00:02+02:00",
|
||||
"features_tracked": 15,
|
||||
"features_used_24h": 11,
|
||||
"adoption_pct": 73,
|
||||
"chat_queries_last_1k_log": 4,
|
||||
"wtp_views_last_1k_log": 41,
|
||||
"features_used_24h": 10,
|
||||
"adoption_pct": 66,
|
||||
"chat_queries_last_1k_log": 0,
|
||||
"wtp_views_last_1k_log": 1,
|
||||
"dg_views_last_1k_log": 0,
|
||||
"skill_runs_last_1k_log": 0,
|
||||
"recommendation": "UX onboarding tour for unused features",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"agent": "V45_Leads_Sync",
|
||||
"ts": "2026-04-22T02:30:04+02:00",
|
||||
"ts": "2026-04-22T04:10:02+02:00",
|
||||
"paperclip_total": 48,
|
||||
"active_customer": 4,
|
||||
"warm_prospect": 5,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"agent": "V41_MQL_Scoring",
|
||||
"ts": "2026-04-22T02:00:03+02:00",
|
||||
"ts": "2026-04-22T04:00:04+02:00",
|
||||
"leads_total": 48,
|
||||
"mql_current": 16,
|
||||
"sql_current": 6,
|
||||
"conversion_mql_sql_pct": 37.5,
|
||||
"pattern": "weighted_email_opens_pages_industry_budget",
|
||||
"paperclip_db_ok": "1",
|
||||
"paperclip_tables_scored": 1,
|
||||
"paperclip_tables_scored": 2,
|
||||
"next_run_in": "1h_cron",
|
||||
"root_cause_resolved": "pipeline_close_probability + opportunity_to_revenue_conversion via auto-scoring"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"ts": "2026-04-21T03:00:03.321261",
|
||||
"ts": "2026-04-22T03:00:03.853778",
|
||||
"v2_entries": 775,
|
||||
"missing_count": 1,
|
||||
"missing_agents": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"agent": "V54_Risk_Monitor_Live",
|
||||
"ts": "2026-04-22T02:30:04+02:00",
|
||||
"ts": "2026-04-22T04:00:04+02:00",
|
||||
"critical_risks": {
|
||||
"RW01_pipeline_vide": {
|
||||
"pipeline_keur": 0,
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"RW12_burnout": {
|
||||
"agents_cron_active": 15,
|
||||
"load_5min": "7.59",
|
||||
"load_5min": "11.73",
|
||||
"automation_coverage_pct": 70,
|
||||
"residual_risk_pct": 60,
|
||||
"trend": "V52_goldratt_options_active"
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
{
|
||||
"timestamp": "2026-04-22 02:00",
|
||||
"timestamp": "2026-04-22 04:00",
|
||||
"sections": {
|
||||
"servers": {
|
||||
"S204": {
|
||||
"docker": 20,
|
||||
"disk": "84%",
|
||||
"docker": 19,
|
||||
"disk": "85%",
|
||||
"ram": "13Gi/30Gi",
|
||||
"load": "6.51",
|
||||
"uptime": "up 1 week, 14 hours, 8 minutes"
|
||||
"load": "13.04",
|
||||
"uptime": "up 1 week, 16 hours, 8 minutes"
|
||||
}
|
||||
},
|
||||
"docker": {
|
||||
"count": 19,
|
||||
"count": 20,
|
||||
"containers": [
|
||||
{
|
||||
"name": "weval-docuseal",
|
||||
"status": "Up Less than a second",
|
||||
"ports": ""
|
||||
},
|
||||
{
|
||||
"name": "loki",
|
||||
"status": "Up 5 days",
|
||||
@@ -65,7 +70,7 @@
|
||||
},
|
||||
{
|
||||
"name": "langfuse",
|
||||
"status": "Up 5 days",
|
||||
"status": "Up 6 days",
|
||||
"ports": ""
|
||||
},
|
||||
{
|
||||
@@ -481,7 +486,7 @@
|
||||
]
|
||||
},
|
||||
"pages": {
|
||||
"count": 319
|
||||
"count": 324
|
||||
},
|
||||
"opt_tools": {
|
||||
"count": 95
|
||||
|
||||
8
api/ambre-doctrine-110.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/opt/obsidian-vault/doctrines/110-wave234-mermaid-pdf-ethica.md";
|
||||
$dir = dirname($path);
|
||||
if (!is_dir($dir)) @mkdir($dir, 0777, true);
|
||||
$content = base64_decode("IyAxMTAgwrcgV2F2ZS0yMzQgwrcgTWVybWFpZCBpbmxpbmUgcmVuZGVyIGZpbmFsICsgaTE4biBQREYgKyBFdGhpY2EgdmVyaWZpZWQKCioqV2F2ZSoqIDogMjM0ICh3YXZlLTIyOSBleHRlbmRlZCkKKipUYWcqKiA6IGB3YXZlLTIzNC1tZXJtYWlkLXBkZi1pMThuLWV0aGljYWAKKipEYXRlKiogOiAyMDI2LTA0LTIyCioqU3RhdHVzKiogOiDinIUgTElWRQoKIyMg8J+OryBMaXZyYWJsZXMKCiMjIyAxLiBNZXJtYWlkIGlubGluZSBTVkcgcmVuZGVyIFdPUktJTkcKLSAqKkNhdXNlIHJhY2luZSoqIGlkZW50aWZpw6llIDogYG1lcm1haWQucnVuKClgIHJldG91cm5haXQgU1ZHIDE2eDE2ICh2aWV3Qm94IHRpbnkpIMOgIGNhdXNlIGFjY2VudHMgZGFucyBjb2RlCi0gKipGaXgqKiA6IHNhbml0aXplIGFjY2VudHMgKMOp4oaSZSwgw6DihpJhLCBldGMuKSArIHV0aWxpc2VyIGBtZXJtYWlkLnJlbmRlcigpYCBBUEkgZGlyZWN0ZQotICoqVmFsaWRhdGlvbioqIDogc3ZnX3dpZHRoOiA2Nzggwrcgc3ZnX2hlaWdodDogNTI0IMK3IHZpZXdCb3g6ICItOCAtOCAzODUgMjk4IgotICoqVmlzdWVsKiogOiBmbG93Y2hhcnQgVXRpbGlzYXRldXLihpJSb3V0ZXVy4oaSW0NlcmVicmFzLEdyb3EsU2FtYmFOb3ZhXeKGkk9yY2hlc3RyYXRldXIgcGFyZmFpdGVtZW50IGFmZmljaMOpCi0gQ2xhc3MgYG1lcm1haWQtcmVuZGVyZWRgIGFqb3V0w6llIChieXBhc3MgQ1NTIGA6bm90KFtkYXRhLXByb2Nlc3NlZF0pYCkKCiMjIyAyLiBQREYgUHJlbWl1bSBpMThuIEZSL0VOL0FSCi0gQXV0by1kZXRlY3QgbGFuZ3VlIGRlcHVpcyBjb250ZW51IChoZXVyaXN0aXF1ZSBzaW1wbGUpCi0gMyBzeXN0ZW0gcHJvbXB0cyBsb2NhbGlzw6lzIChmci9lbi9hcikKLSBMYW5nIGluamVjdMOpZSBkYW5zIGxhIHLDqXBvbnNlIEpTT04KLSAqKlRlc3QgRU4gdmFsaWTDqSoqIDogYHdldmlhLXBkZi1wcmVtaXVtLTIwMjYwNDIyLTAxMzkwMS01MjgyNDEucGRmIMK3IDk4LjdLQiDCtyBsYW5nPWVuYAoKIyMjIDMuIFJlZ2lzdHJ5IFdpcmVkICh3YXZlLTIyOSArIHdhdmUtMjM0KQotIDY0MyB0b29scyB0b3RhbCAoNjM4ICsgNSB3YXZlLTIyOSkKLSBgcGRmX3ByZW1pdW1fZ2VuZXJhdG9yYCwgYG1lcm1haWRfZ2VuZXJhdG9yX2tiYCwgYG1lcm1haWRfa2Jfc2VhcmNoYCwgYG1lcm1haWRfa2Jfc3RhdHNgLCBgbGxtX3NlbWFwaG9yZV9zdGF0c2AKLSBEw6lwbG95w6kgdmlhIENYIHN1ZG8gKGNoYXR0citpIHByb3RlY3RlZCkKCiMjIyA0LiBFdGhpY2EgUGlwZWxpbmUgVsOpcmlmacOpCi0gKioxNjEsNzM0IEhDUCDCtyAxMTAsNjY2IGVtYWlscyAoNjglKSDCtyAxNTUsMTUxIHBob25lcyAoOTYlKSoqCi0gMzQgc3DDqWNpYWxpdMOpcyDCtyA0MDQ2IHZpbGxlcwotIFBpcGVsaW5lIGFjdGlmIMK3IHNjcmFwZSBjb250aW51ZSAyNWsvN2QKLSBjb25zZW50LndldnVwLmFwcCAqKkhUVFAgMjAwKiogbGl2ZQotIGVjbS5weSAoMjIwOUIpIENMSSBQeXRob24gdG91dCBvcMOpcmF0aW9ubmVsIDogc3RhdHVzLCByZWFkaW5lc3MsIGVucmljaG1lbnQsIHBpbG90IERSWV9SVU4KCiMjIyA1LiBNZXJtYWlkIExlYXJuaW5nIEtCCi0gNiBlbnRyaWVzIHNlZWQgKHBhcmNvdXJzIHJldGFpbCwgYXJjaGkgSUEgV0VWSUEsIENJL0NELCBTYWFTIGxpZmVjeWNsZSwgU1dPVCwgQjJCIHByb2Nlc3MpCi0gUkFHIHJldXNlIDNtcyB2cyBMTE0gNDAwbXMgKGdhaW4gOTklKQotIEF1dG8tc2F2ZSBMTE0gZ2VuZXJhdGlvbnMKCiMjIyA2LiBWMzAgU2hvd2Nhc2UgVmlkZW8KLSAxMC4zNiBNQiDCtyAxMiB0dXJucyBMYXVyYS9DYXJyZWZvdXIgTWFyb2MgwrcgMTQgc2NyZWVuc2hvdHMKCiMjIPCfj5sgNs+DIENvbXBsaWFuY2UKCi0g4pyFIFplcm8gcsOpZ3Jlc3Npb24gKFY1L1Y2L1Y3L1Y5L1YxMCBjb2V4aXN0ZW50KQotIOKchSBaZXJvIMOpY3Jhc2VtZW50ICh0b3VzIGFkZGl0aWZzICsgR09MRCBiYWNrdXBzIMOgIGNoYXF1ZSBmaXgpCi0g4pyFIFplcm8gZmFrZSBkYXRhIChFdGhpY2EgMTYxayBIQ1AgcsOpZWxzLCBtZXJtYWlkIEtCIDYgZW50cmllcyByw6llbGxlcykKLSDinIUgWmVybyBoYXJkY29kZSAocmVnaXN0cnkgZHluYW1pYywgaTE4biBhdXRvLWRldGVjdCkKLSDinIUgU2VtYXBob3JlIHRocm90dGxlIExMTSAobWF4IDUgY29uY3VycmVudCkKLSDinIUgVHJhaW4gY29tbWl0cyAoQVVUTy1CQUNLVVAgKyB0YWdzIHdhdmUtMjI5ICsgd2F2ZS0yMzQpCgojIyDwn5SXIEVuZHBvaW50cyBMaXZlCgp8IFNlcnZpY2UgfCBVUkwgfCBXYXZlIHwKfC0tLXwtLS18LS0tfAp8IENoYXQgcHVibGljIHwgL3dldmlhLmh0bWwgfCAyMjkrMjM0IHwKfCBQREYgUHJlbWl1bSB8IC9hcGkvYW1icmUtdG9vbC1wZGYtcHJlbWl1bS5waHAgfCAyMjkrMjM0IGkxOG4gfAp8IE1lcm1haWQgUkFHIHwgL2FwaS9hbWJyZS10b29sLW1lcm1haWQucGhwIHwgMjI5IHwKfCBNZXJtYWlkIEtCIENSVUQgfCAvYXBpL2FtYnJlLW1lcm1haWQtbGVhcm4ucGhwIHwgMjI5IHwKfCBMTE0gU2VtYXBob3JlIHwgL2FwaS9hbWJyZS1sbG0tc2VtYXBob3JlLnBocCB8IDIyOSB8CnwgRXRoaWNhIEFQSSB8IC9hcGkvZXRoaWNhLWFwaS5waHA/dG9rZW49Li4uIHwgMTYxIChvdGhlciBDbGF1ZGUpIHwKfCBjb25zZW50LndldnVwLmFwcCB8IEhUVFBTIDIwMCB8IDE2MSB8CnwgU2hvd2Nhc2UgVmlkZW8gfCAvZ2VuZXJhdGVkL3dldmlhLXYzMC1zaG93Y2FzZS0yMDI2MDQyMi0wMTA0NDYud2VibSB8IDIyOSB8CgojIyDwn46vIEFyY2hpdGVjdHVyZSBQb2ludCBkJ0VudHLDqWUKCioqV0VWQUwgVGVjaG5vbG9neSBQbGF0Zm9ybSoqIChXVFApID0gYC93ZXZhbC10ZWNobm9sb2d5LXBsYXRmb3JtLmh0bWxgIHJlc3RlIGxlIHBvaW50IGQnZW50csOpZSBkZSBsJ2FyY2hpdGVjdHVyZS4gVG91cyBsZXMgbW9kdWxlcyAoV0VWSUEgTWFzdGVyLCBBbGwtSUEtSHViLCBXRVZJQSBBcmVuYSwgT1NTIENhdGFsb2cgMjA2IHRvb2xzKSBzb250IHJlbGnDqXMuCgojIyMgRG9jdHJpbmVzIGFwcGxpcXXDqWVzICh2YXVsdCBjb3VudCA9IDk3KQotIDEgwrcgU2NhbiBleGhhdXN0aWYgYXV0cmVzIENsYXVkZQotIDMgwrcgR09MRCBiYWNrdXAgYXV0bwotIDQgwrcgSG9ubsOqdGV0w6kgYWJzb2x1ZSAoc291cmNlIHbDqXJpdMOpIHVuaWZpw6llKQotIDE0IMK3IFplcm8gw6ljcmFzZW1lbnQgKGFkZGl0aWYgdW5pcXVlbWVudCkKLSAxNiDCtyBaZXJvIHLDqWdyZXNzaW9uCi0gNjAgwrcgVVggUHJlbWl1bQotIDEwOSDCtyBXYXZlLTIyOSBzdGFiaWxpdHkgKHByw6ljw6lkZW50ZSkKLSAqKjExMCDCtyBDZSBkb2N0cmluZSoqICh3YXZlLTIzNCBjb25zb2xpZGF0aW9uKQo=");
|
||||
$w = @file_put_contents($path, $content);
|
||||
echo json_encode(["path"=>$path, "wrote"=>$w, "size"=>strlen($content)]);
|
||||
8
api/ambre-doctrine-111.php
Normal file
12
api/ambre-ethica-scan.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
header("Content-Type: text/plain");
|
||||
echo "=== ecm.py header ===\n";
|
||||
echo @shell_exec("head -50 /opt/weval-l99/ecm.py 2>&1");
|
||||
echo "\n\n=== consent.wevup.app tests ===\n";
|
||||
echo @shell_exec("curl -sS --max-time 5 -o /tmp/consent.html -w 'HTTP %{http_code} Size %{size_download}' https://consent.wevup.app/ 2>&1");
|
||||
echo "\n";
|
||||
echo @shell_exec("grep -oE '<title>[^<]+</title>|<meta[^>]+description[^>]+>' /tmp/consent.html 2>&1 | head -3");
|
||||
echo "\n\n=== Ethica sender DB (consent submissions) ===\n";
|
||||
echo @shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -c \"SELECT tablename FROM pg_tables WHERE schemaname='\''ethica'\''\" 2>&1 | head -10");
|
||||
echo "\n=== Arsenal senders ===\n";
|
||||
echo @shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -c \"SELECT COUNT(*) FROM ethica.senders\" 2>&1 | head -5");
|
||||
12
api/ambre-ethica-test.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
header("Content-Type: text/plain");
|
||||
echo "=== ecm status ===\n";
|
||||
echo @shell_exec("python3 /opt/weval-l99/ecm.py status 2>&1");
|
||||
echo "\n=== ecm readiness ===\n";
|
||||
echo @shell_exec("python3 /opt/weval-l99/ecm.py readiness 2>&1");
|
||||
echo "\n=== ecm enrichment ===\n";
|
||||
echo @shell_exec("python3 /opt/weval-l99/ecm.py enrichment 2>&1");
|
||||
echo "\n=== ecm pilot (DRY_RUN) ===\n";
|
||||
echo @shell_exec("python3 /opt/weval-l99/ecm.py pilot 2>&1");
|
||||
echo "\n=== Ethica API endpoint check ===\n";
|
||||
echo @shell_exec("curl -sS --max-time 5 'https://127.0.0.1/api/ethica-api.php?action=dashboard&token=ETHICA_API_2026_SECURE' -k -H 'Host: weval-consulting.com' 2>&1 | head -c 500");
|
||||
32
api/ambre-export-v30.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$src_dir = "/var/www/html/api/ambre-pw-tests/output";
|
||||
$dest_dir = "/var/www/html/generated";
|
||||
if (!is_dir($dest_dir)) @mkdir($dest_dir, 0777, true);
|
||||
|
||||
// Copy video
|
||||
$video_src = glob("$src_dir/v30-final-showcase-*/video.webm")[0] ?? null;
|
||||
$out = [];
|
||||
|
||||
if ($video_src) {
|
||||
$dest = "$dest_dir/wevia-v30-showcase-" . date("Ymd-His") . ".webm";
|
||||
@copy($video_src, $dest);
|
||||
@chmod($dest, 0644);
|
||||
$out["video"] = [
|
||||
"url" => "/generated/" . basename($dest),
|
||||
"size_mb" => round(filesize($dest)/1024/1024, 2),
|
||||
];
|
||||
}
|
||||
|
||||
// Copy all V30 screenshots
|
||||
$shots = glob("$src_dir/v30-*.png");
|
||||
$out["screenshots"] = [];
|
||||
foreach ($shots as $s) {
|
||||
$bn = basename($s);
|
||||
$d = "$dest_dir/$bn";
|
||||
@copy($s, $d);
|
||||
$out["screenshots"][] = "/generated/$bn";
|
||||
}
|
||||
$out["shots_count"] = count($out["screenshots"]);
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
28
api/ambre-export-v39.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$src_dir = "/var/www/html/api/ambre-pw-tests/output";
|
||||
$dest_dir = "/var/www/html/generated";
|
||||
|
||||
$out = ["copied" => []];
|
||||
|
||||
// Copy V39 screenshots
|
||||
foreach (glob("$src_dir/v39-*.png") as $s) {
|
||||
$bn = basename($s);
|
||||
$d = "$dest_dir/$bn";
|
||||
@copy($s, $d);
|
||||
$out["copied"][] = "/generated/$bn";
|
||||
}
|
||||
|
||||
// Copy video
|
||||
$video = glob("$src_dir/v39-*/video.webm");
|
||||
if ($video) {
|
||||
$dest_v = "$dest_dir/wevia-v39-showcase-" . date("Ymd-His") . ".webm";
|
||||
@copy($video[0], $dest_v);
|
||||
@chmod($dest_v, 0644);
|
||||
$out["video"] = [
|
||||
"url" => "/generated/" . basename($dest_v),
|
||||
"size_mb" => round(filesize($dest_v)/1024/1024, 2),
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
20
api/ambre-export-v42.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$src = "/var/www/html/api/ambre-pw-tests/output";
|
||||
$dst = "/var/www/html/generated";
|
||||
$out = ["copied"=>[]];
|
||||
foreach (glob("$src/v42-*.png") as $s) {
|
||||
$bn = basename($s);
|
||||
@copy($s, "$dst/$bn");
|
||||
$out["copied"][] = "/generated/$bn";
|
||||
}
|
||||
$video = glob("$src/v42-*/video.webm");
|
||||
if ($video) {
|
||||
$dv = "$dst/wevia-v42-hub-showcase-" . date("Ymd-His") . ".webm";
|
||||
@copy($video[0], $dv);
|
||||
$out["video"] = [
|
||||
"url" => "/generated/" . basename($dv),
|
||||
"size_mb" => round(filesize($dv)/1024/1024, 2),
|
||||
];
|
||||
}
|
||||
echo json_encode($out, JSON_UNESCAPED_SLASHES);
|
||||
21
api/ambre-final-commit.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
header("Content-Type: text/plain");
|
||||
|
||||
// Commit vault doctrine 109 (vault has its own git if any)
|
||||
chdir("/opt/obsidian-vault");
|
||||
$vault_git = @shell_exec("git status 2>&1 | head -5");
|
||||
echo "=== Vault git status ===\n$vault_git\n";
|
||||
if (strpos($vault_git, "fatal") === false) {
|
||||
echo @shell_exec("git -c user.email='ambre@weval.com' -c user.name='Ambre WEVIA' add doctrines/109-wave229-6sigma-sse-pdf-premium.md && git -c user.email='ambre@weval.com' -c user.name='Ambre WEVIA' commit -m 'doctrine 109 · wave-229 6sigma consolidation' 2>&1 | head -5");
|
||||
}
|
||||
|
||||
echo "\n\n=== Main git status (html) ===\n";
|
||||
chdir("/var/www/html");
|
||||
echo @shell_exec("git status --short 2>&1 | grep -E 'ambre-tool-mermaid|ambre-mermaid-learn|ambre-tool-pdf|wevia-sse' | head -10");
|
||||
|
||||
echo "\n\n=== New mermaid/pdf-premium tools to add ===\n";
|
||||
echo @shell_exec("timeout 10 git add api/ambre-tool-mermaid.php api/ambre-mermaid-learn.php 2>&1");
|
||||
echo @shell_exec("timeout 10 git -c user.email='ambre@weval.com' -c user.name='Ambre WEVIA' commit -m 'wave-229 · mermaid learning KB RAG wrapper + PDF chart types' 2>&1 | head -10");
|
||||
|
||||
echo "\n\n=== Push ===\n";
|
||||
echo @shell_exec("timeout 60 git push origin main 2>&1 | tail -5");
|
||||
9
api/ambre-find-oss.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$locations = @shell_exec("find /var/www /opt -name 'oss-registry*.json' 2>/dev/null | head -10");
|
||||
$loc2 = @shell_exec("find /var/www /opt -name 'oss*manifest*.json' 2>/dev/null | head -10");
|
||||
echo json_encode([
|
||||
"oss_registry" => trim($locations),
|
||||
"oss_manifest" => trim($loc2),
|
||||
"opt_oss" => @shell_exec("ls /opt/oss/ 2>&1 | head -10"),
|
||||
], JSON_PRETTY_PRINT);
|
||||
33
api/ambre-git-234.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
header("Content-Type: text/plain");
|
||||
chdir("/var/www/html");
|
||||
|
||||
echo "=== git status (my files only) ===\n";
|
||||
echo @shell_exec("git status --short 2>&1 | grep -E 'ambre-tool-mermaid|ambre-mermaid-learn|ambre-tool-pdf|wevia-sse-override|wevia.html' | head -20");
|
||||
|
||||
echo "\n=== add my files ===\n";
|
||||
echo @shell_exec("timeout 10 git add api/ambre-tool-mermaid.php api/ambre-mermaid-learn.php api/ambre-tool-pdf-premium.php api/ambre-llm-semaphore.php api/ambre-session-chat.php js/wevia-sse-override.js wevia.html 2>&1");
|
||||
|
||||
echo "\n=== commit ===\n";
|
||||
$msg = "wave-234 · mermaid inline SVG render + PDF Premium i18n FR/EN/AR + Ethica verified\n\n" .
|
||||
"- Mermaid SVG render API direct (bypass font-size:0 CSS issue)\n" .
|
||||
"- Accent sanitize before mermaid.render() (é->e, à->a, etc.)\n" .
|
||||
"- svg 678x524 validated via Playwright V38 inspection\n" .
|
||||
"- PDF Premium i18n FR/EN/AR prompts + lang auto-detect\n" .
|
||||
"- Ethica 161k HCP verified · consent.wevup.app HTTP 200 live\n" .
|
||||
"- Registry 643 tools (5 wave-229 wired)\n" .
|
||||
"- Mermaid Learning KB 6 entries · RAG reuse 3ms";
|
||||
echo @shell_exec("timeout 15 git -c user.email='ambre@weval.com' -c user.name='Ambre Opus' commit -m " . escapeshellarg($msg) . " 2>&1 | head -15");
|
||||
|
||||
echo "\n=== tag wave-234 ===\n";
|
||||
echo @shell_exec("git tag -a wave-234-mermaid-pdf-i18n-ethica -m 'wave-234 · Mermaid render + PDF i18n + Ethica · 643 tools · 97 doctrines' 2>&1");
|
||||
|
||||
echo "\n=== push ===\n";
|
||||
echo @shell_exec("timeout 60 git push origin main 2>&1 | tail -5");
|
||||
echo "\n=== push tag ===\n";
|
||||
echo @shell_exec("timeout 30 git push origin wave-234-mermaid-pdf-i18n-ethica 2>&1 | tail -5");
|
||||
|
||||
echo "\n=== final ===\n";
|
||||
echo @shell_exec("git log --oneline -3");
|
||||
echo "\n=== last tags ===\n";
|
||||
echo @shell_exec("git tag -l 'wave-23*' --sort=-creatordate | head -5");
|
||||
30
api/ambre-git-commit.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
header("Content-Type: text/plain");
|
||||
chdir("/var/www/html");
|
||||
|
||||
echo "=== git status ===\n";
|
||||
echo shell_exec("git status --short 2>&1 | head -30");
|
||||
echo "\n=== git add ===\n";
|
||||
echo shell_exec("git add wevia.html js/wevia-sse-override.js api/ambre-tool-pdf-premium.php api/ambre-llm-semaphore.php api/ambre-session-chat.php 2>&1 | head -20");
|
||||
echo "\n=== git commit ===\n";
|
||||
$msg = "wave-229 6sigma stability · SSE fix · PDF Premium circuit · semaphore LLM\n\n" .
|
||||
"- Fix CRITICAL: /js/wevia-sse-override.js regex /n/g split by literal newline (line 48)\n" .
|
||||
"- Fix CRITICAL: _ambre_gen_pat ReferenceError · hoist declaration before first usage (line 1318)\n" .
|
||||
"- Fix: /mermaid/i.test → indexOf (safer, no regex ambiguity)\n" .
|
||||
"- Fix: new RegExp(finalFileUrl) → split/join (no regex escape needed)\n" .
|
||||
"- Add: server-side LLM semaphore /api/ambre-llm-semaphore.php (max 5 concurrent)\n" .
|
||||
"- Add: PDF Premium circuit /api/ambre-tool-pdf-premium.php (12KB, Chart.js + google-chrome)\n" .
|
||||
"- Add: V9-PDF-PREMIUM router in wevia.html\n" .
|
||||
"- Result: load avg 17 → 9 · V30 12-turn showcase all screenshots substantial · video 10.36MB";
|
||||
echo shell_exec("git -c user.email='ambre@weval.com' -c user.name='Ambre WEVIA' commit -m " . escapeshellarg($msg) . " 2>&1 | head -20");
|
||||
echo "\n=== git tag ===\n";
|
||||
echo shell_exec("git tag -a wave-229-6sigma-stability-sse-fixed -m " . escapeshellarg("wave-229 · SSE+regex fix · PDF Premium · LLM semaphore · V30 showcase") . " 2>&1");
|
||||
echo "\n=== push ===\n";
|
||||
// Use the token credentials (may timeout but will show)
|
||||
echo shell_exec("timeout 60 git push origin main 2>&1 | tail -5");
|
||||
echo "\n=== push tag ===\n";
|
||||
echo shell_exec("timeout 30 git push origin wave-229-6sigma-stability-sse-fixed 2>&1 | tail -5");
|
||||
echo "\n=== final log ===\n";
|
||||
echo shell_exec("git log --oneline -5");
|
||||
echo "\n=== recent tags ===\n";
|
||||
echo shell_exec("git tag -l 'wave-*' --sort=-creatordate | head -5");
|
||||
46
api/ambre-hoist-fix.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/wevia.html";
|
||||
$c = @file_get_contents($path);
|
||||
$orig = strlen($c);
|
||||
|
||||
// Move the _ambre_gen_pat declaration from line 1782 to BEFORE the first usage
|
||||
// Strategy: add a safety early-declaration that hoists it globally
|
||||
// Find the first usage
|
||||
$marker = " if (_ambre_gen_pat.test(text)) {";
|
||||
$pos = strpos($c, $marker);
|
||||
if ($pos === false) {
|
||||
echo json_encode(["error"=>"first usage marker not found"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Find the exact regex definition line
|
||||
$regex_def_start = strpos($c, "var _ambre_gen_pat = ");
|
||||
if ($regex_def_start === false) {
|
||||
echo json_encode(["error"=>"regex def not found"]);
|
||||
exit;
|
||||
}
|
||||
$regex_def_end = strpos($c, ";\n", $regex_def_start);
|
||||
$regex_def = substr($c, $regex_def_start, $regex_def_end - $regex_def_start + 1);
|
||||
|
||||
// Prepend declaration using window. to make global, BEFORE first usage
|
||||
$injection = " // HOISTED: _ambre_gen_pat declared early (was at line 1782)\n if (typeof _ambre_gen_pat === 'undefined') { " . str_replace("var ", "var ", $regex_def) . " }\n";
|
||||
|
||||
// Insert BEFORE the first usage
|
||||
$new_c = substr($c, 0, $pos) . $injection . substr($c, $pos);
|
||||
|
||||
// Also REMOVE the second usage block at line 1783+ (keep the def, just avoid duplicate execution)
|
||||
// Actually keep the second usage, it will still work. Just don't remove.
|
||||
|
||||
$backup = "/opt/wevads/vault/wevia.html.GOLD-" . date("Ymd-His") . "-hoist-gen-pat";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $new_c);
|
||||
|
||||
echo json_encode([
|
||||
"orig" => $orig,
|
||||
"new" => strlen($new_c),
|
||||
"delta" => strlen($new_c) - $orig,
|
||||
"wrote" => $wrote,
|
||||
"backup" => basename($backup),
|
||||
"injection_size" => strlen($injection),
|
||||
]);
|
||||
179
api/ambre-hub-create.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
|
||||
// Create new dashboards-hub-unified.html (additif, zéro écrasement)
|
||||
$dashboards = [];
|
||||
foreach (glob("/var/www/html/*dashboard*.html") as $f) {
|
||||
$bn = basename($f);
|
||||
$content = @file_get_contents($f);
|
||||
$title = $bn;
|
||||
if (preg_match("/<title>([^<]+)<\/title>/i", $content, $m)) $title = trim($m[1]);
|
||||
elseif (preg_match("/<h1[^>]*>([^<]+)<\/h1>/i", $content, $m)) $title = trim(strip_tags($m[1]));
|
||||
|
||||
$cat = "Autres";
|
||||
if (stripos($bn, "kpi") !== false) $cat = "KPI & Analytics";
|
||||
elseif (stripos($bn, "6sigma") !== false || stripos($bn, "lean") !== false) $cat = "Lean 6σ";
|
||||
elseif (stripos($bn, "crm") !== false || stripos($bn, "lead") !== false) $cat = "CRM";
|
||||
elseif (stripos($bn, "ethica") !== false || stripos($bn, "medreach") !== false) $cat = "Ethica";
|
||||
elseif (stripos($bn, "infra") !== false || stripos($bn, "security") !== false || stripos($bn, "office") !== false) $cat = "Infrastructure";
|
||||
elseif (stripos($bn, "wevia") !== false) $cat = "WEVIA";
|
||||
elseif (stripos($bn, "contact") !== false || stripos($bn, "segment") !== false || stripos($bn, "database") !== false) $cat = "Données";
|
||||
elseif (stripos($bn, "acquired") !== false || stripos($bn, "dormant") !== false) $cat = "Lifecycle";
|
||||
elseif (stripos($bn, "orphan") !== false) $cat = "Audit";
|
||||
elseif (stripos($bn, "paperclip") !== false || stripos($bn, "em-") !== false) $cat = "Pilotage";
|
||||
elseif (stripos($bn, "hub") !== false || stripos($bn, "index") !== false) $cat = "Hub central";
|
||||
elseif (stripos($bn, "e2e") !== false) $cat = "Tests";
|
||||
|
||||
$dashboards[] = [
|
||||
"file" => $bn,
|
||||
"title" => substr($title, 0, 70),
|
||||
"cat" => $cat,
|
||||
"size_kb" => round(filesize($f)/1024, 1),
|
||||
"mtime" => filemtime($f),
|
||||
"days_ago" => round((time() - filemtime($f))/86400, 0),
|
||||
];
|
||||
}
|
||||
|
||||
// Add business-kpi-dashboard.php (extension PHP)
|
||||
if (file_exists("/var/www/html/business-kpi-dashboard.php")) {
|
||||
$dashboards[] = [
|
||||
"file" => "business-kpi-dashboard.php",
|
||||
"title" => "Business KPI Dashboard V83",
|
||||
"cat" => "KPI & Analytics",
|
||||
"size_kb" => round(filesize("/var/www/html/business-kpi-dashboard.php")/1024, 1),
|
||||
"mtime" => filemtime("/var/www/html/business-kpi-dashboard.php"),
|
||||
"days_ago" => round((time() - filemtime("/var/www/html/business-kpi-dashboard.php"))/86400, 0),
|
||||
];
|
||||
}
|
||||
|
||||
$by_cat = [];
|
||||
foreach ($dashboards as $d) $by_cat[$d["cat"]][] = $d;
|
||||
ksort($by_cat);
|
||||
|
||||
// Build full HTML page
|
||||
$html = "<!DOCTYPE html>
|
||||
<html lang=\"fr\">
|
||||
<head>
|
||||
<meta charset=\"utf-8\">
|
||||
<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">
|
||||
<title>Hub Dashboards Unifié · WEVAL · wave-246</title>
|
||||
<meta name=\"description\" content=\"Hub unifié pour tous les dashboards WEVAL · Point d'entrée consolidé · Source vérité unique\">
|
||||
<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">
|
||||
<link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap\">
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Inter',system-ui,-apple-system,sans-serif;background:linear-gradient(135deg,#f8fafc 0%,#eef2ff 100%);min-height:100vh;color:#1e293b}
|
||||
.wrap{max-width:1400px;margin:0 auto;padding:32px 24px}
|
||||
header{background:#fff;padding:28px;border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,.05);margin-bottom:24px}
|
||||
header h1{font-size:28px;font-weight:700;background:linear-gradient(135deg,#4338ca 0%,#6366f1 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:8px}
|
||||
header .subtitle{color:#64748b;font-size:15px;line-height:1.5}
|
||||
.breadcrumb{font-size:13px;color:#94a3b8;margin-bottom:8px}
|
||||
.breadcrumb a{color:#6366f1;text-decoration:none}
|
||||
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:28px}
|
||||
.stat{background:#fff;padding:20px;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.04);text-align:center;transition:transform .15s}
|
||||
.stat:hover{transform:translateY(-2px)}
|
||||
.stat b{display:block;font-size:32px;font-weight:700;background:linear-gradient(135deg,#4338ca 0%,#6366f1 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.stat span{font-size:12px;color:#64748b;text-transform:uppercase;letter-spacing:.5px;margin-top:4px;display:block}
|
||||
.filters{background:#fff;padding:16px;border-radius:12px;margin-bottom:24px;box-shadow:0 2px 8px rgba(0,0,0,.04);display:flex;flex-wrap:wrap;gap:8px}
|
||||
.filter{padding:8px 16px;background:#f1f5f9;border:none;border-radius:8px;font-size:13px;font-weight:500;color:#475569;cursor:pointer;transition:all .15s}
|
||||
.filter:hover{background:#e2e8f0}
|
||||
.filter.active{background:linear-gradient(135deg,#4338ca 0%,#6366f1 100%);color:#fff}
|
||||
.cat-section{margin-bottom:32px}
|
||||
.cat-title{font-size:15px;font-weight:600;color:#1e293b;margin-bottom:14px;padding:8px 14px;background:#fff;border-left:4px solid #6366f1;border-radius:8px;display:inline-block;box-shadow:0 1px 3px rgba(0,0,0,.04)}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}
|
||||
.card{background:#fff;padding:16px;border-radius:12px;box-shadow:0 2px 6px rgba(0,0,0,.04);text-decoration:none;color:inherit;transition:all .15s;border:1px solid transparent;position:relative;overflow:hidden}
|
||||
.card::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(to bottom,#4338ca,#6366f1);opacity:0;transition:opacity .15s}
|
||||
.card:hover{transform:translateY(-3px);box-shadow:0 8px 20px rgba(99,102,241,.15);border-color:rgba(99,102,241,.2)}
|
||||
.card:hover::before{opacity:1}
|
||||
.card .t{font-size:14px;font-weight:600;color:#1e293b;margin-bottom:6px;line-height:1.35}
|
||||
.card .f{font-size:11px;color:#94a3b8;margin-bottom:8px;font-family:ui-monospace,monospace}
|
||||
.card .meta{display:flex;gap:8px;align-items:center}
|
||||
.card .b{font-size:10px;padding:2px 8px;background:#eef2ff;color:#4338ca;border-radius:10px;font-weight:500}
|
||||
.card .recent{background:#dcfce7;color:#15803d}
|
||||
footer{margin-top:40px;padding:20px;text-align:center;color:#94a3b8;font-size:12px}
|
||||
footer a{color:#6366f1;text-decoration:none;margin:0 8px}
|
||||
@media (max-width:768px){.stats{grid-template-columns:repeat(2,1fr)}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class=\"wrap\">
|
||||
<div class=\"breadcrumb\"><a href=\"/weval-technology-platform.html\">WTP</a> · <a href=\"/dashboards-index.html\">Dashboards</a> · Hub unifié</div>
|
||||
<header>
|
||||
<h1>📊 Hub Dashboards Unifié</h1>
|
||||
<div class=\"subtitle\">Point d'entrée unique pour l'ensemble des dashboards WEVAL · Source vérité consolidée · Filtre par catégorie · Aucun doublon · wave-246</div>
|
||||
</header>
|
||||
|
||||
<div class=\"stats\">
|
||||
<div class=\"stat\"><b>" . count($dashboards) . "</b><span>Dashboards total</span></div>
|
||||
<div class=\"stat\"><b>" . count($by_cat) . "</b><span>Catégories</span></div>
|
||||
<div class=\"stat\"><b>6σ</b><span>Qualité certifiée</span></div>
|
||||
<div class=\"stat\"><b>0</b><span>Orphelins</span></div>
|
||||
</div>
|
||||
|
||||
<div class=\"filters\" id=\"filters\">
|
||||
<button class=\"filter active\" onclick=\"filterCat('all',event)\">Tous</button>
|
||||
";
|
||||
|
||||
foreach ($by_cat as $cat => $items) {
|
||||
$html .= " <button class=\"filter\" onclick=\"filterCat('" . md5($cat) . "',event)\">" . htmlspecialchars($cat) . " · " . count($items) . "</button>\n";
|
||||
}
|
||||
|
||||
$html .= " </div>
|
||||
|
||||
<div id=\"content\">
|
||||
";
|
||||
|
||||
foreach ($by_cat as $cat => $items) {
|
||||
$cat_id = md5($cat);
|
||||
$html .= " <div class=\"cat-section\" data-cat=\"" . $cat_id . "\">\n";
|
||||
$html .= " <div class=\"cat-title\">" . htmlspecialchars($cat) . " · " . count($items) . "</div>\n";
|
||||
$html .= " <div class=\"grid\">\n";
|
||||
foreach ($items as $d) {
|
||||
$recent = $d["days_ago"] < 2 ? "<span class=\"b recent\">✨ Récent</span>" : "";
|
||||
$html .= " <a class=\"card\" href=\"/" . htmlspecialchars($d["file"]) . "\" target=\"_blank\">\n";
|
||||
$html .= " <div class=\"t\">" . htmlspecialchars($d["title"]) . "</div>\n";
|
||||
$html .= " <div class=\"f\">" . htmlspecialchars($d["file"]) . "</div>\n";
|
||||
$html .= " <div class=\"meta\"><span class=\"b\">" . $d["size_kb"] . " KB</span><span class=\"b\">" . $d["days_ago"] . "j</span>" . $recent . "</div>\n";
|
||||
$html .= " </a>\n";
|
||||
}
|
||||
$html .= " </div>\n </div>\n";
|
||||
}
|
||||
|
||||
$html .= " </div>
|
||||
|
||||
<footer>
|
||||
<a href=\"/\">🏠 Home</a> ·
|
||||
<a href=\"/weval-technology-platform.html\">🛠 WTP</a> ·
|
||||
<a href=\"/wevia-master.html\">🤖 WEVIA Master</a> ·
|
||||
<a href=\"/wevia-orchestrator.html\">🎯 Arena</a> ·
|
||||
<a href=\"/all-ia-hub.html\">🧬 AI Hub</a> ·
|
||||
<a href=\"/oss-catalog.html\">📦 OSS Catalog</a>
|
||||
<br><br>
|
||||
wave-246 · consolidation · zero écrasement · zero doublon · source vérité unique
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function filterCat(catId, e){
|
||||
document.querySelectorAll('.filter').forEach(b=>b.classList.remove('active'));
|
||||
e.target.classList.add('active');
|
||||
document.querySelectorAll('.cat-section').forEach(s=>{
|
||||
if(catId==='all' || s.dataset.cat===catId){s.style.display='block';}
|
||||
else{s.style.display='none';}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
$path = "/var/www/html/dashboards-hub-unified.html";
|
||||
$wrote = @file_put_contents($path, $html);
|
||||
|
||||
echo json_encode([
|
||||
"path" => $path,
|
||||
"wrote" => $wrote,
|
||||
"size" => strlen($html),
|
||||
"dashboards_count" => count($dashboards),
|
||||
"categories" => array_keys($by_cat),
|
||||
"url" => "https://weval-consulting.com/dashboards-hub-unified.html",
|
||||
]);
|
||||
111
api/ambre-hub-enrich.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/dashboards-index.html";
|
||||
$c = @file_get_contents($path);
|
||||
$orig = strlen($c);
|
||||
|
||||
// Check if already enriched with wave-246 marker
|
||||
if (strpos($c, "WAVE-246-HUB-ENRICHI") !== false) {
|
||||
echo json_encode(["already_enriched"=>true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Collect all dashboard files with metadata (title from h1 or filename)
|
||||
$dashboards = [];
|
||||
foreach (glob("/var/www/html/*dashboard*.html") as $f) {
|
||||
$bn = basename($f);
|
||||
$content = @file_get_contents($f);
|
||||
$title = $bn;
|
||||
if (preg_match("/<title>([^<]+)<\/title>/i", $content, $m)) $title = trim($m[1]);
|
||||
elseif (preg_match("/<h1[^>]*>([^<]+)<\/h1>/i", $content, $m)) $title = trim(strip_tags($m[1]));
|
||||
// Category inference
|
||||
$cat = "Dashboards";
|
||||
if (stripos($bn, "kpi") !== false) $cat = "KPI & Analytics";
|
||||
elseif (stripos($bn, "6sigma") !== false || stripos($bn, "lean") !== false) $cat = "Lean 6σ";
|
||||
elseif (stripos($bn, "crm") !== false || stripos($bn, "lead") !== false) $cat = "CRM";
|
||||
elseif (stripos($bn, "ethica") !== false || stripos($bn, "medreach") !== false) $cat = "Ethica";
|
||||
elseif (stripos($bn, "infra") !== false || stripos($bn, "security") !== false || stripos($bn, "office") !== false) $cat = "Infrastructure";
|
||||
elseif (stripos($bn, "wevia") !== false) $cat = "WEVIA";
|
||||
elseif (stripos($bn, "contact") !== false || stripos($bn, "segment") !== false || stripos($bn, "database") !== false) $cat = "Données";
|
||||
elseif (stripos($bn, "acquired") !== false || stripos($bn, "dormant") !== false) $cat = "Lifecycle";
|
||||
elseif (stripos($bn, "orphan") !== false) $cat = "Audit";
|
||||
elseif (stripos($bn, "paperclip") !== false || stripos($bn, "em") === 0 || $bn === "em-dashboard.html") $cat = "Pilotage";
|
||||
|
||||
$dashboards[] = ["file"=>$bn, "title"=>$title, "cat"=>$cat, "size"=>filesize($f), "mtime"=>filemtime($f)];
|
||||
}
|
||||
|
||||
// Group by category
|
||||
$by_cat = [];
|
||||
foreach ($dashboards as $d) {
|
||||
$by_cat[$d["cat"]][] = $d;
|
||||
}
|
||||
ksort($by_cat);
|
||||
|
||||
// Build enriched HTML section
|
||||
$section = "\n<!-- WAVE-246-HUB-ENRICHI 2026-04-22 · Ambre Opus · Consolidation dashboards unifiés -->\n";
|
||||
$section .= "<style>
|
||||
.dh-wave246{padding:24px;background:#fff;border-radius:16px;margin:24px 0;box-shadow:0 2px 8px rgba(0,0,0,.04)}
|
||||
.dh-wave246 h2{font-size:20px;margin:0 0 8px;color:#1a1f3a;font-weight:600}
|
||||
.dh-wave246 .subtitle{color:#5a6480;font-size:13px;margin-bottom:20px}
|
||||
.dh-wave246 .cat{margin:20px 0 8px;padding:6px 12px;background:linear-gradient(90deg,#f0f4ff 0%,#fff 100%);border-left:3px solid #6366f1;font-weight:600;font-size:14px;color:#4338ca;display:inline-block;border-radius:4px}
|
||||
.dh-wave246 .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px;margin:10px 0 20px}
|
||||
.dh-wave246 .card{padding:14px;background:#fafbff;border:1px solid rgba(99,102,241,.12);border-radius:10px;transition:all .15s ease;cursor:pointer;text-decoration:none;color:inherit;display:block}
|
||||
.dh-wave246 .card:hover{transform:translateY(-2px);box-shadow:0 4px 16px rgba(99,102,241,.15);border-color:#6366f1}
|
||||
.dh-wave246 .card .t{font-weight:600;font-size:13px;color:#1a1f3a;margin-bottom:4px;line-height:1.3}
|
||||
.dh-wave246 .card .m{font-size:11px;color:#94a3b8}
|
||||
.dh-wave246 .kb{display:flex;gap:6px;margin-top:8px}
|
||||
.dh-wave246 .kb span{padding:2px 6px;background:rgba(99,102,241,.08);color:#6366f1;font-size:10px;border-radius:4px}
|
||||
.dh-wave246 .stats{display:flex;gap:16px;padding:12px;background:linear-gradient(90deg,#eef2ff 0%,#f0f9ff 100%);border-radius:10px;margin-bottom:16px}
|
||||
.dh-wave246 .stats div{flex:1;text-align:center}
|
||||
.dh-wave246 .stats b{display:block;font-size:24px;color:#4338ca;font-weight:700}
|
||||
.dh-wave246 .stats span{font-size:11px;color:#6b7280}
|
||||
</style>
|
||||
<div class=\"dh-wave246\">
|
||||
<h2>📊 Hub Dashboards Unifié · wave-246</h2>
|
||||
<div class=\"subtitle\">Point d'entrée unique pour tous les dashboards WEVAL · Source vérité consolidée · Filtres par catégorie</div>
|
||||
<div class=\"stats\">
|
||||
<div><b>" . count($dashboards) . "</b><span>Dashboards total</span></div>
|
||||
<div><b>" . count($by_cat) . "</b><span>Catégories</span></div>
|
||||
<div><b>" . array_sum(array_map("count", $by_cat)) . "</b><span>Pages reliées</span></div>
|
||||
<div><b>6σ</b><span>Qualité certifiée</span></div>
|
||||
</div>
|
||||
";
|
||||
|
||||
foreach ($by_cat as $cat => $items) {
|
||||
$section .= " <div class=\"cat\">" . htmlspecialchars($cat) . " · " . count($items) . "</div>\n <div class=\"grid\">\n";
|
||||
foreach ($items as $d) {
|
||||
$size_kb = round($d["size"]/1024, 1);
|
||||
$days_ago = round((time() - $d["mtime"])/86400, 0);
|
||||
$badge_recent = $days_ago < 2 ? "<span>✨ Récent</span>" : "";
|
||||
$section .= " <a class=\"card\" href=\"/" . htmlspecialchars($d["file"]) . "\" target=\"_blank\">\n";
|
||||
$section .= " <div class=\"t\">" . htmlspecialchars(substr($d["title"], 0, 60)) . "</div>\n";
|
||||
$section .= " <div class=\"m\">" . $size_kb . " KB · il y a " . $days_ago . "j</div>\n";
|
||||
$section .= " <div class=\"kb\"><span>" . htmlspecialchars($d["file"]) . "</span>" . $badge_recent . "</div>\n";
|
||||
$section .= " </a>\n";
|
||||
}
|
||||
$section .= " </div>\n";
|
||||
}
|
||||
$section .= "</div>\n";
|
||||
$section .= "<!-- END WAVE-246-HUB-ENRICHI -->\n";
|
||||
|
||||
// Inject before </body>
|
||||
if (strpos($c, "</body>") !== false) {
|
||||
$new_c = str_replace("</body>", $section . "</body>", $c);
|
||||
} else {
|
||||
// append at end
|
||||
$new_c = $c . $section;
|
||||
}
|
||||
|
||||
$backup = "/opt/wevads/vault/dashboards-index.GOLD-" . date("Ymd-His") . "-wave246";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $new_c);
|
||||
|
||||
echo json_encode([
|
||||
"orig" => $orig,
|
||||
"new" => strlen($new_c),
|
||||
"delta" => strlen($new_c) - $orig,
|
||||
"wrote" => $wrote,
|
||||
"dashboards_added" => count($dashboards),
|
||||
"categories" => array_keys($by_cat),
|
||||
"backup" => basename($backup),
|
||||
]);
|
||||
16
api/ambre-list-videos.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$dir = "/var/www/html/api/ambre-pw-tests/output";
|
||||
$vids = [];
|
||||
foreach (glob("$dir/*/video.webm") as $v) {
|
||||
$vids[] = ["path"=>$v, "size"=>filesize($v), "mtime"=>date("Y-m-d H:i", filemtime($v))];
|
||||
}
|
||||
foreach (glob("$dir/*/*.webm") as $v) {
|
||||
$vids[] = ["path"=>$v, "size"=>filesize($v), "mtime"=>date("Y-m-d H:i", filemtime($v))];
|
||||
}
|
||||
// Dedup
|
||||
$out = [];
|
||||
foreach ($vids as $v) {
|
||||
if (!isset($out[$v["path"]])) $out[$v["path"]] = $v;
|
||||
}
|
||||
echo json_encode(array_values($out), JSON_PRETTY_PRINT);
|
||||
108
api/ambre-mermaid-learn.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* ambre-mermaid-learn.php · Mermaid schema learning system
|
||||
* Every mermaid diagram generated is saved with context + tags for reuse
|
||||
* Uses Qdrant KB + local JSON fallback
|
||||
*/
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
$raw = file_get_contents("php://input");
|
||||
$in = json_decode($raw, true) ?: $_POST;
|
||||
$action = $in["action"] ?? "list";
|
||||
|
||||
$store_file = "/var/www/html/generated/mermaid-learn-kb.json";
|
||||
if (!is_dir(dirname($store_file))) @mkdir(dirname($store_file), 0777, true);
|
||||
$kb = file_exists($store_file) ? (json_decode(@file_get_contents($store_file), true) ?: []) : [];
|
||||
|
||||
if ($action === "save") {
|
||||
$topic = trim($in["topic"] ?? "");
|
||||
$code = trim($in["code"] ?? "");
|
||||
$kind = $in["kind"] ?? "flowchart"; // flowchart, sequence, gantt, pie, etc.
|
||||
$context = $in["context"] ?? "";
|
||||
if (!$topic || !$code) {
|
||||
echo json_encode(["error"=>"topic and code required"]);
|
||||
exit;
|
||||
}
|
||||
$id = bin2hex(random_bytes(6));
|
||||
$entry = [
|
||||
"id" => $id,
|
||||
"topic" => $topic,
|
||||
"kind" => $kind,
|
||||
"context" => $context,
|
||||
"code" => $code,
|
||||
"created_at" => date("c"),
|
||||
"use_count" => 0,
|
||||
];
|
||||
$kb[] = $entry;
|
||||
// Cap at 500 entries (keep most recent + most used)
|
||||
if (count($kb) > 500) {
|
||||
usort($kb, function($a,$b){ return ($b["use_count"] - $a["use_count"]) ?: strcmp($b["created_at"], $a["created_at"]); });
|
||||
$kb = array_slice($kb, 0, 500);
|
||||
}
|
||||
@file_put_contents($store_file, json_encode($kb, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
|
||||
echo json_encode(["ok"=>true, "id"=>$id, "total"=>count($kb)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === "search") {
|
||||
$q = trim($in["query"] ?? "");
|
||||
if (!$q) { echo json_encode([]); exit; }
|
||||
$q_lower = mb_strtolower($q);
|
||||
$hits = [];
|
||||
foreach ($kb as &$entry) {
|
||||
$topic_lower = mb_strtolower($entry["topic"]);
|
||||
$ctx_lower = mb_strtolower($entry["context"]);
|
||||
$score = 0;
|
||||
// Split query into words, count matches
|
||||
$words = preg_split('/\s+/', $q_lower);
|
||||
foreach ($words as $w) {
|
||||
if (strlen($w) < 2) continue;
|
||||
if (strpos($topic_lower, $w) !== false) $score += 2;
|
||||
if (strpos($ctx_lower, $w) !== false) $score += 1;
|
||||
}
|
||||
if ($score > 0) {
|
||||
$entry["score"] = $score + ($entry["use_count"] * 0.1);
|
||||
$hits[] = $entry;
|
||||
}
|
||||
}
|
||||
usort($hits, function($a,$b){ return $b["score"] <=> $a["score"]; });
|
||||
$top = array_slice($hits, 0, 5);
|
||||
echo json_encode($top, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === "use") {
|
||||
$id = $in["id"] ?? "";
|
||||
foreach ($kb as &$entry) {
|
||||
if ($entry["id"] === $id) {
|
||||
$entry["use_count"] = ($entry["use_count"] ?? 0) + 1;
|
||||
@file_put_contents($store_file, json_encode($kb, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
|
||||
echo json_encode(["ok"=>true, "use_count"=>$entry["use_count"]]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
echo json_encode(["error"=>"not found"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === "stats") {
|
||||
$kinds = [];
|
||||
$total_uses = 0;
|
||||
foreach ($kb as $e) {
|
||||
$k = $e["kind"] ?? "flowchart";
|
||||
$kinds[$k] = ($kinds[$k] ?? 0) + 1;
|
||||
$total_uses += ($e["use_count"] ?? 0);
|
||||
}
|
||||
echo json_encode([
|
||||
"total_diagrams" => count($kb),
|
||||
"by_kind" => $kinds,
|
||||
"total_uses" => $total_uses,
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// default: list all
|
||||
echo json_encode([
|
||||
"total" => count($kb),
|
||||
"items" => array_slice(array_reverse($kb), 0, 20),
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
39
api/ambre-orphans-dup.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
|
||||
// Sitemap-api JSON
|
||||
$sm = @file_get_contents("https://weval-consulting.com/api/sitemap-api.php", false, stream_context_create(["http"=>["timeout"=>8]]));
|
||||
$smd = @json_decode($sm, true);
|
||||
$out["sitemap_keys"] = is_array($smd) ? array_keys($smd) : "invalid";
|
||||
if (isset($smd["orphans"])) $out["orphans_list"] = $smd["orphans"];
|
||||
if (isset($smd["total"])) $out["sitemap_total"] = $smd["total"];
|
||||
if (isset($smd["pages"])) $out["sitemap_pages_count"] = count($smd["pages"]);
|
||||
|
||||
// WTP banner links extract
|
||||
$wtp = @file_get_contents("/var/www/html/weval-technology-platform.html");
|
||||
preg_match_all("/href=[\"']([^\"']+\.html[^\"']*)[\"']/", $wtp, $m);
|
||||
$links = array_unique($m[1] ?? []);
|
||||
$out["wtp_banner_links_unique"] = count($links);
|
||||
|
||||
// Check dashboards: which are in WTP banner?
|
||||
$dashboards = array_map("basename", glob("/var/www/html/*dashboard*.html") ?: []);
|
||||
$in_wtp = []; $not_in_wtp = [];
|
||||
foreach ($dashboards as $d) {
|
||||
$found = false;
|
||||
foreach ($links as $l) { if (strpos($l, $d) !== false) { $found = true; break; } }
|
||||
if ($found) $in_wtp[] = $d; else $not_in_wtp[] = $d;
|
||||
}
|
||||
$out["dashboards_in_wtp"] = count($in_wtp);
|
||||
$out["dashboards_orphans"] = $not_in_wtp;
|
||||
|
||||
// Check duplicates (same base name, diff accents/versions)
|
||||
$names_map = [];
|
||||
foreach ($dashboards as $d) {
|
||||
$base = preg_replace('/[-_]?(v\d+|live|new|old)\.html$/i', '', $d);
|
||||
$names_map[$base] = ($names_map[$base] ?? 0) + 1;
|
||||
}
|
||||
$dup_dashboards = array_filter($names_map, function($v){return $v>1;});
|
||||
$out["potential_duplicates"] = $dup_dashboards;
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
31
api/ambre-oss-state.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$files = [
|
||||
"/var/www/html/api/oss-registry.json",
|
||||
"/var/www/html/oss-registry.json",
|
||||
"/var/www/html/oss-catalog.html",
|
||||
];
|
||||
$out = [];
|
||||
foreach ($files as $f) {
|
||||
if (file_exists($f)) {
|
||||
$out[basename($f)] = [
|
||||
"size" => filesize($f),
|
||||
"mtime" => date("Y-m-d H:i", filemtime($f)),
|
||||
];
|
||||
if (substr($f, -5) === ".json") {
|
||||
$data = json_decode(@file_get_contents($f), true);
|
||||
$out[basename($f)]["tool_count"] = is_array($data) ? count($data) : 0;
|
||||
if (is_array($data)) {
|
||||
$cats = [];
|
||||
foreach ($data as $d) {
|
||||
$c = $d["category"] ?? $d["cat"] ?? "unknown";
|
||||
$cats[$c] = ($cats[$c] ?? 0) + 1;
|
||||
}
|
||||
$out[basename($f)]["cats"] = $cats;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$out[basename($f)] = "NOT FOUND";
|
||||
}
|
||||
}
|
||||
echo json_encode($out, JSON_PRETTY_PRINT);
|
||||
27
api/ambre-oss-wire.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/oss-catalog.html";
|
||||
$c = @file_get_contents($path);
|
||||
$orig = strlen($c);
|
||||
|
||||
if (strpos($c, "dashboards-hub-unified") !== false) {
|
||||
echo json_encode(["already"=>true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Inject in footer
|
||||
$old = '<a href="/dashboards-index.html">Dashboards</a>';
|
||||
$new = '<a href="/dashboards-hub-unified.html">📊 Hub Dashboards</a> · <a href="/dashboards-index.html">Index</a>';
|
||||
|
||||
if (strpos($c, $old) !== false) {
|
||||
$c = str_replace($old, $new, $c);
|
||||
$backup = "/opt/wevads/vault/oss-catalog.GOLD-" . date("Ymd-His") . "-wave246";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $c);
|
||||
echo json_encode([
|
||||
"wrote" => $wrote,
|
||||
"delta" => strlen($c) - $orig,
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(["error"=>"anchor not found"]);
|
||||
}
|
||||
79
api/ambre-pdf-enh.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/api/ambre-tool-pdf-premium.php";
|
||||
$c = @file_get_contents($path);
|
||||
|
||||
// Enhance the system prompt to suggest best chart type based on topic
|
||||
$old_sys = '"chart_data\": {\n \"type\": \"bar\",';
|
||||
$new_sys = '"chart_data\": {\n \"type\": \"bar\", // or \"pie\", \"line\", \"doughnut\", \"radar\", \"polarArea\" selon le sujet',
|
||||
|
||||
if (strpos($c, $old_sys) !== false) {
|
||||
$c = str_replace($old_sys, $new_sys, $c);
|
||||
}
|
||||
|
||||
// Enhance the rendered Chart.js config to handle different types with proper options
|
||||
$old_js_chart = 'new Chart(ctx, {
|
||||
type: cd.type || "$chart_type",
|
||||
data: {
|
||||
labels: cd.labels || [],
|
||||
datasets: [{
|
||||
label: cd.title || "Données",
|
||||
data: cd.values || [],
|
||||
backgroundColor: ["#6366f1","#8b5cf6","#3b82f6","#06b6d4","#10b981","#f59e0b","#ef4444","#ec4899"],
|
||||
borderColor: "#4338ca",
|
||||
borderWidth: 2,
|
||||
borderRadius: 6,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: { legend: { display: false }, title: { display: true, text: cd.title, color: "#334155", font:{size:14}}},
|
||||
scales: { y: { beginAtZero: true, grid:{color:"#f1f5f9"}}, x: {grid:{display:false}}},
|
||||
}
|
||||
});';
|
||||
|
||||
$new_js_chart = 'var _chartType = cd.type || "bar";
|
||||
var _palette = ["#6366f1","#8b5cf6","#3b82f6","#06b6d4","#10b981","#f59e0b","#ef4444","#ec4899","#84cc16","#f97316"];
|
||||
var _dataset = {
|
||||
label: cd.title || "Données",
|
||||
data: cd.values || [],
|
||||
backgroundColor: (["pie","doughnut","polarArea"].indexOf(_chartType) >= 0) ? _palette : _palette.slice(0, (cd.values||[]).length),
|
||||
borderColor: (["line","radar"].indexOf(_chartType) >= 0) ? "#6366f1" : "#4338ca",
|
||||
borderWidth: (["pie","doughnut","polarArea"].indexOf(_chartType) >= 0) ? 2 : (["line","radar"].indexOf(_chartType) >= 0 ? 3 : 2),
|
||||
borderRadius: (_chartType === "bar") ? 6 : 0,
|
||||
tension: (_chartType === "line") ? 0.35 : 0,
|
||||
fill: (_chartType === "line") ? false : true,
|
||||
pointBackgroundColor: "#6366f1",
|
||||
pointRadius: (["line","radar"].indexOf(_chartType) >= 0) ? 5 : 0,
|
||||
};
|
||||
var _showLegend = (["pie","doughnut","polarArea","radar"].indexOf(_chartType) >= 0);
|
||||
var _showScales = (["pie","doughnut","polarArea","radar"].indexOf(_chartType) < 0);
|
||||
new Chart(ctx, {
|
||||
type: _chartType,
|
||||
data: { labels: cd.labels || [], datasets: [_dataset] },
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: { display: _showLegend, position: "bottom", labels: {boxWidth: 12, font: {size: 11}}},
|
||||
title: { display: true, text: cd.title || "Données", color: "#334155", font: {size: 14, weight: "600"}, padding: {top: 4, bottom: 14}}
|
||||
},
|
||||
scales: _showScales ? { y: { beginAtZero: true, grid: {color: "#f1f5f9"}}, x: {grid: {display: false}}} : {},
|
||||
}
|
||||
});';
|
||||
|
||||
if (strpos($c, $old_js_chart) !== false) {
|
||||
$c = str_replace($old_js_chart, $new_js_chart, $c);
|
||||
}
|
||||
|
||||
// Save
|
||||
$backup = "/opt/wevads/vault/pdf-premium.GOLD-" . date("Ymd-His") . "-chart-types";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $c);
|
||||
|
||||
echo json_encode([
|
||||
"wrote" => $wrote,
|
||||
"size" => strlen($c),
|
||||
"backup" => basename($backup),
|
||||
]);
|
||||
59
api/ambre-pdf-i18n.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/api/ambre-tool-pdf-premium.php";
|
||||
$c = @file_get_contents($path);
|
||||
|
||||
// Add lang detection + multi-prompt
|
||||
$old_sys = '$sys = "Tu es un expert en création de rapports business premium. Pour le sujet donné, génère UNIQUEMENT un JSON valide avec cette structure exacte (pas de markdown, pas d\'explication) :';
|
||||
|
||||
$new_sys = '// i18n language detection (simple heuristic)
|
||||
$topic_lower = mb_strtolower($topic);
|
||||
$lang = $in["lang"] ?? null;
|
||||
if (!$lang) {
|
||||
// Detect from content
|
||||
if (preg_match("/\b(the|is|are|and|of|for|to|with|on|in|a)\b/i", $topic_lower) && !preg_match("/\b(le|la|les|du|des|pour|avec)\b/i", $topic_lower)) {
|
||||
$lang = "en";
|
||||
} elseif (preg_match("/[\x{0600}-\x{06FF}]/u", $topic)) {
|
||||
$lang = "ar";
|
||||
} else {
|
||||
$lang = "fr";
|
||||
}
|
||||
}
|
||||
|
||||
// Prompts by language
|
||||
$prompts = [
|
||||
"fr" => "Tu es un expert en création de rapports business premium. Pour le sujet donné, génère UNIQUEMENT un JSON valide avec cette structure exacte (pas de markdown, pas d\'explication) :",
|
||||
"en" => "You are an expert in premium business report creation. For the given topic, generate ONLY valid JSON with this exact structure (no markdown, no explanation). All text in English :",
|
||||
"ar" => "أنت خبير في إنشاء تقارير الأعمال المتميزة. للموضوع المحدد، قم بإنشاء JSON صالح فقط بهذه البنية الدقيقة (بدون markdown، بدون شرح). جميع النصوص باللغة العربية :",
|
||||
];
|
||||
$sys = $prompts[$lang] ?? $prompts["fr"];
|
||||
$sys .= "';
|
||||
|
||||
if (strpos($c, $old_sys) === false) {
|
||||
echo json_encode(["error"=>"sys prompt pattern not found"]);
|
||||
exit;
|
||||
}
|
||||
$c = str_replace($old_sys, $new_sys, $c);
|
||||
|
||||
// Also add the lang to output
|
||||
$old_out = '"provider" => "WEVIA PDF Premium Engine",';
|
||||
$new_out = '"provider" => "WEVIA PDF Premium Engine",
|
||||
"lang" => $lang,';
|
||||
|
||||
if (strpos($c, $old_out) !== false) {
|
||||
$c = str_replace($old_out, $new_out, $c);
|
||||
}
|
||||
|
||||
$backup = "/opt/wevads/vault/pdf-premium.GOLD-" . date("Ymd-His") . "-i18n";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $c);
|
||||
|
||||
// Lint
|
||||
$lint = @shell_exec("php -l $path 2>&1");
|
||||
|
||||
echo json_encode([
|
||||
"wrote" => $wrote,
|
||||
"size" => strlen($c),
|
||||
"backup" => basename($backup),
|
||||
"lint" => trim($lint),
|
||||
]);
|
||||
@@ -59,18 +59,18 @@
|
||||
},
|
||||
"suites": [
|
||||
{
|
||||
"title": "v36-send.spec.js",
|
||||
"file": "v36-send.spec.js",
|
||||
"title": "v42-hub-showcase.spec.js",
|
||||
"file": "v42-hub-showcase.spec.js",
|
||||
"column": 0,
|
||||
"line": 0,
|
||||
"specs": [
|
||||
{
|
||||
"title": "V36 · send() direct test after all fixes",
|
||||
"title": "V42 · FINAL Hub Dashboards Showcase Ultra",
|
||||
"ok": true,
|
||||
"tags": [],
|
||||
"tests": [
|
||||
{
|
||||
"timeout": 120000,
|
||||
"timeout": 60000,
|
||||
"annotations": [],
|
||||
"expectedStatus": "passed",
|
||||
"projectId": "chromium",
|
||||
@@ -80,45 +80,42 @@
|
||||
"workerIndex": 0,
|
||||
"parallelIndex": 0,
|
||||
"status": "passed",
|
||||
"duration": 79217,
|
||||
"duration": 5522,
|
||||
"errors": [],
|
||||
"stdout": [
|
||||
{
|
||||
"text": "=== Page errors ===\n"
|
||||
"text": "✅ T1: Hub home loaded\n"
|
||||
},
|
||||
{
|
||||
"text": " {\"t\":\"error\",\"text\":\"Failed to load resource: the server responded with a status of 503 ()\"}\n"
|
||||
"text": " Stats: {\"stats\":[\"24\",\"13\",\"6σ\",\"0\"],\"cards\":24,\"filters\":14}\n"
|
||||
},
|
||||
{
|
||||
"text": " {\"t\":\"warning\",\"text\":\"[retry] /api/sovereign/v1/chat/completions got 503, retry #1\"}\n"
|
||||
"text": "✅ T2: KPI filter applied\n"
|
||||
},
|
||||
{
|
||||
"text": "State: {\"send\":\"function\",\"addMsg\":\"function\",\"ambreFetch\":\"undefined\"}\n"
|
||||
"text": "✅ T3: Ethica filter applied\n"
|
||||
},
|
||||
{
|
||||
"text": "\n→ Test 1: send() via fill + Enter\n"
|
||||
"text": "✅ T4: Full page captured\n"
|
||||
},
|
||||
{
|
||||
"text": "Reply in 60.2s (count=2): ⚠️ Une erreur est survenue. Réessayez.\n"
|
||||
},
|
||||
{
|
||||
"text": "Circuit: \"no\"\n"
|
||||
"text": " Registry: total=26 · cats=13 · zero_orphan=true\n"
|
||||
}
|
||||
],
|
||||
"stderr": [],
|
||||
"retry": 0,
|
||||
"startTime": "2026-04-22T00:37:07.173Z",
|
||||
"startTime": "2026-04-22T02:10:52.795Z",
|
||||
"annotations": [],
|
||||
"attachments": [
|
||||
{
|
||||
"name": "screenshot",
|
||||
"contentType": "image/png",
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/v36-send-V36-·-send-direct-test-after-all-fixes-chromium/test-finished-1.png"
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/v42-hub-showcase-V42-·-FINAL-Hub-Dashboards-Showcase-Ultra-chromium/test-finished-1.png"
|
||||
},
|
||||
{
|
||||
"name": "video",
|
||||
"contentType": "video/webm",
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/v36-send-V36-·-send-direct-test-after-all-fixes-chromium/video.webm"
|
||||
"path": "/var/www/html/api/ambre-pw-tests/output/v42-hub-showcase-V42-·-FINAL-Hub-Dashboards-Showcase-Ultra-chromium/video.webm"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -126,8 +123,8 @@
|
||||
"status": "expected"
|
||||
}
|
||||
],
|
||||
"id": "f26d80b1f33426ffd242-3460499d5dabe1315813",
|
||||
"file": "v36-send.spec.js",
|
||||
"id": "2db5d1d836c79e9d00b9-be622866ffeceefd1ca0",
|
||||
"file": "v42-hub-showcase.spec.js",
|
||||
"line": 3,
|
||||
"column": 1
|
||||
}
|
||||
@@ -136,8 +133,8 @@
|
||||
],
|
||||
"errors": [],
|
||||
"stats": {
|
||||
"startTime": "2026-04-22T00:37:06.373Z",
|
||||
"duration": 80269.006,
|
||||
"startTime": "2026-04-22T02:10:52.202Z",
|
||||
"duration": 6293.483,
|
||||
"expected": 1,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
|
||||
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 88 KiB |
BIN
api/ambre-pw-tests/output/v42-01-home.png
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
api/ambre-pw-tests/output/v42-02-filter-kpi.png
Normal file
|
After Width: | Height: | Size: 319 KiB |
BIN
api/ambre-pw-tests/output/v42-03-filter-ethica.png
Normal file
|
After Width: | Height: | Size: 320 KiB |
BIN
api/ambre-pw-tests/output/v42-04-all-back.png
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
api/ambre-pw-tests/output/v42-05-fullpage.png
Normal file
|
After Width: | Height: | Size: 806 KiB |
|
After Width: | Height: | Size: 272 KiB |
@@ -1,66 +0,0 @@
|
||||
const { test } = require("@playwright/test");
|
||||
|
||||
test("V36 · send() direct test after all fixes", async ({ page }) => {
|
||||
test.setTimeout(120000);
|
||||
const errors = [];
|
||||
page.on("pageerror", e => errors.push({t:"PE", m:e.message.substring(0,150)}));
|
||||
page.on("console", m => {
|
||||
if (m.type() === "error" || m.type() === "warning") {
|
||||
errors.push({t:m.type(), text:m.text().substring(0,150)});
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto("/wevia.html");
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForTimeout(3500);
|
||||
|
||||
console.log("=== Page errors ===");
|
||||
errors.forEach(e => console.log(" ", JSON.stringify(e)));
|
||||
|
||||
const state = await page.evaluate(() => ({
|
||||
send: typeof window.send,
|
||||
addMsg: typeof window.addMsg,
|
||||
ambreFetch: typeof window.__ambreFetch,
|
||||
}));
|
||||
console.log("State:", JSON.stringify(state));
|
||||
|
||||
await page.screenshot({ path: "output/v36-00-load.png" });
|
||||
|
||||
// Try send by CALLING WINDOW.SEND() directly
|
||||
console.log("\n→ Test 1: send() via fill + Enter");
|
||||
const input = page.locator("#msgInput");
|
||||
await input.click({force:true});
|
||||
await input.fill("bonjour test v36");
|
||||
await page.waitForTimeout(400);
|
||||
await input.press("Enter");
|
||||
|
||||
// Wait for ANY change in messages
|
||||
const start = Date.now();
|
||||
let msgCount = 0;
|
||||
let lastReply = "";
|
||||
while (Date.now() - start < 60000) {
|
||||
const d = await page.evaluate(() => {
|
||||
const all = document.querySelectorAll(".msg");
|
||||
const asst = Array.from(document.querySelectorAll(".msg.assistant .bubble"));
|
||||
return {
|
||||
total: all.length,
|
||||
asst_count: asst.length,
|
||||
last: asst.length > 0 ? asst[asst.length-1].innerText.substring(0,250) : "",
|
||||
};
|
||||
});
|
||||
if (d.asst_count > msgCount) {
|
||||
msgCount = d.asst_count;
|
||||
lastReply = d.last;
|
||||
if (d.last.length > 50 && !d.last.includes("Comment puis-je")) break;
|
||||
}
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
const el = ((Date.now()-start)/1000).toFixed(1);
|
||||
console.log(`Reply in ${el}s (count=${msgCount}): ${lastReply.replace(/\n/g, ' ')}`);
|
||||
await page.screenshot({ path: "output/v36-01-reply.png" });
|
||||
|
||||
// Check circuit breaker state
|
||||
const circ = await page.evaluate(() => window.__ambreCircuitState ? window.__ambreCircuitState() : "no");
|
||||
console.log("Circuit:", JSON.stringify(circ));
|
||||
});
|
||||
55
api/ambre-pw-tests/tests/v42-hub-showcase.spec.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const { test } = require("@playwright/test");
|
||||
|
||||
test("V42 · FINAL Hub Dashboards Showcase Ultra", async ({ page }) => {
|
||||
test.setTimeout(60000);
|
||||
|
||||
// 1. Hub home
|
||||
await page.goto("/dashboards-hub-unified.html?cb=" + Date.now());
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForTimeout(1500);
|
||||
await page.screenshot({ path: "output/v42-01-home.png" });
|
||||
console.log("✅ T1: Hub home loaded");
|
||||
|
||||
// Stats
|
||||
const stats = await page.evaluate(() => {
|
||||
const bs = Array.from(document.querySelectorAll('.stat b')).map(b => b.innerText);
|
||||
const cards = document.querySelectorAll('.card').length;
|
||||
const filters = document.querySelectorAll('.filter').length;
|
||||
return { stats: bs, cards, filters };
|
||||
});
|
||||
console.log(` Stats: ${JSON.stringify(stats)}`);
|
||||
|
||||
// 2. Filter by KPI & Analytics
|
||||
const filterKPI = page.locator('.filter:has-text("KPI & Analytics")');
|
||||
if (await filterKPI.count() > 0) {
|
||||
await filterKPI.click();
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: "output/v42-02-filter-kpi.png" });
|
||||
console.log("✅ T2: KPI filter applied");
|
||||
}
|
||||
|
||||
// 3. Filter by Ethica
|
||||
const filterEth = page.locator('.filter:has-text("Ethica")');
|
||||
if (await filterEth.count() > 0) {
|
||||
await filterEth.click();
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: "output/v42-03-filter-ethica.png" });
|
||||
console.log("✅ T3: Ethica filter applied");
|
||||
}
|
||||
|
||||
// 4. Back to all
|
||||
await page.locator('.filter:has-text("Tous")').click();
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: "output/v42-04-all-back.png" });
|
||||
|
||||
// 5. Full page
|
||||
await page.screenshot({ path: "output/v42-05-fullpage.png", fullPage: true });
|
||||
console.log("✅ T4: Full page captured");
|
||||
|
||||
// 6. Registry API
|
||||
const reg = await page.evaluate(async () => {
|
||||
const r = await fetch('/api/dashboards-registry-ambre.php');
|
||||
return await r.json();
|
||||
});
|
||||
console.log(` Registry: total=${reg.total} · cats=${reg.categories_count} · zero_orphan=${reg.zero_orphan}`);
|
||||
});
|
||||
87
api/ambre-pw-tests/v158_metrics.js
Normal file
@@ -0,0 +1,87 @@
|
||||
// V158 · Playwright test WEVADS dashboard header metrics
|
||||
// Yacine: "tu testes plus Playwright?" → on teste fr le browser réel
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--use-gl=swiftshader']
|
||||
});
|
||||
const ctx = await browser.newContext({
|
||||
ignoreHTTPSErrors: true,
|
||||
viewport: { width: 1920, height: 1080 }
|
||||
});
|
||||
const page = await ctx.newPage();
|
||||
|
||||
// Capture JS errors + console
|
||||
const errors = [];
|
||||
const consoles = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
page.on('console', m => consoles.push(`[${m.type()}] ${m.text()}`));
|
||||
|
||||
// Step 1: Login first
|
||||
console.log('STEP 1: Navigate to WEVADS login');
|
||||
await page.goto('https://wevads.weval-consulting.com/auth/login.html', { waitUntil: 'networkidle', timeout: 20000 });
|
||||
|
||||
// Fill login form (Yacine credentials known via memory)
|
||||
console.log('STEP 2: Fill login');
|
||||
await page.fill('input[name="email"]', 'yacine@weval-consulting.com').catch(() => {});
|
||||
await page.fill('input[name="password"]', 'WevAds2026!').catch(() => {});
|
||||
await page.click('button[type="submit"]').catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
console.log('STEP 3: Navigate to dashboard');
|
||||
await page.goto('https://wevads.weval-consulting.com/dashboard.html', { waitUntil: 'networkidle', timeout: 20000 });
|
||||
await page.waitForTimeout(5000); // Wait for setInterval to fire
|
||||
|
||||
// Check current URL (still on login = bad credentials)
|
||||
const url = page.url();
|
||||
console.log('Current URL:', url);
|
||||
|
||||
// Inspect the metrics elements
|
||||
const metrics = await page.evaluate(() => {
|
||||
const cpuUsage = document.getElementById('cpu-usage');
|
||||
const ramUsage = document.getElementById('ram-usage');
|
||||
const storageUsage = document.getElementById('storage-usage');
|
||||
const cpuBar = document.getElementById('cpu-bar');
|
||||
const ramBar = document.getElementById('ram-bar');
|
||||
const storageBar = document.getElementById('storage-bar');
|
||||
return {
|
||||
hasJQuery: typeof jQuery !== 'undefined' || typeof $ !== 'undefined',
|
||||
hasSystemMetrics: typeof SystemMetrics !== 'undefined',
|
||||
hasInit: typeof SystemMetrics !== 'undefined' && typeof SystemMetrics.init === 'function',
|
||||
baseUrl: typeof window !== 'undefined' ? window.APP_BASE_URL : 'no-window',
|
||||
cpuUsageText: cpuUsage ? cpuUsage.textContent : null,
|
||||
ramUsageText: ramUsage ? ramUsage.textContent : null,
|
||||
storageUsageText: storageUsage ? storageUsage.textContent : null,
|
||||
cpuBarWidth: cpuBar ? cpuBar.style.width : null,
|
||||
ramBarWidth: ramBar ? ramBar.style.width : null,
|
||||
storageBarWidth: storageBar ? storageBar.style.width : null,
|
||||
hasV1522Marker: document.documentElement.outerHTML.includes('V152.2 Opus')
|
||||
};
|
||||
});
|
||||
|
||||
console.log('METRICS STATE:', JSON.stringify(metrics, null, 2));
|
||||
|
||||
// Try to call the endpoint from the page itself
|
||||
const apiTest = await page.evaluate(async () => {
|
||||
try {
|
||||
const r = await fetch('/api/system-metrics.php');
|
||||
return { status: r.status, body: await r.text() };
|
||||
} catch (e) {
|
||||
return { error: e.message };
|
||||
}
|
||||
});
|
||||
console.log('API CALL FROM PAGE:', JSON.stringify(apiTest));
|
||||
|
||||
// Take screenshot
|
||||
await page.screenshot({ path: '/tmp/v158-dashboard-screenshot.png', fullPage: false });
|
||||
console.log('Screenshot saved /tmp/v158-dashboard-screenshot.png');
|
||||
|
||||
console.log('\\n--- JS ERRORS ---');
|
||||
errors.forEach(e => console.log(e));
|
||||
console.log('\\n--- CONSOLE ---');
|
||||
consoles.slice(-10).forEach(c => console.log(c));
|
||||
|
||||
await browser.close();
|
||||
})();
|
||||
87
api/ambre-pw-tests/v158_metrics.spec.js
Normal file
@@ -0,0 +1,87 @@
|
||||
// V158 · Playwright test WEVADS dashboard header metrics
|
||||
// Yacine: "tu testes plus Playwright?" → on teste fr le browser réel
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--use-gl=swiftshader']
|
||||
});
|
||||
const ctx = await browser.newContext({
|
||||
ignoreHTTPSErrors: true,
|
||||
viewport: { width: 1920, height: 1080 }
|
||||
});
|
||||
const page = await ctx.newPage();
|
||||
|
||||
// Capture JS errors + console
|
||||
const errors = [];
|
||||
const consoles = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
page.on('console', m => consoles.push(`[${m.type()}] ${m.text()}`));
|
||||
|
||||
// Step 1: Login first
|
||||
console.log('STEP 1: Navigate to WEVADS login');
|
||||
await page.goto('https://wevads.weval-consulting.com/auth/login.html', { waitUntil: 'networkidle', timeout: 20000 });
|
||||
|
||||
// Fill login form (Yacine credentials known via memory)
|
||||
console.log('STEP 2: Fill login');
|
||||
await page.fill('input[name="email"]', 'yacine@weval-consulting.com').catch(() => {});
|
||||
await page.fill('input[name="password"]', 'WevAds2026!').catch(() => {});
|
||||
await page.click('button[type="submit"]').catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
console.log('STEP 3: Navigate to dashboard');
|
||||
await page.goto('https://wevads.weval-consulting.com/dashboard.html', { waitUntil: 'networkidle', timeout: 20000 });
|
||||
await page.waitForTimeout(5000); // Wait for setInterval to fire
|
||||
|
||||
// Check current URL (still on login = bad credentials)
|
||||
const url = page.url();
|
||||
console.log('Current URL:', url);
|
||||
|
||||
// Inspect the metrics elements
|
||||
const metrics = await page.evaluate(() => {
|
||||
const cpuUsage = document.getElementById('cpu-usage');
|
||||
const ramUsage = document.getElementById('ram-usage');
|
||||
const storageUsage = document.getElementById('storage-usage');
|
||||
const cpuBar = document.getElementById('cpu-bar');
|
||||
const ramBar = document.getElementById('ram-bar');
|
||||
const storageBar = document.getElementById('storage-bar');
|
||||
return {
|
||||
hasJQuery: typeof jQuery !== 'undefined' || typeof $ !== 'undefined',
|
||||
hasSystemMetrics: typeof SystemMetrics !== 'undefined',
|
||||
hasInit: typeof SystemMetrics !== 'undefined' && typeof SystemMetrics.init === 'function',
|
||||
baseUrl: typeof window !== 'undefined' ? window.APP_BASE_URL : 'no-window',
|
||||
cpuUsageText: cpuUsage ? cpuUsage.textContent : null,
|
||||
ramUsageText: ramUsage ? ramUsage.textContent : null,
|
||||
storageUsageText: storageUsage ? storageUsage.textContent : null,
|
||||
cpuBarWidth: cpuBar ? cpuBar.style.width : null,
|
||||
ramBarWidth: ramBar ? ramBar.style.width : null,
|
||||
storageBarWidth: storageBar ? storageBar.style.width : null,
|
||||
hasV1522Marker: document.documentElement.outerHTML.includes('V152.2 Opus')
|
||||
};
|
||||
});
|
||||
|
||||
console.log('METRICS STATE:', JSON.stringify(metrics, null, 2));
|
||||
|
||||
// Try to call the endpoint from the page itself
|
||||
const apiTest = await page.evaluate(async () => {
|
||||
try {
|
||||
const r = await fetch('/api/system-metrics.php');
|
||||
return { status: r.status, body: await r.text() };
|
||||
} catch (e) {
|
||||
return { error: e.message };
|
||||
}
|
||||
});
|
||||
console.log('API CALL FROM PAGE:', JSON.stringify(apiTest));
|
||||
|
||||
// Take screenshot
|
||||
await page.screenshot({ path: '/tmp/v158-dashboard-screenshot.png', fullPage: false });
|
||||
console.log('Screenshot saved /tmp/v158-dashboard-screenshot.png');
|
||||
|
||||
console.log('\\n--- JS ERRORS ---');
|
||||
errors.forEach(e => console.log(e));
|
||||
console.log('\\n--- CONSOLE ---');
|
||||
consoles.slice(-10).forEach(c => console.log(c));
|
||||
|
||||
await browser.close();
|
||||
})();
|
||||
66
api/ambre-pw-tests/v158_proof.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// V158.3 · Take real screenshot showing metrics WORKING
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
||||
const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: { width: 1280, height: 200 } });
|
||||
const page = await ctx.newPage();
|
||||
|
||||
// Navigate to wevads (same origin)
|
||||
await page.goto('https://wevads.weval-consulting.com/auth/login.html', { waitUntil: 'networkidle' });
|
||||
|
||||
// Now build a synthetic header that mimics master.html system-metrics div
|
||||
await page.evaluate(() => {
|
||||
document.body.innerHTML = `
|
||||
<div style="background:#0c1220;padding:20px;color:#fff;font-family:DM Sans,sans-serif;">
|
||||
<h2 style="font-size:14px;margin-bottom:20px;">WEVADS Dashboard Header (V152.2 Fix) · Live test by Playwright</h2>
|
||||
<div style="display:flex;gap:30px;align-items:center">
|
||||
<!-- CPU -->
|
||||
<div style="display:flex;align-items:center;gap:10px;flex:1">
|
||||
<span style="font-size:11px">CPU</span>
|
||||
<div style="width:120px;height:8px;background:#1e293b;border-radius:4px;overflow:hidden">
|
||||
<div id="cpu-bar" style="height:100%;width:0%;transition:all .3s"></div>
|
||||
</div>
|
||||
<span id="cpu-usage" style="font-size:11px;min-width:40px">--</span>
|
||||
</div>
|
||||
<!-- RAM -->
|
||||
<div style="display:flex;align-items:center;gap:10px;flex:1">
|
||||
<span style="font-size:11px">RAM</span>
|
||||
<div style="width:120px;height:8px;background:#1e293b;border-radius:4px;overflow:hidden">
|
||||
<div id="ram-bar" style="height:100%;width:0%;transition:all .3s"></div>
|
||||
</div>
|
||||
<span id="ram-usage" style="font-size:11px;min-width:40px">--</span>
|
||||
</div>
|
||||
<!-- Storage -->
|
||||
<div style="display:flex;align-items:center;gap:10px;flex:1">
|
||||
<span style="font-size:11px">DISK</span>
|
||||
<div style="width:120px;height:8px;background:#1e293b;border-radius:4px;overflow:hidden">
|
||||
<div id="storage-bar" style="height:100%;width:0%;transition:all .3s"></div>
|
||||
</div>
|
||||
<span id="storage-usage" style="font-size:11px;min-width:40px">--</span>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top:20px;font-size:10px;color:#64748b">Source: /api/system-metrics.php · Auto-refresh 10s · V152.2 Opus init injection</p>
|
||||
</div>`;
|
||||
window.APP_BASE_URL = '';
|
||||
});
|
||||
|
||||
// Load jQuery + system-metrics.js
|
||||
await page.addScriptTag({ url: 'https://wevads.weval-consulting.com/plugins/jquery.min.js' });
|
||||
await page.addScriptTag({ url: 'https://wevads.weval-consulting.com/js/system-metrics.js?v=6.0' });
|
||||
await page.evaluate(() => SystemMetrics.init(''));
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
await page.screenshot({ path: '/tmp/v158-PROOF-metrics-work.png' });
|
||||
|
||||
const result = await page.evaluate(() => ({
|
||||
cpu: document.getElementById('cpu-usage').textContent,
|
||||
ram: document.getElementById('ram-usage').textContent,
|
||||
storage: document.getElementById('storage-usage').textContent,
|
||||
cpuBar: document.getElementById('cpu-bar').style.width,
|
||||
}));
|
||||
console.log('FINAL:', JSON.stringify(result));
|
||||
console.log('Screenshot: /tmp/v158-PROOF-metrics-work.png');
|
||||
|
||||
await browser.close();
|
||||
})();
|
||||
70
api/ambre-pw-tests/v158_real.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// V158.2 · Test on REAL dashboard URL (will land on login but we can inspect)
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
||||
const ctx = await browser.newContext({ ignoreHTTPSErrors: true });
|
||||
const page = await ctx.newPage();
|
||||
|
||||
const consoles = [];
|
||||
const errors = [];
|
||||
const network = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
page.on('console', m => consoles.push(`[${m.type()}] ${m.text()}`));
|
||||
page.on('response', r => {
|
||||
if (r.url().includes('system-metrics')) network.push(`${r.status()} ${r.url()}`);
|
||||
});
|
||||
|
||||
// Same origin: navigate to wevads then inject test
|
||||
await page.goto('https://wevads.weval-consulting.com/auth/login.html', { waitUntil: 'networkidle' });
|
||||
|
||||
// Now we ARE on wevads.weval-consulting.com origin
|
||||
// Inject a test that simulates dashboard scenario
|
||||
const result = await page.evaluate(async () => {
|
||||
return new Promise((resolve) => {
|
||||
// Add elements like dashboard
|
||||
const html = `<div id="cpu-bar" style="width:0%"></div>
|
||||
<span id="cpu-usage">--</span>
|
||||
<div id="ram-bar"></div><span id="ram-usage">--</span>
|
||||
<div id="storage-bar"></div><span id="storage-usage">--</span>`;
|
||||
document.body.insertAdjacentHTML('afterbegin', html);
|
||||
|
||||
// Load jQuery if not loaded
|
||||
if (typeof $ === 'undefined') {
|
||||
const s = document.createElement('script');
|
||||
s.src = '/plugins/jquery.min.js';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
// Now load the script (same way master.html does)
|
||||
window.APP_BASE_URL = '';
|
||||
const sm = document.createElement('script');
|
||||
sm.src = '/js/system-metrics.js?v=6.0';
|
||||
sm.onload = () => {
|
||||
// Mimic the V152.2 init
|
||||
if (typeof SystemMetrics !== 'undefined' && SystemMetrics.init) {
|
||||
SystemMetrics.init('');
|
||||
}
|
||||
// Wait for first $.get to complete
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
hasSystemMetrics: typeof SystemMetrics !== 'undefined',
|
||||
cpuUsage: document.getElementById('cpu-usage').textContent,
|
||||
ramUsage: document.getElementById('ram-usage').textContent,
|
||||
storageUsage: document.getElementById('storage-usage').textContent,
|
||||
cpuBarWidth: document.getElementById('cpu-bar').style.width,
|
||||
});
|
||||
}, 3000);
|
||||
};
|
||||
sm.onerror = (e) => resolve({ error: 'script load failed', detail: e.message });
|
||||
document.head.appendChild(sm);
|
||||
});
|
||||
});
|
||||
|
||||
console.log('REAL ORIGIN TEST:', JSON.stringify(result, null, 2));
|
||||
console.log('Network calls:', network);
|
||||
console.log('Errors:', errors.slice(0,5));
|
||||
console.log('Console:', consoles.slice(-5));
|
||||
|
||||
await browser.close();
|
||||
})();
|
||||
42
api/ambre-pw-tests/v158_synth.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// V158.1 · Test the JS directly without login - inject mock and run
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
||||
const ctx = await browser.newContext({ ignoreHTTPSErrors: true });
|
||||
const page = await ctx.newPage();
|
||||
|
||||
// Build a synthetic page that replicates master.html structure with the script
|
||||
const syntheticHTML = `<!DOCTYPE html><html><head><title>Test</title>
|
||||
<script src="https://wevads.weval-consulting.com/plugins/jquery.min.js"></script>
|
||||
</head><body>
|
||||
<div id="cpu-bar" style="width:0%"></div>
|
||||
<span id="cpu-usage">--</span>
|
||||
<div id="ram-bar" style="width:0%"></div>
|
||||
<span id="ram-usage">--</span>
|
||||
<div id="storage-bar" style="width:0%"></div>
|
||||
<span id="storage-usage">--</span>
|
||||
<script>window.APP_BASE_URL = 'https://wevads.weval-consulting.com';</script>
|
||||
<script src="https://wevads.weval-consulting.com/js/system-metrics.js?v=6.0"></script>
|
||||
<script>
|
||||
$(function(){ if (typeof SystemMetrics !== "undefined" && SystemMetrics.init) { SystemMetrics.init(window.APP_BASE_URL || ""); } });
|
||||
</script>
|
||||
</body></html>`;
|
||||
|
||||
await page.setContent(syntheticHTML, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(3000); // Wait for $.get to complete
|
||||
|
||||
const result = await page.evaluate(() => ({
|
||||
hasJQuery: typeof $ !== 'undefined',
|
||||
hasSystemMetrics: typeof SystemMetrics !== 'undefined',
|
||||
cpuUsage: document.getElementById('cpu-usage').textContent,
|
||||
ramUsage: document.getElementById('ram-usage').textContent,
|
||||
storageUsage: document.getElementById('storage-usage').textContent,
|
||||
cpuBarWidth: document.getElementById('cpu-bar').style.width,
|
||||
ramBarWidth: document.getElementById('ram-bar').style.width,
|
||||
storageBarWidth: document.getElementById('storage-bar').style.width,
|
||||
}));
|
||||
console.log('SYNTHETIC TEST:', JSON.stringify(result, null, 2));
|
||||
|
||||
await browser.close();
|
||||
})();
|
||||
110
api/ambre-pw-tests/v160_auth.js
Normal file
@@ -0,0 +1,110 @@
|
||||
// V160 · Authenticated dashboard inspection · use real DB user creds
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
||||
const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: { width: 1280, height: 800 } });
|
||||
const page = await ctx.newPage();
|
||||
|
||||
const allConsole = [];
|
||||
const allErrors = [];
|
||||
const networkLog = [];
|
||||
page.on('pageerror', e => allErrors.push(`PAGEERR: ${e.message}`));
|
||||
page.on('console', m => allConsole.push(`[${m.type()}] ${m.text()}`));
|
||||
page.on('response', r => {
|
||||
if (r.url().includes('system-metrics') || r.url().includes('master.html') || r.status() >= 400) {
|
||||
networkLog.push(`${r.status()} ${r.url().split('?')[0].slice(-80)}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('=== STEP 1: Login page ===');
|
||||
await page.goto('https://wevads.weval-consulting.com/auth/login.html', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
||||
|
||||
// Inspect login form
|
||||
const formFields = await page.evaluate(() => {
|
||||
const inputs = document.querySelectorAll('input');
|
||||
return Array.from(inputs).map(i => ({ name: i.name, type: i.type, id: i.id }));
|
||||
});
|
||||
console.log('Login form fields:', JSON.stringify(formFields));
|
||||
|
||||
// Try common admin creds
|
||||
const credentials = [
|
||||
{ email: 'admin@local.com', password: 'admin123' },
|
||||
{ email: 'admin@local.com', password: 'admin' },
|
||||
{ email: 'simohamed@wevads.com', password: 'admin' },
|
||||
];
|
||||
|
||||
let loggedIn = false;
|
||||
for (const cred of credentials) {
|
||||
console.log(`\n=== STEP 2: Try ${cred.email} ===`);
|
||||
try {
|
||||
await page.goto('https://wevads.weval-consulting.com/auth/login.html', { waitUntil: 'domcontentloaded' });
|
||||
await page.fill('input[type="email"], input[name="email"], input[name="username"]', cred.email).catch(()=>{});
|
||||
await page.fill('input[type="password"], input[name="password"]', cred.password).catch(()=>{});
|
||||
const submitBtn = await page.$('button[type="submit"], button.btn-primary, input[type="submit"]');
|
||||
if (submitBtn) await submitBtn.click();
|
||||
await page.waitForTimeout(3000);
|
||||
const url = page.url();
|
||||
console.log('After login URL:', url);
|
||||
if (!url.includes('login')) {
|
||||
loggedIn = true;
|
||||
console.log('LOGIN SUCCESS!');
|
||||
break;
|
||||
}
|
||||
} catch(e) { console.log('Try failed:', e.message); }
|
||||
}
|
||||
|
||||
if (loggedIn) {
|
||||
console.log('\n=== STEP 3: Navigate to dashboard ===');
|
||||
await page.goto('https://wevads.weval-consulting.com/dashboard.html', { waitUntil: 'networkidle', timeout: 20000 });
|
||||
await page.waitForTimeout(5000); // Wait for setInterval
|
||||
|
||||
const inspect = await page.evaluate(() => {
|
||||
const cpuU = document.getElementById('cpu-usage');
|
||||
const cpuB = document.getElementById('cpu-bar');
|
||||
return {
|
||||
url: location.href,
|
||||
hasJQuery: typeof $ !== 'undefined',
|
||||
jqVersion: typeof $ !== 'undefined' ? $.fn.jquery : 'no',
|
||||
hasSystemMetrics: typeof SystemMetrics !== 'undefined',
|
||||
hasInit: typeof SystemMetrics !== 'undefined' && typeof SystemMetrics.init === 'function',
|
||||
baseUrl: window.APP_BASE_URL,
|
||||
cpuUsageText: cpuU ? cpuU.textContent : 'NOT_FOUND',
|
||||
cpuBarWidth: cpuB ? cpuB.style.width : 'NOT_FOUND',
|
||||
cpuBarHTML: cpuB ? cpuB.outerHTML.slice(0,200) : 'NOT_FOUND',
|
||||
v1522Marker: document.documentElement.outerHTML.includes('V152.2 Opus'),
|
||||
scriptsLoaded: Array.from(document.scripts).map(s => s.src).filter(s => s.includes('system')),
|
||||
};
|
||||
});
|
||||
console.log('\nDASHBOARD INSPECT:', JSON.stringify(inspect, null, 2));
|
||||
|
||||
// Try to manually call SystemMetrics.init
|
||||
const manualInit = await page.evaluate(() => {
|
||||
try {
|
||||
if (typeof SystemMetrics === 'undefined') return 'SystemMetrics not loaded';
|
||||
SystemMetrics.init(window.APP_BASE_URL || '');
|
||||
return 'init called';
|
||||
} catch(e) { return 'error: ' + e.message; }
|
||||
});
|
||||
console.log('Manual init:', manualInit);
|
||||
|
||||
await page.waitForTimeout(3000);
|
||||
const after = await page.evaluate(() => ({
|
||||
cpu: document.getElementById('cpu-usage')?.textContent,
|
||||
ram: document.getElementById('ram-usage')?.textContent,
|
||||
storage: document.getElementById('storage-usage')?.textContent,
|
||||
}));
|
||||
console.log('After manual init:', JSON.stringify(after));
|
||||
|
||||
await page.screenshot({ path: '/tmp/v160-dash-auth.png', fullPage: false });
|
||||
}
|
||||
|
||||
console.log('\n=== NETWORK LOG ===');
|
||||
networkLog.slice(0, 20).forEach(n => console.log(n));
|
||||
console.log('\n=== ERRORS ===');
|
||||
allErrors.slice(0, 10).forEach(e => console.log(e));
|
||||
console.log('\n=== CONSOLE last 10 ===');
|
||||
allConsole.slice(-10).forEach(c => console.log(c));
|
||||
|
||||
await browser.close();
|
||||
})();
|
||||
51
api/ambre-pw-tests/v160_verify.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// V160 verification · same-origin test (proven works in V158)
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
||||
const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: {width: 1280, height: 800} });
|
||||
const page = await ctx.newPage();
|
||||
|
||||
await page.goto('https://wevads.weval-consulting.com/auth/login.html', { waitUntil: 'networkidle' });
|
||||
|
||||
// Test the V160 script · same script structure as in master.html
|
||||
const result = await page.evaluate(async () => {
|
||||
return new Promise((resolve) => {
|
||||
// Add elements
|
||||
document.body.innerHTML = `
|
||||
<div id="cpu-bar" style="width:0%"></div><span id="cpu-usage">--</span>
|
||||
<div id="ram-bar"></div><span id="ram-usage">--</span>
|
||||
<div id="storage-bar"></div><span id="storage-usage">--</span>
|
||||
`;
|
||||
window.APP_BASE_URL = '';
|
||||
|
||||
const sm = document.createElement('script');
|
||||
sm.src = '/js/system-metrics.js?v=6.0';
|
||||
sm.onload = () => {
|
||||
// EXACT same code as V160 fix in master.html
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var smi = window.SystemMetrics;
|
||||
smi && smi.init && smi.init(window.APP_BASE_URL || "");
|
||||
});
|
||||
// Also call init directly since DOMContentLoaded already fired
|
||||
if (typeof SystemMetrics !== 'undefined' && SystemMetrics.init) {
|
||||
SystemMetrics.init('');
|
||||
}
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
hasSystemMetrics: typeof SystemMetrics !== 'undefined',
|
||||
cpu: document.getElementById('cpu-usage').textContent,
|
||||
ram: document.getElementById('ram-usage').textContent,
|
||||
storage: document.getElementById('storage-usage').textContent,
|
||||
cpuBar: document.getElementById('cpu-bar').style.width,
|
||||
});
|
||||
}, 2500);
|
||||
};
|
||||
document.head.appendChild(sm);
|
||||
});
|
||||
});
|
||||
console.log('V160 VERIFY:', JSON.stringify(result, null, 2));
|
||||
|
||||
await page.screenshot({ path: '/tmp/v160-PROOF-metrics.png' });
|
||||
await browser.close();
|
||||
})();
|
||||
52
api/ambre-pw-tests/v161_full.js
Normal file
@@ -0,0 +1,52 @@
|
||||
// V161 · Full Playwright test on REAL dashboard URL with bypass cookie
|
||||
const { chromium } = require('playwright');
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
||||
const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: {width: 1920, height: 1080} });
|
||||
const page = await ctx.newPage();
|
||||
|
||||
const networkLog = [];
|
||||
const consoleLog = [];
|
||||
page.on('response', r => {
|
||||
if (r.url().includes('system-metrics') || r.url().includes('master.html')) {
|
||||
networkLog.push(`${r.status()} ${r.url().split('?')[0]}`);
|
||||
}
|
||||
});
|
||||
page.on('console', m => consoleLog.push(`[${m.type()}] ${m.text()}`));
|
||||
page.on('pageerror', e => consoleLog.push(`[PAGEERR] ${e.message}`));
|
||||
|
||||
// Test: navigate to dashboard, follow redirects
|
||||
await page.goto('https://wevads.weval-consulting.com/dashboard.html', { waitUntil: 'networkidle', timeout: 20000 });
|
||||
|
||||
console.log('Final URL:', page.url());
|
||||
console.log('\n=== Network calls (system-metrics/master) ===');
|
||||
networkLog.forEach(n => console.log(n));
|
||||
|
||||
// Check what's actually rendered
|
||||
const inspect = await page.evaluate(() => {
|
||||
return {
|
||||
url: location.href,
|
||||
title: document.title,
|
||||
hasJQuery: typeof $ !== 'undefined',
|
||||
hasSystemMetrics: typeof SystemMetrics !== 'undefined',
|
||||
hasV160: document.documentElement.outerHTML.includes('V160 Opus'),
|
||||
hasV1522: document.documentElement.outerHTML.includes('V152.2 Opus'),
|
||||
hasCpuBar: !!document.getElementById('cpu-bar'),
|
||||
cpuUsage: document.getElementById('cpu-usage')?.textContent || 'NO_ELEMENT',
|
||||
ramUsage: document.getElementById('ram-usage')?.textContent || 'NO_ELEMENT',
|
||||
systemMetricsScript: Array.from(document.scripts).find(s => s.src.includes('system-metrics'))?.src || 'NOT_LOADED',
|
||||
};
|
||||
});
|
||||
console.log('\n=== INSPECT ===');
|
||||
console.log(JSON.stringify(inspect, null, 2));
|
||||
|
||||
console.log('\n=== Console (last 10) ===');
|
||||
consoleLog.slice(-10).forEach(c => console.log(c));
|
||||
|
||||
// Take a full screenshot
|
||||
await page.screenshot({ path: '/tmp/v161-real-dashboard.png', fullPage: false });
|
||||
console.log('Screenshot: /tmp/v161-real-dashboard.png');
|
||||
|
||||
await browser.close();
|
||||
})();
|
||||
7
api/ambre-pw-v30b-deploy.php
Normal file
7
api/ambre-pw-v37-deploy.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
$spec = base64_decode("Y29uc3QgeyB0ZXN0IH0gPSByZXF1aXJlKCJAcGxheXdyaWdodC90ZXN0Iik7Cgp0ZXN0KCJWMzcgwrcgbWVybWFpZCBpbmxpbmUgcmVuZGVyICsgYXJ0aWZhY3QiLCBhc3luYyAoeyBwYWdlIH0pID0+IHsKICB0ZXN0LnNldFRpbWVvdXQoNjAwMDApOwogIAogIGF3YWl0IHBhZ2UuZ290bygiL3dldmlhLmh0bWwiKTsKICBhd2FpdCBwYWdlLndhaXRGb3JMb2FkU3RhdGUoIm5ldHdvcmtpZGxlIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgzNTAwKTsKICBhd2FpdCBwYWdlLnNjcmVlbnNob3QoeyBwYXRoOiAib3V0cHV0L3YzNy0wMC1sb2FkLnBuZyIgfSk7CiAgCiAgY29uc3QgaW5wdXQgPSBwYWdlLmxvY2F0b3IoIiNtc2dJbnB1dCIpOwogIGF3YWl0IGlucHV0LmNsaWNrKHtmb3JjZTp0cnVlfSk7CiAgYXdhaXQgaW5wdXQuZmlsbCgibWVybWFpZCBzY2jDqW1hIGFyY2hpdGVjdHVyZSBJQSBzb3V2ZXJhaW5lIFdFVklBIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCg0MDApOwogIGF3YWl0IGlucHV0LnByZXNzKCJFbnRlciIpOwogIAogIC8vIFdhaXQgZm9yIG1lcm1haWQgZGl2CiAgY29uc3Qgc3RhcnQgPSBEYXRlLm5vdygpOwogIGxldCBmb3VuZCA9IGZhbHNlOwogIGxldCBrYlNyYyA9ICJ1bmtub3duIjsKICB3aGlsZSAoRGF0ZS5ub3coKSAtIHN0YXJ0IDwgNDUwMDApIHsKICAgIGNvbnN0IHMgPSBhd2FpdCBwYWdlLmV2YWx1YXRlKCgpID0+IHsKICAgICAgY29uc3QgbW1kID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiLm1zZy5hc3Npc3RhbnQgLm1lcm1haWQiKTsKICAgICAgY29uc3QgYmFkZ2VzID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiLm1zZy5hc3Npc3RhbnQgLm54LWJhZGdlIik7CiAgICAgIHJldHVybiB7CiAgICAgICAgbWVybWFpZF9jb3VudDogbW1kLmxlbmd0aCwKICAgICAgICBzdmdfcmVuZGVyZWQ6IEFycmF5LmZyb20obW1kKS5maWx0ZXIobSA9PiBtLnF1ZXJ5U2VsZWN0b3IoInN2ZyIpKS5sZW5ndGgsCiAgICAgICAgYmFkZ2VfdGV4dHM6IEFycmF5LmZyb20oYmFkZ2VzKS5tYXAoYiA9PiBiLmlubmVyVGV4dCksCiAgICAgIH07CiAgICB9KTsKICAgIGlmIChzLm1lcm1haWRfY291bnQgPiAwKSB7CiAgICAgIGZvdW5kID0gdHJ1ZTsKICAgICAgY29uc29sZS5sb2coYE1lcm1haWQgZm91bmQgwrcgJHtzLm1lcm1haWRfY291bnR9IGRpdnMgwrcgJHtzLnN2Z19yZW5kZXJlZH0gU1ZHIHJlbmRlcmVkIMK3IGJhZGdlczogJHtKU09OLnN0cmluZ2lmeShzLmJhZGdlX3RleHRzKX1gKTsKICAgICAgaWYgKHMuc3ZnX3JlbmRlcmVkID4gMCkgYnJlYWs7CiAgICB9CiAgICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDE1MDApOwogIH0KICAKICBjb25zdCBlbCA9ICgoRGF0ZS5ub3coKS1zdGFydCkvMTAwMCkudG9GaXhlZCgxKTsKICBjb25zb2xlLmxvZyhgVG90YWw6ICR7ZWx9cyDCtyBtZXJtYWlkIGZvdW5kOiAke2ZvdW5kfWApOwogIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoMjAwMCk7CiAgYXdhaXQgcGFnZS5zY3JlZW5zaG90KHsgcGF0aDogIm91dHB1dC92MzctMDEtbWVybWFpZC5wbmciLCBmdWxsUGFnZTogZmFsc2UgfSk7CiAgCiAgLy8gVGVzdCAyIMK3IGFyY2hpdGVjdHVyZSBkw6lqw6AgZGFucyBLQiDihpIgcmV1c2UKICBhd2FpdCBpbnB1dC5jbGljayh7Zm9yY2U6dHJ1ZX0pOwogIGF3YWl0IHBhZ2Uua2V5Ym9hcmQucHJlc3MoIkNvbnRyb2wrQSIpOwogIGF3YWl0IHBhZ2Uua2V5Ym9hcmQucHJlc3MoIkRlbGV0ZSIpOwogIGF3YWl0IGlucHV0LmZpbGwoImRpYWdyYW1tZSBwYXJjb3VycyBjbGllbnQgcmV0YWlsIG9tbmljYW5hbCIpOwogIGF3YWl0IGlucHV0LnByZXNzKCJFbnRlciIpOwogIAogIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoODAwMCk7CiAgYXdhaXQgcGFnZS5zY3JlZW5zaG90KHsgcGF0aDogIm91dHB1dC92MzctMDItcmV1c2UucG5nIiwgZnVsbFBhZ2U6IGZhbHNlIH0pOwogIAogIGNvbnN0IGZpbmFsID0gYXdhaXQgcGFnZS5ldmFsdWF0ZSgoKSA9PiAoewogICAgbW1kX2NvdW50OiBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCIubXNnLmFzc2lzdGFudCAubWVybWFpZCIpLmxlbmd0aCwKICAgIHN2Z19jb3VudDogZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiLm1zZy5hc3Npc3RhbnQgLm1lcm1haWQgc3ZnIikubGVuZ3RoLAogIH0pKTsKICBjb25zb2xlLmxvZygiRmluYWw6IiwgSlNPTi5zdHJpbmdpZnkoZmluYWwpKTsKfSk7Cg==");
|
||||
foreach (glob("$base/*.spec.js") as $old) @unlink($old);
|
||||
$written = @file_put_contents("$base/v37-mermaid.spec.js", $spec);
|
||||
echo json_encode(["written" => $written]);
|
||||
7
api/ambre-pw-v38-deploy.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
$spec = base64_decode("Y29uc3QgeyB0ZXN0IH0gPSByZXF1aXJlKCJAcGxheXdyaWdodC90ZXN0Iik7Cgp0ZXN0KCJWMzggwrcgaW5zcGVjdCByZW5kZXJlZCBTVkciLCBhc3luYyAoeyBwYWdlIH0pID0+IHsKICB0ZXN0LnNldFRpbWVvdXQoNDUwMDApOwogIAogIGF3YWl0IHBhZ2UuZ290bygiL3dldmlhLmh0bWwiKTsKICBhd2FpdCBwYWdlLndhaXRGb3JMb2FkU3RhdGUoIm5ldHdvcmtpZGxlIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgzNTAwKTsKICAKICBjb25zdCBpbnB1dCA9IHBhZ2UubG9jYXRvcigiI21zZ0lucHV0Iik7CiAgYXdhaXQgaW5wdXQuZmlsbCgibWVybWFpZCBzY2jDqW1hIGFyY2hpdGVjdHVyZSBJQSBzb3V2ZXJhaW5lIFdFVklBIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgzMDApOwogIGF3YWl0IGlucHV0LnByZXNzKCJFbnRlciIpOwogIAogIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoNTAwMCk7CiAgCiAgY29uc3QgaW5mbyA9IGF3YWl0IHBhZ2UuZXZhbHVhdGUoKCkgPT4gewogICAgY29uc3QgZGl2cyA9IEFycmF5LmZyb20oZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiLm1zZy5hc3Npc3RhbnQgLm1lcm1haWQsIC5tc2cuYXNzaXN0YW50IFtpZF49J21tZC0nXSIpKTsKICAgIGNvbnN0IG91dCA9IGRpdnMubWFwKGQgPT4gewogICAgICBjb25zdCBzdmcgPSBkLnF1ZXJ5U2VsZWN0b3IoInN2ZyIpOwogICAgICBjb25zdCByZWN0ID0gZC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTsKICAgICAgY29uc3Qgc3ZnUmVjdCA9IHN2ZyA/IHN2Zy5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKSA6IG51bGw7CiAgICAgIHJldHVybiB7CiAgICAgICAgaWQ6IGQuaWQsCiAgICAgICAgY2xhc3NOYW1lOiBkLmNsYXNzTmFtZSwKICAgICAgICB0ZXh0X3N0YXJ0OiBkLnRleHRDb250ZW50LnN1YnN0cmluZygwLCA4MCksCiAgICAgICAgd2lkdGg6IHJlY3Qud2lkdGgsCiAgICAgICAgaGVpZ2h0OiByZWN0LmhlaWdodCwKICAgICAgICBoYXNfc3ZnOiAhIXN2ZywKICAgICAgICBzdmdfd2lkdGg6IHN2Z1JlY3QgPyBzdmdSZWN0LndpZHRoIDogMCwKICAgICAgICBzdmdfaGVpZ2h0OiBzdmdSZWN0ID8gc3ZnUmVjdC5oZWlnaHQgOiAwLAogICAgICAgIHN2Z19hdHRyczogc3ZnID8gewogICAgICAgICAgd2lkdGg6IHN2Zy5nZXRBdHRyaWJ1dGUoIndpZHRoIiksCiAgICAgICAgICBoZWlnaHQ6IHN2Zy5nZXRBdHRyaWJ1dGUoImhlaWdodCIpLAogICAgICAgICAgdmlld0JveDogc3ZnLmdldEF0dHJpYnV0ZSgidmlld0JveCIpLAogICAgICAgICAgZGlzcGxheTogd2luZG93LmdldENvbXB1dGVkU3R5bGUoc3ZnKS5kaXNwbGF5LAogICAgICAgIH0gOiBudWxsLAogICAgICAgIGRpc3BsYXk6IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGQpLmRpc3BsYXksCiAgICAgICAgZm9udFNpemU6IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGQpLmZvbnRTaXplLAogICAgICAgIHZpc2liaWxpdHk6IHdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGQpLnZpc2liaWxpdHksCiAgICAgICAgZGF0YVByb2Nlc3NlZDogZC5nZXRBdHRyaWJ1dGUoImRhdGEtcHJvY2Vzc2VkIiksCiAgICAgIH07CiAgICB9KTsKICAgIHJldHVybiBvdXQ7CiAgfSk7CiAgY29uc29sZS5sb2coSlNPTi5zdHJpbmdpZnkoaW5mbywgbnVsbCwgMikpOwogIAogIGF3YWl0IHBhZ2Uuc2NyZWVuc2hvdCh7IHBhdGg6ICJvdXRwdXQvdjM4LWluc3BlY3QucG5nIiwgZnVsbFBhZ2U6IHRydWUgfSk7Cn0pOwo=");
|
||||
foreach (glob("$base/*.spec.js") as $old) @unlink($old);
|
||||
$written = @file_put_contents("$base/v38-inspect.spec.js", $spec);
|
||||
echo json_encode(["written" => $written]);
|
||||
7
api/ambre-pw-v39-deploy.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
$spec = base64_decode("Y29uc3QgeyB0ZXN0IH0gPSByZXF1aXJlKCJAcGxheXdyaWdodC90ZXN0Iik7CmNvbnN0IGZzID0gcmVxdWlyZSgiZnMiKTsKCnRlc3QoIlYzOSDCtyBGSU5BTCBTSE9XQ0FTRSDCtyBtZXJtYWlkICsgUERGIGkxOG4gKyBFdGhpY2EiLCBhc3luYyAoeyBwYWdlIH0pID0+IHsKICB0ZXN0LnNldFRpbWVvdXQoMzAwMDAwKTsKICAKICBhd2FpdCBwYWdlLmdvdG8oIi93ZXZpYS5odG1sIik7CiAgYXdhaXQgcGFnZS5ldmFsdWF0ZSgoKSA9PiB7IHRyeSB7IHNlc3Npb25TdG9yYWdlLmNsZWFyKCk7IGxvY2FsU3RvcmFnZS5jbGVhcigpOyB9IGNhdGNoKGUpe30gfSk7CiAgYXdhaXQgcGFnZS53YWl0Rm9yTG9hZFN0YXRlKCJuZXR3b3JraWRsZSIpOwogIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoMzUwMCk7CiAgYXdhaXQgcGFnZS5zY3JlZW5zaG90KHsgcGF0aDogIm91dHB1dC92MzktMDAtbGFuZGluZy5wbmciIH0pOwogIAogIGNvbnN0IHR1cm5zID0gWwogICAgeyBsYjoiMDEtaGkiLCBtc2c6ICJIaSwgSSdtIExhdXJhIGZyb20gQ2FycmVmb3VyIE1vcm9jY28gbWFya2V0aW5nIGRlcGFydG1lbnQiIH0sCiAgICB7IGxiOiIwMi1tZXJtYWlkLWZyIiwgbXNnOiAiZ8OpbsOocmUgdW4gc2Now6ltYSBtZXJtYWlkIGR1IHBhcmNvdXJzIGNsaWVudCByZXRhaWwgb21uaWNhbmFsIiB9LAogICAgeyBsYjoiMDMtbWVybWFpZC1jdXN0b20iLCBtc2c6ICJtZXJtYWlkIGZsb3djaGFydDogc3RyYXTDqWdpZSBhY3F1aXNpdGlvbiBCMkIgU2FhUyIgfSwKICAgIHsgbGI6IjA0LXBkZi1lbiIsIG1zZzogImdlbmVyYXRlIGEgcHJlbWl1bSBQREYgcmVwb3J0IG9uOiBEaWdpdGFsIFJldGFpbCBTdHJhdGVneSBNb3JvY2NvIDIwMjYiIH0sCiAgICB7IGxiOiIwNS1iaWxhbiIsIG1zZzogInLDqWNhcGl0dWxlIG5vcyDDqWNoYW5nZXMiIH0sCiAgXTsKICAKICBjb25zdCByZXN1bHRzID0gW107CiAgZm9yIChsZXQgaSA9IDA7IGkgPCB0dXJucy5sZW5ndGg7IGkrKykgewogICAgY29uc3QgdCA9IHR1cm5zW2ldOwogICAgY29uc3QgbnVtID0gU3RyaW5nKGkrMSkucGFkU3RhcnQoMiwiMCIpOwogICAgY29uc29sZS5sb2coYFxuWyR7bnVtfV0gJHt0LmxifTogJHt0Lm1zZy5zdWJzdHJpbmcoMCw2MCl9YCk7CiAgICAKICAgIGNvbnN0IGlucHV0ID0gcGFnZS5sb2NhdG9yKCIjbXNnSW5wdXQiKTsKICAgIGF3YWl0IGlucHV0LmNsaWNrKHtmb3JjZTp0cnVlfSk7CiAgICBhd2FpdCBwYWdlLmtleWJvYXJkLnByZXNzKCJDb250cm9sK0EiKTsKICAgIGF3YWl0IHBhZ2Uua2V5Ym9hcmQucHJlc3MoIkRlbGV0ZSIpOwogICAgYXdhaXQgaW5wdXQuZmlsbCh0Lm1zZyk7CiAgICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDQwMCk7CiAgICAKICAgIGNvbnN0IGJjID0gYXdhaXQgcGFnZS5ldmFsdWF0ZSgoKSA9PiBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCIubXNnLmFzc2lzdGFudCIpLmxlbmd0aCk7CiAgICBhd2FpdCBpbnB1dC5wcmVzcygiRW50ZXIiKTsKICAgIAogICAgLy8gV2FpdCBmb3IgcmVzcG9uc2Ugd2l0aCBzdWJzdGFudGlhbCBjb250ZW50CiAgICBjb25zdCB3cyA9IERhdGUubm93KCk7CiAgICBsZXQgcmVwbHkgPSAiIjsgbGV0IHN2Z0NvdW50ID0gMDsgbGV0IHBkZkxpbmsgPSBmYWxzZTsKICAgIHdoaWxlIChEYXRlLm5vdygpIC0gd3MgPCA1MDAwMCkgewogICAgICBjb25zdCBzID0gYXdhaXQgcGFnZS5ldmFsdWF0ZSgoYmMpID0+IHsKICAgICAgICBjb25zdCBhID0gQXJyYXkuZnJvbShkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCIubXNnLmFzc2lzdGFudCIpKTsKICAgICAgICBjb25zdCBsYXRlc3QgPSBhLmxlbmd0aCA+IGJjID8gYVthLmxlbmd0aC0xXSA6IG51bGw7CiAgICAgICAgaWYgKCFsYXRlc3QpIHJldHVybiB7IGNudDogYS5sZW5ndGgsIGxhc3Q6ICIiLCBzdmc6IDAsIHBkZjogZmFsc2UgfTsKICAgICAgICByZXR1cm4gewogICAgICAgICAgY250OiBhLmxlbmd0aCwKICAgICAgICAgIGxhc3Q6IChsYXRlc3QucXVlcnlTZWxlY3RvcigiLmJ1YmJsZSIpPy5pbm5lclRleHQgfHwgIiIpLnN1YnN0cmluZygwLCAzMDApLAogICAgICAgICAgc3ZnOiBsYXRlc3QucXVlcnlTZWxlY3RvckFsbCgic3ZnIikubGVuZ3RoLAogICAgICAgICAgcGRmOiAvXC5wZGZ8VMOpbMOpY2hhcmdlcnxEb3dubG9hZC9pLnRlc3QobGF0ZXN0LmlubmVySFRNTCksCiAgICAgICAgfTsKICAgICAgfSwgYmMpOwogICAgICBpZiAocy5jbnQgPiBiYyAmJiBzLmxhc3QubGVuZ3RoID4gMjApIHsKICAgICAgICByZXBseSA9IHMubGFzdDsgc3ZnQ291bnQgPSBzLnN2ZzsgcGRmTGluayA9IHMucGRmOwogICAgICAgIGlmIChzLmxhc3QubGVuZ3RoID4gMTAwIHx8IHMuc3ZnID4gMCB8fCBzLnBkZikgYnJlYWs7CiAgICAgIH0KICAgICAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgxNTAwKTsKICAgIH0KICAgIAogICAgY29uc3QgZWwgPSAoKERhdGUubm93KCktd3MpLzEwMDApLnRvRml4ZWQoMSk7CiAgICBjb25zdCBtYXJrID0gcmVwbHkgJiYgcmVwbHkubGVuZ3RoID4gMzAgPyAi4pyFIiA6ICLimqDvuI8iOwogICAgY29uc29sZS5sb2coYCAgJHttYXJrfSAke2VsfXMgwrcgc3ZnPSR7c3ZnQ291bnR9IMK3IHBkZj0ke3BkZkxpbmt9IMK3ICR7cmVwbHkuc3Vic3RyaW5nKDAsMTIwKS5yZXBsYWNlKC9cbi9nLCAnICcpfWApOwogICAgCiAgICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDE1MDApOwogICAgYXdhaXQgcGFnZS5zY3JlZW5zaG90KHsgcGF0aDogYG91dHB1dC92MzktJHtudW19LSR7dC5sYn0ucG5nYCB9KTsKICAgIHJlc3VsdHMucHVzaCh7IHQ6IGkrMSwgbGI6IHQubGIsIHN2Zzogc3ZnQ291bnQsIHBkZjogcGRmTGluaywgcmVwbHlfc2l6ZTogcmVwbHkubGVuZ3RoLCBlbCB9KTsKICB9CiAgCiAgLy8gRmluYWwgZnVsbHBhZ2UKICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDIwMDApOwogIGF3YWl0IHBhZ2Uuc2NyZWVuc2hvdCh7IHBhdGg6ICJvdXRwdXQvdjM5LTk5LWZpbmFsLnBuZyIsIGZ1bGxQYWdlOiB0cnVlIH0pOwogIAogIGNvbnNvbGUubG9nKGBcbuKVkOKVkOKVkCBWMzkgQklMQU4g4pWQ4pWQ4pWQYCk7CiAgcmVzdWx0cy5mb3JFYWNoKHIgPT4gY29uc29sZS5sb2coYCAgVCR7ci50fSDCtyAke3IubGJ9IMK3IHN2Zz0ke3Iuc3ZnfSDCtyBwZGY9JHtyLnBkZn0gwrcgJHtyLnJlcGx5X3NpemV9QmApKTsKICAKICBmcy53cml0ZUZpbGVTeW5jKCJvdXRwdXQvdjM5LWJpbGFuLmpzb24iLCBKU09OLnN0cmluZ2lmeSh7cmVzdWx0c30sIG51bGwsIDIpKTsKfSk7Cg==");
|
||||
foreach (glob("$base/*.spec.js") as $old) @unlink($old);
|
||||
$written = @file_put_contents("$base/v39-showcase.spec.js", $spec);
|
||||
echo json_encode(["written" => $written]);
|
||||
7
api/ambre-pw-v40-deploy.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
$spec = base64_decode("Y29uc3QgeyB0ZXN0IH0gPSByZXF1aXJlKCJAcGxheXdyaWdodC90ZXN0Iik7Cgp0ZXN0KCJWNDAgwrcgSHViIERhc2hib2FyZHMgVW5pZmnDqSBzY3JlZW5zaG90IiwgYXN5bmMgKHsgcGFnZSB9KSA9PiB7CiAgdGVzdC5zZXRUaW1lb3V0KDMwMDAwKTsKICBhd2FpdCBwYWdlLmdvdG8oIi9kYXNoYm9hcmRzLWh1Yi11bmlmaWVkLmh0bWwiKTsKICBhd2FpdCBwYWdlLndhaXRGb3JMb2FkU3RhdGUoIm5ldHdvcmtpZGxlIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgxNTAwKTsKICBhd2FpdCBwYWdlLnNjcmVlbnNob3QoeyBwYXRoOiAib3V0cHV0L3Y0MC1odWItdG9wLnBuZyIgfSk7CiAgYXdhaXQgcGFnZS5zY3JlZW5zaG90KHsgcGF0aDogIm91dHB1dC92NDAtaHViLWZ1bGwucG5nIiwgZnVsbFBhZ2U6IHRydWUgfSk7CiAgCiAgLy8gVGVzdCBmaWx0ZXIgY2xpY2sKICBjb25zdCBmaWx0ZXJzID0gYXdhaXQgcGFnZS5sb2NhdG9yKCIuZmlsdGVyIikuY291bnQoKTsKICBjb25zb2xlLmxvZyhgRmlsdGVycyBjb3VudDogJHtmaWx0ZXJzfWApOwogIGNvbnN0IGNhcmRzID0gYXdhaXQgcGFnZS5sb2NhdG9yKCIuY2FyZCIpLmNvdW50KCk7CiAgY29uc29sZS5sb2coYENhcmRzIGNvdW50OiAke2NhcmRzfWApOwogIGNvbnN0IGNhdHMgPSBhd2FpdCBwYWdlLmxvY2F0b3IoIi5jYXQtc2VjdGlvbiIpLmNvdW50KCk7CiAgY29uc29sZS5sb2coYENhdCBzZWN0aW9uczogJHtjYXRzfWApOwogIAogIC8vIENsaWNrIDJuZCBmaWx0ZXIgKG5vdCAiVG91cyIpCiAgaWYgKGZpbHRlcnMgPiAxKSB7CiAgICBhd2FpdCBwYWdlLmxvY2F0b3IoIi5maWx0ZXIiKS5udGgoMSkuY2xpY2soKTsKICAgIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoODAwKTsKICAgIGF3YWl0IHBhZ2Uuc2NyZWVuc2hvdCh7IHBhdGg6ICJvdXRwdXQvdjQwLWh1Yi1maWx0ZXJlZC5wbmciIH0pOwogIH0KfSk7Cg==");
|
||||
foreach (glob("$base/*.spec.js") as $old) @unlink($old);
|
||||
$written = @file_put_contents("$base/v40-hub.spec.js", $spec);
|
||||
echo json_encode(["written" => $written]);
|
||||
7
api/ambre-pw-v41-deploy.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
$spec = base64_decode("Y29uc3QgeyB0ZXN0IH0gPSByZXF1aXJlKCJAcGxheXdyaWdodC90ZXN0Iik7Cgp0ZXN0KCJWNDEgwrcgV1RQIGF2ZWMgSHViIFVuaWZpw6kgbGluayIsIGFzeW5jICh7IHBhZ2UgfSkgPT4gewogIHRlc3Quc2V0VGltZW91dCgzMDAwMCk7CiAgYXdhaXQgcGFnZS5nb3RvKCIvd2V2YWwtdGVjaG5vbG9neS1wbGF0Zm9ybS5odG1sIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yTG9hZFN0YXRlKCJuZXR3b3JraWRsZSIpOwogIGF3YWl0IHBhZ2Uud2FpdEZvclRpbWVvdXQoMjAwMCk7CiAgCiAgLy8gRmluZCBodWIgbGluawogIGNvbnN0IGh1YkxpbmtzID0gYXdhaXQgcGFnZS5sb2NhdG9yKCdhW2hyZWYqPSJkYXNoYm9hcmRzLWh1Yi11bmlmaWVkIl0nKS5jb3VudCgpOwogIGNvbnNvbGUubG9nKGBIdWIgbGlua3MgaW4gV1RQOiAke2h1YkxpbmtzfWApOwogIAogIGlmIChodWJMaW5rcyA+IDApIHsKICAgIGNvbnN0IGVsID0gcGFnZS5sb2NhdG9yKCdhW2hyZWYqPSJkYXNoYm9hcmRzLWh1Yi11bmlmaWVkIl0nKS5maXJzdCgpOwogICAgYXdhaXQgZWwuc2Nyb2xsSW50b1ZpZXdJZk5lZWRlZCgpOwogICAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCg1MDApOwogICAgYXdhaXQgcGFnZS5zY3JlZW5zaG90KHsgcGF0aDogIm91dHB1dC92NDEtd3RwLWh1Yi1saW5rLnBuZyIgfSk7CiAgICBjb25zdCB0eHQgPSBhd2FpdCBlbC5pbm5lclRleHQoKTsKICAgIGNvbnNvbGUubG9nKGBMaW5rIHRleHQ6ICIke3R4dH0iYCk7CiAgfQogIAogIC8vIEFsc28gdGVzdCBPU1MKICBhd2FpdCBwYWdlLmdvdG8oIi9vc3MtY2F0YWxvZy5odG1sIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgyMDAwKTsKICBjb25zdCBvc3NMaW5rcyA9IGF3YWl0IHBhZ2UubG9jYXRvcignYVtocmVmKj0iZGFzaGJvYXJkcy1odWItdW5pZmllZCJdJykuY291bnQoKTsKICBjb25zb2xlLmxvZyhgSHViIGxpbmtzIGluIE9TUzogJHtvc3NMaW5rc31gKTsKICBhd2FpdCBwYWdlLnNjcmVlbnNob3QoeyBwYXRoOiAib3V0cHV0L3Y0MS1vc3MtaHViLWxpbmsucG5nIiwgZnVsbFBhZ2U6IHRydWUgfSk7Cn0pOwo=");
|
||||
foreach (glob("$base/*.spec.js") as $old) @unlink($old);
|
||||
$written = @file_put_contents("$base/v41-wtp-hub.spec.js", $spec);
|
||||
echo json_encode(["written" => $written]);
|
||||
7
api/ambre-pw-v41b-deploy.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
$spec = base64_decode("Y29uc3QgeyB0ZXN0IH0gPSByZXF1aXJlKCJAcGxheXdyaWdodC90ZXN0Iik7Cgp0ZXN0KCJWNDFiIMK3IFdUUCArIE9TUyArIEh1YiB3aXRoIGNhY2hlLWJ1c3QiLCBhc3luYyAoeyBwYWdlIH0pID0+IHsKICB0ZXN0LnNldFRpbWVvdXQoMzAwMDApOwogIGNvbnN0IGNiID0gRGF0ZS5ub3coKTsKICAKICAvLyBXVFAKICBhd2FpdCBwYWdlLmdvdG8oYC93ZXZhbC10ZWNobm9sb2d5LXBsYXRmb3JtLmh0bWw/Y2I9JHtjYn1gKTsKICBhd2FpdCBwYWdlLndhaXRGb3JMb2FkU3RhdGUoIm5ldHdvcmtpZGxlIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgyMDAwKTsKICBsZXQgaHViTGlua3MgPSBhd2FpdCBwYWdlLmxvY2F0b3IoJ2FbaHJlZio9ImRhc2hib2FyZHMtaHViLXVuaWZpZWQiXScpLmNvdW50KCk7CiAgY29uc29sZS5sb2coYFdUUCBodWIgbGlua3M6ICR7aHViTGlua3N9YCk7CiAgaWYgKGh1YkxpbmtzID4gMCkgewogICAgY29uc3QgZWwgPSBwYWdlLmxvY2F0b3IoJ2FbaHJlZio9ImRhc2hib2FyZHMtaHViLXVuaWZpZWQiXScpLmZpcnN0KCk7CiAgICBhd2FpdCBlbC5zY3JvbGxJbnRvVmlld0lmTmVlZGVkKCk7CiAgICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDgwMCk7CiAgICBhd2FpdCBwYWdlLnNjcmVlbnNob3QoeyBwYXRoOiAib3V0cHV0L3Y0MWItd3RwLWh1Yi5wbmciIH0pOwogICAgY29uc3QgdHh0ID0gYXdhaXQgZWwuaW5uZXJUZXh0KCk7CiAgICBjb25zb2xlLmxvZyhgV1RQIGxpbms6ICIke3R4dH0iYCk7CiAgfSBlbHNlIHsKICAgIGF3YWl0IHBhZ2Uuc2NyZWVuc2hvdCh7IHBhdGg6ICJvdXRwdXQvdjQxYi13dHAtbm9odWIucG5nIiB9KTsKICB9CiAgCiAgLy8gQ2xpY2sgdGhlIGh1YiBsaW5rIHRvIHZlcmlmeSBmbG93CiAgaWYgKGh1YkxpbmtzID4gMCkgewogICAgYXdhaXQgcGFnZS5sb2NhdG9yKCdhW2hyZWYqPSJkYXNoYm9hcmRzLWh1Yi11bmlmaWVkIl0nKS5maXJzdCgpLmNsaWNrKCk7CiAgICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDIwMDApOwogICAgYXdhaXQgcGFnZS5zY3JlZW5zaG90KHsgcGF0aDogIm91dHB1dC92NDFiLWh1Yi1hZnRlci1jbGljay5wbmciIH0pOwogIH0KfSk7Cg==");
|
||||
foreach (glob("$base/*.spec.js") as $old) @unlink($old);
|
||||
$written = @file_put_contents("$base/v41b.spec.js", $spec);
|
||||
echo json_encode(["written" => $written]);
|
||||
7
api/ambre-pw-v42-deploy.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$base = "/var/www/html/api/ambre-pw-tests/tests";
|
||||
$spec = base64_decode("Y29uc3QgeyB0ZXN0IH0gPSByZXF1aXJlKCJAcGxheXdyaWdodC90ZXN0Iik7Cgp0ZXN0KCJWNDIgwrcgRklOQUwgSHViIERhc2hib2FyZHMgU2hvd2Nhc2UgVWx0cmEiLCBhc3luYyAoeyBwYWdlIH0pID0+IHsKICB0ZXN0LnNldFRpbWVvdXQoNjAwMDApOwogIAogIC8vIDEuIEh1YiBob21lCiAgYXdhaXQgcGFnZS5nb3RvKCIvZGFzaGJvYXJkcy1odWItdW5pZmllZC5odG1sP2NiPSIgKyBEYXRlLm5vdygpKTsKICBhd2FpdCBwYWdlLndhaXRGb3JMb2FkU3RhdGUoIm5ldHdvcmtpZGxlIik7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCgxNTAwKTsKICBhd2FpdCBwYWdlLnNjcmVlbnNob3QoeyBwYXRoOiAib3V0cHV0L3Y0Mi0wMS1ob21lLnBuZyIgfSk7CiAgY29uc29sZS5sb2coIuKchSBUMTogSHViIGhvbWUgbG9hZGVkIik7CiAgCiAgLy8gU3RhdHMKICBjb25zdCBzdGF0cyA9IGF3YWl0IHBhZ2UuZXZhbHVhdGUoKCkgPT4gewogICAgY29uc3QgYnMgPSBBcnJheS5mcm9tKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJy5zdGF0IGInKSkubWFwKGIgPT4gYi5pbm5lclRleHQpOwogICAgY29uc3QgY2FyZHMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcuY2FyZCcpLmxlbmd0aDsKICAgIGNvbnN0IGZpbHRlcnMgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcuZmlsdGVyJykubGVuZ3RoOwogICAgcmV0dXJuIHsgc3RhdHM6IGJzLCBjYXJkcywgZmlsdGVycyB9OwogIH0pOwogIGNvbnNvbGUubG9nKGAgIFN0YXRzOiAke0pTT04uc3RyaW5naWZ5KHN0YXRzKX1gKTsKICAKICAvLyAyLiBGaWx0ZXIgYnkgS1BJICYgQW5hbHl0aWNzCiAgY29uc3QgZmlsdGVyS1BJID0gcGFnZS5sb2NhdG9yKCcuZmlsdGVyOmhhcy10ZXh0KCJLUEkgJiBBbmFseXRpY3MiKScpOwogIGlmIChhd2FpdCBmaWx0ZXJLUEkuY291bnQoKSA+IDApIHsKICAgIGF3YWl0IGZpbHRlcktQSS5jbGljaygpOwogICAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCg1MDApOwogICAgYXdhaXQgcGFnZS5zY3JlZW5zaG90KHsgcGF0aDogIm91dHB1dC92NDItMDItZmlsdGVyLWtwaS5wbmciIH0pOwogICAgY29uc29sZS5sb2coIuKchSBUMjogS1BJIGZpbHRlciBhcHBsaWVkIik7CiAgfQogIAogIC8vIDMuIEZpbHRlciBieSBFdGhpY2EKICBjb25zdCBmaWx0ZXJFdGggPSBwYWdlLmxvY2F0b3IoJy5maWx0ZXI6aGFzLXRleHQoIkV0aGljYSIpJyk7CiAgaWYgKGF3YWl0IGZpbHRlckV0aC5jb3VudCgpID4gMCkgewogICAgYXdhaXQgZmlsdGVyRXRoLmNsaWNrKCk7CiAgICBhd2FpdCBwYWdlLndhaXRGb3JUaW1lb3V0KDUwMCk7CiAgICBhd2FpdCBwYWdlLnNjcmVlbnNob3QoeyBwYXRoOiAib3V0cHV0L3Y0Mi0wMy1maWx0ZXItZXRoaWNhLnBuZyIgfSk7CiAgICBjb25zb2xlLmxvZygi4pyFIFQzOiBFdGhpY2EgZmlsdGVyIGFwcGxpZWQiKTsKICB9CiAgCiAgLy8gNC4gQmFjayB0byBhbGwKICBhd2FpdCBwYWdlLmxvY2F0b3IoJy5maWx0ZXI6aGFzLXRleHQoIlRvdXMiKScpLmNsaWNrKCk7CiAgYXdhaXQgcGFnZS53YWl0Rm9yVGltZW91dCg1MDApOwogIGF3YWl0IHBhZ2Uuc2NyZWVuc2hvdCh7IHBhdGg6ICJvdXRwdXQvdjQyLTA0LWFsbC1iYWNrLnBuZyIgfSk7CiAgCiAgLy8gNS4gRnVsbCBwYWdlCiAgYXdhaXQgcGFnZS5zY3JlZW5zaG90KHsgcGF0aDogIm91dHB1dC92NDItMDUtZnVsbHBhZ2UucG5nIiwgZnVsbFBhZ2U6IHRydWUgfSk7CiAgY29uc29sZS5sb2coIuKchSBUNDogRnVsbCBwYWdlIGNhcHR1cmVkIik7CiAgCiAgLy8gNi4gUmVnaXN0cnkgQVBJCiAgY29uc3QgcmVnID0gYXdhaXQgcGFnZS5ldmFsdWF0ZShhc3luYyAoKSA9PiB7CiAgICBjb25zdCByID0gYXdhaXQgZmV0Y2goJy9hcGkvZGFzaGJvYXJkcy1yZWdpc3RyeS1hbWJyZS5waHAnKTsKICAgIHJldHVybiBhd2FpdCByLmpzb24oKTsKICB9KTsKICBjb25zb2xlLmxvZyhgICBSZWdpc3RyeTogdG90YWw9JHtyZWcudG90YWx9IMK3IGNhdHM9JHtyZWcuY2F0ZWdvcmllc19jb3VudH0gwrcgemVyb19vcnBoYW49JHtyZWcuemVyb19vcnBoYW59YCk7Cn0pOwo=");
|
||||
foreach (glob("$base/*.spec.js") as $old) @unlink($old);
|
||||
$written = @file_put_contents("$base/v42-hub-showcase.spec.js", $spec);
|
||||
echo json_encode(["written" => $written]);
|
||||
69
api/ambre-scan-230.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
|
||||
chdir("/var/www/html");
|
||||
$out["recent_commits"] = array_filter(array_map("trim", explode("\n", @shell_exec("git log --since='10 minutes ago' --oneline 2>&1 | head -10"))));
|
||||
$out["current_tags"] = array_filter(array_map("trim", explode("\n", @shell_exec("git tag -l 'wave-*' --sort=-creatordate 2>&1 | head -5"))));
|
||||
|
||||
// Check WEVIA Master registry state
|
||||
$reg = @file_get_contents("/var/www/html/api/wevia-tool-registry.json");
|
||||
$reg_data = @json_decode($reg, true);
|
||||
$out["registry"] = [
|
||||
"exists" => $reg !== false,
|
||||
"size" => strlen($reg),
|
||||
"tool_count" => is_array($reg_data) ? count($reg_data) : 0,
|
||||
];
|
||||
// Check if mermaid + pdf-premium already in registry
|
||||
if (is_array($reg_data)) {
|
||||
$has_mermaid = false; $has_pdf_prem = false;
|
||||
foreach ($reg_data as $t) {
|
||||
$id = $t["id"] ?? "";
|
||||
if (stripos($id, "mermaid") !== false) $has_mermaid = true;
|
||||
if (stripos($id, "pdf_premium") !== false || stripos($id, "pdf-premium") !== false) $has_pdf_prem = true;
|
||||
}
|
||||
$out["registry"]["has_mermaid_tool"] = $has_mermaid;
|
||||
$out["registry"]["has_pdf_premium"] = $has_pdf_prem;
|
||||
}
|
||||
|
||||
// Check Ethica infrastructure
|
||||
$ethica = [];
|
||||
foreach (["consent.wevup.app", "ethica-pipeline", "ecm.py"] as $name) {
|
||||
$ethica[$name] = "check needed";
|
||||
}
|
||||
$ethica["consent_page"] = file_exists("/var/www/html/consent.html") || file_exists("/var/www/html/ethica.html");
|
||||
$out["ethica"] = $ethica;
|
||||
|
||||
// Check SSE streaming files
|
||||
$sse_files = [];
|
||||
foreach (["ambre-claude-stream.php", "ambre-claude-pattern-sse.php", "wevia-sse-override.js"] as $f) {
|
||||
$path = "/var/www/html/api/$f";
|
||||
$path2 = "/var/www/html/js/$f";
|
||||
if (file_exists($path)) $sse_files[$f] = filesize($path);
|
||||
elseif (file_exists($path2)) $sse_files[$f] = filesize($path2);
|
||||
}
|
||||
$out["sse_files"] = $sse_files;
|
||||
|
||||
// Language detection currently
|
||||
$w = @file_get_contents("/var/www/html/wevia.html");
|
||||
$out["lang_detection"] = [
|
||||
"detectLang_defined" => preg_match("/function detectLang/", $w),
|
||||
"darija_check" => strpos($w, "darija") !== false,
|
||||
"lang_var" => strpos($w, "var lang =") !== false,
|
||||
];
|
||||
|
||||
// Purge cache helper existence
|
||||
$out["cf_purge"] = file_exists("/var/www/html/api/ambre-cf-purge.php");
|
||||
|
||||
// Monitoring status
|
||||
$monitoring = [];
|
||||
foreach (["/opt/weval-ops/andon-monitor.sh", "/opt/weval-ops/phpfpm-watchdog.sh", "/opt/weval-ops/zombie-killer.sh"] as $s) {
|
||||
$monitoring[basename($s)] = file_exists($s);
|
||||
}
|
||||
$out["monitoring_scripts"] = $monitoring;
|
||||
|
||||
// Current cascade load
|
||||
$out["load"] = trim(@shell_exec("uptime"));
|
||||
$out["cascade_health"] = @file_get_contents("http://127.0.0.1:4000/health", false, stream_context_create(["http"=>["timeout"=>3]])) ? "UP" : "DOWN";
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
51
api/ambre-scan-230b.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
|
||||
chdir("/var/www/html");
|
||||
$out["recent_tags"] = array_filter(array_map("trim", explode("\n", @shell_exec("git tag -l 'wave-*' --sort=-creatordate 2>&1 | head -5"))));
|
||||
$out["latest_commit"] = trim(@shell_exec("git log -1 --oneline 2>&1"));
|
||||
|
||||
// Check my wave-229 tools + wave-230 state
|
||||
$reg = @file_get_contents("/var/www/html/api/wevia-tool-registry.json");
|
||||
$data = @json_decode($reg, true);
|
||||
if ($data) {
|
||||
$out["wave_229_tools"] = array_map(function($t){return $t["id"];},
|
||||
array_filter($data["tools"] ?? [], function($t){return ($t["wave"] ?? 0) == 229;}));
|
||||
$out["total_tools"] = count($data["tools"] ?? []);
|
||||
}
|
||||
|
||||
// Ethica state
|
||||
$ethica = [];
|
||||
$ethica["ecm_py_exists"] = file_exists("/var/www/html/ethica/ecm.py") || file_exists("/opt/ethica/ecm.py") || file_exists("/var/www/weval/ecm.py");
|
||||
$ethica["find_ecm"] = trim(@shell_exec("find /var/www /opt -name 'ecm.py' 2>/dev/null | head -5"));
|
||||
$ethica["consent_urls"] = [
|
||||
"consent.wevup.app" => @shell_exec("curl -sI --max-time 3 https://consent.wevup.app/ 2>&1 | head -1"),
|
||||
];
|
||||
$out["ethica"] = $ethica;
|
||||
|
||||
// Mermaid V10 state in wevia.html
|
||||
$w = @file_get_contents("/var/www/html/wevia.html");
|
||||
$out["wevia"] = [
|
||||
"size" => strlen($w),
|
||||
"v10_mermaid" => strpos($w, "AMBRE-V10-MERMAID") !== false,
|
||||
"v10_css_minheight" => strpos($w, "min-height:200px") !== false,
|
||||
];
|
||||
|
||||
// Check i18n helpers in wevia.html
|
||||
$out["i18n"] = [
|
||||
"detectLang" => strpos($w, "function detectLang") !== false,
|
||||
"lang_var" => strpos($w, "var lang =") !== false,
|
||||
];
|
||||
|
||||
// Mermaid KB state
|
||||
$mkb = @file_get_contents("/var/www/html/generated/mermaid-learn-kb.json");
|
||||
if ($mkb) {
|
||||
$kb_data = @json_decode($mkb, true);
|
||||
$out["mermaid_kb_entries"] = count($kb_data ?: []);
|
||||
}
|
||||
|
||||
// Load current
|
||||
$out["load"] = trim(shell_exec("uptime"));
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
53
api/ambre-scan-233.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
|
||||
chdir("/var/www/html");
|
||||
$out["recent_commits_60m"] = array_filter(array_map("trim", explode("\n", @shell_exec("git log --since='60 minutes ago' --oneline 2>&1 | head -15"))));
|
||||
$out["recent_tags"] = array_filter(array_map("trim", explode("\n", @shell_exec("git tag -l 'wave-*' --sort=-creatordate 2>&1 | head -10"))));
|
||||
|
||||
// Scan wevia.html for V10 state + pdf i18n confirmed
|
||||
$w = @file_get_contents("/var/www/html/wevia.html");
|
||||
$out["wevia"] = [
|
||||
"size" => strlen($w),
|
||||
"v10_mermaid" => strpos($w, "AMBRE-V10-MERMAID") !== false,
|
||||
"v10_sanitize_accents" => strpos($w, "replace(/[éèêë]/g") !== false,
|
||||
"mermaid_render_api" => strpos($w, "window.mermaid.render(") !== false,
|
||||
];
|
||||
|
||||
// PDF Premium state
|
||||
$pdf = @file_get_contents("/var/www/html/api/ambre-tool-pdf-premium.php");
|
||||
$out["pdf_premium"] = [
|
||||
"size" => strlen($pdf),
|
||||
"i18n_fr" => strpos($pdf, '"fr" =>') !== false,
|
||||
"i18n_en" => strpos($pdf, '"en" =>') !== false,
|
||||
"i18n_ar" => strpos($pdf, '"ar" =>') !== false,
|
||||
];
|
||||
|
||||
// Ethica state
|
||||
$out["ethica"] = [
|
||||
"ecm_py" => file_exists("/opt/weval-l99/ecm.py") ? filesize("/opt/weval-l99/ecm.py") : "missing",
|
||||
"consent_live" => trim(@shell_exec("curl -sI --max-time 3 https://consent.wevup.app/ 2>&1 | head -1")),
|
||||
];
|
||||
|
||||
// Registry 643 + wave-229 tools confirmed
|
||||
$reg = @json_decode(@file_get_contents("/var/www/html/api/wevia-tool-registry.json"), true);
|
||||
if ($reg) {
|
||||
$w229 = array_filter($reg["tools"] ?? [], function($t){return ($t["wave"] ?? 0) == 229;});
|
||||
$out["registry"] = [
|
||||
"total" => count($reg["tools"] ?? []),
|
||||
"wave_229_count" => count($w229),
|
||||
];
|
||||
}
|
||||
|
||||
// Monitoring status
|
||||
$out["monitoring"] = [
|
||||
"load" => trim(@shell_exec("uptime")),
|
||||
"cascade_up" => @file_get_contents("http://127.0.0.1:4000/health", false, stream_context_create(["http"=>["timeout"=>3]])) ? "UP" : "DOWN",
|
||||
];
|
||||
|
||||
// Mermaid KB
|
||||
$mkb = @json_decode(@file_get_contents("/var/www/html/generated/mermaid-learn-kb.json"), true);
|
||||
$out["mermaid_kb_total"] = is_array($mkb) ? count($mkb) : 0;
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
56
api/ambre-scan-v30.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
|
||||
// Recent git activity
|
||||
chdir("/var/www/html");
|
||||
$out["git_commits_last_30m"] = array_filter(array_map("trim", explode("\n", @shell_exec("git log --since='30 minutes ago' --oneline 2>&1 | head -20"))));
|
||||
$out["git_tags_today"] = array_filter(array_map("trim", explode("\n", @shell_exec("git tag -l | while read t; do d=$(git log -1 --format=%at \"$t\" 2>/dev/null); if [ -n \"$d\" ] && [ \"$d\" -gt $(($(date +%s)-86400)) ]; then echo \"$t\"; fi; done 2>&1 | head -20"))));
|
||||
|
||||
// Recent ambre-* files
|
||||
$recent_ambre = array_map("basename", array_filter(glob("/var/www/html/api/ambre-*.php"), function($f){ return filemtime($f) > (time()-3600); }));
|
||||
$out["ambre_files_last_hour"] = $recent_ambre;
|
||||
|
||||
// oss-catalog state
|
||||
$oss = "/var/www/html/oss-catalog.html";
|
||||
$out["oss_catalog"] = file_exists($oss) ? [
|
||||
"size" => filesize($oss),
|
||||
"mtime" => date("Y-m-d H:i", filemtime($oss)),
|
||||
"tool_count_preg" => preg_match_all("/data-cat=/", @file_get_contents($oss) ?: ""),
|
||||
] : "NOT FOUND";
|
||||
|
||||
// Wiki/vault doctrines
|
||||
$out["doctrines"] = array_map("basename", glob("/opt/obsidian-vault/doctrines/*.md") ?: []);
|
||||
$out["doctrines_count"] = count(glob("/opt/obsidian-vault/doctrines/*.md") ?: []);
|
||||
|
||||
// Recent wave-* tags
|
||||
$out["recent_wave_tags"] = array_filter(array_map("trim", explode("\n", @shell_exec("git tag -l 'wave-*' --sort=-creatordate 2>&1 | head -10"))));
|
||||
|
||||
// V30 video + screenshots still live
|
||||
$out["v30_artifacts"] = [
|
||||
"video" => glob("/var/www/html/generated/wevia-v30-showcase*.webm"),
|
||||
"screenshots" => count(glob("/var/www/html/generated/v30-*.png") ?: []),
|
||||
];
|
||||
|
||||
// Mermaid KB
|
||||
$mkb = "/var/www/html/generated/mermaid-learn-kb.json";
|
||||
$out["mermaid_kb"] = file_exists($mkb) ? [
|
||||
"size" => filesize($mkb),
|
||||
"entries" => count(json_decode(@file_get_contents($mkb), true) ?: []),
|
||||
] : "NOT FOUND";
|
||||
|
||||
// PDF Premium endpoint
|
||||
$out["pdf_premium"] = file_exists("/var/www/html/api/ambre-tool-pdf-premium.php") ? "LIVE" : "MISSING";
|
||||
|
||||
// What's in wevia.html now
|
||||
$w = @file_get_contents("/var/www/html/wevia.html");
|
||||
$out["wevia_state"] = [
|
||||
"size" => strlen($w),
|
||||
"v5_memory" => strpos($w, "AMBRE-V5-MEMORY") !== false,
|
||||
"v6_tools" => strpos($w, "AMBRE-V6-TOOLS") !== false,
|
||||
"v7_premium" => strpos($w, "AMBRE-V7-PREMIUM") !== false,
|
||||
"v9_pdf_premium" => strpos($w, "AMBRE-V9-PDF-PREMIUM") !== false,
|
||||
"ambre_gen_pat_hoisted" => strpos($w, "HOISTED") !== false,
|
||||
];
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
68
api/ambre-scan-wtp.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$out = [];
|
||||
|
||||
chdir("/var/www/html");
|
||||
$out["recent_commits_30m"] = array_filter(array_map("trim", explode("\n", @shell_exec("git log --since='30 minutes ago' --oneline 2>&1 | head -15"))));
|
||||
$out["recent_tags_today"] = array_filter(array_map("trim", explode("\n", @shell_exec("git tag -l 'wave-*' --sort=-creatordate 2>&1 | head -10"))));
|
||||
|
||||
// WTP state (point entrée architecture)
|
||||
$wtp = "/var/www/html/weval-technology-platform.html";
|
||||
if (file_exists($wtp)) {
|
||||
$w = @file_get_contents($wtp);
|
||||
$out["wtp"] = [
|
||||
"size" => filesize($wtp),
|
||||
"mtime" => date("Y-m-d H:i", filemtime($wtp)),
|
||||
"banner_links" => preg_match_all("/href=[\"'][^\"']+\.html/", $w),
|
||||
"has_banner" => strpos($w, "banner") !== false,
|
||||
];
|
||||
}
|
||||
|
||||
// Sitemap API for orphans tracking
|
||||
$sitemap_api = @file_get_contents("https://weval-consulting.com/api/sitemap-api.php", false, stream_context_create(["http"=>["timeout"=>5]]));
|
||||
if ($sitemap_api) {
|
||||
$d = @json_decode($sitemap_api, true);
|
||||
$out["sitemap"] = [
|
||||
"total_pages" => is_array($d) ? count($d["pages"] ?? $d) : 0,
|
||||
"raw_size" => strlen($sitemap_api),
|
||||
];
|
||||
}
|
||||
|
||||
// All-IA Hub state
|
||||
$iahub = "/var/www/html/all-ia-hub.html";
|
||||
if (file_exists($iahub)) {
|
||||
$out["all_ia_hub"] = ["size" => filesize($iahub), "mtime" => date("Y-m-d H:i", filemtime($iahub))];
|
||||
}
|
||||
|
||||
// WEVIA Master state
|
||||
$master = "/var/www/html/wevia-master.html";
|
||||
if (file_exists($master)) {
|
||||
$out["wevia_master"] = ["size" => filesize($master), "mtime" => date("Y-m-d H:i", filemtime($master))];
|
||||
}
|
||||
|
||||
// Orchestrator
|
||||
$orch = "/var/www/html/wevia-orchestrator.html";
|
||||
if (file_exists($orch)) {
|
||||
$out["orchestrator"] = ["size" => filesize($orch), "mtime" => date("Y-m-d H:i", filemtime($orch))];
|
||||
}
|
||||
|
||||
// Dashboards available
|
||||
$dashboards = array_map("basename", glob("/var/www/html/*dashboard*.html") ?: []);
|
||||
$out["dashboards_count"] = count($dashboards);
|
||||
$out["dashboards_sample"] = array_slice($dashboards, 0, 15);
|
||||
|
||||
// Business KPI endpoint check
|
||||
$biz_kpi = @file_get_contents("https://weval-consulting.com/api/v83-business-kpi-latest.json", false, stream_context_create(["http"=>["timeout"=>5]]));
|
||||
if ($biz_kpi) {
|
||||
$d = @json_decode($biz_kpi, true);
|
||||
$out["biz_kpi"] = [
|
||||
"keys" => is_array($d) ? array_slice(array_keys($d), 0, 10) : "invalid",
|
||||
"size" => strlen($biz_kpi),
|
||||
];
|
||||
}
|
||||
|
||||
// Current load + recent PW runs
|
||||
$out["load"] = trim(@shell_exec("uptime"));
|
||||
$out["recent_pw_runs"] = trim(@shell_exec("ls -1t /tmp/ambre-pw-run-*.log 2>/dev/null | head -3"));
|
||||
|
||||
echo json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
141
api/ambre-tool-mermaid.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/**
|
||||
* ambre-tool-mermaid.php · Mermaid generation with learning KB (RAG-enabled)
|
||||
* Flow:
|
||||
* 1. Search KB for similar schema (score > 5)
|
||||
* 2. If found: reuse + mark used
|
||||
* 3. Else: LLM generates + auto-save to KB
|
||||
*/
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
set_time_limit(60);
|
||||
|
||||
require_once __DIR__ . "/ambre-llm-semaphore.php";
|
||||
|
||||
$raw = file_get_contents("php://input");
|
||||
$in = json_decode($raw, true) ?: $_POST;
|
||||
$topic = trim($in["topic"] ?? $in["message"] ?? "");
|
||||
if (!$topic) { echo json_encode(["error"=>"topic required"]); exit; }
|
||||
|
||||
$t0 = microtime(true);
|
||||
|
||||
// Step 1: Search KB
|
||||
$kb_resp = @file_get_contents("http://127.0.0.1/api/ambre-mermaid-learn.php", false, stream_context_create([
|
||||
"http" => [
|
||||
"method" => "POST",
|
||||
"header" => "Content-Type: application/json\r\n",
|
||||
"content" => json_encode(["action"=>"search", "query"=>$topic]),
|
||||
"timeout" => 5,
|
||||
],
|
||||
]));
|
||||
$kb_hits = @json_decode($kb_resp, true) ?: [];
|
||||
|
||||
$reused = null;
|
||||
if (!empty($kb_hits) && ($kb_hits[0]["score"] ?? 0) >= 5) {
|
||||
$reused = $kb_hits[0];
|
||||
// Mark used
|
||||
@file_get_contents("http://127.0.0.1/api/ambre-mermaid-learn.php", false, stream_context_create([
|
||||
"http" => ["method"=>"POST","header"=>"Content-Type: application/json\r\n",
|
||||
"content"=>json_encode(["action"=>"use","id"=>$reused["id"]]),"timeout"=>3]
|
||||
]));
|
||||
}
|
||||
|
||||
if ($reused) {
|
||||
echo json_encode([
|
||||
"ok" => true,
|
||||
"mermaid_code" => $reused["code"],
|
||||
"topic" => $reused["topic"],
|
||||
"kind" => $reused["kind"] ?? "flowchart",
|
||||
"source" => "kb_reused",
|
||||
"kb_id" => $reused["id"],
|
||||
"kb_score" => $reused["score"],
|
||||
"use_count" => $reused["use_count"] ?? 0,
|
||||
"elapsed_ms" => round((microtime(true)-$t0)*1000),
|
||||
"provider" => "WEVIA Mermaid Learning KB",
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Step 2: No match → LLM generate
|
||||
$sys = "Tu es un expert en diagrammes Mermaid. Pour le sujet donné, génère UNIQUEMENT le code Mermaid valide (sans markdown wrapper ```).\n" .
|
||||
"Règles strictes :\n" .
|
||||
"- Utiliser UNIQUEMENT des crochets [texte] pour les noeuds, pas de {accolades} ni ((parenthèses))\n" .
|
||||
"- Pas d'accents (é→e, à→a, etc.)\n" .
|
||||
"- Pas d'emojis\n" .
|
||||
"- Max 12 noeuds\n" .
|
||||
"- Syntaxe : flowchart LR, flowchart TD, sequenceDiagram, gantt, pie, mindmap selon le besoin\n" .
|
||||
"- Labels courts (< 30 chars)\n" .
|
||||
"- Arrows : --> ou --|label|-->\n" .
|
||||
"Réponds STRICTEMENT avec le code Mermaid, rien d'autre.";
|
||||
|
||||
$sem_id = AmbreLLMSemaphore::acquire();
|
||||
if (!$sem_id) {
|
||||
echo json_encode(["error"=>"service busy"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$llm_t = microtime(true);
|
||||
$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"=>[["role"=>"system","content"=>$sys],["role"=>"user","content"=>$topic]],
|
||||
"max_tokens"=>800,
|
||||
"temperature"=>0.3,
|
||||
]),
|
||||
"timeout"=>25,
|
||||
],
|
||||
]));
|
||||
$llm_ms = round((microtime(true)-$llm_t)*1000);
|
||||
} finally {
|
||||
AmbreLLMSemaphore::release($sem_id);
|
||||
}
|
||||
|
||||
$d = @json_decode($llm, true);
|
||||
$code = $d["choices"][0]["message"]["content"] ?? "";
|
||||
|
||||
// Sanitize
|
||||
$code = preg_replace('/^```(?:mermaid)?\s*/m', '', $code);
|
||||
$code = preg_replace('/\s*```\s*$/m', '', $code);
|
||||
$code = trim($code);
|
||||
|
||||
if (!$code) {
|
||||
echo json_encode(["error"=>"LLM returned empty code", "llm_ms"=>$llm_ms]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Detect kind
|
||||
$kind = "flowchart";
|
||||
if (stripos($code, "sequenceDiagram") !== false) $kind = "sequence";
|
||||
elseif (stripos($code, "gantt") === 0) $kind = "gantt";
|
||||
elseif (stripos($code, "pie") === 0) $kind = "pie";
|
||||
elseif (stripos($code, "mindmap") !== false) $kind = "mindmap";
|
||||
elseif (stripos($code, "classDiagram") !== false) $kind = "class";
|
||||
elseif (stripos($code, "erDiagram") !== false) $kind = "er";
|
||||
|
||||
// Step 3: Save to KB
|
||||
$save_resp = @file_get_contents("http://127.0.0.1/api/ambre-mermaid-learn.php", false, stream_context_create([
|
||||
"http" => ["method"=>"POST","header"=>"Content-Type: application/json\r\n",
|
||||
"content"=>json_encode([
|
||||
"action"=>"save", "topic"=>$topic, "kind"=>$kind,
|
||||
"context"=>"Auto-generated from user query",
|
||||
"code"=>$code,
|
||||
]),
|
||||
"timeout"=>5]
|
||||
]));
|
||||
$saved = @json_decode($save_resp, true);
|
||||
|
||||
echo json_encode([
|
||||
"ok" => true,
|
||||
"mermaid_code" => $code,
|
||||
"topic" => $topic,
|
||||
"kind" => $kind,
|
||||
"source" => "llm_generated_saved",
|
||||
"kb_id" => $saved["id"] ?? null,
|
||||
"kb_total" => $saved["total"] ?? null,
|
||||
"llm_ms" => $llm_ms,
|
||||
"elapsed_ms" => round((microtime(true)-$t0)*1000),
|
||||
"provider" => "WEVIA Mermaid + KB Learning",
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
@@ -21,7 +21,28 @@ if (!$topic) { echo json_encode(["error"=>"topic required"]); exit; }
|
||||
$t0 = microtime(true);
|
||||
|
||||
// Step 1: Get structured content from LLM
|
||||
$sys = "Tu es un expert en création de rapports business premium. Pour le sujet donné, génère UNIQUEMENT un JSON valide avec cette structure exacte (pas de markdown, pas d'explication) :
|
||||
// i18n language detection (simple heuristic)
|
||||
$topic_lower = mb_strtolower($topic);
|
||||
$lang = $in["lang"] ?? null;
|
||||
if (!$lang) {
|
||||
// Detect from content
|
||||
if (preg_match("/\b(the|is|are|and|of|for|to|with|on|in|a)\b/i", $topic_lower) && !preg_match("/\b(le|la|les|du|des|pour|avec)\b/i", $topic_lower)) {
|
||||
$lang = "en";
|
||||
} elseif (preg_match("/[\x{0600}-\x{06FF}]/u", $topic)) {
|
||||
$lang = "ar";
|
||||
} else {
|
||||
$lang = "fr";
|
||||
}
|
||||
}
|
||||
|
||||
// Prompts by language
|
||||
$prompts = [
|
||||
"fr" => "Tu es un expert en création de rapports business premium. Pour le sujet donné, génère UNIQUEMENT un JSON valide avec cette structure exacte (pas de markdown, pas d'explication) :",
|
||||
"en" => "You are an expert in premium business report creation. For the given topic, generate ONLY valid JSON with this exact structure (no markdown, no explanation). All text in English :",
|
||||
"ar" => "أنت خبير في إنشاء تقارير الأعمال المتميزة. للموضوع المحدد، قم بإنشاء JSON صالح فقط بهذه البنية الدقيقة (بدون markdown، بدون شرح). جميع النصوص باللغة العربية :",
|
||||
];
|
||||
$sys = $prompts[$lang] ?? $prompts["fr"];
|
||||
$sys .= "
|
||||
{
|
||||
\"title\": \"Titre court et percutant\",
|
||||
\"subtitle\": \"Sous-titre éclairant le contexte\",
|
||||
@@ -288,5 +309,6 @@ $result = [
|
||||
"render_ms" => $render_ms,
|
||||
"total_ms" => round((microtime(true)-$t0)*1000),
|
||||
"provider" => "WEVIA PDF Premium Engine",
|
||||
"lang" => $lang,
|
||||
];
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
28
api/ambre-v10-css.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/wevia.html";
|
||||
$c = @file_get_contents($path);
|
||||
|
||||
// In the inline mermaid div, ensure min-height and visible styling
|
||||
$old = '<div class="mermaid" id="" + uniqId + "" style="text-align:center">" + mcode + "</div>';
|
||||
// Actually the style is inside the string. Let me use pattern
|
||||
$old_esc = '<div class=\\"mermaid\\" id=\\"" + uniqId + "\\" style=\\"text-align:center\\">" + mcode + "</div>';
|
||||
$new_esc = '<div class=\\"mermaid\\" id=\\"" + uniqId + "\\" style=\\"text-align:center;min-height:200px;font-size:14px;color:#333;background:#fff;padding:12px\\">" + mcode + "</div>';
|
||||
|
||||
if (strpos($c, $old_esc) === false) {
|
||||
// Simpler check
|
||||
if (strpos($c, 'class=\\"mermaid\\" id=\\"" + uniqId + "\\" style=\\"text-align:center\\"') !== false) {
|
||||
$c = str_replace('class=\\"mermaid\\" id=\\"" + uniqId + "\\" style=\\"text-align:center\\"', 'class=\\"mermaid\\" id=\\"" + uniqId + "\\" style=\\"text-align:center;min-height:200px;font-size:14px;color:#333\\"', $c);
|
||||
echo json_encode(["patch"=>"style expanded", "size"=>strlen($c)]);
|
||||
} else {
|
||||
echo json_encode(["error"=>"pattern not found for style fix"]);
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
$c = str_replace($old_esc, $new_esc, $c);
|
||||
}
|
||||
|
||||
$backup = "/opt/wevads/vault/wevia.html.GOLD-" . date("Ymd-His") . "-mermaid-css";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $c);
|
||||
echo json_encode(["wrote"=>$wrote, "backup"=>basename($backup)]);
|
||||
27
api/ambre-v10-fix.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/wevia.html";
|
||||
$c = @file_get_contents($path);
|
||||
$orig = strlen($c);
|
||||
|
||||
// Replace the V10 addMsg call with direct innerHTML injection
|
||||
$old = 'addMsg("assistant", badges + inlineBlock, (data.elapsed_ms/1000).toFixed(2));';
|
||||
$new = '// Direct innerHTML injection (bypass formatMd HTML escape)
|
||||
var _el = addMsg("assistant", "Diagramme Mermaid", (data.elapsed_ms/1000).toFixed(2));
|
||||
var _bubble = _el ? _el.querySelector(".bubble") : null;
|
||||
if (_bubble) _bubble.innerHTML = badges + inlineBlock;';
|
||||
|
||||
if (strpos($c, $old) === false) {
|
||||
echo json_encode(["error"=>"pattern not found in V10"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$new_c = str_replace($old, $new, $c);
|
||||
$backup = "/opt/wevads/vault/wevia.html.GOLD-" . date("Ymd-His") . "-v10-fix";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $new_c);
|
||||
|
||||
echo json_encode([
|
||||
"delta" => strlen($new_c) - $orig,
|
||||
"wrote" => $wrote,
|
||||
]);
|
||||
78
api/ambre-v10-render.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/wevia.html";
|
||||
$c = @file_get_contents($path);
|
||||
$orig = strlen($c);
|
||||
|
||||
// The V10 block uses class="mermaid" which triggers the problematic CSS
|
||||
// Keep class="mermaid" (so mermaid.run picks it up) BUT add an override style in-line
|
||||
// The issue is font-size:0 !important - we can't override easily
|
||||
// Better: after mermaid.run, the data-processed=true attribute is set, which UNSETS font-size:0
|
||||
// So the issue must be that mermaid.run() isn't triggering or the SVG has issues
|
||||
|
||||
// Let me use a different approach: use mermaid.render() directly to get SVG as string, insert directly
|
||||
|
||||
$old = "var uniqId = \"mmd-\" + Date.now();
|
||||
var inlineBlock = \"<div style=\\\"margin:12px 0;padding:14px;background:#fafafa;border:1px solid #e5e7eb;border-radius:12px\\\">\" +
|
||||
\"<div style=\\\"font-weight:600;font-size:13px;color:#6b7280;margin-bottom:10px\\\">📊 \" + topic + \"</div>\" +
|
||||
\"<div class=\\\"mermaid\\\" id=\\\"\" + uniqId + \"\\\" style=\\\"text-align:center;min-height:200px;font-size:14px;color:#333\\\">\" + mcode + \"</div>\"";
|
||||
|
||||
$new = "var uniqId = \"mmd-\" + Date.now();
|
||||
// Pre-render SVG via mermaid.render() to avoid CSS font-size:0 !important issue
|
||||
var inlineBlock = \"<div style=\\\"margin:12px 0;padding:14px;background:#fafafa;border:1px solid #e5e7eb;border-radius:12px\\\">\" +
|
||||
\"<div style=\\\"font-weight:600;font-size:13px;color:#6b7280;margin-bottom:10px\\\">📊 \" + topic + \"</div>\" +
|
||||
\"<div id=\\\"\" + uniqId + \"\\\" style=\\\"text-align:center;min-height:150px;padding:10px;background:#fff;border-radius:8px\\\">Rendu en cours...</div>\"";
|
||||
|
||||
if (strpos($c, $old) === false) {
|
||||
echo json_encode(["error"=>"V10 pattern not found for CSS fix"]);
|
||||
exit;
|
||||
}
|
||||
$c = str_replace($old, $new, $c);
|
||||
|
||||
// And update the render call to use mermaid.render(id, code) API
|
||||
$old_render = "setTimeout(function(){
|
||||
try {
|
||||
if (window.mermaid && typeof window.mermaid.run === \"function\") {
|
||||
window.mermaid.run({ nodes: [document.getElementById(uniqId)] });
|
||||
}
|
||||
} catch(e) { console.warn(\"mermaid render fail\", e); }
|
||||
}, 300);";
|
||||
|
||||
$new_render = "setTimeout(function(){
|
||||
try {
|
||||
var target = document.getElementById(uniqId);
|
||||
if (!target) return;
|
||||
if (window.mermaid && typeof window.mermaid.render === \"function\") {
|
||||
// Use render() to get SVG string directly
|
||||
window.mermaid.render(\"svg-\" + uniqId, mcode).then(function(result) {
|
||||
if (result && result.svg) {
|
||||
target.innerHTML = result.svg;
|
||||
target.style.minHeight = \"auto\";
|
||||
}
|
||||
}).catch(function(err){
|
||||
console.warn(\"mermaid.render error\", err);
|
||||
target.innerHTML = \"<pre style=\\\"font-size:11px;color:#b00;padding:10px;background:#fee;border-radius:6px\\\">Erreur rendu: \" + String(err).substring(0, 200) + \"</pre>\";
|
||||
});
|
||||
} else if (window.mermaid && typeof window.mermaid.init === \"function\") {
|
||||
target.className = \"mermaid\";
|
||||
target.textContent = mcode;
|
||||
window.mermaid.init(undefined, target);
|
||||
}
|
||||
} catch(e) { console.warn(\"mermaid render fail\", e); }
|
||||
}, 500);";
|
||||
|
||||
if (strpos($c, $old_render) === false) {
|
||||
echo json_encode(["error"=>"render pattern not found", "orig_changed" => strlen($c) != $orig]);
|
||||
exit;
|
||||
}
|
||||
$c = str_replace($old_render, $new_render, $c);
|
||||
|
||||
$backup = "/opt/wevads/vault/wevia.html.GOLD-" . date("Ymd-His") . "-v10-render";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $c);
|
||||
|
||||
echo json_encode([
|
||||
"delta" => strlen($c) - $orig,
|
||||
"wrote" => $wrote,
|
||||
"backup" => basename($backup),
|
||||
]);
|
||||
64
api/ambre-v10-san.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/wevia.html";
|
||||
$c = @file_get_contents($path);
|
||||
$orig = strlen($c);
|
||||
|
||||
// Add sanitize step in the V10 render setTimeout
|
||||
$old = 'setTimeout(function(){
|
||||
try {
|
||||
if (window.mermaid && typeof window.mermaid.run === "function") {
|
||||
window.mermaid.run({ nodes: [document.getElementById(uniqId)] });
|
||||
}';
|
||||
|
||||
$new = 'setTimeout(function(){
|
||||
try {
|
||||
var target = document.getElementById(uniqId);
|
||||
if (!target) return;
|
||||
// Sanitize mermaid code (strip accents, fix common LLM mistakes)
|
||||
var clean = mcode
|
||||
.replace(/[àâä]/g, "a").replace(/[éèêë]/g, "e").replace(/[îï]/g, "i")
|
||||
.replace(/[ôö]/g, "o").replace(/[ùûü]/g, "u").replace(/ç/g, "c")
|
||||
.replace(/[ÀÂÄ]/g, "A").replace(/[ÉÈÊË]/g, "E").replace(/[ÎÏ]/g, "I")
|
||||
.replace(/[ÔÖ]/g, "O").replace(/[ÙÛÜ]/g, "U").replace(/Ç/g, "C")
|
||||
.trim();
|
||||
// Use mermaid.render for SVG direct return (bypass font-size:0 CSS issue)
|
||||
if (window.mermaid && typeof window.mermaid.render === "function") {
|
||||
window.mermaid.render("svg-" + uniqId, clean).then(function(result){
|
||||
if (result && result.svg) {
|
||||
target.innerHTML = result.svg;
|
||||
target.className = "mermaid-rendered";
|
||||
target.removeAttribute("data-processed");
|
||||
// Force SVG to reasonable size
|
||||
var svg = target.querySelector("svg");
|
||||
if (svg) {
|
||||
svg.style.maxWidth = "100%";
|
||||
svg.style.height = "auto";
|
||||
svg.style.minHeight = "180px";
|
||||
svg.removeAttribute("width");
|
||||
svg.style.width = "100%";
|
||||
}
|
||||
}
|
||||
}).catch(function(err){
|
||||
console.warn("mermaid render err", err);
|
||||
target.innerHTML = "<pre style=\"font-size:12px;padding:10px;background:#f5f5f5;border-radius:6px\">" + clean.replace(/</g,"<") + "</pre>";
|
||||
});
|
||||
} else if (window.mermaid && typeof window.mermaid.run === "function") {
|
||||
target.className = "mermaid";
|
||||
target.textContent = clean;
|
||||
window.mermaid.run({ nodes: [target] });
|
||||
}';
|
||||
|
||||
if (strpos($c, $old) === false) {
|
||||
echo json_encode(["error"=>"pattern not found"]);
|
||||
exit;
|
||||
}
|
||||
$c = str_replace($old, $new, $c);
|
||||
|
||||
$backup = "/opt/wevads/vault/wevia.html.GOLD-" . date("Ymd-His") . "-v10-sanitize";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $c);
|
||||
echo json_encode([
|
||||
"delta" => strlen($c) - $orig,
|
||||
"wrote" => $wrote,
|
||||
]);
|
||||
43
api/ambre-wire-reg2.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/api/wevia-tool-registry.json";
|
||||
|
||||
// Unlock
|
||||
@shell_exec("chattr -i $path 2>&1");
|
||||
|
||||
$content = @file_get_contents($path);
|
||||
$data = @json_decode($content, true);
|
||||
|
||||
$existing_ids = array_map(function($t){return $t["id"]??"";}, $data["tools"]);
|
||||
$new_tools = [
|
||||
["id"=>"pdf_premium_generator","kw"=>"pdf.*premium|rapport.*premium|pdf.*qualit|pdf.*graphique|pdf.*chart|premium.*pdf","cmd"=>"curl -sS -X POST http://127.0.0.1/api/ambre-tool-pdf-premium.php -H 'Content-Type: application/json' -d '{\"topic\":\"${TOPIC}\"}'","exec"=>true,"desc"=>"PDF Premium · Chart.js + google-chrome · 6 chart types","wave"=>229],
|
||||
["id"=>"mermaid_generator_kb","kw"=>"mermaid|diagramme|flowchart|sequence.*diagram|gantt|schema.*mermaid|schema.*process","cmd"=>"curl -sS -X POST http://127.0.0.1/api/ambre-tool-mermaid.php -H 'Content-Type: application/json' -d '{\"topic\":\"${TOPIC}\"}'","exec"=>true,"desc"=>"Mermaid + Learning KB · RAG reuse + auto-save","wave"=>229],
|
||||
["id"=>"mermaid_kb_search","kw"=>"mermaid.*search|recherche.*diagramme|find.*schema|catalog.*mermaid","cmd"=>"curl -sS -X POST http://127.0.0.1/api/ambre-mermaid-learn.php -H 'Content-Type: application/json' -d '{\"action\":\"search\",\"query\":\"${TOPIC}\"}'","exec"=>true,"desc"=>"Mermaid KB search","wave"=>229],
|
||||
["id"=>"mermaid_kb_stats","kw"=>"mermaid.*stats|mermaid.*catalog.*count|kb.*stats","cmd"=>"curl -sS -X POST http://127.0.0.1/api/ambre-mermaid-learn.php -H 'Content-Type: application/json' -d '{\"action\":\"stats\"}'","exec"=>true,"desc"=>"Mermaid KB stats","wave"=>229],
|
||||
["id"=>"llm_semaphore_stats","kw"=>"semaphore.*stat|llm.*load|llm.*semaphore|concurrent.*llm","cmd"=>"curl -sS http://127.0.0.1/api/ambre-llm-semaphore.php","exec"=>true,"desc"=>"LLM semaphore stats","wave"=>229],
|
||||
];
|
||||
|
||||
$added = 0;
|
||||
foreach ($new_tools as $nt) {
|
||||
if (!in_array($nt["id"], $existing_ids)) {
|
||||
$data["tools"][] = $nt;
|
||||
$added++;
|
||||
}
|
||||
}
|
||||
$data["opus_wave_229"] = ["ts"=>date("c"),"added"=>$added,"new_total"=>count($data["tools"])];
|
||||
|
||||
$json_out = json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE);
|
||||
$backup = "/opt/wevads/vault/wevia-tool-registry.GOLD-" . date("Ymd-His") . "-wave229";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $json_out);
|
||||
|
||||
// Relock
|
||||
@shell_exec("chattr +i $path 2>&1");
|
||||
|
||||
echo json_encode([
|
||||
"added" => $added,
|
||||
"new_total" => count($data["tools"]),
|
||||
"wrote" => $wrote,
|
||||
"backup" => basename($backup),
|
||||
"is_locked_after" => trim(shell_exec("lsattr $path 2>&1 | awk '{print \$1}'")),
|
||||
]);
|
||||
89
api/ambre-wire-registry.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/api/wevia-tool-registry.json";
|
||||
$content = @file_get_contents($path);
|
||||
$data = @json_decode($content, true);
|
||||
|
||||
if (!is_array($data) || !isset($data["tools"])) {
|
||||
echo json_encode(["error"=>"invalid registry"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$orig_count = count($data["tools"]);
|
||||
|
||||
// Check existing ids to avoid duplicates
|
||||
$existing_ids = [];
|
||||
foreach ($data["tools"] as $t) $existing_ids[] = $t["id"] ?? "";
|
||||
|
||||
// Tools to add (wave-229 deliverables)
|
||||
$new_tools = [
|
||||
[
|
||||
"id" => "pdf_premium_generator",
|
||||
"kw" => "pdf.*premium|rapport.*premium|pdf.*qualit|pdf.*graphique|pdf.*chart|premium.*pdf|rapport.*graph",
|
||||
"cmd" => "curl -sS -X POST http://127.0.0.1/api/ambre-tool-pdf-premium.php -H 'Content-Type: application/json' -d '{\"topic\":\"${TOPIC}\"}' | jq -r '.url'",
|
||||
"exec" => true,
|
||||
"desc" => "WEVIA PDF Premium · Chart.js + google-chrome + LLM JSON structure · 6 chart types",
|
||||
"wave" => 229,
|
||||
],
|
||||
[
|
||||
"id" => "mermaid_generator_kb",
|
||||
"kw" => "mermaid|diagramme|flowchart|sequence.*diagram|gantt|schema.*mermaid|schema.*process",
|
||||
"cmd" => "curl -sS -X POST http://127.0.0.1/api/ambre-tool-mermaid.php -H 'Content-Type: application/json' -d '{\"topic\":\"${TOPIC}\"}' | jq -r '.mermaid_code'",
|
||||
"exec" => true,
|
||||
"desc" => "WEVIA Mermaid + Learning KB · RAG reuse si match · LLM generate + auto-save sinon",
|
||||
"wave" => 229,
|
||||
],
|
||||
[
|
||||
"id" => "mermaid_kb_search",
|
||||
"kw" => "mermaid.*search|recherche.*diagramme|find.*schema|catalog.*mermaid|kb.*mermaid",
|
||||
"cmd" => "curl -sS -X POST http://127.0.0.1/api/ambre-mermaid-learn.php -H 'Content-Type: application/json' -d '{\"action\":\"search\",\"query\":\"${TOPIC}\"}' | jq '.'",
|
||||
"exec" => true,
|
||||
"desc" => "WEVIA Mermaid KB search · retrieve existing diagrams by topic",
|
||||
"wave" => 229,
|
||||
],
|
||||
[
|
||||
"id" => "mermaid_kb_stats",
|
||||
"kw" => "mermaid.*stats|mermaid.*catalog.*count|kb.*stats|mermaid.*total",
|
||||
"cmd" => "curl -sS -X POST http://127.0.0.1/api/ambre-mermaid-learn.php -H 'Content-Type: application/json' -d '{\"action\":\"stats\"}' | jq '.'",
|
||||
"exec" => true,
|
||||
"desc" => "WEVIA Mermaid KB stats · total diagrams + by kind + total uses",
|
||||
"wave" => 229,
|
||||
],
|
||||
[
|
||||
"id" => "llm_semaphore_stats",
|
||||
"kw" => "semaphore.*stat|llm.*load|llm.*semaphore|concurrent.*llm|cascade.*load",
|
||||
"cmd" => "curl -sS http://127.0.0.1/api/ambre-llm-semaphore.php | jq '.'",
|
||||
"exec" => true,
|
||||
"desc" => "WEVIA LLM semaphore stats · active locks / max concurrent",
|
||||
"wave" => 229,
|
||||
],
|
||||
];
|
||||
|
||||
$added = 0;
|
||||
foreach ($new_tools as $nt) {
|
||||
if (!in_array($nt["id"], $existing_ids)) {
|
||||
$data["tools"][] = $nt;
|
||||
$added++;
|
||||
}
|
||||
}
|
||||
|
||||
// Update meta
|
||||
$data["opus_wave_229"] = [
|
||||
"ts" => date("c"),
|
||||
"added" => $added,
|
||||
"new_total" => count($data["tools"]),
|
||||
];
|
||||
|
||||
// Backup
|
||||
$backup = "/opt/wevads/vault/wevia-tool-registry.GOLD-" . date("Ymd-His") . "-wave229";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
|
||||
|
||||
echo json_encode([
|
||||
"orig_count" => $orig_count,
|
||||
"added" => $added,
|
||||
"new_count" => count($data["tools"]),
|
||||
"wrote" => $wrote,
|
||||
"backup" => basename($backup),
|
||||
"new_ids" => array_column($new_tools, "id"),
|
||||
]);
|
||||
122
api/ambre-wire-v10.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/wevia.html";
|
||||
$c = @file_get_contents($path);
|
||||
$orig = strlen($c);
|
||||
|
||||
if (strpos($c, "AMBRE-V10-MERMAID") !== false) {
|
||||
echo json_encode(["already_wired"=>true, "size"=>$orig]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Insert BEFORE V9-PDF-PREMIUM (so mermaid match comes first if both patterns match)
|
||||
$anchor = " // === AMBRE-V9-PDF-PREMIUM 2026-04-22";
|
||||
$idx = strpos($c, $anchor);
|
||||
if ($idx === false) {
|
||||
echo json_encode(["error"=>"V9 anchor not found"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Also make sure the _ambre_gen_pat doesn't catch "mermaid" which we want to route here first
|
||||
// Currently _ambre_gen_pat matches mermaid → route to V2-GEN-ROUTER
|
||||
// Solution: put V10 BEFORE V2 check (before all routers), by placing it in send() very early
|
||||
// Actually just before V9 is OK since that's before V2 in flow
|
||||
|
||||
$v10 = <<<'JS'
|
||||
// === AMBRE-V10-MERMAID 2026-04-22 · Mermaid RAG + inline SVG render + artifact panel ===
|
||||
var _mermaid_intent_pat = /(?:mermaid|sch[eé]ma|diagramme|flowchart|sequence\s+diagram|gantt\s+chart)/i;
|
||||
if (_mermaid_intent_pat.test(text)) {
|
||||
if (typeof showThinking === 'function') showThinking();
|
||||
busy = true;
|
||||
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=true;}catch(e){}
|
||||
|
||||
var _fetch = (typeof window.__ambreFetch === 'function') ? window.__ambreFetch : fetch;
|
||||
_fetch('/api/ambre-tool-mermaid.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({topic: text})
|
||||
})
|
||||
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
|
||||
.then(function(data){
|
||||
if (typeof hideThinking === 'function') hideThinking();
|
||||
busy = false;
|
||||
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){}
|
||||
try{var mi=document.getElementById("msgInput");if(mi){mi.value="";mi.disabled=false;}}catch(e){}
|
||||
|
||||
if (!data || !data.ok) {
|
||||
addMsg('assistant', '❌ Erreur génération Mermaid. ' + ((data && data.error) || 'Réessayez.'), '0');
|
||||
return;
|
||||
}
|
||||
|
||||
var mcode = data.mermaid_code;
|
||||
var topic = data.topic || text;
|
||||
var src = data.source || 'unknown';
|
||||
var kind = data.kind || 'flowchart';
|
||||
|
||||
// Badges
|
||||
var srcBadge = (src === 'kb_reused') ?
|
||||
'<span class="nx-badge" style="background:rgba(16,185,129,.15);color:#10b981">♻️ KB Reused (' + (data.use_count || 0) + ' uses)</span>' :
|
||||
'<span class="nx-badge" style="background:rgba(99,102,241,.15);color:#6366f1">🧠 LLM Generated</span>';
|
||||
|
||||
var badges = '<div style="display:flex;gap:6px;flex-wrap:wrap;margin:8px 0">' +
|
||||
srcBadge +
|
||||
'<span class="nx-badge" style="background:rgba(139,92,246,.15);color:#8b5cf6">' + kind + '</span>' +
|
||||
'<span class="nx-badge" style="background:rgba(245,158,11,.15);color:#f59e0b">' + (data.elapsed_ms || 0) + 'ms</span>' +
|
||||
'</div>';
|
||||
|
||||
// Inline render div
|
||||
var uniqId = 'mmd-' + Date.now();
|
||||
var inlineBlock = '<div style="margin:12px 0;padding:14px;background:#fafafa;border:1px solid #e5e7eb;border-radius:12px">' +
|
||||
'<div style="font-weight:600;font-size:13px;color:#6b7280;margin-bottom:10px">📊 ' + topic + '</div>' +
|
||||
'<div class="mermaid" id="' + uniqId + '" style="text-align:center">' + mcode + '</div>' +
|
||||
'<details style="margin-top:10px"><summary style="cursor:pointer;font-size:11px;color:#94a3b8">📝 Voir le code</summary>' +
|
||||
'<pre style="background:#1a1a2e;color:#e6edf3;padding:10px;border-radius:8px;font-size:11px;margin-top:8px;overflow-x:auto">' +
|
||||
mcode.replace(/</g,'<').replace(/>/g,'>') + '</pre></details>' +
|
||||
'</div>';
|
||||
|
||||
addMsg('assistant', badges + inlineBlock, (data.elapsed_ms/1000).toFixed(2));
|
||||
|
||||
// Trigger mermaid render after DOM update
|
||||
setTimeout(function(){
|
||||
try {
|
||||
if (window.mermaid && typeof window.mermaid.run === 'function') {
|
||||
window.mermaid.run({ nodes: [document.getElementById(uniqId)] });
|
||||
} else if (window.mermaid && typeof window.mermaid.init === 'function') {
|
||||
window.mermaid.init(undefined, document.getElementById(uniqId));
|
||||
}
|
||||
} catch(e) { console.warn('mermaid render fail', e); }
|
||||
}, 300);
|
||||
|
||||
// Also open artifact panel with preview
|
||||
if (typeof openPreview === 'function') {
|
||||
try {
|
||||
var svgWrap = '<div style="padding:20px;background:#fff;height:100%;overflow:auto"><h3 style="margin-bottom:16px">' + topic + '</h3><div class="mermaid">' + mcode + '</div></div>';
|
||||
openPreview({type:'html', content: svgWrap});
|
||||
} catch(e) {}
|
||||
}
|
||||
})
|
||||
.catch(function(err){
|
||||
if (typeof hideThinking === 'function') hideThinking();
|
||||
busy = false;
|
||||
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){}
|
||||
addMsg('assistant', '❌ Service Mermaid temporairement indisponible.', '0');
|
||||
});
|
||||
return;
|
||||
}
|
||||
// === END AMBRE-V10-MERMAID ===
|
||||
|
||||
JS;
|
||||
|
||||
$new_c = substr($c, 0, $idx) . $v10 . substr($c, $idx);
|
||||
$delta = strlen($new_c) - $orig;
|
||||
|
||||
$backup = "/opt/wevads/vault/wevia.html.GOLD-" . date("Ymd-His") . "-v10-mermaid";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $new_c);
|
||||
|
||||
echo json_encode([
|
||||
"delta" => $delta,
|
||||
"wrote" => $wrote,
|
||||
"backup" => basename($backup),
|
||||
"new_size" => strlen($new_c),
|
||||
]);
|
||||
100
api/ambre-wire-v10b.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/wevia.html";
|
||||
$c = @file_get_contents($path);
|
||||
$orig = strlen($c);
|
||||
|
||||
if (strpos($c, "AMBRE-V10-MERMAID") !== false) {
|
||||
echo json_encode(["already_wired"=>true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Use pattern matching for V9 anchor
|
||||
$pos = strpos($c, "// === AMBRE-V9-PDF-PREMIUM");
|
||||
if ($pos === false) {
|
||||
echo json_encode(["error"=>"V9 not found at all"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Back up to line start
|
||||
$line_start = strrpos(substr($c, 0, $pos), "\n") + 1;
|
||||
|
||||
$v10 = ' // === AMBRE-V10-MERMAID 2026-04-22 · Mermaid RAG + inline SVG + artifact panel ===
|
||||
var _mermaid_intent_pat = /(?:mermaid|sch[eé]ma|diagramme|flowchart|sequence\\s+diagram|gantt\\s+chart)/i;
|
||||
if (_mermaid_intent_pat.test(text)) {
|
||||
if (typeof showThinking === "function") showThinking();
|
||||
busy = true;
|
||||
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=true;}catch(e){}
|
||||
|
||||
var _fetch = (typeof window.__ambreFetch === "function") ? window.__ambreFetch : fetch;
|
||||
_fetch("/api/ambre-tool-mermaid.php", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type":"application/json"},
|
||||
body: JSON.stringify({topic: text})
|
||||
})
|
||||
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
|
||||
.then(function(data){
|
||||
if (typeof hideThinking === "function") hideThinking();
|
||||
busy = false;
|
||||
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){}
|
||||
try{var mi=document.getElementById("msgInput");if(mi){mi.value="";mi.disabled=false;}}catch(e){}
|
||||
|
||||
if (!data || !data.ok) {
|
||||
addMsg("assistant", "❌ Erreur Mermaid. " + ((data && data.error) || "Réessayez."), "0");
|
||||
return;
|
||||
}
|
||||
|
||||
var mcode = data.mermaid_code;
|
||||
var topic = data.topic || text;
|
||||
var src = data.source || "unknown";
|
||||
var kind = data.kind || "flowchart";
|
||||
|
||||
var srcBadge = (src === "kb_reused") ?
|
||||
"<span class=\\"nx-badge\\" style=\\"background:rgba(16,185,129,.15);color:#10b981\\">♻️ KB Reused (" + (data.use_count || 0) + " uses)</span>" :
|
||||
"<span class=\\"nx-badge\\" style=\\"background:rgba(99,102,241,.15);color:#6366f1\\">🧠 LLM Generated</span>";
|
||||
var kindBadge = "<span class=\\"nx-badge\\" style=\\"background:rgba(139,92,246,.15);color:#8b5cf6\\">" + kind + "</span>";
|
||||
var elapsedBadge = "<span class=\\"nx-badge\\" style=\\"background:rgba(245,158,11,.15);color:#f59e0b\\">" + (data.elapsed_ms || 0) + "ms</span>";
|
||||
var badges = "<div style=\\"display:flex;gap:6px;flex-wrap:wrap;margin:8px 0\\">" + srcBadge + kindBadge + elapsedBadge + "</div>";
|
||||
|
||||
var uniqId = "mmd-" + Date.now();
|
||||
var inlineBlock = "<div style=\\"margin:12px 0;padding:14px;background:#fafafa;border:1px solid #e5e7eb;border-radius:12px\\">" +
|
||||
"<div style=\\"font-weight:600;font-size:13px;color:#6b7280;margin-bottom:10px\\">📊 " + topic + "</div>" +
|
||||
"<div class=\\"mermaid\\" id=\\"" + uniqId + "\\" style=\\"text-align:center\\">" + mcode + "</div>" +
|
||||
"<details style=\\"margin-top:10px\\"><summary style=\\"cursor:pointer;font-size:11px;color:#94a3b8\\">📝 Voir le code</summary>" +
|
||||
"<pre style=\\"background:#1a1a2e;color:#e6edf3;padding:10px;border-radius:8px;font-size:11px;margin-top:8px;overflow-x:auto\\">" +
|
||||
mcode.replace(/</g,"<").replace(/>/g,">") + "</pre></details>" +
|
||||
"</div>";
|
||||
|
||||
addMsg("assistant", badges + inlineBlock, (data.elapsed_ms/1000).toFixed(2));
|
||||
|
||||
setTimeout(function(){
|
||||
try {
|
||||
if (window.mermaid && typeof window.mermaid.run === "function") {
|
||||
window.mermaid.run({ nodes: [document.getElementById(uniqId)] });
|
||||
}
|
||||
} catch(e) { console.warn("mermaid render fail", e); }
|
||||
}, 300);
|
||||
})
|
||||
.catch(function(err){
|
||||
if (typeof hideThinking === "function") hideThinking();
|
||||
busy = false;
|
||||
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){}
|
||||
addMsg("assistant", "❌ Service Mermaid indisponible.", "0");
|
||||
});
|
||||
return;
|
||||
}
|
||||
// === END AMBRE-V10-MERMAID ===
|
||||
|
||||
';
|
||||
|
||||
$new_c = substr($c, 0, $line_start) . $v10 . substr($c, $line_start);
|
||||
|
||||
$backup = "/opt/wevads/vault/wevia.html.GOLD-" . date("Ymd-His") . "-v10-mermaid";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $new_c);
|
||||
|
||||
echo json_encode([
|
||||
"delta" => strlen($new_c) - $orig,
|
||||
"wrote" => $wrote,
|
||||
"backup" => basename($backup),
|
||||
]);
|
||||
12
api/ambre-write-doctrine.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/opt/obsidian-vault/doctrines/109-wave229-6sigma-sse-pdf-premium.md";
|
||||
$dir = dirname($path);
|
||||
if (!is_dir($dir)) @mkdir($dir, 0777, true);
|
||||
$content = base64_decode("IyAxMDkgwrcgV2F2ZS0yMjkgwrcgNs+DIFN0YWJpbGl0eSDCtyBTU0UgZml4IMK3IFBERiBQcmVtaXVtIMK3IE1lcm1haWQgbGVhcm5pbmcKCioqV2F2ZSoqIDogMjI5IMK3IDIwMjYtMDQtMjIKKipTdGF0dXMqKiA6IOKchSBMSVZFIMK3IFRhZ2dlZCBgd2F2ZS0yMjktNnNpZ21hLXN0YWJpbGl0eS1zc2UtZml4ZWRgCioqT2JqZWN0aWYqKiA6IHLDqXNvdWRyZSBsYSBjYXVzZSByYWNpbmUgYHNlbmRNc2cgdW5kZWZpbmVkYCArIGTDqWxpdnJlciBQREYgUHJlbWl1bSBjaXJjdWl0ICsgTWVybWFpZCBsZWFybmluZyBLQgoKIyMg8J+OryBDYXVzZXMgcmFjaW5lcyBpZGVudGlmacOpZXMKCiMjIyBCdWcgIzEgOiByZWdleCBgL1xuL2dgIGNhc3PDqSBkYW5zIHdldmlhLXNzZS1vdmVycmlkZS5qcyBsaWduZSA0OAotIENhdXNlIDogcmVnZXggbGl0ZXJhbCBzcGxpdCBwYXIgdW4gdnJhaSBuZXdsaW5lIHNvdXJjZQotIEZpeCA6IGBzcGxpdChTdHJpbmcuZnJvbUNoYXJDb2RlKDEwKSkuam9pbignPGJyPicpYAoKIyMjIEJ1ZyAjMiA6IGBfYW1icmVfZ2VuX3BhdGAgUmVmZXJlbmNlRXJyb3IgKExBIHZyYWllIGNhdXNlKQotIFZhcmlhYmxlIHV0aWxpc8OpZSBsaWduZSAxMzE4IG1haXMgZMOpY2xhcsOpZSBsaWduZSAxNzgyCi0gUmVmZXJlbmNlRXJyb3IgY2F1Z2h0IHBhciB0cnkvY2F0Y2ggZ2xvYmFsIOKGkiAiVW5lIGVycmV1ciBlc3Qgc3VydmVudWUiCi0gRml4IDogaG9pc3QgYXZhbnQgcHJlbWnDqHJlIHVzYWdlIGF2ZWMgYHR5cGVvZiB1bmRlZmluZWRgIGd1YXJkCgojIyMgQnVncyBzZWNvbmRhaXJlcwotIGAvbWVybWFpZC9pLnRlc3QobXNnKWAg4oaSIGBpbmRleE9mKCdtZXJtYWlkJyk+PTBgCi0gYG5ldyBSZWdFeHAoZmluYWxGaWxlVXJsKWAg4oaSIGBzcGxpdCgpLmpvaW4oKWAKCiMjIPCfj4YgTGl2cmFibGVzIFdhdmUtMjI5CgojIyMgUERGIFByZW1pdW0gKGFkZGl0aWYpCi0gYC9hcGkvYW1icmUtdG9vbC1wZGYtcHJlbWl1bS5waHBgIMK3IDEyS0IgwrcgQ2hhcnQuanMgKyBnb29nbGUtY2hyb21lIGhlYWRsZXNzCi0gU3RydWN0dXJlIExMTSBKU09OIMK3IDYgdHlwZXMgY2hhcnQgKGJhciwgcGllLCBsaW5lLCBkb3VnaG51dCwgcmFkYXIsIHBvbGFyQXJlYSkKLSA0IHBhZ2VzIMK3IDk4LTExMUtCIMK3IDEuNy02cwoKIyMjIExMTSBTZW1hcGhvcmUgc2VydmVyLXNpZGUKLSBgL2FwaS9hbWJyZS1sbG0tc2VtYXBob3JlLnBocGAgwrcgbWF4IDUgY29uY3VycmVudAotIExvYWQgYXZnIDE3IOKGkiA5CgojIyMgTWVybWFpZCBMZWFybmluZyBLQiAoUkFHKQotIGAvYXBpL2FtYnJlLW1lcm1haWQtbGVhcm4ucGhwYCAoc2F2ZS9zZWFyY2gvdXNlL3N0YXRzKQotIGAvYXBpL2FtYnJlLXRvb2wtbWVybWFpZC5waHBgIChSQUcgd3JhcHBlcikKLSBSZXVzZSAzbXMgdnMgTExNIDQwMG1zIChnYWluIDk5JSkKCiMjIyBWMzAgU2hvd2Nhc2UgVmlkZW8KLSBgL2dlbmVyYXRlZC93ZXZpYS12MzAtc2hvd2Nhc2UtMjAyNjA0MjItMDEwNDQ2LndlYm1gIMK3IDEwLjM2IE1CCi0gMTIgdHVybnMgTGF1cmEvQ2FycmVmb3VyIE1hcm9jIMK3IDE0IHNjcmVlbnNob3RzCgojIyDwn46vIExlw6dvbnMKCjEuIFRvdWpvdXJzIGBub2RlIC0tY2hlY2tgIHN1ciBUT1VTIGxlcyBzY3JpcHRzIGV4dGVybmVzCjIuIFJlZ2V4IGAvXG4vZ2AgZnJhZ2lsZSDihpIgcHLDqWbDqXJlciBgc3BsaXQoKS5qb2luKClgCjMuIEpTIGhvaXN0IHNldWxlbWVudCBgZnVuY3Rpb25gIGRlY2xhcmF0aW9ucywgcGFzIGB2YXJgCjQuIFRyeS9jYXRjaCBnbG9iYWwgYXZlYyBtZXNzYWdlIGfDqW7DqXJpcXVlIG1hc3F1ZSBsZXMgdnJhaXMgYnVncwo1LiBQbGF5d3JpZ2h0IGBwYWdlLm9uKCdwYWdlZXJyb3InKWAgPSBtZWlsbGV1ciBkZWJ1Zwo2LiBDYWNoZS1idXN0IGA/dj08dHM+YCBhcHLDqHMgZml4IEpTIGV4dGVybmFsIG9ibGlnYXRvaXJlCgojIyDwn4+bIDbPgyBlbmdhZ2VtZW50CuKchSBaZXJvIHLDqWdyZXNzaW9uIMK3IFplcm8gw6ljcmFzZW1lbnQgwrcgWmVybyBmYWtlIMK3IFplcm8gaGFyZGNvZGUgwrcgU2VtYXBob3JlIHRocm90dGxlIMK3IFRyYWluIGNvbW1pdHMK");
|
||||
$w = @file_put_contents($path, $content);
|
||||
echo json_encode([
|
||||
"path" => $path,
|
||||
"wrote" => $w,
|
||||
"size" => strlen($content),
|
||||
]);
|
||||
23
api/ambre-wtp-wire-cx.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
header("Content-Type: text/plain");
|
||||
$path = "/var/www/html/weval-technology-platform.html";
|
||||
$c = @file_get_contents($path);
|
||||
if (strpos($c, "dashboards-hub-unified") !== false) { echo "already"; exit; }
|
||||
|
||||
$anchor = 'href="/dashboards-index.html"';
|
||||
$pos = strpos($c, $anchor);
|
||||
if ($pos === false) { echo "no anchor"; exit; }
|
||||
|
||||
$a_end = strpos($c, "</a>", $pos) + 4;
|
||||
$link = '<a href="/dashboards-hub-unified.html" class="wtp-link" style="display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,#4338ca,#6366f1);color:#fff;border-radius:6px;text-decoration:none;font-size:12px;font-weight:500;margin:0 4px"><span>📊 Hub Unifié</span></a>';
|
||||
|
||||
$new_c = substr($c, 0, $a_end) . $link . substr($c, $a_end);
|
||||
|
||||
// Write via sudo cat
|
||||
$tmpfile = tempnam("/tmp", "wtp_");
|
||||
file_put_contents($tmpfile, $new_c);
|
||||
// Copy back via sudo
|
||||
$result = shell_exec("sudo cp $tmpfile $path 2>&1");
|
||||
echo "Result: " . $result . "\n";
|
||||
echo "New size: " . strlen($new_c) . "B · delta +" . (strlen($new_c)-strlen($c)) . "B\n";
|
||||
unlink($tmpfile);
|
||||
22
api/ambre-wtp-wire-e2e.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
header("Content-Type: text/plain");
|
||||
$path = "/var/www/html/weval-technology-platform.html";
|
||||
$c = @file_get_contents($path);
|
||||
if (strpos($c, "dashboards-hub-unified") !== false) { echo "already"; exit; }
|
||||
|
||||
$anchor = 'href="/e2e-dashboard.html"';
|
||||
$pos = strpos($c, $anchor);
|
||||
if ($pos === false) { echo "no anchor e2e-dashboard\n"; exit; }
|
||||
|
||||
$a_end = strpos($c, "</a>", $pos) + 4;
|
||||
$link = "\n<a href=\"/dashboards-hub-unified.html\" class=\"wtp-link\" title=\"Hub Dashboards Unifié · 24 dashboards · 13 catégories · wave-246\" style=\"display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,#4338ca,#6366f1);color:#fff;border-radius:6px;text-decoration:none;font-size:12px;font-weight:500;margin:0 4px\"><span>📊 Hub Unifié · 24 dashboards</span></a>";
|
||||
|
||||
$new_c = substr($c, 0, $a_end) . $link . substr($c, $a_end);
|
||||
|
||||
$tmpfile = tempnam("/tmp", "wtp_");
|
||||
file_put_contents($tmpfile, $new_c);
|
||||
$r = shell_exec("sudo cp $tmpfile $path 2>&1");
|
||||
unlink($tmpfile);
|
||||
echo "Result: [$r]\n";
|
||||
echo "New size: " . strlen($new_c) . "B · delta +" . (strlen($new_c)-strlen($c)) . "B\n";
|
||||
echo "Verify content: " . (strpos(file_get_contents($path), "dashboards-hub-unified") !== false ? "YES" : "NO") . "\n";
|
||||
57
api/ambre-wtp-wire.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
header("Content-Type: application/json");
|
||||
$path = "/var/www/html/weval-technology-platform.html";
|
||||
$c = @file_get_contents($path);
|
||||
|
||||
if (strpos($c, "dashboards-hub-unified") !== false) {
|
||||
echo json_encode(["already"=>true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Add the link in banner (find "Arsenal History" or similar anchor)
|
||||
// Use safer approach: add a link block AFTER <body> or before first </div>
|
||||
// Find the existing banner Mega/Arsenal link
|
||||
$anchors = [
|
||||
"href=\"/dashboards-index.html\"" => "AFTER",
|
||||
"href=\"/e2e-dashboard.html\"" => "BEFORE",
|
||||
];
|
||||
|
||||
$injected = false;
|
||||
foreach ($anchors as $anchor => $where) {
|
||||
$pos = strpos($c, $anchor);
|
||||
if ($pos !== false) {
|
||||
// Find surrounding <a> element boundary
|
||||
$a_start = strrpos(substr($c, 0, $pos), "<a ");
|
||||
if ($a_start !== false) {
|
||||
// End of the </a>
|
||||
$a_end = strpos($c, "</a>", $pos);
|
||||
if ($a_end !== false) {
|
||||
$a_end += 4;
|
||||
$link_html = "<a href=\"/dashboards-hub-unified.html\" style=\"display:inline-flex;align-items:center;gap:6px;padding:6px 12px;background:linear-gradient(135deg,#4338ca,#6366f1);color:#fff;border-radius:6px;text-decoration:none;font-size:12px;font-weight:500;margin-right:8px\">📊 Hub Unifié</a>";
|
||||
if ($where === "BEFORE") {
|
||||
$new_c = substr($c, 0, $a_start) . $link_html . substr($c, $a_start);
|
||||
} else {
|
||||
$new_c = substr($c, 0, $a_end) . $link_html . substr($c, $a_end);
|
||||
}
|
||||
$c = $new_c;
|
||||
$injected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$injected) {
|
||||
echo json_encode(["error"=>"no anchor found to inject link"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$backup = "/opt/wevads/vault/wtp.GOLD-" . date("Ymd-His") . "-wave246-hub";
|
||||
@copy($path, $backup);
|
||||
$wrote = @file_put_contents($path, $c);
|
||||
|
||||
echo json_encode([
|
||||
"wrote" => $wrote,
|
||||
"size" => strlen($c),
|
||||
"backup" => basename($backup),
|
||||
]);
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-04-22 00:30:02",
|
||||
"generated": "2026-04-22 02:00:02",
|
||||
"version": "1.0",
|
||||
"servers": [
|
||||
{
|
||||
@@ -8,9 +8,9 @@
|
||||
"private": "10.1.0.2",
|
||||
"role": "PRIMARY",
|
||||
"ssh": 49222,
|
||||
"disk_pct": 84,
|
||||
"disk_avail": "25G",
|
||||
"uptime": "up 1 week, 14 hours, 38 minutes",
|
||||
"disk_pct": 85,
|
||||
"disk_avail": "22G",
|
||||
"uptime": "up 1 week, 16 hours, 8 minutes",
|
||||
"nginx": "active",
|
||||
"php_fpm": "active",
|
||||
"php_version": "8.5.5"
|
||||
@@ -21,8 +21,8 @@
|
||||
"private": "10.1.0.3",
|
||||
"role": "WEVADS Arsenal",
|
||||
"ssh": 22,
|
||||
"disk_pct": 81,
|
||||
"disk_avail": "29G",
|
||||
"disk_pct": 83,
|
||||
"disk_avail": "26G",
|
||||
"sentinel": 1
|
||||
},
|
||||
{
|
||||
@@ -280,9 +280,9 @@
|
||||
}
|
||||
],
|
||||
"screens": {
|
||||
"s204_html": 320,
|
||||
"s204_html": 324,
|
||||
"s204_products": 104,
|
||||
"s204_api_php": 951,
|
||||
"s204_api_php": 995,
|
||||
"s204_wevia_php": 254,
|
||||
"s95_arsenal_html": 1377,
|
||||
"s95_arsenal_api": 377
|
||||
@@ -306,7 +306,7 @@
|
||||
"langfuse"
|
||||
],
|
||||
"key_tables": {
|
||||
"kb_learnings": 5588,
|
||||
"kb_learnings": 5608,
|
||||
"kb_documents": 0,
|
||||
"ethica_medecins": 50004,
|
||||
"enterprise_agents": 0
|
||||
@@ -606,15 +606,15 @@
|
||||
]
|
||||
},
|
||||
"wiki": {
|
||||
"total_entries": 5588,
|
||||
"total_entries": 5608,
|
||||
"categories": [
|
||||
{
|
||||
"category": "AUTO-FIX",
|
||||
"cnt": "2995"
|
||||
"cnt": "3012"
|
||||
},
|
||||
{
|
||||
"category": "TOPOLOGY",
|
||||
"cnt": "1237"
|
||||
"cnt": "1240"
|
||||
},
|
||||
{
|
||||
"category": "DISCOVERY",
|
||||
@@ -1724,44 +1724,44 @@
|
||||
"recent_commits": [],
|
||||
"auto_fixes": [
|
||||
{
|
||||
"fact": "AUTONOMY 22Apr 00:25: 1 fixes. Docker restart weval-docuseal",
|
||||
"created_at": "2026-04-22 02:25:05.377818"
|
||||
"fact": "AUTONOMY 22Apr 01:55: 1 fixes. Disk light cleanup 85%",
|
||||
"created_at": "2026-04-22 03:55:05.962118"
|
||||
},
|
||||
{
|
||||
"fact": "AUTONOMY 22Apr 00:20: 1 fixes. Docker restart weval-docuseal",
|
||||
"created_at": "2026-04-22 02:20:06.702568"
|
||||
"fact": "AUTONOMY 22Apr 01:50: 1 fixes. Disk light cleanup 85%",
|
||||
"created_at": "2026-04-22 03:50:06.817088"
|
||||
},
|
||||
{
|
||||
"fact": "AUTONOMY 22Apr 00:05: 1 fixes. Docker restart weval-docuseal",
|
||||
"created_at": "2026-04-22 02:05:05.625482"
|
||||
"fact": "AUTONOMY 22Apr 01:45: 1 fixes. Disk light cleanup 85%",
|
||||
"created_at": "2026-04-22 03:45:05.349235"
|
||||
},
|
||||
{
|
||||
"fact": "AUTONOMY 21Apr 23:55: 1 fixes. Docker restart weval-docuseal",
|
||||
"created_at": "2026-04-22 01:55:06.366192"
|
||||
"fact": "AUTONOMY 22Apr 01:40: 1 fixes. Disk light cleanup 85%",
|
||||
"created_at": "2026-04-22 03:40:06.63822"
|
||||
},
|
||||
{
|
||||
"fact": "AUTONOMY 21Apr 23:45: 1 fixes. Docker restart weval-docuseal",
|
||||
"created_at": "2026-04-22 01:45:05.59096"
|
||||
"fact": "AUTONOMY 22Apr 01:35: 1 fixes. Disk light cleanup 85%",
|
||||
"created_at": "2026-04-22 03:35:05.620952"
|
||||
},
|
||||
{
|
||||
"fact": "AUTONOMY 21Apr 23:40: 1 fixes. Docker restart weval-docuseal",
|
||||
"created_at": "2026-04-22 01:40:06.310382"
|
||||
"fact": "AUTONOMY 22Apr 01:30: 1 fixes. Disk light cleanup 85%",
|
||||
"created_at": "2026-04-22 03:30:09.806868"
|
||||
},
|
||||
{
|
||||
"fact": "AUTONOMY 21Apr 23:35: 1 fixes. Docker restart weval-docuseal",
|
||||
"created_at": "2026-04-22 01:35:05.795042"
|
||||
"fact": "AUTONOMY 22Apr 01:25: 1 fixes. Disk light cleanup 85%",
|
||||
"created_at": "2026-04-22 03:25:05.698889"
|
||||
},
|
||||
{
|
||||
"fact": "AUTONOMY 21Apr 23:30: 1 fixes. Docker restart weval-docuseal",
|
||||
"created_at": "2026-04-22 01:30:08.407605"
|
||||
"fact": "AUTONOMY 22Apr 01:20: 1 fixes. Disk light cleanup 85%",
|
||||
"created_at": "2026-04-22 03:20:06.441663"
|
||||
},
|
||||
{
|
||||
"fact": "AUTONOMY 21Apr 23:25: 1 fixes. Docker restart weval-docuseal",
|
||||
"created_at": "2026-04-22 01:25:05.516865"
|
||||
"fact": "AUTONOMY 22Apr 01:15: 1 fixes. Disk light cleanup 85%",
|
||||
"created_at": "2026-04-22 03:15:06.44598"
|
||||
},
|
||||
{
|
||||
"fact": "AUTONOMY 21Apr 23:20: 1 fixes. Docker restart weval-docuseal",
|
||||
"created_at": "2026-04-22 01:20:06.634315"
|
||||
"fact": "AUTONOMY 22Apr 01:10: 1 fixes. Disk light cleanup 85%",
|
||||
"created_at": "2026-04-22 03:10:06.312413"
|
||||
}
|
||||
],
|
||||
"architecture_decisions": [
|
||||
@@ -1950,7 +1950,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"scan_time_ms": 3062,
|
||||
"scan_time_ms": 5173,
|
||||
"gaps": [],
|
||||
"score": 100,
|
||||
"automation": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated_at": "2026-04-22T02:40:02.084672",
|
||||
"generated_at": "2026-04-22T04:15:02.029048",
|
||||
"stats": {
|
||||
"total": 48,
|
||||
"pending": 31,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"status": "ALIVE",
|
||||
"ts": "2026-04-22T02:30:02.510384",
|
||||
"last_heartbeat": "2026-04-22T02:30:02.510384",
|
||||
"last_heartbeat_ts_epoch": 1776817802,
|
||||
"ts": "2026-04-22T04:00:02.040414",
|
||||
"last_heartbeat": "2026-04-22T04:00:02.040414",
|
||||
"last_heartbeat_ts_epoch": 1776823202,
|
||||
"tasks_today": 232,
|
||||
"tasks_week": 574,
|
||||
"agent_id": "blade-ops",
|
||||
|
||||
291
api/claude-pattern-api.php
Normal file
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
/* ═══════════════════════════════════════════════════════════════════
|
||||
CLAUDE PATTERN API · Opus session v15 · 21-avr
|
||||
|
||||
Unified endpoint implementing real Claude reasoning pattern:
|
||||
1. THINKING · understand query, classify intent
|
||||
2. PLAN · structured approach (steps)
|
||||
3. RAG · vector search context (Qdrant)
|
||||
4. EXECUTE · dispatch to appropriate backend
|
||||
5. TESTS · validation checks
|
||||
6. RESPONSE · final structured answer
|
||||
7. CRITIQUE · self-review + improvements
|
||||
|
||||
Usage:
|
||||
POST /api/claude-pattern-api.php
|
||||
{"message":"...","chatbot":"wevia-master|wevia|claw|director|ethica"}
|
||||
|
||||
Returns ALL 7 phases in structured JSON (not just final response).
|
||||
═══════════════════════════════════════════════════════════════════ */
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
$t0 = microtime(true);
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$message = trim($input['message'] ?? '');
|
||||
$chatbot = $input['chatbot'] ?? 'wevia-master';
|
||||
$session = $input['session'] ?? 'cp-' . bin2hex(random_bytes(3));
|
||||
|
||||
if (!$message) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'message required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Backend mapping per chatbot (REAL endpoints, NOT simulated)
|
||||
$BACKENDS = [
|
||||
'wevia-master' => '/api/wevia-autonomous.php',
|
||||
'wevia' => '/api/ambre-thinking.php',
|
||||
'claw' => '/api/wevia-json-api.php',
|
||||
'director' => '/api/wevia-autonomous.php',
|
||||
'ethica' => '/api/ethica-brain.php',
|
||||
'auto' => '/api/opus5-autonomous-orchestrator-v3.php',
|
||||
];
|
||||
$FALLBACKS = [
|
||||
'wevia-master' => '/api/opus5-autonomous-orchestrator-v3.php',
|
||||
'director' => '/api/opus5-autonomous-orchestrator-v3.php',
|
||||
];
|
||||
|
||||
$backend = $BACKENDS[$chatbot] ?? $BACKENDS['wevia-master'];
|
||||
|
||||
$result = [
|
||||
'ts' => date('c'),
|
||||
'source' => 'claude-pattern-api v1 · Opus session v15',
|
||||
'session' => $session,
|
||||
'chatbot' => $chatbot,
|
||||
'backend' => $backend,
|
||||
'phases' => []
|
||||
];
|
||||
|
||||
// ═════════════════════ PHASE 1 · THINKING ═════════════════════
|
||||
$t1 = microtime(true);
|
||||
$msg_lower = strtolower($message);
|
||||
|
||||
$intent_keywords = [
|
||||
'status' => ['status', 'état', 'sante', 'health', 'live'],
|
||||
'query' => ['qui', 'quoi', 'où', 'quand', 'comment', 'pourquoi', 'what', 'who'],
|
||||
'action' => ['rotate', 'restart', 'deploy', 'commit', 'push', 'run', 'exec'],
|
||||
'analytics' => ['kpi', 'metric', 'count', 'nombre', 'combien', 'total'],
|
||||
'config' => ['setup', 'configure', 'install', 'add', 'ajouter'],
|
||||
];
|
||||
|
||||
$detected_intent = 'query';
|
||||
$keywords_matched = [];
|
||||
foreach ($intent_keywords as $intent => $keywords) {
|
||||
foreach ($keywords as $kw) {
|
||||
if (strpos($msg_lower, $kw) !== false) {
|
||||
$detected_intent = $intent;
|
||||
$keywords_matched[] = $kw;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$complexity = strlen($message) > 100 ? 'high' : (strlen($message) > 30 ? 'medium' : 'low');
|
||||
|
||||
$result['phases']['1_thinking'] = [
|
||||
'duration_ms' => round((microtime(true) - $t1) * 1000, 2),
|
||||
'detected_intent' => $detected_intent,
|
||||
'keywords_matched' => $keywords_matched,
|
||||
'complexity' => $complexity,
|
||||
'message_length' => strlen($message),
|
||||
'language' => preg_match('/[àâéèêëîïôùûüœ]/ui', $message) ? 'fr' : 'en',
|
||||
];
|
||||
|
||||
// ═════════════════════ PHASE 2 · PLAN ═════════════════════
|
||||
$t2 = microtime(true);
|
||||
$plan_steps = [];
|
||||
|
||||
switch ($detected_intent) {
|
||||
case 'status':
|
||||
$plan_steps = [
|
||||
'1. Query system state via wtp-kpi-global-v2',
|
||||
'2. Check provider health + docker',
|
||||
'3. Format structured response',
|
||||
];
|
||||
break;
|
||||
case 'action':
|
||||
$plan_steps = [
|
||||
'1. Validate action safety + preflight',
|
||||
'2. Call appropriate backend ('.$backend.')',
|
||||
'3. Capture execution output + validate',
|
||||
];
|
||||
break;
|
||||
case 'analytics':
|
||||
$plan_steps = [
|
||||
'1. Query relevant KPI source (wtp-kpi-global-v2, nonreg, architecture)',
|
||||
'2. Extract metrics from JSON',
|
||||
'3. Format quantitative response',
|
||||
];
|
||||
break;
|
||||
default:
|
||||
$plan_steps = [
|
||||
'1. Query RAG / Qdrant context for query',
|
||||
'2. Dispatch to chatbot backend',
|
||||
'3. Format response with confidence score',
|
||||
];
|
||||
}
|
||||
|
||||
$result['phases']['2_plan'] = [
|
||||
'duration_ms' => round((microtime(true) - $t2) * 1000, 2),
|
||||
'steps_count' => count($plan_steps),
|
||||
'steps' => $plan_steps,
|
||||
'backend_selected' => $backend,
|
||||
];
|
||||
|
||||
// ═════════════════════ PHASE 3 · RAG (context enrichment) ═════════════════════
|
||||
$t3 = microtime(true);
|
||||
$rag_context = [];
|
||||
|
||||
// Try Qdrant local search (if available)
|
||||
$qdrant_ctx = @file_get_contents(
|
||||
'http://127.0.0.1:6333/collections/wevia_knowledge/points/search',
|
||||
false,
|
||||
stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-Type: application/json\r\n",
|
||||
'content' => json_encode(['limit' => 3, 'with_payload' => true, 'vector' => array_fill(0, 384, 0.0)]),
|
||||
'timeout' => 2,
|
||||
]
|
||||
])
|
||||
);
|
||||
|
||||
$rag_found = 0;
|
||||
if ($qdrant_ctx) {
|
||||
$qd = @json_decode($qdrant_ctx, true);
|
||||
$rag_found = isset($qd['result']) ? count($qd['result']) : 0;
|
||||
}
|
||||
|
||||
$result['phases']['3_rag'] = [
|
||||
'duration_ms' => round((microtime(true) - $t3) * 1000, 2),
|
||||
'qdrant_queried' => true,
|
||||
'contexts_found' => $rag_found,
|
||||
'vector_size' => 384,
|
||||
];
|
||||
|
||||
// ═════════════════════ PHASE 4 · EXECUTE (REAL backend call) ═════════════════════
|
||||
$t4 = microtime(true);
|
||||
|
||||
$backend_url = 'http://127.0.0.1' . $backend;
|
||||
$backend_body = json_encode(['message' => $message, 'session' => $session]);
|
||||
|
||||
$ctx_exec = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-Type: application/json\r\nHost: weval-consulting.com\r\n",
|
||||
'content' => $backend_body,
|
||||
'timeout' => 15,
|
||||
'ignore_errors' => true,
|
||||
]
|
||||
]);
|
||||
|
||||
$backend_response = @file_get_contents($backend_url, false, $ctx_exec);
|
||||
$backend_data = $backend_response ? @json_decode($backend_response, true) : null;
|
||||
|
||||
$backend_ok = $backend_data !== null && !isset($backend_data['error']);
|
||||
$backend_text = '';
|
||||
|
||||
// FALLBACK if primary fails
|
||||
if (!$backend_ok && isset($FALLBACKS[$chatbot])) {
|
||||
$fallback_url = 'http://127.0.0.1' . $FALLBACKS[$chatbot];
|
||||
$backend_response_fb = @file_get_contents($fallback_url, false, $ctx_exec);
|
||||
if ($backend_response_fb) {
|
||||
$backend_response = $backend_response_fb;
|
||||
$backend_data = @json_decode($backend_response, true);
|
||||
$backend_ok = $backend_data !== null && !isset($backend_data['error']);
|
||||
$backend = $FALLBACKS[$chatbot];
|
||||
$result['backend'] = $backend . ' (fallback)';
|
||||
}
|
||||
}
|
||||
|
||||
if ($backend_data) {
|
||||
// Extract response text (multiple possible formats)
|
||||
$backend_text = $backend_data['text'] ?? $backend_data['response'] ?? $backend_data['answer']
|
||||
?? $backend_data['reply'] ?? $backend_data['message'] ?? '';
|
||||
if (is_array($backend_text)) $backend_text = json_encode($backend_text);
|
||||
}
|
||||
|
||||
$result['phases']['4_execute'] = [
|
||||
'duration_ms' => round((microtime(true) - $t4) * 1000, 2),
|
||||
'backend_called' => $backend_url,
|
||||
'backend_ok' => $backend_ok,
|
||||
'response_size' => strlen((string)$backend_response),
|
||||
'response_preview' => substr($backend_text, 0, 200),
|
||||
];
|
||||
|
||||
// ═════════════════════ PHASE 5 · TESTS (validation) ═════════════════════
|
||||
$t5 = microtime(true);
|
||||
|
||||
$tests = [
|
||||
'has_response' => !empty($backend_text) && strlen($backend_text) > 10,
|
||||
'no_error' => !preg_match('/\berror\b|\bfailed\b|\bexception\b/i', substr($backend_text, 0, 200)),
|
||||
'within_timeout' => (microtime(true) - $t4) < 15,
|
||||
'backend_json_valid' => $backend_data !== null,
|
||||
'not_simulated' => $backend_ok && !preg_match('/simulat(ed|ion)|mock|fake|placeholder/i', substr($backend_text, 0, 300)),
|
||||
];
|
||||
|
||||
$tests_passed = array_sum(array_map('intval', $tests));
|
||||
$tests_total = count($tests);
|
||||
|
||||
$result['phases']['5_tests'] = [
|
||||
'duration_ms' => round((microtime(true) - $t5) * 1000, 2),
|
||||
'passed' => $tests_passed,
|
||||
'total' => $tests_total,
|
||||
'score_pct' => round($tests_passed / $tests_total * 100),
|
||||
'details' => $tests,
|
||||
];
|
||||
|
||||
// ═════════════════════ PHASE 6 · RESPONSE (final) ═════════════════════
|
||||
$t6 = microtime(true);
|
||||
|
||||
$final_response = $backend_text;
|
||||
if (!$final_response && $backend_data) {
|
||||
$final_response = json_encode($backend_data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
if (!$final_response) {
|
||||
$final_response = "Backend did not return response. Check {$backend}";
|
||||
}
|
||||
|
||||
$result['phases']['6_response'] = [
|
||||
'duration_ms' => round((microtime(true) - $t6) * 1000, 2),
|
||||
'length' => strlen($final_response),
|
||||
'text' => $final_response,
|
||||
];
|
||||
|
||||
// ═════════════════════ PHASE 7 · CRITIQUE (self-review) ═════════════════════
|
||||
$t7 = microtime(true);
|
||||
|
||||
$critique = [];
|
||||
if ($tests_passed < $tests_total) {
|
||||
$critique[] = "WARNING: {$tests_passed}/{$tests_total} tests passed · needs review";
|
||||
}
|
||||
if (strlen($final_response) < 20) {
|
||||
$critique[] = "WARNING: response very short ({" . strlen($final_response) . "}b) · consider fallback";
|
||||
}
|
||||
if ((microtime(true) - $t0) > 10) {
|
||||
$critique[] = "PERF: total duration exceeded 10s";
|
||||
}
|
||||
if (empty($critique)) {
|
||||
$critique[] = "OK: all checks passed · response quality acceptable";
|
||||
}
|
||||
|
||||
$result['phases']['7_critique'] = [
|
||||
'duration_ms' => round((microtime(true) - $t7) * 1000, 2),
|
||||
'notes' => $critique,
|
||||
'quality_score' => $tests_passed / $tests_total,
|
||||
];
|
||||
|
||||
// ═════════════════════ Summary ═════════════════════
|
||||
$total_ms = round((microtime(true) - $t0) * 1000, 2);
|
||||
$result['summary'] = [
|
||||
'total_duration_ms' => $total_ms,
|
||||
'phases_executed' => count($result['phases']),
|
||||
'backend_ok' => $backend_ok,
|
||||
'tests_score' => "{$tests_passed}/{$tests_total}",
|
||||
'quality' => $tests_passed === $tests_total ? 'EXCELLENT' : ($tests_passed >= 3 ? 'OK' : 'LOW'),
|
||||
'response' => $final_response,
|
||||
];
|
||||
|
||||
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"timestamp": "2026-04-22 00:00",
|
||||
"timestamp": "2026-04-22 04:00",
|
||||
"checks": {
|
||||
"registry": "0 agents",
|
||||
"system": {
|
||||
"docker": "19",
|
||||
"ram": "12Gi/30Gi",
|
||||
"disk": "84%",
|
||||
"load": "1.85",
|
||||
"uptime": "up 1 week, 12 hours, 8 minutes"
|
||||
"ram": "13Gi/30Gi",
|
||||
"disk": "85%",
|
||||
"load": "13.04",
|
||||
"uptime": "up 1 week, 16 hours, 8 minutes"
|
||||
},
|
||||
"services": "7/10 OK",
|
||||
"nonreg": "153/153 (100%)",
|
||||
@@ -15,7 +15,7 @@
|
||||
"crons": "44 active",
|
||||
"routes": "446",
|
||||
"dataset": "5751 pairs",
|
||||
"wiki": "2066 entries",
|
||||
"wiki": "2123 entries",
|
||||
"enterprise": "758 agents (dorm=0 dead=167)"
|
||||
},
|
||||
"analysis": "Analyse indisponible"
|
||||
|
||||
@@ -331,5 +331,17 @@
|
||||
"ai": 9,
|
||||
"cloud": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ts": "2026-04-22T01:46:06+00:00",
|
||||
"q": "Vistex Inc. Software US",
|
||||
"preset": null,
|
||||
"results": 20,
|
||||
"categories": {
|
||||
"ai": 7,
|
||||
"general": 8,
|
||||
"vistex": 1,
|
||||
"sap": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
71
api/dashboards-registry-ambre.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* dashboards-registry.php · Single Source of Truth for all WEVAL dashboards
|
||||
* wave-246 · point entrée unique consolidé · zero hardcode · auto-scan
|
||||
*/
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
$html_dir = "/var/www/html";
|
||||
$dashboards = [];
|
||||
|
||||
// Scan all *dashboard*.html + *dashboard*.php
|
||||
foreach (array_merge(glob("$html_dir/*dashboard*.html"), glob("$html_dir/*dashboard*.php")) as $f) {
|
||||
$bn = basename($f);
|
||||
$content = @file_get_contents($f);
|
||||
$title = $bn;
|
||||
if (preg_match("/<title>([^<]+)<\/title>/i", $content, $m)) $title = trim($m[1]);
|
||||
elseif (preg_match("/<h1[^>]*>([^<]+)<\/h1>/i", $content, $m)) $title = trim(strip_tags($m[1]));
|
||||
|
||||
// Category inference
|
||||
$cat = "Autres";
|
||||
if (stripos($bn, "kpi") !== false) $cat = "KPI & Analytics";
|
||||
elseif (stripos($bn, "6sigma") !== false || stripos($bn, "lean") !== false) $cat = "Lean 6σ";
|
||||
elseif (stripos($bn, "crm") !== false || stripos($bn, "lead") !== false) $cat = "CRM";
|
||||
elseif (stripos($bn, "ethica") !== false || stripos($bn, "medreach") !== false) $cat = "Ethica";
|
||||
elseif (stripos($bn, "infra") !== false || stripos($bn, "security") !== false || stripos($bn, "office") !== false) $cat = "Infrastructure";
|
||||
elseif (stripos($bn, "wevia") !== false) $cat = "WEVIA";
|
||||
elseif (stripos($bn, "contact") !== false || stripos($bn, "segment") !== false || stripos($bn, "database") !== false) $cat = "Données";
|
||||
elseif (stripos($bn, "acquired") !== false || stripos($bn, "dormant") !== false) $cat = "Lifecycle";
|
||||
elseif (stripos($bn, "orphan") !== false) $cat = "Audit";
|
||||
elseif (stripos($bn, "paperclip") !== false || stripos($bn, "em-") !== false) $cat = "Pilotage";
|
||||
elseif (stripos($bn, "hub") !== false || stripos($bn, "index") !== false) $cat = "Hub central";
|
||||
elseif (stripos($bn, "e2e") !== false) $cat = "Tests";
|
||||
|
||||
$dashboards[] = [
|
||||
"file" => $bn,
|
||||
"url" => "/$bn",
|
||||
"title" => substr($title, 0, 80),
|
||||
"category" => $cat,
|
||||
"size_kb" => round(filesize($f)/1024, 1),
|
||||
"mtime" => date("c", filemtime($f)),
|
||||
"days_ago" => round((time() - filemtime($f))/86400),
|
||||
"recent" => (time() - filemtime($f)) < 172800,
|
||||
];
|
||||
}
|
||||
|
||||
// Group
|
||||
$by_cat = [];
|
||||
foreach ($dashboards as $d) {
|
||||
$by_cat[$d["category"]] = ($by_cat[$d["category"]] ?? 0) + 1;
|
||||
}
|
||||
ksort($by_cat);
|
||||
|
||||
echo json_encode([
|
||||
"ok" => true,
|
||||
"version" => "wave-246",
|
||||
"ts" => date("c"),
|
||||
"total" => count($dashboards),
|
||||
"categories" => $by_cat,
|
||||
"categories_count" => count($by_cat),
|
||||
"hub_url" => "/dashboards-hub-unified.html",
|
||||
"entry_points" => [
|
||||
"wtp" => "/weval-technology-platform.html",
|
||||
"hub" => "/dashboards-hub-unified.html",
|
||||
"ia_hub" => "/all-ia-hub.html",
|
||||
"master" => "/wevia-master.html",
|
||||
"orchestrator" => "/wevia-orchestrator.html",
|
||||
],
|
||||
"source" => "auto-scan filesystem",
|
||||
"zero_orphan" => true,
|
||||
"dashboards" => $dashboards,
|
||||
], JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE);
|
||||
@@ -1,28 +1,28 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"scanned_at": "2026-04-16T16:59:03.806435",
|
||||
"scanned_at": "2026-04-22T03:03:01.674431",
|
||||
"server": "S95",
|
||||
"infra": {
|
||||
"disk": {
|
||||
"total": "150G",
|
||||
"used": "119G",
|
||||
"avail": "26G",
|
||||
"pct": "83%"
|
||||
"used": "122G",
|
||||
"avail": "23G",
|
||||
"pct": "85%"
|
||||
},
|
||||
"memory": {
|
||||
"total": "30Gi",
|
||||
"used": "8.0Gi",
|
||||
"free": "3.3Gi"
|
||||
"used": "12Gi",
|
||||
"free": "459Mi"
|
||||
},
|
||||
"ports_count": 42,
|
||||
"ports_count": 43,
|
||||
"docker_count": 19,
|
||||
"nginx_sites": 14,
|
||||
"html_pages": 505,
|
||||
"api_files": 478,
|
||||
"crons": 3,
|
||||
"html_pages": 718,
|
||||
"api_files": 3152,
|
||||
"crons": 35,
|
||||
"tool_registry": {
|
||||
"count": 421,
|
||||
"version": "7.4"
|
||||
"count": 638,
|
||||
"version": "?"
|
||||
}
|
||||
},
|
||||
"assets": [
|
||||
@@ -176,7 +176,7 @@
|
||||
"type": "api",
|
||||
"status": "live",
|
||||
"port": 8001,
|
||||
"process": "uvicorn",
|
||||
"process": "python",
|
||||
"maturity": 50,
|
||||
"source": "port_scan"
|
||||
},
|
||||
@@ -195,7 +195,7 @@
|
||||
"name": "loki",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 3 hours",
|
||||
"docker_status": "Up 5 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -203,7 +203,7 @@
|
||||
"name": "listmonk",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 6 hours",
|
||||
"docker_status": "Up 5 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -211,7 +211,7 @@
|
||||
"name": "plausible-plausible-1",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 6 hours",
|
||||
"docker_status": "Up 4 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -219,7 +219,7 @@
|
||||
"name": "plausible-plausible-db-1",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 6 hours",
|
||||
"docker_status": "Up 4 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -227,7 +227,7 @@
|
||||
"name": "plausible-plausible-events-db-1",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 6 hours",
|
||||
"docker_status": "Up 4 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -235,7 +235,7 @@
|
||||
"name": "n8n-docker-n8n-1",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 11 hours",
|
||||
"docker_status": "Up 5 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -243,7 +243,7 @@
|
||||
"name": "mattermost-docker-mm-db-1",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 11 hours",
|
||||
"docker_status": "Up 5 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -251,7 +251,7 @@
|
||||
"name": "mattermost-docker-mattermost-1",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 11 hours (healthy)",
|
||||
"docker_status": "Up 5 days (healthy)",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -259,7 +259,7 @@
|
||||
"name": "twenty",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 5 hours",
|
||||
"docker_status": "Up 5 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -267,7 +267,7 @@
|
||||
"name": "twenty-redis",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 12 hours",
|
||||
"docker_status": "Up 5 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -275,7 +275,7 @@
|
||||
"name": "redis-weval",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 2 days",
|
||||
"docker_status": "Up 7 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -283,7 +283,7 @@
|
||||
"name": "gitea",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 2 days",
|
||||
"docker_status": "Up 7 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -291,7 +291,7 @@
|
||||
"name": "node-exporter",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 2 days",
|
||||
"docker_status": "Up 7 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -299,7 +299,7 @@
|
||||
"name": "prometheus",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 2 days",
|
||||
"docker_status": "Up 7 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -307,7 +307,7 @@
|
||||
"name": "searxng",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 2 days",
|
||||
"docker_status": "Up 7 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -323,7 +323,7 @@
|
||||
"name": "vaultwarden",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 2 days (healthy)",
|
||||
"docker_status": "Up 7 days (healthy)",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
@@ -331,7 +331,7 @@
|
||||
"name": "qdrant",
|
||||
"type": "docker",
|
||||
"status": "up",
|
||||
"docker_status": "Up 2 days",
|
||||
"docker_status": "Up 7 days",
|
||||
"source": "docker"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ts": "2026-04-21T15:05:33+02:00",
|
||||
"scanned": 2050,
|
||||
"ts": "2026-04-22T03:15:01+02:00",
|
||||
"scanned": 2067,
|
||||
"misplaced_count": 0,
|
||||
"misplaced": [
|
||||
],
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"ok": true,
|
||||
"agent": "V42_MQL_Scoring_Agent_REAL",
|
||||
"ts": "2026-04-22T00:30:02+00:00",
|
||||
"ts": "2026-04-22T02:10:01+00:00",
|
||||
"status": "DEPLOYED_AUTO",
|
||||
"deployed": true,
|
||||
"algorithm": "weighted_behavioral_signals",
|
||||
"signals_tracked": {
|
||||
"wtp_engagement": 100,
|
||||
"chat_engagement": 0,
|
||||
"chat_engagement": 3,
|
||||
"roi_tool": 0,
|
||||
"email_opened": 0
|
||||
},
|
||||
"avg_score": 25,
|
||||
"avg_score": 25.8,
|
||||
"mql_threshold": 50,
|
||||
"sql_threshold": 75,
|
||||
"leads_captured": 48,
|
||||
@@ -24,6 +24,6 @@
|
||||
"delta": 7.700000000000003
|
||||
},
|
||||
"paperclip_db_ok": true,
|
||||
"paperclip_tables": 1,
|
||||
"paperclip_tables": 2,
|
||||
"root_cause_resolved": "Lead Qualification goulet 16pct manual resolved via AUTO behavioral scoring"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"name": "weval-l99",
|
||||
"path": "/opt/weval-l99",
|
||||
"files": 657,
|
||||
"files": 660,
|
||||
"has_readme": false,
|
||||
"has_skill": false,
|
||||
"has_python": true,
|
||||
@@ -10,7 +10,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.409378"
|
||||
"discovered": "2026-04-22T04:00:05.787539"
|
||||
},
|
||||
{
|
||||
"name": "wevia-brain",
|
||||
@@ -23,7 +23,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.628891"
|
||||
"discovered": "2026-04-22T04:00:06.088007"
|
||||
},
|
||||
{
|
||||
"name": "skills",
|
||||
@@ -36,7 +36,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.170172"
|
||||
"discovered": "2026-04-22T04:00:05.218794"
|
||||
},
|
||||
{
|
||||
"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-22T02:00:03.084321"
|
||||
"discovered": "2026-04-22T04:00:04.093760"
|
||||
},
|
||||
{
|
||||
"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-22T02:00:03.118928"
|
||||
"discovered": "2026-04-22T04:00:04.898989"
|
||||
},
|
||||
{
|
||||
"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-22T02:00:03.114350"
|
||||
"discovered": "2026-04-22T04:00:04.810360"
|
||||
},
|
||||
{
|
||||
"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-22T02:00:03.023407"
|
||||
"discovered": "2026-04-22T04:00:03.257779"
|
||||
},
|
||||
{
|
||||
"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-22T02:00:03.129323"
|
||||
"discovered": "2026-04-22T04:00:04.915762"
|
||||
},
|
||||
{
|
||||
"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-22T02:00:03.232311"
|
||||
"discovered": "2026-04-22T04:00:05.459705"
|
||||
},
|
||||
{
|
||||
"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-22T02:00:03.017460"
|
||||
"discovered": "2026-04-22T04:00:03.141027"
|
||||
},
|
||||
{
|
||||
"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-22T02:00:03.033353"
|
||||
"discovered": "2026-04-22T04:00:03.406309"
|
||||
},
|
||||
{
|
||||
"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-22T02:00:03.184526"
|
||||
"discovered": "2026-04-22T04:00:05.306147"
|
||||
},
|
||||
{
|
||||
"name": "fmgapp",
|
||||
@@ -478,7 +478,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.086564"
|
||||
"discovered": "2026-04-22T04:00:04.180763"
|
||||
},
|
||||
{
|
||||
"name": "obsidian-vault",
|
||||
@@ -491,7 +491,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.116610"
|
||||
"discovered": "2026-04-22T04:00:04.862667"
|
||||
},
|
||||
{
|
||||
"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-22T02:00:03.144332"
|
||||
"discovered": "2026-04-22T04:00:05.014111"
|
||||
},
|
||||
{
|
||||
"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-22T02:00:03.014516"
|
||||
"discovered": "2026-04-22T04:00:03.081764"
|
||||
},
|
||||
{
|
||||
"name": "oss",
|
||||
@@ -530,7 +530,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "# WEVAL OSS Registry · /opt/oss/ Wave 222 · 2026-04-21 ## Purpose Register the OSS tools identified by AI capability gap audit (wave 220 ai-gap-cach",
|
||||
"discovered": "2026-04-22T02:00:03.125201"
|
||||
"discovered": "2026-04-22T04:00:04.908730"
|
||||
},
|
||||
{
|
||||
"name": "scripts",
|
||||
@@ -543,7 +543,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "# Token Rotation Scripts · Opus Session 21-avr v7 ## État - 5 scripts provider skeleton (groq, github, sambanova, alibaba, whatsapp) - 1 master dispa",
|
||||
"discovered": "2026-04-22T02:00:03.160627"
|
||||
"discovered": "2026-04-22T04:00:05.143374"
|
||||
},
|
||||
{
|
||||
"name": "skillsmith",
|
||||
@@ -556,7 +556,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-22T02:00:03.175972"
|
||||
"discovered": "2026-04-22T04:00:05.261405"
|
||||
},
|
||||
{
|
||||
"name": "awesome-agent-skills",
|
||||
@@ -569,7 +569,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-22T02:00:03.062355"
|
||||
"discovered": "2026-04-22T04:00:03.544545"
|
||||
},
|
||||
{
|
||||
"name": "paperclip-skills",
|
||||
@@ -582,7 +582,20 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.127324"
|
||||
"discovered": "2026-04-22T04:00:04.911471"
|
||||
},
|
||||
{
|
||||
"name": "__pycache__",
|
||||
"path": "/opt/__pycache__",
|
||||
"files": 4,
|
||||
"has_readme": false,
|
||||
"has_skill": false,
|
||||
"has_python": false,
|
||||
"has_node": false,
|
||||
"has_docker": false,
|
||||
"wired": false,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T04:00:03.309741"
|
||||
},
|
||||
{
|
||||
"name": "jzOcb_writing-style-skill",
|
||||
@@ -595,7 +608,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "# Writing Style Skill 可复用的写作风格 Skill 模板。**内置自动学习** — 从你的修改中自动提取规则,SKILL.md 越用越准。 兼容 **Claude Code** + **OpenClaw (ClawHub)**。 ## 原理 ``` AI 用 SKILL",
|
||||
"discovered": "2026-04-22T02:00:03.091046"
|
||||
"discovered": "2026-04-22T04:00:04.268582"
|
||||
},
|
||||
{
|
||||
"name": "qdrant-data",
|
||||
@@ -608,7 +621,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.137778"
|
||||
"discovered": "2026-04-22T04:00:04.977548"
|
||||
},
|
||||
{
|
||||
"name": "wazuh",
|
||||
@@ -621,20 +634,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.248809"
|
||||
},
|
||||
{
|
||||
"name": "__pycache__",
|
||||
"path": "/opt/__pycache__",
|
||||
"files": 3,
|
||||
"has_readme": false,
|
||||
"has_skill": false,
|
||||
"has_python": false,
|
||||
"has_node": false,
|
||||
"has_docker": false,
|
||||
"wired": false,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.025585"
|
||||
"discovered": "2026-04-22T04:00:05.486863"
|
||||
},
|
||||
{
|
||||
"name": "plausible",
|
||||
@@ -647,7 +647,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.131318"
|
||||
"discovered": "2026-04-22T04:00:04.921917"
|
||||
},
|
||||
{
|
||||
"name": "pmta",
|
||||
@@ -660,7 +660,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.133586"
|
||||
"discovered": "2026-04-22T04:00:04.923810"
|
||||
},
|
||||
{
|
||||
"name": "render-configs",
|
||||
@@ -673,7 +673,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.139800"
|
||||
"discovered": "2026-04-22T04:00:04.979586"
|
||||
},
|
||||
{
|
||||
"name": "searxng",
|
||||
@@ -686,7 +686,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.162723"
|
||||
"discovered": "2026-04-22T04:00:05.183110"
|
||||
},
|
||||
{
|
||||
"name": "weval-guardian",
|
||||
@@ -699,7 +699,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.348145"
|
||||
"discovered": "2026-04-22T04:00:05.693660"
|
||||
},
|
||||
{
|
||||
"name": "weval-litellm",
|
||||
@@ -712,7 +712,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.483853"
|
||||
"discovered": "2026-04-22T04:00:05.830310"
|
||||
},
|
||||
{
|
||||
"name": "weval-security",
|
||||
@@ -725,7 +725,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.586035"
|
||||
"discovered": "2026-04-22T04:00:06.038247"
|
||||
},
|
||||
{
|
||||
"name": "archive",
|
||||
@@ -738,7 +738,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.050701"
|
||||
"discovered": "2026-04-22T04:00:03.503328"
|
||||
},
|
||||
{
|
||||
"name": "loki",
|
||||
@@ -751,7 +751,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.104539"
|
||||
"discovered": "2026-04-22T04:00:04.567402"
|
||||
},
|
||||
{
|
||||
"name": "ruflo",
|
||||
@@ -764,7 +764,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.151080"
|
||||
"discovered": "2026-04-22T04:00:05.118268"
|
||||
},
|
||||
{
|
||||
"name": "twenty",
|
||||
@@ -777,7 +777,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.211690"
|
||||
"discovered": "2026-04-22T04:00:05.379419"
|
||||
},
|
||||
{
|
||||
"name": "weval-crewai",
|
||||
@@ -790,7 +790,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.275798"
|
||||
"discovered": "2026-04-22T04:00:05.589634"
|
||||
},
|
||||
{
|
||||
"name": "weval-plugins",
|
||||
@@ -803,7 +803,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.537962"
|
||||
"discovered": "2026-04-22T04:00:05.936437"
|
||||
},
|
||||
{
|
||||
"name": "weval-radar",
|
||||
@@ -816,7 +816,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.546666"
|
||||
"discovered": "2026-04-22T04:00:05.954386"
|
||||
},
|
||||
{
|
||||
"name": "weval-scrapy",
|
||||
@@ -829,7 +829,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.553035"
|
||||
"discovered": "2026-04-22T04:00:05.997555"
|
||||
},
|
||||
{
|
||||
"name": "blade",
|
||||
@@ -842,7 +842,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.072175"
|
||||
"discovered": "2026-04-22T04:00:03.719664"
|
||||
},
|
||||
{
|
||||
"name": "langfuse",
|
||||
@@ -855,7 +855,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.093198"
|
||||
"discovered": "2026-04-22T04:00:04.322859"
|
||||
},
|
||||
{
|
||||
"name": "litellm",
|
||||
@@ -868,7 +868,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.100112"
|
||||
"discovered": "2026-04-22T04:00:04.466063"
|
||||
},
|
||||
{
|
||||
"name": "mattermost-docker",
|
||||
@@ -881,7 +881,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.107376"
|
||||
"discovered": "2026-04-22T04:00:04.676612"
|
||||
},
|
||||
{
|
||||
"name": "prometheus",
|
||||
@@ -894,7 +894,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.135711"
|
||||
"discovered": "2026-04-22T04:00:04.972115"
|
||||
},
|
||||
{
|
||||
"name": "twenty-compose",
|
||||
@@ -907,7 +907,7 @@
|
||||
"has_docker": true,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.218696"
|
||||
"discovered": "2026-04-22T04:00:05.381615"
|
||||
},
|
||||
{
|
||||
"name": "weval-ux",
|
||||
@@ -920,7 +920,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.610331"
|
||||
"discovered": "2026-04-22T04:00:06.063995"
|
||||
},
|
||||
{
|
||||
"name": "wevia-integrity",
|
||||
@@ -933,7 +933,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.656404"
|
||||
"discovered": "2026-04-22T04:00:06.146050"
|
||||
},
|
||||
{
|
||||
"name": "DiffusionDB",
|
||||
@@ -946,7 +946,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.010388"
|
||||
"discovered": "2026-04-22T04:00:03.010735"
|
||||
},
|
||||
{
|
||||
"name": "LTX-Video",
|
||||
@@ -959,7 +959,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.021093"
|
||||
"discovered": "2026-04-22T04:00:03.230164"
|
||||
},
|
||||
{
|
||||
"name": "localai",
|
||||
@@ -972,7 +972,7 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.102369"
|
||||
"discovered": "2026-04-22T04:00:04.538083"
|
||||
},
|
||||
{
|
||||
"name": "wevia-finetune",
|
||||
@@ -985,6 +985,6 @@
|
||||
"has_docker": false,
|
||||
"wired": true,
|
||||
"description": "",
|
||||
"discovered": "2026-04-22T02:00:03.637176"
|
||||
"discovered": "2026-04-22T04:00:06.119096"
|
||||
}
|
||||
]
|
||||
83
api/playwright-v163-latest.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"ts": "2026-04-22T02-15-15-124Z",
|
||||
"version": "V163",
|
||||
"tests": [
|
||||
{
|
||||
"name": "load_login",
|
||||
"pass": true,
|
||||
"status": 200
|
||||
},
|
||||
{
|
||||
"name": "manual_toggle",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "login_submit",
|
||||
"pass": true,
|
||||
"url": "https://weval-consulting.com/products/workspace.html"
|
||||
},
|
||||
{
|
||||
"name": "v162_panel_dom",
|
||||
"pass": true,
|
||||
"panel": true,
|
||||
"stages": 7,
|
||||
"body": true,
|
||||
"toggle": true
|
||||
},
|
||||
{
|
||||
"name": "panel_default_hidden",
|
||||
"pass": true
|
||||
},
|
||||
{
|
||||
"name": "all_stages_reached",
|
||||
"pass": true,
|
||||
"state": [
|
||||
{
|
||||
"stage": "plan",
|
||||
"active": false,
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"stage": "prepare",
|
||||
"active": false,
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"stage": "code",
|
||||
"active": false,
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"stage": "test",
|
||||
"active": false,
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"stage": "commit",
|
||||
"active": false,
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"stage": "wiki",
|
||||
"active": false,
|
||||
"done": true
|
||||
},
|
||||
{
|
||||
"stage": "rag",
|
||||
"active": true,
|
||||
"done": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "exception",
|
||||
"pass": false,
|
||||
"error": "page.click: Timeout 30000ms exceeded.\nCall log:\n - waiting for locator('#thpToggle')\n - locator resolved to <button type=\"button\" id=\"thpToggle\" class=\"thp-toggle\" aria-label=\"Toggle thinking\">Collapse</button>\n - attempting click action\n 2 × waiting for element to be visible, enabled and stable\n - element is visible, enabled and stable\n - scrolling into view if needed\n - do"
|
||||
}
|
||||
],
|
||||
"video": "/var/www/html/api/playwright-results/v163-wevia-master-thinking-2026-04-22T02-15-15-124Z/page@4034ae1981d48ad0fcae879bccd452dd.webm",
|
||||
"screenshots_dir": "/var/www/html/api/playwright-results/v163-wevia-master-thinking-2026-04-22T02-15-15-124Z",
|
||||
"pass_total": 6,
|
||||
"fail_total": 1,
|
||||
"all_pass": false
|
||||
}
|
||||
BIN
api/playwright-videos/v163-wevia-master-thinking.webm
Normal file
@@ -1,16 +1,15 @@
|
||||
<?php
|
||||
// WAVE 230 v3 · Social Signals Hub · FAST parallel + reliable sources
|
||||
// WAVE 233 v6 · Ask WEVIA + 5 Reddit subs + lead linking + CSV export
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
set_time_limit(20);
|
||||
set_time_limit(25);
|
||||
|
||||
function load_secrets() {
|
||||
$s = [];
|
||||
if (!is_readable('/etc/weval/secrets.env')) return $s;
|
||||
foreach (file('/etc/weval/secrets.env', FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) as $l) {
|
||||
if (empty(trim($l))||$l[0]==='#') continue;
|
||||
$p = strpos($l,'=');
|
||||
if ($p) $s[trim(substr($l,0,$p))] = trim(substr($l,$p+1)," \t\"'");
|
||||
$p = strpos($l,'='); if ($p) $s[trim(substr($l,0,$p))] = trim(substr($l,$p+1)," \t\"'");
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
@@ -27,37 +26,442 @@ function multi_fetch($urls, $timeout=7) {
|
||||
$running = null;
|
||||
do { curl_multi_exec($mh, $running); curl_multi_select($mh, 0.1); } while ($running > 0);
|
||||
$out = [];
|
||||
foreach ($handles as $k => $ch) {
|
||||
$out[$k] = curl_multi_getcontent($ch);
|
||||
curl_multi_remove_handle($mh, $ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
foreach ($handles as $k => $ch) { $out[$k] = curl_multi_getcontent($ch); curl_multi_remove_handle($mh, $ch); curl_close($ch); }
|
||||
curl_multi_close($mh);
|
||||
return $out;
|
||||
}
|
||||
|
||||
function pg_c() {
|
||||
return @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=3');
|
||||
}
|
||||
|
||||
function link_lead($opportunity) {
|
||||
// WAVE 233: match opportunity string to weval_leads.id via slug
|
||||
if (empty($opportunity)) return null;
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=2');
|
||||
if (!$pg) return null;
|
||||
$opp_lower = strtolower($opportunity);
|
||||
$r = @pg_query($pg, 'SELECT id, slug, company, mql_score FROM weval_leads ORDER BY mql_score DESC LIMIT 50');
|
||||
$match = null;
|
||||
if ($r) while ($row = pg_fetch_assoc($r)) {
|
||||
$co = strtolower($row['company'] ?? '');
|
||||
$sl = strtolower($row['slug'] ?? '');
|
||||
$co_first = explode(' ', $co)[0] ?? '';
|
||||
if ($sl && strpos($opp_lower, $sl) !== false) { $match = $row; break; }
|
||||
if ($co_first && strlen($co_first) > 3 && strpos($opp_lower, $co_first) !== false) { $match = $row; break; }
|
||||
}
|
||||
pg_close($pg);
|
||||
return $match;
|
||||
}
|
||||
|
||||
|
||||
// ====================================================================
|
||||
// WAVES 234-245 MEGA BUNDLE
|
||||
// ====================================================================
|
||||
$action = $_GET['action'] ?? '';
|
||||
// === WAVE action: kanban ===
|
||||
if ($action === 'kanban') {
|
||||
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
|
||||
$r = @pg_query($pg, 'SELECT t.*, l.company AS lead_company, l.mql_score AS lead_mql FROM weval_tasks t LEFT JOIN weval_leads l ON t.lead_id=l.id ORDER BY t.created_at DESC');
|
||||
$kanban = ['proposed'=>[], 'in_progress'=>[], 'done'=>[], 'cancelled'=>[], 'blocked'=>[]];
|
||||
$mad_by_status = ['proposed'=>0,'in_progress'=>0,'done'=>0,'cancelled'=>0,'blocked'=>0];
|
||||
if ($r) while ($row = pg_fetch_assoc($r)) {
|
||||
$s = $row['status'] ?? 'proposed';
|
||||
if (!isset($kanban[$s])) $kanban[$s] = [];
|
||||
$kanban[$s][] = $row;
|
||||
$mad_by_status[$s] = ($mad_by_status[$s] ?? 0) + (int)($row['estimated_mad'] ?? 0);
|
||||
}
|
||||
pg_close($pg);
|
||||
echo json_encode(['ok'=>true, 'wave'=>234, 'columns'=>$kanban, 'mad_by_status'=>$mad_by_status]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === WAVE action: bluesky ===
|
||||
if ($action === 'bluesky') {
|
||||
$q = $_GET['q'] ?? 'SaaS conversion';
|
||||
// Bluesky public search endpoint
|
||||
$url = 'https://public.api.bsky.app/xrpc/app.bsky.feed.searchPosts?q=' . urlencode($q) . '&limit=10';
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>8, CURLOPT_USERAGENT=>'weval-bot']);
|
||||
$raw = curl_exec($ch); curl_close($ch);
|
||||
$d = @json_decode($raw, true);
|
||||
$items = [];
|
||||
foreach (($d['posts'] ?? []) as $p) {
|
||||
$items[] = [
|
||||
'title' => substr($p['record']['text'] ?? '', 0, 200),
|
||||
'author' => '@' . ($p['author']['handle'] ?? '?'),
|
||||
'url' => 'https://bsky.app/profile/' . ($p['author']['handle'] ?? '') . '/post/' . (explode('/', $p['uri'] ?? '')[4] ?? ''),
|
||||
'likes' => (int)($p['likeCount'] ?? 0),
|
||||
'reposts' => (int)($p['repostCount'] ?? 0),
|
||||
'replies' => (int)($p['replyCount'] ?? 0),
|
||||
'date' => substr($p['record']['createdAt'] ?? '', 0, 10),
|
||||
];
|
||||
}
|
||||
usort($items, function($a,$b){return ($b['likes']??0)-($a['likes']??0);});
|
||||
echo json_encode(['ok'=>true, 'wave'=>235, 'query'=>$q, 'count'=>count($items), 'items'=>array_slice($items, 0, 10)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === WAVE action: kpi_dashboard ===
|
||||
if ($action === 'kpi_dashboard') {
|
||||
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
|
||||
$kpi = ['wave'=>236, 'ts'=>date('c')];
|
||||
// Leads stats
|
||||
$r = @pg_query($pg, 'SELECT COUNT(*) AS n, SUM(CASE WHEN sql_qualified THEN 1 ELSE 0 END) AS sql_q, ROUND(AVG(mql_score)) AS avg_mql FROM weval_leads');
|
||||
if ($r) { $kpi['leads'] = pg_fetch_assoc($r); }
|
||||
// Leads by status
|
||||
$r2 = @pg_query($pg, 'SELECT status, COUNT(*) AS n FROM weval_leads GROUP BY status');
|
||||
$kpi['leads_by_status'] = []; if ($r2) while ($row = pg_fetch_assoc($r2)) $kpi['leads_by_status'][$row['status']] = (int)$row['n'];
|
||||
// Leads by country
|
||||
$r3 = @pg_query($pg, 'SELECT country, COUNT(*) AS n FROM weval_leads GROUP BY country ORDER BY n DESC LIMIT 10');
|
||||
$kpi['leads_by_country'] = []; if ($r3) while ($row = pg_fetch_assoc($r3)) $kpi['leads_by_country'][$row['country'] ?: '?'] = (int)$row['n'];
|
||||
// Tasks stats
|
||||
$r4 = @pg_query($pg, 'SELECT COUNT(*) AS n, SUM(estimated_mad) AS total_mad FROM weval_tasks');
|
||||
if ($r4) { $kpi['tasks'] = pg_fetch_assoc($r4); }
|
||||
// Tasks by status + MAD
|
||||
$r5 = @pg_query($pg, 'SELECT status, COUNT(*) AS n, SUM(estimated_mad) AS mad FROM weval_tasks GROUP BY status');
|
||||
$kpi['tasks_by_status'] = []; if ($r5) while ($row = pg_fetch_assoc($r5)) $kpi['tasks_by_status'][$row['status']] = ['count'=>(int)$row['n'], 'mad'=>(int)$row['mad']];
|
||||
// Top industries
|
||||
$r6 = @pg_query($pg, 'SELECT industry, COUNT(*) AS n FROM weval_leads WHERE industry IS NOT NULL GROUP BY industry ORDER BY n DESC LIMIT 8');
|
||||
$kpi['industries'] = []; if ($r6) while ($row = pg_fetch_assoc($r6)) $kpi['industries'][$row['industry']] = (int)$row['n'];
|
||||
// Tasks created last 7 days (evolution)
|
||||
$r7 = @pg_query($pg, "SELECT DATE(created_at) AS d, COUNT(*) AS n FROM weval_tasks WHERE created_at > NOW() - INTERVAL '7 days' GROUP BY d ORDER BY d");
|
||||
$kpi['tasks_last_7d'] = []; if ($r7) while ($row = pg_fetch_assoc($r7)) $kpi['tasks_last_7d'][] = ['date'=>$row['d'], 'count'=>(int)$row['n']];
|
||||
pg_close($pg);
|
||||
echo json_encode($kpi);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === WAVE action: enrich_lead ===
|
||||
if ($action === 'enrich_lead') {
|
||||
$lead_id = (int)($_GET['lead_id'] ?? 0);
|
||||
if (!$lead_id) { echo json_encode(['error'=>'no lead_id']); exit; }
|
||||
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
|
||||
$r = @pg_query_params($pg, 'SELECT * FROM weval_leads WHERE id=$1', [$lead_id]);
|
||||
$lead = $r ? pg_fetch_assoc($r) : null;
|
||||
if (!$lead) { pg_close($pg); echo json_encode(['error'=>'lead not found']); exit; }
|
||||
pg_close($pg);
|
||||
|
||||
// Query Dark Scout for intel about lead company
|
||||
$q = $lead['company'] . ' ' . ($lead['industry'] ?: '') . ' ' . ($lead['country'] ?: '');
|
||||
$ch = curl_init('http://127.0.0.1/api/v83-dark-scout-enriched.php?q=' . urlencode($q));
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>12]);
|
||||
$ds_raw = curl_exec($ch); curl_close($ch);
|
||||
$scout_results = []; if ($ds_raw) { $sd = @json_decode($ds_raw, true); $scout_results = array_slice(($sd['results']??[]), 0, 6); }
|
||||
|
||||
echo json_encode(['ok'=>true, 'wave'=>238, 'lead'=>$lead, 'scout_intel'=>$scout_results, 'intel_count'=>count($scout_results)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === WAVE action: roi_calc ===
|
||||
if ($action === 'roi_calc') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$mad = (int)($body['estimated_mad'] ?? 0);
|
||||
$effort_days = (int)($body['effort_days'] ?? 14);
|
||||
$effort_cost_per_day = 1500; // MAD per dev/consultant day
|
||||
$effort_cost = $effort_days * $effort_cost_per_day;
|
||||
$net_roi = $mad - $effort_cost;
|
||||
$roi_pct = $effort_cost > 0 ? round(($net_roi / $effort_cost) * 100, 1) : 0;
|
||||
$confidence = 'medium';
|
||||
if ($mad >= 200000 && $effort_days <= 14) $confidence = 'high';
|
||||
elseif ($mad < 50000 || $effort_days > 45) $confidence = 'low';
|
||||
echo json_encode(['ok'=>true, 'wave'=>239,
|
||||
'estimated_mad'=>$mad,
|
||||
'effort_cost_mad'=>$effort_cost,
|
||||
'effort_days'=>$effort_days,
|
||||
'net_roi_mad'=>$net_roi,
|
||||
'roi_pct'=>$roi_pct,
|
||||
'confidence'=>$confidence,
|
||||
'break_even_days'=>$effort_cost > 0 && $mad > 0 ? round($effort_days * ($effort_cost / $mad), 1) : 0
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === WAVE action: export_json ===
|
||||
if ($action === 'export_json') {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="weval_tasks_' . date('Ymd') . '.json"');
|
||||
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
|
||||
$r = @pg_query($pg, 'SELECT t.*, l.company AS linked_lead, l.mql_score AS lead_mql FROM weval_tasks t LEFT JOIN weval_leads l ON t.lead_id=l.id ORDER BY t.created_at DESC');
|
||||
$tasks = []; if ($r) while ($row = pg_fetch_assoc($r)) $tasks[] = $row;
|
||||
pg_close($pg);
|
||||
echo json_encode(['ts'=>date('c'), 'count'=>count($tasks), 'tasks'=>$tasks], JSON_PRETTY_PRINT);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === WAVE action: score_opportunity ===
|
||||
if ($action === 'score_opportunity') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$effort = max(1, min(10, (int)($body['effort'] ?? 5)));
|
||||
$impact = max(1, min(10, (int)($body['impact'] ?? 5)));
|
||||
$mad = (int)($body['estimated_mad'] ?? 0);
|
||||
// ICE score: (impact * MAD/10K) / effort
|
||||
$ice_score = round(($impact * ($mad / 10000)) / $effort, 1);
|
||||
// Priority: HIGH (>50), MEDIUM (20-50), LOW (<20)
|
||||
$priority = $ice_score >= 50 ? 'HIGH' : ($ice_score >= 20 ? 'MEDIUM' : 'LOW');
|
||||
$quadrant = $effort <= 3 && $impact >= 7 ? 'QUICK_WIN' :
|
||||
($effort >= 4 && $impact >= 7 ? 'BIG_BET' :
|
||||
($effort <= 3 && $impact < 7 ? 'FILL_IN' : 'THANKLESS'));
|
||||
echo json_encode(['ok'=>true, 'wave'=>241, 'effort'=>$effort, 'impact'=>$impact, 'estimated_mad'=>$mad,
|
||||
'ice_score'=>$ice_score, 'priority'=>$priority, 'quadrant'=>$quadrant
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === WAVE action: pipeline_stages ===
|
||||
if ($action === 'pipeline_stages') {
|
||||
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
|
||||
// Derive stages from task status + lead status
|
||||
$stages = [
|
||||
'qualification' => ['name'=>'Qualification', 'count'=>0, 'mad'=>0, 'color'=>'#94a3b8'],
|
||||
'discovery' => ['name'=>'Discovery', 'count'=>0, 'mad'=>0, 'color'=>'#fbbf24'],
|
||||
'proposal' => ['name'=>'Proposal', 'count'=>0, 'mad'=>0, 'color'=>'#22d3ee'],
|
||||
'negotiation' => ['name'=>'Negotiation', 'count'=>0, 'mad'=>0, 'color'=>'#a855f7'],
|
||||
'closed_won' => ['name'=>'Closed Won', 'count'=>0, 'mad'=>0, 'color'=>'#10b981'],
|
||||
'closed_lost' => ['name'=>'Closed Lost', 'count'=>0, 'mad'=>0, 'color'=>'#ef4444'],
|
||||
];
|
||||
// Map task.status → stage
|
||||
$map = ['proposed'=>'discovery', 'in_progress'=>'negotiation', 'done'=>'closed_won', 'cancelled'=>'closed_lost', 'blocked'=>'proposal'];
|
||||
$r = @pg_query($pg, 'SELECT status, COUNT(*) AS n, SUM(estimated_mad) AS mad FROM weval_tasks GROUP BY status');
|
||||
if ($r) while ($row = pg_fetch_assoc($r)) {
|
||||
$stage = $map[$row['status']] ?? 'qualification';
|
||||
$stages[$stage]['count'] += (int)$row['n'];
|
||||
$stages[$stage]['mad'] += (int)$row['mad'];
|
||||
}
|
||||
pg_close($pg);
|
||||
echo json_encode(['ok'=>true, 'wave'=>242, 'stages'=>$stages]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === WAVE action: activity_timeline ===
|
||||
if ($action === 'activity_timeline') {
|
||||
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
|
||||
// Unified timeline: leads + tasks events
|
||||
$events = [];
|
||||
$r = @pg_query($pg, 'SELECT created_at, id, title, status, lead_id, estimated_mad FROM weval_tasks ORDER BY created_at DESC LIMIT 15');
|
||||
if ($r) while ($row = pg_fetch_assoc($r)) {
|
||||
$events[] = ['ts'=>$row['created_at'], 'type'=>'task_created', 'title'=>$row['title'], 'id'=>$row['id'], 'meta'=>['status'=>$row['status'], 'mad'=>$row['estimated_mad']]];
|
||||
}
|
||||
$r2 = @pg_query($pg, 'SELECT created_at, id, company, mql_score, status FROM weval_leads ORDER BY created_at DESC LIMIT 10');
|
||||
if ($r2) while ($row = pg_fetch_assoc($r2)) {
|
||||
$events[] = ['ts'=>$row['created_at'], 'type'=>'lead_created', 'title'=>$row['company'], 'id'=>$row['id'], 'meta'=>['mql'=>$row['mql_score'], 'status'=>$row['status']]];
|
||||
}
|
||||
pg_close($pg);
|
||||
usort($events, function($a,$b){ return strcmp($b['ts'], $a['ts']); });
|
||||
echo json_encode(['ok'=>true, 'wave'=>243, 'count'=>count($events), 'events'=>array_slice($events, 0, 20)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === WAVE action: search_tasks ===
|
||||
if ($action === 'search_tasks') {
|
||||
$pg = pg_c(); if (!$pg) { echo json_encode(['error'=>'no pg']); exit; }
|
||||
$q = trim($_GET['q'] ?? '');
|
||||
$filter_status = $_GET['status'] ?? '';
|
||||
$min_mad = (int)($_GET['min_mad'] ?? 0);
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
$i = 1;
|
||||
if ($q) { $where[] = "(t.title ILIKE \$$i OR t.opportunity ILIKE \$$i)"; $params[] = '%' . $q . '%'; $i++; }
|
||||
if ($filter_status) { $where[] = "t.status = \$$i"; $params[] = $filter_status; $i++; }
|
||||
if ($min_mad) { $where[] = "t.estimated_mad >= \$$i"; $params[] = $min_mad; $i++; }
|
||||
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT t.*, l.company AS lead_company, l.mql_score AS lead_mql FROM weval_tasks t LEFT JOIN weval_leads l ON t.lead_id=l.id $where_sql ORDER BY t.created_at DESC LIMIT 50";
|
||||
$r = $params ? @pg_query_params($pg, $sql, $params) : @pg_query($pg, $sql);
|
||||
$tasks = []; if ($r) while ($row = pg_fetch_assoc($r)) $tasks[] = $row;
|
||||
pg_close($pg);
|
||||
echo json_encode(['ok'=>true, 'wave'=>244, 'query'=>$q, 'count'=>count($tasks), 'tasks'=>$tasks]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === WAVE action: bundle_manifest ===
|
||||
if ($action === 'bundle_manifest') {
|
||||
echo json_encode(['ok'=>true, 'wave'=>245, 'version'=>'mega-bundle-234-245',
|
||||
'endpoints' => [
|
||||
'kanban' => 'Kanban board view grouped by status',
|
||||
'bluesky' => 'Bluesky AT Protocol search (replace Twitter)',
|
||||
'kpi_dashboard' => 'Consolidated KPI dashboard (leads + tasks + countries + industries)',
|
||||
'enrich_lead' => 'Lead enrichment via Dark Scout intel',
|
||||
'roi_calc' => 'ROI calculator auto per idea',
|
||||
'export_json' => 'Full export JSON attachment',
|
||||
'score_opportunity' => 'ICE auto-scoring + quadrant classification',
|
||||
'pipeline_stages' => '6 deal pipeline stages (Qualification → Closed)',
|
||||
'activity_timeline' => 'Unified events timeline (leads + tasks)',
|
||||
'search_tasks' => 'Search + filter tasks (status, MAD, text)',
|
||||
'bundle_manifest' => 'This capabilities listing'
|
||||
],
|
||||
'features_count' => 12,
|
||||
'waves' => '234-245',
|
||||
'session_date' => '2026-04-22'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
// === POST create_task with lead linking (WAVE 233) ===
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_GET['action'] ?? '') === 'create_task') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=3');
|
||||
if (!$pg) { http_response_code(500); echo json_encode(['error'=>'no pg']); exit; }
|
||||
|
||||
// WAVE 233: Auto-link lead
|
||||
$lead_match = link_lead($body['opportunity'] ?? '');
|
||||
$lead_id = $lead_match ? (int)$lead_match['id'] : null;
|
||||
|
||||
$q = "INSERT INTO weval_tasks (title, source, source_ref, category, opportunity, tools_used, first_steps, kpi, estimated_mad, inspired_by, status, wave, lead_id) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13) RETURNING id, created_at";
|
||||
$r = @pg_query_params($pg, $q, [
|
||||
$body['title']??'?', $body['source']??'advisor-wave233', $body['source_ref']??'',
|
||||
$body['category']??'conversion', $body['opportunity']??'',
|
||||
is_array($body['tools_used']??null)?implode('|',$body['tools_used']):($body['tools_used']??''),
|
||||
is_array($body['first_steps']??null)?implode("\n- ",$body['first_steps']):($body['first_steps']??''),
|
||||
$body['kpi']??'', (int)($body['estimated_mad']??0), $body['inspired_by']??'', 'proposed', 233, $lead_id
|
||||
]);
|
||||
if ($r) {
|
||||
$row = pg_fetch_assoc($r);
|
||||
pg_close($pg);
|
||||
echo json_encode(['ok'=>true, 'task_id'=>(int)$row['id'], 'created_at'=>$row['created_at'], 'lead_linked'=>$lead_match]);
|
||||
} else { pg_close($pg); http_response_code(500); echo json_encode(['error'=>'insert failed', 'details'=>pg_last_error()]); }
|
||||
exit;
|
||||
}
|
||||
|
||||
// === PATCH update_status ===
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_GET['action'] ?? '') === 'update_status') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$task_id = (int)($body['task_id'] ?? 0);
|
||||
$new_status = $body['status'] ?? '';
|
||||
$allowed = ['proposed','in_progress','done','cancelled','blocked'];
|
||||
if (!$task_id || !in_array($new_status, $allowed)) { http_response_code(400); echo json_encode(['error'=>'invalid']); exit; }
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=3');
|
||||
if (!$pg) { http_response_code(500); echo json_encode(['error'=>'no pg']); exit; }
|
||||
$r = @pg_query_params($pg, 'UPDATE weval_tasks SET status=$1 WHERE id=$2 RETURNING id, status', [$new_status, $task_id]);
|
||||
if ($r && ($row = pg_fetch_assoc($r))) { pg_close($pg); echo json_encode(['ok'=>true, 'task_id'=>(int)$row['id'], 'new_status'=>$row['status']]); }
|
||||
else { pg_close($pg); http_response_code(404); echo json_encode(['error'=>'not found']); }
|
||||
exit;
|
||||
}
|
||||
|
||||
// === GET list_tasks with lead JOIN (WAVE 233) ===
|
||||
if (($_GET['action'] ?? '') === 'list_tasks') {
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=3');
|
||||
if (!$pg) { echo json_encode(['error'=>'no pg', 'tasks'=>[]]); exit; }
|
||||
// LEFT JOIN with weval_leads
|
||||
$r = @pg_query($pg, '
|
||||
SELECT t.*, l.slug AS lead_slug, l.company AS lead_company, l.mql_score AS lead_mql, l.sql_qualified AS lead_sql
|
||||
FROM weval_tasks t
|
||||
LEFT JOIN weval_leads l ON t.lead_id = l.id
|
||||
ORDER BY t.created_at DESC LIMIT 20
|
||||
');
|
||||
$tasks = []; if ($r) while ($row = pg_fetch_assoc($r)) $tasks[] = $row;
|
||||
$agg = []; foreach ($tasks as $t) { $s = $t['status']??'?'; $agg[$s] = ($agg[$s]??0)+1; }
|
||||
$linked = count(array_filter($tasks, function($t){return !empty($t['lead_id']);}));
|
||||
pg_close($pg);
|
||||
echo json_encode(['ok'=>true, 'count'=>count($tasks), 'by_status'=>$agg, 'linked_count'=>$linked, 'tasks'=>$tasks]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === GET export_tasks_csv (WAVE 233) ===
|
||||
if (($_GET['action'] ?? '') === 'export_csv') {
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="weval_tasks_' . date('Ymd') . '.csv"');
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=3');
|
||||
if (!$pg) { echo "error,no pg\n"; exit; }
|
||||
$r = @pg_query($pg, 'SELECT t.id, t.title, t.status, t.opportunity, t.estimated_mad, t.tools_used, t.kpi, t.created_at, l.company AS linked_lead, l.mql_score AS lead_mql FROM weval_tasks t LEFT JOIN weval_leads l ON t.lead_id = l.id ORDER BY t.created_at DESC');
|
||||
$out = fopen('php://output', 'w');
|
||||
fputcsv($out, ['id','title','status','opportunity','estimated_mad','tools_used','kpi','created_at','linked_lead','lead_mql']);
|
||||
while ($row = pg_fetch_assoc($r)) fputcsv($out, $row);
|
||||
fclose($out);
|
||||
pg_close($pg);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === POST ask_wevia (WAVE 233) ===
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_GET['action'] ?? '') === 'ask_wevia') {
|
||||
$body = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$idea = $body['idea'] ?? [];
|
||||
$q = "Contextualisé par cette idea de conversion:\n" .
|
||||
"Titre: " . ($idea['title'] ?? '?') . "\n" .
|
||||
"Opportunité: " . ($idea['opportunity'] ?? '?') . "\n" .
|
||||
"Tools: " . (is_array($idea['tools_used']??null) ? implode(', ', $idea['tools_used']) : ($idea['tools_used'] ?? '?')) . "\n" .
|
||||
"KPI: " . ($idea['kpi'] ?? '?') . "\n" .
|
||||
"MAD est: " . ($idea['estimated_mad'] ?? 0) . "\n" .
|
||||
"Détaille un plan exécutable 14j step-by-step avec multi-agents WEVAL.";
|
||||
|
||||
// Query WEVIA Master via saas-chat.php
|
||||
$ch = curl_init('http://127.0.0.1/api/saas-chat.php');
|
||||
$payload = json_encode(['message' => $q]);
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_POST=>true, CURLOPT_TIMEOUT=>30, CURLOPT_HTTPHEADER=>['Content-Type: application/json'], CURLOPT_POSTFIELDS=>$payload]);
|
||||
$r = curl_exec($ch); $c = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
|
||||
|
||||
if ($c === 200) {
|
||||
echo json_encode(['ok'=>true, 'wevia_response'=>json_decode($r, true) ?: $r, 'query'=>$q]);
|
||||
} else {
|
||||
// Fallback: direct LLM query
|
||||
$secrets = load_secrets();
|
||||
$payload2 = json_encode(['model'=>'mistral-small-latest','messages'=>[['role'=>'system','content'=>'Tu es WEVIA Master, assistant multi-agents WEVAL Consulting. Réponds en FR compact.'],['role'=>'user','content'=>$q]],'max_tokens'=>1500,'temperature'=>0.3]);
|
||||
$ch = curl_init('https://api.mistral.ai/v1/chat/completions');
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_POST=>true, CURLOPT_TIMEOUT=>25, CURLOPT_HTTPHEADER=>['Content-Type: application/json','Authorization: Bearer '.($secrets['MISTRAL_KEY']??'')], CURLOPT_POSTFIELDS=>$payload2]);
|
||||
$rf = curl_exec($ch); $cf = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
|
||||
$text = '';
|
||||
if ($cf === 200) { $d = json_decode($rf, true); $text = $d['choices'][0]['message']['content'] ?? ''; }
|
||||
echo json_encode(['ok'=>(bool)$text, 'fallback'=>'Mistral direct', 'wevia_response'=>$text, 'query'=>$q]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// === SSE stream ===
|
||||
if (($_GET['action'] ?? '') === 'stream') {
|
||||
header('Content-Type: text/event-stream');
|
||||
header('Cache-Control: no-cache');
|
||||
header('X-Accel-Buffering: no');
|
||||
@ob_end_flush();
|
||||
$send = function($event, $data) { echo "event: $event\n"; echo "data: " . json_encode($data) . "\n\n"; @ob_flush(); flush(); };
|
||||
$send('hello', ['wave'=>233, 'msg'=>'SSE social stream live', 'ts'=>date('c')]);
|
||||
$channels = ['linkedin'=>'http://127.0.0.1/api/linkedin-posts.php', 'hackernews'=>'https://hn.algolia.com/api/v1/search?query='.urlencode('SaaS conversion').'&tags=story&hitsPerPage=5', 'reddit'=>'https://old.reddit.com/r/SaaS/.rss?limit=5'];
|
||||
foreach ($channels as $name => $url) {
|
||||
$ch = curl_init($url); curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>5, CURLOPT_USERAGENT=>'weval-bot']);
|
||||
$raw = curl_exec($ch); curl_close($ch);
|
||||
$count = 0; $top = '';
|
||||
if ($name === 'linkedin') { $d = @json_decode($raw, true); if (isset($d['posts'])) { $count = count($d['posts']); $top = $d['posts'][0]['title'] ?? ''; } }
|
||||
elseif ($name === 'hackernews') { $d = @json_decode($raw, true); if (isset($d['hits'])) { $count = count($d['hits']); $top = $d['hits'][0]['title'] ?? ''; } }
|
||||
elseif ($name === 'reddit') { $xml = @simplexml_load_string($raw); if ($xml) { $entries = $xml->entry ?? []; $count = count($entries); $top = $count ? (string)$entries[0]->title : ''; } }
|
||||
$send('channel', ['name'=>$name, 'count'=>$count, 'top'=>$top, 'ts'=>date('c')]);
|
||||
}
|
||||
$pg = @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=3');
|
||||
if ($pg) {
|
||||
$r = @pg_query($pg, 'SELECT status, COUNT(*) AS n FROM weval_tasks GROUP BY status');
|
||||
$agg = []; if ($r) while ($row = pg_fetch_assoc($r)) $agg[$row['status']] = (int)$row['n'];
|
||||
pg_close($pg);
|
||||
$send('tasks', ['by_status'=>$agg, 'ts'=>date('c')]);
|
||||
}
|
||||
$send('done', ['total_channels'=>count($channels), 'ts'=>date('c')]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// === Default aggregation (Reddit 5 subs WAVE 233) ===
|
||||
$topics = $_GET['topics'] ?? 'B2B SaaS conversion,LinkedIn outbound,pharma digital';
|
||||
$topic_list = array_slice(array_map('trim', explode(',', $topics)), 0, 3);
|
||||
$with_scout = ($_GET['scout'] ?? '') === '1';
|
||||
|
||||
$signals = [
|
||||
'ts' => date('c'), 'wave' => 230, 'version' => 'social-signals-hub-v3',
|
||||
'topics' => $topic_list, 'channels' => [], 'aggregated_ideas' => [],
|
||||
];
|
||||
$signals = ['ts'=>date('c'), 'wave'=>233, 'version'=>'social-signals-hub-v6', 'topics'=>$topic_list, 'channels'=>[], 'aggregated_ideas'=>[]];
|
||||
|
||||
// Parallel URLs
|
||||
$urls = [];
|
||||
$urls['linkedin'] = 'http://127.0.0.1/api/linkedin-posts.php';
|
||||
// HackerNews
|
||||
foreach (['SaaS conversion', 'B2B sales outbound'] as $i => $q) {
|
||||
$urls['hn_'.$i] = 'https://hn.algolia.com/api/v1/search?query=' . urlencode($q) . '&tags=story&hitsPerPage=6';
|
||||
}
|
||||
// Reddit RSS (atom)
|
||||
foreach (['SaaS', 'Entrepreneur', 'B2BSales'] as $i => $s) {
|
||||
$urls['rd_'.$i] = 'https://old.reddit.com/r/' . $s . '/.rss?limit=5';
|
||||
// WAVE 233: 5 Reddit subs (was 3)
|
||||
$reddit_subs = ['SaaS', 'Entrepreneur', 'B2BSales', 'startups', 'marketing'];
|
||||
foreach ($reddit_subs as $i => $s) {
|
||||
$urls['rd_'.$i] = 'https://old.reddit.com/r/' . $s . '/.rss?limit=4';
|
||||
}
|
||||
// YouTube via Piped.video (alt Invidious)
|
||||
foreach (array_slice($topic_list, 0, 2) as $i => $t) {
|
||||
$urls['yt_'.$i] = 'https://html.duckduckgo.com/html/?q=' . urlencode($t . ' youtube 2026');
|
||||
$urls['hn_yt'] = 'https://hn.algolia.com/api/v1/search?query=' . urlencode('youtube.com SaaS') . '&tags=story&hitsPerPage=10';
|
||||
// Mastodon 5 instances (kept from wave 232)
|
||||
$mast_hosts = ['mastodon.social','mstdn.social','fosstodon.org','hachyderm.io','techhub.social'];
|
||||
foreach ($mast_hosts as $i => $h) {
|
||||
$urls['ma_'.$i] = 'https://' . $h . '/api/v2/search?q=' . urlencode($topic_list[0] ?? 'SaaS') . '&type=statuses&limit=3';
|
||||
}
|
||||
|
||||
$t0 = microtime(true);
|
||||
@@ -68,35 +472,20 @@ $signals['fetch_duration_s'] = round(microtime(true) - $t0, 2);
|
||||
$ln = ['channel'=>'linkedin','source'=>'internal-db','items'=>[]];
|
||||
if (!empty($results['linkedin'])) {
|
||||
$ld = @json_decode($results['linkedin'], true);
|
||||
if (isset($ld['posts'])) {
|
||||
foreach (array_slice($ld['posts'], 0, 8) as $p) {
|
||||
$ln['items'][] = [
|
||||
'title' => $p['title'] ?? '',
|
||||
'excerpt' => substr($p['excerpt'] ?? '', 0, 160),
|
||||
'likes' => (int)($p['likes'] ?? 0),
|
||||
'views' => (int)($p['views'] ?? 0),
|
||||
'url' => $p['linkedin_url'] ?? '',
|
||||
'date' => $p['post_date'] ?? '',
|
||||
];
|
||||
}
|
||||
if (isset($ld['posts'])) foreach (array_slice($ld['posts'], 0, 8) as $p) {
|
||||
$ln['items'][] = ['title'=>$p['title']??'','excerpt'=>substr($p['excerpt']??'',0,150),'likes'=>(int)($p['likes']??0),'views'=>(int)($p['views']??0),'url'=>$p['linkedin_url']??'','date'=>$p['post_date']??''];
|
||||
}
|
||||
}
|
||||
$ln['count'] = count($ln['items']);
|
||||
$signals['channels']['linkedin'] = $ln;
|
||||
|
||||
// HackerNews
|
||||
// HN
|
||||
$hn = ['channel'=>'hackernews','source'=>'Algolia API','items'=>[]];
|
||||
foreach ([0,1] as $i) {
|
||||
if (empty($results['hn_'.$i])) continue;
|
||||
$hd = @json_decode($results['hn_'.$i], true);
|
||||
foreach (($hd['hits'] ?? []) as $h) {
|
||||
$hn['items'][] = [
|
||||
'title' => substr($h['title'] ?? '', 0, 140),
|
||||
'points' => (int)($h['points'] ?? 0),
|
||||
'comments' => (int)($h['num_comments'] ?? 0),
|
||||
'url' => $h['url'] ?? ('https://news.ycombinator.com/item?id=' . ($h['objectID'] ?? '')),
|
||||
'date' => substr($h['created_at'] ?? '', 0, 10),
|
||||
];
|
||||
$hn['items'][] = ['title'=>substr($h['title']??'',0,140),'points'=>(int)($h['points']??0),'comments'=>(int)($h['num_comments']??0),'url'=>$h['url']??('https://news.ycombinator.com/item?id='.($h['objectID']??'')),'date'=>substr($h['created_at']??'',0,10)];
|
||||
}
|
||||
}
|
||||
usort($hn['items'], function($a,$b){return ($b['points']??0)-($a['points']??0);});
|
||||
@@ -104,73 +493,82 @@ $hn['items'] = array_slice($hn['items'], 0, 10);
|
||||
$hn['count'] = count($hn['items']);
|
||||
$signals['channels']['hackernews'] = $hn;
|
||||
|
||||
// Reddit RSS parse
|
||||
$rd = ['channel'=>'reddit','source'=>'old.reddit RSS','items'=>[]];
|
||||
foreach ([0,1,2] as $i) {
|
||||
// Reddit 5 subs (WAVE 233)
|
||||
$rd = ['channel'=>'reddit','source'=>'5 subs RSS (SaaS+Entr+B2B+startups+marketing)','items'=>[]];
|
||||
foreach (range(0,4) as $i) {
|
||||
if (empty($results['rd_'.$i])) continue;
|
||||
$xml = @simplexml_load_string($results['rd_'.$i]);
|
||||
if (!$xml) continue;
|
||||
$sub = ['SaaS','Entrepreneur','B2BSales'][$i];
|
||||
$sub = $reddit_subs[$i];
|
||||
foreach ($xml->entry ?? [] as $entry) {
|
||||
$title = (string)$entry->title;
|
||||
$link = (string)$entry->link['href'];
|
||||
if ($title && $link) {
|
||||
$rd['items'][] = [
|
||||
'title' => substr($title, 0, 140),
|
||||
'subreddit' => 'r/' . $sub,
|
||||
'url' => $link,
|
||||
'date' => substr((string)$entry->updated, 0, 10),
|
||||
];
|
||||
}
|
||||
$rd['items'][] = ['title'=>substr((string)$entry->title,0,140),'subreddit'=>'r/'.$sub,'url'=>(string)$entry->link['href'],'date'=>substr((string)$entry->updated,0,10)];
|
||||
}
|
||||
}
|
||||
$rd['items'] = array_slice($rd['items'], 0, 15);
|
||||
$rd['items'] = array_slice($rd['items'], 0, 20);
|
||||
$rd['count'] = count($rd['items']);
|
||||
$signals['channels']['reddit'] = $rd;
|
||||
|
||||
// YouTube from DDG
|
||||
$yt = ['channel'=>'youtube','source'=>'DDG search','items'=>[]];
|
||||
foreach ([0,1] as $i) {
|
||||
if (empty($results['yt_'.$i])) continue;
|
||||
// Catch both youtube.com and youtu.be
|
||||
preg_match_all('~<a[^>]*class="result__a"[^>]*href="([^"]+(?:youtube\.com/watch|youtu\.be/)[^"]*)"[^>]*>([^<]+)</a>~s', $results['yt_'.$i], $m, PREG_SET_ORDER);
|
||||
if (!$m) {
|
||||
// Fallback: any youtube link
|
||||
preg_match_all('~href="(https?://[^"]*youtu[^"]+)"[^>]*>([^<]+)~', $results['yt_'.$i], $m, PREG_SET_ORDER);
|
||||
}
|
||||
foreach (array_slice($m, 0, 4) as $mm) {
|
||||
$url = html_entity_decode($mm[1]);
|
||||
$title = html_entity_decode(trim(strip_tags($mm[2])));
|
||||
if (strlen($title) > 3) {
|
||||
$yt['items'][] = [
|
||||
'title' => substr($title, 0, 140),
|
||||
'url' => $url,
|
||||
'topic' => $topic_list[$i] ?? '',
|
||||
];
|
||||
// YouTube
|
||||
$yt = ['channel'=>'youtube','source'=>'HackerNews YT-filtered','items'=>[]];
|
||||
if (!empty($results['hn_yt'])) {
|
||||
$hd = @json_decode($results['hn_yt'], true);
|
||||
foreach (($hd['hits'] ?? []) as $h) {
|
||||
$url = $h['url'] ?? '';
|
||||
if (strpos($url, 'youtube.com') !== false || strpos($url, 'youtu.be') !== false) {
|
||||
$yt['items'][] = ['title'=>substr($h['title']??'',0,140),'url'=>$url,'points'=>(int)($h['points']??0),'hn_discussion'=>'https://news.ycombinator.com/item?id='.($h['objectID']??''),'date'=>substr($h['created_at']??'',0,10)];
|
||||
}
|
||||
}
|
||||
}
|
||||
$yt['items'] = array_slice($yt['items'], 0, 10);
|
||||
usort($yt['items'], function($a,$b){return ($b['points']??0)-($a['points']??0);});
|
||||
$yt['items'] = array_slice($yt['items'], 0, 8);
|
||||
$yt['count'] = count($yt['items']);
|
||||
$signals['channels']['youtube'] = $yt;
|
||||
|
||||
// Aggregate top unique titles
|
||||
// Mastodon
|
||||
$ma = ['channel'=>'mastodon','source'=>'5 instances merged','items'=>[]];
|
||||
foreach (range(0,4) as $i) {
|
||||
if (empty($results['ma_'.$i])) continue;
|
||||
$md = @json_decode($results['ma_'.$i], true);
|
||||
foreach (($md['statuses'] ?? []) as $s) {
|
||||
$content = trim(strip_tags($s['content'] ?? ''));
|
||||
if (strlen($content) > 20) {
|
||||
$ma['items'][] = ['title'=>substr($content,0,180),'url'=>$s['url']??'','user'=>'@'.($s['account']['acct']??'?'),'instance'=>$mast_hosts[$i]??'?','favorites'=>(int)($s['favourites_count']??0)];
|
||||
}
|
||||
}
|
||||
}
|
||||
$ma['items'] = array_slice($ma['items'], 0, 10);
|
||||
$ma['count'] = count($ma['items']);
|
||||
$signals['channels']['mastodon'] = $ma;
|
||||
|
||||
// Dark Scout opt-in
|
||||
if ($with_scout) {
|
||||
$ds_ch = curl_init('http://127.0.0.1/api/v83-dark-scout-enriched.php?q=' . urlencode($topic_list[0] ?? 'SaaS'));
|
||||
curl_setopt_array($ds_ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>12]);
|
||||
$ds_raw = curl_exec($ds_ch); curl_close($ds_ch);
|
||||
$sc = ['channel'=>'dark_scout','source'=>'multi-engine','items'=>[]];
|
||||
if ($ds_raw) {
|
||||
$sd = @json_decode($ds_raw, true);
|
||||
foreach (array_slice(($sd['results']??[]),0,6) as $r) {
|
||||
$sc['items'][] = ['title'=>substr($r['title']??'',0,140),'snippet'=>substr($r['snippet']??'',0,150),'url'=>$r['url']??'','category'=>$r['category']??''];
|
||||
}
|
||||
}
|
||||
$sc['count'] = count($sc['items']);
|
||||
$signals['channels']['dark_scout'] = $sc;
|
||||
}
|
||||
|
||||
$all = [];
|
||||
foreach ($signals['channels'] as $c) foreach ($c['items'] as $i) if (!empty($i['title'])) $all[] = $i['title'];
|
||||
$signals['aggregated_ideas'] = array_slice(array_unique($all), 0, 25);
|
||||
$signals['aggregated_ideas'] = array_slice(array_unique($all), 0, 30);
|
||||
$signals['total_items'] = array_sum(array_map(function($c){return $c['count']??0;}, $signals['channels']));
|
||||
|
||||
// LLM cascade
|
||||
// LLM
|
||||
if (($_GET['llm'] ?? '') === '1') {
|
||||
$secrets = load_secrets();
|
||||
$weval_ctx = "WEVAL Consulting (Casablanca/Paris · SAP Ecosystem Partner).\nLive state: 48 leads Paperclip, Vistex MQL 95 (SAP Cosumar 450K MAD), Ethica MQL 100 (pharma MA Kaouther 200K MAD signing), Huawei MQL 90.\nProducts: SAP consulting, API HCP Maghreb 157K HCPs, Pharma Cloud, WEVAL SaaS Freemium.\nSovereign tools: WEVIA Master (269 tools), Dark Scout V83, WePredict 16 cockpits, WEVADS Brain (9 winners PMTA+Kumo+Postfix), Blade AI (Selenium), DocuSeal live port 3050, pandasai+Ollama, WeasyPrint.\nPipeline 2.9M MAD (650K quick wins + 2.25M big bets).";
|
||||
$weval_ctx = "WEVAL Consulting: 48 leads Paperclip · Vistex MQL95 450K MAD · Ethica MQL100 200K MAD · Huawei MQL90. Products: SAP, API HCP Maghreb 157K HCPs, Pharma Cloud, WEVAL SaaS Freemium. Tools: WEVIA Master 269 tools, Dark Scout, WePredict, WEVADS Brain, Blade AI, DocuSeal. Pipeline 2.9M MAD.";
|
||||
$summary = "";
|
||||
foreach ($signals['channels'] as $k => $c) {
|
||||
$top = $c['items'][0]['title'] ?? '(none)';
|
||||
$summary .= "- $k ({$c['count']}): $top\n";
|
||||
}
|
||||
foreach ($signals['channels'] as $k => $c) { $summary .= "- $k ({$c['count']}): " . substr($c['items'][0]['title']??'(none)', 0, 60) . "\n"; }
|
||||
$headlines = implode("\n - ", array_slice($signals['aggregated_ideas'], 0, 15));
|
||||
$prompt = "$weval_ctx\n\nSignals aggregated from LinkedIn + HackerNews + Reddit + YouTube:\n$summary\nTop headlines seen:\n - $headlines\n\nProvide 5 CONCRETE conversion ideas ADAPTED to WEVAL's sovereign stack + MENA market. Each idea must:\n1. Target one real opportunity (Vistex Cosumar / Ethica MA / API HCP / SaaS Freemium / Carrefour / LinkedIn outbound)\n2. Use only existing WEVAL tools (no new stack)\n3. Be executable in 14 days\n4. Include a measurable KPI and estimated MAD revenue\n5. Cite the social signal that inspired it (LinkedIn/HN/Reddit/YT)\n\nReply ONLY with JSON: {ideas:[{rank:N, title:str, channel:str, opportunity:str, tools_used:[str], first_steps:[str,str,str], kpi:str, estimated_mad:N, inspired_by:str}]}";
|
||||
$prompt = "$weval_ctx\n\nSignals 7 channels:\n$summary\nHeadlines:\n - $headlines\n\n5 CONCRETE conversion ideas for WEVAL MENA. Each: opp, tools, 14d exec, KPI, MAD, inspired_by.\nJSON: {ideas:[{rank:N,title:str,channel:str,opportunity:str,tools_used:[str],first_steps:[str,str,str],kpi:str,estimated_mad:N,inspired_by:str}]}";
|
||||
$payload = json_encode(['model'=>'llama-3.3-70b','messages'=>[['role'=>'user','content'=>$prompt]],'max_tokens'=>2200,'temperature'=>0.4]);
|
||||
$provs = [
|
||||
['url'=>'https://api.cerebras.ai/v1/chat/completions','key'=>$secrets['CEREBRAS_API_KEY']??'','name'=>'Cerebras'],
|
||||
@@ -181,9 +579,7 @@ if (($_GET['llm'] ?? '') === '1') {
|
||||
if (empty($p['key'])) continue;
|
||||
$pp = isset($p['override']) ? preg_replace('/"model":"[^"]+"/','"model":"'.$p['override'].'"',$payload,1) : $payload;
|
||||
$ch = curl_init($p['url']);
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_POST=>true, CURLOPT_TIMEOUT=>20,
|
||||
CURLOPT_HTTPHEADER=>['Content-Type: application/json','Authorization: Bearer '.$p['key']],
|
||||
CURLOPT_POSTFIELDS=>$pp]);
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_TIMEOUT=>20,CURLOPT_HTTPHEADER=>['Content-Type: application/json','Authorization: Bearer '.$p['key']],CURLOPT_POSTFIELDS=>$pp]);
|
||||
$r = curl_exec($ch); $c = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
|
||||
if ($c === 200) {
|
||||
$d = json_decode($r, true);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"timestamp": "2026-04-22T02:30:15",
|
||||
"timestamp": "2026-04-22T04:00:23",
|
||||
"features": {
|
||||
"total": 36,
|
||||
"pass": 35
|
||||
@@ -13,7 +13,7 @@
|
||||
"score": 97.2,
|
||||
"log": [
|
||||
"=== UX AGENT v1.0 ===",
|
||||
"Time: 2026-04-22 02:30:02",
|
||||
"Time: 2026-04-22 04:00:02",
|
||||
" core: 4/4",
|
||||
" layout: 3/4",
|
||||
" interaction: 6/6",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ok": true,
|
||||
"version": "V83-business-kpi",
|
||||
"ts": "2026-04-22T00:38:42+00:00",
|
||||
"ts": "2026-04-22T02:14:42+00:00",
|
||||
"summary": {
|
||||
"total_categories": 8,
|
||||
"total_kpis": 64,
|
||||
|
||||
@@ -387,11 +387,14 @@ if (!$lite) {
|
||||
$kpis['apis_total'] = (int)trim((string)@shell_exec("find /var/www/html/api -name '*.php' 2>/dev/null | wc -l"));
|
||||
$kpis['ollama_models'] = (int)trim((string)@shell_exec("curl -s --max-time 2 http://127.0.0.1:11434/api/tags 2>/dev/null | python3 -c 'import json,sys; print(len(json.load(sys.stdin).get(\"models\",[])))' 2>/dev/null"));
|
||||
// V99: Orphans Rescue KPIs (doctrine 60 UX premium - visible first-glance)
|
||||
$kpis['orphans_count'] = 9; // 9 pages with is_orphan=true (linkedin-control-v98, méthodologie, orphans-hub, paperclip-dashboard, erp-gap-fill, office-app, infra-tour, lean-6sigma, wtp.html)
|
||||
// V159 dynamic orphans · was hardcoded · now real-time from sitemap-api
|
||||
$_v159_orphans_data = @json_decode(@file_get_contents('http://127.0.0.1/api/weval-sitemap-api.php', false, stream_context_create(['http' => ['timeout' => 3, 'header' => "Host: weval-consulting.com\r\n"]])), true);
|
||||
$kpis['orphans_count'] = $_v159_orphans_data['stats']['orphan_count'] ?? 9; // V159 dynamic · fallback hardcoded
|
||||
$kpis['orphans_rescued_pages'] = 11; // Pages accessible via knowledge.orphans_rescue_v98 submodule
|
||||
$kpis['orphans_hub_inbound'] = 183; // Pages wired inside orphans-hub.html (V96.22 catch-all)
|
||||
$kpis['orphans_hub_inbound'] = (int)trim(@shell_exec("grep -cE 'href=' /var/www/html/orphans-hub.html 2>/dev/null") ?: 183); // V159 dynamic count
|
||||
$kpis['orphans_rescue_url'] = '/orphans-hub.html';
|
||||
$kpis['orphans_rescue_status'] = 'live'; // since V98 commit 432eb8969
|
||||
$kpis['orphans_count_source'] = $_v159_orphans_data ? 'sitemap-api-live' : 'fallback-hardcoded'; // V159 honesty
|
||||
// WEVIA TRUTH SYNC · read from truth-registry (Opus Yacine 19avr)
|
||||
$__truth = @json_decode(@file_get_contents('/var/www/html/api/wevia-truth-registry.json'), true);
|
||||
if (is_array($__truth)) {
|
||||
|
||||
@@ -87,14 +87,15 @@ if (!empty($_mam)) {
|
||||
if (isset($_mam) && $_mam) {
|
||||
$__v103_msg = mb_strtolower(trim($_mam));
|
||||
// Natural language patterns - ALL trigger multi-agent SSE
|
||||
// status_agents_v154 · enriched patterns Opus V154.1
|
||||
$__v103_patterns = [
|
||||
// English/Franglais
|
||||
'/\borchestrate\b/i',
|
||||
'/\b(status\s+all|all\s+status|full\s+scan|all\s+agents)\b/i',
|
||||
'/\b(status\s+all|all\s+status|full\s+scan|all\s+agents|status\s+agents|agents\s+status|status\s+complet|complete\s+status)\b/i',
|
||||
'/\b(parallel|simultan)/i',
|
||||
// French naturel
|
||||
'/\b(bilan\s+complet|rapport\s+(multi|complet|exhaustif)|orchestre|orchestrate)\b/i',
|
||||
'/\b(tous\s+les\s+agents|toutes\s+les\s+(metriques|capacites)|cartograph)\b/i',
|
||||
'/\b(tous\s+(les\s+)?agents?|toutes\s+les\s+(metriques|capacites)|cartograph)\b/i',
|
||||
'/\b(exhaustif|reconcile|6sigma|six\s*sigma|tout\s+finir)\b/i',
|
||||
'/\b(agir|agis|fais)\s+(en\s+)?(multi|plusieurs)/i',
|
||||
'/\b(en\s+)?multi[\s\-]?agents?\b/i',
|
||||
@@ -575,6 +576,50 @@ if (!empty($_mam)) {
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// === SELF_META INTENT (22avr Opus - vrai count tools) ===
|
||||
if (preg_match('/\b(?:self[_\s-]?meta|tool[s]?[_\s-]?count|combien.*tool|tools[\s_]?meta|capabilities[_\s-]?count|stats?\s+(?:wevia|master|tools)|tu\s+as\s+combien)\b/iu', $__bm)) {
|
||||
$__sm = [];
|
||||
$__sm[] = "=== WEVIA MASTER SELF_META ===";
|
||||
// Tool registry count
|
||||
$__reg = @file_get_contents('/opt/wevia-brain/wevia-tool-registry.json');
|
||||
if ($__reg) {
|
||||
$__rj = json_decode($__reg, true);
|
||||
$__sm[] = "Tool registry: v" . ($__rj['v'] ?? '?') . " · count=" . ($__rj['count'] ?? '?') . " · array_size=" . count($__rj['tools'] ?? []);
|
||||
} else { $__sm[] = "Tool registry: NOT FOUND"; }
|
||||
// Priority intents NL
|
||||
$__pi = trim(@shell_exec('wc -l < /opt/wevia-brain/priority-intents-nl.json 2>/dev/null'));
|
||||
$__sm[] = "Priority intents NL: " . ($__pi ?: '?') . " lines";
|
||||
// OSS exec registry
|
||||
$__oss = trim(@shell_exec('cat /opt/wevia-brain/oss-exec-registry.json 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d) if isinstance(d,(list,dict)) else 0)" 2>/dev/null'));
|
||||
$__sm[] = "OSS exec registry tools: " . ($__oss ?: '?');
|
||||
// Brain JSONs
|
||||
$__bj = trim(@shell_exec('find /opt/wevia-brain -maxdepth 2 -name "*.json" 2>/dev/null | wc -l'));
|
||||
$__sm[] = "Brain knowledge JSONs: " . ($__bj ?: '?');
|
||||
// Top-IA scripts
|
||||
$__ti = trim(@shell_exec('ls /opt/weval-ops/top-ia/ 2>/dev/null | wc -l'));
|
||||
$__sm[] = "Top-IA scripts: " . ($__ti ?: '?');
|
||||
// Plugins
|
||||
$__pl = trim(@shell_exec('find /opt/weval-plugins -maxdepth 2 -type d 2>/dev/null | wc -l'));
|
||||
$__sm[] = "Plugins dirs: " . ($__pl ?: '?');
|
||||
// DeerFlow skills
|
||||
$__df = trim(@shell_exec('find /opt/deer-flow -name "*.py" 2>/dev/null | wc -l'));
|
||||
$__sm[] = "DeerFlow Python scripts: " . ($__df ?: '?');
|
||||
// Doctrines
|
||||
$__dc = trim(@shell_exec('ls /var/www/html/wiki/doctrine-*.md 2>/dev/null | wc -l'));
|
||||
$__sm[] = "Doctrines wiki: " . ($__dc ?: '?');
|
||||
// Crons WEVIA
|
||||
$__cr = trim(@shell_exec('ls /etc/cron.d/wevia* 2>/dev/null | wc -l'));
|
||||
$__sm[] = "Active WEVIA crons: " . ($__cr ?: '?');
|
||||
$__sm[] = "";
|
||||
$__sm[] = "=== ARCHITECTURE ===";
|
||||
$__sm[] = "S204 nginx PHP-FPM dual-pool · S95 PMTA email PostgreSQL · 0eur month all sovereign";
|
||||
$__sm[] = "Pipeline: nl-priority(80) -> pareto -> fast-path-v3 -> opus-intents(66) -> conv-guard -> arena -> dynamic-resolver -> wave200 -> gap-intents -> master-router -> LLM";
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['provider'=>'opus-early-guard','content'=>implode("\n", $__sm),'tool'=>'self_meta_real','source'=>'early-guard-primary']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// === END OPUS_BUSINESS_COUNT_GUARD_17AVR ===
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"timestamp": "2026-04-21 22:00:02",
|
||||
"timestamp": "2026-04-22 02:00:02",
|
||||
"frameworks": [
|
||||
{
|
||||
"name": "Lean Six Sigma",
|
||||
@@ -8,17 +8,17 @@
|
||||
{
|
||||
"name": "KPIs defined",
|
||||
"ok": true,
|
||||
"detail": "Quality=99%"
|
||||
"detail": "Quality=99.3%"
|
||||
},
|
||||
{
|
||||
"name": "Monitoring",
|
||||
"ok": true,
|
||||
"detail": "76 crons"
|
||||
"detail": "77 crons"
|
||||
},
|
||||
{
|
||||
"name": "Quality>95",
|
||||
"ok": true,
|
||||
"detail": "99%"
|
||||
"detail": "99.3%"
|
||||
},
|
||||
{
|
||||
"name": "Auto-fix",
|
||||
@@ -46,7 +46,7 @@
|
||||
{
|
||||
"name": "Change Mgmt",
|
||||
"ok": true,
|
||||
"detail": "3693 commits\/7d"
|
||||
"detail": "3676 commits\/7d"
|
||||
},
|
||||
{
|
||||
"name": "SLA Monitor",
|
||||
@@ -74,7 +74,7 @@
|
||||
{
|
||||
"name": "Process monitor",
|
||||
"ok": true,
|
||||
"detail": "76 crons"
|
||||
"detail": "77 crons"
|
||||
},
|
||||
{
|
||||
"name": "Continuous improvement",
|
||||
@@ -84,7 +84,7 @@
|
||||
{
|
||||
"name": "Nonconformity",
|
||||
"ok": true,
|
||||
"detail": "99%"
|
||||
"detail": "99.3%"
|
||||
}
|
||||
],
|
||||
"s": 3,
|
||||
@@ -134,8 +134,8 @@
|
||||
},
|
||||
{
|
||||
"name": "Disk<85%",
|
||||
"ok": true,
|
||||
"detail": "84%"
|
||||
"ok": false,
|
||||
"detail": "85%"
|
||||
},
|
||||
{
|
||||
"name": "Local inference",
|
||||
@@ -143,7 +143,7 @@
|
||||
"detail": "8 Ollama"
|
||||
}
|
||||
],
|
||||
"s": 3,
|
||||
"s": 2,
|
||||
"t": 3
|
||||
},
|
||||
{
|
||||
@@ -153,7 +153,7 @@
|
||||
{
|
||||
"name": "CI\/CD",
|
||||
"ok": true,
|
||||
"detail": "3693 commits"
|
||||
"detail": "3676 commits"
|
||||
},
|
||||
{
|
||||
"name": "Auto testing",
|
||||
@@ -163,7 +163,7 @@
|
||||
{
|
||||
"name": "DevOps",
|
||||
"ok": true,
|
||||
"detail": "76 pipelines"
|
||||
"detail": "77 pipelines"
|
||||
},
|
||||
{
|
||||
"name": "Retrospective",
|
||||
@@ -175,7 +175,7 @@
|
||||
"t": 4
|
||||
}
|
||||
],
|
||||
"score": 76,
|
||||
"score": 72,
|
||||
"total_checks": 25,
|
||||
"total_pass": 19
|
||||
"total_pass": 18
|
||||
}
|
||||
@@ -4580,6 +4580,64 @@
|
||||
"desc": "Liste 19 Docker containers actifs (Mattermost, n8n, Twenty CRM, Plausible, Vaultwarden, Qdrant, SearXNG, Langfuse, Gitea, etc.)",
|
||||
"since": "opus-session-20260421-v13-oss",
|
||||
"added_ts": "2026-04-22T01:24:58+02:00"
|
||||
},
|
||||
{
|
||||
"id": "pdf_premium_generator",
|
||||
"kw": "pdf.*premium|rapport.*premium|pdf.*qualit|pdf.*graphique|pdf.*chart|premium.*pdf",
|
||||
"cmd": "curl -sS -X POST http://127.0.0.1/api/ambre-tool-pdf-premium.php -H 'Content-Type: application/json' -d '{\"topic\":\"${TOPIC}\"}'",
|
||||
"exec": true,
|
||||
"desc": "PDF Premium Chart.js google-chrome 6 types",
|
||||
"wave": 229
|
||||
},
|
||||
{
|
||||
"id": "mermaid_generator_kb",
|
||||
"kw": "mermaid|diagramme|flowchart|sequence.*diagram|gantt|schema.*mermaid",
|
||||
"cmd": "curl -sS -X POST http://127.0.0.1/api/ambre-tool-mermaid.php -H 'Content-Type: application/json' -d '{\"topic\":\"${TOPIC}\"}'",
|
||||
"exec": true,
|
||||
"desc": "Mermaid + Learning KB RAG",
|
||||
"wave": 229
|
||||
},
|
||||
{
|
||||
"id": "mermaid_kb_search",
|
||||
"kw": "mermaid.*search|recherche.*diagramme|find.*schema|catalog.*mermaid",
|
||||
"cmd": "curl -sS -X POST http://127.0.0.1/api/ambre-mermaid-learn.php -H 'Content-Type: application/json' -d '{\"action\":\"search\",\"query\":\"${TOPIC}\"}'",
|
||||
"exec": true,
|
||||
"desc": "Mermaid KB search",
|
||||
"wave": 229
|
||||
},
|
||||
{
|
||||
"id": "mermaid_kb_stats",
|
||||
"kw": "mermaid.*stats|mermaid.*catalog.*count",
|
||||
"cmd": "curl -sS -X POST http://127.0.0.1/api/ambre-mermaid-learn.php -H 'Content-Type: application/json' -d '{\"action\":\"stats\"}'",
|
||||
"exec": true,
|
||||
"desc": "Mermaid KB stats",
|
||||
"wave": 229
|
||||
},
|
||||
{
|
||||
"id": "llm_semaphore_stats",
|
||||
"kw": "semaphore.*stat|llm.*load|llm.*semaphore|concurrent.*llm",
|
||||
"cmd": "curl -sS http://127.0.0.1/api/ambre-llm-semaphore.php",
|
||||
"exec": true,
|
||||
"desc": "LLM semaphore stats",
|
||||
"wave": 229
|
||||
},
|
||||
{
|
||||
"id": "claude_pattern_api",
|
||||
"kw": "claude.*pattern|pattern.*claude|7.*phases|thinking.*plan.*execute",
|
||||
"cmd": "curl -sk -X POST http://127.0.0.1/api/claude-pattern-api.php -H 'Host: weval-consulting.com' -H 'Content-Type: application/json' --data '{\"message\":\"{MSG}\",\"chatbot\":\"wevia-master\"}' 2>/dev/null",
|
||||
"exec": true,
|
||||
"desc": "Claude pattern API · 7 phases (thinking/plan/RAG/execute/tests/response/critique) · 5 chatbots + fallback",
|
||||
"since": "opus-session-20260421-v15",
|
||||
"added_ts": "2026-04-22T04:18:52+02:00"
|
||||
},
|
||||
{
|
||||
"id": "chatbot_health_check",
|
||||
"kw": "chatbot.*health|chatbot.*status|test.*chatbot|5.*chatbot",
|
||||
"cmd": "for B in wevia-master wevia claw director ethica; do curl -sk -X POST http://127.0.0.1/api/claude-pattern-api.php -H 'Host: weval-consulting.com' -H 'Content-Type: application/json' --data \"{\\\"message\\\":\\\"ping\\\",\\\"chatbot\\\":\\\"$B\\\"}\" --max-time 15 | python3 -c 'import sys,json;d=json.load(sys.stdin);s=d.get(\"summary\",{});print(f\"{\\\"$B\\\"}: {s.get(\"tests_score\")}·{s.get(\"quality\")}\")'; done",
|
||||
"exec": true,
|
||||
"desc": "Health check 5 chatbots (wevia-master/wevia/claw/director/ethica) avec pattern Claude",
|
||||
"since": "opus-session-20260421-v15",
|
||||
"added_ts": "2026-04-22T04:18:52+02:00"
|
||||
}
|
||||
],
|
||||
"opus_safe_wire": {
|
||||
@@ -4596,5 +4654,10 @@
|
||||
"ts": "20260421-1231",
|
||||
"wired": 131,
|
||||
"ratio": "79.1%"
|
||||
},
|
||||
"opus_wave_229": {
|
||||
"ts": "2026-04-22T01:20:00+00:00",
|
||||
"added": 5,
|
||||
"new_total": 643
|
||||
}
|
||||
}
|
||||
@@ -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); $lost=intval($sl["customers_lost_30d"]??0); return $c>0?round(($lost/$c)*100,1):0;})(), "unit" => "%", "target" => 5, "trend" => "live", "status" => (function(){$sl=@json_decode(@file_get_contents("/opt/weval-l99/data/kpi-wire/stripe-live.json"),true); $c=intval($sl["customers_total"]??0); $lost=intval($sl["customers_lost_30d"]??0); $pct=$c>0?($lost/$c)*100:0; return $pct<=5?"ok":($pct<=10?"warn":"fail");})(), "source" => "Stripe live (lost_30d/total_customers)", "drill" => "Currently 0 lost / 1 active = 0pct churn"],
|
||||
["id" => "revenue_forecast_next_q", "label" => "Revenue forecast Q+1", "value" => $v50["revenue_forecast_q1"], "unit" => "€", "target" => 5000, "trend" => "live", "status" => $v50["revenue_forecast_q1"] >= 5000 ? "ok" : "warn", "source" => "Time-series ML on Stripe", "drill" => "ARIMA/Prophet model"],
|
||||
["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" => "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" => 30, "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>=30?"ok":($days>=14?"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" => 15, "trend" => "predicted", "status" => (20) >= 15 ? "ok" : "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(){$cache="/opt/weval-l99/data/kpi-wire/pipeline-close.json"; if(file_exists($cache)){$d=@json_decode(@file_get_contents($cache),true); return floatval($d["weighted_pct"]??0);} return 0;})(), "unit" => "%", "target" => 40, "trend" => "live", "status" => (function(){$cache="/opt/weval-l99/data/kpi-wire/pipeline-close.json"; $v=0; if(file_exists($cache)){$d=@json_decode(@file_get_contents($cache),true); $v=floatval($d["weighted_pct"]??0);} return $v>=40?"ok":($v>0?"warn":"wire_needed");})(), "source" => "PG admin.pipeline_deals weighted (cache 5min)", "drill" => "AVG stage_probability on open deals"],
|
||||
|
||||
336
arsenal-history/arsenal-102020.html
Normal file
@@ -0,0 +1,336 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr"><head>
|
||||
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Arsenal — Framework ERP Intelligence | Fill-Gap + IA + Reporting</title>
|
||||
<meta name="description" content="Arsenal — Framework ERP Intelligence - Solutions IA souveraines pour entreprises">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>:root{--bg:#05080f;--s:#0c1222;--a:#f0c674;--a15:rgba(240,198,116,.15);--p:#7c5cfc;--t:#00c9a7;--r:#ff6b6b;--bl:#4ea8de;--sv:#7a8ba5;--w:#edf2f7;--b:rgba(240,198,116,.08)}*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Outfit',sans-serif;background:var(--bg);color:var(--w);overflow-x:hidden}nav{position:fixed;top:0;width:100%;padding:1rem 4%;display:flex;justify-content:space-between;align-items:center;z-index:100;backdrop-filter:blur(20px);background:rgba(5,8,15,.85);border-bottom:1px solid var(--b)}.logo{font-weight:800;font-size:1.5rem}.logo span{color:var(--a)}.btn-n{background:var(--a);color:var(--bg);padding:.55rem 1.3rem;border-radius:7px;font-weight:700;font-size:.82rem;text-decoration:none}.hero{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:7rem 4% 4rem;position:relative}.hero::after{content:'';position:absolute;top:5%;left:50%;transform:translateX(-50%);width:700px;height:700px;background:radial-gradient(circle,rgba(240,198,116,.06) 0%,transparent 70%);border-radius:50%;pointer-events:none}.badge{display:inline-flex;align-items:center;gap:.5rem;background:var(--a15);border:1px solid rgba(240,198,116,.2);border-radius:100px;padding:.35rem 1rem;font-size:.75rem;font-weight:600;color:var(--a);margin-bottom:2rem}h1{font-size:3.4rem;font-weight:800;line-height:1.08;letter-spacing:-.04em;margin-bottom:1.2rem;max-width:800px}h1 em{font-style:normal;color:var(--a)}.sub{font-size:1.05rem;color:var(--sv);max-width:600px;line-height:1.7;margin-bottom:2rem}.btns{display:flex;gap:1rem;flex-wrap:wrap;justify-content:center}.btn-p{background:var(--a);color:var(--bg);padding:.85rem 2rem;border-radius:8px;font-weight:700;text-decoration:none;transition:all .3s}.btn-p:hover{transform:translateY(-2px);box-shadow:0 8px 30px rgba(240,198,116,.25)}.btn-o{background:transparent;color:var(--w);padding:.85rem 2rem;border-radius:8px;text-decoration:none;border:1px solid rgba(255,255,255,.12)}.sec{padding:5rem 4%;max-width:1200px;margin:0 auto}.stag{font-family:'Space Mono',monospace;font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.2em;color:var(--a);margin-bottom:1rem}h2{font-size:2.2rem;font-weight:800;letter-spacing:-.03em;margin-bottom:.8rem}.sd{color:var(--sv);font-size:.95rem;line-height:1.7;max-width:560px;margin-bottom:2.5rem}.stats{display:grid;grid-template-columns:repeat(6,1fr);gap:1px;background:rgba(240,198,116,.06);border-radius:14px;overflow:hidden;margin:2rem 0}.stat{padding:1.3rem;text-align:center;background:rgba(12,18,34,.95)}.stat-n{font-family:'Space Mono',monospace;font-size:1.6rem;font-weight:700;color:var(--a)}.stat-l{font-size:.68rem;color:var(--sv);margin-top:.15rem}.g3{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}.g2{display:grid;grid-template-columns:1fr 1fr;gap:1.2rem}.cd{background:var(--s);border:1px solid var(--b);border-radius:14px;padding:1.5rem;transition:all .3s}.cd:hover{border-color:rgba(240,198,116,.25);transform:translateY(-2px)}.cd h3{font-size:.95rem;font-weight:600;margin:.6rem 0 .3rem}.cd p{font-size:.8rem;color:var(--sv);line-height:1.55}.erps{display:flex;flex-wrap:wrap;gap:.5rem;margin:1.5rem 0}.erp{font-family:'Space Mono',monospace;font-size:.72rem;font-weight:700;padding:.4rem .8rem;border-radius:8px;background:rgba(240,198,116,.08);color:var(--a);border:1px solid rgba(240,198,116,.12);transition:all .2s}.erp:hover{background:rgba(240,198,116,.2)}.erp.active{background:rgba(240,198,116,.2);border-color:rgba(240,198,116,.4)}.vs{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;margin:2rem 0}.vs-card{background:var(--s);border:1px solid var(--b);border-radius:14px;padding:1.5rem}.vs-card.us{border-color:rgba(240,198,116,.2);background:rgba(240,198,116,.02)}.vs-card h3{font-size:.95rem;font-weight:700;margin-bottom:.8rem}.vs-items{font-size:.8rem;color:var(--sv);line-height:1.8}.cta{text-align:center;padding:4rem 2rem;margin:2rem 4%;background:linear-gradient(135deg,var(--s),rgba(240,198,116,.04));border:1px solid rgba(240,198,116,.1);border-radius:20px}.cta p{color:var(--sv);max-width:500px;margin:.5rem auto 1.5rem}footer{padding:2rem 4%;max-width:1200px;margin:0 auto;display:flex;justify-content:space-between;border-top:1px solid rgba(255,255,255,.04);font-size:.75rem;color:var(--sv)}footer a{color:var(--a);text-decoration:none}@media(max-width:900px){h1{font-size:2.2rem}.g3,.g2,.vs,.stats{grid-template-columns:1fr}footer{flex-direction:column;gap:.5rem;text-align:center}}input,select,textarea{background:#0b0d14!important;color:#e2e8f0!important;border:1px solid #1e293b!important;border-radius:8px!important}input::placeholder{color:#475569!important}</style><style>/* Hide nav in iframe */
|
||||
@media all{.in-iframe nav{display:none!important}.in-iframe .hero{padding-top:3rem!important;min-height:auto!important}.in-iframe footer{display:none!important}.in-iframe .cta{display:none!important}.in-iframe .wv-links{display:none!important}.wv-links{display:none!important}}</style>
|
||||
<script>if(window!==window.top)document.documentElement.classList.add('in-iframe');</script>
|
||||
<link rel="canonical" href="https://weval-consulting.com/products/arsenal.html">
|
||||
<meta property="og:title" content="Arsenal — Framework ERP Intelligence | Fill-Gap + IA + Repor">
|
||||
<meta property="og:description" content="Arsenal — Framework ERP Intelligence - Solutions IA souveraines pour entreprises">
|
||||
<meta property="og:url" content="https://weval-consulting.com/products/arsenal.html">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="WEVAL Consulting">
|
||||
<meta property="og:image" content="https://weval-consulting.com/assets/logo-weval-png-DChrMGao.png">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Arsenal — Framework ERP Intelligence | Fill-Gap + IA + Repor">
|
||||
<meta name="twitter:description" content="Arsenal — Framework ERP Intelligence - Solutions IA souveraines pour entreprises">
|
||||
<link rel="alternate" hreflang="fr" href="https://weval-consulting.com/products/arsenal.html">
|
||||
<link rel="alternate" hreflang="x-default" href="https://weval-consulting.com/products/arsenal.html">
|
||||
<link rel="stylesheet" href="/assets/dark-iframe.css"></head><body>
|
||||
<nav><div class="logo" style="display:flex;align-items:center;gap:8px"><img src="/assets/logo-arsenal.svg" alt="" style="width:32px;height:32px">Arsenal<span>.</span></div><a href="#cta" class="btn-n">Demander une démo →</a></nav>
|
||||
|
||||
<section class="hero">
|
||||
<div class="badge">Framework ERP Intelligence — En production</div>
|
||||
<h1>Votre ERP fait 80%.<br>Arsenal fait les <em>20% manquants</em></h1>
|
||||
<p class="sub">150+ écrans modulaires, Brain Engine IA, reporting temps réel. Se branche sur n'importe quel ERP (SAP, Odoo, Oracle, Salesforce, custom) pour combler les gaps fonctionnels sans remplacer l'existant.</p>
|
||||
<div class="btns"><a href="#modules" class="btn-p">Explorer les modules →</a><a href="#cta" class="btn-o">Demander une demo</a></div></section>
|
||||
|
||||
<div class="sec"><div class="stats">
|
||||
<div class="stat"><div class="stat-n">150+</div><div class="stat-l">Écrans HTML</div></div>
|
||||
<div class="stat"><div class="stat-n">38</div><div class="stat-l">Crons automatiques</div></div>
|
||||
<div class="stat"><div class="stat-n">6</div><div class="stat-l">APIs core sync</div></div>
|
||||
<div class="stat"><div class="stat-n">500+</div><div class="stat-l">Brain configs IA</div></div>
|
||||
<div class="stat"><div class="stat-n">7.3M</div><div class="stat-l">Contacts gérés</div></div>
|
||||
<div class="stat"><div class="stat-n">0</div><div class="stat-l">Pages cassées</div></div></div></div>
|
||||
|
||||
<section class="sec"><div class="stag">ERP compatibles</div><h2>Se branche sur tout</h2>
|
||||
<p class="sd">Arsenal n'est pas un ERP. C'est la couche d'intelligence qui se greffe sur votre ERP existant. Plug-and-play, zéro migration.</p>
|
||||
<div class="erps">
|
||||
<span class="erp active">WEVADS ✓ Prouvé</span>
|
||||
<span class="erp">SAP S/4HANA</span>
|
||||
<span class="erp">SAP ECC</span>
|
||||
<span class="erp">Oracle EBS</span>
|
||||
<span class="erp">Oracle Fusion</span>
|
||||
<span class="erp">Odoo</span>
|
||||
<span class="erp">Sage X3</span>
|
||||
<span class="erp">Microsoft Dynamics</span>
|
||||
<span class="erp">Salesforce</span>
|
||||
<span class="erp">JD Edwards</span>
|
||||
<span class="erp">Custom ERP</span>
|
||||
<span class="erp">Legacy Systems</span></div></section>
|
||||
|
||||
<section class="sec" id="modules"><div class="stag">Modules</div><h2>10 catégories de modules</h2>
|
||||
<p class="sd">Chaque module est un écran autonome avec API, données temps réel et IA intégrée. Activez uniquement ce dont vous avez besoin.</p>
|
||||
<div class="g3">
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Brain Engine IA</h3><p>centaines de configurations IA, 9 winners sacrés, smart failover 11 providers. Optimisation automatique des processus par machine learning. Prédiction, scoring, anomaly detection.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Data Intelligence</h3><p>Data Manager, Drill-down API, cross-database queries via connecteur. Connecte et agrège les données de toutes vos sources : ERP, CRM, fichiers, APIs externes.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><circle cx="6" cy="6" r="1" fill="currentColor"/><circle cx="6" cy="18" r="1" fill="currentColor"/></svg></div><h3>Reporting Avancé</h3><p>Dashboards temps réel, KPIs customisables, alertes automatiques. Ce que votre ERP ne montre pas — Arsenal le visualise.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Pipeline Automation</h3><p>38 crons orchestrés, workflows E2E, queue workers multi-provider. Automatisez les processus que votre ERP ne gère pas nativement.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Data Intelligence Factory</h3><p>Extraction de données web automatisée. Veille concurrentielle, enrichissement CRM, monitoring prix. 6+ sources prouvées.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15 15 0 014 10 15 15 0 01-4 10 15 15 0 01-4-10A15 15 0 0112 2z"/></svg></div><h3>Account Factory</h3><p>Création automatisée de comptes multi-providers (Exchange, cloud, DNS). centaines de comptes Exchange, 47 cloud, 191 FreeDNS gérés.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Offer Engine</h3><p>Gestion des offres, sponsors, payouts. Séparation multi-source, drill-down par offre. 85 offres actives, $265 max payout.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Scout Intelligence</h3><p>Reconnaissance et analyse d'infrastructure. Hostname mapping, IP tracking, target identification. Lookalike engine 178 personas.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></div><h3>Sentinel Security</h3><p>Monitoring sécurité, exec API, commande à distance sécurisée. Cyber scanner score 73/100, OWASP, fail2ban intégré.</p></div></div></section>
|
||||
|
||||
<section class="sec"><div class="stag">Use Cases</div><h2>Arsenal sur le terrain</h2>
|
||||
<div class="g2">
|
||||
<div class="cd" style="border-left:3px solid var(--a)"><h3>Arsenal × Marketing Digital (WEVADS)</h3><p>150+ écrans déployés. processus complet send→open→click→conversion. Brain Engine optimisé les configs par ISP. Optimisation automatique milliers de comptes. Résultat : système complet là où l'ERP natif ne gère pas le marketing digital.</p></div>
|
||||
<div class="cd" style="border-left:3px solid var(--p)"><h3>Arsenal × Manufacturing (SAP)</h3><p>Fill-gap reporting production : OEE temps réel, prédiction pannes IA, tableau de bord qualité, alertes seuils automatiques. Ce que SAP PP ne montre pas nativement.</p></div>
|
||||
<div class="cd" style="border-left:3px solid var(--t)"><h3>Arsenal × Retail (Odoo)</h3><p>Intelligence commerciale : scoring client IA, prédiction stock, analytics promotion, heatmap ventes géographique. Complète Odoo Sales/Inventory.</p></div>
|
||||
<div class="cd" style="border-left:3px solid var(--bl)"><h3>Arsenal × Finance (Oracle)</h3><p>Reporting financier augmenté : consolidation multi-entités, prévision trésorerie IA, alertes anomalies comptables, dashboards CFO temps réel.</p></div>
|
||||
<div class="cd" style="border-left:3px solid var(--r)"><h3>Arsenal × Supply Chain</h3><p>Visibilité E2E : tracking temps réel, optimisation routes IA, prédiction délais, alertes rupture stock. Complète n'importe quel WMS/TMS.</p></div>
|
||||
<div class="cd" style="border-left:3px solid var(--a)"><h3>Arsenal × RH (Dynamics)</h3><p>Analytics RH avancés : prédiction turnover, scoring candidats IA, dashboard compétences, planning formation automatisé.</p></div></div></section>
|
||||
|
||||
<section class="sec"><div class="stag">Différenciation</div><h2>Pourquoi pas un intégrateur classique ?</h2>
|
||||
<div class="vs">
|
||||
<div class="vs-card us"><h3 style="color:var(--a)">Arsenal (WEVAL)</h3><div class="vs-items"> Framework réutilisable — déployé en semaines, pas en mois<br> 150+ écrans prêts à brancher<br> IA native (Brain Engine centaines de configs)<br> Se greffe sur l'ERP existant — zéro migration<br> Coût 10-50x inférieur au custom SAP<br> Mises à jour continues incluses<br> Cloud souverain — vos données chez vous</div></div>
|
||||
<div class="vs-card"><h3 style="color:var(--sv)">Intégrateurs classiques (développement sur mesure)</h3><div class="vs-items"> Custom dev from scratch — 6-18 mois<br> Chaque écran facturé séparément<br> Pas d'IA intégrée nativement<br> Souvent impose une migration ERP<br> Budget 100K-500K$+<br> Maintenance facturée en plus<br> Cloud US — données hors contrôle</div></div></div></section>
|
||||
|
||||
<section class="sec"><div class="stag">Architecture</div><h2>Stack technique</h2>
|
||||
<div class="g3">
|
||||
<div class="cd"><h3>Frontend</h3><p>150+ écrans HTML5 standalone. Responsive. Arsenal-common.js (25KB, 6 modules partagés). Zéro framework lourd — performance maximale.</p></div>
|
||||
<div class="cd"><h3>Backend</h3><p>PHP APIs + PostgreSQL. Dual vhosts Apache (5821 + 5890). connecteur bridge cross-databases. 38 crons orchestrés.</p></div>
|
||||
<div class="cd"><h3>IA Layer</h3><p>Brain Engine : 11 providers IA (Cloud Tier 1, Cloud Tier 2, WEVIA, Gemini, WEVIA...) avec smart failover. Cloud souverain GPU dédié 20GB.</p></div></div></section>
|
||||
|
||||
<div class="cta" id="cta"><div class="stag">Deploy</div><h2>Arsenal sur votre ERP en 2 semaines</h2><p>Audit gratuit de votre ERP. On identifié les gaps et on déploie les modules Arsenal adaptés. Aucune migration nécessaire.</p><a href="#cta" class="btn-p">Demander l'audit gratuit →</a></div>
|
||||
<footer><div><strong>Arsenal</strong> · <a href="/products/">WEVAL Products</a></div><div>France · Maroc · États-Unis · International</div></footer><!-- WEVAL Product Assistant Chatbot Widget -->
|
||||
<div id="weval-bot-widget" style="position:fixed;bottom:20px;right:20px;z-index:9999;font-family:'Inter',system-ui,sans-serif">
|
||||
<style>
|
||||
#weval-bot-btn{width:56px;height:56px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#8b5cf6);border:none;cursor:pointer;box-shadow:0 4px 24px rgba(99,102,241,.4);display:flex;align-items:center;justify-content:center;transition:transform .2s,box-shadow .2s}
|
||||
#weval-bot-btn:hover{transform:scale(1.08);box-shadow:0 6px 32px rgba(99,102,241,.5)}
|
||||
#weval-bot-btn svg{width:28px;height:28px;fill:#fff}
|
||||
#weval-bot-badge{position:absolute;top:-2px;right:-2px;width:14px;height:14px;background:#22c55e;border-radius:50%;border:2px solid #fff;animation:pulse-badge 2s infinite}
|
||||
@keyframes pulse-badge{0%,100%{opacity:1}50%{opacity:.5}}
|
||||
#weval-bot-panel{display:none;position:fixed;bottom:90px;right:20px;width:380px;max-height:520px;background:#0f1629;border:1px solid rgba(99,102,241,.2);border-radius:16px;box-shadow:0 12px 48px rgba(0,0,0,.5);overflow:hidden;flex-direction:column}
|
||||
#weval-bot-panel.open{display:flex}
|
||||
#weval-bot-head{background:linear-gradient(135deg,#6366f1,#8b5cf6);padding:14px 18px;display:flex;align-items:center;gap:10px}
|
||||
#weval-bot-head .avatar{width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,.2);display:flex;align-items:center;justify-content:center;font-size:18px}
|
||||
#weval-bot-head .info{flex:1;color:#fff}
|
||||
#weval-bot-head .info .name{font-weight:600;font-size:14px}
|
||||
#weval-bot-head .info .status{font-size:11px;opacity:.8}
|
||||
#weval-bot-close{background:none;border:none;color:rgba(255,255,255,.7);cursor:pointer;font-size:20px;padding:4px}
|
||||
#weval-bot-msgs{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:10px;min-height:280px;max-height:360px}
|
||||
.bot-msg{max-width:85%;padding:10px 14px;border-radius:12px;font-size:13px;line-height:1.5;word-wrap:break-word}
|
||||
.bot-msg.bot{background:rgba(99,102,241,.12);color:#e2e8f0;border-bottom-left-radius:4px;align-self:flex-start}
|
||||
.bot-msg.user{background:#6366f1;color:#fff;border-bottom-right-radius:4px;align-self:flex-end}
|
||||
.bot-msg a{color:#818cf8;text-decoration:underline}
|
||||
.bot-typing{display:flex;gap:4px;padding:10px 14px;align-self:flex-start}
|
||||
.bot-typing span{width:6px;height:6px;background:#6366f1;border-radius:50%;animation:bounce .6s infinite alternate}
|
||||
.bot-typing span:nth-child(2){animation-delay:.2s}
|
||||
.bot-typing span:nth-child(3){animation-delay:.4s}
|
||||
@keyframes bounce{to{opacity:.3;transform:translateY(-4px)}}
|
||||
#weval-bot-input-area{padding:10px 14px;border-top:1px solid rgba(255,255,255,.06);display:flex;gap:8px}
|
||||
#weval-bot-input{flex:1;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:8px 12px;color:#e2e8f0;font-size:13px;outline:none}
|
||||
#weval-bot-input::placeholder{color:rgba(255,255,255,.3)}
|
||||
#weval-bot-send{background:#6366f1;border:none;border-radius:8px;padding:8px 12px;color:#fff;cursor:pointer;font-size:13px;font-weight:600}
|
||||
#weval-bot-send:hover{background:#5558e6}
|
||||
.quick-btns{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
|
||||
.quick-btn{background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.25);color:#a5b4fc;padding:5px 10px;border-radius:6px;font-size:11px;cursor:pointer;transition:all .15s}
|
||||
.quick-btn:hover{background:rgba(99,102,241,.3);color:#fff}
|
||||
</style>
|
||||
|
||||
<button id="weval-bot-btn" onclick="toggleBot()">
|
||||
<svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
|
||||
<div id="weval-bot-badge"></div>
|
||||
</button>
|
||||
|
||||
<div id="weval-bot-panel">
|
||||
<div id="weval-bot-head">
|
||||
<div class="avatar">W</div>
|
||||
<div class="info">
|
||||
<div class="name">WEVAL Assistant</div>
|
||||
<div class="status">En ligne - 62 produits</div>
|
||||
</div>
|
||||
<button id="weval-bot-close" onclick="toggleBot()">×</button>
|
||||
</div>
|
||||
<div id="weval-bot-msgs"></div>
|
||||
<div id="weval-bot-input-area">
|
||||
<input id="weval-bot-input" placeholder="Posez une question sur nos produits..." onkeypress="if(event.key==='Enter')sendBot()">
|
||||
<button id="weval-bot-send" onclick="sendBot()">Envoyer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const PRODUCTS_KB = {
|
||||
deliVerscore: {name:'DeliverScore',desc:'Audit délivrabilité email - SPF/DKIM/DMARC/listes de blocage. Score + recommandations IA.',price:'Gratuit + Pro $49/mo',url:'/products/deliverscore.html',category:'Email Intelligence'},
|
||||
medreach: {name:'MedReach API',desc:'Base de 18596+ professionnels de santé vérifiés (Afrique, Europe, Moyen-Orient, Asie, Europe). API REST + export.',price:'Gratuit + Pro $299/mo',url:'/products/medreach.html',category:'Data'},
|
||||
gpu: {name:'WEVIA Inference',desc:'IA-as-a-Service. WEVIA Deep, WEVIA sur GPU dédié. API IA Cloud-compatible.',price:'Gratuit + Pro $99/mo',url:'/products/gpu-inference.html',category:'IA'},
|
||||
contentfactory: {name:'AI Content Factory',desc:'Génération de contenu IA - articles, fiches produits, LinkedIn. 6 templates.',price:'Gratuit + Pro $29/mo',url:'/products/content-factory.html',category:'IA'},
|
||||
proposalai: {name:'ProposalAI',desc:'Generateur de propositions commerciales qualité Big4. Brief -> propale en 30 sec.',price:'Gratuit + Pro $19/mo',url:'/products/proposalai.html',category:'IA'},
|
||||
blueprintai: {name:'BlueprintAI',desc:'Process docs, BPMN, CDC, blueprints L1/L2/L3, 8D, RACI.',price:'$25/mo',url:'/products/blueprintai.html',category:'IA'},
|
||||
storeforge: {name:'StoreForge',desc:'E-commerce IA. Boutique en 5 min. Descriptions IA, chatbot WEVIA, paiement Maroc.',price:'0-$29/mo',url:'/products/storeforge.html',category:'Commerce'},
|
||||
leadforge: {name:'LeadForge',desc:'B2B Lead Intelligence sur mesure. Leads vérifiés tous secteurs. 0.30-0.$50/lead.',price:'0.30-0.$49/lead',url:'/products/workspace.html',category:'Data'},
|
||||
mailwarm: {name:'MailWarm',desc:'Optimisation email automatique. 500+ seeds. Inbox 90%+ en 4-6 semaines.',price:'$29/compte/mo',url:'/products/workspace.html',category:'Email'},
|
||||
sentinel: {name:'Sentinel Monitor',desc:'Cyber monitoring PME - SSL, ports, DNS, OWASP. Alertes Telegram.',price:'Gratuit + Pro $49/mo',url:'/products/workspace.html',category:'Security'},
|
||||
outreachai: {name:'OutreachAI',desc:'Cold outreach IA. Upload leads -> IA personnalisé, envoi -> tracking.',price:'$199/mo',url:'/products/workspace.html',category:'Email'},
|
||||
wevia: {name:'WEVIA White-Label',desc:'Chatbot IA clé en main. Widget embed 5 min. KB custom, memoire, vision.',price:'100-$299/mo',url:'/products/wevia-whitelabel.html',category:'IA'},
|
||||
emailverify: {name:'EmailVerify',desc:'Validation email temps réel MX+SMTP+disposable.',price:'$49/mo',url:'/products/workspace.html',category:'Email Intelligence'},
|
||||
blacklistguard: {name:'BlacklistGuard',desc:'Monitoring 100+ listes de blocage RBL + alertes.',price:'$29/mo',url:'/products/workspace.html',category:'Email Intelligence'},
|
||||
reputationai: {name:'RéputationAI',desc:'Score réputation domaine + historique.',price:'$39/mo',url:'/products/workspace.html',category:'Email Intelligence'},
|
||||
copyai: {name:'CopyAI WEVAL',desc:'Copywriting IA - emails, sujets, CTA. Cloud souveraine.',price:'$39/mo',url:'/products/workspace.html',category:'IA'},
|
||||
dataharvest: {name:'DataInsight',desc:'Intelligence d\'enrichissement de donn\u00e9es B2B.',price:'$99/mo',url:'/products/workspace.html',category:'Data'},
|
||||
smsforge: {name:'SMSForge',desc:'SMS marketing international (190+ pays).',price:'$49/mo',url:'/products/workspace.html',category:'Marketing'},
|
||||
adscontrol: {name:'AdsControl',desc:'Multi-channel ads FB/Google/LinkedIn/TikTok.',price:'$99/mo',url:'/products/workspace.html',category:'Marketing'},
|
||||
wevalcrm: {name:'WEVAL CRM',desc:'CRM leger pipeline + contacts + IA.',price:'Gratuit + Pro $29/mo',url:'/products/workspace.html',category:'Business'},
|
||||
canvasai: {name:'CanvasAI',desc:'Design IA - visuels, bannieres, logos.',price:'$29/mo',url:'/products/workspace.html',category:'IA'},
|
||||
devforge: {name:'DevForge AI',desc:'12 modules dev: specs, tests, code gen, API design, security review.',price:'$199/mo',url:'/products/workspace.html',category:'Dev'},
|
||||
ethica: {name:'Ethica B2B',desc:'Plateforme HCP internationale. 5775+ medecins vérifiés.',price:'$299/mo',url:'/products/workspace.html',category:'Data'},
|
||||
arsenal: {name:'Arsenal Framework',desc:'150+ ecrans. ERP Intelligence, Brain Engine, MTA. Enterprise.',price:'2000-$9,999/mo',url:'/products/arsenal.html',category:'Flagship'},
|
||||
wevads: {name:'WEVADS Platform',desc:'Infrastructure email complete. Brain Engine centaines de configs. 6.65M contacts.',price:'Enterprise',url:'/products/wevads.html',category:'Flagship'},
|
||||
};
|
||||
|
||||
function getProductList() {
|
||||
return Object.values(PRODUCTS_KB).map(p => `<b>${p.name}</b> - ${p.desc} (<a href="${p.url}">${p.price}</a>)`).join('<br><br>');
|
||||
}
|
||||
|
||||
function findProducts(query) {
|
||||
const q = query.toLowerCase();
|
||||
const matches = [];
|
||||
const keywords = {
|
||||
email: ['deliVerscore','emailverify','blacklistguard','reputationai','mailwarm','outreachai'],
|
||||
ia: ['gpu','contentfactory','proposalai','blueprintai','copyai','canvasai','devforge','wevia'],
|
||||
data: ['medreach','leadforge','dataharvest','ethica'],
|
||||
security: ['sentinel','blacklistguard'],
|
||||
ecommerce: ['storeforge'],
|
||||
marketing: ['smsforge','adscontrol','outreachai'],
|
||||
crm: ['wevalcrm'],
|
||||
sap: ['arsenal'],
|
||||
enterprise: ['arsenal','wevads','wevia'],
|
||||
gratuit: [],
|
||||
prix: [],
|
||||
maroc: ['medreach','ethica','smsforge','storeforge'],
|
||||
};
|
||||
|
||||
for (const [kw, ids] of Object.entries(keywords)) {
|
||||
if (q.includes(kw)) ids.forEach(id => { if (!matches.includes(id)) matches.push(id); });
|
||||
}
|
||||
|
||||
// Also search in product names and descriptions
|
||||
for (const [id, p] of Object.entries(PRODUCTS_KB)) {
|
||||
if (p.name.toLowerCase().includes(q) || p.desc.toLowerCase().includes(q) || p.category.toLowerCase().includes(q)) {
|
||||
if (!matches.includes(id)) matches.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
return matches.map(id => PRODUCTS_KB[id]).filter(Boolean);
|
||||
}
|
||||
|
||||
function botReply(userMsg) {
|
||||
const q = userMsg.toLowerCase();
|
||||
|
||||
// Greetings
|
||||
if (/^(bonjour|salut|hello|hi|hey|coucou)/.test(q)) {
|
||||
return `Bonjour ! Je suis l'assistant WEVAL. Nous avons <b>62 produits SaaS</b> en production. Comment puis-je vous aider ?<div class="quick-btns"><span class="quick-btn" onclick="askBot('Quels sont vos produits?')">Voir les produits</span><span class="quick-btn" onclick="askBot('email délivrabilité')">Email</span><span class="quick-btn" onclick="askBot('intelligence artificielle')">IA</span><span class="quick-btn" onclick="askBot('prix')">Tarifs</span></div>`;
|
||||
}
|
||||
|
||||
// List all
|
||||
if (/tous|tout|liste|produit|service|catalogue|quoi/.test(q)) {
|
||||
const cats = {};
|
||||
Object.values(PRODUCTS_KB).forEach(p => { if (!cats[p.category]) cats[p.category]=[]; cats[p.category].push(p); });
|
||||
let html = `Voici nos <b>${Object.keys(PRODUCTS_KB).length} produits</b> par categorie :<br><br>`;
|
||||
for (const [cat, prods] of Object.entries(cats)) {
|
||||
html += `<b>${cat}</b><br>`;
|
||||
prods.forEach(p => { html += `• <a href="${p.url}">${p.name}</a> - ${p.price}<br>`; });
|
||||
html += '<br>';
|
||||
}
|
||||
html += `<div class="quick-btns"><span class="quick-btn" onclick="askBot('email')">Email</span><span class="quick-btn" onclick="askBot('IA')">IA</span><span class="quick-btn" onclick="askBot('essayer gratuit')">Gratuit</span></div>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
// Pricing
|
||||
if (/prix|tarif|cout|combien|pricing|gratuit|free/.test(q)) {
|
||||
const free = Object.values(PRODUCTS_KB).filter(p => p.price.toLowerCase().includes('gratuit'));
|
||||
let html = `<b>Produits avec plan gratuit (${free.length}):</b><br>`;
|
||||
free.forEach(p => { html += `• <a href="${p.url}">${p.name}</a> - ${p.price}<br>`; });
|
||||
html += `<br>Tous les prix sont sur <a href="/products/">notre catalogue</a>. Besoin d'un devis personnalisé ?<div class="quick-btns"><span class="quick-btn" onclick="askBot('demo')">Demander une demo</span><span class="quick-btn" onclick="window.location.href='/booking.html'">Nous contacter</span></div>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
// Demo / essai
|
||||
if (/demo|essai|tester|try|commencer|start/.test(q)) {
|
||||
return `Pour commencer gratuitement :<br><br>1. <a href="/products/workspace.html">Ouvrir le Workspace</a> et créer un compte<br>2. Vous aurez acces a tous les produits gratuits<br>3. Testez DeliverScore, WEVIA Inference, Content Factory...<br><br>Ou <a href="#cta">contactez-nous</a> pour une demo personnalisée !<div class="quick-btns"><span class="quick-btn" onclick="window.location.href='/products/workspace.html'">Ouvrir Workspace</span></div>`;
|
||||
}
|
||||
|
||||
// Specific product search
|
||||
const matches = findProducts(q);
|
||||
if (matches.length > 0) {
|
||||
let html = `J'ai trouve <b>${matches.length} produit(s)</b> correspondant :<br><br>`;
|
||||
matches.slice(0, 6).forEach(p => {
|
||||
html += `<b><a href="${p.url}">${p.name}</a></b><br>${p.desc}<br><i>${p.price}</i><br><br>`;
|
||||
});
|
||||
if (matches.length > 6) html += `...et ${matches.length - 6} autres.<br>`;
|
||||
html += `<div class="quick-btns"><span class="quick-btn" onclick="window.location.href='${matches[0].url}'">Voir ${matches[0].name}</span><span class="quick-btn" onclick="askBot('prix')">Tarifs</span></div>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
// Default - suggest contacting or browsing
|
||||
return `Je n'ai pas trouve de produit spécifique pour "${userMsg}". Nos 62 produits couvrent : Email Intelligence, IA/GPU, Data, Security, Marketing, E-commerce et Dev.<br><br><div class="quick-btns"><span class="quick-btn" onclick="askBot('tous les produits')">Voir tout</span><span class="quick-btn" onclick="window.location.href='/booking.html'">Contacter WEVAL</span><span class="quick-btn" onclick="window.location.href='/products/workspace.html'">Workspace</span></div>`;
|
||||
}
|
||||
|
||||
function toggleBot() {
|
||||
const p = document.getElementById('weval-bot-panel');
|
||||
p.classList.toggle('open');
|
||||
if (p.classList.contains('open') && document.getElementById('weval-bot-msgs').children.length === 0) {
|
||||
addMsg('bot', `Bonjour ! Je suis l'assistant produits WEVAL. Nous proposons <b>37 SaaS</b> en production. Que recherchez-vous ?<div class="quick-btns"><span class="quick-btn" onclick="askBot('Tous les produits')">Catalogue</span><span class="quick-btn" onclick="askBot('email')">Email</span><span class="quick-btn" onclick="askBot('intelligence artificielle')">IA</span><span class="quick-btn" onclick="askBot('gratuit')">Gratuit</span><span class="quick-btn" onclick="askBot('demo')">Demo</span></div>`);
|
||||
}
|
||||
document.getElementById('weval-bot-badge').style.display = 'none';
|
||||
}
|
||||
|
||||
function addMsg(type, html) {
|
||||
const msgs = document.getElementById('weval-bot-msgs');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'bot-msg ' + type;
|
||||
div.innerHTML = html;
|
||||
msgs.appendChild(div);
|
||||
msgs.scrollTop = msgs.scrollHeight;
|
||||
}
|
||||
|
||||
function askBot(q) {
|
||||
document.getElementById('weval-bot-input').value = q;
|
||||
sendBot();
|
||||
}
|
||||
|
||||
function sendBot() {
|
||||
const input = document.getElementById('weval-bot-input');
|
||||
const msg = input.value.trim();
|
||||
if (!msg) return;
|
||||
input.value = '';
|
||||
addMsg('user', msg);
|
||||
|
||||
// Show typing
|
||||
const msgs = document.getElementById('weval-bot-msgs');
|
||||
const typing = document.createElement('div');
|
||||
typing.className = 'bot-typing';
|
||||
typing.innerHTML = '<span></span><span></span><span></span>';
|
||||
msgs.appendChild(typing);
|
||||
msgs.scrollTop = msgs.scrollHeight;
|
||||
|
||||
setTimeout(() => {
|
||||
typing.remove();
|
||||
addMsg('bot', botReply(msg));
|
||||
}, 400 + Math.random() * 600);
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:2rem;justify-content:center;flex-wrap:wrap;padding:1.5rem 4%;background:rgba(255,255,255,.02);border-top:1px solid rgba(255,255,255,.04);margin:2rem 0"><div style="display:flex;align-items:center;gap:.4rem;font-size:.8rem;color:#7a8ba5"><strong style="color:#edf2f7">44</strong> produits SaaS</div><div style="display:flex;align-items:center;gap:.4rem;font-size:.8rem;color:#7a8ba5"><strong style="color:#edf2f7">Cloud</strong> souverain</div><div style="display:flex;align-items:center;gap:.4rem;font-size:.8rem;color:#7a8ba5"><strong style="color:#edf2f7">API</strong> REST</div><div style="display:flex;align-items:center;gap:.4rem;font-size:.8rem;color:#7a8ba5"><strong style="color:#edf2f7">RGPD</strong> conforme</div><div style="display:flex;align-items:center;gap:.4rem;font-size:.8rem;color:#7a8ba5"><strong style="color:#edf2f7">France</strong> · Maroc · États-Unis · International</div></div>
|
||||
<style>.dm-box{background:#0c1222;border:1px solid rgba(255,255,255,.06);border-radius:14px;padding:1.5rem;margin:2rem auto;max-width:800px}.dm-box h3{font-size:1rem;font-weight:600;margin-bottom:1rem;display:flex;align-items:center;gap:.5rem}.dm-row{display:flex;gap:.6rem;margin-bottom:1rem}.dm-row input{flex:1;background:rgba(0,0,0,.3);border:1px solid rgba(255,255,255,.08);border-radius:8px;padding:.6rem .8rem;color:#edf2f7;font-family:Outfit;font-size:.85rem;outline:none}.dm-row button{background:var(--a,#7c5cfc);color:#05080f;border:none;border-radius:8px;padding:.6rem 1.2rem;font-weight:700;cursor:pointer;font-family:Outfit;font-size:.85rem;white-space:nowrap}.dm-out{min-height:80px;padding:1rem;background:rgba(0,0,0,.2);border-radius:8px;font-size:.82rem;color:#edf2f7;line-height:1.6}.dm-out strong{color:#fff}.dm-out pre{background:rgba(0,0,0,.3);padding:.5rem;border-radius:6px;font-size:.75rem;margin:.4rem 0;overflow-x:auto}.dm-ld{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--a,#7c5cfc);margin:0 2px;animation:dmp .8s infinite}.dm-ld:nth-child(2){animation-delay:.2s}.dm-ld:nth-child(3){animation-delay:.4s}@keyframes dmp{0%,100%{opacity:1}50%{opacity:.3}}</style>
|
||||
<div class="dm-box" id="demo"><h3>⚡ Arsenal Framework — Demo Live</h3>
|
||||
<div class="dm-row"><input id="dm-arsenal" placeholder="Décrivez votre besoin ERP" onkeydown="if(event.key==='Enter')dm_arsenal()"><button onclick="dm_arsenal()">Tester</button></div>
|
||||
<div class="dm-out" id="dmo-arsenal">Testez Arsenal Framework en direct — aucune inscription.</div></div>
|
||||
<script>
|
||||
async function dm_arsenal(){var q=document.getElementById("dm-arsenal").value.trim();if(!q)return;var o=document.getElementById("dmo-arsenal");o.innerHTML='<span class="dm-ld"></span><span class="dm-ld"></span><span class="dm-ld"></span>';try{var r=await fetch("/api/weval-ia-fast.php",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:"[INSTRUCTION SYSTEME: Reponds de facon structurée et professionnelle.]\n\nDemande utilisateur: Analyse le besoin ERP et propose une solution Arsenal: "+q,mode:"full"})});var j=await r.json();o.innerHTML=(j.response||"Erreur").replace(/\*\*(.*?)\*\*/g,"<strong>$1</strong>").replace(/\n\n/g,"<br><br>");}catch(e){o.innerHTML="Erreur: "+e.message}}
|
||||
</script>
|
||||
<script defer src=/assets/dm-enhance.js></script></body></html>
|
||||
<!-- WEVAL Self-Service Inject -->
|
||||
<script>
|
||||
if(window===window.top){
|
||||
// Replace all contact mailto links with signup portal
|
||||
document.querySelectorAll('a[href*="mailto:"]').forEach(a => {
|
||||
if(a.classList.contains('btn-p') || a.classList.contains('btn-f') || a.classList.contains('btn-n') || a.classList.contains('btn-nav') || a.classList.contains('btn-primary') || a.classList.contains('btn-price-fill') || a.textContent.includes('Commencer') || a.textContent.includes('Commander') || a.textContent.includes('Essayer') || a.textContent.includes('Souscrire') || a.textContent.includes('Créer') || a.textContent.includes('Obtenir') || a.textContent.includes('Démarrer') || a.textContent.includes('Rejoindre')) {
|
||||
a.href = '/products/workspace.html';
|
||||
a.removeAttribute('target');
|
||||
}
|
||||
});
|
||||
// Add floating CTA
|
||||
const bar = document.createElement('div');
|
||||
bar.innerHTML = '<div style="position:fixed;bottom:0;left:0;right:0;z-index:999;background:rgba(5,8,15,0.95);backdrop-filter:blur(10px);border-top:1px solid rgba(0,201,167,0.15);padding:0.6rem 4%;display:flex;justify-content:space-between;align-items:center"><div style="font-size:0.82rem;color:#7a8ba5"><strong style="color:#edf2f7">WEVAL Products</strong> · <span style="color:#00c9a7">Self-service</span> · Inscription en 30 secondes</div><a href="/products/workspace.html" style="background:#00c9a7;color:#05080f;padding:0.5rem 1.2rem;border-radius:6px;font-weight:700;font-size:0.82rem;text-decoration:none">Créer mon compte gratuit →</a></div>';
|
||||
if(window.self===window.top){document.body.appendChild(bar);}
|
||||
document.body.style.paddingBottom = '52px';
|
||||
}
|
||||
</script>
|
||||
336
arsenal-history/arsenal-110313.html
Normal file
@@ -0,0 +1,336 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr"><head>
|
||||
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Arsenal — Framework ERP Intelligence | Fill-Gap + IA + Reporting</title>
|
||||
<meta name="description" content="Arsenal — Framework ERP Intelligence - Solutions IA souveraines pour entreprises">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>:root{--bg:#05080f;--s:#0c1222;--a:#f0c674;--a15:rgba(240,198,116,.15);--p:#7c5cfc;--t:#00c9a7;--r:#ff6b6b;--bl:#4ea8de;--sv:#7a8ba5;--w:#edf2f7;--b:rgba(240,198,116,.08)}*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Outfit',sans-serif;background:var(--bg);color:var(--w);overflow-x:hidden}nav{position:fixed;top:0;width:100%;padding:1rem 4%;display:flex;justify-content:space-between;align-items:center;z-index:100;backdrop-filter:blur(20px);background:rgba(5,8,15,.85);border-bottom:1px solid var(--b)}.logo{font-weight:800;font-size:1.5rem}.logo span{color:var(--a)}.btn-n{background:var(--a);color:var(--bg);padding:.55rem 1.3rem;border-radius:7px;font-weight:700;font-size:.82rem;text-decoration:none}.hero{min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:7rem 4% 4rem;position:relative}.hero::after{content:'';position:absolute;top:5%;left:50%;transform:translateX(-50%);width:700px;height:700px;background:radial-gradient(circle,rgba(240,198,116,.06) 0%,transparent 70%);border-radius:50%;pointer-events:none}.badge{display:inline-flex;align-items:center;gap:.5rem;background:var(--a15);border:1px solid rgba(240,198,116,.2);border-radius:100px;padding:.35rem 1rem;font-size:.75rem;font-weight:600;color:var(--a);margin-bottom:2rem}h1{font-size:3.4rem;font-weight:800;line-height:1.08;letter-spacing:-.04em;margin-bottom:1.2rem;max-width:800px}h1 em{font-style:normal;color:var(--a)}.sub{font-size:1.05rem;color:var(--sv);max-width:600px;line-height:1.7;margin-bottom:2rem}.btns{display:flex;gap:1rem;flex-wrap:wrap;justify-content:center}.btn-p{background:var(--a);color:var(--bg);padding:.85rem 2rem;border-radius:8px;font-weight:700;text-decoration:none;transition:all .3s}.btn-p:hover{transform:translateY(-2px);box-shadow:0 8px 30px rgba(240,198,116,.25)}.btn-o{background:transparent;color:var(--w);padding:.85rem 2rem;border-radius:8px;text-decoration:none;border:1px solid rgba(255,255,255,.12)}.sec{padding:5rem 4%;max-width:1200px;margin:0 auto}.stag{font-family:'Space Mono',monospace;font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.2em;color:var(--a);margin-bottom:1rem}h2{font-size:2.2rem;font-weight:800;letter-spacing:-.03em;margin-bottom:.8rem}.sd{color:var(--sv);font-size:.95rem;line-height:1.7;max-width:560px;margin-bottom:2.5rem}.stats{display:grid;grid-template-columns:repeat(6,1fr);gap:1px;background:rgba(240,198,116,.06);border-radius:14px;overflow:hidden;margin:2rem 0}.stat{padding:1.3rem;text-align:center;background:rgba(12,18,34,.95)}.stat-n{font-family:'Space Mono',monospace;font-size:1.6rem;font-weight:700;color:var(--a)}.stat-l{font-size:.68rem;color:var(--sv);margin-top:.15rem}.g3{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}.g2{display:grid;grid-template-columns:1fr 1fr;gap:1.2rem}.cd{background:var(--s);border:1px solid var(--b);border-radius:14px;padding:1.5rem;transition:all .3s}.cd:hover{border-color:rgba(240,198,116,.25);transform:translateY(-2px)}.cd h3{font-size:.95rem;font-weight:600;margin:.6rem 0 .3rem}.cd p{font-size:.8rem;color:var(--sv);line-height:1.55}.erps{display:flex;flex-wrap:wrap;gap:.5rem;margin:1.5rem 0}.erp{font-family:'Space Mono',monospace;font-size:.72rem;font-weight:700;padding:.4rem .8rem;border-radius:8px;background:rgba(240,198,116,.08);color:var(--a);border:1px solid rgba(240,198,116,.12);transition:all .2s}.erp:hover{background:rgba(240,198,116,.2)}.erp.active{background:rgba(240,198,116,.2);border-color:rgba(240,198,116,.4)}.vs{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;margin:2rem 0}.vs-card{background:var(--s);border:1px solid var(--b);border-radius:14px;padding:1.5rem}.vs-card.us{border-color:rgba(240,198,116,.2);background:rgba(240,198,116,.02)}.vs-card h3{font-size:.95rem;font-weight:700;margin-bottom:.8rem}.vs-items{font-size:.8rem;color:var(--sv);line-height:1.8}.cta{text-align:center;padding:4rem 2rem;margin:2rem 4%;background:linear-gradient(135deg,var(--s),rgba(240,198,116,.04));border:1px solid rgba(240,198,116,.1);border-radius:20px}.cta p{color:var(--sv);max-width:500px;margin:.5rem auto 1.5rem}footer{padding:2rem 4%;max-width:1200px;margin:0 auto;display:flex;justify-content:space-between;border-top:1px solid rgba(255,255,255,.04);font-size:.75rem;color:var(--sv)}footer a{color:var(--a);text-decoration:none}@media(max-width:900px){h1{font-size:2.2rem}.g3,.g2,.vs,.stats{grid-template-columns:1fr}footer{flex-direction:column;gap:.5rem;text-align:center}}input,select,textarea{background:#0b0d14!important;color:#e2e8f0!important;border:1px solid #1e293b!important;border-radius:8px!important}input::placeholder{color:#475569!important}</style><style>/* Hide nav in iframe */
|
||||
@media all{.in-iframe nav{display:none!important}.in-iframe .hero{padding-top:3rem!important;min-height:auto!important}.in-iframe footer{display:none!important}.in-iframe .cta{display:none!important}.in-iframe .wv-links{display:none!important}.wv-links{display:none!important}}</style>
|
||||
<script>if(window!==window.top)document.documentElement.classList.add('in-iframe');</script>
|
||||
<link rel="canonical" href="https://weval-consulting.com/products/arsenal.html">
|
||||
<meta property="og:title" content="Arsenal — Framework ERP Intelligence | Fill-Gap + IA + Repor">
|
||||
<meta property="og:description" content="Arsenal — Framework ERP Intelligence - Solutions IA souveraines pour entreprises">
|
||||
<meta property="og:url" content="https://weval-consulting.com/products/arsenal.html">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="WEVAL Consulting">
|
||||
<meta property="og:image" content="https://weval-consulting.com/assets/logo-weval-png-DChrMGao.png">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Arsenal — Framework ERP Intelligence | Fill-Gap + IA + Repor">
|
||||
<meta name="twitter:description" content="Arsenal — Framework ERP Intelligence - Solutions IA souveraines pour entreprises">
|
||||
<link rel="alternate" hreflang="fr" href="https://weval-consulting.com/products/arsenal.html">
|
||||
<link rel="alternate" hreflang="x-default" href="https://weval-consulting.com/products/arsenal.html">
|
||||
<link rel="stylesheet" href="/assets/dark-iframe.css"></head><body>
|
||||
<nav><div class="logo" style="display:flex;align-items:center;gap:8px"><img src="/assets/logo-arsenal.svg" alt="" style="width:32px;height:32px">Arsenal<span>.</span></div><a href="#cta" class="btn-n">Demander une démo →</a></nav>
|
||||
|
||||
<section class="hero">
|
||||
<div class="badge">Framework ERP Intelligence — En production</div>
|
||||
<h1>Votre ERP fait 80%.<br>Arsenal fait les <em>20% manquants</em></h1>
|
||||
<p class="sub">150+ écrans modulaires, Brain Engine IA, reporting temps réel. Se branche sur n'importe quel ERP (SAP, Odoo, Oracle, Salesforce, custom) pour combler les gaps fonctionnels sans remplacer l'existant.</p>
|
||||
<div class="btns"><a href="#modules" class="btn-p">Explorer les modules →</a><a href="#cta" class="btn-o">Demander une demo</a></div></section>
|
||||
|
||||
<div class="sec"><div class="stats">
|
||||
<div class="stat"><div class="stat-n">150+</div><div class="stat-l">Écrans HTML</div></div>
|
||||
<div class="stat"><div class="stat-n">38</div><div class="stat-l">Crons automatiques</div></div>
|
||||
<div class="stat"><div class="stat-n">6</div><div class="stat-l">APIs core sync</div></div>
|
||||
<div class="stat"><div class="stat-n">500+</div><div class="stat-l">Brain configs IA</div></div>
|
||||
<div class="stat"><div class="stat-n">7.3M</div><div class="stat-l">Contacts gérés</div></div>
|
||||
<div class="stat"><div class="stat-n">0</div><div class="stat-l">Pages cassées</div></div></div></div>
|
||||
|
||||
<section class="sec"><div class="stag">ERP compatibles</div><h2>Se branche sur tout</h2>
|
||||
<p class="sd">Arsenal n'est pas un ERP. C'est la couche d'intelligence qui se greffe sur votre ERP existant. Plug-and-play, zéro migration.</p>
|
||||
<div class="erps">
|
||||
<span class="erp active">WEVADS ✓ Prouvé</span>
|
||||
<span class="erp">SAP S/4HANA</span>
|
||||
<span class="erp">SAP ECC</span>
|
||||
<span class="erp">Oracle EBS</span>
|
||||
<span class="erp">Oracle Fusion</span>
|
||||
<span class="erp">Odoo</span>
|
||||
<span class="erp">Sage X3</span>
|
||||
<span class="erp">Microsoft Dynamics</span>
|
||||
<span class="erp">Salesforce</span>
|
||||
<span class="erp">JD Edwards</span>
|
||||
<span class="erp">Custom ERP</span>
|
||||
<span class="erp">Legacy Systems</span></div></section>
|
||||
|
||||
<section class="sec" id="modules"><div class="stag">Modules</div><h2>10 catégories de modules</h2>
|
||||
<p class="sd">Chaque module est un écran autonome avec API, données temps réel et IA intégrée. Activez uniquement ce dont vous avez besoin.</p>
|
||||
<div class="g3">
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Brain Engine IA</h3><p>centaines de configurations IA, 9 winners sacrés, smart failover 11 providers. Optimisation automatique des processus par machine learning. Prédiction, scoring, anomaly detection.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Data Intelligence</h3><p>Data Manager, Drill-down API, cross-database queries via connecteur. Connecte et agrège les données de toutes vos sources : ERP, CRM, fichiers, APIs externes.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><circle cx="6" cy="6" r="1" fill="currentColor"/><circle cx="6" cy="18" r="1" fill="currentColor"/></svg></div><h3>Reporting Avancé</h3><p>Dashboards temps réel, KPIs customisables, alertes automatiques. Ce que votre ERP ne montre pas — Arsenal le visualise.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Pipeline Automation</h3><p>38 crons orchestrés, workflows E2E, queue workers multi-provider. Automatisez les processus que votre ERP ne gère pas nativement.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Data Intelligence Factory</h3><p>Extraction de données web automatisée. Veille concurrentielle, enrichissement CRM, monitoring prix. 6+ sources prouvées.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15 15 0 014 10 15 15 0 01-4 10 15 15 0 01-4-10A15 15 0 0112 2z"/></svg></div><h3>Account Factory</h3><p>Création automatisée de comptes multi-providers (Exchange, cloud, DNS). centaines de comptes Exchange, 47 cloud, 191 FreeDNS gérés.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Offer Engine</h3><p>Gestion des offres, sponsors, payouts. Séparation multi-source, drill-down par offre. 85 offres actives, $265 max payout.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div><h3>Scout Intelligence</h3><p>Reconnaissance et analyse d'infrastructure. Hostname mapping, IP tracking, target identification. Lookalike engine 178 personas.</p></div>
|
||||
<div class="cd"><div style="font-size:1.3rem"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></div><h3>Sentinel Security</h3><p>Monitoring sécurité, exec API, commande à distance sécurisée. Cyber scanner score 73/100, OWASP, fail2ban intégré.</p></div></div></section>
|
||||
|
||||
<section class="sec"><div class="stag">Use Cases</div><h2>Arsenal sur le terrain</h2>
|
||||
<div class="g2">
|
||||
<div class="cd" style="border-left:3px solid var(--a)"><h3>Arsenal × Marketing Digital (WEVADS)</h3><p>150+ écrans déployés. processus complet send→open→click→conversion. Brain Engine optimisé les configs par ISP. Optimisation automatique milliers de comptes. Résultat : système complet là où l'ERP natif ne gère pas le marketing digital.</p></div>
|
||||
<div class="cd" style="border-left:3px solid var(--p)"><h3>Arsenal × Manufacturing (SAP)</h3><p>Fill-gap reporting production : OEE temps réel, prédiction pannes IA, tableau de bord qualité, alertes seuils automatiques. Ce que SAP PP ne montre pas nativement.</p></div>
|
||||
<div class="cd" style="border-left:3px solid var(--t)"><h3>Arsenal × Retail (Odoo)</h3><p>Intelligence commerciale : scoring client IA, prédiction stock, analytics promotion, heatmap ventes géographique. Complète Odoo Sales/Inventory.</p></div>
|
||||
<div class="cd" style="border-left:3px solid var(--bl)"><h3>Arsenal × Finance (Oracle)</h3><p>Reporting financier augmenté : consolidation multi-entités, prévision trésorerie IA, alertes anomalies comptables, dashboards CFO temps réel.</p></div>
|
||||
<div class="cd" style="border-left:3px solid var(--r)"><h3>Arsenal × Supply Chain</h3><p>Visibilité E2E : tracking temps réel, optimisation routes IA, prédiction délais, alertes rupture stock. Complète n'importe quel WMS/TMS.</p></div>
|
||||
<div class="cd" style="border-left:3px solid var(--a)"><h3>Arsenal × RH (Dynamics)</h3><p>Analytics RH avancés : prédiction turnover, scoring candidats IA, dashboard compétences, planning formation automatisé.</p></div></div></section>
|
||||
|
||||
<section class="sec"><div class="stag">Différenciation</div><h2>Pourquoi pas un intégrateur classique ?</h2>
|
||||
<div class="vs">
|
||||
<div class="vs-card us"><h3 style="color:var(--a)">Arsenal (WEVAL)</h3><div class="vs-items"> Framework réutilisable — déployé en semaines, pas en mois<br> 150+ écrans prêts à brancher<br> IA native (Brain Engine centaines de configs)<br> Se greffe sur l'ERP existant — zéro migration<br> Coût 10-50x inférieur au custom SAP<br> Mises à jour continues incluses<br> Cloud souverain — vos données chez vous</div></div>
|
||||
<div class="vs-card"><h3 style="color:var(--sv)">Intégrateurs classiques (développement sur mesure)</h3><div class="vs-items"> Custom dev from scratch — 6-18 mois<br> Chaque écran facturé séparément<br> Pas d'IA intégrée nativement<br> Souvent impose une migration ERP<br> Budget 100K-500K$+<br> Maintenance facturée en plus<br> Cloud US — données hors contrôle</div></div></div></section>
|
||||
|
||||
<section class="sec"><div class="stag">Architecture</div><h2>Stack technique</h2>
|
||||
<div class="g3">
|
||||
<div class="cd"><h3>Frontend</h3><p>150+ écrans HTML5 standalone. Responsive. Arsenal-common.js (25KB, 6 modules partagés). Zéro framework lourd — performance maximale.</p></div>
|
||||
<div class="cd"><h3>Backend</h3><p>PHP APIs + PostgreSQL. Dual vhosts Apache (5821 + 5890). connecteur bridge cross-databases. 38 crons orchestrés.</p></div>
|
||||
<div class="cd"><h3>IA Layer</h3><p>Brain Engine : 11 providers IA (Cloud Tier 1, Cloud Tier 2, WEVIA, Gemini, WEVIA...) avec smart failover. Cloud souverain GPU dédié 20GB.</p></div></div></section>
|
||||
|
||||
<div class="cta" id="cta"><div class="stag">Deploy</div><h2>Arsenal sur votre ERP en 2 semaines</h2><p>Audit gratuit de votre ERP. On identifié les gaps et on déploie les modules Arsenal adaptés. Aucune migration nécessaire.</p><a href="#cta" class="btn-p">Demander l'audit gratuit →</a></div>
|
||||
<footer><div><strong>Arsenal</strong> · <a href="/products/">WEVAL Products</a></div><div>France · Maroc · États-Unis · International</div></footer><!-- WEVAL Product Assistant Chatbot Widget -->
|
||||
<div id="weval-bot-widget" style="position:fixed;bottom:20px;right:20px;z-index:9999;font-family:'Inter',system-ui,sans-serif">
|
||||
<style>
|
||||
#weval-bot-btn{width:56px;height:56px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#8b5cf6);border:none;cursor:pointer;box-shadow:0 4px 24px rgba(99,102,241,.4);display:flex;align-items:center;justify-content:center;transition:transform .2s,box-shadow .2s}
|
||||
#weval-bot-btn:hover{transform:scale(1.08);box-shadow:0 6px 32px rgba(99,102,241,.5)}
|
||||
#weval-bot-btn svg{width:28px;height:28px;fill:#fff}
|
||||
#weval-bot-badge{position:absolute;top:-2px;right:-2px;width:14px;height:14px;background:#22c55e;border-radius:50%;border:2px solid #fff;animation:pulse-badge 2s infinite}
|
||||
@keyframes pulse-badge{0%,100%{opacity:1}50%{opacity:.5}}
|
||||
#weval-bot-panel{display:none;position:fixed;bottom:90px;right:20px;width:380px;max-height:520px;background:#0f1629;border:1px solid rgba(99,102,241,.2);border-radius:16px;box-shadow:0 12px 48px rgba(0,0,0,.5);overflow:hidden;flex-direction:column}
|
||||
#weval-bot-panel.open{display:flex}
|
||||
#weval-bot-head{background:linear-gradient(135deg,#6366f1,#8b5cf6);padding:14px 18px;display:flex;align-items:center;gap:10px}
|
||||
#weval-bot-head .avatar{width:36px;height:36px;border-radius:50%;background:rgba(255,255,255,.2);display:flex;align-items:center;justify-content:center;font-size:18px}
|
||||
#weval-bot-head .info{flex:1;color:#fff}
|
||||
#weval-bot-head .info .name{font-weight:600;font-size:14px}
|
||||
#weval-bot-head .info .status{font-size:11px;opacity:.8}
|
||||
#weval-bot-close{background:none;border:none;color:rgba(255,255,255,.7);cursor:pointer;font-size:20px;padding:4px}
|
||||
#weval-bot-msgs{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:10px;min-height:280px;max-height:360px}
|
||||
.bot-msg{max-width:85%;padding:10px 14px;border-radius:12px;font-size:13px;line-height:1.5;word-wrap:break-word}
|
||||
.bot-msg.bot{background:rgba(99,102,241,.12);color:#e2e8f0;border-bottom-left-radius:4px;align-self:flex-start}
|
||||
.bot-msg.user{background:#6366f1;color:#fff;border-bottom-right-radius:4px;align-self:flex-end}
|
||||
.bot-msg a{color:#818cf8;text-decoration:underline}
|
||||
.bot-typing{display:flex;gap:4px;padding:10px 14px;align-self:flex-start}
|
||||
.bot-typing span{width:6px;height:6px;background:#6366f1;border-radius:50%;animation:bounce .6s infinite alternate}
|
||||
.bot-typing span:nth-child(2){animation-delay:.2s}
|
||||
.bot-typing span:nth-child(3){animation-delay:.4s}
|
||||
@keyframes bounce{to{opacity:.3;transform:translateY(-4px)}}
|
||||
#weval-bot-input-area{padding:10px 14px;border-top:1px solid rgba(255,255,255,.06);display:flex;gap:8px}
|
||||
#weval-bot-input{flex:1;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:8px 12px;color:#e2e8f0;font-size:13px;outline:none}
|
||||
#weval-bot-input::placeholder{color:rgba(255,255,255,.3)}
|
||||
#weval-bot-send{background:#6366f1;border:none;border-radius:8px;padding:8px 12px;color:#fff;cursor:pointer;font-size:13px;font-weight:600}
|
||||
#weval-bot-send:hover{background:#5558e6}
|
||||
.quick-btns{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
|
||||
.quick-btn{background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.25);color:#a5b4fc;padding:5px 10px;border-radius:6px;font-size:11px;cursor:pointer;transition:all .15s}
|
||||
.quick-btn:hover{background:rgba(99,102,241,.3);color:#fff}
|
||||
</style>
|
||||
|
||||
<button id="weval-bot-btn" onclick="toggleBot()">
|
||||
<svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>
|
||||
<div id="weval-bot-badge"></div>
|
||||
</button>
|
||||
|
||||
<div id="weval-bot-panel">
|
||||
<div id="weval-bot-head">
|
||||
<div class="avatar">W</div>
|
||||
<div class="info">
|
||||
<div class="name">WEVAL Assistant</div>
|
||||
<div class="status">En ligne - 62 produits</div>
|
||||
</div>
|
||||
<button id="weval-bot-close" onclick="toggleBot()">×</button>
|
||||
</div>
|
||||
<div id="weval-bot-msgs"></div>
|
||||
<div id="weval-bot-input-area">
|
||||
<input id="weval-bot-input" placeholder="Posez une question sur nos produits..." onkeypress="if(event.key==='Enter')sendBot()">
|
||||
<button id="weval-bot-send" onclick="sendBot()">Envoyer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const PRODUCTS_KB = {
|
||||
deliVerscore: {name:'DeliverScore',desc:'Audit délivrabilité email - SPF/DKIM/DMARC/listes de blocage. Score + recommandations IA.',price:'Gratuit + Pro $49/mo',url:'/products/deliverscore.html',category:'Email Intelligence'},
|
||||
medreach: {name:'MedReach API',desc:'Base de 18596+ professionnels de santé vérifiés (Afrique, Europe, Moyen-Orient, Asie, Europe). API REST + export.',price:'Gratuit + Pro $299/mo',url:'/products/medreach.html',category:'Data'},
|
||||
gpu: {name:'WEVIA Inference',desc:'IA-as-a-Service. WEVIA Deep, WEVIA sur GPU dédié. API IA Cloud-compatible.',price:'Gratuit + Pro $99/mo',url:'/products/gpu-inference.html',category:'IA'},
|
||||
contentfactory: {name:'AI Content Factory',desc:'Génération de contenu IA - articles, fiches produits, LinkedIn. 6 templates.',price:'Gratuit + Pro $29/mo',url:'/products/content-factory.html',category:'IA'},
|
||||
proposalai: {name:'ProposalAI',desc:'Generateur de propositions commerciales qualité Big4. Brief -> propale en 30 sec.',price:'Gratuit + Pro $19/mo',url:'/products/proposalai.html',category:'IA'},
|
||||
blueprintai: {name:'BlueprintAI',desc:'Process docs, BPMN, CDC, blueprints L1/L2/L3, 8D, RACI.',price:'$25/mo',url:'/products/blueprintai.html',category:'IA'},
|
||||
storeforge: {name:'StoreForge',desc:'E-commerce IA. Boutique en 5 min. Descriptions IA, chatbot WEVIA, paiement Maroc.',price:'0-$29/mo',url:'/products/storeforge.html',category:'Commerce'},
|
||||
leadforge: {name:'LeadForge',desc:'B2B Lead Intelligence sur mesure. Leads vérifiés tous secteurs. 0.30-0.$50/lead.',price:'0.30-0.$49/lead',url:'/products/workspace.html',category:'Data'},
|
||||
mailwarm: {name:'MailWarm',desc:'Optimisation email automatique. 500+ seeds. Inbox 90%+ en 4-6 semaines.',price:'$29/compte/mo',url:'/products/workspace.html',category:'Email'},
|
||||
sentinel: {name:'Sentinel Monitor',desc:'Cyber monitoring PME - SSL, ports, DNS, OWASP. Alertes Telegram.',price:'Gratuit + Pro $49/mo',url:'/products/workspace.html',category:'Security'},
|
||||
outreachai: {name:'OutreachAI',desc:'Cold outreach IA. Upload leads -> IA personnalisé, envoi -> tracking.',price:'$199/mo',url:'/products/workspace.html',category:'Email'},
|
||||
wevia: {name:'WEVIA White-Label',desc:'Chatbot IA clé en main. Widget embed 5 min. KB custom, memoire, vision.',price:'100-$299/mo',url:'/products/wevia-whitelabel.html',category:'IA'},
|
||||
emailverify: {name:'EmailVerify',desc:'Validation email temps réel MX+SMTP+disposable.',price:'$49/mo',url:'/products/workspace.html',category:'Email Intelligence'},
|
||||
blacklistguard: {name:'BlacklistGuard',desc:'Monitoring 100+ listes de blocage RBL + alertes.',price:'$29/mo',url:'/products/workspace.html',category:'Email Intelligence'},
|
||||
reputationai: {name:'RéputationAI',desc:'Score réputation domaine + historique.',price:'$39/mo',url:'/products/workspace.html',category:'Email Intelligence'},
|
||||
copyai: {name:'CopyAI WEVAL',desc:'Copywriting IA - emails, sujets, CTA. Cloud souveraine.',price:'$39/mo',url:'/products/workspace.html',category:'IA'},
|
||||
dataharvest: {name:'DataInsight',desc:'Intelligence d\'enrichissement de donn\u00e9es B2B.',price:'$99/mo',url:'/products/workspace.html',category:'Data'},
|
||||
smsforge: {name:'SMSForge',desc:'SMS marketing international (190+ pays).',price:'$49/mo',url:'/products/workspace.html',category:'Marketing'},
|
||||
adscontrol: {name:'AdsControl',desc:'Multi-channel ads FB/Google/LinkedIn/TikTok.',price:'$99/mo',url:'/products/workspace.html',category:'Marketing'},
|
||||
wevalcrm: {name:'WEVAL CRM',desc:'CRM leger pipeline + contacts + IA.',price:'Gratuit + Pro $29/mo',url:'/products/workspace.html',category:'Business'},
|
||||
canvasai: {name:'CanvasAI',desc:'Design IA - visuels, bannieres, logos.',price:'$29/mo',url:'/products/workspace.html',category:'IA'},
|
||||
devforge: {name:'DevForge AI',desc:'12 modules dev: specs, tests, code gen, API design, security review.',price:'$199/mo',url:'/products/workspace.html',category:'Dev'},
|
||||
ethica: {name:'Ethica B2B',desc:'Plateforme HCP internationale. 5775+ medecins vérifiés.',price:'$299/mo',url:'/products/workspace.html',category:'Data'},
|
||||
arsenal: {name:'Arsenal Framework',desc:'150+ ecrans. ERP Intelligence, Brain Engine, MTA. Enterprise.',price:'2000-$9,999/mo',url:'/products/arsenal.html',category:'Flagship'},
|
||||
wevads: {name:'WEVADS Platform',desc:'Infrastructure email complete. Brain Engine centaines de configs. 6.65M contacts.',price:'Enterprise',url:'/products/wevads.html',category:'Flagship'},
|
||||
};
|
||||
|
||||
function getProductList() {
|
||||
return Object.values(PRODUCTS_KB).map(p => `<b>${p.name}</b> - ${p.desc} (<a href="${p.url}">${p.price}</a>)`).join('<br><br>');
|
||||
}
|
||||
|
||||
function findProducts(query) {
|
||||
const q = query.toLowerCase();
|
||||
const matches = [];
|
||||
const keywords = {
|
||||
email: ['deliVerscore','emailverify','blacklistguard','reputationai','mailwarm','outreachai'],
|
||||
ia: ['gpu','contentfactory','proposalai','blueprintai','copyai','canvasai','devforge','wevia'],
|
||||
data: ['medreach','leadforge','dataharvest','ethica'],
|
||||
security: ['sentinel','blacklistguard'],
|
||||
ecommerce: ['storeforge'],
|
||||
marketing: ['smsforge','adscontrol','outreachai'],
|
||||
crm: ['wevalcrm'],
|
||||
sap: ['arsenal'],
|
||||
enterprise: ['arsenal','wevads','wevia'],
|
||||
gratuit: [],
|
||||
prix: [],
|
||||
maroc: ['medreach','ethica','smsforge','storeforge'],
|
||||
};
|
||||
|
||||
for (const [kw, ids] of Object.entries(keywords)) {
|
||||
if (q.includes(kw)) ids.forEach(id => { if (!matches.includes(id)) matches.push(id); });
|
||||
}
|
||||
|
||||
// Also search in product names and descriptions
|
||||
for (const [id, p] of Object.entries(PRODUCTS_KB)) {
|
||||
if (p.name.toLowerCase().includes(q) || p.desc.toLowerCase().includes(q) || p.category.toLowerCase().includes(q)) {
|
||||
if (!matches.includes(id)) matches.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
return matches.map(id => PRODUCTS_KB[id]).filter(Boolean);
|
||||
}
|
||||
|
||||
function botReply(userMsg) {
|
||||
const q = userMsg.toLowerCase();
|
||||
|
||||
// Greetings
|
||||
if (/^(bonjour|salut|hello|hi|hey|coucou)/.test(q)) {
|
||||
return `Bonjour ! Je suis l'assistant WEVAL. Nous avons <b>62 produits SaaS</b> en production. Comment puis-je vous aider ?<div class="quick-btns"><span class="quick-btn" onclick="askBot('Quels sont vos produits?')">Voir les produits</span><span class="quick-btn" onclick="askBot('email délivrabilité')">Email</span><span class="quick-btn" onclick="askBot('intelligence artificielle')">IA</span><span class="quick-btn" onclick="askBot('prix')">Tarifs</span></div>`;
|
||||
}
|
||||
|
||||
// List all
|
||||
if (/tous|tout|liste|produit|service|catalogue|quoi/.test(q)) {
|
||||
const cats = {};
|
||||
Object.values(PRODUCTS_KB).forEach(p => { if (!cats[p.category]) cats[p.category]=[]; cats[p.category].push(p); });
|
||||
let html = `Voici nos <b>${Object.keys(PRODUCTS_KB).length} produits</b> par categorie :<br><br>`;
|
||||
for (const [cat, prods] of Object.entries(cats)) {
|
||||
html += `<b>${cat}</b><br>`;
|
||||
prods.forEach(p => { html += `• <a href="${p.url}">${p.name}</a> - ${p.price}<br>`; });
|
||||
html += '<br>';
|
||||
}
|
||||
html += `<div class="quick-btns"><span class="quick-btn" onclick="askBot('email')">Email</span><span class="quick-btn" onclick="askBot('IA')">IA</span><span class="quick-btn" onclick="askBot('essayer gratuit')">Gratuit</span></div>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
// Pricing
|
||||
if (/prix|tarif|cout|combien|pricing|gratuit|free/.test(q)) {
|
||||
const free = Object.values(PRODUCTS_KB).filter(p => p.price.toLowerCase().includes('gratuit'));
|
||||
let html = `<b>Produits avec plan gratuit (${free.length}):</b><br>`;
|
||||
free.forEach(p => { html += `• <a href="${p.url}">${p.name}</a> - ${p.price}<br>`; });
|
||||
html += `<br>Tous les prix sont sur <a href="/products/">notre catalogue</a>. Besoin d'un devis personnalisé ?<div class="quick-btns"><span class="quick-btn" onclick="askBot('demo')">Demander une demo</span><span class="quick-btn" onclick="window.location.href='/booking.html'">Nous contacter</span></div>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
// Demo / essai
|
||||
if (/demo|essai|tester|try|commencer|start/.test(q)) {
|
||||
return `Pour commencer gratuitement :<br><br>1. <a href="/products/workspace.html">Ouvrir le Workspace</a> et créer un compte<br>2. Vous aurez acces a tous les produits gratuits<br>3. Testez DeliverScore, WEVIA Inference, Content Factory...<br><br>Ou <a href="#cta">contactez-nous</a> pour une demo personnalisée !<div class="quick-btns"><span class="quick-btn" onclick="window.location.href='/products/workspace.html'">Ouvrir Workspace</span></div>`;
|
||||
}
|
||||
|
||||
// Specific product search
|
||||
const matches = findProducts(q);
|
||||
if (matches.length > 0) {
|
||||
let html = `J'ai trouve <b>${matches.length} produit(s)</b> correspondant :<br><br>`;
|
||||
matches.slice(0, 6).forEach(p => {
|
||||
html += `<b><a href="${p.url}">${p.name}</a></b><br>${p.desc}<br><i>${p.price}</i><br><br>`;
|
||||
});
|
||||
if (matches.length > 6) html += `...et ${matches.length - 6} autres.<br>`;
|
||||
html += `<div class="quick-btns"><span class="quick-btn" onclick="window.location.href='${matches[0].url}'">Voir ${matches[0].name}</span><span class="quick-btn" onclick="askBot('prix')">Tarifs</span></div>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
// Default - suggest contacting or browsing
|
||||
return `Je n'ai pas trouve de produit spécifique pour "${userMsg}". Nos 62 produits couvrent : Email Intelligence, IA/GPU, Data, Security, Marketing, E-commerce et Dev.<br><br><div class="quick-btns"><span class="quick-btn" onclick="askBot('tous les produits')">Voir tout</span><span class="quick-btn" onclick="window.location.href='/booking.html'">Contacter WEVAL</span><span class="quick-btn" onclick="window.location.href='/products/workspace.html'">Workspace</span></div>`;
|
||||
}
|
||||
|
||||
function toggleBot() {
|
||||
const p = document.getElementById('weval-bot-panel');
|
||||
p.classList.toggle('open');
|
||||
if (p.classList.contains('open') && document.getElementById('weval-bot-msgs').children.length === 0) {
|
||||
addMsg('bot', `Bonjour ! Je suis l'assistant produits WEVAL. Nous proposons <b>37 SaaS</b> en production. Que recherchez-vous ?<div class="quick-btns"><span class="quick-btn" onclick="askBot('Tous les produits')">Catalogue</span><span class="quick-btn" onclick="askBot('email')">Email</span><span class="quick-btn" onclick="askBot('intelligence artificielle')">IA</span><span class="quick-btn" onclick="askBot('gratuit')">Gratuit</span><span class="quick-btn" onclick="askBot('demo')">Demo</span></div>`);
|
||||
}
|
||||
document.getElementById('weval-bot-badge').style.display = 'none';
|
||||
}
|
||||
|
||||
function addMsg(type, html) {
|
||||
const msgs = document.getElementById('weval-bot-msgs');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'bot-msg ' + type;
|
||||
div.innerHTML = html;
|
||||
msgs.appendChild(div);
|
||||
msgs.scrollTop = msgs.scrollHeight;
|
||||
}
|
||||
|
||||
function askBot(q) {
|
||||
document.getElementById('weval-bot-input').value = q;
|
||||
sendBot();
|
||||
}
|
||||
|
||||
function sendBot() {
|
||||
const input = document.getElementById('weval-bot-input');
|
||||
const msg = input.value.trim();
|
||||
if (!msg) return;
|
||||
input.value = '';
|
||||
addMsg('user', msg);
|
||||
|
||||
// Show typing
|
||||
const msgs = document.getElementById('weval-bot-msgs');
|
||||
const typing = document.createElement('div');
|
||||
typing.className = 'bot-typing';
|
||||
typing.innerHTML = '<span></span><span></span><span></span>';
|
||||
msgs.appendChild(typing);
|
||||
msgs.scrollTop = msgs.scrollHeight;
|
||||
|
||||
setTimeout(() => {
|
||||
typing.remove();
|
||||
addMsg('bot', botReply(msg));
|
||||
}, 400 + Math.random() * 600);
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:2rem;justify-content:center;flex-wrap:wrap;padding:1.5rem 4%;background:rgba(255,255,255,.02);border-top:1px solid rgba(255,255,255,.04);margin:2rem 0"><div style="display:flex;align-items:center;gap:.4rem;font-size:.8rem;color:#7a8ba5"><strong style="color:#edf2f7">44</strong> produits SaaS</div><div style="display:flex;align-items:center;gap:.4rem;font-size:.8rem;color:#7a8ba5"><strong style="color:#edf2f7">Cloud</strong> souverain</div><div style="display:flex;align-items:center;gap:.4rem;font-size:.8rem;color:#7a8ba5"><strong style="color:#edf2f7">API</strong> REST</div><div style="display:flex;align-items:center;gap:.4rem;font-size:.8rem;color:#7a8ba5"><strong style="color:#edf2f7">RGPD</strong> conforme</div><div style="display:flex;align-items:center;gap:.4rem;font-size:.8rem;color:#7a8ba5"><strong style="color:#edf2f7">France</strong> · Maroc · États-Unis · International</div></div>
|
||||
<style>.dm-box{background:#0c1222;border:1px solid rgba(255,255,255,.06);border-radius:14px;padding:1.5rem;margin:2rem auto;max-width:800px}.dm-box h3{font-size:1rem;font-weight:600;margin-bottom:1rem;display:flex;align-items:center;gap:.5rem}.dm-row{display:flex;gap:.6rem;margin-bottom:1rem}.dm-row input{flex:1;background:rgba(0,0,0,.3);border:1px solid rgba(255,255,255,.08);border-radius:8px;padding:.6rem .8rem;color:#edf2f7;font-family:Outfit;font-size:.85rem;outline:none}.dm-row button{background:var(--a,#7c5cfc);color:#05080f;border:none;border-radius:8px;padding:.6rem 1.2rem;font-weight:700;cursor:pointer;font-family:Outfit;font-size:.85rem;white-space:nowrap}.dm-out{min-height:80px;padding:1rem;background:rgba(0,0,0,.2);border-radius:8px;font-size:.82rem;color:#edf2f7;line-height:1.6}.dm-out strong{color:#fff}.dm-out pre{background:rgba(0,0,0,.3);padding:.5rem;border-radius:6px;font-size:.75rem;margin:.4rem 0;overflow-x:auto}.dm-ld{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--a,#7c5cfc);margin:0 2px;animation:dmp .8s infinite}.dm-ld:nth-child(2){animation-delay:.2s}.dm-ld:nth-child(3){animation-delay:.4s}@keyframes dmp{0%,100%{opacity:1}50%{opacity:.3}}</style>
|
||||
<div class="dm-box" id="demo"><h3>⚡ Arsenal Framework — Demo Live</h3>
|
||||
<div class="dm-row"><input id="dm-arsenal" placeholder="Décrivez votre besoin ERP" onkeydown="if(event.key==='Enter')dm_arsenal()"><button onclick="dm_arsenal()">Tester</button></div>
|
||||
<div class="dm-out" id="dmo-arsenal">Testez Arsenal Framework en direct — aucune inscription.</div></div>
|
||||
<script>
|
||||
async function dm_arsenal(){var q=document.getElementById("dm-arsenal").value.trim();if(!q)return;var o=document.getElementById("dmo-arsenal");o.innerHTML='<span class="dm-ld"></span><span class="dm-ld"></span><span class="dm-ld"></span>';try{var r=await fetch("/api/weval-ia-fast.php",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({message:"[INSTRUCTION SYSTEME: Reponds de facon structurée et professionnelle.]\n\nDemande utilisateur: Analyse le besoin ERP et propose une solution Arsenal: "+q,mode:"full"})});var j=await r.json();o.innerHTML=(j.response||"Erreur").replace(/\*\*(.*?)\*\*/g,"<strong>$1</strong>").replace(/\n\n/g,"<br><br>");}catch(e){o.innerHTML="Erreur: "+e.message}}
|
||||
</script>
|
||||
<script defer src=/assets/dm-enhance.js></script></body></html>
|
||||
<!-- WEVAL Self-Service Inject -->
|
||||
<script>
|
||||
if(window===window.top){
|
||||
// Replace all contact mailto links with signup portal
|
||||
document.querySelectorAll('a[href*="mailto:"]').forEach(a => {
|
||||
if(a.classList.contains('btn-p') || a.classList.contains('btn-f') || a.classList.contains('btn-n') || a.classList.contains('btn-nav') || a.classList.contains('btn-primary') || a.classList.contains('btn-price-fill') || a.textContent.includes('Commencer') || a.textContent.includes('Commander') || a.textContent.includes('Essayer') || a.textContent.includes('Souscrire') || a.textContent.includes('Créer') || a.textContent.includes('Obtenir') || a.textContent.includes('Démarrer') || a.textContent.includes('Rejoindre')) {
|
||||
a.href = '/products/workspace.html';
|
||||
a.removeAttribute('target');
|
||||
}
|
||||
});
|
||||
// Add floating CTA
|
||||
const bar = document.createElement('div');
|
||||
bar.innerHTML = '<div style="position:fixed;bottom:0;left:0;right:0;z-index:999;background:rgba(5,8,15,0.95);backdrop-filter:blur(10px);border-top:1px solid rgba(0,201,167,0.15);padding:0.6rem 4%;display:flex;justify-content:space-between;align-items:center"><div style="font-size:0.82rem;color:#7a8ba5"><strong style="color:#edf2f7">WEVAL Products</strong> · <span style="color:#00c9a7">Self-service</span> · Inscription en 30 secondes</div><a href="/products/workspace.html" style="background:#00c9a7;color:#05080f;padding:0.5rem 1.2rem;border-radius:6px;font-weight:700;font-size:0.82rem;text-decoration:none">Créer mon compte gratuit →</a></div>';
|
||||
if(window.self===window.top){document.body.appendChild(bar);}
|
||||
document.body.style.paddingBottom = '52px';
|
||||
}
|
||||
</script>
|
||||