Files
html/weval-live-ops.html
Opus c7994d48be
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
phase45 doctrine 183 inject 29 pages PRIO3 - 121 pages UX total
29 pages PRIO3 enrichies via inject-d60-direct.py:
registries: wtp-orphans-registry agents-unified-registry ia-sovereign-registry
hubs: wevia-hub vsm-hub wevads-hub weval-data-hub wevia-unified-hub tools-hub toolhub
dashboards: office-365-dashboard-live crm-pipeline-live orphans-dashboard
           medreach-dashboard wevia-director-dashboard security-dashboard
           wevia-memory-dashboard
monitors: sso-monitor monitoring
centers: mega-command-center trust-center
studios: bpmn-studio-live admin-saas
others: ethica-hcp-manager ops-screens-live vsm-pipelines lean-6sigma
        office-admins weval-live-ops

Cumul session Opus:
- 121 pages UX doctrine 60 (92 + 29)
- 31 tags Opus (avec phase45)
- 28 doctrines vault (146-183)

Handler inject-d60-direct.py prouve robuste sur 65+ pages consecutives.
ZERO regression. ZERO ecrasement. NR 153/153 invariant.
2026-04-24 14:25:37 +02:00

512 lines
24 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>WEVAL Live Ops — Temps réel agents + Paperclip + Social</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<style>
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@600;800;900&display=swap');
*{box-sizing:border-box;margin:0;padding:0}
body{background:#eaeff7;background-image:radial-gradient(#c8d4e4 0.4px,transparent 0.4px),linear-gradient(180deg,#e0e8f4 0%,#eaeff7 100%);background-size:14px 14px,100% 100%;overflow-y:auto;font-family:'Nunito',sans-serif;color:#2a2a4a;min-height:100vh}
#live-stats{position:fixed;top:0;left:0;right:0;z-index:9999;display:flex;justify-content:center;align-items:center;gap:14px;padding:3px 10px;height:22px;background:linear-gradient(135deg,#1e293b,#0f172a);box-shadow:0 1px 3px rgba(0,0,0,.15)}
#live-stats > div{font:700 10px sans-serif}
.ls-g{color:#4ade80}.ls-b{color:#60a5fa}.ls-y{color:#fbbf24}.ls-p{color:#a78bfa}.ls-r{color:#f87171}.ls-e{color:#34d399}.ls-c{color:#22d3ee}
@keyframes lp{0%,100%{opacity:1}50%{opacity:.3}}
.pulse{width:6px;height:6px;border-radius:50%;background:#4ade80;animation:lp 2s infinite}
#hud{position:fixed;top:26px;left:0;right:0;height:28px;background:rgba(255,255,255,0.9);backdrop-filter:blur(12px);border-bottom:1px solid #c8d8e8;z-index:10;display:flex;align-items:center;padding:0 12px;font-size:.7rem;gap:10px}
#hud b{color:#e94560;font-weight:900;font-size:.8rem}
#hud .g{color:#059669;font-weight:800}
#hud .ts{color:#64748b;margin-left:4px;font-weight:600}
#hud input{margin-left:auto;font:600 11px Nunito;padding:3px 8px;border:1px solid #c8d8e8;border-radius:5px;width:160px;background:#fff8}
#nav{position:fixed;top:58px;left:0;right:0;display:flex;justify-content:center;gap:6px;padding:5px;z-index:10;background:#e4ecf6dd;border-bottom:1px solid #c8d8e8}
#nav a{padding:3px 9px;border-radius:5px;font-size:10px;font-weight:700;text-decoration:none;background:#fff8;color:#5a6a80;border:1px solid #c8d8e8;transition:all .2s}
#nav a:hover{background:#fff;color:#e94560;transform:translateY(-1px)}
#nav a.ac{background:#059669;color:#fff;border-color:#059669}
main{padding:94px 16px 80px;max-width:1600px;margin:0 auto;display:grid;grid-template-columns:repeat(12,1fr);gap:14px}
.card{background:#fff;border-radius:12px;box-shadow:0 2px 8px rgba(42,42,74,.06);border:1px solid #d8e4f0;padding:16px;transition:all .2s;position:relative}
.card:hover{box-shadow:0 4px 16px rgba(42,42,74,.10);transform:translateY(-1px)}
.card h3{font-size:.7rem;text-transform:uppercase;letter-spacing:1.5px;color:#64748b;font-weight:800;margin-bottom:12px;display:flex;align-items:center;gap:8px}
.card h3 .live{width:7px;height:7px;border-radius:50%;background:#22c55e;animation:lp 2s infinite}
.card .v{font-size:2rem;font-weight:900;color:#2a2a4a;line-height:1}
.card .s{font-size:.7rem;color:#64748b;font-weight:600;margin-top:4px}
.card .bar{height:6px;background:#eef2f7;border-radius:3px;margin-top:10px;overflow:hidden}
.card .bar > div{height:100%;background:linear-gradient(90deg,#059669,#10b981);transition:width .5s}
.c3{grid-column:span 3}.c4{grid-column:span 4}.c6{grid-column:span 6}.c8{grid-column:span 8}.c12{grid-column:span 12}
@media(max-width:1200px){.c3{grid-column:span 4}.c4{grid-column:span 6}.c6{grid-column:span 12}.c8{grid-column:span 12}}
@media(max-width:640px){main{grid-template-columns:1fr}.card{grid-column:1}}
/* Paperclip queue */
.q-item{padding:10px 12px;background:#f8fafc;border-left:3px solid #3b82f6;border-radius:6px;margin-bottom:8px;font-size:.78rem;transition:all .15s}
.q-item:hover{background:#eef4fc;transform:translateX(2px)}
.q-item .qh{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}
.q-item .qs{padding:2px 8px;border-radius:10px;font-size:.65rem;font-weight:800;text-transform:uppercase}
.qs-queued{background:#fef3c7;color:#92400e}
.qs-running{background:#dbeafe;color:#1e40af;animation:lp 1s infinite}
.qs-completed{background:#d1fae5;color:#065f46}
.qs-failed{background:#fee2e2;color:#991b1b}
.q-item .qr{color:#475569;line-height:1.4;word-break:break-word}
.q-item .qm{font-size:.6rem;color:#94a3b8;margin-top:3px}
.q-item.qs-completed{border-left-color:#10b981}
.q-item.qs-failed{border-left-color:#ef4444}
.q-item.qs-running{border-left-color:#3b82f6}
/* Agents feed */
.ag-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:8px;max-height:320px;overflow-y:auto;padding:4px}
.ag{padding:8px;background:#f8fafc;border-radius:8px;border:1px solid #e2e8f0;font-size:.7rem;cursor:pointer;transition:all .15s;position:relative}
.ag:hover{background:#fff;border-color:#059669;transform:translateY(-2px);box-shadow:0 2px 6px rgba(5,150,105,.15)}
.ag .an{font-weight:800;color:#1e293b;margin-bottom:2px;font-size:.72rem}
.ag .ac{font-size:.55rem;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px}
.ag .ad{width:6px;height:6px;border-radius:50%;background:#22c55e;position:absolute;top:8px;right:8px}
/* Activity stream */
.act-stream{max-height:380px;overflow-y:auto;font-size:.75rem}
.act-line{padding:7px 10px;border-bottom:1px solid #eef2f7;display:flex;align-items:flex-start;gap:8px;line-height:1.4}
.act-line:last-child{border:none}
.act-line.new{background:#fef9c3;animation:flash 1.5s ease-out}
@keyframes flash{0%{background:#fde68a}100%{background:#fff}}
.act-ico{font-size:.9rem;flex-shrink:0}
.act-txt{flex:1;color:#334155}
.act-ts{font-size:.62rem;color:#94a3b8;font-weight:600;flex-shrink:0}
/* Trigger form (floating) */
.trigger-btn{position:fixed;bottom:20px;right:20px;width:60px;height:60px;border-radius:50%;background:linear-gradient(135deg,#e94560,#c03350);border:none;color:#fff;font-size:24px;cursor:pointer;box-shadow:0 6px 20px rgba(233,69,96,.4);z-index:100;transition:all .2s;font-weight:900}
.trigger-btn:hover{transform:scale(1.1);box-shadow:0 8px 25px rgba(233,69,96,.6)}
#modal{display:none;position:fixed;inset:0;background:#000a;z-index:1000;align-items:center;justify-content:center;padding:20px}
#modal.on{display:flex}
#modal .box{background:#fff;border-radius:14px;padding:22px;max-width:520px;width:100%;max-height:90vh;overflow-y:auto;box-shadow:0 12px 40px rgba(0,0,0,.3)}
#modal h2{font-size:1.1rem;margin-bottom:14px;color:#e94560;font-weight:900}
#modal label{display:block;font-size:.78rem;font-weight:800;margin:10px 0 4px;color:#334155}
#modal input,#modal textarea,#modal select{width:100%;padding:9px 11px;border:1.5px solid #c8d8e8;border-radius:7px;font:500 12px Nunito;background:#f8fafc}
#modal input:focus,#modal textarea:focus{outline:none;border-color:#059669;background:#fff}
#modal textarea{resize:vertical;min-height:80px}
#modal .btns{display:flex;gap:8px;margin-top:14px}
#modal .btns button{flex:1;padding:10px;border-radius:8px;border:none;font:800 12px Nunito;cursor:pointer;transition:all .15s}
#modal .btns .go{background:#059669;color:#fff}
#modal .btns .go:hover{background:#047857}
#modal .btns .cx{background:#e5e7eb;color:#334155}
/* Chart mini */
.mini-chart{height:60px;margin-top:8px;display:flex;align-items:flex-end;gap:3px}
.mini-chart > div{flex:1;background:linear-gradient(180deg,#3b82f6,#1e40af);border-radius:3px 3px 0 0;transition:height .5s;min-height:3px}
.mini-chart > div.run{background:linear-gradient(180deg,#22c55e,#059669)}
.mini-chart > div.fail{background:linear-gradient(180deg,#ef4444,#b91c1c)}
@media(prefers-color-scheme:dark){
body{background:#0f172a!important;color:#e2e8f0!important;background-image:radial-gradient(#1e293b 0.4px,transparent 0.4px),linear-gradient(180deg,#0f172a 0%,#1e293b 100%)!important}
#hud{background:#1a1a2edd!important;border-color:#2a2a4a!important}
#nav{background:#0f172add!important;border-color:#2a2a4a!important}
#nav a{background:#1e293b88!important;color:#94a3b8!important;border-color:#334155!important}
#nav a.ac{background:#059669!important;color:#fff!important}
.card{background:#1e293b!important;border-color:#334155!important;color:#e2e8f0!important}
.card h3{color:#94a3b8!important}
.card .v{color:#f1f5f9!important}
.q-item{background:#0f172a!important;color:#e2e8f0!important}
.ag{background:#0f172a!important;border-color:#334155!important;color:#e2e8f0!important}
.ag .an{color:#f1f5f9!important}
.act-line{border-color:#334155!important}
.act-txt{color:#cbd5e1!important}
#modal .box{background:#1e293b!important;color:#e2e8f0!important}
#modal input,#modal textarea,#modal select{background:#0f172a!important;border-color:#334155!important;color:#e2e8f0!important}
}
</style>
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-142458 -->
<style id="doctrine60-ux-direct">
/* DOCTRINE-60-UX-ENRICH injected-direct */
body::before {
content: '';
position: fixed;
top: 0; left: 0; width: 100vw; height: 100vh;
background: radial-gradient(circle at 50% 50%, rgba(100,180,255,0.08), transparent 60%);
pointer-events: none;
z-index: -1;
}
.card, .kpi, .panel, .btn {
transition: all 0.3s cubic-bezier(0.2,0,0.1,1);
}
.card:hover, .kpi:hover, .panel:hover {
box-shadow: 0 4px 20px rgba(100,180,255,0.2);
border-color: rgba(100,180,255,0.5);
}
@keyframes pulseD60 {
0%,100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.7; transform: scale(1.05); }
}
.pulse, .live-indicator, .active, .online {
animation: pulseD60 3s ease-in-out infinite;
}
.modal, .chat, .speech, .overlay {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
.enter-stagger {
animation: enterStagD60 0.5s cubic-bezier(0.2,0,0.1,1) forwards;
}
@keyframes enterStagD60 {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<div id="live-stats">
<div class="ls-g">🤖 <span id="ls-ag">-</span> Agents</div>
<div class="ls-c">📋 <span id="ls-queued">-</span> Queued</div>
<div class="ls-b">🏃 <span id="ls-running">-</span> Running</div>
<div class="ls-e"><span id="ls-done">-</span> Done</div>
<div class="ls-r"><span id="ls-fail">-</span> Fail</div>
<div class="ls-y">💓 <span id="ls-hb">-</span> Heartbeats/h</div>
<div class="ls-p">🧠 <span id="ls-nr">-</span></div>
<div class="pulse"></div>
</div>
<div id="hud">
<b>WEVAL Live Ops</b>
<span class="g">● LIVE</span>
<span class="ts" id="last-sync">sync —</span>
<input id="q" placeholder="🔍 Rechercher agent / action..." oninput="applySearch()">
</div>
<div id="nav">
<a href="/">🏠 Home</a>
<a href="/wevia-master.html">⚡ WEVIA Master</a>
<a href="/wevia-orchestrator.html">🎛️ Orchestrator</a>
<a href="/wevia-meeting-rooms.html">🏛️ Meeting Rooms</a>
<a href="/cloudbot-social.html">☁️ Cloudbot Social</a>
<a href="/all-ia-hub.html">🌐 All IA Hub</a>
<a href="/weval-live-ops.html" class="ac">📊 Live Ops</a>
<a href="/weval-technology-platform.html">🏭 WTP</a>
</div>
<main>
<!-- KPIs principaux -->
<div class="card c3">
<h3><span class="live"></span>Agents actifs</h3>
<div class="v" id="kpi-agents">-</div>
<div class="s" id="kpi-agents-sub">Catalog Paperclip + IA</div>
<div class="bar"><div id="bar-agents" style="width:0%"></div></div>
</div>
<div class="card c3">
<h3><span class="live"></span>Paperclip Queue</h3>
<div class="v" id="kpi-queue">-</div>
<div class="s" id="kpi-queue-sub">Total actions enregistrées</div>
<div class="mini-chart" id="mini-chart"></div>
</div>
<div class="card c3">
<h3><span class="live"></span>NR / L99</h3>
<div class="v" id="kpi-nr">-</div>
<div class="s" id="kpi-nr-sub">Zero regression session 42+</div>
<div class="bar"><div id="bar-nr" style="width:100%"></div></div>
</div>
<div class="card c3">
<h3><span class="live"></span>Ecosystem</h3>
<div class="v" id="kpi-eco">-</div>
<div class="s" id="kpi-eco-sub">Tools wired · Score A+</div>
<div class="bar"><div id="bar-eco" style="width:100%"></div></div>
</div>
<!-- Paperclip Live Queue -->
<div class="card c6">
<h3><span class="live"></span>🚀 Paperclip — Queue Temps Réel</h3>
<div id="queue-list" style="max-height:360px;overflow-y:auto">
<div style="text-align:center;color:#94a3b8;padding:30px;font-size:.8rem">Chargement...</div>
</div>
</div>
<!-- Activity Stream -->
<div class="card c6">
<h3><span class="live"></span>📰 Activity Stream</h3>
<div class="act-stream" id="act-stream">
<div class="act-line"><span class="act-ico"></span><span class="act-txt">Connexion au flux temps réel...</span></div>
</div>
</div>
<!-- Agents grid -->
<div class="card c8">
<h3><span class="live"></span>🤖 Agents Catalog (<span id="ag-count">-</span>)</h3>
<div class="ag-grid" id="agents-grid">
<div style="grid-column:1/-1;text-align:center;color:#94a3b8;padding:20px">Chargement 726 agents...</div>
</div>
</div>
<!-- Paperclip agents actifs -->
<div class="card c4">
<h3><span class="live"></span>⚙️ Paperclip Agents</h3>
<div id="pc-agents" style="font-size:.78rem">
<div style="text-align:center;color:#94a3b8;padding:20px">Chargement...</div>
</div>
</div>
</main>
<button class="trigger-btn" onclick="openTrigger()" title="Déclencher Paperclip">🚀</button>
<!-- Modal trigger -->
<div id="modal">
<div class="box">
<h2>🚀 Déclencher Paperclip</h2>
<label>Source</label>
<select id="mp-source">
<option value="cloudbot-social">Cloudbot Social</option>
<option value="meeting-rooms">Meeting Rooms</option>
<option value="weval-social">Live Ops Direct</option>
</select>
<label>Action (résumé court)</label>
<input id="mp-action" placeholder="ex: analyser logs nginx 500">
<label>Prompt détaillé pour Paperclip</label>
<textarea id="mp-prompt" placeholder="Instructions précises (fichier, commande, vérification...)"></textarea>
<label>Agents concernés (séparés par virgule)</label>
<input id="mp-agents" placeholder="WEVIA Master, WEDROID, Ethica">
<div class="btns">
<button class="cx" onclick="closeTrigger()">Annuler</button>
<button class="go" onclick="submitTrigger()">Déclencher →</button>
</div>
</div>
</div>
<script>
var AGENTS_CAT=[], ALL_QUEUE=[], PC_AGENTS=[], SEARCH="", LAST_IDS=new Set();
var REFRESH_MS=5000; // 5s
function ts(){ return new Date().toLocaleTimeString("fr-FR",{hour12:false}); }
async function loadAll(){
try{
// Parallèle : 5 fetches
var results = await Promise.allSettled([
fetch("/api/agents-catalog-api.php").then(r=>r.json()),
fetch("/api/paperclip-bridge.php?action=stats").then(r=>r.json()),
fetch("/api/paperclip-bridge.php?action=list&limit=15").then(r=>r.json()),
fetch("/api/nonreg-api.php?cat=all").then(r=>r.json()).catch(function(){return null}),
fetch("/api/ecosystem-health.php").then(r=>r.json()).catch(function(){return null})
]);
var cat=results[0].status==="fulfilled"?results[0].value:null;
var stats=results[1].status==="fulfilled"?results[1].value:null;
var list=results[2].status==="fulfilled"?results[2].value:null;
var nr=results[3].status==="fulfilled"?results[3].value:null;
var eco=results[4].status==="fulfilled"?results[4].value:null;
if(cat) updateAgents(cat);
if(stats && stats.stats) updateStats(stats.stats);
if(list && list.actions) updateQueue(list.actions);
if(nr) updateNR(nr);
if(eco) updateEco(eco);
document.getElementById("last-sync").textContent = "sync " + ts();
}catch(e){
addActivity("⚠️", "Erreur sync: " + e.message, true);
}
}
function updateAgents(cat){
AGENTS_CAT = cat.agents || [];
document.getElementById("ls-ag").textContent = cat.total || AGENTS_CAT.length;
document.getElementById("kpi-agents").textContent = cat.total || AGENTS_CAT.length;
document.getElementById("ag-count").textContent = AGENTS_CAT.length;
var cats = Object.keys(cat.categories||{});
document.getElementById("kpi-agents-sub").textContent = cats.length + " catégories";
var maxCat = Math.max.apply(null, Object.values(cat.categories||{1:1}));
document.getElementById("bar-agents").style.width = Math.min(100, 100 * (cat.total||1) / 1000) + "%";
renderAgentsGrid();
}
function renderAgentsGrid(){
var grid = document.getElementById("agents-grid");
var filtered = SEARCH ? AGENTS_CAT.filter(function(a){
return (a.name||"").toLowerCase().includes(SEARCH) || (a.desc||"").toLowerCase().includes(SEARCH);
}) : AGENTS_CAT;
var sliced = filtered.slice(0, 60);
grid.innerHTML = sliced.map(function(a){
return '<div class="ag" title="'+escapeHtml(a.desc||"")+'" onclick="window.location.href=\'/cloudbot-social.html\'">'+
'<div class="ad"></div>'+
'<div class="an">'+escapeHtml((a.name||"?").substring(0,18))+'</div>'+
'<div class="ac">'+escapeHtml(a.cat||"")+'</div>'+
'</div>';
}).join("") + (filtered.length > 60 ? '<div style="grid-column:1/-1;text-align:center;color:#94a3b8;font-size:.7rem;padding:8px">... +' + (filtered.length-60) + ' agents</div>' : '');
}
function updateStats(s){
document.getElementById("kpi-queue").textContent = s.total || 0;
document.getElementById("ls-queued").textContent = s.queued || 0;
document.getElementById("ls-running").textContent = s.running || 0;
document.getElementById("ls-done").textContent = s.completed || 0;
document.getElementById("ls-fail").textContent = s.failed || 0;
document.getElementById("kpi-queue-sub").textContent = (s.completed||0) + " done · " + (s.queued||0) + " attente · " + (s.failed||0) + " fail";
// Mini chart : 4 bars = queued / running / completed / failed
var total = Math.max(1, s.total||1);
var chart = document.getElementById("mini-chart");
chart.innerHTML = [
{c:"", h:Math.max(3, 60*(s.queued||0)/total), t:"Queued"},
{c:"run", h:Math.max(3, 60*(s.running||0)/total), t:"Running"},
{c:"", h:Math.max(3, 60*(s.completed||0)/total), t:"Completed", c2:"background:linear-gradient(180deg,#22c55e,#059669)"},
{c:"fail", h:Math.max(3, 60*(s.failed||0)/total), t:"Failed"}
].map(function(b){
return '<div class="'+b.c+'" style="height:'+b.h+'px'+(b.c2?";"+b.c2:"")+'" title="'+b.t+': '+((s[b.t.toLowerCase()]||0))+'"></div>';
}).join("");
}
function updateQueue(actions){
ALL_QUEUE = actions;
var list = document.getElementById("queue-list");
if(!actions.length){
list.innerHTML = '<div style="text-align:center;color:#94a3b8;padding:30px;font-size:.8rem">📭 Aucune action. Cliquez 🚀 pour déclencher.</div>';
return;
}
// Detecter nouvelles actions pour activity stream
actions.forEach(function(a){
if(!LAST_IDS.has(a.id)){
LAST_IDS.add(a.id);
if(LAST_IDS.size > 1) { // ignorer la première charge
addActivity("🚀", "Nouvelle action [" + (a.status||"queued") + "] : " + (a.reason||"").substring(0,80), true);
}
}
});
list.innerHTML = actions.map(function(a){
var st = a.status||"queued";
return '<div class="q-item qs-'+st+'">'+
'<div class="qh">'+
'<span class="qs qs-'+st+'">'+st+'</span>'+
'<span style="font-size:.6rem;color:#94a3b8;font-weight:700">'+(a.requested_at||"").substring(11,19)+'</span>'+
'</div>'+
'<div class="qr">'+escapeHtml((a.reason||"").substring(0,160))+'</div>'+
'<div class="qm">id: '+(a.id||"").substring(0,8)+' · source: '+(a.source||"")+'</div>'+
'</div>';
}).join("");
}
function updateNR(nr){
var p = nr.pass ?? nr.passed ?? 0;
var t = nr.total ?? 0;
document.getElementById("kpi-nr").textContent = p + "/" + t;
document.getElementById("ls-nr").textContent = "NR " + p + "/" + t;
document.getElementById("kpi-nr-sub").textContent = t ? (100*p/t).toFixed(0) + "% pass" : "loading";
document.getElementById("bar-nr").style.width = (t ? 100*p/t : 0) + "%";
}
function updateEco(eco){
document.getElementById("kpi-eco").textContent = (eco.score||"?");
var pct = eco.percent||0;
document.getElementById("kpi-eco-sub").textContent = (eco.tools_wired||0) + " tools · L99 " + ((eco.l99||{}).pass||0) + "/" + ((eco.l99||{}).total||0);
document.getElementById("bar-eco").style.width = pct + "%";
}
function applySearch(){
SEARCH = (document.getElementById("q").value||"").toLowerCase();
renderAgentsGrid();
}
function addActivity(ico, txt, flashIt){
var stream = document.getElementById("act-stream");
// Remove placeholder
var placeholders = stream.querySelectorAll(".act-line");
if(placeholders.length===1 && placeholders[0].textContent.includes("Connexion")) placeholders[0].remove();
var line = document.createElement("div");
line.className = "act-line" + (flashIt?" new":"");
line.innerHTML = '<span class="act-ico">'+ico+'</span><span class="act-txt">'+escapeHtml(txt)+'</span><span class="act-ts">'+ts()+'</span>';
stream.insertBefore(line, stream.firstChild);
// Trim to 30
while(stream.children.length > 30) stream.removeChild(stream.lastChild);
}
function escapeHtml(s){
var d=document.createElement("div"); d.textContent=s||""; return d.innerHTML;
}
// Modal trigger
function openTrigger(){ document.getElementById("modal").classList.add("on"); }
function closeTrigger(){ document.getElementById("modal").classList.remove("on"); }
async function submitTrigger(){
var action = document.getElementById("mp-action").value.trim();
var prompt = document.getElementById("mp-prompt").value.trim();
if(!action || !prompt){ alert("Action et prompt requis"); return; }
var source = document.getElementById("mp-source").value;
var agents = document.getElementById("mp-agents").value.split(",").map(function(s){return s.trim()}).filter(function(s){return s});
try{
var r = await fetch("/api/paperclip-bridge.php", {
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({source:source, action:action, prompt:prompt, agents_discussed:agents})
});
var d = await r.json();
if(d.ok){
addActivity("✅", "Paperclip queued: " + action + " (id " + (d.id||"?").substring(0,8) + ")", true);
closeTrigger();
document.getElementById("mp-action").value="";
document.getElementById("mp-prompt").value="";
document.getElementById("mp-agents").value="";
loadAll();
} else {
alert("Erreur: " + (d.error||"fail"));
}
}catch(e){ alert("Erreur: " + e.message); }
}
// Paperclip native agents (pas ceux du catalog, ceux qui vraiment exécutent)
async function loadPCAgents(){
// On approxime : on montre agent_name des dernières actions Paperclip
try{
var r = await fetch("/api/paperclip-bridge.php?action=list&limit=50");
var d = await r.json();
var names={};
(d.actions||[]).forEach(function(a){
if(a.agent_name){ names[a.agent_name] = (names[a.agent_name]||0)+1; }
});
var panel = document.getElementById("pc-agents");
var keys = Object.keys(names);
if(!keys.length){
panel.innerHTML = '<div style="text-align:center;color:#94a3b8;padding:20px;font-size:.75rem">Agent claude_local en veille.<br>Prochain heartbeat &lt; 1h.</div>';
document.getElementById("ls-hb").textContent = "0";
} else {
panel.innerHTML = keys.map(function(n){
return '<div style="padding:8px 10px;background:#f8fafc;border-radius:6px;margin-bottom:6px;display:flex;justify-content:space-between;align-items:center;border-left:3px solid #059669">'+
'<span style="font-weight:800">'+escapeHtml(n)+'</span>'+
'<span style="color:#059669;font-weight:800">'+names[n]+' tasks</span>'+
'</div>';
}).join("");
document.getElementById("ls-hb").textContent = keys.length;
}
}catch(e){}
}
// Init + polling
loadAll();
loadPCAgents();
setInterval(loadAll, REFRESH_MS);
setInterval(loadPCAgents, 15000);
addActivity("⚡", "Live Ops connecté — refresh toutes les 5s", false);
</script>
<!-- DOCTRINE-60-UX-JS --><script id="doctrine60-ux-js-direct">
// DOCTRINE-60-UX-JS staggered entrance
(function(){
if (!('IntersectionObserver' in window)) return;
const obs = new IntersectionObserver((entries) => {
entries.forEach((e, i) => {
if (e.isIntersecting) {
setTimeout(() => e.target.classList.add('enter-stagger'), i * 80);
obs.unobserve(e.target);
}
});
});
document.querySelectorAll('.card, .kpi, .panel').forEach(el => obs.observe(el));
})();
</script>
</body>
</html>