519 lines
24 KiB
HTML
519 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 110px 3px 10px;height:22px;background:linear-gradient(135deg,#1e293b,#0f172a);box-shadow:0 1px 3px rgba(0,0,0,.15)} /* V2 24avr Opus - 110px right reserve pour injected Logout */
|
|
#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:100px;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>
|
|
<style id="logout-dedup-24avr-opus">/* Doctrine anti-overlap: hide nginx duplicate Logout */ #weval-global-logout { display: none !important; }</style>
|
|
<style id="below-livestats-opus-v2">
|
|
/* FIX Opus 25avr 00:55 v2 - push ALL fixed top elements below live-stats bar (24px) */
|
|
html body #weval-gl,
|
|
html body #weval-global-logout,
|
|
html body #wtp-udock { top: 30px !important; }
|
|
</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 < 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>
|