Files
html/director-chat.html

634 lines
37 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WEVIA Director — Command</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600&family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#06080d;--bg2:#0c1018;--bg3:#141a26;--bg4:#1c2535;
--t1:#d4dce8;--t2:#7a8ba0;--t3:#4a5568;
--g:#00e87b;--g2:#00c96a;--g3:rgba(0,232,123,.08);
--b:#00b4ff;--b2:rgba(0,180,255,.1);
--r:#ff3b5c;--r2:rgba(255,59,92,.1);
--y:#ffc120;--y2:rgba(255,193,32,.1);
--brd:rgba(255,255,255,.04);
--glow:0 0 20px rgba(0,232,123,.15);
}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Outfit',sans-serif;background:var(--bg);color:var(--t1);height:100vh;display:flex;flex-direction:column;overflow:hidden}
::selection{background:var(--g);color:var(--bg)}
::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--t3);border-radius:2px}
/* ═══ TOP BAR ═══ */
.top{height:42px;background:var(--bg2);border-bottom:1px solid var(--brd);display:flex;align-items:center;padding:0 16px;gap:10px;flex-shrink:0}
.top-logo{display:flex;align-items:center;gap:8px}
.top-logo .dot{width:8px;height:8px;border-radius:50%;background:var(--g);box-shadow:var(--glow);animation:blink 3s infinite}
@keyframes blink{0%,90%,100%{opacity:1}95%{opacity:.3}}
.top-logo span{font-family:'IBM Plex Mono',monospace;font-size:13px;font-weight:600;letter-spacing:.5px}
.top-stats{display:flex;gap:14px;margin-left:auto;font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--t2)}
.top-stat{display:flex;align-items:center;gap:4px}
.top-stat .v{color:var(--g);font-weight:600}
.top-stat .v.w{color:var(--y)}
.top-stat .v.c{color:var(--r)}
.top-nav{display:flex;gap:4px;margin-left:16px}
.top-nav a{padding:4px 10px;border-radius:6px;font-size:11px;color:var(--t2);text-decoration:none;font-family:'IBM Plex Mono',monospace;border:1px solid transparent;transition:.15s}
.top-nav a:hover{background:var(--bg3);color:var(--t1);border-color:var(--brd)}
/* ═══ MAIN LAYOUT ═══ */
.main{flex:1;display:flex;overflow:hidden}
/* ═══ SIDEBAR ═══ */
.side{width:220px;background:var(--bg2);border-right:1px solid var(--brd);display:flex;flex-direction:column;flex-shrink:0}
.side-section{padding:12px}
.side-title{font-family:'IBM Plex Mono',monospace;font-size:9px;color:var(--t3);text-transform:uppercase;letter-spacing:1.5px;margin-bottom:8px}
.side-btn{width:100%;padding:8px 10px;border-radius:6px;border:1px solid var(--brd);background:transparent;color:var(--t2);font-size:12px;font-family:'Outfit',sans-serif;cursor:pointer;text-align:left;display:flex;align-items:center;gap:8px;margin-bottom:3px;transition:.15s}
.side-btn:hover{background:var(--bg3);color:var(--t1);border-color:rgba(0,232,123,.15)}
.side-btn.active{background:var(--g3);color:var(--g);border-color:rgba(0,232,123,.2)}
.side-btn .ico{font-size:14px;width:20px;text-align:center}
.side-btn .kbd{margin-left:auto;font-family:'IBM Plex Mono',monospace;font-size:9px;color:var(--t3);background:var(--bg);padding:1px 5px;border-radius:3px}
.side-indicator{display:flex;align-items:center;gap:6px;padding:6px 10px;font-size:11px;color:var(--t2)}
.side-indicator .idot{width:6px;height:6px;border-radius:50%}
.side-indicator .idot.on{background:var(--g);box-shadow:0 0 6px rgba(0,232,123,.4)}
.side-indicator .idot.off{background:var(--r);box-shadow:0 0 6px rgba(255,59,92,.4)}
.side-indicator .idot.warn{background:var(--y)}
.side-sep{height:1px;background:var(--brd);margin:4px 12px}
/* ═══ CHAT AREA ═══ */
.chat-wrap{flex:1;display:flex;flex-direction:column;overflow:hidden}
.chat-header{padding:8px 16px;background:var(--bg2);border-bottom:1px solid var(--brd);display:flex;align-items:center;gap:10px;font-size:12px;flex-shrink:0}
.chat-header .mode{padding:3px 10px;border-radius:4px;font-family:'IBM Plex Mono',monospace;font-size:10px;font-weight:500;border:1px solid var(--brd)}
.chat-header .mode.live{color:var(--g);border-color:rgba(0,232,123,.3);background:var(--g3)}
.chat-msgs{flex:1;overflow-y:auto;padding:16px}
.msg{margin-bottom:14px;animation:msgIn .25s ease}
@keyframes msgIn{from{opacity:0;transform:translateY(8px)}to{opacity:1}}
.msg-u{display:flex;justify-content:flex-end}
.msg-d{display:flex;justify-content:flex-start}
.msg-u .bbl{background:rgba(0,180,255,.08);border:1px solid rgba(0,180,255,.15);border-radius:12px 12px 2px 12px}
.msg-d .bbl{background:var(--bg2);border:1px solid var(--brd);border-radius:12px 12px 12px 2px}
.bbl{max-width:85%;padding:12px 16px;font-size:13px;line-height:1.65}
.bbl pre{background:var(--bg);border:1px solid var(--brd);padding:10px;border-radius:6px;overflow-x:auto;margin:8px 0;font-family:'IBM Plex Mono',monospace;font-size:11px;line-height:1.5}
.bbl b{color:var(--t1);font-weight:600}
.bbl .tag{display:inline-block;padding:1px 7px;border-radius:4px;font-family:'IBM Plex Mono',monospace;font-size:10px;font-weight:600;margin:1px 2px;letter-spacing:.3px}
.tag-ok{background:var(--g3);color:var(--g)}
.tag-warn{background:var(--y2);color:var(--y)}
.tag-crit{background:var(--r2);color:var(--r)}
.tag-info{background:var(--b2);color:var(--b)}
.msg-meta{font-family:'IBM Plex Mono',monospace;font-size:9px;color:var(--t3);margin-top:4px;padding:0 4px}
.typing{display:none;padding:8px 16px}
.typing.on{display:flex;align-items:center;gap:8px}
.typing-bar{display:flex;gap:3px}.typing-bar span{width:4px;height:4px;border-radius:50%;background:var(--g);animation:tp .5s infinite alternate}.typing-bar span:nth-child(2){animation-delay:.12s}.typing-bar span:nth-child(3){animation-delay:.24s}
@keyframes tp{from{opacity:.2;transform:scale(.8)}to{opacity:1;transform:scale(1.2)}}
.typing-text{font-size:11px;color:var(--t3);font-style:italic}
/* ═══ INPUT ═══ */
.input-area{padding:10px 16px;background:var(--bg2);border-top:1px solid var(--brd);flex-shrink:0}
.input-row{display:flex;gap:8px;align-items:flex-end}
.input-row textarea{flex:1;resize:none;padding:10px 14px;border-radius:8px;border:1px solid var(--brd);background:var(--bg3);color:var(--t1);font-size:13px;font-family:'Outfit',sans-serif;outline:none;max-height:120px;line-height:1.5;transition:border .15s}
.input-row textarea:focus{border-color:var(--g);box-shadow:0 0 0 2px rgba(0,232,123,.08)}
.input-row textarea::placeholder{color:var(--t3)}
.send-btn{width:38px;height:38px;border-radius:8px;border:none;background:var(--g);color:var(--bg);cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:.15s}
.send-btn:hover{box-shadow:var(--glow)}
.send-btn:disabled{opacity:.2;cursor:default}
.send-btn svg{width:16px;height:16px}
/* ═══ RIGHT PANEL ═══ */
.right{width:260px;background:var(--bg2);border-left:1px solid var(--brd);overflow-y:auto;flex-shrink:0;display:flex;flex-direction:column}
.right-section{padding:12px}
.right-title{font-family:'IBM Plex Mono',monospace;font-size:9px;color:var(--t3);text-transform:uppercase;letter-spacing:1.5px;margin-bottom:8px}
.srv-row{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:12px}
.srv-row .sdot{width:6px;height:6px;border-radius:50%}
.srv-row .sdot.on{background:var(--g)}.srv-row .sdot.off{background:var(--r)}
.srv-row .sname{font-weight:500;flex:1}
.srv-row .sval{font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--t2)}
.metric{display:flex;justify-content:space-between;padding:4px 0;font-size:11px;border-bottom:1px solid var(--brd)}
.metric .mk{color:var(--t2)}.metric .mv{font-family:'IBM Plex Mono',monospace;font-weight:500}
.mv.ok{color:var(--g)}.mv.w{color:var(--y)}.mv.c{color:var(--r)}
.log-stream{font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--t3);padding:4px 0;line-height:1.6;max-height:200px;overflow-y:auto}
.log-line{padding:2px 0;border-bottom:1px solid rgba(255,255,255,.02)}
@media(max-width:1000px){.side{display:none}.right{display:none}}
@media(max-width:600px){.top-stats{display:none}.top-nav{display:none}}
</style>
<link rel="stylesheet" href="/css/weval-premium.css">
<script src="/js/wevia-a11y-auto.js" defer></script>
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-143214 -->
<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>
<!-- BETON-DOCTRINE-101 dual-dummy block (pages pub) -->
<div id="weval-global-logout" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection"></div>
<a id="weval-gl" href="#" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection" tabindex="-1"></a>
<!-- TOP BAR -->
<div class="top">
<div class="top-logo"><div class="dot"></div><span>WEVIA DIRECTOR</span></div>
<div class="top-stats">
<div class="top-stat">URLS <span class="v" id="tsUrls"></span></div>
<div class="top-stat">SUBS <span class="v" id="tsSubs"></span></div>
<div class="top-stat">DOCK <span class="v" id="tsDocker"></span></div>
<div class="top-stat">DISK <span class="v" id="tsDisk"></span></div>
<div class="top-stat">ISS <span class="v" id="tsIssues"></span></div>
<div class="top-stat">CYCLE <span class="v" id="tsCycle"></span></div>
</div>
<div class="top-nav">
<a href="/director.html">Dashboard</a>
<a href="/l99-brain.html">L99</a>
<a href="/wevia-master.html">Master</a>
<a href="/ai-benchmark.html">Bench</a>
</div>
</div>
<div class="main">
<!-- SIDEBAR -->
<div class="side">
<div class="side-section">
<div class="side-title">Commands</div>
<button class="side-btn" onclick="send('status')"><span class="ico"></span>Status<span class="kbd">S</span></button>
<button class="side-btn" onclick="send('run cycle force')"><span class="ico"></span>Run Cycle<span class="kbd">R</span></button>
<button class="side-btn" onclick="send('fiability')"><span class="ico"></span>Fiability<span class="kbd">F</span></button>
<button class="side-btn" onclick="send('docker fix')"><span class="ico"></span>Docker Fix<span class="kbd">D</span></button>
</div>
<div class="side-sep"></div>
<div class="side-section">
<div class="side-title">Inspect</div>
<button class="side-btn" onclick="send('urls check')"><span class="ico"></span>URLs Health</button>
<button class="side-btn" onclick="send('architecture')"><span class="ico"></span>Architecture</button>
<button class="side-btn" onclick="send('master health')"><span class="ico"></span>Master AI</button>
<button class="side-btn" onclick="send('history')"><span class="ico"></span>History</button>
<button class="side-btn" onclick="send('nonreg')"><span class="ico"></span>NonReg</button>
<button class="side-btn" onclick="send('git')"><span class="ico"></span>Git</button>
</div>
<div class="side-sep"></div>
<div class="side-section">
<div class="side-title">Servers</div>
<div class="side-indicator"><div class="idot on" id="iS204"></div>S204 PRIMARY</div>
<div class="side-indicator"><div class="idot on" id="iS95"></div>S95 WEVADS</div>
<div class="side-indicator"><div class="idot on" id="iS151"></div>S151 TRACKING</div>
<div class="side-indicator"><div class="idot on" id="iOllama"></div>Ollama (10)</div>
<div class="side-indicator"><div class="idot on" id="iDocker"></div>Docker (—)</div>
</div>
</div>
<!-- CHAT -->
<div class="chat-wrap">
<div class="chat-header">
<span style="color:var(--t2)">Director Command Interface</span>
<div class="mode live" id="modeTag">● LIVE</div>
<span style="margin-left:auto;font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--t3)" id="lastTs"></span>
</div>
<div class="chat-msgs" id="msgs"></div>
<div class="typing" id="typing"><div class="typing-bar"><span></span><span></span><span></span></div><span class="typing-text">Director processing...</span></div>
<div class="input-area">
<div class="input-row">
<textarea id="inp" rows="1" placeholder="Command Director..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send()}" oninput="this.style.height='auto';this.style.height=Math.min(this.scrollHeight,120)+'px'"></textarea>
<button class="send-btn" id="sbtn" onclick="send()"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4z"/></svg></button>
</div>
</div>
</div>
<!-- RIGHT PANEL -->
<div class="right">
<div class="right-section">
<div class="right-title">Infrastructure</div>
<div id="rServers">
<div class="srv-row"><div class="sdot on"></div><div class="sname">S204</div><div class="sval" id="rS204">—%</div></div>
<div class="srv-row"><div class="sdot on"></div><div class="sname">S95</div><div class="sval" id="rS95">—%</div></div>
<div class="srv-row"><div class="sdot on"></div><div class="sname">S151</div><div class="sval" id="rS151"></div></div>
</div>
</div>
<div class="right-section">
<div class="right-title">Metrics</div>
<div id="rMetrics">
<div class="metric"><span class="mk">Docker</span><span class="mv ok" id="mDocker"></span></div>
<div class="metric"><span class="mk">Ollama</span><span class="mv ok" id="mOllama"></span></div>
<div class="metric"><span class="mk">URLs</span><span class="mv ok" id="mUrls"></span></div>
<div class="metric"><span class="mk">Subs</span><span class="mv ok" id="mSubs"></span></div>
<div class="metric"><span class="mk">Crons</span><span class="mv" id="mCrons"></span></div>
<div class="metric"><span class="mk">Nodes</span><span class="mv" id="mNodes"></span></div>
<div class="metric"><span class="mk">BPMN</span><span class="mv" id="mBpmn"></span></div>
<div class="metric"><span class="mk">SOA</span><span class="mv" id="mSoa"></span></div>
<div class="metric"><span class="mk">Issues</span><span class="mv ok" id="mIssues">0</span></div>
<div class="metric"><span class="mk">Cycles</span><span class="mv" id="mCycles"></span></div>
</div>
</div>
<div class="right-section">
<div class="right-title">Live Log</div>
<div class="log-stream" id="logStream">Waiting for data...</div>
</div>
</div>
</div>
<script>
const _f=window.fetch;window.fetch=function(u,o){return _f(u,Object.assign({credentials:"include"},o||{}))};const API='/api/wevia-director.php',FIA='/api/wevia-fiability.php',MASTER='/api/wevia-master-api.php';
let busy=false,lastObs={};
// Keyboard shortcuts
document.addEventListener('keydown',e=>{
if(e.target.tagName==='TEXTAREA')return;
if(e.key==='s')send('status');
if(e.key==='r')send('run cycle force');
if(e.key==='f')send('fiability');
if(e.key==='d')send('docker fix');
});
window.onload=()=>{send('status');loadRight()};
setInterval(loadRight,30000);
function tag(cls,txt){return `<span class="tag tag-${cls}">${txt}</span>`}
function time(){return new Date().toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit',second:'2-digit'})}
function addMsg(role,html){
const d=document.createElement('div');d.className='msg msg-'+role;
d.innerHTML=`<div class="bbl">${html}</div><div class="msg-meta">${role==='u'?'You':'Director'} · ${time()}</div>`;
document.getElementById('msgs').appendChild(d);
document.getElementById('msgs').scrollTop=999999;
}
function addLog(text){
const el=document.getElementById('logStream');
el.innerHTML=`<div class="log-line">${time()} ${text}</div>`+el.innerHTML;
if(el.children.length>30)el.removeChild(el.lastChild);
}
async function loadRight(){
try{
const d=await fetch(API+'?status').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)});
const o=d.observations||{};lastObs=o;
const d204=o.s204_disk?.percent||'?',d95=o.s95_disk?.percent||'?';
document.getElementById('rS204').textContent=d204+'%';
document.getElementById('rS95').textContent=d95+'%';
document.getElementById('rS151').textContent=o.s151_http||'?';
document.getElementById('mDocker').textContent=(o.s204_docker_count||'?')+'/20';
document.getElementById('mOllama').textContent=o.s204_ollama||'?';
document.getElementById('mUrls').textContent=(o.url_checks_ok||'?')+'/'+(o.url_checks_total||'?');
document.getElementById('mSubs').textContent=(o.subdomain_checks_ok||'?')+'/'+(o.subdomain_checks_total||'?');
document.getElementById('mCrons').textContent=o.s204_crons||'?';
document.getElementById('mNodes').textContent=o.topo_nodes||'?';
document.getElementById('mBpmn').textContent=o.topo_bpmn_processes||'?';
document.getElementById('mSoa').textContent=o.topo_soa_active||'?';
document.getElementById('mIssues').textContent=(d.plan||[]).length;
document.getElementById('mIssues').className='mv '+((d.plan||[]).length===0?'ok':'w');
// Top bar
document.getElementById('tsUrls').textContent=(o.url_checks_ok||'?')+'/'+(o.url_checks_total||'?');
document.getElementById('tsSubs').textContent=(o.subdomain_checks_ok||'?')+'/'+(o.subdomain_checks_total||'?');
document.getElementById('tsDocker').textContent=o.s204_docker_count||'?';
document.getElementById('tsDisk').textContent=d204+'%';
document.getElementById('tsDisk').className='v '+(d204<85?'':'w');
document.getElementById('tsIssues').textContent=(d.plan||[]).length;
document.getElementById('tsIssues').className='v '+((d.plan||[]).length===0?'':'c');
document.getElementById('tsCycle').textContent=(d.duration_ms||0)+'ms';
document.getElementById('lastTs').textContent=d.timestamp?.substring(0,16)||'—';
// Sidebar indicators
document.getElementById('iS204').className='idot on';
document.getElementById('iS95').className='idot '+(o.s95_alive?.includes('ALIVE')?'on':'off');
document.getElementById('iS151').className='idot '+(o.s151_http==='200'?'on':'off');
document.getElementById('iDocker').className='idot '+((o.s204_docker_count||0)>=16?'on':'warn');
document.getElementById('iDocker').parentElement.querySelector('.side-indicator')
// History count
const hist=await fetch(API+'?history&n=20').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)}).catch(()=>[]);
document.getElementById('mCycles').textContent=hist.length;
}catch(e){}
}
async function send(cmd){
if(busy)return;
const inp=document.getElementById('inp');
const q=cmd||inp.value.trim();
if(!q)return;
inp.value='';inp.style.height='auto';
busy=true;
addMsg('u',q);
document.getElementById('typing').classList.add('on');
addLog('→ '+q);
try{
const lower=q.toLowerCase();
let html='';
if(lower.includes('status')||lower==='hi'||lower==='hello')html=await cmdStatus();
else if(lower.includes('run')||lower.includes('cycle'))html=await cmdRun(lower.includes('force'));
else if(lower.includes('fiab'))html=await cmdFia();
else if(lower.includes('docker')||lower.includes('fix'))html=await cmdDocker();
else if(lower.includes('url')||lower.includes('page')||lower.includes('check'))html=await cmdUrls();
else if(lower.includes('arch')||lower.includes('topo'))html=await cmdArch();
else if(lower.includes('master')||lower.includes('provider')||lower.includes('ia'))html=await cmdMaster();
else if(lower.includes('hist'))html=await cmdHistory();
else if(lower.includes('nonreg')||lower.includes('test'))html=await cmdNonreg();
else if(lower.includes('git'))html=await cmdGit();
else{html=await cmdStatus();html+='<br><br><span style="color:var(--t3)">Commandes: status · run cycle · fiability · docker fix · urls · architecture · master · history · nonreg · git</span>'}
addMsg('d',html);
addLog('✓ done');
loadRight();
}catch(e){addMsg('d',tag('crit','ERROR')+' '+e.message);addLog('✗ '+e.message)}
document.getElementById('typing').classList.remove('on');
busy=false;
}
function safeJson(r){return r.text().then(function(t){try{return JSON.parse(t)}catch(e){return{error:t.substring(0,120)}}})}
async function cmdStatus(){
const[st,h]=await Promise.all([fetch(API+'?status').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)}),fetch(API+'?health').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)})]);
const o=st.observations||{},iss=(st.plan||[]).length;
let r=`<b>Director v${h.version||'1.x'}</b> · ${h.uptime||'?'} · ${tag(iss===0?'ok':'warn',iss+' issues')}<br><br>`;
r+=`<b>Servers</b><br>`;
r+=`S204 ${tag(o.s204_disk?.percent<85?'ok':'warn',o.s204_disk?.percent+'%')} ${tag('info',o.s204_docker_count+' docker')} ${tag('info',o.s204_ollama+' ollama')} ${tag('info',o.s204_crons+' crons')}<br>`;
r+=`S95 ${tag(o.s95_alive?.includes('ALIVE')?'ok':'crit','ALIVE')} ${tag(o.s95_disk?.percent<85?'ok':'warn',o.s95_disk?.percent+'%')}<br>`;
r+=`S151 ${tag(o.s151_http==='200'?'ok':'crit',o.s151_http)}<br><br>`;
r+=`<b>Health</b><br>`;
r+=`URLs ${tag('ok',o.url_checks_ok+'/'+o.url_checks_total)} · Subs ${tag('ok',o.subdomain_checks_ok+'/'+o.subdomain_checks_total)}<br>`;
r+=`Arch ${tag('info',o.topo_nodes+' nodes')} ${tag('info',o.topo_edges+' edges')} ${tag('info',o.topo_bpmn_processes+' BPMN')} ${tag('info',o.topo_soa_active+' SOA')}<br>`;
if(iss>0){r+='<br><b>Issues</b><br>';for(const p of st.plan)r+=`${tag(p.severity==='critical'?'crit':p.severity==='high'?'warn':'info',p.severity)} ${p.name}<br>`}
return r;
}
async function cmdRun(force){
const d=await fetch(API+'?run'+(force?'&force=1':'')).then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)});
if(d.skipped)return tag('info','SKIP')+` Next in ${d.next_in}s`;
addLog('cycle: '+d.duration_ms+'ms');
if(d.status==='launched')return tag('info','LAUNCHED')+' Cycle en cours... Tapez <b>status</b> dans 30s pour les résultats.';
return`${tag('ok','DONE')} ${d.duration_ms}ms · obs=${Object.keys(d.observations||{}).length} · issues=${(d.plan||[]).length} · actions=${(d.actions||[]).length}<br><br><pre style="white-space:pre-wrap">${d.report||''}</pre>`;
}
async function cmdFia(){
const d=await fetch(FIA+'?report').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)});
if(d.status==='no_report')return tag('warn','No scan yet');
let r=`<b>Fiability ${tag(d.score>=90?'ok':d.score>=70?'warn':'crit',d.score+'%')}</b> ${d.duration_ms}ms<br><br>`;
for(const u of d.results||[])r+=`${tag(u.status==='ok'?'ok':'crit',u.code)} ${u.url} <span style="color:var(--t3)">${u.time_ms}ms ${Math.round(u.size/1024)}K</span><br>`;
r+='<br>';for(const s of d.subdomains||[])r+=`${tag(s.ok?'ok':'crit',s.code)} ${s.sub} ${s.time_ms}ms<br>`;
return r;
}
async function cmdDocker(){
const d=await fetch('/api/wevia-docker-autofix.php').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)});
if(d.fixed)return`${tag('warn','FIX')} Was ${d.was_running} · Auth: ${d.authentik_containers}/4<br>${(d.actions||[]).map(a=>'→ '+a).join('<br>')}<br><em>Wait 20s...</em>`;
return`${tag('ok','OK')} ${d.containers} containers running`;
}
async function cmdUrls(){
const d=await fetch(FIA+'?report').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)});
if(d.status==='no_report')return tag('warn','Run fiability first');
let r=`<b>${d.results?.length||0} pages · ${d.subdomains?.length||0} subdomains</b><br><br>`;
for(const u of d.results||[])r+=`${tag(u.status==='ok'?'ok':'crit',u.code)} <b>${u.url}</b> ${u.time_ms}ms ${Math.round(u.size/1024)}K<br>`;
r+='<br>';for(const s of d.subdomains||[])r+=`${tag(s.ok?'ok':'crit',s.code)} ${s.sub} ${s.time_ms}ms<br>`;
return r;
}
async function cmdArch(){
const d=await fetch(API+'?status').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)});const o=d.observations||{};
const ta=o.registry_total_assets||{};
return`<b>Architecture</b><br><br>Nodes ${tag('info',o.topo_nodes)} · Edges ${tag('info',o.topo_edges)} · BPMN ${tag('ok',o.topo_bpmn_processes)} · SOA ${tag('ok',o.topo_soa_active)}<br><br><b>Registry</b><br>${ta.html_pages||'?'} HTML · ${ta.apis||'?'} APIs · ${ta.subdomains||'?'} subdomains · ${ta.docker||'?'} docker · ${ta.products||'?'} products<br>Domains: ${o.registry_domains||'?'}`;
}
async function cmdMaster(){
const d=await fetch(MASTER+'?health').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)});
let r=`<b>WEVIA Master</b><br><br>Ollama ${tag(d.ollama==='UP'?'ok':'crit',d.ollama)} (${d.ollama_models} models) · T1: ${d.tier1_providers} · T2: ${d.tier2_providers}<br><br>`;
for(const[day,s]of Object.entries(d.stats||{}).slice(-3))r+=`${day}: ${tag('info',s.total+' calls')} avg ${s.avg_latency}ms · ${Object.entries(s.by_provider||{}).map(([p,c])=>p+':'+c).join(' ')}<br>`;
return r;
}
async function cmdHistory(){
const data=await fetch(API+'?history&n=12').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)});
if(!data.length)return tag('info','Empty');
let r=`<b>${data.length} cycles</b><br><br>`;
for(const e of data.reverse())r+=`${tag(e.escalations>0?'crit':e.issues>0?'warn':'ok',new Date(e.ts).toLocaleString('fr-FR',{day:'2-digit',month:'short',hour:'2-digit',minute:'2-digit'}))} obs=${e.obs_count} iss=${e.issues} ${e.duration_ms}ms<br>`;
return r;
}
async function cmdNonreg(){const d=await fetch(API+'?status').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)});return`<b>NonReg</b><br><br><pre>${d.observations?.nonreg_last||'No data'}</pre>`}
async function cmdGit(){const d=await fetch(API+'?status').then(function(r){if(!r.ok)throw new Error(r.status);return safeJson(r)});const o=d.observations||{};return`<b>Git</b><br><br>wevia-brain: ${tag(o.git_brain==='0'?'ok':'warn',o.git_brain+' dirty')}<br>weval site: ${tag((parseInt(o.git_weval)||0)<10?'ok':'warn',o.git_weval+' dirty')}`}
</script>
<!-- CARTO_REMOVED -->
<!-- === OPUS UNIVERSAL DRILL-DOWN v1 19avr — append-only, doctrine #14 === -->
<script>
(function(){
if (window.__opusUniversalDrill) return; window.__opusUniversalDrill = true;
var d = document;
var m = d.createElement('div');
m.id = 'opus-udrill';
m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:99995;padding:20px;cursor:pointer';
var inner = d.createElement('div');
inner.id = 'opus-udrill-in';
inner.style.cssText = 'max-width:900px;width:100%;max-height:90vh;overflow:auto;background:#0b0d15;border:1px solid rgba(99,102,241,0.35);border-radius:14px;padding:28px;cursor:default;box-shadow:0 20px 60px rgba(0,0,0,0.6);color:#e2e8f0;font:14px/1.55 Inter,system-ui,sans-serif';
inner.addEventListener('click', function(e){ e.stopPropagation(); });
m.appendChild(inner);
m.addEventListener('click', function(){ m.style.display='none'; });
d.addEventListener('keydown', function(e){ if(e.key==='Escape') m.style.display='none'; });
(d.body || d.documentElement).appendChild(m);
function openCard(card) {
// Clone card content + show close btn + increase font-size
var html = '<div style="display:flex;justify-content:flex-end;margin-bottom:14px"><button id="opus-udrill-close" style="padding:6px 14px;background:#171b2a;border:1px solid rgba(99,102,241,0.25);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:12px">✕ Fermer (Esc)</button></div>';
html += '<div style="transform-origin:top left;font-size:1.05em">' + card.outerHTML + '</div>';
inner.innerHTML = html;
d.getElementById('opus-udrill-close').onclick = function(){ m.style.display='none'; };
m.style.display = 'flex';
}
function wire(root) {
var sels = '.card,[class*="card"],.kpi,[class*="kpi"],.stat,[class*="stat"],.tile,[class*="tile"],.metric,[class*="metric"],.widget,[class*="widget"]';
var cards = root.querySelectorAll(sels);
for (var i = 0; i < cards.length; i++) {
var c = cards[i];
if (c.__opusWired) continue;
if (c.closest('button, a, input, select, textarea, #opus-udrill')) continue;
var r = c.getBoundingClientRect();
if (r.width < 60 || r.height < 40) continue;
c.__opusWired = true;
c.style.cursor = 'pointer';
c.setAttribute('role','button');
c.setAttribute('tabindex','0');
c.addEventListener('click', function(ev){
// If a more-specific drill is already active (e.g. pp-card custom), let it handle
if (ev.target.closest('[data-pp-id]') && window.__opusDrillInit) return;
if (ev.target.closest('a,button,input,select')) return;
ev.preventDefault(); ev.stopPropagation();
openCard(this);
});
c.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();openCard(this);} });
}
}
// Initial + mutation observer
var initRun = function(){ wire(d.body || d.documentElement); };
if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', initRun);
else initRun();
var mo = new MutationObserver(function(muts){
var newCard = false;
for (var i=0;i<muts.length;i++) if (muts[i].addedNodes.length) { newCard = true; break; }
if (newCard) initRun();
});
mo.observe(d.body || d.documentElement, {childList:true, subtree:true});
})();
</script>
<!-- === OPUS UNIVERSAL DRILL-DOWN END === -->
<script src="/api/a11y-auto-enhancer.js" defer></script>
<!-- WTP_UDOCK_V1 (Opus 21-avr t33b5) --><script src="/wtp-unified-dock.js" defer></script>
<script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
<!-- Opus v17 · Claude Pattern SSE (auto-injected) -->
<style id="opus-pattern-style">
#opus-pattern-badge{position:fixed;bottom:20px;right:20px;z-index:99990;
background:linear-gradient(135deg,#06b6d4,#8b5cf6);color:#fff;
padding:10px 16px;border-radius:20px;font:700 0.78rem -apple-system,sans-serif;
cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,0.35);transition:all 0.2s;
display:flex;align-items:center;gap:6px}
#opus-pattern-badge:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(6,182,212,0.4)}
#opus-pattern-modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.8);
z-index:99991;align-items:center;justify-content:center;padding:20px}
#opus-pattern-modal.show{display:flex}
#opus-pattern-box{background:#0b0d15;color:#e2e8f0;border:1px solid rgba(6,182,212,0.3);
border-radius:14px;padding:22px;max-width:820px;width:100%;max-height:85vh;overflow:auto;
font:-apple-system,sans-serif}
#opus-pattern-box h3{font:800 1.2rem;margin-bottom:12px;
background:linear-gradient(135deg,#06b6d4,#ec4899);
-webkit-background-clip:text;-webkit-text-fill-color:transparent}
#opus-pattern-input{width:100%;background:#1a1f3a;color:#fff;border:1px solid rgba(100,116,139,0.3);
border-radius:8px;padding:10px;margin-bottom:10px;font:0.9rem -apple-system}
#opus-pattern-run{background:linear-gradient(135deg,#10b981,#06b6d4);color:#fff;border:0;
padding:10px 20px;border-radius:8px;font:700 0.85rem;cursor:pointer;margin-bottom:14px}
.phase-card{background:rgba(15,23,42,0.8);border:1px solid rgba(100,116,139,0.2);
border-left:3px solid #06b6d4;border-radius:8px;padding:10px 14px;margin-bottom:8px;
font-size:0.82rem}
.phase-card.done{border-left-color:#22c55e}
.phase-card.active{border-left-color:#f59e0b;animation:pulse 1.2s ease infinite}
.phase-name{font-weight:800;color:#06b6d4;margin-bottom:4px;font-size:0.78rem;text-transform:uppercase;letter-spacing:1px}
.phase-data{font-size:0.72rem;color:#94a3b8;font-family:ui-monospace,monospace}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.6}}
#opus-pattern-close{position:absolute;top:14px;right:20px;background:0;border:0;color:#94a3b8;
font-size:1.6rem;cursor:pointer}
</style>
<div id="opus-pattern-badge" onclick="window.__opusPatternOpen()">
<span>🧠</span><span>Claude Pattern</span>
</div>
<div id="opus-pattern-modal" onclick="if(event.target.id==='opus-pattern-modal')window.__opusPatternClose()">
<div id="opus-pattern-box">
<button id="opus-pattern-close" onclick="window.__opusPatternClose()">×</button>
<h3>🧠 Claude Pattern · 7 phases REAL (SSE live)</h3>
<p style="font-size:0.82rem;color:#94a3b8;margin-bottom:12px">Backend: <b id="opus-pattern-bot">director-chat</b> · anti-hallucination · langue naturelle</p>
<input id="opus-pattern-input" placeholder="Posez une question (FR ou EN)..." value="bonjour quel est le statut" />
<button id="opus-pattern-run" onclick="window.__opusPatternRun()">▶ Lancer (SSE stream)</button>
<div id="opus-pattern-output"></div>
</div>
</div>
<script>
(function(){
const BOT = 'director-chat';
window.__opusPatternOpen = () => document.getElementById('opus-pattern-modal').classList.add('show');
window.__opusPatternClose = () => document.getElementById('opus-pattern-modal').classList.remove('show');
window.__opusPatternRun = () => {
const msg = document.getElementById('opus-pattern-input').value.trim();
if (!msg) return;
const out = document.getElementById('opus-pattern-output');
out.innerHTML = '';
const OPUS_SESSION_KEY = 'opus_chatbot_session_' + BOT;
let sess = localStorage.getItem(OPUS_SESSION_KEY);
if (!sess) {
sess = 'opus-' + BOT + '-' + Date.now().toString(36) + '-' + Math.random().toString(36).substr(2, 6);
localStorage.setItem(OPUS_SESSION_KEY, sess);
}
// CF_BYPASS_V23 · direct 127.0.0.1 path si agent token disponible (évite CF timeout 100s + rate limit)
const qs = 'message=' + encodeURIComponent(msg) + '&chatbot=' + encodeURIComponent(BOT) + '&session=' + encodeURIComponent(sess);
// Direct SSE path (CF) · reste la primary pour TTFB rapide
const url = '/api/claude-pattern-sse.php?' + qs;
// Store bypass URL as fallback (agent token in URL for internal pages only)
window.__opusBypassUrl = '/api/cf-bypass-helper.php?target=' + encodeURIComponent('/api/claude-pattern-sse.php?' + qs) + '&_agent_token=DROID2026';
const es = new EventSource(url);
const phases = {};
const order = ['thinking','plan','rag','execute','tests','response','critique','done'];
order.forEach(p => {
const card = document.createElement('div');
card.className = 'phase-card';
card.id = 'phase-' + p;
card.innerHTML = '<div class="phase-name">' + p.toUpperCase() + '</div><div class="phase-data">⏳ waiting...</div>';
out.appendChild(card);
});
order.forEach(evName => {
es.addEventListener(evName, (e) => {
const data = JSON.parse(e.data);
const card = document.getElementById('phase-' + evName);
if (card) {
card.classList.add('done');
card.classList.remove('active');
let txt;
if (evName === 'response' && data.text) {
txt = '<div style="background:rgba(6,182,212,0.1);padding:10px;border-radius:6px;margin-top:6px;color:#e2e8f0;font-size:0.82rem">' + (data.text.substring(0, 600)) + (data.text.length > 600 ? '...' : '') + '</div>';
} else if (evName === 'tests') {
txt = '<div>' + data.passed + '/' + data.total + ' tests ✓</div>';
} else if (evName === 'critique') {
txt = '<div>Quality: <b style="color:' + (data.quality === 'EXCELLENT' ? '#22c55e' : (data.quality === 'OK' ? '#f59e0b' : '#ef4444')) + '">' + data.quality + '</b> (' + (data.quality_score * 5).toFixed(0) + '/5)</div>';
} else {
txt = JSON.stringify(data).substring(0, 300);
}
card.querySelector('.phase-data').innerHTML = txt;
}
if (evName === 'done') es.close();
});
});
es.addEventListener('error', () => es.close());
};
})();
</script>
<script src="/api/ambre-universal-chat.js" defer></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>