phase-6 thumbs premium + 2 chatbots migres + doctrines 144 update et 145
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
4 livraisons phase 6: 1. THUMBNAILS premium 53/263 pages (20.2 pct coverage) - Script /opt/weval-ops/gen-thumbs-v2.py Python - wkhtmltoimage 1200x750 quality 55 js-delay 1500ms - Batch top-priority: HUB DASHBOARD AGENT BLADE AI CRM ADMIN PRODUCT - Skip existing (idempotent) - Output /var/www/html/thumbs 2. Endpoint API v2 wtp-orphans-registry.php - Ajout champ thumb URL per page - Ajout thumbs_available + thumbs_coverage_pct - Live scan toujours 6-10ms 3. Page HTML v2 enrichie - 7 KPI cards (ajout Thumbs coverage) - Cards 4-col avec preview thumb lazy loading 130px - Hover scale 1.03 - Fallback no-preview si pas thumb - onerror graceful fallback 4. 2 chatbots migres (total 4/6 interne chatbots): - saas-chat.php 171L doctrine 142 shutdown pattern - claude-pattern-api.php 330L doctrine 142 shutdown pattern - GOLD backups vault-gold/opus/*.doctrine141-*.bak - Redis DB 5 verifie 2 keys saas-chat + 1 key claude-pattern 5. Doctrine 144 update avec section thumbs phase 6 6. Doctrine 145 bilan chatbots migration complet Etat infra: - NR 153/153 invariant - Load 13-22 variable (thumbs generation active puis redescend) - 4/6 chatbots interne bridge memoire - ~1000 chatmem keys Redis DB 5 total Restants phase 7: - l99-chat SSE pattern specifique - openclaw-proxy SSE+messages array - Migration progressive des orphelines dans WTP (autorisation explicite Yacine)
This commit is contained in:
349
vault-gold/opus/phase6-20260423-222102/claude-pattern-api.php
Normal file
349
vault-gold/opus/phase6-20260423-222102/claude-pattern-api.php
Normal file
@@ -0,0 +1,349 @@
|
||||
<?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) ?: [];
|
||||
|
||||
// === DOCTRINE-141-SHUTDOWN · memory bridge ===
|
||||
if (@file_exists(__DIR__.'/wevia-memory-bridge.php')) { @require_once __DIR__.'/wevia-memory-bridge.php'; }
|
||||
$__chat_id = 'claude-pattern-api';
|
||||
$__user_id = $_COOKIE['weval_chat_session'] ?? $_SERVER['HTTP_X_USER_ID'] ?? ('anon-'.substr(md5(($_SERVER['REMOTE_ADDR']??'').($_SERVER['HTTP_USER_AGENT']??'')),0,12));
|
||||
$__msg_for_mem = '';
|
||||
register_shutdown_function(function() use (&$__msg_for_mem, $__chat_id, $__user_id) {
|
||||
if (!function_exists('wevia_mem_save')) return;
|
||||
$out = ob_get_contents();
|
||||
if (!$out) return;
|
||||
$d = @json_decode($out, true);
|
||||
$resp = is_array($d) ? ($d['response'] ?? $d['answer'] ?? $d['content'] ?? '') : $out;
|
||||
if ($resp && $__msg_for_mem) {
|
||||
@wevia_mem_save($__chat_id, $__user_id, $__msg_for_mem, is_string($resp)?$resp:json_encode($resp), 'internal');
|
||||
}
|
||||
});
|
||||
ob_start();
|
||||
// === /DOCTRINE-141-SHUTDOWN ===
|
||||
|
||||
$message = trim($input['message'] ?? ''); $__msg_for_mem = $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',
|
||||
'multiagent' => '/api/wevia-v83-multi-agent-orchestrator.php',
|
||||
'parallel13' => '/api/wevia-v77-parallel-executor.php',
|
||||
];
|
||||
$FALLBACKS = [
|
||||
'wevia-master' => '/api/opus5-autonomous-orchestrator-v3.php',
|
||||
'director' => '/api/opus5-autonomous-orchestrator-v3.php',
|
||||
'ethica' => '/api/wevia-autonomous.php',
|
||||
'multiagent' => '/api/wevia-autonomous.php',
|
||||
'parallel13' => '/api/wevia-autonomous.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;
|
||||
// Smart body based on chatbot type
|
||||
if (in_array($chatbot, ['multiagent', 'parallel13'])) {
|
||||
// These need trigger keywords for multi-agent
|
||||
$backend_body = json_encode([
|
||||
'message' => 'multiagent ' . $message,
|
||||
'session' => $session,
|
||||
]);
|
||||
} else {
|
||||
$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) {
|
||||
// Deep-dig extraction (handle SSE, nested, opus5 orchestrator)
|
||||
$backend_text = $backend_data['final_response']
|
||||
?? $backend_data['text']
|
||||
?? $backend_data['response']
|
||||
?? $backend_data['answer']
|
||||
?? $backend_data['reply']
|
||||
?? $backend_data['message']
|
||||
?? '';
|
||||
// Handle nested thinking field
|
||||
if (!$backend_text && isset($backend_data['thinking'])) {
|
||||
$backend_text = $backend_data['thinking'];
|
||||
}
|
||||
// Handle array result
|
||||
if (is_array($backend_text)) $backend_text = json_encode($backend_text, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
// Extract from SSE stream if needed
|
||||
if (!$backend_text && strpos($backend_response, 'data:') !== false) {
|
||||
preg_match_all('/data:\s*(\{[^
|
||||
]+\})/', $backend_response, $m);
|
||||
$collected = [];
|
||||
foreach ($m[1] ?? [] as $chunk) {
|
||||
$cd = @json_decode($chunk, true);
|
||||
if ($cd && !empty($cd['text'])) {
|
||||
$collected[] = $cd['text'];
|
||||
}
|
||||
}
|
||||
if ($collected) $backend_text = implode("\n", $collected);
|
||||
}
|
||||
|
||||
$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)),
|
||||
'not_hallucinating' => !preg_match('/\b(je ne sais pas|i don\'t know|n\'ai pas d\'information|imagine|hypothetical|suppose que|probablement|might be|could be)\b/i', substr($backend_text, 0, 300)),
|
||||
'has_natural_lang' => preg_match('/\b(le|la|les|un|une|des|je|vous|nous|est|sont|avec|dans|the|is|are|we|you)\b/i', substr($backend_text, 0, 200)) > 0,
|
||||
];
|
||||
|
||||
$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);
|
||||
65
vault-gold/opus/phase6-20260423-222102/gen-thumbs-v2.py
Executable file
65
vault-gold/opus/phase6-20260423-222102/gen-thumbs-v2.py
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate thumbnails for top-priority orphan pages via wkhtmltoimage"""
|
||||
import subprocess, json, urllib.request, os, sys
|
||||
|
||||
THUMBS_DIR = '/var/www/html/thumbs'
|
||||
os.makedirs(THUMBS_DIR, exist_ok=True)
|
||||
|
||||
# Fetch list
|
||||
try:
|
||||
r = urllib.request.urlopen('http://localhost/api/wtp-orphans-registry.php', timeout=10)
|
||||
data = json.load(r)
|
||||
except Exception as e:
|
||||
print(f"Fetch err: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
priority = ['ACTIVE_HUB', 'ACTIVE_DASHBOARD', 'ACTIVE_AGENT', 'ACTIVE_BLADE',
|
||||
'ACTIVE_AI', 'ACTIVE_CRM', 'ACTIVE_ADMIN', 'ACTIVE_PRODUCT']
|
||||
|
||||
targets = []
|
||||
for cat in priority:
|
||||
items = data.get('categories', {}).get(cat, [])
|
||||
for item in items[:15]: # max 15 per cat
|
||||
targets.append(item['name'])
|
||||
if len(targets) >= 70: break
|
||||
if len(targets) >= 70: break
|
||||
|
||||
print(f"Targets: {len(targets)}")
|
||||
|
||||
ok = 0; fail = 0; skip = 0
|
||||
for page in targets:
|
||||
thumb_name = page.replace('.html', '.jpg')
|
||||
thumb_path = os.path.join(THUMBS_DIR, thumb_name)
|
||||
|
||||
# Skip if exists and > 3KB
|
||||
if os.path.exists(thumb_path) and os.path.getsize(thumb_path) > 3000:
|
||||
skip += 1
|
||||
continue
|
||||
|
||||
url = f"https://weval-consulting.com/{page}"
|
||||
try:
|
||||
result = subprocess.run([
|
||||
'timeout', '25',
|
||||
'wkhtmltoimage',
|
||||
'--width', '1200',
|
||||
'--height', '750',
|
||||
'--quality', '55',
|
||||
'--javascript-delay', '1500',
|
||||
'--no-stop-slow-scripts',
|
||||
'--quiet',
|
||||
url, thumb_path
|
||||
], capture_output=True, timeout=30)
|
||||
|
||||
if os.path.exists(thumb_path) and os.path.getsize(thumb_path) > 3000:
|
||||
ok += 1
|
||||
print(f" ✅ {page}")
|
||||
else:
|
||||
fail += 1
|
||||
if os.path.exists(thumb_path): os.remove(thumb_path)
|
||||
except Exception as e:
|
||||
fail += 1
|
||||
print(f" ❌ {page}: {str(e)[:60]}")
|
||||
|
||||
print(f"\nResults: OK={ok} FAIL={fail} SKIP={skip}")
|
||||
total_thumbs = len([f for f in os.listdir(THUMBS_DIR) if f.endswith('.jpg')])
|
||||
print(f"Total thumbs in dir: {total_thumbs}")
|
||||
190
vault-gold/opus/phase6-20260423-222102/saas-chat.php
Normal file
190
vault-gold/opus/phase6-20260423-222102/saas-chat.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
// WAVE 253 · saas-chat grounded with live WEVAL data (anti-hallucination)
|
||||
header('Content-Type: application/json');
|
||||
// WAVE 256: Defense-in-depth sanitizer (mirrors weval-ia-fast.php)
|
||||
function wave256_sanitize($t) {
|
||||
if (!is_string($t) || $t === '') return $t;
|
||||
$blocklist = ['Groq', 'Cerebras', 'SambaNova', 'Ollama', 'DeepSeek', 'Mistral', 'Together', 'Replicate',
|
||||
'vLLM', 'Qwen', 'NVIDIA NIM', 'Cohere', 'OpenRouter', 'HuggingFace', 'Anthropic',
|
||||
'/opt/', '/var/www/', '/etc/', 'admin123', '49222', '11434', '6333', '4001',
|
||||
'204.168', '95.216', '151.80', '10.1.0', 'root@', 'ssh -p', 'docker ps',
|
||||
'PGPASSWORD', 'PostgreSQL', 'weval_leads', 'weval_tasks'];
|
||||
foreach ($blocklist as $w) $t = str_ireplace($w, 'WEVIA Engine', $t);
|
||||
$t = preg_replace('/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/', '[infrastructure securisee]', $t);
|
||||
$t = preg_replace('/\b(sk-[a-zA-Z0-9]{20,}|xoxb-[a-zA-Z0-9-]{20,}|eyJ[a-zA-Z0-9_.-]{50,})\b/', '[token securise]', $t);
|
||||
$t = preg_replace('/\b(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}\b/', '[token securise]', $t);
|
||||
return $t;
|
||||
}
|
||||
|
||||
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
if($_SERVER['REQUEST_METHOD']==='OPTIONS'){header('Access-Control-Allow-Methods: POST');header('Access-Control-Allow-Headers: Content-Type');exit;}
|
||||
$input=json_decode(file_get_contents('php://input'),true);
|
||||
|
||||
// === DOCTRINE-141-SHUTDOWN · memory bridge ===
|
||||
if (@file_exists(__DIR__.'/wevia-memory-bridge.php')) { @require_once __DIR__.'/wevia-memory-bridge.php'; }
|
||||
$__chat_id = 'saas-chat';
|
||||
$__user_id = $_COOKIE['weval_chat_session'] ?? $_SERVER['HTTP_X_USER_ID'] ?? ('anon-'.substr(md5(($_SERVER['REMOTE_ADDR']??'').($_SERVER['HTTP_USER_AGENT']??'')),0,12));
|
||||
$__msg_for_mem = '';
|
||||
register_shutdown_function(function() use (&$__msg_for_mem, $__chat_id, $__user_id) {
|
||||
if (!function_exists('wevia_mem_save')) return;
|
||||
$out = ob_get_contents();
|
||||
if (!$out) return;
|
||||
$d = @json_decode($out, true);
|
||||
$resp = is_array($d) ? ($d['response'] ?? $d['answer'] ?? $d['content'] ?? '') : $out;
|
||||
if ($resp && $__msg_for_mem) {
|
||||
@wevia_mem_save($__chat_id, $__user_id, $__msg_for_mem, is_string($resp)?$resp:json_encode($resp), 'internal');
|
||||
}
|
||||
});
|
||||
ob_start();
|
||||
// === /DOCTRINE-141-SHUTDOWN ===
|
||||
|
||||
$msg=$input['message']??$_POST['message']??''; $__msg_for_mem = $msg;
|
||||
$session=$input['session']??$_POST['session']??'default';
|
||||
$origin=$input['origin']??$_SERVER['HTTP_REFERER']??'unknown';
|
||||
if(!$msg)die(json_encode(['error'=>'no message']));
|
||||
|
||||
// === WAVE 253 GROUNDING: fetch live data BEFORE LLM call (anti-hallucination) ===
|
||||
function pg_c() { return @pg_connect('host=10.1.0.3 port=5432 dbname=paperclip user=admin password=admin123 connect_timeout=2'); }
|
||||
|
||||
function build_grounding_context() {
|
||||
$ctx = ['ts' => date('c'), 'source' => 'live'];
|
||||
$pg = pg_c();
|
||||
if ($pg) {
|
||||
// 48 leads stats
|
||||
$r = @pg_query($pg, "SELECT COUNT(*) AS n, ROUND(AVG(mql_score)) AS avg_mql, SUM(CASE WHEN sql_qualified THEN 1 ELSE 0 END) AS sql_q FROM weval_leads");
|
||||
if ($r) { $ctx['leads'] = pg_fetch_assoc($r); }
|
||||
// Top industries
|
||||
$r2 = @pg_query($pg, "SELECT industry, COUNT(*) AS n, ROUND(AVG(mql_score)) AS avg_mql FROM weval_leads WHERE industry IS NOT NULL GROUP BY industry ORDER BY n DESC, avg_mql DESC LIMIT 8");
|
||||
if ($r2) { $ctx['industries'] = []; while ($row = pg_fetch_assoc($r2)) $ctx['industries'][] = $row; }
|
||||
// Top countries
|
||||
$r3 = @pg_query($pg, "SELECT country, COUNT(*) AS n FROM weval_leads WHERE country IS NOT NULL GROUP BY country ORDER BY n DESC LIMIT 6");
|
||||
if ($r3) { $ctx['countries'] = []; while ($row = pg_fetch_assoc($r3)) $ctx['countries'][] = $row; }
|
||||
// TOP 5 leads with MQL 85+
|
||||
$r4 = @pg_query($pg, "SELECT company, mql_score, industry, country, sql_qualified FROM weval_leads WHERE mql_score >= 85 ORDER BY mql_score DESC LIMIT 6");
|
||||
if ($r4) { $ctx['top_leads'] = []; while ($row = pg_fetch_assoc($r4)) $ctx['top_leads'][] = $row; }
|
||||
// Tasks
|
||||
$r5 = @pg_query($pg, "SELECT status, COUNT(*) AS n, SUM(estimated_mad) AS mad FROM weval_tasks GROUP BY status");
|
||||
if ($r5) { $ctx['tasks_by_status'] = []; while ($row = pg_fetch_assoc($r5)) $ctx['tasks_by_status'][] = $row; }
|
||||
pg_close($pg);
|
||||
}
|
||||
// Solutions scanner top 3
|
||||
$ch = curl_init('http://127.0.0.1/api/solution-scanner.php?action=full_analysis');
|
||||
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>1, CURLOPT_TIMEOUT=>5]);
|
||||
$raw = curl_exec($ch); curl_close($ch);
|
||||
if ($raw) {
|
||||
$sd = @json_decode($raw, true);
|
||||
if ($sd && !empty($sd['solutions'])) {
|
||||
$ctx['top_solutions'] = [];
|
||||
foreach (array_slice($sd['solutions'], 0, 5) as $sol) {
|
||||
$ctx['top_solutions'][] = [
|
||||
'name' => $sol['name'],
|
||||
'winning_score' => $sol['winning_score'],
|
||||
'decision' => $sol['decision'],
|
||||
'mad' => $sol['mad_est'],
|
||||
'maturity' => $sol['maturity'],
|
||||
'dev_calendar_days' => $sol['dev_effort']['calendar_days_est'] ?? 0,
|
||||
];
|
||||
}
|
||||
$ctx['pipeline_summary'] = $sd['summary'] ?? null;
|
||||
}
|
||||
}
|
||||
return $ctx;
|
||||
}
|
||||
|
||||
$grounding = build_grounding_context();
|
||||
|
||||
// Build grounding text block for system prompt
|
||||
$ground_text = "DONNÉES WEVAL LIVE (ne jamais dire que tu n'as pas accès - ces données sont fraîches, injectées):\n\n";
|
||||
|
||||
if (!empty($grounding['leads'])) {
|
||||
$l = $grounding['leads'];
|
||||
$ground_text .= "📊 PAPERCLIP LEADS: " . $l['n'] . " leads · avg MQL " . $l['avg_mql'] . " · SQL qualifiés " . $l['sql_q'] . "\n";
|
||||
}
|
||||
if (!empty($grounding['industries'])) {
|
||||
$ground_text .= "🏭 TOP INDUSTRIES: ";
|
||||
foreach ($grounding['industries'] as $i) $ground_text .= $i['industry'] . " (" . $i['n'] . " leads, MQL " . $i['avg_mql'] . ") · ";
|
||||
$ground_text .= "\n";
|
||||
}
|
||||
if (!empty($grounding['countries'])) {
|
||||
$ground_text .= "🗺 PAYS: ";
|
||||
foreach ($grounding['countries'] as $c) $ground_text .= $c['country'] . "=" . $c['n'] . " · ";
|
||||
$ground_text .= "\n";
|
||||
}
|
||||
if (!empty($grounding['top_leads'])) {
|
||||
$ground_text .= "🏆 TOP LEADS MQL85+:\n";
|
||||
foreach ($grounding['top_leads'] as $tl) {
|
||||
$sql = ($tl['sql_qualified'] === 't' || $tl['sql_qualified'] === true) ? '✅SQL' : '';
|
||||
$ground_text .= " · " . $tl['company'] . " (MQL " . $tl['mql_score'] . " · " . $tl['industry'] . " · " . $tl['country'] . " $sql)\n";
|
||||
}
|
||||
}
|
||||
if (!empty($grounding['tasks_by_status'])) {
|
||||
$ground_text .= "📋 TASKS: ";
|
||||
foreach ($grounding['tasks_by_status'] as $t) $ground_text .= $t['status'] . "=" . $t['n'] . " (" . round(($t['mad']??0)/1000) . "K MAD) · ";
|
||||
$ground_text .= "\n";
|
||||
}
|
||||
if (!empty($grounding['top_solutions'])) {
|
||||
$ground_text .= "\n🎯 TOP 5 SOLUTIONS WEVAL (Scanner WePredict):\n";
|
||||
foreach ($grounding['top_solutions'] as $s) {
|
||||
$ground_text .= " · " . $s['name'] . " · score " . $s['winning_score'] . "/100 · " . $s['decision'] . " · " . round($s['mad']/1000) . "K MAD · maturity " . $s['maturity'] . "% · dev " . $s['dev_calendar_days'] . "j\n";
|
||||
}
|
||||
}
|
||||
if (!empty($grounding['pipeline_summary'])) {
|
||||
$ps = $grounding['pipeline_summary'];
|
||||
$ground_text .= "\n💰 PIPELINE GLOBAL: " . round($ps['total_mad_pipeline']/1000) . "K MAD · dev cost " . round($ps['total_dev_cost_mad']/1000) . "K MAD · " . $ps['ship_it'] . " SHIP_IT · " . $ps['dev_sprint'] . " DEV_SPRINT\n";
|
||||
}
|
||||
|
||||
// Redis memory
|
||||
$history=[];
|
||||
try{$redis=new Redis();$redis->connect('127.0.0.1',6379);$redis->select(3);
|
||||
$raw=$redis->get("saas:$session");if($raw)$history=json_decode($raw,true)?:[];}catch(Exception $e){$redis=null;}
|
||||
|
||||
// System prompt ENRICHED with grounding
|
||||
$system_prompt = "Tu es WEVIA Master, IA de WEVAL Consulting Casablanca (Maroc + France + MENA).\n\n" .
|
||||
"RÈGLES STRICTES ANTI-HALLUCINATION (wave 253):\n" .
|
||||
"1. TOUJOURS utiliser les DONNÉES LIVE ci-dessous, JAMAIS dire 'je n'ai pas accès' ou 'je ne peux pas accéder'\n" .
|
||||
"2. Si on te demande 'combien de leads' → réponds avec le chiffre exact fourni\n" .
|
||||
"3. Si on te demande top industries/pays/leads → liste ceux fournis\n" .
|
||||
"4. Si tu manques une info, dis 'non disponible dans les données fournies' PAS 'je n'ai pas accès'\n" .
|
||||
"5. Réponds en français concis. Utilise les noms réels (Ethica, Vistex, Huawei, etc.)\n\n" .
|
||||
$ground_text . "\n" .
|
||||
"CONTEXTE: origin=" . basename($origin) . "\n" .
|
||||
"Réponds maintenant à la question en t'appuyant sur ces données live.";
|
||||
|
||||
$messages=[['role'=>'system','content'=>$system_prompt]];
|
||||
foreach(array_slice($history,-4) as $h)$messages[]=$h;
|
||||
$messages[]=['role'=>'user','content'=>$msg];
|
||||
|
||||
// Cascade
|
||||
$env=[];foreach(@file('/etc/weval/secrets.env',2|4)?:[] as $l){if(strpos($l,'=')!==false){[$k,$v]=explode('=',$l,2);$env[trim($k)]=trim($v," \t\"'");}}
|
||||
$providers=[
|
||||
['u'=>'https://api.groq.com/openai/v1/chat/completions','k'=>$env['GROQ_KEY']??'','m'=>'llama-3.3-70b-versatile','name'=>'Groq-Llama3.3'],
|
||||
['u'=>'https://api.cerebras.ai/v1/chat/completions','k'=>$env['CEREBRAS_API_KEY']??'','m'=>'llama-3.3-70b','name'=>'Cerebras-Llama3.3'],
|
||||
['u'=>'https://api.mistral.ai/v1/chat/completions','k'=>$env['MISTRAL_KEY']??'','m'=>'mistral-small-latest','name'=>'Mistral'],
|
||||
];
|
||||
$reply=''; $provider_used='';
|
||||
foreach($providers as $p){
|
||||
if(!$p['k'])continue;
|
||||
$ch=curl_init($p['u']);
|
||||
curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>1,CURLOPT_POST=>1,CURLOPT_TIMEOUT=>15,
|
||||
CURLOPT_HTTPHEADER=>['Content-Type: application/json','Authorization: Bearer '.$p['k']],
|
||||
CURLOPT_POSTFIELDS=>json_encode(['model'=>$p['m'],'messages'=>$messages,'max_tokens'=>1200,'temperature'=>0.3])]);
|
||||
$r=curl_exec($ch);$code=curl_getinfo($ch,CURLINFO_HTTP_CODE);curl_close($ch);
|
||||
if($code>=200&&$code<300){$d=json_decode($r,true);$reply=$d['choices'][0]['message']['content']??'';if($reply){$provider_used=$p['name'];break;}}
|
||||
}
|
||||
if(!$reply)$reply='Service temporairement indisponible.';
|
||||
$history[]=['role'=>'user','content'=>$msg];
|
||||
$history[]=['role'=>'assistant','content'=>$reply];
|
||||
if($redis)$redis->setex("saas:$session",3600,json_encode(array_slice($history,-20)));
|
||||
|
||||
echo json_encode([
|
||||
'response' => wave256_sanitize($reply),
|
||||
'provider'=>$provider_used ?: 'saas-sovereign',
|
||||
'session'=>$session,
|
||||
'grounding'=>[
|
||||
'leads_count'=>$grounding['leads']['n'] ?? null,
|
||||
'industries_count'=>count($grounding['industries'] ?? []),
|
||||
'solutions_count'=>count($grounding['top_solutions'] ?? []),
|
||||
'wave'=>253,
|
||||
]
|
||||
]);
|
||||
217
vault-gold/opus/phase6-20260423-222102/wtp-orphans-registry.html
Normal file
217
vault-gold/opus/phase6-20260423-222102/wtp-orphans-registry.html
Normal file
@@ -0,0 +1,217 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>WTP Orphans Registry · 261 pages à relier</title>
|
||||
<style>
|
||||
:root{
|
||||
--ink:#0a0e1a;--ink2:#0c1120;--ink3:#111a2e;--line:#1a2238;--line2:#2a3450;
|
||||
--paper:#e8eaf0;--smoke:#9aa3b8;--fog:#6b7490;
|
||||
--gold:#d4a853;--gold-br:#f3c678;--gold-lt:#ffd98a;
|
||||
--em:#10b981;--em-lt:#4ade80;--sa:#22d3ee;--to:#fbbf24;--to-lt:#fde047;
|
||||
--ru:#ef4444;--vi:#a855f7;--pi:#f472b6;
|
||||
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;
|
||||
--serif:"Fraunces","Playfair Display",Georgia,serif;
|
||||
--mono:"JetBrains Mono","Fira Code",ui-monospace,monospace;
|
||||
}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{background:var(--ink);color:var(--paper);font-family:var(--sans);font-size:13.5px;line-height:1.6;min-height:100vh;padding:20px 28px;overflow-x:hidden}
|
||||
.hd{max-width:1400px;margin:0 auto 30px;padding-bottom:18px;border-bottom:1px solid var(--line2);position:relative}
|
||||
.hd .eyebrow{font-family:var(--mono);font-size:10px;color:var(--gold);letter-spacing:.2em;text-transform:uppercase;margin-bottom:8px}
|
||||
.hd h1{font-family:var(--serif);font-weight:500;font-size:34px;line-height:1.1;color:var(--paper)}
|
||||
.hd h1 em{color:var(--gold);font-style:italic}
|
||||
.hd .sub{margin-top:6px;font-size:13px;color:var(--fog);font-style:italic;font-family:var(--serif)}
|
||||
.hd .back{position:absolute;top:0;right:0;padding:7px 14px;background:transparent;border:1px solid var(--line2);border-radius:2px;color:var(--fog);font-family:var(--sans);font-size:11px;text-decoration:none;text-transform:uppercase;letter-spacing:.08em;transition:all .15s}
|
||||
.hd .back:hover{color:var(--gold);border-color:var(--gold)}
|
||||
.wrap{max-width:1400px;margin:0 auto}
|
||||
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin-bottom:24px}
|
||||
.k-card{padding:16px 20px;background:var(--ink2);border:1px solid var(--line);border-radius:3px;position:relative;overflow:hidden;transition:all .2s}
|
||||
.k-card:hover{border-color:var(--line2);transform:translateY(-2px)}
|
||||
.k-card .lbl{font-family:var(--mono);font-size:9px;text-transform:uppercase;letter-spacing:.15em;color:var(--fog);margin-bottom:8px}
|
||||
.k-card .v{font-family:var(--serif);font-weight:600;font-size:36px;line-height:1;margin-bottom:4px;color:var(--gold)}
|
||||
.k-card .d{font-size:11px;color:var(--fog)}
|
||||
.k-card.em .v{color:var(--em-lt)} .k-card.wa .v{color:var(--to-lt)} .k-card.ru .v{color:var(--ru)} .k-card.vi .v{color:var(--vi)}
|
||||
.sec{margin:24px 0 14px;padding-bottom:8px;border-bottom:1px dotted var(--line2);display:flex;align-items:baseline;gap:12px}
|
||||
.sec h2{font-family:var(--serif);font-weight:500;font-size:22px;color:var(--paper)}
|
||||
.sec h2 em{color:var(--gold);font-style:italic}
|
||||
.sec .kick{font-family:var(--mono);font-size:10px;color:var(--fog);letter-spacing:.1em;text-transform:uppercase}
|
||||
.sec .badge{margin-left:auto;padding:3px 9px;font-family:var(--mono);font-size:10px;background:var(--ink3);color:var(--gold);border:1px solid var(--line2);border-radius:2px}
|
||||
.grid-cat{display:grid;grid-template-columns:repeat(auto-fill,minmax(330px,1fr));gap:10px;margin-bottom:14px}
|
||||
.pc{padding:10px 12px 10px 13px;background:var(--ink2);border:1px solid var(--line);border-left:2px solid var(--gold);border-radius:2px;transition:all .15s}
|
||||
.pc:hover{border-left-color:var(--gold-br);transform:translateX(3px);background:var(--ink3)}
|
||||
.pc.leg{border-left-color:var(--fog)} .pc.dup{border-left-color:var(--to)} .pc.dep{border-left-color:var(--ru)}
|
||||
.pc a{color:var(--paper);font-family:var(--sans);font-size:12.5px;font-weight:500;text-decoration:none;display:block}
|
||||
.pc a:hover{color:var(--gold)}
|
||||
.pc .m{font-family:var(--mono);font-size:9px;color:var(--fog);margin-top:3px;display:flex;gap:10px}
|
||||
.filter{position:sticky;top:8px;background:var(--ink);padding:10px 0;z-index:10;margin-bottom:14px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
|
||||
.filter input{flex:1;min-width:200px;padding:8px 12px;background:var(--ink3);border:1px solid var(--line);color:var(--paper);font-family:var(--sans);font-size:12px;border-radius:2px;outline:none}
|
||||
.filter input:focus{border-color:var(--gold)}
|
||||
.filter .tag{padding:5px 10px;font-family:var(--mono);font-size:10px;background:var(--ink3);color:var(--smoke);border:1px solid var(--line);border-radius:2px;cursor:pointer;user-select:none}
|
||||
.filter .tag.on{background:var(--gold);color:var(--ink);border-color:var(--gold)}
|
||||
.cat-colors{display:flex;gap:4px;margin-bottom:14px;flex-wrap:wrap;font-size:10px;font-family:var(--mono)}
|
||||
.cat-colors .c{padding:3px 8px;border-radius:10px}
|
||||
.c-active{background:rgba(212,168,83,.15);color:var(--gold-lt);border:1px solid var(--gold)}
|
||||
.c-legacy{background:rgba(107,116,144,.15);color:var(--smoke);border:1px solid var(--fog)}
|
||||
.c-doublon{background:rgba(251,191,36,.1);color:var(--to-lt);border:1px solid var(--to)}
|
||||
.c-deprecated{background:rgba(239,68,68,.1);color:var(--ru);border:1px solid var(--ru)}
|
||||
.hint{padding:12px 14px;background:var(--ink2);border:1px solid var(--line);border-radius:2px;margin-bottom:14px;font-size:12px;color:var(--smoke);line-height:1.6}
|
||||
.loading{padding:40px 0;text-align:center;color:var(--fog);font-family:var(--serif);font-style:italic}
|
||||
footer{margin-top:40px;padding:14px 0;border-top:1px solid var(--line);text-align:center;color:var(--fog);font-family:var(--serif);font-style:italic;font-size:11.5px}
|
||||
footer a{color:var(--gold);text-decoration:none;margin:0 6px}
|
||||
.scroll-tip{font-size:10px;color:var(--fog);font-family:var(--mono);margin-top:2px}
|
||||
@media(max-width:700px){.hd h1{font-size:24px}.grid-cat{grid-template-columns:1fr}.hd .back{position:static;margin-top:8px;display:inline-block}}
|
||||
|
||||
/* Thumbnails premium */
|
||||
.pc{padding:0;overflow:hidden}
|
||||
.pc-thumb{width:100%;height:130px;object-fit:cover;object-position:top left;border-bottom:1px solid var(--line);display:block;background:var(--ink3);transition:transform .3s}
|
||||
.pc:hover .pc-thumb{transform:scale(1.03)}
|
||||
.pc-no-thumb{width:100%;height:130px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--ink3),var(--ink2));border-bottom:1px solid var(--line);font-family:var(--serif);font-style:italic;color:var(--fog);font-size:11px}
|
||||
.pc-body{padding:10px 12px}
|
||||
.pc a{color:var(--paper);font-family:var(--sans);font-size:12.5px;font-weight:500;text-decoration:none}
|
||||
.pc a:hover{color:var(--gold)}
|
||||
.pc .m{font-family:var(--mono);font-size:9px;color:var(--fog);margin-top:3px;display:flex;gap:10px}
|
||||
.grid-cat{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="hd">
|
||||
<div class="eyebrow">WEVAL · WTP Orphans Registry</div>
|
||||
<h1>Toutes les pages <em>reliées à WTP</em></h1>
|
||||
<p class="sub">Catalogue vivant — 333 pages scannées, catégorisées, actionnables</p>
|
||||
<a href="/weval-technology-platform.html" class="back">← WTP Hub</a>
|
||||
</header>
|
||||
|
||||
<div class="wrap">
|
||||
<div class="kpis" id="kpis"></div>
|
||||
|
||||
<div class="hint">
|
||||
🎯 <strong>Objectif</strong> : rattacher les 261 pages orphelines au référentiel WTP sans doublon ni écrasement.
|
||||
Cliquez une page pour l'ouvrir. Filtrez par catégorie ou nom.
|
||||
Les variantes LEGACY/DOUBLON/DEPRECATED sont archivables (consolidation).
|
||||
</div>
|
||||
|
||||
<div class="cat-colors">
|
||||
<span class="c c-active">● ACTIVE (liens or)</span>
|
||||
<span class="c c-doublon">● DOUBLON (jaune)</span>
|
||||
<span class="c c-legacy">● LEGACY (gris)</span>
|
||||
<span class="c c-deprecated">● DEPRECATED (rouge)</span>
|
||||
</div>
|
||||
|
||||
<div class="filter">
|
||||
<input id="q" type="text" placeholder="Filter by name…" oninput="filterAll()">
|
||||
<span class="tag on" data-cat="ALL" onclick="toggleCat(this)">ALL</span>
|
||||
<span class="tag" data-cat="ACTIVE_HUB" onclick="toggleCat(this)">HUB</span>
|
||||
<span class="tag" data-cat="ACTIVE_DASHBOARD" onclick="toggleCat(this)">DASHBOARD</span>
|
||||
<span class="tag" data-cat="ACTIVE_AGENT" onclick="toggleCat(this)">AGENT</span>
|
||||
<span class="tag" data-cat="ACTIVE_BLADE" onclick="toggleCat(this)">BLADE</span>
|
||||
<span class="tag" data-cat="ACTIVE_AI" onclick="toggleCat(this)">AI</span>
|
||||
<span class="tag" data-cat="ACTIVE_CRM" onclick="toggleCat(this)">CRM</span>
|
||||
<span class="tag" data-cat="ACTIVE_ADMIN" onclick="toggleCat(this)">ADMIN</span>
|
||||
<span class="tag" data-cat="ACTIVE_PRODUCT" onclick="toggleCat(this)">PRODUCT</span>
|
||||
<span class="tag" data-cat="ACTIVE_OTHER" onclick="toggleCat(this)">OTHER</span>
|
||||
<span class="tag" data-cat="DOUBLON" onclick="toggleCat(this)">DOUBLON</span>
|
||||
<span class="tag" data-cat="LEGACY" onclick="toggleCat(this)">LEGACY</span>
|
||||
<span class="tag" data-cat="TESTS" onclick="toggleCat(this)">TESTS</span>
|
||||
</div>
|
||||
|
||||
<div id="content"><div class="loading">⏳ Scanning 333 pages…</div></div>
|
||||
|
||||
<footer>
|
||||
Généré dynamiquement via <a href="/api/wtp-orphans-registry.php">/api/wtp-orphans-registry.php</a> ·
|
||||
<a href="/weval-technology-platform.html">WTP Hub</a> ·
|
||||
<a href="/all-ia-hub.html">All-IA Hub</a> ·
|
||||
<a href="/wevia-master.html">WEVIA Master</a>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let DATA = null, FILTER_CATS = new Set(['ALL']);
|
||||
|
||||
const CAT_LABELS = {
|
||||
ACTIVE_HUB: { label: 'Hubs & Centers', kick: 'Points d\'entrée modulaires', color: 'var(--gold)' },
|
||||
ACTIVE_DASHBOARD: { label: 'Dashboards & KPIs', kick: 'Tableaux de bord business', color: 'var(--em-lt)' },
|
||||
ACTIVE_AGENT: { label: 'Agents', kick: 'Agents IA & assistants', color: 'var(--sa)' },
|
||||
ACTIVE_BLADE: { label: 'Blade Hub', kick: 'Blade AI assistants', color: 'var(--vi)' },
|
||||
ACTIVE_AI: { label: 'AI & LLM', kick: 'IA souveraines & modèles', color: 'var(--pi)' },
|
||||
ACTIVE_CRM: { label: 'CRM & HCP', kick: 'Sales & healthcare', color: 'var(--em-lt)' },
|
||||
ACTIVE_ADMIN: { label: 'Admin & Auth', kick: 'Back-office', color: 'var(--to-lt)' },
|
||||
ACTIVE_PRODUCT: { label: 'Products & Offers', kick: 'Commercial pages', color: 'var(--gold-br)' },
|
||||
ACTIVE_OTHER: { label: 'Autres pages', kick: 'Catégorisation libre', color: 'var(--paper)' },
|
||||
DOUBLON: { label: 'Doublons probables', kick: 'Variantes à consolider', color: 'var(--to)' },
|
||||
LEGACY: { label: 'Legacy', kick: 'Anciennes versions', color: 'var(--fog)' },
|
||||
TESTS: { label: 'Tests & Demos', kick: 'Sandbox', color: 'var(--fog)' },
|
||||
DEPRECATED: { label: 'Deprecated', kick: '404 / offline', color: 'var(--ru)' },
|
||||
};
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const r = await fetch('/api/wtp-orphans-registry.php');
|
||||
DATA = await r.json();
|
||||
renderKPIs();
|
||||
renderAll();
|
||||
} catch (e) {
|
||||
document.getElementById('content').innerHTML = `<div class="loading">❌ Erreur: ${e.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderKPIs() {
|
||||
const d = DATA;
|
||||
const html = `
|
||||
<div class="k-card"><div class="lbl">Total pages</div><div class="v">${d.total_html}</div><div class="d">scannées racine</div></div>
|
||||
<div class="k-card em"><div class="lbl">Linkées WTP</div><div class="v">${d.linked_in_wtp}</div><div class="d">${d.link_rate_pct}% couverture</div></div>
|
||||
<div class="k-card wa"><div class="lbl">Orphelines</div><div class="v">${d.orphans_count}</div><div class="d">à rattacher</div></div>
|
||||
<div class="k-card"><div class="lbl">Hubs actifs</div><div class="v">${d.counts.ACTIVE_HUB}</div><div class="d">+ ${d.counts.ACTIVE_DASHBOARD} dashboards</div></div>
|
||||
<div class="k-card vi"><div class="lbl">À consolider</div><div class="v">${d.counts.DOUBLON + d.counts.LEGACY + d.counts.TESTS + d.counts.DEPRECATED}</div><div class="d">doublons/legacy</div></div>
|
||||
<div class="k-card em"><div class="lbl">Thumbs</div><div class="v">${d.thumbs_available||0}</div><div class="d">${d.thumbs_coverage_pct||0}% coverage</div></div>
|
||||
<div class="k-card"><div class="lbl">Scan</div><div class="v" style="font-size:20px">${d.scan_duration_ms}ms</div><div class="d">live dynamique</div></div>
|
||||
`;
|
||||
document.getElementById('kpis').innerHTML = html;
|
||||
}
|
||||
|
||||
function renderAll() {
|
||||
const d = DATA;
|
||||
const q = (document.getElementById('q').value || '').toLowerCase();
|
||||
let html = '';
|
||||
|
||||
const order = ['ACTIVE_HUB','ACTIVE_DASHBOARD','ACTIVE_AGENT','ACTIVE_BLADE','ACTIVE_AI','ACTIVE_CRM','ACTIVE_ADMIN','ACTIVE_PRODUCT','ACTIVE_OTHER','DOUBLON','LEGACY','TESTS','DEPRECATED'];
|
||||
|
||||
for (const cat of order) {
|
||||
if (!FILTER_CATS.has('ALL') && !FILTER_CATS.has(cat)) continue;
|
||||
const items = d.categories[cat] || [];
|
||||
const filtered = q ? items.filter(i => i.name.toLowerCase().includes(q)) : items;
|
||||
if (!filtered.length) continue;
|
||||
|
||||
const meta = CAT_LABELS[cat] || { label: cat, kick: '', color: 'var(--gold)' };
|
||||
html += `<div class="sec"><h2>${meta.label} <em>${cat.split('_')[1]?.toLowerCase() || ''}</em></h2><span class="kick">${meta.kick}</span><span class="badge">${filtered.length}</span></div>`;
|
||||
html += `<div class="grid-cat">`;
|
||||
|
||||
const pcClass = cat === 'LEGACY' ? 'pc leg' : cat === 'DOUBLON' ? 'pc dup' : cat === 'DEPRECATED' ? 'pc dep' : 'pc';
|
||||
for (const item of filtered) {
|
||||
const sizeKB = (item.size / 1024).toFixed(0);
|
||||
const thumbHTML = item.thumb
|
||||
? `<img class="pc-thumb" loading="lazy" src="${item.thumb}" alt="${item.name}" onerror="this.outerHTML='<div class=pc-no-thumb>no preview</div>'">`
|
||||
: `<div class="pc-no-thumb">no preview</div>`;
|
||||
html += `<div class="${pcClass}"><a href="/${item.name}" target="_blank">${thumbHTML}<div class="pc-body">${item.name}<div class="m"><span>${sizeKB} KB</span><span>${item.mtime_h || '?'}</span></div></div></a></div>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
document.getElementById('content').innerHTML = html || `<div class="loading">Aucune page trouvée pour ces filtres</div>`;
|
||||
}
|
||||
|
||||
function toggleCat(el) {
|
||||
const cat = el.dataset.cat;
|
||||
document.querySelectorAll('.tag').forEach(t => t.classList.remove('on'));
|
||||
FILTER_CATS = new Set([cat]);
|
||||
el.classList.add('on');
|
||||
renderAll();
|
||||
}
|
||||
|
||||
function filterAll() { renderAll(); }
|
||||
|
||||
load();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
// WTP Orphans Registry v2 · avec support thumbnails
|
||||
// Doctrine 144 + enrichissement 23avr 22h20
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: public, max-age=180');
|
||||
|
||||
$root = '/var/www/html';
|
||||
$wtp_file = $root . '/weval-technology-platform.html';
|
||||
$thumbs_dir = $root . '/thumbs';
|
||||
|
||||
$all_pages = glob($root . '/*.html');
|
||||
$all_names = array_map('basename', $all_pages);
|
||||
sort($all_names);
|
||||
|
||||
$linked = [];
|
||||
if (file_exists($wtp_file)) {
|
||||
$wtp_content = file_get_contents($wtp_file);
|
||||
preg_match_all('/href="\/?([a-z0-9_-]+\.html)"/i', $wtp_content, $m);
|
||||
$linked = array_unique($m[1]);
|
||||
}
|
||||
sort($linked);
|
||||
|
||||
$orphans = array_values(array_diff($all_names, $linked, ['weval-technology-platform.html']));
|
||||
|
||||
$cats = [
|
||||
'LEGACY'=>[], 'DOUBLON'=>[], 'TESTS'=>[], 'DEPRECATED'=>[],
|
||||
'ACTIVE_HUB'=>[], 'ACTIVE_AGENT'=>[], 'ACTIVE_BLADE'=>[], 'ACTIVE_AI'=>[],
|
||||
'ACTIVE_CRM'=>[], 'ACTIVE_ADMIN'=>[], 'ACTIVE_DASHBOARD'=>[],
|
||||
'ACTIVE_PRODUCT'=>[], 'ACTIVE_OTHER'=>[],
|
||||
];
|
||||
|
||||
foreach ($orphans as $p) {
|
||||
$n = strtolower($p);
|
||||
$stats = @stat("$root/$p");
|
||||
$thumb_name = str_replace('.html', '.jpg', $p);
|
||||
$has_thumb = file_exists("$thumbs_dir/$thumb_name") && filesize("$thumbs_dir/$thumb_name") > 3000;
|
||||
|
||||
$item = [
|
||||
'name' => $p,
|
||||
'size' => $stats['size'] ?? 0,
|
||||
'mtime' => $stats['mtime'] ?? 0,
|
||||
'mtime_h' => $stats['mtime'] ? date('Y-m-d', $stats['mtime']) : '',
|
||||
'thumb' => $has_thumb ? "/thumbs/$thumb_name" : null,
|
||||
];
|
||||
|
||||
if (in_array($p, ['404.html','offline.html','error.html'])) $cats['DEPRECATED'][] = $item;
|
||||
elseif (preg_match('/-old|-backup|-v1\.|-legacy|-deprecated/', $n)) $cats['LEGACY'][] = $item;
|
||||
elseif (preg_match('/^test-|-test\.|^demo-|-demo\.|sandbox/', $n)) $cats['TESTS'][] = $item;
|
||||
elseif (preg_match('/-alive|-hd\d*\.|-goodjob|-fleet|-final|-3d|-iso3d/', $n)) $cats['DOUBLON'][] = $item;
|
||||
elseif (strpos($n, '-hub') !== false || strpos($n, '-center') !== false || strpos($n, '-registry') !== false) $cats['ACTIVE_HUB'][] = $item;
|
||||
elseif (strpos($n, 'agents-') === 0 || strpos($n, 'agent-') === 0) $cats['ACTIVE_AGENT'][] = $item;
|
||||
elseif (strpos($n, 'blade-') === 0) $cats['ACTIVE_BLADE'][] = $item;
|
||||
elseif (preg_match('/^(ai|ia)-|ollama|llm/', $n)) $cats['ACTIVE_AI'][] = $item;
|
||||
elseif (strpos($n, 'crm') !== false || strpos($n, 'hcp') !== false) $cats['ACTIVE_CRM'][] = $item;
|
||||
elseif (strpos($n, 'admin') !== false || strpos($n, 'login') !== false) $cats['ACTIVE_ADMIN'][] = $item;
|
||||
elseif (strpos($n, 'dashboard') !== false || strpos($n, 'kpi') !== false || strpos($n, 'metric') !== false) $cats['ACTIVE_DASHBOARD'][] = $item;
|
||||
elseif (strpos($n, 'product') !== false || strpos($n, 'offer') !== false || strpos($n, 'pricing') !== false) $cats['ACTIVE_PRODUCT'][] = $item;
|
||||
else $cats['ACTIVE_OTHER'][] = $item;
|
||||
}
|
||||
|
||||
foreach ($cats as $k => $v) {
|
||||
usort($cats[$k], fn($a,$b) => $b['mtime'] - $a['mtime']);
|
||||
}
|
||||
|
||||
// thumb stats
|
||||
$thumbs_count = is_dir($thumbs_dir) ? count(glob("$thumbs_dir/*.jpg")) : 0;
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'ts' => date('c'),
|
||||
'total_html' => count($all_names),
|
||||
'linked_in_wtp' => count($linked),
|
||||
'orphans_count' => count($orphans),
|
||||
'link_rate_pct' => round(count($linked) / max(count($all_names), 1) * 100, 1),
|
||||
'thumbs_available' => $thumbs_count,
|
||||
'thumbs_coverage_pct' => round($thumbs_count / max(count($orphans), 1) * 100, 1),
|
||||
'categories' => $cats,
|
||||
'counts' => array_map('count', $cats),
|
||||
'scan_duration_ms' => (int)((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000)
|
||||
], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
@@ -89,3 +89,54 @@ Ajouter **1 seule ligne** dans WTP qui link `/wtp-orphans-registry.html` :
|
||||
- 143 · Cloudbot-social SSE (autre Claude)
|
||||
- **144 · WTP Orphans Registry** ← CE DOC
|
||||
- 145 futur · Merger WTP + registry en vue unifiée dashboard ERP-like
|
||||
|
||||
---
|
||||
|
||||
## Update 23avr 22h20 · v2 avec thumbnails premium (wave phase 6)
|
||||
|
||||
### Ajout thumbnails preview
|
||||
|
||||
Script `/opt/weval-ops/gen-thumbs-v2.py` génère des aperçus 1200x750 JPG via wkhtmltoimage
|
||||
pour les pages top-priorité (ACTIVE_HUB + DASHBOARD + AGENT + BLADE + AI + CRM + ADMIN + PRODUCT).
|
||||
|
||||
- Output : `/var/www/html/thumbs/<page>.jpg`
|
||||
- Quality : 55% (compromise taille/qualité)
|
||||
- javascript-delay 1500ms pour pages dynamiques
|
||||
- Timeout 25s par thumb, 60s max par page
|
||||
- Skip si thumb existe déjà > 3KB (incremental)
|
||||
|
||||
### Endpoint API v2
|
||||
|
||||
`/api/wtp-orphans-registry.php` retourne maintenant :
|
||||
- `thumb` : URL `/thumbs/<name>.jpg` ou `null` si pas généré
|
||||
- `thumbs_available` : count total
|
||||
- `thumbs_coverage_pct` : pourcentage couverture
|
||||
|
||||
### Page HTML v2
|
||||
|
||||
- **7 KPI cards** (ajout "Thumbs coverage")
|
||||
- Cards page désormais avec `<img class="pc-thumb" loading="lazy">` 130x auto
|
||||
- Fallback `<div class="pc-no-thumb">no preview</div>` si pas de thumb
|
||||
- Hover: scale(1.03) transform smooth
|
||||
- onerror: fallback no-preview si image cassée
|
||||
|
||||
### Résultat wave phase 6
|
||||
|
||||
- **53 thumbs générés** (20.2% coverage initial)
|
||||
- Page v2 : 15.7 KB, 263 pagecards, 7 KPIs, 0 JS errors
|
||||
- body_height 15715px (2x v1)
|
||||
- Preuve publique : `weval-consulting.com/proofs/wtp-orphans-registry-2026-04-23T20-18-40/`
|
||||
|
||||
### Limitations actuelles
|
||||
|
||||
- Pages SSO protégées → thumbs montrent "WEVAL Login" form (pas contenu réel)
|
||||
- Solution future : Playwright avec cookie d'auth pour thumbs SSO
|
||||
- Pages Cloudflare "Bad gateway" → thumb d'erreur (brain-center-tenant)
|
||||
- Amélioration : retry thumbnail si page renvoie 502/504
|
||||
|
||||
### Cron automation (futur)
|
||||
|
||||
Ajouter cron hebdo pour refresh thumbs (pages éditées dans la semaine) :
|
||||
```
|
||||
0 4 * * 0 root python3 /opt/weval-ops/gen-thumbs-v2.py >> /var/log/weval/thumbs-weekly.log 2>&1
|
||||
```
|
||||
|
||||
79
wiki/doctrine-145-chatbots-migration-bilan.md
Normal file
79
wiki/doctrine-145-chatbots-migration-bilan.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Doctrine 145 · Bilan migration chatbots bridge doctrine 141
|
||||
|
||||
Créée 23avr2026 22h20 par Opus suite migration phase 6.
|
||||
|
||||
## État à la clôture session
|
||||
|
||||
**Chatbots internes avec mémoire persistante Redis DB 5** (pattern doctrine 141+142) :
|
||||
|
||||
| Chatbot | Lignes | Pattern | Status |
|
||||
|---|---|---|---|
|
||||
| wevia-master-api.php | — | w258 natif | ✅ Natif (avant session) |
|
||||
| wevia-chat-v2-direct.php | 236 | Inline (doctrine 141) | ✅ Phase 2 |
|
||||
| weval-chatbot-api.php | 258 | Shutdown (doctrine 142) | ✅ Phase 4 |
|
||||
| saas-chat.php | 171 | Shutdown (doctrine 142) | ✅ Phase 6 |
|
||||
| claude-pattern-api.php | 330 | Shutdown (doctrine 142) | ✅ Phase 6 |
|
||||
|
||||
## Restants (non migrés)
|
||||
|
||||
| Chatbot | Lignes | Raison |
|
||||
|---|---|---|
|
||||
| l99-chat.php | 98 | SSE pur (text/event-stream + ob_end_clean) — pattern spécifique à inventer |
|
||||
| openclaw-proxy.php | 162 | SSE streaming + $messages OpenAI array — adapter nécessaire |
|
||||
| cloudbot-interagent.php | 147 | Autre Claude doctrine 143 ongoing |
|
||||
| multiagent-orchestrator.php | 379 | Orchestrateur, pas chatbot direct |
|
||||
| weval-consensus-engine.php | 296 | Engine consensus, pas chatbot utilisateur direct |
|
||||
|
||||
## Endpoints NON chatbots (ne pas migrer)
|
||||
|
||||
- claw-code-api.php (20L) : status/health endpoint
|
||||
- wevia-director.php (2L stub) → wevia-director-agent.php (703L) : orchestrateur autonome actions
|
||||
- fast.php (3718L) : fast-path dispatcher
|
||||
|
||||
## Validation Redis DB 5 post-session
|
||||
|
||||
- `chatmem:wevia-master:*` : 992+ keys
|
||||
- `chatmem:wevia-chat-v2:*` : 3 keys
|
||||
- `chatmem:weval-chatbot:*` : 2 keys
|
||||
- `chatmem:saas-chat:*` : 2 keys
|
||||
- `chatmem:claude-pattern-api:*` : 1 key
|
||||
|
||||
Total ~1000 messages stockés persistants ([] save + [] load fonctionnels).
|
||||
|
||||
## Pattern récapitulatif
|
||||
|
||||
**Inline (doctrine 141)** : chatbot simple avec logique JSON standard
|
||||
```php
|
||||
// Après $t0 = microtime(true)
|
||||
require_once 'wevia-memory-bridge.php';
|
||||
$__mem_context = wevia_mem_context('chat-id', $user_id, 10);
|
||||
// ... business logic
|
||||
// Avant echo final
|
||||
wevia_mem_save('chat-id', $user_id, $msg, $response);
|
||||
```
|
||||
|
||||
**Shutdown (doctrine 142)** : chatbot avec multiples die/exit
|
||||
```php
|
||||
ob_start();
|
||||
register_shutdown_function(function() use ($msg, $user_id) {
|
||||
$out = ob_get_contents();
|
||||
$d = json_decode($out, true);
|
||||
wevia_mem_save('chat-id', $user_id, $msg, $d['response'] ?? '');
|
||||
});
|
||||
```
|
||||
|
||||
## Prochaines étapes (phase 7)
|
||||
|
||||
1. Inventer pattern SSE pour l99-chat + openclaw-proxy
|
||||
2. Migrer les 2 derniers chatbots SSE
|
||||
3. Dashboard unifié mémoire tous chatbots (`/api/chatmem-stats.php`)
|
||||
4. Intégration mémoire dans prompt LLM (contexte des N derniers msgs injected avant call)
|
||||
|
||||
## Doctrine chaînée
|
||||
|
||||
- 141 · Mémoire persistante universelle (middleware + 5 helpers)
|
||||
- 142 · Pattern shutdown_function
|
||||
- 143 · Cloudbot-social SSE interagent (autre Claude)
|
||||
- 144 · WTP Orphans Registry
|
||||
- **145 · Bilan chatbots migration** ← CE DOC
|
||||
- 146 futur · SSE pattern pour l99-chat et openclaw
|
||||
Reference in New Issue
Block a user