feat(paperclip-flow): module visuel UI premium temps reel du flux Social -> Bridge -> Paperclip -> Execution - 4 noeuds pipeline animes - stats KPI 5 colonnes - liste actions filtrable status queued/running/completed/failed - timeline 24h - modal detail action - auto-refresh 10s - CTA nouvelle action - link Cloudbot Social
This commit is contained in:
445
paperclip-flow.html
Normal file
445
paperclip-flow.html
Normal file
@@ -0,0 +1,445 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>WEVIA Paperclip Flow - Social to Real Execution</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<style>
|
||||
:root{
|
||||
--bg:#07091a;--card:#11152b;--card-h:#171c36;--bd:rgba(140,160,240,.14);
|
||||
--ac:#7c5cff;--a2:#4fd1c7;--wn:#f59e0b;--er:#ef4444;--ok:#10b981;--bl:#3b82f6;
|
||||
--fg:#e8edf8;--dm:#6b7490;--mg:#9aa4bf;
|
||||
--grad:linear-gradient(135deg,#7c5cff 0%,#4fd1c7 100%);
|
||||
--grad-h:linear-gradient(135deg,#f59e0b 0%,#ef4444 100%);
|
||||
--glow-ac:0 0 20px rgba(124,92,255,.35);
|
||||
--glow-ok:0 0 20px rgba(16,185,129,.35);
|
||||
--glow-wn:0 0 20px rgba(245,158,11,.35);
|
||||
}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
html,body{background:var(--bg);color:var(--fg);font:13px/1.55 -apple-system,Segoe UI,Inter,sans-serif;min-height:100vh}
|
||||
body{background:radial-gradient(ellipse at top,rgba(124,92,255,.08),transparent 60%),radial-gradient(ellipse at bottom right,rgba(79,209,199,.06),transparent 50%),var(--bg);background-attachment:fixed}
|
||||
|
||||
header{background:linear-gradient(180deg,rgba(124,92,255,.08),transparent);padding:20px 28px;border-bottom:1px solid var(--bd);display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:100;backdrop-filter:blur(20px);background-color:rgba(7,9,26,.85)}
|
||||
h1{font-size:22px;font-weight:800;background:var(--grad);-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-.5px}
|
||||
.sub{font-size:11px;color:var(--mg);margin-top:2px}
|
||||
.hdr-links{display:flex;gap:8px}
|
||||
.btn{padding:8px 14px;border-radius:8px;background:var(--card);border:1px solid var(--bd);color:var(--fg);font-size:11px;font-weight:600;cursor:pointer;text-decoration:none;transition:all .15s}
|
||||
.btn:hover{background:var(--card-h);border-color:var(--ac);transform:translateY(-1px)}
|
||||
.btn-primary{background:var(--grad);border:none;color:#fff;box-shadow:var(--glow-ac)}
|
||||
.live-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;background:rgba(16,185,129,.12);border:1px solid rgba(16,185,129,.3);border-radius:12px;font-size:10px;color:var(--ok);font-weight:700;text-transform:uppercase;letter-spacing:1px}
|
||||
.live-dot{width:6px;height:6px;border-radius:50%;background:var(--ok);animation:pulse 1.8s infinite}
|
||||
@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(1.4)}}
|
||||
|
||||
main{max-width:1600px;margin:0 auto;padding:28px}
|
||||
|
||||
.hero{background:var(--card);border:1px solid var(--bd);border-radius:18px;padding:24px;margin-bottom:28px;box-shadow:0 10px 40px rgba(0,0,0,.2)}
|
||||
.hero-title{font-size:16px;font-weight:800;margin-bottom:4px;display:flex;align-items:center;gap:10px}
|
||||
.hero-sub{font-size:12px;color:var(--mg);margin-bottom:20px}
|
||||
|
||||
/* === FLOW DIAGRAM === */
|
||||
.flow{display:grid;grid-template-columns:1fr 44px 1fr 44px 1fr 44px 1fr;gap:8px;align-items:stretch;margin-bottom:16px}
|
||||
@media(max-width:1100px){.flow{grid-template-columns:1fr;gap:16px}.flow-arrow{transform:rotate(90deg);margin:0 auto}}
|
||||
.flow-node{background:var(--bg);border:1px solid var(--bd);border-radius:14px;padding:18px;position:relative;overflow:hidden;transition:all .3s}
|
||||
.flow-node:hover{transform:translateY(-2px);border-color:var(--ac);box-shadow:var(--glow-ac)}
|
||||
.flow-node::before{content:"";position:absolute;inset:0;background:var(--grad);opacity:.05;z-index:0}
|
||||
.flow-node>*{position:relative;z-index:1}
|
||||
.node-icon{font-size:28px;margin-bottom:8px;display:block}
|
||||
.node-title{font-size:13px;font-weight:800;margin-bottom:4px}
|
||||
.node-desc{font-size:10px;color:var(--mg);line-height:1.5;margin-bottom:10px;min-height:30px}
|
||||
.node-kpi{font-size:20px;font-weight:900;background:var(--grad);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin:6px 0 2px}
|
||||
.node-kpi-lbl{font-size:9px;color:var(--dm);text-transform:uppercase;letter-spacing:1px}
|
||||
.node-status{margin-top:10px;padding-top:10px;border-top:1px solid var(--bd);font-size:10px;color:var(--mg);display:flex;align-items:center;gap:6px}
|
||||
.status-led{width:8px;height:8px;border-radius:50%;background:var(--ok);box-shadow:0 0 8px var(--ok);animation:pulse 2s infinite}
|
||||
.flow-arrow{display:flex;align-items:center;justify-content:center;color:var(--ac);font-size:24px;animation:slideRight 2s infinite ease-in-out}
|
||||
@keyframes slideRight{0%,100%{transform:translateX(0);opacity:.5}50%{transform:translateX(4px);opacity:1}}
|
||||
|
||||
/* === STATS ROW === */
|
||||
.stats-row{display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:28px}
|
||||
@media(max-width:900px){.stats-row{grid-template-columns:repeat(2,1fr)}}
|
||||
.stat{background:var(--card);border:1px solid var(--bd);border-radius:12px;padding:16px;position:relative;overflow:hidden}
|
||||
.stat::after{content:"";position:absolute;top:-30px;right:-30px;width:80px;height:80px;border-radius:50%;background:var(--grad);opacity:.06}
|
||||
.stat-val{font-size:28px;font-weight:900;background:var(--grad);-webkit-background-clip:text;-webkit-text-fill-color:transparent;line-height:1}
|
||||
.stat-lbl{font-size:10px;color:var(--dm);text-transform:uppercase;letter-spacing:1.2px;margin-top:4px;font-weight:700}
|
||||
.stat.wn .stat-val{background:linear-gradient(135deg,#f59e0b,#fbbf24);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.stat.ok .stat-val{background:linear-gradient(135deg,#10b981,#34d399);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.stat.er .stat-val{background:linear-gradient(135deg,#ef4444,#f87171);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.stat.bl .stat-val{background:linear-gradient(135deg,#3b82f6,#60a5fa);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
|
||||
/* === TWO COLUMNS === */
|
||||
.panels{display:grid;grid-template-columns:1fr 380px;gap:20px}
|
||||
@media(max-width:1200px){.panels{grid-template-columns:1fr}}
|
||||
|
||||
.panel{background:var(--card);border:1px solid var(--bd);border-radius:16px;overflow:hidden}
|
||||
.panel-head{padding:16px 20px;border-bottom:1px solid var(--bd);display:flex;align-items:center;justify-content:space-between;background:rgba(124,92,255,.05)}
|
||||
.panel-title{font-size:13px;font-weight:800}
|
||||
.panel-body{padding:16px 20px;max-height:640px;overflow-y:auto}
|
||||
|
||||
/* === ACTION CARDS === */
|
||||
.action-card{background:var(--bg);border:1px solid var(--bd);border-radius:12px;padding:14px;margin-bottom:12px;position:relative;overflow:hidden;transition:all .2s;cursor:pointer}
|
||||
.action-card:hover{border-color:var(--ac);transform:translateX(2px)}
|
||||
.action-card.st-queued{border-left:3px solid var(--wn)}
|
||||
.action-card.st-running{border-left:3px solid var(--bl);animation:bluePulse 2s infinite}
|
||||
.action-card.st-completed{border-left:3px solid var(--ok)}
|
||||
.action-card.st-failed{border-left:3px solid var(--er)}
|
||||
@keyframes bluePulse{0%,100%{box-shadow:0 0 0 rgba(59,130,246,.4)}50%{box-shadow:0 0 14px rgba(59,130,246,.4)}}
|
||||
|
||||
.action-head{display:flex;align-items:center;gap:10px;margin-bottom:8px}
|
||||
.action-icon{font-size:18px}
|
||||
.action-title{flex:1;font-weight:700;font-size:13px}
|
||||
.action-meta{font-size:10px;color:var(--dm);display:flex;gap:10px;flex-wrap:wrap}
|
||||
.action-reason{font-size:11px;color:var(--mg);line-height:1.5;margin-top:6px;max-height:60px;overflow:hidden;position:relative}
|
||||
.action-chip{display:inline-block;padding:2px 7px;border-radius:8px;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;margin-right:4px}
|
||||
.chip-queued{background:rgba(245,158,11,.15);color:var(--wn)}
|
||||
.chip-running{background:rgba(59,130,246,.15);color:var(--bl)}
|
||||
.chip-completed{background:rgba(16,185,129,.15);color:var(--ok)}
|
||||
.chip-failed{background:rgba(239,68,68,.15);color:var(--er)}
|
||||
.action-agents{margin-top:6px;display:flex;gap:4px;flex-wrap:wrap}
|
||||
.agent-chip{font-size:9px;background:rgba(124,92,255,.15);color:var(--ac);padding:2px 6px;border-radius:6px;font-weight:600}
|
||||
|
||||
/* === TIMELINE === */
|
||||
.timeline{position:relative;padding-left:24px}
|
||||
.timeline::before{content:"";position:absolute;left:6px;top:0;bottom:0;width:2px;background:linear-gradient(to bottom,var(--ac),transparent)}
|
||||
.tl-item{position:relative;padding:10px 0;padding-left:10px;font-size:11px}
|
||||
.tl-item::before{content:"";position:absolute;left:-22px;top:15px;width:10px;height:10px;border-radius:50%;background:var(--ac);box-shadow:0 0 8px var(--ac)}
|
||||
.tl-item.st-completed::before{background:var(--ok);box-shadow:0 0 8px var(--ok)}
|
||||
.tl-item.st-failed::before{background:var(--er)}
|
||||
.tl-time{color:var(--dm);font-size:9px;font-weight:600;letter-spacing:.5px}
|
||||
.tl-title{color:var(--fg);font-weight:600;margin:2px 0}
|
||||
.tl-sub{color:var(--mg);font-size:10px}
|
||||
|
||||
/* === CTA BAR === */
|
||||
.cta-bar{background:linear-gradient(135deg,rgba(124,92,255,.1),rgba(79,209,199,.1));border:1px solid var(--bd);border-radius:14px;padding:18px;margin-bottom:28px;display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap}
|
||||
.cta-text{flex:1;min-width:260px}
|
||||
.cta-title{font-size:14px;font-weight:800;margin-bottom:4px}
|
||||
.cta-sub{font-size:11px;color:var(--mg)}
|
||||
.cta-actions{display:flex;gap:10px;flex-wrap:wrap}
|
||||
.btn-big{padding:10px 18px;border-radius:10px;border:none;font-size:12px;font-weight:700;cursor:pointer;transition:all .15s}
|
||||
.btn-big.primary{background:var(--grad);color:#fff;box-shadow:var(--glow-ac)}
|
||||
.btn-big.primary:hover{transform:translateY(-2px);box-shadow:0 10px 30px rgba(124,92,255,.4)}
|
||||
.btn-big.secondary{background:var(--bg);border:1px solid var(--bd);color:var(--fg)}
|
||||
.btn-big.secondary:hover{background:var(--card)}
|
||||
|
||||
/* === DETAIL MODAL === */
|
||||
.modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:1000;align-items:center;justify-content:center;padding:20px;backdrop-filter:blur(4px)}
|
||||
.modal.on{display:flex}
|
||||
.modal-box{width:760px;max-width:100%;max-height:90vh;background:var(--card);border-radius:16px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.5)}
|
||||
.modal-head{padding:16px 22px;border-bottom:1px solid var(--bd);display:flex;align-items:center;gap:12px;background:rgba(124,92,255,.06)}
|
||||
.modal-title{flex:1;font-weight:800;font-size:14px}
|
||||
.modal-close{background:none;border:none;color:var(--fg);font-size:24px;cursor:pointer;padding:0 6px;opacity:.6}
|
||||
.modal-close:hover{opacity:1}
|
||||
.modal-body{padding:20px;overflow-y:auto}
|
||||
.mb-section{margin-bottom:18px}
|
||||
.mb-lbl{font-size:10px;text-transform:uppercase;letter-spacing:1.2px;color:var(--dm);font-weight:700;margin-bottom:6px}
|
||||
.mb-val{font-size:12px;color:var(--fg);padding:10px;background:var(--bg);border:1px solid var(--bd);border-radius:8px;word-break:break-word;font-family:ui-monospace,SF Mono,monospace;font-size:11px}
|
||||
pre.mb-val{white-space:pre-wrap;max-height:300px;overflow-y:auto}
|
||||
|
||||
.empty{text-align:center;padding:40px;color:var(--dm);font-size:12px}
|
||||
.loading{text-align:center;padding:40px;color:var(--dm)}
|
||||
|
||||
.pulse-new{animation:flashNew 1.5s ease-out}
|
||||
@keyframes flashNew{0%{background:rgba(124,92,255,.2);border-color:var(--ac)}100%{background:var(--bg)}}
|
||||
|
||||
.toggle{display:flex;gap:6px}
|
||||
.toggle-btn{padding:5px 10px;background:var(--bg);border:1px solid var(--bd);border-radius:6px;font-size:10px;cursor:pointer;color:var(--mg);font-weight:600}
|
||||
.toggle-btn.on{background:var(--ac);color:#fff;border-color:var(--ac)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div>
|
||||
<h1>🔁 Paperclip Flow</h1>
|
||||
<div class="sub">Social → Bridge → Paperclip → Exécution réelle · <span class="live-badge"><span class="live-dot"></span>LIVE</span></div>
|
||||
</div>
|
||||
<div class="hdr-links">
|
||||
<a class="btn" href="/cloudbot-social.html">☁️ Cloudbot Social</a>
|
||||
<a class="btn" href="/wevia-meeting-rooms.html">🏛️ Meeting Rooms</a>
|
||||
<a class="btn" href="/paperclip-dashboard.html">📊 Paperclip DB</a>
|
||||
<a class="btn" href="/wevia-master.html">🤖 WEVIA Master</a>
|
||||
<a class="btn btn-primary" href="/weval-technology-platform.html">⚡ WTP</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<section class="hero">
|
||||
<div class="hero-title">🌊 Vue d'ensemble du pipeline</div>
|
||||
<div class="hero-sub">Les discussions entre IA déclenchent des actions réelles exécutées par Paperclip AI (agent <code style="color:var(--a2);font-family:ui-monospace">claude_local</code> sur port 3102)</div>
|
||||
|
||||
<div class="flow">
|
||||
<div class="flow-node">
|
||||
<span class="node-icon">💬</span>
|
||||
<div class="node-title">1. Social / Meeting</div>
|
||||
<div class="node-desc">Les 726 agents discutent, collaborent, brainstorment</div>
|
||||
<div class="node-kpi" id="f1-agents">726</div>
|
||||
<div class="node-kpi-lbl">agents disponibles</div>
|
||||
<div class="node-status"><span class="status-led"></span> <span>Cloudbot Social + Meeting Rooms</span></div>
|
||||
</div>
|
||||
<div class="flow-arrow">➜</div>
|
||||
<div class="flow-node">
|
||||
<span class="node-icon">🌉</span>
|
||||
<div class="node-title">2. Bridge API</div>
|
||||
<div class="node-desc">/api/paperclip-bridge.php transforme la discussion en tâche Paperclip</div>
|
||||
<div class="node-kpi" id="f2-total">0</div>
|
||||
<div class="node-kpi-lbl">actions transférées</div>
|
||||
<div class="node-status"><span class="status-led"></span> <span>Bridge opérationnel</span></div>
|
||||
</div>
|
||||
<div class="flow-arrow">➜</div>
|
||||
<div class="flow-node">
|
||||
<span class="node-icon">🤖</span>
|
||||
<div class="node-title">3. Paperclip AI</div>
|
||||
<div class="node-desc">Agent claude_local picke la tâche (heartbeat 1h) et execute</div>
|
||||
<div class="node-kpi" id="f3-runs">0</div>
|
||||
<div class="node-kpi-lbl">exécutions heartbeat</div>
|
||||
<div class="node-status"><span class="status-led"></span> <span>Agent actif · port 3102</span></div>
|
||||
</div>
|
||||
<div class="flow-arrow">➜</div>
|
||||
<div class="flow-node">
|
||||
<span class="node-icon">✅</span>
|
||||
<div class="node-title">4. Résultat réel</div>
|
||||
<div class="node-desc">Code édité, commit git, deploy, scripts lancés — tout tracé</div>
|
||||
<div class="node-kpi" id="f4-done">0</div>
|
||||
<div class="node-kpi-lbl">actions complétées</div>
|
||||
<div class="node-status"><span class="status-led"></span> <span>heartbeat_runs DB</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cta-bar">
|
||||
<div class="cta-text">
|
||||
<div class="cta-title">🚀 Déclenche une action concrète maintenant</div>
|
||||
<div class="cta-sub">Transforme n'importe quelle discussion IA en vraie exécution Paperclip (code, deploy, analyse)</div>
|
||||
</div>
|
||||
<div class="cta-actions">
|
||||
<button class="btn-big primary" onclick="newAction()">+ Nouvelle action</button>
|
||||
<button class="btn-big secondary" onclick="refreshAll()">🔄 Rafraîchir</button>
|
||||
<button class="btn-big secondary" onclick="toggleAuto()"><span id="auto-lbl">⏸️ Pause auto-refresh</span></button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="stats-row">
|
||||
<div class="stat"><div class="stat-val" id="k-total">0</div><div class="stat-lbl">Total actions</div></div>
|
||||
<div class="stat wn"><div class="stat-val" id="k-queued">0</div><div class="stat-lbl">⏳ File d'attente</div></div>
|
||||
<div class="stat bl"><div class="stat-val" id="k-running">0</div><div class="stat-lbl">🏃 En cours</div></div>
|
||||
<div class="stat ok"><div class="stat-val" id="k-completed">0</div><div class="stat-lbl">✅ Complétées</div></div>
|
||||
<div class="stat er"><div class="stat-val" id="k-failed">0</div><div class="stat-lbl">❌ Échouées</div></div>
|
||||
</section>
|
||||
|
||||
<section class="panels">
|
||||
<div class="panel">
|
||||
<div class="panel-head">
|
||||
<div class="panel-title">📋 Actions en cours & récentes</div>
|
||||
<div class="toggle">
|
||||
<div class="toggle-btn on" data-filter="all" onclick="setFilter(this,'all')">Toutes</div>
|
||||
<div class="toggle-btn" data-filter="queued" onclick="setFilter(this,'queued')">⏳ Queue</div>
|
||||
<div class="toggle-btn" data-filter="running" onclick="setFilter(this,'running')">🏃 Running</div>
|
||||
<div class="toggle-btn" data-filter="completed" onclick="setFilter(this,'completed')">✅ Done</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body" id="actions-list">
|
||||
<div class="loading">⚡ Chargement...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-head">
|
||||
<div class="panel-title">⏱️ Timeline activité (24h)</div>
|
||||
<div style="font-size:10px;color:var(--dm)" id="last-refresh">--:--</div>
|
||||
</div>
|
||||
<div class="panel-body" id="timeline">
|
||||
<div class="loading">Chargement...</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Modal action detail -->
|
||||
<div class="modal" id="m-detail">
|
||||
<div class="modal-box">
|
||||
<div class="modal-head">
|
||||
<div class="modal-title" id="md-title">Action detail</div>
|
||||
<button class="modal-close" onclick="closeModal()">✕</button>
|
||||
</div>
|
||||
<div class="modal-body" id="md-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var STATE={filter:"all",auto:true,lastData:null,knownIds:new Set()};
|
||||
|
||||
async function fetchData(){
|
||||
try{
|
||||
var [statsR, listR] = await Promise.all([
|
||||
fetch("/api/paperclip-bridge.php?action=stats"),
|
||||
fetch("/api/paperclip-bridge.php?action=list&limit=30")
|
||||
]);
|
||||
var stats = (await statsR.json()).stats || {};
|
||||
var list = (await listR.json()).actions || [];
|
||||
STATE.lastData={stats:stats,list:list};
|
||||
renderAll(stats,list);
|
||||
}catch(e){
|
||||
document.getElementById("actions-list").innerHTML='<div class="empty">⚠️ Erreur: '+e.message+'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderAll(stats, list){
|
||||
// KPIs
|
||||
document.getElementById("k-total").textContent=stats.total||0;
|
||||
document.getElementById("k-queued").textContent=stats.queued||0;
|
||||
document.getElementById("k-running").textContent=stats.running||0;
|
||||
document.getElementById("k-completed").textContent=stats.completed||0;
|
||||
document.getElementById("k-failed").textContent=stats.failed||0;
|
||||
|
||||
// Flow nodes
|
||||
document.getElementById("f2-total").textContent=stats.total||0;
|
||||
document.getElementById("f3-runs").textContent=(stats.running||0)+(stats.completed||0)+(stats.failed||0);
|
||||
document.getElementById("f4-done").textContent=stats.completed||0;
|
||||
|
||||
// Actions list
|
||||
renderActions(list);
|
||||
renderTimeline(list);
|
||||
|
||||
var now=new Date();
|
||||
document.getElementById("last-refresh").textContent="MAJ "+now.toLocaleTimeString();
|
||||
}
|
||||
|
||||
function renderActions(list){
|
||||
var filtered=list.filter(function(a){
|
||||
if(STATE.filter==="all")return true;
|
||||
return a.status===STATE.filter;
|
||||
});
|
||||
if(!filtered.length){
|
||||
document.getElementById("actions-list").innerHTML='<div class="empty">Aucune action '+(STATE.filter!=="all"?"en "+STATE.filter:"")+'. <br><br>Clique "+ Nouvelle action" pour commencer 🚀</div>';
|
||||
return;
|
||||
}
|
||||
var html=filtered.map(function(a){
|
||||
var icon={"completed":"✅","running":"🏃","queued":"⏳","failed":"❌"}[a.status]||"📌";
|
||||
var isNew=!STATE.knownIds.has(a.id);
|
||||
if(isNew)STATE.knownIds.add(a.id);
|
||||
var src=(a.source||"").split(":")[0];
|
||||
var reason=(a.reason||"").substring(0,180);
|
||||
var agents=[];
|
||||
try{var pl=typeof a.payload==="string"?JSON.parse(a.payload):a.payload;if(pl&&pl.agents_discussed)agents=pl.agents_discussed;}catch(e){}
|
||||
var agentsHtml=agents.slice(0,3).map(function(ag){return '<span class="agent-chip">'+escapeHtml(ag)+'</span>'}).join("");
|
||||
var dt=new Date(a.requested_at);
|
||||
var elapsed=timeAgo(dt);
|
||||
return '<div class="action-card st-'+a.status+(isNew?' pulse-new':'')+'" onclick="showDetail(\''+a.id+'\')">'+
|
||||
'<div class="action-head">'+
|
||||
'<span class="action-icon">'+icon+'</span>'+
|
||||
'<div class="action-title">'+escapeHtml((a.reason||"").split("—")[0].trim().substring(0,60))+'</div>'+
|
||||
'<span class="action-chip chip-'+a.status+'">'+a.status+'</span>'+
|
||||
'</div>'+
|
||||
'<div class="action-meta"><span>📡 '+escapeHtml(src)+'</span><span>⏱️ '+elapsed+'</span></div>'+
|
||||
(reason.length>20?'<div class="action-reason">'+escapeHtml(reason)+'</div>':'')+
|
||||
(agentsHtml?'<div class="action-agents">'+agentsHtml+'</div>':'')+
|
||||
'</div>';
|
||||
}).join("");
|
||||
document.getElementById("actions-list").innerHTML=html;
|
||||
}
|
||||
|
||||
function renderTimeline(list){
|
||||
if(!list.length){
|
||||
document.getElementById("timeline").innerHTML='<div class="empty">Pas encore d\'activité</div>';
|
||||
return;
|
||||
}
|
||||
var html='<div class="timeline">'+list.slice(0,15).map(function(a){
|
||||
var dt=new Date(a.requested_at);
|
||||
var time=dt.toLocaleTimeString().substring(0,5);
|
||||
var title=(a.reason||"").split("—")[0].trim().substring(0,50);
|
||||
var sub=(a.source||"").split(":")[0];
|
||||
return '<div class="tl-item st-'+a.status+'">'+
|
||||
'<div class="tl-time">'+time+' · '+a.status+'</div>'+
|
||||
'<div class="tl-title">'+escapeHtml(title)+'</div>'+
|
||||
'<div class="tl-sub">'+escapeHtml(sub)+'</div>'+
|
||||
'</div>';
|
||||
}).join("")+'</div>';
|
||||
document.getElementById("timeline").innerHTML=html;
|
||||
}
|
||||
|
||||
function setFilter(btn,f){
|
||||
STATE.filter=f;
|
||||
document.querySelectorAll(".toggle-btn").forEach(function(x){x.classList.remove("on")});
|
||||
btn.classList.add("on");
|
||||
if(STATE.lastData)renderActions(STATE.lastData.list);
|
||||
}
|
||||
|
||||
async function showDetail(id){
|
||||
document.getElementById("md-title").textContent="Chargement...";
|
||||
document.getElementById("md-body").innerHTML='<div class="loading">⚡</div>';
|
||||
document.getElementById("m-detail").classList.add("on");
|
||||
try{
|
||||
var r=await fetch("/api/paperclip-bridge.php?action=status&id="+id);
|
||||
var d=await r.json();
|
||||
if(!d.ok){ document.getElementById("md-body").innerHTML='<div class="empty">Action introuvable</div>'; return; }
|
||||
var a=d.action;
|
||||
var pl={};
|
||||
try{pl=typeof a.payload==="string"?JSON.parse(a.payload):(a.payload||{})}catch(e){}
|
||||
document.getElementById("md-title").textContent=(a.reason||"Action").split("—")[0].trim().substring(0,80);
|
||||
var html='';
|
||||
html+='<div class="mb-section"><div class="mb-lbl">Statut</div><div class="mb-val"><span class="action-chip chip-'+a.status+'">'+a.status+'</span>'+(a.run_status?' · Run: '+a.run_status+(a.exit_code!==null?' (exit '+a.exit_code+')':''):'')+'</div></div>';
|
||||
html+='<div class="mb-section"><div class="mb-lbl">ID</div><div class="mb-val">'+escapeHtml(a.id)+'</div></div>';
|
||||
html+='<div class="mb-section"><div class="mb-lbl">Source</div><div class="mb-val">'+escapeHtml(a.source||"?")+'</div></div>';
|
||||
html+='<div class="mb-section"><div class="mb-lbl">Action</div><div class="mb-val">'+escapeHtml(a.reason||"")+'</div></div>';
|
||||
if(pl.prompt){html+='<div class="mb-section"><div class="mb-lbl">Prompt Paperclip</div><pre class="mb-val">'+escapeHtml(pl.prompt)+'</pre></div>'}
|
||||
if(pl.agents_discussed&&pl.agents_discussed.length){html+='<div class="mb-section"><div class="mb-lbl">Agents impliqués</div><div class="mb-val">'+pl.agents_discussed.map(escapeHtml).join(", ")+'</div></div>'}
|
||||
html+='<div class="mb-section"><div class="mb-lbl">Timestamps</div><div class="mb-val">Requested: '+escapeHtml(a.requested_at||"")+'<br>Claimed: '+escapeHtml(a.claimed_at||"-")+'<br>Finished: '+escapeHtml(a.finished_at||"-")+'</div></div>';
|
||||
if(a.result_json){html+='<div class="mb-section"><div class="mb-lbl">Résultat exécution</div><pre class="mb-val">'+escapeHtml(JSON.stringify(a.result_json,null,2))+'</pre></div>'}
|
||||
if(a.error){html+='<div class="mb-section"><div class="mb-lbl">⚠️ Erreur</div><pre class="mb-val">'+escapeHtml(a.error)+'</pre></div>'}
|
||||
document.getElementById("md-body").innerHTML=html;
|
||||
}catch(e){
|
||||
document.getElementById("md-body").innerHTML='<div class="empty">Erreur: '+e.message+'</div>';
|
||||
}
|
||||
}
|
||||
function closeModal(){document.getElementById("m-detail").classList.remove("on")}
|
||||
|
||||
async function newAction(){
|
||||
var action=prompt("Action concrète à exécuter (max 200 chars):\nEx: analyser logs nginx, fix bug X, deploy Y...");
|
||||
if(!action)return;
|
||||
var prmpt=prompt("Prompt détaillé pour Paperclip (instructions techniques complètes):");
|
||||
if(!prmpt)return;
|
||||
var agents=prompt("Agents IA impliqués (séparés par virgule, ex: WEVIA Master,Ethica):")||"";
|
||||
try{
|
||||
var r=await fetch("/api/paperclip-bridge.php",{
|
||||
method:"POST",
|
||||
headers:{"Content-Type":"application/json"},
|
||||
body:JSON.stringify({
|
||||
source:"paperclip-flow-ui",
|
||||
action:action,
|
||||
prompt:prmpt,
|
||||
agents_discussed:agents.split(",").map(function(s){return s.trim()}).filter(Boolean),
|
||||
priority:"normal"
|
||||
})
|
||||
});
|
||||
var d=await r.json();
|
||||
if(d.ok){
|
||||
alert("✅ Action envoyée à Paperclip !\nID: "+d.id.substring(0,8)+"...\nStatus: "+d.status);
|
||||
fetchData();
|
||||
}else{
|
||||
alert("⚠️ "+(d.error||"Erreur"));
|
||||
}
|
||||
}catch(e){alert("⚠️ "+e.message)}
|
||||
}
|
||||
|
||||
function refreshAll(){fetchData()}
|
||||
function toggleAuto(){
|
||||
STATE.auto=!STATE.auto;
|
||||
document.getElementById("auto-lbl").textContent=STATE.auto?"⏸️ Pause auto-refresh":"▶️ Reprendre auto";
|
||||
}
|
||||
|
||||
function escapeHtml(s){var d=document.createElement("div");d.textContent=s||"";return d.innerHTML.replace(/\n/g,"<br>")}
|
||||
function timeAgo(d){var s=Math.floor((Date.now()-d.getTime())/1000);if(s<60)return "il y a "+s+"s";var m=Math.floor(s/60);if(m<60)return "il y a "+m+"min";var h=Math.floor(m/60);if(h<24)return "il y a "+h+"h";return d.toLocaleDateString()}
|
||||
|
||||
// Init + auto-refresh
|
||||
fetchData();
|
||||
setInterval(function(){if(STATE.auto)fetchData()},10000);
|
||||
|
||||
// Load 726 agents count
|
||||
fetch("/api/agents-catalog-api.php").then(function(r){return r.json()}).then(function(d){
|
||||
if(d&&d.total)document.getElementById("f1-agents").textContent=d.total;
|
||||
}).catch(function(){});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user