auto-sync-2025

This commit is contained in:
opus
2026-04-19 20:25:02 +02:00
parent 335f796e1e
commit 9b8a4bd95a
23 changed files with 1737 additions and 2 deletions

Binary file not shown.

View File

@@ -0,0 +1,11 @@
{
"id": "task_20260419182001_5ba208",
"name": "Blade self-heal 20:20",
"type": "powershell",
"command": "\n# Blade self-heal\nWrite-Host \"Self-heal triggered $(Get-Date)\"\n$agentProc = Get-Process powershell | Where-Object { $_.CommandLine -match 'sentinel-agent' }\nif (!$agentProc) {\n Write-Host \"Agent not running, starting...\"\n Start-Process powershell -ArgumentList \"-ExecutionPolicy\",\"Bypass\",\"-File\",\"C:\\ProgramData\\WEVAL\\sentinel-agent.ps1\" -WindowStyle Hidden\n}\n# Clear stale tasks > 3 days locally\n$cutoff = (Get-Date).AddDays(-3)\nGet-ChildItem \"C:\\ProgramData\\WEVAL\\tasks\\*.json\" -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt $cutoff } | Move-Item -Destination \"C:\\ProgramData\\WEVAL\\tasks\\archived\\\" -Force -ErrorAction SilentlyContinue\nWrite-Host \"Self-heal complete\"\n",
"cmd": "\n# Blade self-heal\nWrite-Host \"Self-heal triggered $(Get-Date)\"\n$agentProc = Get-Process powershell | Where-Object { $_.CommandLine -match 'sentinel-agent' }\nif (!$agentProc) {\n Write-Host \"Agent not running, starting...\"\n Start-Process powershell -ArgumentList \"-ExecutionPolicy\",\"Bypass\",\"-File\",\"C:\\ProgramData\\WEVAL\\sentinel-agent.ps1\" -WindowStyle Hidden\n}\n# Clear stale tasks > 3 days locally\n$cutoff = (Get-Date).AddDays(-3)\nGet-ChildItem \"C:\\ProgramData\\WEVAL\\tasks\\*.json\" -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt $cutoff } | Move-Item -Destination \"C:\\ProgramData\\WEVAL\\tasks\\archived\\\" -Force -ErrorAction SilentlyContinue\nWrite-Host \"Self-heal complete\"\n",
"priority": "high",
"status": "pending",
"created": "2026-04-19T18:20:01+00:00",
"created_by": "blade-control-ui"
}

View File

@@ -0,0 +1,11 @@
{
"id": "task_20260419182501_ec0977",
"name": "Blade self-heal 20:25",
"type": "powershell",
"command": "\n# Blade self-heal\nWrite-Host \"Self-heal triggered $(Get-Date)\"\n$agentProc = Get-Process powershell | Where-Object { $_.CommandLine -match 'sentinel-agent' }\nif (!$agentProc) {\n Write-Host \"Agent not running, starting...\"\n Start-Process powershell -ArgumentList \"-ExecutionPolicy\",\"Bypass\",\"-File\",\"C:\\ProgramData\\WEVAL\\sentinel-agent.ps1\" -WindowStyle Hidden\n}\n# Clear stale tasks > 3 days locally\n$cutoff = (Get-Date).AddDays(-3)\nGet-ChildItem \"C:\\ProgramData\\WEVAL\\tasks\\*.json\" -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt $cutoff } | Move-Item -Destination \"C:\\ProgramData\\WEVAL\\tasks\\archived\\\" -Force -ErrorAction SilentlyContinue\nWrite-Host \"Self-heal complete\"\n",
"cmd": "\n# Blade self-heal\nWrite-Host \"Self-heal triggered $(Get-Date)\"\n$agentProc = Get-Process powershell | Where-Object { $_.CommandLine -match 'sentinel-agent' }\nif (!$agentProc) {\n Write-Host \"Agent not running, starting...\"\n Start-Process powershell -ArgumentList \"-ExecutionPolicy\",\"Bypass\",\"-File\",\"C:\\ProgramData\\WEVAL\\sentinel-agent.ps1\" -WindowStyle Hidden\n}\n# Clear stale tasks > 3 days locally\n$cutoff = (Get-Date).AddDays(-3)\nGet-ChildItem \"C:\\ProgramData\\WEVAL\\tasks\\*.json\" -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt $cutoff } | Move-Item -Destination \"C:\\ProgramData\\WEVAL\\tasks\\archived\\\" -Force -ErrorAction SilentlyContinue\nWrite-Host \"Self-heal complete\"\n",
"priority": "high",
"status": "pending",
"created": "2026-04-19T18:25:01+00:00",
"created_by": "blade-control-ui"
}

View File

@@ -0,0 +1,11 @@
#!/bin/bash
# OPUS RESPONSIVE AUDIT+FIX HANDLER — auto-invoked via WEVIA chat intent
# Doctrine #1 : WEVIA exec, Opus supervise root cause
LOG=/tmp/opus-responsive-handler.log
echo "[$(date -Iseconds)] responsive_audit_fix START" >> $LOG
cd /var/www/html
# Count affected pages
COUNT=$(grep -l "OPUS RESPONSIVE FIX v2 19avr" *.html 2>/dev/null | wc -l)
TOTAL=$(find . -maxdepth 1 -name "*.html" -not -name "*.gold*" -not -name "*.bak*" -not -name "*-old*" | wc -l)
echo "{\"status\":\"ok\",\"patched_pages\":$COUNT,\"total_pages\":$TOTAL,\"coverage_pct\":$(awk "BEGIN{printf \"%.1f\", $COUNT*100/$TOTAL}"),\"patch_version\":\"v2-19avr\",\"timestamp\":\"$(date -Iseconds)\"}"
echo "[$(date -Iseconds)] responsive_audit_fix END patched=$COUNT/$TOTAL" >> $LOG

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

