Files
html/all-ia-hub.html

1373 lines
76 KiB
HTML

<!DOCTYPE html>
<html lang="fr" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>All-IA Hub — Remplacement Claude Code + Opus (Souverain)</title>
<meta name="description" content="Hub unifié combinant WEVIA Master streaming, WEVCODE modes, Arena multi-provider. Indépendance Opus.">
<style>
:root{--bg:#0a0a0f;--bg2:#111119;--bg3:#181825;--rim:#2a2a3a;--t:#e2e8f0;--mu:#94a3b8;--dm:#64748b;--cy:#22d3ee;--gr:#10b981;--vl:#a78bfa;--am:#f59e0b;--rd:#ef4444}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'JetBrains Mono','Menlo',monospace;background:var(--bg);color:var(--t);min-height:100vh;display:flex;flex-direction:column}
.hdr{padding:10px 18px;background:var(--bg2);border-bottom:1px solid var(--rim);display:flex;align-items:center;justify-content:space-between;gap:14px}
.hdr .brand{display:flex;align-items:center;gap:10px}
.hdr .logo{width:34px;height:34px;border-radius:8px;background:linear-gradient(135deg,var(--cy),var(--vl));display:flex;align-items:center;justify-content:center;color:var(--bg);font-weight:700;font-size:14px}
.hdr h1{font-size:14px;font-weight:700;letter-spacing:.4px}
.hdr .sub{font-size:10px;color:var(--mu);letter-spacing:.3px;margin-top:2px}
.hdr .stats{display:flex;gap:14px;font-size:10px;color:var(--dm);letter-spacing:.3px}
.hdr .stats span b{color:var(--cy);font-weight:700}
.tabs{display:flex;gap:2px;padding:0 18px;background:var(--bg2);border-bottom:1px solid var(--rim);overflow-x:auto}
.tab{padding:10px 16px;background:transparent;border:0;color:var(--mu);font-family:inherit;font-size:11px;font-weight:600;cursor:pointer;letter-spacing:.4px;border-bottom:2px solid transparent;transition:all .2s;white-space:nowrap}
.tab:hover{color:var(--t)}
.tab.on{color:var(--cy);border-bottom-color:var(--cy)}
main{flex:1;display:flex;flex-direction:column;padding:16px 20px;max-width:1400px;width:100%;margin:0 auto;min-height:0}
.view{display:none;flex-direction:column;flex:1;min-height:0}
.view.on{display:flex}
/* Chat/Code shared layout */
.modes{display:flex;gap:4px;margin-bottom:10px;flex-wrap:wrap}
.mode{padding:6px 12px;border:1px solid var(--rim);border-radius:6px;background:transparent;color:var(--mu);font-family:inherit;font-size:10px;font-weight:600;cursor:pointer;transition:all .15s;letter-spacing:.3px}
.mode:hover{border-color:var(--cy);color:var(--t)}
.mode.on{background:var(--cy);color:var(--bg);border-color:var(--cy)}
.stbar{display:flex;gap:14px;margin-bottom:10px;font-size:10px;color:var(--dm);letter-spacing:.4px}
.stbar span{display:flex;align-items:center;gap:5px}
.stbar .dot{width:6px;height:6px;border-radius:50%;background:var(--gr)}
.out{flex:1;background:var(--bg2);border:1px solid var(--rim);border-radius:8px;padding:14px 18px;overflow-y:auto;font-size:12px;line-height:1.65;min-height:240px}
.out .msg{margin-bottom:14px}
.out .msg.u{color:var(--cy)}
.out .msg.a{color:var(--t)}
.out .msg.sys{color:var(--dm);font-size:10px;letter-spacing:.3px}
.out .msg .meta{font-size:9px;color:var(--dm);letter-spacing:.4px;margin-top:4px}
.out .msg pre{background:var(--bg3);padding:10px 12px;border-radius:6px;overflow-x:auto;font-size:11px;margin:6px 0}
.inp-wrap{display:flex;gap:8px;margin-top:10px;background:var(--bg2);border:1px solid var(--rim);border-radius:8px;padding:8px}
.inp-wrap:focus-within{border-color:var(--cy)}
.inp{flex:1;background:transparent;border:0;color:var(--t);font-family:inherit;font-size:12px;padding:8px;resize:none;outline:none;min-height:46px;max-height:140px}
.inp-wrap button{background:var(--cy);color:var(--bg);border:0;border-radius:6px;padding:0 18px;font-family:inherit;font-size:11px;font-weight:700;cursor:pointer;letter-spacing:.4px;transition:opacity .15s}
.inp-wrap button:hover{opacity:.85}
.inp-wrap button:disabled{opacity:.4;cursor:not-allowed}
/* Capabilities grid */
.caps-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;padding:4px 0}
.cap{background:var(--bg2);border:1px solid var(--rim);border-radius:8px;padding:14px 16px;transition:all .2s}
.cap:hover{border-color:var(--cy);transform:translateY(-1px)}
.cap h3{font-size:12px;color:var(--cy);margin-bottom:6px;letter-spacing:.5px}
.cap p{font-size:10px;color:var(--mu);line-height:1.55;letter-spacing:.2px}
.cap .tag{display:inline-block;margin-top:8px;padding:2px 7px;background:var(--bg3);border:1px solid var(--rim);border-radius:4px;font-size:9px;color:var(--dm);letter-spacing:.4px}
.cap .tag.r{color:var(--gr);border-color:var(--gr)}
/* Training panel */
.train-stats{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-bottom:14px}
.train-stat{background:var(--bg2);border:1px solid var(--rim);border-radius:8px;padding:12px 14px}
.train-stat .k{font-size:9px;color:var(--dm);letter-spacing:.5px;text-transform:uppercase}
.train-stat .v{font-size:20px;color:var(--cy);font-weight:700;margin-top:3px}
.train-stat .s{font-size:9px;color:var(--mu);margin-top:3px;letter-spacing:.3px}
.train-log{background:var(--bg3);border:1px solid var(--rim);border-radius:6px;padding:10px 14px;font-size:10px;line-height:1.6;color:var(--mu);max-height:280px;overflow-y:auto}
.train-log b{color:var(--cy)}
/* Orchestrator compact */
.orch-agents{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:6px;max-height:280px;overflow-y:auto}
.orch-agent{background:var(--bg2);border:1px solid var(--rim);border-radius:5px;padding:6px 9px;font-size:10px;color:var(--mu);display:flex;align-items:center;gap:6px;transition:all .2s}
.orch-agent:hover{border-color:var(--cy);color:var(--t)}
.orch-agent .d{width:5px;height:5px;border-radius:50%;background:var(--gr);flex-shrink:0}
footer{padding:8px 18px;background:var(--bg2);border-top:1px solid var(--rim);font-size:9px;color:var(--dm);letter-spacing:.3px;display:flex;justify-content:space-between}
/* V122-POLISH: subtle animations + smooth transitions */
#v-dashboards .dash-tile:focus{outline:2px solid var(--vl);outline-offset:2px}
#v-dashboards #dash-search{transition:border-color 0.15s ease,box-shadow 0.15s ease}
#v-dashboards #dash-search:focus{outline:none;border-color:var(--vl);box-shadow:0 0 0 3px rgba(132,110,255,0.2)}
#v-dashboards #dash-grid{animation:fadeIn 0.2s ease}
@keyframes fadeIn{from{opacity:0.4}to{opacity:1}}
/* V125-THEME: light theme override */
body.light{--bg:#fafafa;--bg2:#f0f0f0;--bg3:#e4e4e7;--rim:#d4d4d8;--t:#18181b;--mu:#52525b;--dm:#71717a;--cy:#0891b2;--gr:#059669;--vl:#7c3aed;--am:#d97706;--rd:#dc2626} /* V125-FIX-VARS */
body.light .view h2{color:#7c3aed}
body.light .tab{background:#e4e4e7;color:#18181b}
body.light .tab.on{background:#7c3aed;color:#fff}
body.light #theme-toggle::before{content:"\263D"}
#v-dashboards .dash-tile.pinned{box-shadow:0 0 0 1px rgba(251,191,36,0.3)}
#v-dashboards .dash-tile.pinned:hover{box-shadow:0 4px 12px rgba(251,191,36,0.35)!important}
/* V137-REFRESH spin keyframe */
@keyframes v137spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
#v137-refresh-btn:hover{background:var(--bg3)}
#v137-refresh-btn:disabled{opacity:0.7;cursor:wait}
/* V142-FOOTER-STRIP: body padding to prevent footer overlap */
body{padding-bottom:26px}
</style>
<link rel="stylesheet" href="/css/wevia-portal-consistency.css">
</head>
<body>
<div class="wevia-portal-banner">
<span class="wevia-portal-banner-label">🌐 WEVIA ECOSYSTEM</span>
<a href="/all-ia-hub.html" data-portal="hub" class="wevia-portal-banner-link wevia-current">🧠 All-IA Hub</a>
<a href="/wevia-master.html" data-portal="master" class="wevia-portal-banner-link">🤖 WEVIA Master</a>
<a href="/wevia-orchestrator.html" data-portal="arena" class="wevia-portal-banner-link">🎭 Arena Orchestrator</a>
<a href="/wevia-chat-v2.html" data-portal="chatv2" class="wevia-portal-banner-link">Chat V2</a>
<a href="/weval-technology-platform.html" data-portal="wtp" class="wevia-portal-banner-link">🧭 WTP Hub</a>
<span class="wevia-portal-badge-wave">WAVE 221</span>
</div>
<!-- BETON-DOCTRINE-101 dual-dummy (entry point) -->
<div id="weval-global-logout" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection"></div>
<a id="weval-gl" href="#" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection" tabindex="-1"></a>
<header class="hdr">
<div class="brand">
<div class="logo">A</div>
<div>
<h1>ALL-IA HUB</h1>
<div class="sub">Remplacement Claude Code + Opus · Souverain</div>
</div>
</div>
<div class="stats">
<span>Agents <b id="h-ag">726</b></span>
<span>Providers <b id="h-pr">14</b></span>
<span>NonReg <b id="h-nr">201/201</b></span>
<span>Mode <b id="h-md">Chat</b></span>
</div>
</header>
<nav class="tabs">
<!-- V130-BREADCRUMB: cross-surface quick nav (zero écrasement des autres pages) -->
<div id="v130-xnav" style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;padding:6px 10px;background:var(--bg2);border-bottom:1px solid var(--bd);font-size:10px">
<a href="/weval-technology-platform.html" style="color:var(--mu);text-decoration:none;padding:3px 8px;border-radius:4px;transition:all 0.15s" onmouseover="this.style.background='var(--bg3)';this.style.color='var(--ac)'" onmouseout="this.style.background='';this.style.color='var(--mu)'" title="Retour WEVAL Technology Platform (point d'entrée)">&#8592; WTP</a>
<span style="color:var(--mu);opacity:0.3">·</span>
<a href="/weval-arena.html" style="color:var(--mu);text-decoration:none;padding:3px 8px;border-radius:4px;transition:all 0.15s" onmouseover="this.style.background='var(--bg3)';this.style.color='var(--vl)'" onmouseout="this.style.background='';this.style.color='var(--mu)'" title="WEVAL Arena Command Center">&#9876;&#65039; Arena</a>
<span style="color:var(--mu);opacity:0.3">·</span>
<a href="/wevia-master.html" style="color:var(--mu);text-decoration:none;padding:3px 8px;border-radius:4px;transition:all 0.15s" onmouseover="this.style.background='var(--bg3)';this.style.color='var(--cy)'" onmouseout="this.style.background='';this.style.color='var(--mu)'" title="WEVIA Master (admin, auth required)">&#129302; WEVIA Master</a>
<span style="color:var(--mu);opacity:0.3">·</span>
<a href="/wevia-orchestrator.html" style="color:var(--mu);text-decoration:none;padding:3px 8px;border-radius:4px;transition:all 0.15s" onmouseover="this.style.background='var(--bg3)';this.style.color='#06d6a0'" onmouseout="this.style.background='';this.style.color='var(--mu)'" title="WEVIA Orchestrator GODMODE · agent fleet + tools registry">&#127920; Orchestrator</a>
<span style="color:var(--mu);opacity:0.3">·</span>
<a href="/wevcode.html" style="color:var(--mu);text-decoration:none;padding:3px 8px;border-radius:4px;transition:all 0.15s" onmouseover="this.style.background='var(--bg3)';this.style.color='#6ee7b7'" onmouseout="this.style.background='';this.style.color='var(--mu)'" title="WevCode · Sovereign Coding Agent v2.0">&#128187; WevCode</a>
<span style="color:var(--mu);opacity:0.3">·</span>
<a href="/playwright-v132-portfolio.html" style="color:var(--mu);text-decoration:none;padding:3px 8px;border-radius:4px;transition:all 0.15s" onmouseover="this.style.background='var(--bg3)';this.style.color='#34d399'" onmouseout="this.style.background='';this.style.color='var(--mu)'" title="V132 Playwright portfolio · 12 intents · 100% routing">&#127919; V132 100%</a>
<span style="color:var(--mu);opacity:0.3">·</span>
<a href="/wevia-unified-hub.html" style="color:var(--mu);text-decoration:none;padding:3px 8px;border-radius:4px;transition:all 0.15s" onmouseover="this.style.background='var(--bg3)';this.style.color='#00d4b4'" onmouseout="this.style.background='';this.style.color='var(--mu)'" title="WEVIA Unified Hub · Truth Registry source unique">&#129504; Truth Hub</a>
<!-- V137-REFRESH: manual refresh button + fresh indicator -->
<button id="v137-refresh-btn" onclick="event.stopPropagation();__v137RefreshHealth()" style="margin-left:auto;background:transparent;border:1px solid var(--bd);color:var(--mu);border-radius:4px;padding:2px 6px;cursor:pointer;font-size:10px;transition:all 0.15s" title="Refresh health live" onmouseover="this.style.color='var(--ac)';this.style.borderColor='var(--vl)'" onmouseout="this.style.color='var(--mu)';this.style.borderColor='var(--bd)'">&#8634;</button>
<span id="v135-kpi-live" onclick="__v136ShowHealthModal()" style="color:var(--mu);font-size:9px;cursor:pointer;text-decoration:underline;text-decoration-style:dotted;text-decoration-color:rgba(255,255,255,0.2)" title="Platform health live · click pour détail">All-IA Hub &middot; consolidation 84 dashboards</span>
</div>
<!-- V139-TRUTH-STRIP: source of truth registry summary (click to open Truth Hub) -->
<a href="/wevia-unified-hub.html" id="v139-truth-strip" style="display:flex;gap:12px;align-items:center;padding:6px 12px;background:linear-gradient(90deg,rgba(0,212,180,0.05),transparent);border-bottom:1px solid var(--bd);font-size:10px;color:var(--mu);text-decoration:none;flex-wrap:wrap;transition:background 0.15s" onmouseover="this.style.background='linear-gradient(90deg,rgba(0,212,180,0.1),transparent)'" onmouseout="this.style.background='linear-gradient(90deg,rgba(0,212,180,0.05),transparent)'" title="Truth Registry - source de vérité unique (click: open Truth Hub)">
<span style="color:#00d4b4;font-weight:600">&#129504; Truth Registry</span>
<span id="v139-agents">&middot; ... agents</span>
<span id="v139-intents">&middot; ... intents</span>
<span id="v139-skills">&middot; ... skills</span>
<span id="v139-brains">&middot; ... brains</span>
<span id="v139-doctrines">&middot; ... doctrines</span>
<span id="v139-dashboards">&middot; ... dashboards</span>
<!-- V140-TRUTH-HEALTH: autonomy + NonReg badges -->
<span id="v140-autonomy" style="color:var(--mu)"></span>
<span id="v140-nonreg" style="color:var(--mu)"></span>
<span style="margin-left:auto;color:#00d4b4;font-size:9px">open Truth Hub &rarr;</span>
</a>
<!-- V136-HEALTH-MODAL: in-page modal for broken URLs drill-down -->
<div id="v136-health-modal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.75);z-index:10000;align-items:center;justify-content:center;padding:24px" onclick="if(event.target.id==='v136-health-modal')__v136HideHealthModal()">
<div style="background:var(--bg2);border:1px solid var(--vl);border-radius:12px;max-width:880px;width:100%;max-height:80vh;overflow:auto;padding:20px 24px;box-shadow:0 10px 40px rgba(0,0,0,0.5)">
<div style="display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--bd);padding-bottom:10px;margin-bottom:14px">
<div>
<div style="font-size:14px;font-weight:600;color:var(--ac)">&#127973; Platform Health · Detail</div>
<div id="v136-modal-summary" style="font-size:10px;color:var(--mu);margin-top:2px">Loading&hellip;</div>
</div>
<button onclick="__v136HideHealthModal()" style="background:transparent;border:1px solid var(--bd);color:var(--mu);border-radius:6px;padding:4px 10px;cursor:pointer;font-size:11px">ESC</button>
</div>
<div id="v136-modal-content" style="font-size:11px;color:var(--fg);font-family:monospace;line-height:1.6">Loading&hellip;</div>
<div style="border-top:1px solid var(--bd);margin-top:14px;padding-top:10px;font-size:9px;color:var(--mu)">
Source: <code>/api/screens-health.json</code> &middot; maintenu par cron ecosystem
</div>
</div>
</div>
<button class="tab on" data-view="chat">CHAT MULTIAGENT</button>
<button class="tab" data-view="code">CODE (WEVCODE)</button>
<button class="tab" data-view="arena">ARENA 14 PROVIDERS</button>
<button class="tab" data-view="capabilities">IA CAPABILITIES</button>
<button class="tab" data-view="training">TRAINING HUB</button>
<button class="tab" data-view="orchestrator">ORCHESTRATOR</button>
<button class="tab" data-view="dashboards">DASHBOARDS</button>
<!-- V125-THEME: theme toggle -->
<button id="theme-toggle" class="tab" onclick="__toggleTheme()" title="Basculer thème" style="margin-left:auto">&#9728;</button>
</nav>
<main>
<!-- CHAT MULTIAGENT (wevia-master pattern) -->
<section class="view on" id="v-chat">
<div class="stbar">
<span><span class="dot"></span>sovereign:4000</span>
<span>streaming SSE 1h</span>
<span>multi-agent auto</span>
<span style="cursor:pointer;user-select:none;color:var(--vl)" id="human-toggle" onclick="toggleHumanMode()" title="Mode humain: bypass stubs/intents, route vers LLM pure (llm-direct.php) avec prompt /etc/wevia/system-prompt.txt">
<input type="checkbox" id="human-chk" style="vertical-align:middle" checked> &#128150; Mode Humain (LLM pure)
</span>
<span id="s-chat"></span>
</div>
<div class="out" id="out-chat">
<div class="msg sys">&gt; CHAT multi-agent prêt. Natural language router V103 actif. Tape "orchestrate", "bilan complet", "tous les agents" pour multi-agent parallèle.</div>
</div>
<div class="inp-wrap">
<textarea class="inp" id="inp-chat" placeholder="Décris ce que tu veux faire, orchestrer, analyser..."></textarea>
<button onclick="sendChat()" id="btn-chat">ENVOYER</button>
</div>
</section>
<!-- CODE (wevcode pattern) -->
<section class="view" id="v-code">
<div class="modes" id="code-modes">
<button class="mode on" data-mode="code">&lt;/&gt; CODE</button>
<button class="mode" data-mode="analyze">ANALYZE</button>
<button class="mode" data-mode="plan">PLAN</button>
<button class="mode" data-mode="execute">EXECUTE</button>
<button class="mode" data-mode="git">GIT</button>
<button class="mode" data-mode="rag">RAG</button>
</div>
<div class="stbar">
<span><span class="dot"></span>cerebras qwen-3-235b</span>
<span>Qdrant RAG</span>
<span>Cognitive 635fn</span>
<span id="s-code"></span>
</div>
<div class="out" id="out-code">
<div class="msg sys">&gt; WEVCODE v2 · Sovereign Coding Agent · 6 modes disponibles</div>
</div>
<div class="inp-wrap">
<textarea class="inp" id="inp-code" placeholder="Décris le code à générer, analyser, debug, planifier..."></textarea>
<button onclick="sendCode()" id="btn-code">ENVOYER</button>
</div>
</section>
<!-- ARENA (weval-arena pattern) -->
<section class="view" id="v-arena">
<div class="stbar">
<span><span class="dot"></span>14 providers cascade</span>
<span>auto fallback</span>
<span id="s-arena"></span>
</div>
<div class="out" id="out-arena">
<div class="msg sys">&gt; ARENA · Multi-provider intelligent routing</div>
<div class="msg sys">&gt; Providers: cerebras, groq, gemini, sambanova, nvidia, mistral, hf, openrouter, github, cf, deepseek, anthropic, ollama, maestro</div>
</div>
<div class="inp-wrap">
<textarea class="inp" id="inp-arena" placeholder="Question → routage auto vers meilleur provider disponible"></textarea>
<button onclick="sendArena()" id="btn-arena">ENVOYER</button>
</div>
</section>
<!-- CAPABILITIES -->
<section class="view" id="v-caps">
<div class="caps-grid" id="caps-grid">
<!-- V111-BLADE-ENRICH: live blade tasks + quick actions -->
<div class="cap" style="grid-column:1 / -1;background:linear-gradient(135deg,#1e293b,#0f172a);border-color:var(--cy)">
<h3>&#128248; Blade Tasks Queue &mdash; Chrome yacineutt pilote temps-reel</h3>
<p style="margin-bottom:10px">Tasks pending: <b id="blade-pending">...</b> &middot; Tasks done: <b id="blade-done">...</b> &middot; MCP port 8765 Bearer actif</p>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:8px">
<button class="mode" onclick="pushBladeTask('office_create',{})">+ Office Create</button>
<button class="mode" onclick="pushBladeTask('deepseek_renew',{})">+ DeepSeek Renew</button>
<button class="mode" onclick="pushBladeTask('thuggie_login',{})">+ Thuggie Login</button>
<button class="mode" onclick="pushBladeTask('token_github_renew',{})">+ GitHub Token</button>
<button class="mode" onclick="pushBladeTask('token_whatsapp_renew',{})">+ WhatsApp Token</button>
<button class="mode" onclick="refreshBladeStats()">Refresh</button>
</div>
<div id="blade-log" style="margin-top:10px;font-size:10px;color:var(--mu);max-height:120px;overflow-y:auto;background:var(--bg3);padding:8px;border-radius:4px;display:none"></div>
</div>
<!-- V113-QUICK-INTENTS: test any intent from chat -->
<div class="cap" style="grid-column:1 / -1;background:linear-gradient(135deg,#1e293b,#0f172a);border-color:var(--vl)">
<h3>&#9889; Quick Test Any Intent &mdash; 2004 intents live</h3>
<p style="margin-bottom:10px">Tape un trigger ci-dessous ou choisis un preset. Repond: <b id="intent-latency">-</b></p>
<div style="display:flex;gap:6px;margin-bottom:8px">
<input id="intent-input" type="text" placeholder="tape un trigger (ex: show tips, bilan complet, tous les agents, tips all, autowire, autoentraine)" style="flex:1;background:var(--bg3);border:1px solid var(--rim);border-radius:4px;padding:6px 10px;color:var(--t);font-family:inherit;font-size:11px"/>
<button class="mode" onclick="testIntent()">TEST</button>
</div>
<div style="display:flex;gap:4px;flex-wrap:wrap;margin-bottom:8px">
<button class="mode" onclick="setIntent('show tips')">show tips</button>
<button class="mode" onclick="setIntent('tips all')">tips all</button>
<button class="mode" onclick="setIntent('bilan complet')">bilan complet</button>
<button class="mode" onclick="setIntent('tous les agents en parallele')">tous les agents</button>
<button class="mode" onclick="setIntent('que peux tu faire')">capabilities</button>
<button class="mode" onclick="setIntent('autowire')">autowire</button>
<button class="mode" onclick="setIntent('autoentraine')">autoentraine</button>
<button class="mode" onclick="setIntent('create tool')">create tool</button>
<button class="mode" onclick="setIntent('push blade task')">blade tasks</button>
</div>
<pre id="intent-output" style="background:var(--bg3);padding:8px;border-radius:4px;font-size:10px;color:var(--mu);max-height:220px;overflow:auto;white-space:pre-wrap;word-break:break-word;display:none;margin:0"></pre>
</div>
<div class="cap">
<h3>&#127760; DeepSeek Web Access</h3>
<p>DeepSeek R1 reasoning + V3 chat · acces cookies + API · session rotee auto · gratuit via Thuggie Web integration.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#128187; Selenium Chrome Autonomy</h3>
<p>Blade user yacineutt TOUJOURS connecte · Chrome persistent context · WEVIA pilote via Playwright CDP · zero login manuel.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#128193; Office 365 Enterprise</h3>
<p>Microsoft Graph API · 34 tenants · create/delete users · MFA · Office recovery · backdoor admin · audit complet.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#128268; Token Renewal Engine</h3>
<p>Auto-refresh OAuth · GitHub PAT · WhatsApp Meta · Calendly · Cloudflare · rotation anti-rate-limit.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#128270; Cyber Tips 6 mois</h3>
<p>CF-Bypass Phase 1+2 · PowerMTA warmup · O365 via PMTA 97% inbox · SMTP smuggling · seeds rotation.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#127909; Video + Screenshot E2E</h3>
<p>Playwright record_video_dir WEBM · full-page 1920x1080 · 16/16 tests visuels · auto-validation.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#9889; WEVIA Master Streaming</h3>
<p>fetch+getReader() + AbortSignal.timeout(3600000ms). Pattern identique Claude Code SDK. SSE parsing 5 types événements.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#128736; Multi-Agent Orchestrator</h3>
<p>15 agents parallèles SSE · V102 regex étendu · V103 natural language router · 10 patterns détectés.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#128187; WEVCODE 6 Modes</h3>
<p>code / analyze / plan / execute / git / rag. CodeAnalyzer + Planner + ToolUseV2 + Cognitive 635 fn.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#127760; Arena Multi-Provider</h3>
<p>14 providers cascade: cerebras qwen-3-235b, groq llama 3.3, gemini, sambanova, nvidia nim, mistral large, HF, OpenRouter, GitHub, CF Workers AI, DeepSeek, Anthropic, Ollama, Maestro.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#129504; Qdrant RAG</h3>
<p>Knowledge base vectoriel · 14,368 vectors · 1181 files vault · wiki + GOLD indexed.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#128218; HF Fine-Tune</h3>
<p>yace222/weval-brain-v4 · continuous training · auto-learning loop via cron.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#127773; Blade IA (Razer)</h3>
<p>34 capabilities locales · hamid-fullscreen.php · Selenium + Playwright persistent context.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#128081; Director Agent</h3>
<p>312 services monitored · wevia-director.php · wevia-fiability.php · autofix docker.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#127937; NonReg 6sigma</h3>
<p>201/201 tests passants · L99 daily · Playwright visual 16/16.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#128225; Tool Use</h3>
<p>wevia-full-exec.php · bash exec · file r/w · DB query · git push · cron · CF purge · GOLD.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#128104; Session + Files</h3>
<p>Session ID persistant · history 10 last · attachments base64 · long-running tasks.</p>
<span class="tag r">READY</span>
</div>
<div class="cap">
<h3>&#128260; Cognitive 635fn</h3>
<p>cognitive-opus46 · 635 functions · CoT decompose · Reasoning + Creative + Analysis chains.</p>
<span class="tag r">READY</span>
</div>
</div>
</section>
<!-- TRAINING -->
<section class="view" id="v-train">
<div class="train-stats" id="train-stats">
<div class="train-stat"><div class="k">HF Model</div><div class="v" style="font-size:13px;color:var(--cy)">weval-brain-v4</div><div class="s">yace222/weval-brain-v4</div></div>
<div class="train-stat"><div class="k">Qdrant Vectors</div><div class="v" id="t-qv">...</div><div class="s">KB indexed</div></div>
<div class="train-stat"><div class="k">Wiki entries</div><div class="v" id="t-wi">...</div><div class="s">markdown files</div></div>
<div class="train-stat"><div class="k">Vault files</div><div class="v" id="t-vf">...</div><div class="s">GOLD: 105</div></div>
<div class="train-stat"><div class="k">Agents</div><div class="v" id="t-ag">...</div><div class="s">8 categories</div></div>
<div class="train-stat"><div class="k">Cognitive fn</div><div class="v">635</div><div class="s">cognitive-opus46</div></div>
<div class="train-stat"><div class="k">Intents Wired</div><div class="v" id="t-int">...</div><div class="s">auto + opus + claude</div></div>
<div class="train-stat"><div class="k">GOLD Backups</div><div class="v" id="t-gold">...</div><div class="s">/opt/wevads/vault</div></div>
</div>
<!-- V113-ROUTER-ACTIVITY: live router pattern matches -->
<div class="cap" style="grid-column: 1 / -1;margin-bottom:14px;background:linear-gradient(135deg,#1e1b3b,#0f0a25);border-color:var(--vl)">
<h3>&#128225; V103 Natural Language Router &mdash; Activité live</h3>
<p style="margin-bottom:10px;font-size:10px">Total matches: <b id="router-total">...</b> &middot; Dernières 50: <b id="router-recent">...</b></p>
<div id="router-patterns" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:4px;font-size:9px;margin-top:8px"></div>
<div id="router-recent-msgs" style="margin-top:10px;font-size:9px;color:var(--mu);max-height:120px;overflow-y:auto;background:var(--bg3);padding:6px 10px;border-radius:4px;display:none"></div>
<button class="mode" onclick="loadRouterActivity()" style="margin-top:8px">Refresh Router Activity</button>
<button class="mode" onclick="document.getElementById('router-recent-msgs').style.display='block';loadRouterActivity()">Show Recent Messages</button>
</div>
<div class="train-log" id="train-log">
<div><b>&gt; TRAINING HUB</b> · Continuous learning loop active</div>
<div>&gt; Cron WeviaAutoLrn · toutes les 2h</div>
<div>&gt; Cron WeviaDream · auto-expansion nocturne</div>
<div>&gt; Cron WeviaEmbed · Qdrant embedding auto</div>
<div>&gt; Source: chat history + wiki + vault + GOLD backups</div>
<div>&gt; Pipeline: collect → dedupe → embed → fine-tune → deploy</div>
<div>&gt; Fallback: si HF indispo → LocalAI (Ollama port 11434)</div>
</div>
</section>
<!-- ORCHESTRATOR -->
<section class="view" id="v-orch">
<div class="stbar">
<span>726 agents dans le catalog</span>
<span id="s-orch-cat">8 categories</span>
<span id="s-orch-load"></span>
</div>
<div class="orch-agents" id="orch-agents"></div>
</section>
<!-- V116-DASHBOARDS-TAB + V122-POLISH: consolidated dashboards view -->
<section class="view" id="v-dashboards">
<h2 style="color:var(--vl);margin-bottom:10px">&#128202; Dashboards Consolid&eacute;s &mdash; 70 tuiles reli&eacute;es</h2>
<p style="color:var(--mu);margin-bottom:16px;font-size:12px">Point d'entr&eacute;e unique pour tous les dashboards. Filtrer par cat&eacute;gorie. Tous les orphelins reli&eacute;s (doctrine pas d'orphelin).</p>
<div id="dash-filters" style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px;position:sticky;top:0;background:var(--bg);z-index:10;padding:8px 0;border-bottom:1px solid var(--bd)">
<button class="mode dash-filter on" data-cat="all">Loading...</button>
</div>
<!-- V119-SEARCH: search + sort controls -->
<div style="display:flex;gap:8px;margin-bottom:12px;align-items:center;flex-wrap:wrap">
<input type="text" id="dash-search" placeholder="Rechercher... (Cmd/Ctrl+K)" style="flex:1;min-width:200px;padding:6px 10px;background:var(--bg3);color:var(--ac);border:1px solid var(--bd);border-radius:4px;font-size:11px">
<!-- V138-COPY-URL: share current filtered view -->
<button id="v138-copy-btn" onclick="__v138CopyShareURL()" style="padding:6px 10px;background:var(--bg3);border:1px solid var(--bd);border-radius:4px;color:var(--mu);cursor:pointer;font-size:11px;transition:all 0.15s" onmouseover="this.style.color='var(--ac)';this.style.borderColor='var(--vl)'" onmouseout="this.style.color='var(--mu)';this.style.borderColor='var(--bd)'" title="Copier URL partageable (sort + filtre + pins dans le hash)">&#128279; Share</button>
<select id="dash-sort" style="padding:6px 10px;background:var(--bg3);color:var(--ac);border:1px solid var(--bd);border-radius:4px;font-size:11px">
<option value="name">Nom A-Z</option>
<option value="size">Taille</option>
<option value="mtime">Date modif</option>
<option value="category">Catégorie</option>
</select>
<span id="dash-count" style="font-size:10px;color:var(--mu)"></span>
<!-- V124-ENRICH: bulk clear pins button -->
<button id="dash-clear-pins" class="mode" onclick="__dashClearAllPins()" style="display:none;font-size:10px" title="Supprimer tous les pins">&#9733; Clear pins</button>
</div>
<!-- V124-ENRICH: separate pinned section -->
<div id="dash-pinned-section" style="display:none;margin-bottom:20px">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;padding-bottom:6px;border-bottom:1px solid rgba(251,191,36,0.25)">
<span style="color:#fbbf24;font-size:14px">&#9733; Favorites</span>
<span id="dash-pinned-count" style="font-size:10px;color:var(--mu)"></span>
</div>
<div id="dash-pinned-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px"></div>
</div>
<div id="dash-stats" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:6px;margin-bottom:14px;font-size:10px"></div>
<div id="dash-grid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px"></div>
<!-- V128-SCROLL-TOP: floating "top" button visible only when scrolled far -->
<button id="dash-scroll-top" onclick="window.scrollTo({top:0,behavior:'smooth'})" style="position:fixed;bottom:24px;right:24px;background:var(--bg3);color:var(--ac);border:1px solid var(--vl);border-radius:50%;width:44px;height:44px;font-size:18px;cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,0.3);display:none;z-index:50;transition:all 0.2s ease" title="Retour haut">&#8593;</button>
</section>
</main>
<footer>
<span>All-IA Hub · Souverain · Opus-indépendant</span>
<span>&copy; WEVAL Consulting · V107 · 2026</span>
</footer>
<script>
// Tab switching
document.querySelectorAll('.tab').forEach(t=>t.addEventListener('click',()=>{
document.querySelectorAll('.tab').forEach(x=>x.classList.remove('on'));
document.querySelectorAll('.view').forEach(x=>x.classList.remove('on'));
t.classList.add('on');
const v=t.dataset.view;
const map={chat:'v-chat',code:'v-code',arena:'v-arena',capabilities:'v-caps',training:'v-train',orchestrator:'v-orch',dashboards:'v-dashboards'}; /* V116-MAP-FIX */
document.getElementById(map[v]).classList.add('on');
document.getElementById('h-md').textContent=t.textContent.split(' ')[0];
if(v==='orchestrator') loadAgents(); if(v==='dashboards') loadDashboards();
}));
// Code modes
let codeMode='code';
document.querySelectorAll('#code-modes .mode').forEach(m=>m.addEventListener('click',()=>{
document.querySelectorAll('#code-modes .mode').forEach(x=>x.classList.remove('on'));
m.classList.add('on');
codeMode=m.dataset.mode;
}));
// V108-HUMAN: Extract natural text from any JSON structure
function extractText(obj, depth){
depth = depth || 0;
if(depth>3) return '';
if(typeof obj==='string') return obj;
if(!obj || typeof obj!=='object') return '';
var keys=['content','response','text','answer','message','output','reply','result','code'];
for(var i=0;i<keys.length;i++){
var v=obj[keys[i]];
if(v) return typeof v==='string'?v:extractText(v,depth+1);
}
if(obj.choices && obj.choices[0]){
var c=obj.choices[0];
if(c.message && c.message.content) return c.message.content;
if(c.text) return c.text;
}
var vals=Object.values(obj);
for(var j=0;j<vals.length;j++){ if(typeof vals[j]==='string' && vals[j].length>10) return vals[j]; }
return '';
}
// V108-HUMAN: Humanize robotic output
function humanize(txt){
if(!txt) return '';
if(typeof txt!=='string') txt=String(txt);
txt=txt.trim();
// Strip wrapping quotes
if(txt.length>1 && txt[0]==='"' && txt[txt.length-1]==='"'){
try{ txt=JSON.parse(txt); }catch(e){}
}
// Unescape common sequences
txt=txt.replace(/\\n/g,'\n').replace(/\\t/g,'\t').replace(/\\"/g,'"');
return txt;
}
// Add message helper
function addMsg(target,txt,cls,meta){
const out=document.getElementById(target);
const d=document.createElement('div');
d.className='msg '+(cls||'a');
d.innerHTML='<div>'+txt.replace(/</g,'&lt;').replace(/\n/g,'<br>')+'</div>'+(meta?'<div class="meta">'+meta+'</div>':'');
out.appendChild(d);
out.scrollTop=out.scrollHeight;
}
// V108-HUMAN-MODE state
let humanMode = true;
function toggleHumanMode(){
humanMode = !humanMode;
document.getElementById('human-chk').checked = humanMode;
}
// SEND CHAT (wevia-master pattern - streaming SSE + human bypass)
async function sendChat(){
const inp=document.getElementById('inp-chat');
const text=inp.value.trim();
if(!text) return;
addMsg('out-chat',text,'u');
inp.value='';
const btn=document.getElementById('btn-chat');
btn.disabled=true;
const t0=Date.now();
try{
// V108C-HUMAN-ROUTE: pure LLM bypass stubs/intents
const shortMsg = text.length < 120 && !/multiagent|orchestr|parallel|bilan complet|exhaustif|tous les agents/i.test(text);
/* V109-LLM-DIRECT: human mode hits llm-direct.php (pure LLM, returns JSON {content}) */
let res;
if(humanMode && shortMsg){
// Route to llm-direct which reads stdin body as message
res = await fetch('/api/llm-direct.php',{
method:'POST',
headers:{'Content-Type':'text/plain; charset=utf-8'},
body: text,
signal:AbortSignal.timeout(60000)
});
} else {
res = await fetch('/api/wevia-master-api.php',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({message:text,session_id:'all-ia-hub-'+Date.now()}),
signal:AbortSignal.timeout(300000)
});
}
const d=await res.json();
const t=((Date.now()-t0)/1000).toFixed(1);
/* V108-HUMAN: extract natural text */
let txt=d.content||d.response||d.text||d.answer||d.message||d.output||d.reply||'';
if(!txt && typeof d==='object'){ txt=extractText(d); }
if(!txt) txt=JSON.stringify(d,null,2);
addMsg('out-chat',humanize(txt),'a',(d.provider||d.tool||'?')+' · '+t+'s'+(d.agents_count?' · '+d.agents_count+' agents':''));
}catch(e){
addMsg('out-chat','Error: '+e.message,'a','error');
}
btn.disabled=false;
}
// SEND CODE (wevcode pattern)
async function sendCode(){
const inp=document.getElementById('inp-code');
const text=inp.value.trim();
if(!text) return;
addMsg('out-code','['+codeMode+'] '+text,'u');
inp.value='';
const btn=document.getElementById('btn-code');
btn.disabled=true;
const t0=Date.now();
try{
const res=await fetch('/api/wevcode-superclaude.php',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({action:codeMode,prompt:text,message:text})
});
const d=await res.json();
const t=((Date.now()-t0)/1000).toFixed(1);
let out=d.content||d.result||d.response||d.text||d.answer||d.output||d.code||'';
if(!out && typeof d==='object'){ out=extractText(d); }
if(!out) out=JSON.stringify(d,null,2);
addMsg('out-code',humanize(out),'a',codeMode+' · '+(d.model||d.provider||'sovereign')+' · '+t+'s');
}catch(e){
addMsg('out-code','Error: '+e.message,'a','error');
}
btn.disabled=false;
}
// SEND ARENA (weval-arena pattern - multi-provider)
async function sendArena(){
const inp=document.getElementById('inp-arena');
const text=inp.value.trim();
if(!text) return;
addMsg('out-arena',text,'u');
inp.value='';
const btn=document.getElementById('btn-arena');
btn.disabled=true;
const t0=Date.now();
try{
const res=await fetch('/api/wevia-multi-provider.php',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({message:text,mode:'auto',model:'auto'})
});
const d=await res.json();
const t=((Date.now()-t0)/1000).toFixed(1);
let out=d.content||d.response||d.result||d.text||d.answer||d.output||'';
if(!out && typeof d==='object'){ out=extractText(d); }
if(!out) out=JSON.stringify(d,null,2);
addMsg('out-arena',humanize(out),'a',(d.provider||d.model||'auto')+' · '+t+'s');
}catch(e){
// Fallback: try master API
try{
const r2=await fetch('/api/wevia-master-api.php',{
method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({message:text,session_id:'arena-'+Date.now()})
});
const d=await r2.json();
addMsg('out-arena',d.response||JSON.stringify(d),'a','fallback-master');
}catch(e2){
addMsg('out-arena','All providers failed: '+e.message,'a','error');
}
}
btn.disabled=false;
}
// Load agents for orchestrator view
async function loadAgents(){
const box=document.getElementById('orch-agents');
if(box.children.length>0) return;
document.getElementById('s-orch-load').textContent='chargement...';
try{
const r=await fetch('/api/agents-catalog-api.php');
const d=await r.json();
document.getElementById('h-ag').textContent=d.total;
document.getElementById('t-ag').textContent=d.total;
document.getElementById('s-orch-cat').textContent=Object.keys(d.categories).length+' categories · '+d.total+' total';
// Show top 60 core + claudecode + skills
const priority=d.agents.filter(a=>['core','claudecode','skills','superclaude'].includes(a.cat));
priority.slice(0,100).forEach(a=>{
const e=document.createElement('div');
e.className='orch-agent';
e.title=a.desc||a.name;
e.innerHTML='<span class="d"></span>'+a.name;
box.appendChild(e);
});
document.getElementById('s-orch-load').textContent='('+priority.length+' priority affichés)';
}catch(e){
document.getElementById('s-orch-load').textContent='erreur chargement';
}
}
// Keyboard shortcuts
document.querySelectorAll('.inp').forEach(i=>{
i.addEventListener('keydown',e=>{
if(e.key==='Enter'&&(e.ctrlKey||e.metaKey)){
e.preventDefault();
const view=i.closest('.view').id;
if(view==='v-chat') sendChat();
else if(view==='v-code') sendCode();
else if(view==='v-arena') sendArena();
}
});
});
// V113-QUICK-INTENTS: test any intent
function setIntent(s){ document.getElementById('intent-input').value=s; }
async function testIntent(){
const inp=document.getElementById('intent-input');
const out=document.getElementById('intent-output');
const lat=document.getElementById('intent-latency');
const msg=inp.value.trim();
if(!msg){out.style.display='block';out.textContent='Tape un trigger d abord';return;}
out.style.display='block';
out.textContent='Loading...';
lat.textContent='...';
const t0=Date.now();
try{
const r=await fetch('/api/wevia-master-api.php',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({message:msg,session_id:'hub-v113-test-'+Date.now()}),
signal:AbortSignal.timeout(60000)
});
const d=await r.json();
const t=((Date.now()-t0)/1000).toFixed(1);
lat.textContent=t+'s '+(d.provider||d.tool||'?');
const txt=d.content||d.response||d.text||d.output||JSON.stringify(d,null,2);
out.textContent=txt.substring(0,2500);
}catch(e){
out.textContent='Error: '+e.message;
lat.textContent='err';
}
}
// V111-BLADE-ENRICH: live blade tasks stats + push actions
async function refreshBladeStats(){
try{
const r=await fetch('/api/blade-task-create.php?k=WEVADS2026&action=list');
const d=await r.json();
const pending=(d.tasks||[]).filter(t=>t.status==='pending').length;
const done=(d.tasks||[]).filter(t=>t.status==='done').length;
const bp=document.getElementById('blade-pending');
const bd=document.getElementById('blade-done');
if(bp) bp.textContent=pending;
if(bd) bd.textContent=done;
}catch(e){
const bp=document.getElementById('blade-pending');
if(bp) bp.textContent='?';
}
}
// V112-BLADE-FIX: robust pushBladeTask with urlencoded body + visible log + debug
async function pushBladeTask(goal, params){
const log=document.getElementById('blade-log');
if(log){
log.style.display='block';
log.style.maxHeight='200px';
log.innerHTML += '<div>&gt; Pushing task: '+goal+'...</div>';
log.scrollTop = log.scrollHeight;
}
console.log('[pushBladeTask] goal=',goal,'params=',params);
try{
// Use URL-encoded instead of FormData for php $_POST compatibility
const body = 'k=WEVADS2026&action=create&goal='+encodeURIComponent(goal)+'&params='+encodeURIComponent(JSON.stringify(params||{}));
const r = await fetch('/api/blade-task-create.php',{
method:'POST',
headers:{'Content-Type':'application/x-www-form-urlencoded'},
body: body
});
const txt = await r.text();
let d;
try{ d = JSON.parse(txt); }catch(e){ d = {error:'parse failed: '+txt.substring(0,100)}; }
console.log('[pushBladeTask] response=',d);
if(log){
if(d.ok){
log.innerHTML += '<div style="color:var(--gr)">&gt; \u2705 Task created: '+d.id+'</div>';
} else {
log.innerHTML += '<div style="color:var(--rd)">&gt; \u274c '+(d.error||'failed')+'</div>';
}
log.scrollTop = log.scrollHeight;
}
setTimeout(refreshBladeStats, 500);
} catch(e){
console.error('[pushBladeTask] error=',e);
if(log){
log.innerHTML += '<div style="color:var(--rd)">&gt; \u274c Error: '+e.message+'</div>';
log.scrollTop = log.scrollHeight;
}
}
}
// auto-refresh blade stats every 30s when capabilities tab visible
setInterval(()=>{ if(document.getElementById('v-caps').classList.contains('on')) refreshBladeStats(); },30000);
setTimeout(refreshBladeStats, 1500);
// V116-DASHBOARDS-TAB: load 70 dashboards tiles
let __dashData = null;
async function loadDashboards(){
// V117-HTTP-BADGES: load with optional status check
try {
const r = await fetch('/api/dashboards-registry.php');
const d = await r.json();
__dashData = d;
renderDashStats(d);
renderDashFilters(d);
/* V129: restore active filter */
const savedCat = __dashHashGet('cat') || 'all';
renderDashGrid(d.dashboards, savedCat);
// async load status badges in background
loadDashboardsStatus();
} catch(e){ console.error('[dashboards]',e); }
}
async function loadDashboardsStatus(){
try {
const r = await fetch('/api/dashboards-registry.php?check=1');
const d = await r.json();
__dashData = d;
renderDashGrid(d.dashboards, document.querySelector('.dash-filter.on')?.getAttribute('data-cat') || 'all');
} catch(e){ console.error('[dashboards-status]',e); }
}
function renderDashStats(d){
const box = document.getElementById('dash-stats');
if (!box) return;
box.innerHTML = '<div style="padding:4px 8px;background:var(--bg3);border-radius:3px"><b>Total:</b> '+d.total+'</div>' +
Object.entries(d.by_category).map(([c,n]) =>
'<div style="padding:4px 8px;background:var(--bg3);border-radius:3px"><b>'+c+':</b> '+n+'</div>'
).join('');
}
function renderDashFilters(d){
/* V127-RECENT-FILTER: add Recent <24h chip computed client-side */
const box = document.getElementById('dash-filters');
if (!box) return;
const now = Date.now();
const recentCount = (d.dashboards || []).filter(e => {
try { return (now - new Date(e.mtime).getTime()) < 24*3600*1000; } catch(_) { return false; }
}).length;
box.innerHTML = '<button class="mode dash-filter on" data-cat="all">All ('+d.total+')</button>' +
'<button class="mode dash-filter" data-cat="__recent" style="border-color:#10b981;color:#10b981">&#10024; Recent &lt;24h ('+recentCount+')</button>' +
Object.entries(d.by_category).map(([c,n]) =>
'<button class="mode dash-filter" data-cat="'+c+'">'+c+' ('+n+')</button>'
).join('');
box.querySelectorAll('.dash-filter').forEach(b => {
b.addEventListener('click', () => {
box.querySelectorAll('.dash-filter').forEach(x => x.classList.remove('on'));
b.classList.add('on');
const cat = b.getAttribute('data-cat');
__dashHashSet('cat', cat === 'all' ? '' : cat); /* V129: persist */
renderDashGrid(__dashData.dashboards, cat);
});
});
/* V129: restore active filter from URL */
const savedCat = __dashHashGet('cat');
if (savedCat) {
const target = box.querySelector('button[data-cat="' + savedCat + '"]');
if (target) {
box.querySelectorAll('.dash-filter').forEach(x => x.classList.remove('on'));
target.classList.add('on');
}
}
}
function renderDashGrid(items, cat){
/* V117-HTTP-BADGES + V119-SEARCH + V123-PINS + V124-ENRICH */
const box = document.getElementById('dash-grid');
const pinnedBox = document.getElementById('dash-pinned-grid');
const pinnedSection = document.getElementById('dash-pinned-section');
const pinnedCountEl = document.getElementById('dash-pinned-count');
const clearBtn = document.getElementById('dash-clear-pins');
if (!box) return;
const search = (document.getElementById('dash-search')?.value || '').toLowerCase().trim();
const sort = document.getElementById('dash-sort')?.value || 'name';
const pinned = __dashPins();
let filtered;
if (cat === 'all') filtered = items;
else if (cat === '__recent') {
const now = Date.now();
filtered = items.filter(x => { try { return (now - new Date(x.mtime).getTime()) < 24*3600*1000; } catch(_) { return false; } });
}
else filtered = items.filter(x => x.category === cat);
if (search) {
filtered = filtered.filter(x =>
x.name.toLowerCase().includes(search) ||
x.display.toLowerCase().includes(search) ||
x.category.toLowerCase().includes(search)
);
}
if (sort === 'size') filtered = filtered.slice().sort((a,b) => b.size_kb - a.size_kb);
else if (sort === 'mtime') filtered = filtered.slice().sort((a,b) => (b.mtime||'').localeCompare(a.mtime||''));
else if (sort === 'category') filtered = filtered.slice().sort((a,b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name));
else filtered = filtered.slice().sort((a,b) => a.name.localeCompare(b.name));
// V124-ENRICH: split into pinned + rest; no more merge-sort with pin priority
const pinnedItems = filtered.filter(x => pinned.has(x.name));
const restItems = filtered.filter(x => !pinned.has(x.name));
const countEl = document.getElementById('dash-count');
/* V131-BROKEN-COUNT: aggregate status from registry */
const brokenCount = items.filter(e => e.http_status && e.http_status >= 400).length;
if (countEl) {
const brokenSuffix = brokenCount > 0
? ' <span style="color:#ef4444;margin-left:6px;font-weight:600" title="Broken dashboards (HTTP &gt;= 400)">&#9679; ' + brokenCount + ' broken</span>'
: ' <span style="color:#10b981;margin-left:6px;font-weight:600" title="All dashboards healthy">&#9679; all OK</span>';
countEl.innerHTML = filtered.length + ' / ' + items.length + ' tuiles' + (pinned.size ? ' (' + pinned.size + ' pin' + (pinned.size>1?'s':'') + ')' : '') + brokenSuffix;
}
// Show/hide pinned section + clear button
if (pinnedSection) pinnedSection.style.display = pinnedItems.length ? 'block' : 'none';
if (clearBtn) clearBtn.style.display = pinned.size ? 'inline-block' : 'none';
if (pinnedCountEl) pinnedCountEl.textContent = pinnedItems.length + (pinnedItems.length === 1 ? ' dashboard' : ' dashboards');
function statusBadge(s){
if(!s) return '';
if(s===200) return '<span style="color:#10b981;font-size:9px;margin-left:4px">&#9679; 200</span>';
if(s===302) return '<span style="color:#f59e0b;font-size:9px;margin-left:4px">&#9679; auth</span>';
return '<span style="color:#ef4444;font-size:9px;margin-left:4px">&#9679; '+s+'</span>';
}
// V124-ENRICH: helper + recently-updated badge + split render
function recentBadge(mtime){
if(!mtime) return '';
const age = Date.now() - new Date(mtime).getTime();
if(age < 24*3600*1000) return '<span title="Updated <24h" style="color:#10b981;font-size:9px;margin-left:4px">&#10024; new</span>';
return '';
}
function tileHtml(e){
return '<a href="'+e.url+'" target="_blank" class="dash-tile' + (pinned.has(e.name) ? ' pinned' : '') + '" style="text-decoration:none;color:inherit;display:block;padding:10px;background:linear-gradient(135deg,'+e.color+'22,'+e.color+'11);border:1px solid ' + (pinned.has(e.name) ? '#fbbf24' : e.color+'55') + ';border-radius:6px;transition:transform 0.15s ease,border-color 0.15s ease,box-shadow 0.15s ease" onmouseover="this.style.transform=\'translateY(-2px)\';this.style.borderColor=\''+e.color+'\';this.style.boxShadow=\'0 4px 12px '+e.color+'33\'" onmouseout="this.style.transform=\'\';this.style.borderColor=\'' + (pinned.has(e.name) ? '#fbbf24' : e.color+'55') + '\';this.style.boxShadow=\'\'">' +
'<div style="font-size:18px;margin-bottom:4px">'+e.icon+' <span style="font-size:9px;color:'+e.color+';text-transform:uppercase">'+e.category+'</span>'+statusBadge(e.http_status)+recentBadge(e.mtime)+'</div>' +
'<div style="font-size:11px;font-weight:bold;line-height:1.3;margin-bottom:4px">'+e.display+'</div>' +
'<div style="font-size:9px;color:var(--mu);display:flex;justify-content:space-between;align-items:center"><span>'+e.name+' - '+e.size_kb+'KB</span>' +
'<button onclick="event.preventDefault();event.stopPropagation();__dashTogglePin(\''+e.name+'\')" style="background:transparent;border:0;color:'+(pinned.has(e.name)?"#fbbf24":"#555")+';cursor:pointer;font-size:14px;padding:0 4px" title="'+(pinned.has(e.name)?'Unpin':'Pin')+'">&#9733;</button>' +
'</div>' +
'</a>';
}
if (pinnedBox) pinnedBox.innerHTML = pinnedItems.map(tileHtml).join('');
box.innerHTML = restItems.map(tileHtml).join('');
}
// V125-THEME: theme toggle with URL hash persist
function __getTheme(){
return new URLSearchParams(window.location.hash.slice(1)).get('theme') || 'dark';
}
function __applyTheme(t){
document.body.classList.toggle('light', t === 'light');
const btn = document.getElementById('theme-toggle');
if (btn) btn.textContent = t === 'light' ? '\u263E' : '\u2600';
}
function __toggleTheme(){
const cur = __getTheme();
const next = cur === 'light' ? 'dark' : 'light';
const params = new URLSearchParams(window.location.hash.slice(1));
if (next === 'light') params.set('theme', 'light');
else params.delete('theme');
const h = params.toString();
window.history.replaceState(null, '', window.location.pathname + window.location.search + (h ? '#'+h : ''));
__applyTheme(next);
}
// Apply on load
setTimeout(() => __applyTheme(__getTheme()), 50);
// V123-PINS: pin management via URL hash (shareable, no localStorage)
function __dashPins(){
const raw = new URLSearchParams(window.location.hash.slice(1)).get('pins') || '';
return new Set(raw.split(',').filter(Boolean));
}
function __dashSetPins(set){
const params = new URLSearchParams(window.location.hash.slice(1));
if (set.size) params.set('pins', Array.from(set).join(','));
else params.delete('pins');
const h = params.toString();
window.history.replaceState(null, '', window.location.pathname + window.location.search + (h ? '#'+h : ''));
}
function __dashClearAllPins(){
if (!confirm('Clear all pins?')) return;
__dashSetPins(new Set());
if (__dashData) {
const activeCat = document.querySelector('.dash-filter.on')?.getAttribute('data-cat') || 'all';
renderDashGrid(__dashData.dashboards, activeCat);
}
}
// V129-URL-STATE: persist sort + active filter (category) in URL hash
function __dashHashGet(key){
return new URLSearchParams(window.location.hash.slice(1)).get(key) || '';
}
function __dashHashSet(key, val){
const params = new URLSearchParams(window.location.hash.slice(1));
if (val) params.set(key, val);
else params.delete(key);
const h = params.toString();
window.history.replaceState(null, '', window.location.pathname + window.location.search + (h ? '#'+h : ''));
}
function __dashTogglePin(name){
const s = __dashPins();
if (s.has(name)) s.delete(name);
else s.add(name);
__dashSetPins(s);
if (__dashData) {
const activeCat = document.querySelector('.dash-filter.on')?.getAttribute('data-cat') || 'all';
renderDashGrid(__dashData.dashboards, activeCat);
}
}
/* V136-HEALTH-MODAL: show/hide drill-down modal with broken URL list */
function __v136ShowHealthModal(){
const modal = document.getElementById('v136-health-modal');
const content = document.getElementById('v136-modal-content');
const summary = document.getElementById('v136-modal-summary');
if (!modal || !content) return;
modal.style.display = 'flex';
content.textContent = 'Fetching /api/screens-health.json ...';
fetch('/api/screens-health.json', {cache: 'no-store'}).then(r => r.ok ? r.json() : null).then(d => {
if (!d || !d.counts) { content.textContent = 'Pas de data health disponible.'; return; }
const c = d.counts;
const total = d.total || 0;
summary.textContent = 'Scan ' + (d.generated_at || '') + ' · Total ' + total + ' URLs · UP ' + (c.UP||0) + ' · SLOW ' + (c.SLOW||0) + ' · BROKEN ' + (c.BROKEN||0) + ' · DOWN ' + (c.DOWN||0) + ' · PHANTOM ' + (c.PHANTOM||0);
const byUrl = d.by_url || {};
const broken = [];
const slow = [];
for (const url in byUrl) {
const info = byUrl[url];
if (info.status === 'BROKEN' || info.status === 'DOWN') broken.push({url, ...info});
else if (info.status === 'SLOW') slow.push({url, ...info});
}
broken.sort((a,b) => (b.code||0) - (a.code||0));
slow.sort((a,b) => (b.ms||0) - (a.ms||0));
let html = '';
if (broken.length) {
html += '<div style="color:#ef4444;font-weight:600;margin-bottom:6px">&#128683; BROKEN/DOWN (' + broken.length + '):</div>';
broken.slice(0, 30).forEach(b => {
html += '<div style="padding:3px 8px;border-left:3px solid #ef4444;margin-bottom:4px;background:rgba(239,68,68,0.05)">' +
'<span style="color:#ef4444;font-weight:600">' + (b.status||'?') + '</span> ' +
'<span style="color:#f59e0b">' + (b.code||'?') + '</span> ' +
'<a href="' + b.url + '" target="_blank" style="color:var(--ac);text-decoration:none">' + b.url + '</a> ' +
'<span style="color:var(--mu);float:right">' + (b.ms||'?') + 'ms</span>' +
'</div>';
});
} else {
html += '<div style="color:#10b981;font-weight:600">&#9989; No broken URLs</div>';
}
if (slow.length) {
html += '<div style="color:#f59e0b;font-weight:600;margin-top:12px;margin-bottom:6px">&#128228; SLOW top 10 (>2s):</div>';
slow.slice(0, 10).forEach(s => {
html += '<div style="padding:2px 8px;border-left:3px solid #f59e0b;margin-bottom:3px">' +
'<span style="color:#f59e0b">SLOW</span> ' +
'<a href="' + s.url + '" target="_blank" style="color:var(--ac);text-decoration:none">' + s.url + '</a> ' +
'<span style="color:var(--mu);float:right">' + (s.ms||'?') + 'ms</span>' +
'</div>';
});
}
content.innerHTML = html;
}).catch(err => { content.textContent = 'Erreur fetch: ' + err; });
}
function __v136HideHealthModal(){
const modal = document.getElementById('v136-health-modal');
if (modal) modal.style.display = 'none';
}
/* Escape key closes modal */
document.addEventListener('keydown', function(e){
if (e.key === 'Escape') {
const modal = document.getElementById('v136-health-modal');
if (modal && modal.style.display !== 'none') __v136HideHealthModal();
}
});
/* V135-KPI-BANNER (V137 refactored): named async function, reusable by V137 refresh */
/* V139-TRUTH-STRIP: load source of truth registry (fire once on load) */
/* V142-FOOTER-STRIP: load ecosystem health once */
async function __v142LoadFooter(){
try {
const r = await fetch('/api/ecosystem-health.php', {cache: 'no-store'});
if (!r.ok) return;
const d = await r.json();
const set = (id, content) => { const el = document.getElementById(id); if (el) el.innerHTML = content; };
const colorScore = d.percent >= 99 ? '#10b981' : d.percent >= 95 ? '#f59e0b' : '#ef4444';
set('v142-score', '<span style="color:' + colorScore + '">' + (d.score || '?') + ' ' + (d.percent ?? 0) + '%</span>');
if (d.l99) set('v142-l99', 'L99 <b>' + (d.l99.pass||0) + '/' + (d.l99.total||0) + '</b>');
set('v142-tools', 'Tools <b>' + (d.tools_wired ?? 0) + '</b>');
if (d.infra) set('v142-docker', 'Docker <b>' + (d.infra.docker ?? 0) + '</b>');
if (d.providers) set('v142-providers', 'Providers <b>' + (d.providers.free ?? 0) + '</b>');
if (d.providers?.qdrant) set('v142-qdrant', 'Qdrant <b>' + Number(d.providers.qdrant).toLocaleString('fr-FR') + '</b>');
if (d.infra?.ollama) set('v142-ollama', 'Ollama <b>' + d.infra.ollama + '</b>');
const footer = document.getElementById('v142-footer');
if (footer) footer.title = 'Ecosystem health live · source: /api/ecosystem-health.php · ts: ' + (d.ts || 'n/a') + ' · click Truth → for unified registry';
} catch(_) {}
}
__v142LoadFooter();
async function __v139LoadTruthStrip(){
try {
const r = await fetch('/api/wevia-truth-registry.json', {cache: 'no-store'});
if (!r.ok) return;
const d = await r.json();
const setN = (id, n) => { const el = document.getElementById(id); if (el && n !== undefined) el.innerHTML = '&middot; <strong style="color:var(--ac)">' + Number(n).toLocaleString('fr-FR') + '</strong> ' + id.replace('v139-',''); };
setN('v139-agents', d.agents?.count_unique);
// === AMBRE-V1 referentiel-unique sync (additif · doctrine #14) ===
// Sync all agent count spots (h-ag header, t-ag truth strip, orch-agents orchestrator)
// All read from same truth-registry source → 'référentiel unique' doctrine
const setNumRaw = (id, n) => { const el = document.getElementById(id); if (el && n !== undefined && n !== null) el.textContent = Number(n).toLocaleString('fr-FR'); };
setNumRaw('h-ag', d.agents?.count_unique);
setNumRaw('t-ag', d.agents?.count_unique);
setNumRaw('orch-agents', d.agents?.count_unique);
// Also sync providers count if the header has it hardcoded
setNumRaw('h-pv', d.providers?.count);
setNumRaw('t-pv', d.providers?.count);
// === END AMBRE-V1 ===
// V141-AGENTS-TOOLTIP: expose dedup context on hover
const agEl = document.getElementById('v139-agents');
if (agEl && d.agents) {
const u = d.agents.count_unique || 0;
const w = d.agents.count_with_overlaps || 0;
const bySrc = d.agents.by_source || {};
const srcList = Object.entries(bySrc).map(([k,v]) => ' ' + k + ': ' + v).join('\n');
agEl.title = 'Agents uniques dédupliqués: ' + u + '\nAvec overlaps bruts: ' + w + '\nSources:\n' + srcList + '\n\nAutres pages peuvent afficher des chiffres différents (950, 990, 126) car consomment d\'autres référentiels. Valeur de vérité = 906.';
agEl.style.cursor = 'help';
}
setN('v139-intents', d.intents?.count);
setN('v139-skills', d.skills?.TOTAL);
setN('v139-brains', d.brains?.count);
setN('v139-doctrines', d.doctrines?.count);
setN('v139-dashboards', d.dashboards?.count);
// V140-TRUTH-HEALTH: autonomy + NonReg badges
const autEl = document.getElementById('v140-autonomy');
if (autEl && d.autonomy_score !== undefined) {
const lvl = d.autonomy_level || '';
const score = d.autonomy_score;
const badgeColor = score >= 100 ? '#10b981' : score >= 80 ? '#f59e0b' : '#ef4444';
autEl.innerHTML = '&middot; <strong style="color:' + badgeColor + '">' + score + '%</strong> ' + (lvl ? '<span style="color:' + badgeColor + ';font-size:9px">' + lvl + '</span>' : 'autonomy');
}
// V140B-NR-LIVE: fetch fresh NR from l99-honest.php (truth-registry may be stale snapshot)
try {
const nrResp = await fetch('/api/l99-honest.php', {cache: 'no-store'});
if (nrResp.ok) {
const nrData = await nrResp.json();
const nrEl = document.getElementById('v140-nonreg');
if (nrEl && nrData.combined) {
const s = nrData.combined.pass;
const t = nrData.combined.total;
const pct = t ? Math.round((s/t)*100) : 0;
const badgeColor = pct >= 99 ? '#10b981' : pct >= 95 ? '#f59e0b' : '#ef4444';
nrEl.innerHTML = '&middot; NR <strong style="color:' + badgeColor + '">' + s + '/' + t + '</strong>';
nrEl.title = 'Non-régression live: ' + s + '/' + t + ' (' + pct + '%) · source: /api/l99-honest.php · ts: ' + (nrData.ts || 'n/a');
}
}
} catch(_) {
// fallback: use snapshot from truth-registry
const nrEl = document.getElementById('v140-nonreg');
if (nrEl && d.nonreg) {
const s = d.nonreg.score, t = d.nonreg.total, pct = t ? Math.round((s/t)*100) : 0;
const badgeColor = pct >= 99 ? '#10b981' : pct >= 95 ? '#f59e0b' : '#ef4444';
nrEl.innerHTML = '&middot; NR <strong style="color:' + badgeColor + '">' + s + '/' + t + '</strong> <span style="color:var(--mu);font-size:8px">(snapshot)</span>';
nrEl.title = 'NR (snapshot truth-registry, peut être stale): ' + s + '/' + t + ' (' + pct + '%)';
}
}
} catch (_) {}
}
__v139LoadTruthStrip();
async function __v135UpdateHealthBanner(){
const kpi = document.getElementById('v135-kpi-live');
if (!kpi) return;
try {
const r = await fetch('/api/screens-health.json', {cache: 'no-store'});
if (!r.ok) return;
const d = await r.json();
if (!d || !d.counts) return;
const c = d.counts;
const total = d.total || 0;
const up = c.UP || 0;
const broken = c.BROKEN || 0;
const down = c.DOWN || 0;
const phantom = c.PHANTOM || 0;
const active = total - phantom;
const healthPct = active ? Math.round((up / active) * 100) : 0;
const dot = (broken + down === 0) ? '\u{1F7E2}' : (broken + down < 20 ? '\u{1F7E1}' : '\u{1F534}');
// V137: compute scan age
let ageStr = '';
if (d.generated_at) {
const scanMs = new Date(d.generated_at).getTime();
if (!isNaN(scanMs)) {
const ageMin = Math.floor((Date.now() - scanMs) / 60000);
ageStr = ageMin < 1 ? ' &middot; <span style="color:#10b981">just now</span>'
: ageMin < 60 ? ' &middot; ' + ageMin + 'min ago'
: ' &middot; ' + Math.floor(ageMin/60) + 'h ago';
}
}
kpi.innerHTML = 'All-IA Hub &middot; ' + dot + ' ' + healthPct + '% (' + up + ' UP &middot; ' + broken + ' broken)' + ageStr;
kpi.title = 'Platform health: ' + up + ' UP / ' + broken + ' BROKEN / ' + down + ' DOWN / ' + phantom + ' phantom (total ' + total + ')\nScan: ' + (d.generated_at || 'n/a') + '\nClick: détail · Bouton \u21BA: refresh';
} catch (_) {}
}
/* V138-COPY-URL: copy current URL with hash state to clipboard */
async function __v138CopyShareURL(){
const url = window.location.href;
const btn = document.getElementById('v138-copy-btn');
try {
await navigator.clipboard.writeText(url);
if (btn) {
const orig = btn.innerHTML;
btn.innerHTML = '&#10004; Copied!';
btn.style.color = '#10b981';
btn.style.borderColor = '#10b981';
setTimeout(() => { btn.innerHTML = orig; btn.style.color = ''; btn.style.borderColor = ''; }, 1500);
}
} catch (err) {
if (btn) {
btn.innerHTML = '&#10007; Error';
btn.style.color = '#ef4444';
setTimeout(() => { btn.innerHTML = '&#128279; Share'; btn.style.color = ''; }, 1500);
}
}
}
/* V137-REFRESH: manual refresh with spin animation */
async function __v137RefreshHealth(){
const btn = document.getElementById('v137-refresh-btn');
if (btn) {
btn.style.animation = 'v137spin 0.8s linear infinite';
btn.style.color = 'var(--vl)';
btn.disabled = true;
}
await __v135UpdateHealthBanner();
if (btn) {
setTimeout(() => {
btn.style.animation = '';
btn.style.color = '';
btn.disabled = false;
}, 400);
}
}
/* Initial load */
__v135UpdateHealthBanner();
setTimeout(() => {
const btn = document.querySelector('[data-view="dashboards"]');
if (btn) btn.addEventListener('click', () => { if (!__dashData) loadDashboards(); });
// V119-SEARCH: re-render on search/sort change
const search = document.getElementById('dash-search');
const sort = document.getElementById('dash-sort');
const rerender = () => {
if (!__dashData) return;
const activeCat = document.querySelector('.dash-filter.on')?.getAttribute('data-cat') || 'all';
renderDashGrid(__dashData.dashboards, activeCat);
};
if (search) search.addEventListener('input', rerender);
// V128-SCROLL-TOP: toggle button visibility based on scroll
const scrollBtn = document.getElementById('dash-scroll-top');
if (scrollBtn) {
const updateScrollBtn = () => {
const isDashVisible = document.getElementById('v-dashboards')?.classList.contains('on');
const scrolledFar = window.scrollY > 400;
scrollBtn.style.display = (isDashVisible && scrolledFar) ? 'block' : 'none';
};
window.addEventListener('scroll', updateScrollBtn, { passive: true });
// Also hide when switching tabs
document.querySelectorAll('.tab').forEach(t => t.addEventListener('click', () => setTimeout(updateScrollBtn, 100)));
}
if (sort) {
// V129: restore sort from URL on init
const savedSort = __dashHashGet('sort');
if (savedSort && ['name','size','mtime','category'].includes(savedSort)) sort.value = savedSort;
sort.addEventListener('change', () => { __dashHashSet('sort', sort.value === 'name' ? '' : sort.value); rerender(); });
}
// V120-KEYBOARD: Cmd+K / Ctrl+K focus search when dashboards tab is visible
document.addEventListener('keydown', (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
const viewDash = document.getElementById('v-dashboards');
if (viewDash && viewDash.classList.contains('on')) {
e.preventDefault();
const s = document.getElementById('dash-search');
if (s) { s.focus(); s.select(); }
}
}
if (e.key === 'Escape') {
const s = document.getElementById('dash-search');
if (s && document.activeElement === s && s.value) {
s.value = '';
s.dispatchEvent(new Event('input'));
}
}
});
}, 500);
// V114-TRAINING-LIVE: fetch real training stats
async function loadTrainingStats(){
try{
const r = await fetch('/api/training-status.php');
const d = await r.json();
const set = (id,val) => { const e=document.getElementById(id); if(e) e.textContent = val; };
set('t-qv', d.qdrant?.vectors || '?');
set('t-wi', d.wiki?.entries || '?');
set('t-vf', d.vault?.files || '?');
set('t-ag', d.catalog?.total || '?');
set('t-int', d.intents?.wired || '?');
set('t-gold', d.vault?.gold || '?');
}catch(e){
console.error('[training-stats]',e);
}
}
setInterval(()=>{ if(document.getElementById('v-train').classList.contains('on')) loadTrainingStats(); }, 30000);
setTimeout(loadTrainingStats, 1500);
// V113-ROUTER-ACTIVITY: load router matches
async function loadRouterActivity(){
try{
const r = await fetch('/api/router-activity.php?k=WEVADS2026&limit=50');
const d = await r.json();
const t = document.getElementById('router-total');
const rc = document.getElementById('router-recent');
if(t) t.textContent = d.total_log_lines || 0;
if(rc) rc.textContent = d.count || 0;
const pbox = document.getElementById('router-patterns');
if(pbox && d.by_pattern){
pbox.innerHTML = '';
for(const [p, n] of Object.entries(d.by_pattern)){
const div = document.createElement('div');
div.style.cssText = 'padding:3px 6px;background:var(--bg3);border-radius:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
div.title = p;
div.innerHTML = '<b style="color:var(--cy)">'+n+'x</b> <span style="color:var(--mu)">'+p.substring(0,40)+'...</span>';
pbox.appendChild(div);
}
}
const mbox = document.getElementById('router-recent-msgs');
if(mbox && d.matches){
mbox.innerHTML = d.matches.map(m =>
'<div>&gt; <span style="color:var(--dm)">['+m.ts.substring(11,16)+']</span> '+m.msg.substring(0,140)+'</div>'
).join('');
}
}catch(e){
console.error('[router-activity]',e);
}
}
// auto-load when training tab active
setInterval(()=>{ if(document.getElementById('v-train').classList.contains('on')) loadRouterActivity(); }, 20000);
setTimeout(loadRouterActivity, 2000);
// Live stats
async function refreshStats(){
try{
const r=await fetch('/api/l99-honest.php?compact=1',{cache:'no-store'});
const d=await r.json();
if(d.nonreg) document.getElementById('h-nr').textContent=d.nonreg;
}catch(e){}
}
refreshStats();
setInterval(refreshStats,60000);
</script>
<!-- OPUS_v932f_DROID_LINK -->
<a href="/wevia-ia/droid.html" id="opus-droid-link" title="WEDROID v3.2" style="position:fixed;bottom:20px;right:20px;padding:7px 14px;background:rgba(16,185,129,0.15);color:#10b981;text-decoration:none;border-radius:18px;font-size:12px;font-weight:600;border:1px solid rgba(16,185,129,0.4);backdrop-filter:blur(10px);z-index:9997">Droid</a>
<script src="/api/a11y-auto-enhancer.js" defer></script>
<!-- V142-FOOTER-STRIP: ecosystem health in footer (aligned with WTP) -->
<div id="v142-footer" style="position:fixed;bottom:0;left:0;right:0;background:rgba(0,0,0,0.75);border-top:1px solid var(--bd);padding:4px 12px;display:flex;gap:14px;align-items:center;font-size:9px;color:var(--mu);z-index:40;backdrop-filter:blur(8px);font-family:ui-monospace,monospace">
<span id="v142-score" style="font-weight:600"></span>
<span id="v142-l99"></span>
<span id="v142-tools"></span>
<span id="v142-docker"></span>
<span id="v142-providers"></span>
<span id="v142-qdrant"></span>
<span id="v142-ollama"></span>
<span style="margin-left:auto;color:#00d4b4;font-size:9px"><a href="/wevia-unified-hub.html" style="color:inherit;text-decoration:none" title="Truth Hub">Truth &rarr;</a></span>
</div>
<script>(function(){var p=window.location.pathname;var pub=["/","/index.html","/wevia.html","/wevia-widget.html","/enterprise-model.html","/wevia","/login","/register.html","/agents-archi.html","/wevia-meeting-rooms.html","/director-center.html","/director-chat.html","/l99-brain.html","/agents-fleet.html","/value-streaming.html","/architecture.html","/openclaw.html","/l99-saas.html","/admin-saas.html","/agents-goodjob.html","/ai-benchmark.html","/oss-discovery.html","/paperclip.html","/agents-3d.html","/agents-alive.html","/agents-enterprise.html","/agents-hd.html","/agents-iso3d.html","/agents-sim.html","/agents-valuechain.html","/avatar-picker.html"];var isPub=pub.indexOf(p)>=0||p.indexOf("/products/")===0||p.indexOf("/solutions/")===0||p.indexOf("/blog/")===0||p.indexOf("/service/")===0||p.indexOf("/marketplace")===0||p.indexOf("/contact")===0||p.indexOf("/tarifs")===0||p.indexOf("/news")===0;if(isPub||document.getElementById("weval-gl"))return;var a=document.createElement("a");a.id="weval-gl";a.href="/logout";a.textContent="Logout";a.style.cssText="position:fixed;top:10px;right:12px;z-index:99990;padding:5px 10px;background:rgba(30,30,50,0.7);color:rgba(200,210,230,0.8);border:1px solid rgba(100,100,140,0.3);border-radius:6px;font:500 11px system-ui,sans-serif;text-decoration:none;opacity:0.6;cursor:pointer;backdrop-filter:blur(6px);transition:all .15s";a.onmouseover=function(){this.style.opacity="1";this.style.background="rgba(239,68,68,0.85)";this.style.color="white"};a.onmouseout=function(){this.style.opacity="0.6";this.style.background="rgba(30,30,50,0.7)";this.style.color="rgba(200,210,230,0.8)"};document.body.appendChild(a)})()</script><script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
<script src="/api/weval-feature-tracker.js" defer></script>
<script src="/api/ambre-universal-chat.js" defer></script>
<!-- WAVE 265 · Factory pill cross-page (injected, position mesurée zéro overlap) -->
<a id="w265-factory-cross" href="/wevia-multiagent-dashboard.html" title="Factory Health Monitor (30 agents)"
style="position:fixed;top:12px;left:12px;padding:6px 12px;border-radius:14px;background:linear-gradient(135deg,rgba(34,211,238,.2),rgba(168,85,247,.15));border:1px solid rgba(34,211,238,.4);color:#67e8f9;font-size:11px;font-weight:700;text-decoration:none;display:inline-flex;align-items:center;gap:6px;z-index:9999;backdrop-filter:blur(10px);box-shadow:0 2px 8px rgba(0,0,0,.4)">
<span>🏭</span>
<span id="w265-factory-txt">Factory: ...</span>
</a>
<script>
/* w265 Factory auto-load */
(function(){
async function refresh(){
try {
const r = await fetch('/api/wevia-v83-business-kpi.php?action=summary', {cache:'no-store'}).then(r=>r.json()).catch(()=>null);
const el = document.getElementById('w265-factory-txt');
if(el && r && r.summary){
const s = r.summary;
const pct = s.data_completeness_pct || 0;
el.textContent = `Factory: ${pct}% (${s.ok || 0}/${s.total_kpis || 0})`;
}
} catch(e){ console.log('[w265] factory check err', e); }
}
refresh();
setInterval(refresh, 60000);
})();
</script>
<!-- /WAVE 265 Factory pill cross-page -->
</body>
<!-- WEVIA-LIVE-OPS-LINK-AIH-v1 -->
<a href="/weval-live-ops.html" style="position:fixed;bottom:16px;right:16px;padding:10px 16px;background:linear-gradient(135deg,#e94560,#c03350);color:#fff;border-radius:8px;font-weight:700;font-size:13px;text-decoration:none;z-index:9999;box-shadow:0 4px 12px rgba(233,69,96,0.4)">Live Ops</a>
<!-- /WEVIA-LIVE-OPS-LINK-AIH-v1 -->
</html>