Files
html/paperclip-flow.html

503 lines
27 KiB
HTML

<!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>
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-143919 -->
<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>
</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>
<!-- 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>