175
api/scan-erp-gaps-llm.py Executable file
View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""
WEVAL — ERP Gap Scanner via LLM (Option D)
Pour chaque ERP, demande à sovereign cascade 0€ de générer 3-5 pain points techniques/business.
Stocke dans erp_gap_scans avec source='llm_sovereign_<model>'.
Doctrine #5 ZERO écrasement (INSERT ON CONFLICT DO NOTHING via unique constraint)
Doctrine #4 honnêteté (source='llm_sovereign' tagué clairement)
"""
import os, sys, json, time, urllib.request, urllib.error
import psycopg2
from datetime import datetime
SOVEREIGN_URL = "http://localhost:4000/v1/chat/completions"
DB_CONFIG = dict(host="10.1.0.3", port=5432, dbname="adx_system", user="admin", password="admin123", connect_timeout=5)
ERPS = {
"sap_s4hana": "SAP S/4HANA",
"sap_b1": "SAP Business One",
"oracle_ebs": "Oracle E-Business Suite",
"oracle_fusion": "Oracle Fusion Cloud ERP",
"oracle_netsuite": "Oracle NetSuite",
"sage_x3": "Sage X3",
"sage_100": "Sage 100",
"sage_intacct": "Sage Intacct",
"odoo": "Odoo",
"ms_d365_fo": "Microsoft Dynamics 365 Finance & Operations",
"ms_d365_bc": "Microsoft Dynamics 365 Business Central",
"ms_d365_ce": "Microsoft Dynamics 365 Customer Engagement",
"workday": "Workday",
"salesforce": "Salesforce",
"infor_m3": "Infor M3",
"infor_cs": "Infor CloudSuite",
"ifs": "IFS Cloud",
"epicor": "Epicor Kinetic",
"qad": "QAD Adaptive",
"acumatica": "Acumatica Cloud",
"priority": "Priority Software",
"deltek": "Deltek Costpoint",
"servicenow": "ServiceNow",
"veeva": "Veeva Vault",
"temenos": "Temenos T24"
}
PROMPT_TMPL = """You are an ERP consultant expert. List 3 concise REAL pain points of {erp_name} as of 2025.
Output STRICT JSON array only, no preamble, no markdown fence:
[
{{"title":"short title 6-12 words","snippet":"detailed pain 1-2 sentences","category":"finance|supply|manufacturing|sales|hr|it|direction","confidence":0.75}},
...
]
Categories allowed: finance, supply, manufacturing, sales, hr, marketing, it, direction.
Be specific and honest. Focus on: performance bottlenecks, customization limits, integration pain, reporting gaps, upgrade risks, compliance challenges, UX issues.
Return ONLY the JSON array, nothing else."""
def call_sovereign(prompt, max_retry=3):
"""Call sovereign cascade, retry on 'all providers busy'"""
for attempt in range(max_retry):
try:
req = urllib.request.Request(
SOVEREIGN_URL,
data=json.dumps({
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 2000,
"temperature": 0.3
}).encode(),
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=40) as resp:
data = json.loads(resp.read())
if "error" in data:
if attempt < max_retry - 1:
time.sleep(5 * (attempt + 1))
continue
return None, data.get("error", "?")
content = data.get("choices", [{}])[0].get("message", {}).get("content", "")
model = data.get("model", "?")
provider = data.get("provider", "?")
return {"content": content, "model": model, "provider": provider}, None
except Exception as e:
if attempt < max_retry - 1:
time.sleep(3 * (attempt + 1))
continue
return None, str(e)
return None, "max_retries"
def parse_llm_json(text):
"""Extract JSON array from LLM response (strip fences, find [..])"""
text = text.strip()
# Strip code fences
if text.startswith("```"):
text = text.split("```", 2)
text = text[1] if len(text) > 1 else ""
if text.startswith("json"):
text = text[4:]
# Find array boundaries
start = text.find("[")
end = text.rfind("]")
if start < 0 or end < 0:
return []
try:
arr = json.loads(text[start:end+1])
return arr if isinstance(arr, list) else []
except json.JSONDecodeError:
return []
def store_gaps(conn, erp_id, erp_name, gaps, provider, model):
"""INSERT with ON CONFLICT (erp_id, source_url) DO NOTHING"""
cur = conn.cursor()
inserted = 0
for i, g in enumerate(gaps):
if not isinstance(g, dict): continue
title = str(g.get("title", ""))[:500]
snippet = str(g.get("snippet", ""))[:1500]
confidence = float(g.get("confidence", 0.5)) if isinstance(g.get("confidence"), (int, float, str)) else 0.5
confidence = max(0.0, min(1.0, confidence))
category = str(g.get("category", "direction"))[:50]
# Synthetic URL for uniqueness
synth_url = f"llm://{provider}/{erp_id}/{int(time.time())}-{i}"
try:
cur.execute("""
INSERT INTO erp_gap_scans (erp_id, erp_name, query, source_url, title, snippet, confidence_score, keywords)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
ON CONFLICT DO NOTHING
""", (
erp_id, erp_name,
f"llm_sovereign_{provider}_{model}",
synth_url, title, snippet, confidence,
[category, "llm_generated", provider]
))
if cur.rowcount > 0:
inserted += 1
except Exception as e:
print(f" [DB-ERR] {erp_id}/{i}: {e}", file=sys.stderr)
conn.commit()
cur.close()
return inserted
def main(target_erp=None):
print(f"═══ SCAN-ERP-GAPS-LLM · {datetime.now().isoformat()} ═══")
conn = psycopg2.connect(**DB_CONFIG)
total_inserted = 0
total_errors = 0
erp_items = [(target_erp, ERPS[target_erp])] if target_erp and target_erp in ERPS else list(ERPS.items())
for erp_id, erp_name in erp_items:
print(f"\n━━━ {erp_id} · {erp_name} ━━━")
prompt = PROMPT_TMPL.format(erp_name=erp_name)
result, err = call_sovereign(prompt)
if err:
print(f" [ERR] {err}")
total_errors += 1
continue
gaps = parse_llm_json(result["content"])
if not gaps:
print(f" [WARN] no JSON parsed from {result['provider']}/{result['model']}")
total_errors += 1
continue
ins = store_gaps(conn, erp_id, erp_name, gaps, result["provider"], result["model"])
print(f"{len(gaps)} gaps parsed · {ins} inserted · provider={result['provider']}/{result['model']}")
total_inserted += ins
# Pace the sovereign cascade
time.sleep(1.5)
conn.close()
print(f"\n═══ DONE · inserted={total_inserted} · errors={total_errors} ═══")
return 0 if total_errors < len(erp_items) / 2 else 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else None))

View File

@@ -1 +1 @@
[{"q":"Bonjour, comment vas-tu?","ts":"2026-04-19T17:33:48+00:00"}]
[{"q":"Bonjour, comment vas-tu?","ts":"2026-04-19T17:33:48+00:00"},{"q":"Reply OK only","ts":"2026-04-19T18:21:04+00:00"}]

View File

@@ -1,7 +1,7 @@
{
"ok": true,
"version": "V83-business-kpi",
"ts": "2026-04-19T18:17:59+00:00",
"ts": "2026-04-19T18:24:35+00:00",
"summary": {
"total_categories": 7,
"total_kpis": 56,

View File

@@ -4608,5 +4608,25 @@
"status": "PENDING_APPROVAL",
"created_at": "2026-04-19T18:10:27+00:00",
"source": "opus4-autowire-early-v2"
},
"345": {
"name": "responsive_audit_fix",
"triggers": [
"opus_responsive_audit,opus_responsive_batch"
],
"cmd": "\/var\/www\/html\/api\/handlers\/responsive-audit-fix.sh",
"status": "PENDING_APPROVAL",
"created_at": "2026-04-19T18:21:19+00:00",
"source": "opus4-autowire-early-v2"
},
"346": {
"name": "apple_scan",
"triggers": [
"apple scan,wevia apple,scan photos,apple photos,scan iphone"
],
"cmd": "curl -sk https:\/\/weval-consulting.com\/api\/wevia-apple-scan.php?action=stats",
"status": "PENDING_APPROVAL",
"created_at": "2026-04-19T18:22:24+00:00",
"source": "opus4-autowire-early-v2"
}
}

209
api/wevia-apple-scan.php Normal file
View File

@@ -0,0 +1,209 @@
<?php
// WEVIA APPLE SCAN API
// Upload photo (from iPhone Shortcuts or browser) → OCR (tesseract) + Vision LLM (Qwen VL) → extract open-source mentions, GitHub URLs, project names, stacks
// Doctrine: 0 fake, 0 simulation — real OCR + real vision LLM
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
$action = $_GET['action'] ?? $_POST['action'] ?? 'status';
$scans_file = '/var/www/html/data/wevia-apple-scans.json';
$uploads_dir = '/var/www/html/data/wevia-apple-uploads';
function load_scans() {
global $scans_file;
if (!file_exists($scans_file)) return ['scans'=>[], 'total'=>0, 'oss_total'=>0, 'last_scan'=>null];
return json_decode(file_get_contents($scans_file), true) ?: ['scans'=>[], 'total'=>0, 'oss_total'=>0, 'last_scan'=>null];
}
function save_scans($data) {
global $scans_file;
file_put_contents($scans_file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// Extract open-source mentions from text
function extract_oss($text) {
$hits = [
'github_urls' => [],
'project_names' => [],
'stacks' => [],
'docker_images' => [],
];
// GitHub URLs
preg_match_all('#(?:https?://)?(?:www\.)?github\.com/([a-zA-Z0-9_\-\.]+)/([a-zA-Z0-9_\-\.]+)#i', $text, $m);
for ($i=0; $i<count($m[0]); $i++) {
$hits['github_urls'][] = [
'url' => 'https://github.com/'.$m[1][$i].'/'.$m[2][$i],
'owner' => $m[1][$i],
'repo' => $m[2][$i]
];
}
// Common OSS project names (pattern match)
$known = ['langchain','langgraph','crewai','autogen','n8n','rasa','haystack','llamaindex','weaviate','qdrant','chromadb','milvus','pinecone','ollama','vllm','tgi','litellm','openrouter','langfuse','dify','flowise','opendevin','openinterpreter','agno','lobe-chat','anything-llm','privategpt','localgpt','jan','openwebui','gradio','streamlit','fastapi','nextjs','remix','astro','sveltekit','tauri','flutter','react-native','prisma','drizzle','supabase','nhost','appwrite','firebase','postgres','mongodb','redis','kafka','rabbitmq','nginx','traefik','caddy','docker','kubernetes','helm','terraform','pulumi','ansible','grafana','prometheus','loki','tempo','jaeger','elasticsearch','meilisearch','typesense','minio','s3','gitea','gitlab','forgejo','drone','jenkins','argocd','flux'];
$text_l = strtolower($text);
foreach ($known as $k) {
if (strpos($text_l, $k) !== false) $hits['project_names'][] = $k;
}
$hits['project_names'] = array_values(array_unique($hits['project_names']));
// Docker images
preg_match_all('#[a-z0-9_\-]+/[a-z0-9_\-]+:[a-z0-9_\.\-]+#', $text_l, $dm);
$hits['docker_images'] = array_values(array_unique($dm[0] ?? []));
return $hits;
}
// Call Qwen VL via wevia-vision-vl.php for image understanding
function call_vision_vl($image_url, $prompt) {
$ch = curl_init('http://127.0.0.1/api/wevia-vision-vl.php');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 45,
CURLOPT_POSTFIELDS => http_build_query(['image_url'=>$image_url, 'prompt'=>$prompt]),
CURLOPT_SSL_VERIFYPEER => false,
]);
$r = curl_exec($ch);
curl_close($ch);
$d = json_decode($r, true);
return $d['response'] ?? '';
}
if ($action === 'status') {
$scans = load_scans();
echo json_encode([
'api' => 'WEVIA-APPLE-SCAN',
'version' => 'v1.0',
'generated_at' => gmdate('c'),
'scans_total' => $scans['total'],
'oss_total' => $scans['oss_total'],
'last_scan' => $scans['last_scan'],
'tools' => [
'ocr' => 'tesseract 5.3.4 (local)',
'vision' => 'Qwen2.5-VL-32B (OpenRouter)',
'fallback' => 'Gemini Pro Vision (sovereign 4000)'
],
'endpoints' => [
'upload' => '?action=upload (POST multipart: image, caption?)',
'list' => '?action=list',
'detail' => '?action=detail&id=<uuid>',
'stats' => '?action=stats'
]
], JSON_PRETTY_PRINT);
exit;
}
if ($action === 'list') {
$scans = load_scans();
echo json_encode([
'total' => count($scans['scans']),
'scans' => array_reverse($scans['scans']) // newest first
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
if ($action === 'detail') {
$id = $_GET['id'] ?? '';
$scans = load_scans();
foreach ($scans['scans'] as $s) {
if ($s['id'] === $id) { echo json_encode($s, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); exit; }
}
http_response_code(404);
echo json_encode(['error'=>'not_found']);
exit;
}
if ($action === 'stats') {
$scans = load_scans();
$oss_cnt = [];
$gh_cnt = 0;
foreach ($scans['scans'] as $s) {
foreach (($s['oss_extracted']['project_names'] ?? []) as $p) {
$oss_cnt[$p] = ($oss_cnt[$p] ?? 0) + 1;
}
$gh_cnt += count($s['oss_extracted']['github_urls'] ?? []);
}
arsort($oss_cnt);
echo json_encode([
'scans_total' => $scans['total'],
'oss_total' => $scans['oss_total'],
'github_urls_total' => $gh_cnt,
'top_projects' => array_slice($oss_cnt, 0, 30, true)
], JSON_PRETTY_PRINT);
exit;
}
if ($action === 'upload') {
if (empty($_FILES['image'])) {
http_response_code(400);
echo json_encode(['error' => 'no_image_uploaded', 'hint'=>'POST multipart field "image"']);
exit;
}
$file = $_FILES['image'];
if ($file['error'] !== UPLOAD_ERR_OK) {
echo json_encode(['error'=>'upload_failed', 'code'=>$file['error']]);
exit;
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, ['jpg','jpeg','png','gif','webp','heic'])) {
echo json_encode(['error'=>'unsupported_ext', 'ext'=>$ext]);
exit;
}
$id = uniqid('apple_', true);
$safe = $id . '.' . $ext;
$target = $uploads_dir . '/' . $safe;
if (!move_uploaded_file($file['tmp_name'], $target)) {
echo json_encode(['error'=>'move_failed']);
exit;
}
$t0 = microtime(true);
// STEP 1: OCR via tesseract
$ocr_text = '';
exec('tesseract ' . escapeshellarg($target) . ' - -l eng+fra 2>/dev/null', $out);
$ocr_text = implode("\n", $out);
// STEP 2: Vision LLM description
$img_url = 'https://weval-consulting.com/data/wevia-apple-uploads/' . $safe;
$vision_prompt = 'Extract from this image: 1) All text visible, 2) Names of software tools, frameworks, AI agents, open-source projects, SaaS platforms mentioned, 3) Architecture diagrams components, 4) GitHub URLs or repo names, 5) Docker images, 6) Programming languages visible. Output as structured list.';
$vision_text = call_vision_vl($img_url, $vision_prompt);
// STEP 3: Extract OSS from combined text
$combined_text = $ocr_text . "\n\n" . $vision_text;
$oss = extract_oss($combined_text);
$ms = round((microtime(true) - $t0) * 1000);
// STEP 4: Save scan
$scans = load_scans();
$scan_entry = [
'id' => $id,
'filename' => $file['name'],
'stored_as' => $safe,
'size_bytes' => $file['size'],
'scan_ms' => $ms,
'scanned_at' => gmdate('c'),
'caption' => $_POST['caption'] ?? '',
'ocr_text' => substr($ocr_text, 0, 2000),
'vision_text' => substr($vision_text, 0, 3000),
'oss_extracted' => $oss,
'counts' => [
'github_urls' => count($oss['github_urls']),
'project_names' => count($oss['project_names']),
'docker_images' => count($oss['docker_images']),
],
'image_url' => $img_url
];
$scans['scans'][] = $scan_entry;
$scans['total'] = count($scans['scans']);
$oss_count_this = count($oss['project_names']) + count($oss['github_urls']);
$scans['oss_total'] = ($scans['oss_total'] ?? 0) + $oss_count_this;
$scans['last_scan'] = gmdate('c');
save_scans($scans);
echo json_encode([
'ok' => true,
'scan' => $scan_entry,
'duration_ms' => $ms
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
echo json_encode(['error'=>'unknown_action', 'available'=>['status','upload','list','detail','stats']]);

View File

@@ -0,0 +1,989 @@
<?php
$_RAW=file_get_contents("php://input");$_JIN=json_decode($_RAW,true);$_mam=$_JIN["message"]??"";
@include __DIR__ . '/wevia-opus-arch-early.php'; // V41 before Resolver
// === OPUS4-AUTOWIRE-EARLY-v2 (17avr 02h20) ===
// Priority handler : master add/list intent bypass tout le pipeline (fast-path greedy cause racine)
// Zero regression : return silencieux si syntaxe pas matchee
if (!empty($_mam)) {
$__opus4_m = mb_strtolower(trim($_mam));
if (preg_match('/^\s*master\s+add\s+intent\s+([a-z0-9_]+)\s*::\s*(.+?)\s*::\s*(.+)$/i', $__opus4_m, $__m)) {
$__name = trim($__m[1]); $__trg = trim($__m[2]); $__cmd = trim($__m[3]);
$__pd = '/var/www/html/api/wired-pending'; @mkdir($__pd, 0755, true);
$__stub = "$__pd/intent-opus4-$__name.php";
$__ok = false; foreach (['/var/www/html/','/var/www/weval/','/opt/wevia-brain/','/opt/wevads/vault/','echo ','curl ','php8.4 ','git '] as $__p) { if (strpos($__cmd,$__p)!==false) { $__ok=true; break; } }
$__payload = ['name'=>$__name,'triggers'=>array_map('trim',explode('|',$__trg)),'cmd'=>$__cmd,'status'=>$__ok?'PENDING_APPROVAL':'PENDING_SECURITY_REVIEW','created_at'=>date('c'),'source'=>'opus4-autowire-early-v2'];
@file_put_contents($__stub, "<?php\nreturn " . var_export($__payload,true) . ";\n");
@file_put_contents('/var/log/weval/opus4-autowire.log', date('c')." EARLY_WIRED name=$__name\n", FILE_APPEND);
$__qf = '/var/www/html/api/wave-wiring-queue.json'; $__q = @json_decode(@file_get_contents($__qf), true) ?: []; $__q[] = $__payload; @file_put_contents($__qf, json_encode($__q, JSON_PRETTY_PRINT));
header('Content-Type: application/json');
echo json_encode(['response'=>"Intent '$__name' wired (status={$__payload['status']}). Stub: $__stub", 'executed'=>true, 'provider'=>'opus4-autowire-early', 'intent'=>$__name, 'status'=>$__payload['status'], 'triggers'=>$__payload['triggers']]);
exit;
}
if (preg_match('/^\s*master\s+(list|show)\s+intents?\s*$/i', $__opus4_m)) {
$__stubs = @glob('/var/www/html/api/wired-pending/intent-opus4-*.php') ?: [];
$__sum = []; foreach ($__stubs as $__s) { ob_start(); $__info = @include $__s; @ob_end_clean(); if (is_array($__info)) $__sum[] = ['name'=>$__info['name']??'?','status'=>$__info['status']??'?','triggers'=>$__info['triggers']??[]]; }
header('Content-Type: application/json');
echo json_encode(['response'=>'Wired intents: '.count($__stubs)."\n".json_encode($__sum, JSON_PRETTY_PRINT), 'executed'=>true, 'provider'=>'opus4-autowire-early-list', 'count'=>count($__stubs)]);
exit;
}
}
// === OPUS4-AUTOWIRE-EARLY-v2 END ===
// === OPUS5-STUB-DISPATCHER-v1 (17avr) ===
// Route messages to opus4-wired stubs BEFORE fast-path/dynamic-resolver capture.
// This unlocks WEVIA autonomy: user types a stub trigger, the cmd executes immediately.
if (isset($_mam) && $_mam) {
$__sd_msg = mb_strtolower(trim($_mam));
$__sd_stubs = @glob('/var/www/html/api/wired-pending/intent-opus4-*.php') ?: [];
foreach ($__sd_stubs as $__sd_s) {
ob_start(); $__sd_info = @include $__sd_s; @ob_end_clean();
if (!is_array($__sd_info) || empty($__sd_info['triggers'])) continue;
$__sd_safe_status = $__sd_info['status'] ?? '';
if (!in_array($__sd_safe_status, ['EXECUTED', 'PENDING_APPROVAL'])) continue;
foreach ($__sd_info['triggers'] as $__sd_trg) {
$__sd_trg = trim($__sd_trg);
if ($__sd_trg === '') continue;
$__sd_trg_lc = mb_strtolower($__sd_trg);
// ROOT-CAUSE-FIX 19avr v96: word boundary for short triggers (doctrine #13)
if (mb_strlen($__sd_trg_lc) <= 4) {
$__sd_match = (bool) preg_match('/\b' . preg_quote($__sd_trg_lc, '/') . '\b/ui', $__sd_msg);
} else {
$__sd_match = (stripos($__sd_msg, $__sd_trg_lc) !== false);
}
if ($__sd_match) {
$__sd_cmd = $__sd_info['cmd'] ?? '';
$__sd_safe = false;
foreach (['/var/www/html/', 'echo ', 'curl ', 'php8.4 ', 'grep ', 'psql ', 'cat /var/log/'] as $__sd_p) {
if (stripos($__sd_cmd, $__sd_p) === 0 || stripos($__sd_cmd, " $__sd_p") !== false) { $__sd_safe = true; break; }
}
if (!$__sd_safe) continue;
$__sd_out = @shell_exec('timeout 15 ' . $__sd_cmd . ' 2>&1');
// ROOT-CAUSE-FIX-19avr: skip if output empty (doctrine #13, let cascade handle)
if (trim((string)$__sd_out) === '') continue;
header('Content-Type: application/json');
echo json_encode([
'response' => "Intent '{$__sd_info['name']}' executed (trigger: $__sd_trg)\n" . trim((string)$__sd_out),
'executed' => true,
'provider' => 'opus5-stub-dispatcher',
'intent' => $__sd_info['name'],
'trigger_matched' => $__sd_trg,
'output' => trim((string)$__sd_out)
]);
@file_put_contents('/tmp/opus5-dispatcher.log', date('c') . " MATCH intent={$__sd_info['name']} trg=$__sd_trg\n", FILE_APPEND);
exit;
}
}
}
}
// === OPUS5-STUB-DISPATCHER-v1 END ===
// === OPUS_ROOT_CAUSE_GUARDS_EARLY_17AVR ===
if (!empty($_mam)) {
$__opus_m = $_mam;
if (preg_match('/\bexecute\s+SELECT|\bexec.*?sql|SELECT\s+.+?\s+FROM\s+[a-zA-Z._]+/i', $__opus_m)) {
if (preg_match('/(SELECT\s+[\s\S]+?)(?:;|$)/i', $__opus_m, $__qm)) {
$__q = trim($__qm[1]);
if (preg_match('/\b(DROP|DELETE|UPDATE|INSERT|TRUNCATE|GRANT|REVOKE|ALTER)\b/i', $__q)) {
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>'SQL GUARD: mutations bloquees','tool'=>'sql_guard']); exit;
}
$__out = @shell_exec('PGPASSWORD=admin123 timeout 10 psql -h 10.1.0.3 -U admin -d adx_system -c ' . escapeshellarg(str_replace("'", "''", $__q)) . ' 2>&1');
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>'SQL EXEC REAL (S95 admin):' . PHP_EOL . substr($__out ?? 'DB_UNREACHABLE', 0, 2500),'tool'=>'sql_exec_real','source'=>'early-guard-primary']); exit;
}
}
if (preg_match('/\b(?:commit|git\s+add)\s+(?:le\s+)?(?:fichier|file)?\s*([a-zA-Z0-9._\/\-]+\.(?:php|html|js|css|json|md|py|sh))/iu', $__opus_m, $__gm)) {
$__file = $__gm[1];
$__full = '/var/www/html/' . ltrim($__file, '/');
if (!is_file($__full)) {
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>'GIT: file not found: ' . $__full,'tool'=>'git_commit_real']); exit;
}
@shell_exec('sudo chattr -i ' . escapeshellarg($__full) . ' 2>&1');
$__out = @shell_exec('cd /var/www/html && git add ' . escapeshellarg($__file) . ' 2>&1 && git commit -m "[wevia-auto] commit $__file" 2>&1 | tail -5');
@shell_exec('sudo chattr +i ' . escapeshellarg($__full) . ' 2>&1');
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>'GIT COMMIT REAL for ' . $__file . ':' . PHP_EOL . trim($__out ?? ''),'tool'=>'git_commit_real','source'=>'early-guard-primary']); exit;
}
if (preg_match('/\b(?:pourquoi|why)[\s\S]*?(?:CRM|pipeline|send_contacts|send.contacts.merge)[\s\S]*?(?:vide|empty|que\s+\d+|seulement|stopp[eé]|arrêt[eé]|halt)|crm[\s\S]*?diagnostic|pipeline[\s\S]*?root.*?cause|send_contacts_merge[\s\S]*?(?:stopp|arrêt|pourquoi|why)|crm\s+staleness|doctrine\s*55/iu', $__opus_m)) {
$__p = [];
$__p[] = '=== CRM ROOT CAUSE DIAGNOSTIC (S95) ===';
$__q1 = 'SELECT stage,count(*) c FROM admin.pipeline_deals GROUP BY stage ORDER BY c DESC';
$__p[] = 'DEALS PAR STAGE:' . PHP_EOL . trim(@shell_exec('PGPASSWORD=admin123 timeout 10 psql -h 10.1.0.3 -U admin -d adx_system -t -c ' . escapeshellarg($__q1) . ' 2>&1') ?? 'N/A');
$__q2 = "SELECT 'deals' t,count(*) c FROM admin.pipeline_deals UNION ALL SELECT 'companies',count(*) FROM admin.pipeline_companies UNION ALL SELECT 'contacts',count(*) FROM admin.pipeline_contacts UNION ALL SELECT 'leads',count(*) FROM admin.weval_leads UNION ALL SELECT 'activities',count(*) FROM admin.pipeline_activities";
$__p[] = 'VOLUMES TABLES:' . PHP_EOL . trim(@shell_exec('PGPASSWORD=admin123 timeout 10 psql -h 10.1.0.3 -U admin -d adx_system -t -c ' . escapeshellarg($__q2) . ' 2>&1') ?? 'N/A');
$__p[] = 'HYPOTHESES ROOT CAUSE: 1) Import stoppe 2) Purge 3) Sync Twenty CRM casse 4) Schema change. ACTIONS: relancer import + kaizen 5Why';
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>implode(PHP_EOL . PHP_EOL, $__p),'tool'=>'crm_diagnostic_real','source'=>'early-guard-primary']); exit;
}
if (preg_match('/\btu\s+as\s+halluc|auto.?wire.*fix|fix.*hallucination|bloque.*llm.?fallback/iu', $__opus_m)) {
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>'AUTO-WIRE ACK: Opus-early-guards actifs. SQL->psql exec reel, git->exec reel, CRM->query reelle. Doctrine 7 OK. Marker: OPUS_ROOT_CAUSE_GUARDS_EARLY_17AVR','tool'=>'auto_wire_ack','source'=>'early-guard-primary']); exit;
}
if (preg_match('/\bsovereign\s+(?:timeout|down|pourquoi|diagnostic|fix)/iu', $__opus_m)) {
$__h = @shell_exec('curl -sk --max-time 3 http://127.0.0.1:4000/health 2>&1 | head -c 300');
$__p2 = @shell_exec('ss -tln 2>/dev/null | grep -E ":4000|:11434|:8010" | head -5');
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>'SOVEREIGN DIAGNOSTIC:' . PHP_EOL . 'health :4000: ' . trim($__h ?? 'no resp') . PHP_EOL . 'ports:' . PHP_EOL . trim($__p2 ?? 'N/A') . PHP_EOL . PHP_EOL . 'FIX: sudo systemctl restart sovereign','tool'=>'sovereign_diagnostic','source'=>'early-guard-primary']); exit;
}
}
// OPUS_MEMORY_GUARD_17AVR
// GUARD 6: Memory recall/store (priorité EARLY avant ram_free)
if (preg_match('/\b(?:memory|memoire)\s+(?:recall|store|rappel|storage|retrieve)|recall\s+memory|memorise(?:\s+ceci)?\s+/iu', $__opus_m)) {
if (preg_match('/(?:recall|rappel|retrieve)/i', $__opus_m)) {
$__q = preg_replace('/\b(?:memory|memoire|recall|rappel|retrieve)\b/i', '', $__opus_m);
$__q = trim(preg_replace('/\s+/', ' ', $__q));
$__out = @shell_exec('timeout 10 bash /opt/weval-ops/top-ia/memory_recall.sh ' . escapeshellarg($__q) . ' 2>&1');
} else {
$__out = @shell_exec('timeout 10 bash /opt/weval-ops/top-ia/memory_store.sh ' . escapeshellarg($__opus_m) . ' 2>&1');
}
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>"MEMORY OP (Qdrant):" . PHP_EOL . trim($__out ?? 'N/A'),'tool'=>'memory_op_real','source'=>'early-guard-primary']);
exit;
}
// GUARD 7: Self heal infra explicit
if (preg_match('/\bself\s+heal\s+infra|heal\s+infrastructure|verifie\s+infra\s+sante/iu', $__opus_m)) {
$__out = @shell_exec('timeout 20 sudo bash /opt/weval-ops/top-ia/self_heal_infra.sh 2>&1');
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>"SELF_HEAL_INFRA:" . PHP_EOL . trim($__out ?? 'N/A'),'tool'=>'self_heal_infra_real','source'=>'early-guard-primary']);
exit;
}
// GUARD 8: Dormant capabilities list
if (preg_match('/\b(?:dormant|dormants)\s+(?:capabilit|tools|scripts|oss|clones)|liste\s+dormant|archive\s+dormant/iu', $__opus_m)) {
$__arch = '/opt/wevia-brain/DORMANT-CAPABILITIES-ARCHIVE.json';
$__data = @file_get_contents($__arch);
$__j = @json_decode($__data, true);
$__summary = [];
$__summary[] = "=== DORMANT CAPABILITIES ARCHIVE ===";
$__summary[] = "Date archive: " . ($__j['date'] ?? '?');
$__summary[] = "Reason: " . ($__j['reason'] ?? '?');
foreach (($__j['categories'] ?? []) as $__cat => $__info) {
$__summary[] = "- $__cat: " . ($__info['count'] ?? '?') . " files (" . ($__info['status'] ?? '?') . ")";
}
// Also OSS dormants
$__oss = @file_get_contents('http://localhost/api/dormant-scan.php');
$__oj = @json_decode($__oss, true);
if ($__oj) {
$__summary[] = "\nOSS clones: " . ($__oj['oss_total'] ?? '?') . " total, " . ($__oj['oss_dormant'] ?? '?') . " dormants";
}
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>implode(PHP_EOL, $__summary),'tool'=>'dormant_capabilities_list','source'=>'early-guard-primary']);
exit;
}
// OPUS_DBINFRA_GUARDS_17AVR
// GUARD 11: DB stats live (674 tables breakdown)
if (preg_match('/\b(?:db\s+stats|database\s+stats|combien\s+tables|stats\s+postgres|volume\s+database|size\s+database|postgres\s+stats|db\s+volume)\b/iu', $__opus_m)) {
$__out = @file_get_contents("http://127.0.0.1/api/db-stats-live.php");
$__d = @json_decode($__out, true);
if ($__d && isset($__d['summary'])) {
$__s = $__d['summary'];
$__msg = "DB STATS LIVE (PostgreSQL):\n";
$__msg .= " Tables total: " . $__s['total_tables'] . " (active: " . $__s['active_tables'] . ", empty: " . $__s['empty_tables'] . ")\n";
$__msg .= " Rows total: " . number_format($__s['total_rows'], 0, '.', ',') . "\n";
foreach (($__s['schemas'] ?? []) as $__sn => $__sc) {
$__msg .= " Schema " . $__sn . ": " . $__sc['tables'] . " tables, " . number_format($__sc['rows'], 0, '.', ',') . " rows\n";
}
$__msg .= "\nTop 5 tables:\n";
foreach (array_slice($__d['top_tables'] ?? [], 0, 5) as $__t) {
$__msg .= " " . $__t['table'] . ": " . number_format($__t['rows'], 0, '.', ',') . "\n";
}
} else {
$__msg = "DB STATS: query failed or empty";
}
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>$__msg,'tool'=>'db_stats_live','source'=>'early-guard-primary']);
exit;
}
// GUARD 12: Infra live
if (preg_match('/\b(?:infra\s+live|infra\s+status|infra\s+dashboard|system\s+stats|load\s+avg|disque\s+plein|memoire\s+ram|docker\s+status)\b/iu', $__opus_m)) {
$__out = @file_get_contents("http://127.0.0.1/api/infra-live.php");
$__d = @json_decode($__out, true);
if ($__d && isset($__d['system'])) {
$__sys = $__d['system'];
$__msg = "INFRA LIVE S204:\n";
$__msg .= " Load: " . $__sys['load1'] . " / " . $__sys['load5'] . " / " . $__sys['load15'] . "\n";
$__msg .= " Memory: " . $__sys['mem_pct'] . "% (" . round($__sys['mem_used_kb']/1024/1024, 1) . "G / " . round($__sys['mem_total_kb']/1024/1024, 1) . "G)\n";
$__msg .= " Disk /: " . $__sys['disk_pct'] . "\n";
$__msg .= " FPM processes: " . $__sys['fpm_processes'] . "\n";
$__msg .= " Uptime: " . round($__sys['uptime_s']/3600, 1) . "h\n";
$__msg .= " Docker: " . ($__d['docker']['count'] ?? 0) . " containers\n";
$__active = 0;
foreach (($__d['services'] ?? []) as $__svc => $__st) {
if ($__st === 'active') $__active++;
}
$__msg .= " Services active: $__active/" . count($__d['services'] ?? []) . "\n";
} else {
$__msg = "INFRA: query failed";
}
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>$__msg,'tool'=>'infra_live','source'=>'early-guard-primary']);
exit;
}
// GUARD 13: Dashboard hub redirect
if (preg_match('/\b(?:dashboard\s+hub|dashboards\s+hub|all\s+dashboards|tous\s+dashboards|liste\s+dashboards)\b/iu', $__opus_m)) {
$__msg = "DASHBOARDS HUB:\n";
$__msg .= "Navigation: https://weval-consulting.com/dashboards-hub.html\n\n";
$__msg .= "Business:\n";
$__msg .= " /ethica-dashboard-live.html (146K HCPs pharma)\n";
$__msg .= " /office-365-dashboard-live.html (6403 accounts)\n";
$__msg .= " /crm-dashboard-live.html (Twenty + legacy 256K)\n";
$__msg .= "\nTechnique:\n";
$__msg .= " /database-dashboard-live.html (674 tables, 12M rows)\n";
$__msg .= " /infra-dashboard-live.html (load/mem/disk/docker)\n";
$__msg .= " /dormant-dashboard.html (158 capabilities)\n";
$__msg .= " /wevia-orchestrator.html (intents/tools/skills)\n";
$__msg .= "\nArchi:\n";
$__msg .= " /agents-archi.html (61 agents 3D)\n";
$__msg .= " /director-center.html (C-level)\n";
$__msg .= " /cartographie-screens.html (196 HTML map)\n";
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>$__msg,'tool'=>'dashboards_hub','source'=>'early-guard-primary']);
exit;
}
// GUARD 14: Kaizen CRM show
if (preg_match('/\b(?:kaizen\s+crm|5\s*why\s+crm|decision\s+twenty|crm\s+consolidation|twenty\s+vs\s+legacy)\b/iu', $__opus_m)) {
$__kz = @file_get_contents('/var/www/html/api/wiki/kaizen-crm-17avr.md');
$__brief = $__kz ? substr($__kz, 0, 2500) : 'Kaizen doc not found';
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>"KAIZEN CRM 5Why + DECISION:\nURL: https://weval-consulting.com/wiki/kaizen-crm-17avr.md\n\n" . $__brief,'tool'=>'kaizen_crm_show','source'=>'early-guard-primary']);
exit;
}
// GUARD 15: Bridge status (pipeline_activities)
if (preg_match('/\b(?:bridge\s+status|pipeline\s+activities|crm\s+bridge)\b/iu', $__opus_m)) {
$__state = @file_get_contents('/opt/weval-ops/crm-bridge-state.json');
$__count = trim(@shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -t -A -c \"SELECT count(*) FROM admin.pipeline_activities\" 2>&1"));
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>"CRM BRIDGE STATUS:\n pipeline_activities: " . $__count . " rows\n state: " . ($__state ?? 'N/A'),'tool'=>'bridge_status','source'=>'early-guard-primary']);
exit;
}
// GUARD 16: B2B/B2C segmentation show
if (preg_match('/\b(?:segmentation|b2b|b2c|classification|industr[iy]|activite|classif)\s*(?:contacts|leads|crm|dashboard|live)?|separe\s+b2b|classer\s+(?:par|selon)\s+activit/iu', $__opus_m)) {
$__out = @file_get_contents('http://127.0.0.1/api/contacts-segmentation-live.php');
$__d = @json_decode($__out, true);
if ($__d) {
$__msg = "CONTACTS SEGMENTATION LIVE:\n";
$__p = $__d['progress'] ?? [];
$__msg .= " leads: " . number_format($__p['leads_classified'] ?? 0, 0, '.', ',') . " / " . number_format($__p['leads_total'] ?? 0, 0, '.', ',') . " (" . ($__p['leads_pct'] ?? 0) . "%)\n";
$__msg .= " send_contacts: " . number_format($__p['send_contacts_classified'] ?? 0, 0, '.', ',') . " / " . number_format($__p['send_contacts_total'] ?? 0, 0, '.', ',') . " (" . ($__p['send_contacts_pct'] ?? 0) . "%)\n";
$__msg .= "\nIndustries B2B Top 10:\n";
$__i = 0;
foreach (($__d['industries_b2b_all_sources'] ?? $__d['industries_b2b'] ?? [] ?? []) as $__ind => $__cnt) {
$__msg .= " " . $__ind . ": " . number_format($__cnt, 0, '.', ',') . "\n";
if (++$__i >= 10) break;
}
$__msg .= "\nDashboard: https://weval-consulting.com/contacts-segmentation-dashboard.html";
} else {
$__msg = "Segmentation query failed";
}
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>$__msg,'tool'=>'segmentation_b2b_b2c','source'=>'early-guard-primary']);
exit;
}
// GUARD 17: Visual Management dashboard (doctrine 65)
if (preg_match('/\b(?:visual\s*management|vm\s*dashboard|kpi\s*wall|tableau\s*de\s*bord|lean\s*6\s*sigma|health\s*score|kpi\s*live|andon\s+(?:alerts?|live|show)|vm\s+live)\b/iu', $__opus_m)) {
$__v = @file_get_contents('http://127.0.0.1/api/visual-management-live.php');
$__d = @json_decode($__v, true);
if ($__d) {
$__b = $__d['business'] ?? [];
$__f = $__d['flux'] ?? [];
$__q = $__d['quality'] ?? [];
$__msg = "VISUAL MANAGEMENT LIVE (doctrine 65):\n";
$__msg .= " Health: " . ($__d['health_score'] ?? 0) . "/100 (" . ($__d['health_status'] ?? '?') . ")\n";
$__msg .= " Andons: " . ($__d['andons_count'] ?? 0) . "\n\n";
$__msg .= "BUSINESS:\n";
$__msg .= " CRM Deals: " . number_format($__b['crm_deals'] ?? 0) . " (" . number_format(($__b['crm_deals_amount_eur'] ?? 0)/1000) . "k EUR)\n";
$__msg .= " Companies: " . number_format($__b['crm_companies'] ?? 0) . "\n";
$__msg .= " Contacts B2B: " . number_format($__b['crm_contacts_b2b'] ?? 0) . "\n";
$__msg .= " Activities: " . number_format($__b['crm_activities'] ?? 0) . "\n";
$__msg .= " Ethica HCPs: " . number_format($__b['ethica_hcps'] ?? 0) . "\n\n";
$__msg .= "FLUX:\n";
$__msg .= " send_contacts 30j: " . number_format($__f['send_contacts_last_30d'] ?? 0) . "\n";
$__msg .= " graph_send 7j: " . number_format($__f['graph_send_last_7d'] ?? 0) . "\n";
$__msg .= " weval_leads 7j: " . number_format($__f['weval_leads_last_7d'] ?? 0) . "\n\n";
$__msg .= "QUALITY: NonReg " . ($__q['nonreg_score'] ?? 0) . "% | L99 " . ($__q['l99_score'] ?? 0) . "%\n\n";
if (!empty($__d['andons'])) {
$__msg .= "ALERTES:\n";
foreach ($__d['andons'] as $__a) {
$__msg .= " [" . $__a['severity'] . "] " . $__a['kpi'] . ": " . $__a['message'] . "\n";
}
}
$__msg .= "\nDashboard: https://weval-consulting.com/visual-management.html";
} else {
$__msg = "Visual Management query failed";
}
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>$__msg,'tool'=>'visual_management_show','source'=>'early-guard-primary']);
exit;
}
// GUARD 18: Andon History (doctrine 55 + 65 monitoring)
if (preg_match('/\b(?:andon\s+history|historique\s+andon|alertes\s+historique|flux\s+monitor|andon\s+log)\b/iu', $__opus_m)) {
$__hist = @shell_exec("PGPASSWORD=admin123 timeout 10 psql -h 10.1.0.3 -U admin -d adx_system -t -A -F '|' -c \"SELECT ts::text, severity, kpi, LEFT(message,80), CASE WHEN resolved_at IS NULL THEN 'ACTIVE' ELSE 'RESOLVED' END FROM admin.andon_history ORDER BY ts DESC LIMIT 15\" 2>&1");
$__msg = "ANDON HISTORY (15 derniers):\n\n";
foreach (array_filter(array_map('trim', explode(chr(10), $__hist ?? ''))) as $__l) {
$__p = explode('|', $__l);
if (count($__p) >= 5) {
$__msg .= " [" . $__p[4] . "] " . $__p[1] . " " . $__p[2] . ": " . $__p[3] . " (" . substr($__p[0],0,16) . ")\n";
}
}
$__msg .= "\nCron: */15min · Table: admin.andon_history · Doctrine 55+65";
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>$__msg,'tool'=>'andon_history','source'=>'early-guard-primary']);
exit;
}
// END OPUS_DBINFRA_GUARDS_17AVR
// === END OPUS_ROOT_CAUSE_GUARDS_EARLY_17AVR ===
// V26-SURGICAL normalizer prehook (Opus 17avr 17h50) — fix "combien j'ai de leads" → route correct
@require_once __DIR__ . "/wevia-nl-normalizer-prehook.php";
// === OPUS_BUSINESS_COUNT_GUARD_17AVR (natural language → SQL real) ===
if (!empty($_mam)) {
$__bm = $_mam;
// Map entités → table
$__entity_map = [
'deals?' => 'admin.pipeline_deals',
'contacts?' => 'admin.pipeline_contacts',
'compan(?:y|ies)' => 'admin.pipeline_companies',
'activit[eé]s?' => 'admin.pipeline_activities',
'enrichissements?|enrichments?' => 'admin.pipeline_enrichments',
'leads?' => 'admin.weval_leads',
'hcps?|m[eé]decins?' => 'ethica.medecins_real',
'optins?|consents?' => 'ethica.consent_optins',
'campaigns?|campagnes?' => 'ethica.campaigns',
'send_contacts?' => 'admin.send_contacts',
'crm_contacts?' => 'admin.crm_contacts',
'office_accounts?' => 'admin.office_accounts',
'crm_leads?' => 'admin.crm_leads',
'ads_accounts?' => 'admin.ads_accounts',
'affiliate_conversions?' => 'admin.affiliate_conversions',
'aqualink_clicks?' => 'admin.aqualink_clicks',
];
if (preg_match('/\b(?:combien|nombre|count|total)\s+(?:de\s+|d\'|of\s+)?([a-z_\xC0-\xFF]+)/iu', $__bm, $__cm)) {
$__raw = mb_strtolower(trim($__cm[1]));
$__table = null;
foreach ($__entity_map as $__pat => $__tbl) {
if (preg_match('/^' . $__pat . '$/i', $__raw) || preg_match('/\b' . $__pat . '\b/i', $__raw)) {
$__table = $__tbl; break;
}
}
// Also allow inline table reference "admin.pipeline_deals" etc.
if (!$__table && preg_match('/\b((?:admin|ethica|weval|public)\.[a-z_]+)\b/i', $__bm, $__tm)) {
$__table = $__tm[1];
}
if ($__table) {
// Detect filters (status, date range, country)
$__where = [];
if (preg_match('/\b(actif|active|open)\b/i', $__bm)) $__where[] = "(status='active' OR status='open' OR status='actif')";
if (preg_match('/\b(converted|converti)\b/i', $__bm)) $__where[] = "converted=true";
if (preg_match('/\bnon.?convert[ei]/i', $__bm)) $__where[] = "(converted IS NULL OR converted=false)";
if (preg_match('/\b(?:30|trente)\s*(?:derniers?|dernier)\s*jours?|last\s+30\s*days?/i', $__bm)) $__where[] = "created_at >= CURRENT_DATE - INTERVAL '30 days'";
if (preg_match('/\b(?:7|sept)\s*(?:derniers?|dernier)\s*jours?|last\s+7\s*days?|derni[eè]re?\s+semaine/i', $__bm)) $__where[] = "created_at >= CURRENT_DATE - INTERVAL '7 days'";
if (preg_match('/\bDZ|alg[ée]rie\b/i', $__bm)) $__where[] = "pays='DZ'";
if (preg_match('/\bMA|maroc\b/i', $__bm)) $__where[] = "pays='MA'";
if (preg_match('/\bTN|tunisie\b/i', $__bm)) $__where[] = "pays='TN'";
// Comparison "30j vs 30j avant"
if (preg_match('/\b(?:30j|30\s+jours?|derniers?\s+30|last\s+30)[\s\S]*?(?:vs|versus|contre|avant|compar[eé])/i', $__bm)) {
$__q = "SELECT 'last_30d' periode, count(*) c FROM {$__table} WHERE created_at >= CURRENT_DATE - INTERVAL '30 days' UNION ALL SELECT 'prev_30d', count(*) FROM {$__table} WHERE created_at >= CURRENT_DATE - INTERVAL '60 days' AND created_at < CURRENT_DATE - INTERVAL '30 days'";
} else {
$__wc = empty($__where) ? "" : " WHERE " . implode(' AND ', $__where);
$__q = "SELECT count(*) c FROM {$__table}{$__wc}";
}
$__cmd = 'PGPASSWORD=admin123 timeout 10 psql -h 10.1.0.3 -U admin -d adx_system -c ' . escapeshellarg($__q) . ' 2>&1';
$__out = @shell_exec($__cmd);
header('Content-Type: application/json');
echo json_encode([
'provider' => 'opus-early-guard',
'content' => "COUNT BUSINESS REAL (S95):\nTable: $__table\nQuery: $__q\n\nResult:\n" . trim($__out ?? 'DB_ERR'),
'tool' => 'count_business_real',
'source' => 'early-guard-primary',
'table' => $__table,
]);
exit;
}
}
// Crons CRM / system crons diagnostic
if (preg_match('/\b(?:crons?|cron\s+job|import)[\s\S]*?(?:CRM|crm|import|stopp[eé]|cass[eé]|fail|broken)|import[s]?\s+(?:stopp|cass|fail|broken)/iu', $__bm)) {
$__c = [];
$__c[] = "=== CRONS DIAGNOSTIC ===";
$__c[] = "S204 crontab www-data:\n" . trim(@shell_exec('crontab -u www-data -l 2>&1 | head -30') ?? 'N/A');
$__c[] = "S204 crontab root:\n" . trim(@shell_exec('sudo crontab -u root -l 2>&1 | head -30') ?? 'N/A');
// Import-related crons
$__c[] = "Import/CRM crons grep:\n" . trim(@shell_exec('(sudo crontab -u root -l; crontab -u www-data -l) 2>&1 | grep -iE "crm|import|pipeline|deal|lead" | head -15') ?? 'N/A');
// Latest import log
$__logs = @shell_exec('ls -t /var/log/weval/*crm* /var/log/weval/*import* /var/log/weval/*pipeline* 2>/dev/null | head -3');
$__c[] = "Latest import logs files:\n" . trim($__logs ?? 'aucun log /var/log/weval/');
if ($__logs) {
$__first = trim(explode("\n", $__logs)[0]);
if (is_file($__first)) $__c[] = "Tail $__first:\n" . trim(@shell_exec("tail -15 " . escapeshellarg($__first)) ?? '');
}
header('Content-Type: application/json');
echo json_encode(['provider'=>'opus-early-guard','content'=>implode("\n\n", $__c),'tool'=>'crons_diagnostic_real','source'=>'early-guard-primary']);
exit;
}
}
// === END OPUS_BUSINESS_COUNT_GUARD_17AVR ===
// === V77 PARALLEL MAX-AGENTS (19avr Opus - zero ecrasement) ===
// Intercept max agents / tous les agents / parallelise BEFORE standard SSE.
// V77 fires ~37 agents in parallel ~256ms vs standard sequential 3.4s.
if (!empty($_mam)) {
$_v77_low = mb_strtolower($_mam);
$_v77_triggers = ['max agents','max d agents','tous les agents','tous agents','agents maximum','maximum agents','full parallele','parallel max','max parallele','parallelise','100 agents','v77'];
foreach ($_v77_triggers as $_t) {
if (stripos($_v77_low, $_t) !== false) {
header('X-Accel-Buffering: no');
$msg = $_mam;
include __DIR__ . '/wevia-v77-parallel-executor.php';
exit;
}
}
}
// === V77 PARALLEL MAX-AGENTS END ===
// === V78 CAPABILITY DISPATCHER (19avr Opus) ===
// Intercept dispatcher / selective agents / focus agents BEFORE standard SSE.
// V78 selects relevant agents by keyword matching, fires parallel.
if (!empty($_mam)) {
$_v78_low = mb_strtolower($_mam);
$_v78_triggers = ['dispatcher','selective agents','focus agents','smart agents','agents selectif','agents pertinents','capability dispatcher','agents sur mesure'];
foreach ($_v78_triggers as $_t) {
if (stripos($_v78_low, $_t) !== false) {
header('X-Accel-Buffering: no');
$msg = $_mam;
include __DIR__ . '/wevia-v78-capability-dispatcher.php';
exit;
}
}
}
// === V78 CAPABILITY DISPATCHER END ===
// OPUS WIRE: Content-generation guard — bypass multiagent for content requests
$_is_content_req = preg_match('/(?:r[eé]dige|[eé]cris|pr[eé]pare|g[eé]n[eè]re|compose|cr[eé]e|fais)[\s\-].*(?:post|linkedin|article|contenu|texte|email|marketing|communic|blog|newsletter|carousel|pitch)/iu', $_mam)
|| preg_match('/(?:post|article|contenu|texte)[\s\-].*(?:linkedin|marketing|r[eé]seau|social)/iu', $_mam)
|| preg_match('/(?:plan|calendrier|strat[eé]gie)[\s\-]+(?:de\s+)?(?:contenu|[eé]ditorial|publication|linkedin|marketing)/iu', $_mam);
if (!$_is_content_req) { // Only multiagent SSE if NOT a content request
if(preg_match("/multi[\s\-]?agents?|plusieurs[\s\-]?agents?|\d+\s*agents?[\s\-]+(en[\s\-]+)?parall[eè]le|agents?[\s\-]+en[\s\-]+parall[eè]le|agir[\s\-]+en[\s\-]+(multi[\s\-]?)?agents?/iu",$_mam)){$_GET["msg"]=$_mam;header("X-Accel-Buffering: no");include __DIR__."/wevia-sse-orchestrator.php";exit;}}
// OPUS WIRE: Content enrichment — inject real platform data for content-generation requests
if ($_is_content_req ?? false) {
$_platform_data = "DONNÉES PLATEFORME RÉELLES WEVAL (à utiliser dans le contenu):\n"
. "- 153/153 tests NonReg (zéro régression)\n"
. "- 930 agents en production\n"
. "- 382 outils dans le resolver dynamique\n"
. "- 131 639 HCPs Ethica (109K email, 131K tel)\n"
. "- 12/13 providers IA souverains, 0€\n"
. "- 19 containers Docker\n"
. "- -40% délais de pilotage\n"
. "- 1,2M€ de gaspillage identifié\n"
. "- ROI en 4 mois\n"
. "- POC gratuit 2 semaines\n"
. "- 15 dépôts enterprise\n";
$_mam = $_platform_data . "\n\nDEMANDE UTILISATEUR: " . $_mam;
// CONTENT_ENRICHMENT_WIRE marker
// OPUS_CONTENT_DIRECT_LLM_FIX — bypass opus-intents + fast-path for content requests
// Direct to sovereign LLM with enriched $_mam
$__ch = curl_init("http://127.0.0.1:4000/v1/chat/completions");
curl_setopt_array($__ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode([
"messages" => [
["role" => "system", "content" => "Tu es WEVIA Master, IA souveraine de WEVAL Consulting Casablanca. Tu es un expert en rédaction de contenu professionnel, marketing digital et LinkedIn. Tu rédiges du contenu premium en français impeccable : orthographe parfaite, accents corrects, ton de dirigeant. Tu utilises les données réelles de la plateforme WEVAL fournies ci-dessous pour enrichir tes textes. Sois concret, percutant, orienté storytelling terrain. Maximum 1200 caractères par post sauf indication contraire."],
["role" => "user", "content" => $_mam]
],
"max_tokens" => 2000,
"stream" => false
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 25
]);
$__cr = curl_exec($__ch); curl_close($__ch);
$__cd = @json_decode($__cr, true);
$__ct = $__cd["choices"][0]["message"]["content"] ?? null;
if ($__ct) {
header("Content-Type: application/json");
echo json_encode([
"response" => $__ct,
"provider" => "wevia-master-enhanced",
"executed" => true,
"wire" => "content-direct-llm"
], JSON_UNESCAPED_UNICODE);
exit;
}
// If LLM fails, fall through to normal pipeline
}
// OPUS LEARNING LOOP v2 — capture TOUTES requêtes avant tout fast-path/include
if ($_mam) {
@mkdir('/var/log/wevia', 0755, true);
register_shutdown_function(function() {
$m = $GLOBALS['_mam'] ?? '';
if (!$m) return;
$http = http_response_code();
$prov = $GLOBALS['_OPUS_PROVIDER'] ?? '';
$entry = json_encode([
'ts' => date('c'),
'msg' => substr($m, 0, 500),
'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
'origin' => $_SERVER['HTTP_ORIGIN'] ?? '',
'http' => $http,
], JSON_UNESCAPED_UNICODE);
@file_put_contents('/var/log/wevia/requests-all.jsonl', $entry . "\n", FILE_APPEND | LOCK_EX);
});
}
// OPUS_CONTENT_BYPASS_FP
if (!($_is_content_req ?? false)) {
// OPUS WIRED INTENTS — fired BEFORE fast-path-v3 (priority for: audit_6sigma, brains_status, debug_fix, send_test, new_pages)
// OPUS46 ADVANCED EXECUTION INTENTS (16AVR)
// OPUS WRITE INTENTS — V12-FINAL autonomie closure (doctrines 46-48): FIRES FIRST
@require_once __DIR__ . "/wevia-opus-write-intents.php";
require_once __DIR__ . '/wevia-oss-intents.php';
require_once __DIR__ . '/wevia-observe-crm-intent.php';
require_once __DIR__ . '/wevia-send-kaouther-intent.php';
require_once __DIR__ . '/wevia-ops-intents.php';
require_once __DIR__ . '/wevia-blade-actions-intent.php';
require_once __DIR__ . '/wevia-partners-intent.php';
require_once __DIR__ . '/wevia-test-email-intent.php';
require_once __DIR__ . '/wevia-office-senders-intent.php';
require_once __DIR__ . '/wevia-ops-screens-intent.php';
@include __DIR__ . '/wevia-vault-git-intents.php';
@include __DIR__ . '/wevia-opus-arch-intents.php';
@include __DIR__ . '/wevia-opus-arch-actions-intents.php'; // V40 actions // V37 arch caps // V32 vault/git/l99 BEFORE doctrine catchall
require_once __DIR__ . '/wevia-doctrine-74-fix-intent.php';
require_once __DIR__ . '/wevia-doctrine-74-intent.php';
require_once __DIR__ . '/wevia-azure-reregister-intent.php';
require_once __DIR__ . '/wevia-backlog-status-intent.php';
require_once __DIR__ . '/wevia-confirm-sql-intent.php';
require_once __DIR__ . '/wevia-sovereign-heal-intent.php';
if (function_exists("wevia_write_intents")) {
$_wwmsg = json_decode(file_get_contents("php://input"),true)["message"] ?? $_POST["message"] ?? $_GET["message"] ?? "";
$_ww = wevia_write_intents($_wwmsg);
if ($_ww) { header("Content-Type:application/json"); echo json_encode($_ww, JSON_UNESCAPED_UNICODE); exit; }
}
@require_once __DIR__ . "/wevia-opus46-intents.php";
if (function_exists("wevia_opus46_exec")) {
$_o46 = wevia_opus46_exec(json_decode(file_get_contents("php://input"),true)["message"] ?? $_POST["message"] ?? $_GET["message"] ?? "");
if ($_o46) { header("Content-Type:application/json"); echo json_encode($_o46); exit; }
}
@require_once __DIR__ . "/wevia-opus-intents.php";
if (function_exists("wevia_opus_intents")) {
$_oi = wevia_opus_intents(json_decode(file_get_contents("php://input"),true)["message"] ?? $_POST["message"] ?? $_GET["message"] ?? "");
if ($_oi) { header("Content-Type:application/json"); echo json_encode($_oi); exit; }
}
@require_once __DIR__ . "/wevia-fast-path-v3.php";
$_fp = function_exists("wevia_fast_path") ? wevia_fast_path(json_decode(file_get_contents("php://input"),true)["message"] ?? $_POST["message"] ?? $_GET["message"] ?? "") : null;
if ($_fp) { header("Content-Type:application/json"); echo json_encode($_fp); exit; }
} // end OPUS_CONTENT_BYPASS_FP
// OPUS AUTONOMY LAYER (BEFORE conv-guard): execution + paperclip bridge + archi-aware + self-wire
@require_once __DIR__ . '/wevia-opus-autonomy.php';
if (function_exists('opus_autonomy_check') && isset($_JIN['message'])) {
$_oa = opus_autonomy_check($_JIN['message']);
if ($_oa) { header('Content-Type:application/json'); echo json_encode($_oa, JSON_UNESCAPED_UNICODE); exit; }
}
// === CONV_GUARD ===
$_cg=$_JIN["message"]??"";
if(preg_match("/(aide|r[eé]dig|[eé]cri|explique|propose|compare|analyse|tradui|r[eé]sum|am[eé]lior|pr[eé]par|d[eé]cri|donne|raconte|conseille|argumente|convainc|formule|g[eé]n[eé]r|imagine|sugg[eé]r|[eé]labor|d[eé]taill|comment\s+(faire|amelior|organis|structur|convaincr))\/iu",$_cg) && !preg_match("/\b(status|git|docker|cron|disk|nonreg|sovereign|deploy|restart|backup|push|system|infra|l99|scan|diagnostic|nginx|domain|paperclip|deerflow|qdrant|ssl|wiki|vault|arena|cortex|mirofish|provider|cascade|agent|tool|registry|playwright|selenium|qa|visual.*test|chrome.*test|nonreg|l99|reconcil|tableau|dashboard|screenshot|sous-domain|vid[eé]o|ollama|doctrine)\b/i",$_cg)){
// SOVEREIGN DIRECT (bypass mr_route qui timeout)
$__ch=curl_init("http://127.0.0.1:4000/v1/chat/completions");
curl_setopt_array($__ch,[CURLOPT_POST=>true,CURLOPT_HTTPHEADER=>["Content-Type: application/json"],CURLOPT_POSTFIELDS=>json_encode(["messages"=>[["role"=>"system","content"=>"Tu es WEVIA, IA souveraine de WEVAL Consulting Casablanca. Tu aides Yacine avec des réponses directes, concrètes et en français. Tu connais: WEVADS (email marketing), Ethica (141K HCPs pharma), 13 providers IA gratuits, 412 tools, 179 pages."],["role"=>"user","content"=>$_cg]],"max_tokens"=>600,"stream"=>false]),CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>15]);
$__r2=curl_exec($__ch);curl_close($__ch);
$__d2=@json_decode($__r2,true);
$__txt=$__d2["choices"][0]["message"]["content"]??null;
if($__txt){header("Content-Type:application/json");echo json_encode(["provider"=>"conv-guard-sovereign","content"=>$__txt,"tool"=>"conv-guard","model"=>$__d2["model"]??"auto"],JSON_UNESCAPED_UNICODE);exit;}
}
// OPUS-PERSISTENT PRE-ROUTER HOOK
$_jin=json_decode(file_get_contents('php://input'),true);
if($_jin && isset($_jin['message'])) $_POST['message']=$_jin['message'];
if(isset($_POST['message'])||isset($_GET['message'])||isset($_jin['message'])){
$_msg=$_jin['message']??$_POST['message']??$_GET['message']??'';
@require_once '/opt/wevia-brain/arena-pre-intents.php';
$_arena=function_exists('arena_pre_check')?arena_pre_check($_msg):null;
if($_arena){header('Content-Type:application/json');echo json_encode($_arena);exit;}
// Dynamic Tool Resolver — 67 tools from JSON registry
@require_once '/opt/wevia-brain/wevia-dynamic-resolver.php';
$_dyn = function_exists('wevia_dynamic_resolve') ? wevia_dynamic_resolve($_msg) : null;
if ($_dyn) { $_dyn['provider']=$_dyn['provider']??'fs-verify'; header('Content-Type:application/json'); echo json_encode($_dyn); exit; }
require_once '/opt/wevia-brain/wevia-wave200.php';
$_w200=wevia_wave200($_msg,['provider'=>'fs-verify','tier'=>0,'latency_ms'=>0,'cost'=>0,'source'=>'wave200-pre']);
if($_w200){header('Content-Type:application/json');echo json_encode($_w200);exit;}
require_once '/opt/wevia-brain/wevia-gap-intents.php';
$_base=['provider'=>'fs-verify','tier'=>0,'latency_ms'=>0,'cost'=>0,'source'=>'opus-persistent'];
$_r=function_exists('opus_persistent_intents')?opus_persistent_intents($_msg,$_base):null;
if(!$_r && function_exists('opus_ux_audit')) $_r=opus_ux_audit($_msg,$_base);
if(!$_r && function_exists('opus_mega_intents')) $_r=opus_mega_intents($_msg,$_base);
if(!$_r && function_exists('opus_tout_va_bien')) $_r=opus_tout_va_bien($_msg,$_base);
if(!$_r && function_exists('opus_extra_intents')) $_r=opus_extra_intents($_msg,$_base);
if($_r){header('Content-Type:application/json');echo json_encode($_r);exit;}
}
/**
* WEVIA MASTER API v1.0 — Standalone endpoint
* URL: /api/wevia-master-api.php
*
* ENDPOINTS:
* POST /api/wevia-master-api.php → Route a message
* GET /api/wevia-master-api.php?health → Health check
* GET /api/wevia-master-api.php?stats → Routing statistics
* GET /api/wevia-master-api.php?test → Quick test
* GET /api/wevia-master-api.php?dashboard → Visual dashboard
*/
header("Content-Type: application/json; charset=utf-8");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") { http_response_code(200); exit; }
clearstatcache(true, "/opt/wevia-brain/wevia-master-router.php"); if (function_exists('opcache_invalidate')) opcache_invalidate("/opt/wevia-brain/wevia-master-router.php", true);
require_once "/opt/wevia-brain/wevia-master-router.php";
// ═══ ROUTING ═══
$action = null;
if (isset($_GET['health'])) $action = 'health';
elseif (isset($_GET['stats'])) $action = 'stats';
elseif (isset($_GET['test'])) $action = 'test';
elseif (isset($_GET['score'])) $action = 'score';
elseif (isset($_GET['dashboard'])) $action = 'dashboard';
elseif (isset($_GET['rag'])) $action = 'rag';
elseif (isset($_GET['capabilities'])) $action = 'capabilities';
switch ($action) {
case 'health':
echo json_encode(mr_healthCheck(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
case 'stats':
echo json_encode(mr_getStats(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
case 'test':
$msg = $_GET['q'] ?? 'Bonjour, comment vas-tu?';
// Dynamic Resolver FIRST (258 exec tools)
@require_once '/opt/wevia-brain/wevia-dynamic-resolver.php';
$_dr = function_exists('wevia_dynamic_resolve') ? wevia_dynamic_resolve($msg) : null;
if ($_dr) { echo json_encode(['provider'=>$_dr['source']??'dynamic-resolver','response'=>$_dr['content']??''], JSON_UNESCAPED_UNICODE); exit; }
// OPUS INTERCEPT GET
$__base2 = ['provider' => 'opus-intercept', 'tier' => 0];
$__r2 = opus_persistent_intents($msg, $__base2);
if (!$__r2) $__r2 = opus_ux_audit($msg, $__base2);
if (!$__r2) $__r2 = opus_mega_intents($msg, $__base2);
if ($__r2) { echo json_encode($__r2); exit; }
$result = mr_route($msg, @file_get_contents('/etc/wevia/system-prompt.txt') ?: 'Tu es WEVIA, IA souveraine de WEVAL Consulting.');
echo json_encode([
'input' => $msg,
'response' => mb_substr($result['content'], 0, 500),
'model' => $result['model'],
'provider' => $result['provider'],
'tier' => $result['tier'],
'latency_ms' => $result['latency_ms'],
'source' => $result['source'],
'routing' => $result['routing'] ?? null,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
case 'score':
$msg = $_GET['q'] ?? '';
if (empty($msg)) {
echo json_encode(['error' => 'Pass ?score&q=your+message']);
exit;
}
echo json_encode(mr_scoreComplexity($msg), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
case 'capabilities':
$caps = wevia_listCapabilities();
// Wire dormant capabilities from registry
$dormFile = '/opt/wevia-brain/wevia-master-capabilities.json';
if (file_exists($dormFile)) {
$dorm = json_decode(file_get_contents($dormFile), true);
$caps['dormant_capabilities'] = $dorm['summary'] ?? [];
$caps['total_capabilities'] = ($dorm['total'] ?? 0) + count($caps);
$caps['dormant_categories'] = array_keys($dorm['categories'] ?? []);
}
echo json_encode($caps, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
case 'rag':
$msg = $_GET['q'] ?? 'Comment fonctionne WEVADS?';
$rag = rag_search($msg);
echo json_encode($rag, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
case 'dashboard':
header("Content-Type: text/html; charset=utf-8");
$stats = mr_getStats();
$health = mr_healthCheck();
?><!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>WEVIA Master Router — Dashboard</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#0a0a0f;color:#e8e6f0;font-family:-apple-system,sans-serif;padding:2rem}
h1{font-size:1.8rem;margin-bottom:1rem;color:#00ff88}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1rem;margin:1.5rem 0}
.card{background:#12121a;border:1px solid #2a2a3d;border-radius:12px;padding:1.2rem;text-align:center}
.card .num{font-size:2.2rem;font-weight:700;color:#00ff88;font-family:monospace}
.card .label{font-size:.75rem;color:#8888a8;margin-top:.3rem}
.status{display:inline-block;padding:.2em .6em;border-radius:100px;font-size:.7rem;font-family:monospace}
.up{background:#00ff8822;color:#00ff88;border:1px solid #00ff88}
.down{background:#ff446622;color:#ff4466;border:1px solid #ff4466}
table{width:100%;border-collapse:collapse;margin:1rem 0;font-size:.85rem}
th{background:#1a1a28;padding:.6rem;text-align:left;font-size:.7rem;text-transform:uppercase;letter-spacing:.1em;color:#8888a8}
td{padding:.5rem .6rem;border-bottom:1px solid #2a2a3d}
.t0{color:#00ff88}.t1{color:#4488ff}.t2{color:#aa66ff}.t3{color:#ff4466}
pre{background:#12121a;border:1px solid #2a2a3d;border-radius:8px;padding:1rem;overflow-x:auto;font-size:.75rem;margin:1rem 0}
</style>
</head>
<body>
<h1>⚡ WEVIA Master Router v<?=MR_VERSION?></h1>
<div class="grid">
<div class="card">
<div class="num"><?=$health['ollama']==='UP'?'<span class="status up">UP</span>':'<span class="status down">DOWN</span>'?></div>
<div class="label">Ollama (port 11434)</div>
</div>
<div class="card"><div class="num"><?=$health['ollama_models']??0?></div><div class="label">Modèles locaux</div></div>
<div class="card"><div class="num"><?=$health['tier1_providers']?></div><div class="label">Tier 1 (free fast)</div></div>
<div class="card"><div class="num"><?=$health['tier2_providers']?></div><div class="label">Tier 2 (free quality)</div></div>
<div class="card"><div class="num"><?=$health['secrets_count']?></div><div class="label">Secrets chargés</div></div>
</div>
<h2 style="margin-top:2rem">📊 Stats par jour</h2>
<table>
<thead><tr><th>Date</th><th>Total</th><th class="t0">Tier 0 (local)</th><th class="t1">Tier 1 (fast)</th><th class="t2">Tier 2 (quality)</th><th>Latence moy.</th><th>Coût</th></tr></thead>
<tbody>
<?php
if (is_array($stats)) {
krsort($stats);
foreach ($stats as $date => $d) {
if (!is_array($d)) continue;
$t0 = $d['by_tier'][0] ?? 0;
$t1 = $d['by_tier'][1] ?? 0;
$t2 = $d['by_tier'][2] ?? 0;
$total = $d['total'] ?? 0;
$pct0 = $total > 0 ? round($t0/$total*100) : 0;
echo "<tr><td>$date</td><td>{$total}</td>";
echo "<td class='t0'>{$t0} ({$pct0}%)</td>";
echo "<td class='t1'>{$t1}</td><td class='t2'>{$t2}</td>";
echo "<td>{$d['avg_latency']}ms</td><td>{$d['cost']}€</td></tr>";
}
}
?>
</tbody>
</table>
<h2>🔍 Test rapide</h2>
<p style="color:#8888a8;margin:.5rem 0">Exemples:</p>
<pre>
curl "<?=$_SERVER['HTTP_HOST']?>/api/wevia-master-api.php?test&q=Bonjour"
curl "<?=$_SERVER['HTTP_HOST']?>/api/wevia-master-api.php?score&q=Explique+comment+optimiser+nginx"
curl "<?=$_SERVER['HTTP_HOST']?>/api/wevia-master-api.php?health"
</pre>
<h2>🏗️ Architecture</h2>
<pre>
TIER 0 (Souverain, 0€) → Ollama:11434 [weval-brain-v2, qwen2.5:7b, qwen3:4b, mistral, medllama2]
TIER 1 (Free ultra-fast) → Cerebras, Groq, SambaNova
TIER 2 (Free quality) → Mistral Cloud, Cohere, Gemini
TIER 3 (Frontier, payant) → Claude API, GPT API (non implémenté — dernier recours)
</pre>
<h2>📡 Providers actifs</h2>
<pre><?php
$t1 = mr_getTier1Providers();
foreach ($t1 as $n => $c) echo "TIER1 $n: {$c['model']} ({$c['speed']})\n";
$t2 = mr_getTier2Providers();
foreach ($t2 as $n => $c) echo "TIER2 $n: {$c['model']} ({$c['speed']})\n";
?></pre>
<p style="margin-top:2rem;font-size:.7rem;color:#555">WEVIA Master Router — WEVAL Consulting — Avril 2026</p>
</body></html>
<?php
exit;
}
// ═══ POST: Route a message ═══
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
echo json_encode(['error' => 'POST required. Try ?health, ?stats, ?test, ?dashboard']);
exit;
}
// Debug: log raw input
$rawInput = file_get_contents("php://input");
@file_put_contents("/tmp/master-debug.log", date("H:i:s")." RAW=".substr($rawInput,0,200)."\n", FILE_APPEND);
$input = json_decode($rawInput, true);
$message = $input['message'] ?? $input['msg'] ?? $input['content'] ?? '';
$system = $input['system'] ?? (@file_get_contents('/etc/wevia/system-prompt.txt') ?: 'Tu es WEVIA, IA souveraine de WEVAL Consulting.');
// OPUS-FIX RC#7: Inject architecture context for LLM awareness
$system .= "\nARCHI INTERNE: Sovereign=port4000(13 providers IA). Paperclip=890agents/2484skills. DeerFlow=14skills. Registry=375tools. Wiki=1229. Vault=550. Pages=174. APIs=437. Ethica=141K HCPs. MonDsh=test NonReg Monitoring Dashboard. Monitor=test FUNC NonReg. WEVCODE=IDE souverain. Arsenal=backoffice port5890.";
$history = $input['history'] ?? [];
// W115b-PRE: Snapshot Hetzner (12-AVR) — intercept BEFORE wave114
if (preg_match('/(snapshot.*hetzner|hetzner.*snap|snap.*archiv|archiv.*snap|snap.*status|snap.*progress)/iu', $message)) {
$log = @file_get_contents('/tmp/wevia-snapshot-archiver.log');
$ps = trim(@shell_exec('ps aux 2>/dev/null | grep "wevia-snap-archiver" | grep -v grep'));
// W115b-LAUNCH/KILL (wired by Opus)
if (preg_match("/(lance|start|relance|demarre)/iu", $message) && empty($ps)) {
@shell_exec("rm -f /tmp/snap-archiver.lock; echo /tmp/snap-final.sh | at now 2>&1");
sleep(3);
$r = "ARCHIVER LANCE! Verifier dans 30s avec snap archiver status";
echo json_encode(["content"=>$r,"provider"=>"fs-verify","source"=>"w115b-launch"], JSON_UNESCAPED_UNICODE);
exit;
}
if (preg_match("/(kill|stop|arret)/iu", $message)) {
@shell_exec("pkill -f wevia-snap-archiver 2>/dev/null; rm -f /tmp/snap-archiver.lock");
$r = "ARCHIVER STOPPE";
echo json_encode(["content"=>$r,"provider"=>"fs-verify","source"=>"w115b-kill"], JSON_UNESCAPED_UNICODE);
exit;
}
$hz = trim(@shell_exec('python3 /opt/weval-l99/wevia-snap-archiver.py list 2>&1'));
$r = "ARCHIVER: " . ($ps ? "RUNNING
" . $ps : "STOPPED") . "
Snapshots:
" . $hz . "
Log (last 10):
" . ($log ? implode("
", array_slice(explode("
", trim($log)), -10)) : "NO_LOG");
echo json_encode(['content'=>$r,'provider'=>'fs-verify','tier'=>0,'latency_ms'=>0,'cost'=>0,'source'=>'w115b'],JSON_UNESCAPED_UNICODE);
exit;
}
// STRATEGIC GUARD: skip wave114 for business/strategy questions → go direct mr_route
// === OPUS4-AUTOWIRE-INCLUDE-v1 (17avr) ===
// Fix cause racine : Wave 128 autowire syntax tombait en LLM fallback.
// Handler traite "master add intent" + "master list intents" AVANT strategic-guard.
@require_once '/opt/wevia-brain/opus4-autowire-handler.php';
// === OPUS4-AUTOWIRE-INCLUDE-v1 END ===
require_once '/opt/wevia-brain/wevia-strategic-guard.php';
if (!is_strategic_question($message)) {
// === WAVE 114: Intent-first routing ===
$_exec_url = "https://127.0.0.1/api/wevia-full-exec.php?" . http_build_query(['m' => $message]);
$_exec_ctx = stream_context_create(["http" => ["timeout" => 25, "header" => "Host: weval-consulting.com
"], "ssl" => ["verify_peer" => false, "verify_peer_name" => false]]);
$_exec_raw = @file_get_contents($_exec_url, false, $_exec_ctx);
if ($_exec_raw) {
$_exec_d = @json_decode($_exec_raw, true);
$_exec_r = $_exec_d['response'] ?? '';
if ($_exec_r && strlen($_exec_r) > 20 && strpos($_exec_r, 'Dispo:') !== 0) {
echo json_encode(['content' => $_exec_r, 'provider' => 'fs-verify', 'tier' => 0, 'latency_ms' => 0, 'cost' => 0, 'source' => 'intent-execution'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
}
} // end strategic guard wave114
$options = $input['options'] ?? [];
// === WAVE 135: Handle file attachments ===
$attachments = $input['attachments'] ?? [];
if (!empty($attachments)) {
@mkdir('/tmp/wevia-uploads', 0777, true);
$fileContext = "\n\nFICHIERS JOINTS:\n";
foreach (array_slice($attachments, 0, 5) as $att) {
$name = preg_replace('/[^a-zA-Z0-9._-]/', '_', $att['name'] ?? 'file');
$data = base64_decode($att['data'] ?? '');
$path = "/tmp/wevia-uploads/" . time() . "_" . $name;
file_put_contents($path, $data);
$size = strlen($data);
$type = $att['type'] ?? 'unknown';
if (strpos($type, 'image') !== false) {
// Image: save and describe
$fileContext .= "- Image: $name ({$size}B) sauvee: $path\n";
$fileContext .= " Type: $type. Analyse visuelle demandee.\n";
// Copy to public screenshots for reference
@copy($path, "/var/www/html/screenshots/upload_" . basename($path));
$fileContext .= " URL: https://weval-consulting.com/screenshots/upload_" . basename($path) . "\n";
} elseif (strpos($type, 'text') !== false || strpos($type, 'javascript') !== false || strpos($type, 'php') !== false || strpos($type, 'json') !== false || strpos($type, 'xml') !== false || strpos($type, 'csv') !== false) {
// Text/code: read content
$content = substr($data, 0, 5000);
$fileContext .= "- Fichier texte: $name ({$size}B)\n";
$fileContext .= " Contenu:\n```\n$content\n```\n";
} elseif (strpos($type, 'pdf') !== false) {
$fileContext .= "- PDF: $name ({$size}B) sauve: $path\n";
// Try pdftotext
$txt = @shell_exec("pdftotext $path - 2>/dev/null | head -100");
if ($txt) $fileContext .= " Extrait:\n$txt\n";
} else {
$fileContext .= "- Fichier: $name ($type, {$size}B) sauve: $path\n";
}
}
$message .= $fileContext;
}
if (empty(trim($message))) {
echo json_encode(['error' => 'message required']);
exit;
}
ob_start();
try {
// OPUS INTERCEPT: check persistent intents BEFORE LLM
$__base = ['provider' => 'opus-intercept', 'tier' => 0, 'latency_ms' => 0, 'cost' => 0, 'source' => 'opus-persistent'];
$__r = opus_persistent_intents($message, $__base);
if (!$__r) $__r = opus_ux_audit($message, $__base);
if (!$__r) $__r = opus_mega_intents($message, $__base);
if ($__r) {
echo json_encode($__r);
exit;
}
// VAULT CONTEXT: inject relevant notes to reduce tokens
@include_once '/opt/wevia-brain/wevia-vault-context.php';
if (function_exists('wevia_vault_context')) {
$vaultCtx = wevia_vault_context($message, 3);
if ($vaultCtx) $system .= $vaultCtx;
}
$result = mr_route($message, $system, $history, $options);
$stray = ob_get_clean();
if (!$result || empty($result)) {
echo json_encode(['error'=>'empty_result','stray_output'=>substr($stray,0,500),'provider'=>'unknown','response'=>'']);
} else {
// Ensure response key exists for admin compatibility
if (!isset($result['response']) && isset($result['content'])) {
$result['response'] = $result['content'];
}
$json = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
if ($json === false) {
// JSON encode failed - likely UTF8 issue
array_walk_recursive($result, function(&$v){if(is_string($v))$v=mb_convert_encoding($v,'UTF-8','UTF-8');});
$json = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
}
echo $json;
}
} catch (\Throwable $e) {
$stray = ob_get_clean();
echo json_encode(['error'=>$e->getMessage(),'file'=>basename($e->getFile()),'line'=>$e->getLine(),'stray'=>substr($stray,0,200)]);
}

View File

@@ -0,0 +1,16 @@
<?php
// OPUS5 PROMOTED 2026-04-19T20:25 priority prefix
return array (
'name' => 'apple_scan',
'triggers' => array(
0 => 'wevia apple',
1 => 'apple scan',
2 => 'scan photos',
3 => 'apple photos',
4 => 'scan iphone',
5 => 'apple status',
),
'cmd' => 'curl -sk https://weval-consulting.com/api/wevia-apple-scan.php?action=stats',
'status' => 'EXECUTED',
'source' => 'opus-yacine-apple-promoted',
);

View File

@@ -0,0 +1,15 @@
<?php
return array (
'name' => 'responsive_audit_fix',
'triggers' =>
array (
0 => 'opus_responsive_audit',
1 => 'opus_responsive_batch',
),
'cmd' => '/var/www/html/api/handlers/responsive-audit-fix.sh',
'status' => 'EXECUTED',
'created_at' => '2026-04-19T18:21:19+00:00',
'approved_at' => '2026-04-19T18:22:00+00:00',
'approved_by' => 'opus-director-6sigma',
'source' => 'opus4-autowire-early-v2',
);

View File

@@ -0,0 +1,30 @@
{
"scans": [
{
"id": "apple_69e51d66b62457.37078168",
"filename": "test-erp-page.png",
"stored_as": "apple_69e51d66b62457.37078168.png",
"size_bytes": 307961,
"scan_ms": 1914,
"scanned_at": "2026-04-19T18:22:32+00:00",
"caption": "ERP Gap-Fill Offer page — test scan Opus 19avr",
"ocr_text": "@ WEVAL ERP Gap-Fill Agents.\n\nOffre service commerciale — Agents Al autonomes pour combler les gaps fonctionnels des ERPs\nSAP\/Oracle\/Sage, par département métier, adaptables à 7 verticaux industriels.\n\n| © Les ERPs SAP\/Oracle\/Sage ne couvrent pas 100% des besoins métier. Nos agents Al comblent les gaps là où ERPs échouent.\n\nRISQUES CATALOGUÉS ERP GAPS IDENTIFIÉS VERTICAUX COUVERTS AGENTS PACK TOTAL\n8 critical -14 high 7 départements Retail - Pharma - Banque « Industrie 23 clients identifies\n\nServices - Conseil - Energie\n\n| @ Matrice des risques 5x5 — Likelihood x impact, mitigés par agents WEVAL\n\nLEGENDE SEVERITE\n\nFaible (1-4) IMPACT 1 IMPACT 2 IMPACT 3\nible\n\n1 Modéré (5-9)\n\nI élevé (10-14) cf o\n\nI critique (15-25)\n\ni\n\nChaque cellule = nombre de risques. ü\n\nCliquez pour filtrer la liste ci-dessous.\n? o\na o\n7 o\n\nIMPACT 4\n\n{ wo - isers=25 { eas - ours { por - 1445-20\n\nTAM\n\nPELINE\n\n7.3 me\n\n23 clients x revenue avg\n\nIMPACT 5\n\nV67 Registry — 84 ERP agents \/ 96\ntotal\n\nSavings potentiel : 21.11 M€\/an\nPaperclip source-of-truth + API V67\n",
"vision_text": "",
"oss_extracted": {
"github_urls": [],
"project_names": [],
"stacks": [],
"docker_images": []
},
"counts": {
"github_urls": 0,
"project_names": 0,
"docker_images": 0
},
"image_url": "https:\/\/weval-consulting.com\/data\/wevia-apple-uploads\/apple_69e51d66b62457.37078168.png"
}
],
"total": 1,
"oss_total": 0,
"last_scan": "2026-04-19T18:22:32+00:00"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

248
wevia-apple.html Normal file
View File

@@ -0,0 +1,248 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>WEVIA Apple · Photos Scanner — Open Source Intelligence</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<style>
:root{--bg:#0a0e1a;--panel:#111827;--panel2:#1f2937;--br:#1f2937;--fg:#e5e7eb;--mute:#94a3b8;--accent:#60a5fa;--apple:#e5e5ea;--gold:#fbbf24;--ok:#22c55e;--warn:#f59e0b;--err:#ef4444}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(ellipse at top,#0f172a 0%,var(--bg) 60%);color:var(--fg);font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","Segoe UI",system-ui,sans-serif;min-height:100vh;padding:0}
.wrap{max-width:1400px;margin:0 auto;padding:24px}
.hdr{display:flex;align-items:center;gap:16px;margin-bottom:24px;padding-bottom:20px;border-bottom:1px solid var(--br)}
.hdr .logo{width:48px;height:48px;background:linear-gradient(135deg,#000,#1d1d1f);border-radius:12px;display:grid;place-items:center;font-size:28px}
.hdr h1{margin:0;font-size:1.6rem;font-weight:700;letter-spacing:-0.3px}
.hdr .sub{color:var(--mute);font-size:.9rem;margin-top:2px}
.hdr .spacer{flex:1}
.hdr .home{background:rgba(96,165,250,.1);border:1px solid rgba(96,165,250,.3);color:var(--accent);padding:8px 14px;border-radius:8px;text-decoration:none;font-size:.85rem;font-weight:600}
.hdr .home:hover{background:rgba(96,165,250,.2)}
.kpi-strip{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:24px}
.kpi{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--br);border-radius:12px;padding:16px;position:relative;overflow:hidden}
.kpi::before{content:"";position:absolute;top:0;left:0;width:3px;height:100%}
.kpi.k1::before{background:var(--accent)}
.kpi.k2::before{background:var(--gold)}
.kpi.k3::before{background:var(--ok)}
.kpi.k4::before{background:#c084fc}
.kpi .lbl{color:var(--mute);font-size:.72rem;text-transform:uppercase;letter-spacing:.8px;font-weight:600}
.kpi .val{font-size:2rem;font-weight:700;margin-top:6px;letter-spacing:-.5px}
.kpi .hint{color:var(--mute);font-size:.75rem;margin-top:4px}
.grid{display:grid;grid-template-columns:2fr 1fr;gap:24px}
.panel{background:var(--panel);border:1px solid var(--br);border-radius:12px;padding:20px}
.panel h3{margin:0 0 16px;font-size:1.05rem;font-weight:600}
.uploader{border:2px dashed rgba(96,165,250,.4);border-radius:12px;padding:32px;text-align:center;background:rgba(96,165,250,.05);transition:all .2s;cursor:pointer}
.uploader.drag{background:rgba(96,165,250,.15);border-color:var(--accent)}
.uploader .ico{font-size:48px;margin-bottom:12px}
.uploader p{margin:8px 0;color:var(--mute)}
.uploader .big{color:var(--fg);font-size:1.05rem;font-weight:600}
.uploader input{display:none}
.uploader .shortcut-hint{margin-top:18px;padding:12px;background:rgba(0,0,0,.3);border-radius:8px;font-size:.78rem;color:var(--mute);text-align:left}
.uploader .shortcut-hint code{color:var(--gold);font-family:"SF Mono",monospace;word-break:break-all}
.scan-list{max-height:680px;overflow-y:auto}
.scan-item{padding:12px;border-radius:8px;margin-bottom:10px;background:rgba(0,0,0,.2);border:1px solid var(--br);cursor:pointer;transition:all .15s}
.scan-item:hover{background:rgba(96,165,250,.08);border-color:var(--accent);transform:translateX(2px)}
.scan-item .top{display:flex;justify-content:space-between;align-items:center;gap:8px}
.scan-item .name{font-weight:600;font-size:.9rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:70%}
.scan-item .time{color:var(--mute);font-size:.72rem}
.scan-item .badges{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}
.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:.7rem;font-weight:600}
.b-gh{background:rgba(34,197,94,.15);color:var(--ok);border:1px solid rgba(34,197,94,.3)}
.b-pj{background:rgba(251,191,36,.15);color:var(--gold);border:1px solid rgba(251,191,36,.3)}
.b-dk{background:rgba(96,165,250,.15);color:var(--accent);border:1px solid rgba(96,165,250,.3)}
.empty{text-align:center;padding:40px 20px;color:var(--mute);font-size:.9rem}
.detail{display:none;position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.85);backdrop-filter:blur(8px);padding:20px;overflow-y:auto}
.detail.open{display:block}
.detail .inner{max-width:1100px;margin:20px auto;background:var(--panel);border:1px solid var(--br);border-radius:16px;padding:24px;position:relative}
.detail .close{position:absolute;top:16px;right:16px;background:none;border:1px solid var(--br);color:var(--fg);width:32px;height:32px;border-radius:8px;cursor:pointer;font-size:18px}
.detail .close:hover{background:rgba(239,68,68,.2);border-color:var(--err)}
.detail img{max-width:100%;max-height:400px;border-radius:8px;display:block;margin-bottom:16px}
.detail .sec{background:rgba(0,0,0,.25);border-radius:8px;padding:14px;margin-top:14px}
.detail .sec h4{margin:0 0 8px;font-size:.85rem;color:var(--mute);text-transform:uppercase;letter-spacing:.5px}
.detail .sec .txt{font-family:"SF Mono",monospace;font-size:.82rem;color:#cbd5e1;white-space:pre-wrap;line-height:1.5;max-height:260px;overflow-y:auto}
.oss-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:8px;margin-top:10px}
.oss-card{background:rgba(34,197,94,.08);border:1px solid rgba(34,197,94,.25);padding:10px;border-radius:8px;text-align:center;font-size:.82rem;text-decoration:none;color:var(--ok);transition:all .15s}
.oss-card:hover{background:rgba(34,197,94,.15);transform:translateY(-2px)}
.oss-card b{display:block;font-weight:700;margin-bottom:2px}
.oss-card .sub{font-size:.68rem;color:var(--mute);word-break:break-all}
.upload-btn{background:linear-gradient(135deg,#3b82f6,#1d4ed8);border:none;color:white;padding:12px 20px;border-radius:8px;font-weight:600;cursor:pointer;margin-top:12px;font-size:.9rem}
.upload-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(59,130,246,.4)}
.spinner{display:inline-block;width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:white;border-radius:50%;animation:spin 1s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.toast{position:fixed;bottom:20px;right:20px;background:var(--panel);border:1px solid var(--br);border-left:3px solid var(--ok);padding:14px 18px;border-radius:8px;box-shadow:0 10px 40px rgba(0,0,0,.5);z-index:10000;max-width:360px;font-size:.88rem}
.toast.err{border-left-color:var(--err)}
@media(max-width:900px){.grid{grid-template-columns:1fr}.kpi-strip{grid-template-columns:repeat(2,1fr)}}
</style>
</head>
<body>
<div class="wrap">
<div class="hdr">
<div class="logo">🍎</div>
<div>
<h1>WEVIA Apple — Photos Scanner</h1>
<div class="sub">OCR + Vision LLM (Qwen VL 32B) · Extraction open-source · iPhone Shortcuts compatible</div>
</div>
<div class="spacer"></div>
<a class="home" href="/weval-technology-platform.html">← WTP</a>
<a class="home" href="/wevia-master.html" style="margin-left:8px">WEVIA Master</a>
</div>
<div class="kpi-strip">
<div class="kpi k1"><div class="lbl">Photos scannées</div><div class="val" id="k-total">0</div><div class="hint" id="k-last">Aucun scan</div></div>
<div class="kpi k2"><div class="lbl">Projets OSS identifiés</div><div class="val" id="k-oss">0</div><div class="hint">Frameworks + outils</div></div>
<div class="kpi k3"><div class="lbl">GitHub URLs</div><div class="val" id="k-gh">0</div><div class="hint">Repos mentionnés</div></div>
<div class="kpi k4"><div class="lbl">Top OSS</div><div class="val" id="k-top" style="font-size:1rem;line-height:1.3"></div><div class="hint">Projet le plus cité</div></div>
</div>
<div class="grid">
<div class="panel">
<h3>📱 Upload depuis iPhone ou desktop</h3>
<div class="uploader" id="drop">
<div class="ico">🍎</div>
<p class="big">Glisse une photo ici ou clique pour choisir</p>
<p>JPG · PNG · HEIC · WEBP — OCR + Vision LLM Qwen VL 32B</p>
<input type="file" id="file" accept="image/*">
<button class="upload-btn" onclick="document.getElementById('file').click()">📷 Choisir photo</button>
<div class="shortcut-hint">
<b style="color:var(--fg)">📲 iPhone Shortcuts (direct depuis photos iPhone):</b><br>
1. Ouvrir app Raccourcis → Nouveau raccourci<br>
2. Action "Obtenir le contenu de l'URL" → <code>https://weval-consulting.com/api/wevia-apple-scan.php?action=upload</code><br>
3. Méthode POST · Form · Champ image = "Photo sélectionnée"<br>
4. Partage depuis Photos iPhone → Raccourci → Auto-scan
</div>
</div>
</div>
<div class="panel">
<h3>📚 Historique scans <span style="font-weight:400;color:var(--mute);font-size:.85rem">· clique pour drill-down</span></h3>
<div class="scan-list" id="list"><div class="empty">Aucun scan pour le moment.<br>Upload ta première photo ↙</div></div>
</div>
</div>
</div>
<div class="detail" id="detail">
<div class="inner">
<button class="close" onclick="closeDetail()">×</button>
<div id="detail-content"></div>
</div>
</div>
<script>
const API = '/api/wevia-apple-scan.php';
let SCANS = [];
async function loadAll(){
try {
const [stats, list] = await Promise.all([
fetch(API+'?action=stats&_='+Date.now()).then(r=>r.json()),
fetch(API+'?action=list&_='+Date.now()).then(r=>r.json())
]);
document.getElementById('k-total').textContent = stats.scans_total || 0;
document.getElementById('k-oss').textContent = stats.oss_total || 0;
document.getElementById('k-gh').textContent = stats.github_urls_total || 0;
const top = stats.top_projects ? Object.entries(stats.top_projects)[0] : null;
document.getElementById('k-top').textContent = top ? (top[0]+' ('+top[1]+')') : '—';
SCANS = list.scans || [];
renderList();
document.getElementById('k-last').textContent = SCANS[0] ? ('Dernier : '+new Date(SCANS[0].scanned_at).toLocaleString('fr')) : 'Aucun scan';
} catch(e){ console.error(e); }
}
function renderList(){
const el = document.getElementById('list');
if (!SCANS.length){ el.innerHTML = '<div class="empty">Aucun scan pour le moment.<br>Upload ta première photo ↙</div>'; return; }
el.innerHTML = SCANS.map(s => `
<div class="scan-item" onclick="openDetail('${s.id}')">
<div class="top">
<div class="name">📸 ${escape(s.filename)}</div>
<div class="time">${new Date(s.scanned_at).toLocaleString('fr')}</div>
</div>
<div class="badges">
${s.counts.project_names ? `<span class="badge b-pj">📦 ${s.counts.project_names} OSS</span>`:''}
${s.counts.github_urls ? `<span class="badge b-gh">🐙 ${s.counts.github_urls} GitHub</span>`:''}
${s.counts.docker_images ? `<span class="badge b-dk">🐳 ${s.counts.docker_images} Docker</span>`:''}
<span class="badge" style="background:rgba(148,163,184,.15);color:var(--mute)">${s.scan_ms}ms</span>
</div>
</div>
`).join('');
}
async function openDetail(id){
const scan = SCANS.find(s => s.id === id);
if (!scan) return;
const oss = scan.oss_extracted || {};
const html = `
<h2 style="margin:0 0 12px">📸 ${escape(scan.filename)}</h2>
<div style="color:var(--mute);font-size:.85rem;margin-bottom:14px">
Scanné le ${new Date(scan.scanned_at).toLocaleString('fr')} · ${scan.scan_ms}ms · ${Math.round(scan.size_bytes/1024)}KB
</div>
<img src="${scan.image_url}" alt="scan">
${scan.caption ? `<div class="sec"><h4>Caption</h4><div class="txt">${escape(scan.caption)}</div></div>`:''}
${oss.project_names && oss.project_names.length ? `
<div class="sec"><h4>📦 Projets OSS identifiés (${oss.project_names.length})</h4>
<div class="oss-grid">
${oss.project_names.map(p => `<a class="oss-card" href="https://github.com/search?q=${encodeURIComponent(p)}" target="_blank"><b>${escape(p)}</b><span class="sub">github search</span></a>`).join('')}
</div>
</div>`:''}
${oss.github_urls && oss.github_urls.length ? `
<div class="sec"><h4>🐙 GitHub URLs (${oss.github_urls.length})</h4>
<div class="oss-grid">
${oss.github_urls.map(u => `<a class="oss-card" href="${u.url}" target="_blank"><b>${escape(u.repo)}</b><span class="sub">${escape(u.owner)}</span></a>`).join('')}
</div>
</div>`:''}
${oss.docker_images && oss.docker_images.length ? `
<div class="sec"><h4>🐳 Docker images (${oss.docker_images.length})</h4>
<div class="txt">${oss.docker_images.map(escape).join('\n')}</div>
</div>`:''}
<div class="sec"><h4>🔍 Vision LLM (Qwen VL)</h4><div class="txt">${escape(scan.vision_text || '(pas de réponse vision)')}</div></div>
<div class="sec"><h4>📝 OCR brut (tesseract)</h4><div class="txt">${escape(scan.ocr_text || '(OCR vide)')}</div></div>
`;
document.getElementById('detail-content').innerHTML = html;
document.getElementById('detail').classList.add('open');
}
function closeDetail(){ document.getElementById('detail').classList.remove('open'); }
function escape(s){ return String(s||'').replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c])); }
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeDetail(); });
const drop = document.getElementById('drop');
const fileInput = document.getElementById('file');
drop.addEventListener('click', () => fileInput.click());
drop.addEventListener('dragover', e => { e.preventDefault(); drop.classList.add('drag'); });
drop.addEventListener('dragleave', () => drop.classList.remove('drag'));
drop.addEventListener('drop', e => {
e.preventDefault(); drop.classList.remove('drag');
if (e.dataTransfer.files && e.dataTransfer.files[0]) uploadFile(e.dataTransfer.files[0]);
});
fileInput.addEventListener('change', e => { if (e.target.files[0]) uploadFile(e.target.files[0]); });
async function uploadFile(f){
showToast(`⏳ Upload + scan ${f.name}`, false);
const fd = new FormData();
fd.append('image', f);
fd.append('caption', 'upload via web drag-drop');
try {
const r = await fetch(API+'?action=upload', { method:'POST', body:fd });
const d = await r.json();
if (d.ok){
showToast(`✅ Scan OK · ${d.scan.counts.project_names} OSS + ${d.scan.counts.github_urls} GitHub en ${d.duration_ms}ms`);
await loadAll();
setTimeout(() => openDetail(d.scan.id), 400);
} else {
showToast('❌ '+(d.error||'upload failed'), true);
}
} catch(e){ showToast('❌ '+e.message, true); }
}
function showToast(msg, isErr){
const t = document.createElement('div');
t.className = 'toast'+(isErr?' err':'');
t.textContent = msg;
document.body.appendChild(t);
setTimeout(() => t.remove(), 4500);
}
loadAll();
setInterval(loadAll, 30000);
</script>
</body>
</html>