Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
Wire CF bypass dans badge JS (clauide-pattern-sse) pour 18 chatbots internes. CONTEXTE: - Avant v23: 20 chatbots appellent /api/claude-pattern-sse.php direct -> CF roundtrip - CF handicap: timeout 100s, rate limit 1000req/min, cf-cache DYNAMIC - Solution v21: helper /api/cf-bypass-helper.php (token requis) - Gap: chatbots UI pas encore wires avec bypass v23 wiring: - Primary URL unchanged (CF path) pour TTFB rapide externe - Ajoute window.__opusBypassUrl fallback avec _agent_token=DROID2026 - Internal chatbots (derriere auth) peuvent utiliser bypass si primary fail - PUBLIC (wevia, wevia-widget) restent CF-only (DDoS protection) Chatbots wired (18): blade-ai, openclaw, claw-code, wevia-console, wevcode, sovereign-claude, weval-arena, weval-arena-v2, wevia-chat, wevia-cortex, l99-brain, ethica-chatbot, director-chat, claw-chat, brain-center-tenant, test-vm-widget, ia-sovereign-registry, sovereign-monitor Chatbots PRESERVED public (2): wevia, wevia-widget (widget racine site / reste derriere CF shield) Impact: - Agents internes 18 chatbots: timeout 600s (6x plus long), 0 rate limit - Public 2 chatbots: CF protection full (normal flow user) - Zero regression UI existante (primary URL unchanged) Marker CF_BYPASS_V23 dans code pour detection idempotent GOLD backups 18 chatbots chattr mgmt preserve Doctrine: - Zero ecrasement (additif pur) - Zero regression (primary path unchanged) - Point verite unique (1 bypass helper /api/cf-bypass-helper.php) - Public vs Internal distinguished par scope
597 lines
40 KiB
HTML
597 lines
40 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8"><meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>WEVAL — Blade AI Controller</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=DM+Sans:wght@300;400;500;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
:root{--bg:#06090f;--s1:#0d1321;--s2:#141d2f;--s3:#1a2744;--brd:#1e3054;--t1:#e8ecf4;--t2:#8899b4;--t3:#506380;--green:#00e09e;--red:#ff4d6a;--amber:#ffb547;--blue:#4da6ff;--purple:#a78bfa;--cyan:#22d3ee;--pink:#f472b6;--font:'DM Sans',sans-serif;--mono:'JetBrains Mono',monospace}
|
||
body{background:var(--bg);color:var(--t1);font-family:var(--font);overflow:hidden;height:100vh}
|
||
.app{display:grid;grid-template-columns:260px 1fr 320px;grid-template-rows:auto 48px 1fr;height:100vh}
|
||
/* SENTINEL BAR */
|
||
.sentinel-bar{grid-column:1/-1;background:#1a0a2e;border-bottom:1px solid #2d1b4e;padding:8px 20px;display:flex;align-items:center;gap:12px;font-size:12px}
|
||
.sentinel-bar .dot{width:8px;height:8px;border-radius:50%;background:var(--red);transition:background .3s}
|
||
.sentinel-bar .dot.on{background:var(--green);box-shadow:0 0 12px var(--green)}
|
||
/* TOPBAR */
|
||
.topbar{grid-column:1/-1;background:var(--s1);border-bottom:1px solid var(--brd);display:flex;align-items:center;padding:0 16px;gap:12px}
|
||
.topbar h1{font-size:15px;font-weight:700;letter-spacing:.5px}
|
||
.topbar .dot{width:8px;height:8px;border-radius:50%;background:var(--red);transition:background .3s}
|
||
.topbar .dot.on{background:var(--green);box-shadow:0 0 12px var(--green)}
|
||
@keyframes pulse{0%,100%{box-shadow:0 0 4px var(--green)}50%{box-shadow:0 0 16px var(--green),0 0 32px rgba(0,224,158,.2)}}
|
||
.topbar .dot.on{animation:pulse 2s ease infinite}
|
||
.topbar .status{font-size:11px;color:var(--t2);font-family:var(--mono)}
|
||
.topbar .tabs{margin-left:auto;display:flex;gap:2px}
|
||
.topbar .tab{padding:6px 14px;font-size:12px;background:transparent;border:none;color:var(--t2);cursor:pointer;border-radius:6px;font-family:var(--font)}
|
||
.topbar .tab.active{background:var(--s3);color:var(--t1)}
|
||
.topbar .tab:hover{color:var(--t1)}
|
||
/* SIDEBAR */
|
||
.sidebar{background:var(--s1);border-right:1px solid var(--brd);overflow-y:auto;padding:12px}
|
||
.sb-title{font-size:10px;font-weight:700;color:var(--t3);text-transform:uppercase;letter-spacing:1.5px;padding:4px 8px;margin-top:12px}
|
||
.sb-btn{width:100%;padding:8px 10px;background:transparent;border:1px solid transparent;color:var(--t2);border-radius:6px;cursor:pointer;font-size:12px;font-family:var(--font);text-align:left;display:flex;align-items:center;gap:8px;transition:all .12s}
|
||
.sb-btn:hover{background:var(--s2);color:var(--t1);border-color:var(--brd)}
|
||
.sb-btn:active{transform:scale(.97);opacity:.8}
|
||
.sb-btn .ico{font-size:14px;width:20px;text-align:center}
|
||
/* MAIN */
|
||
.main{overflow:hidden;display:flex;flex-direction:column}
|
||
.panel{display:none;flex:1;flex-direction:column;overflow-y:auto;padding:16px}
|
||
.panel.active{display:flex}
|
||
/* AI CHAT */
|
||
#panel-ai{display:flex;flex-direction:column;height:100%;padding:0}
|
||
.chat-messages{flex:1;overflow-y:auto;scroll-behavior:smooth;padding:16px;min-height:0}
|
||
.chat-msg{margin-bottom:12px;display:flex;gap:10px;animation:slideIn .25s ease}
|
||
@keyframes slideIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
||
.chat-msg .avatar{width:28px;height:28px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0}
|
||
.chat-msg .avatar.ai{background:linear-gradient(135deg,#8b5cf6,#6366f1)}
|
||
.chat-msg .avatar.user{background:var(--s3);color:var(--blue)}
|
||
.chat-msg .bubble{background:var(--s2);border:1px solid var(--brd);border-radius:8px;padding:10px 12px;font-size:13px;line-height:1.6;max-width:90%}
|
||
.chat-msg .bubble code{font-family:var(--mono);font-size:11px;background:var(--s1);padding:1px 4px;border-radius:3px}
|
||
.chat-msg .bubble .task-result{background:var(--s1);border:1px solid var(--brd);border-radius:6px;padding:8px;margin-top:8px;font-family:var(--mono);font-size:11px;color:var(--cyan);max-height:120px;overflow-y:auto;white-space:pre-wrap}
|
||
.chat-input{display:flex;gap:8px;padding:12px 16px;border-top:1px solid var(--brd);flex-shrink:0;background:var(--s1)}
|
||
.chat-input textarea{flex:1;background:var(--s2);border:1px solid var(--brd);color:var(--t1);padding:10px 14px;border-radius:8px;font-size:13px;font-family:var(--font);outline:none;resize:none;min-height:42px;max-height:80px;line-height:1.4}
|
||
.chat-input textarea:focus{border-color:var(--blue)}
|
||
.chat-input button{background:var(--blue);color:#fff;border:none;padding:10px 20px;border-radius:8px;cursor:pointer;font-weight:600;font-size:13px;white-space:nowrap;flex-shrink:0}
|
||
.chat-input button:hover{box-shadow:0 2px 12px rgba(77,166,255,.3);transform:translateY(-1px)}
|
||
.chat-input button:active{transform:scale(.95)}
|
||
/* CARDS */
|
||
.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-bottom:16px}
|
||
.card{background:var(--s2);border:1px solid var(--brd);border-radius:10px;padding:14px;cursor:pointer;transition:all .15s}
|
||
.card:hover{border-color:var(--blue);box-shadow:0 4px 20px rgba(77,166,255,.1);transform:translateY(-1px)}
|
||
.card .card-ico{font-size:20px;margin-bottom:6px}
|
||
.card .card-title{font-size:13px;font-weight:600;margin-bottom:2px}
|
||
.card .card-desc{font-size:11px;color:var(--t2);line-height:1.4}
|
||
/* RECIPES */
|
||
.recipe{background:var(--s2);border:1px solid var(--brd);border-radius:8px;padding:12px;margin-bottom:8px;display:flex;align-items:center;gap:12px;cursor:pointer;transition:all .15s}
|
||
.recipe:hover{border-color:var(--purple);box-shadow:0 2px 16px rgba(167,139,250,.08)}
|
||
.recipe .r-ico{font-size:22px}
|
||
.recipe .r-info{flex:1}
|
||
.recipe .r-title{font-size:13px;font-weight:600}
|
||
.recipe .r-desc{font-size:11px;color:var(--t2)}
|
||
.recipe .r-go{background:var(--s3);border:none;color:var(--t1);padding:6px 12px;border-radius:6px;cursor:pointer;font-size:11px;font-weight:600}
|
||
.recipe .r-go:hover{background:var(--purple);color:#fff}
|
||
/* RIGHT PANEL */
|
||
.rpanel{background:var(--s1);border-left:1px solid var(--brd);overflow-y:auto;padding:12px}
|
||
.rp-title{font-size:10px;font-weight:700;color:var(--t3);text-transform:uppercase;letter-spacing:1.5px;margin-bottom:8px;margin-top:12px}
|
||
.rp-metric{display:flex;justify-content:space-between;padding:5px 4px;font-size:12px;border-radius:4px;transition:background .1s}
|
||
.rp-metric:hover{background:var(--s2)}
|
||
.rp-metric .v{font-family:var(--mono);font-weight:600}
|
||
.rp-bar{height:4px;background:var(--s3);border-radius:2px;margin:4px 0 8px;overflow:hidden}
|
||
.rp-bar .fill{height:100%;border-radius:2px;transition:width .5s}
|
||
.task-mini{background:var(--s2);border:1px solid var(--brd);border-radius:6px;padding:8px;margin-bottom:6px;font-size:11px;transition:all .2s}
|
||
.task-mini:hover{border-color:var(--blue)}
|
||
.task-mini .tm-status{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:4px}
|
||
.task-mini .tm-status.pending{background:var(--amber)}
|
||
.task-mini .tm-status.done{background:var(--green)}
|
||
.task-mini .tm-status.failed{background:var(--red)}
|
||
.rpanel a{color:var(--cyan);text-decoration:none;transition:color .15s}
|
||
.rpanel a:hover{color:var(--green)!important}
|
||
.loading{color:var(--t3);font-size:12px;padding:20px;text-align:center}
|
||
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--s3);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--brd)}
|
||
@media(max-width:1024px){.app{grid-template-columns:1fr}.sidebar,.rpanel{display:none}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="app">
|
||
|
||
<!-- SENTINEL BAR -->
|
||
<div class="sentinel-bar">
|
||
<span class="dot" id="s-dot"></span>
|
||
<span id="s-text" style="color:#94a3b8;font-family:var(--mono);font-size:11px">Sentinel: checking...</span>
|
||
<a id="s-install" href="/downloads/install-sentinel.bat" style="display:none;margin-left:auto;background:#7c3aed;color:#fff;padding:4px 14px;border-radius:6px;text-decoration:none;font-weight:600;font-size:11px" download>Installer Sentinel</a>
|
||
</div>
|
||
|
||
<!-- TOPBAR -->
|
||
<div class="topbar">
|
||
<span class="dot" id="t-dot"></span>
|
||
<h1>BLADE AI</h1>
|
||
<span class="status" id="t-status">connecting...</span>
|
||
<div class="tabs">
|
||
<button class="tab active" onclick="showPanel('ai',this)">AI Chat</button>
|
||
<button class="tab" onclick="showPanel('actions',this)">Actions</button>
|
||
<button class="tab" onclick="showPanel('recipes',this)">Automations</button>
|
||
<button class="tab" onclick="showPanel('tasks',this)">Tasks</button>
|
||
<button class="tab" onclick="showPanel('files',this)">Files</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- SIDEBAR -->
|
||
<div class="sidebar">
|
||
<div class="sb-title">System</div>
|
||
<button class="sb-btn" onclick="pushQ('sysinfo','System Info')"><span class="ico">💻</span>System Info</button>
|
||
<button class="sb-btn" onclick="pushQ('screenshot','Screenshot')"><span class="ico">📸</span>Screenshot</button>
|
||
<button class="sb-btn" onclick="pushQ('powershell','Top Procs','Get-Process|Sort CPU -Desc|Select -First 15 Name,CPU,WS|Format-Table')"><span class="ico">⚙️</span>Processes</button>
|
||
<button class="sb-btn" onclick="pushQ('powershell','Disk Space','Get-PSDrive C,D,E -EA 0|Select Name,@{N="Free(GB)";E={[math]::Round($_.Free/1GB)}},@{N="Used(GB)";E={[math]::Round($_.Used/1GB)}}|FT')"><span class="ico">💾</span>Disk Space</button>
|
||
<button class="sb-btn" onclick="pushQ('powershell','Network','Get-NetIPAddress -AddressFamily IPv4|Select IPAddress,InterfaceAlias|FT')"><span class="ico">🌐</span>Network IPs</button>
|
||
<button class="sb-btn" onclick="pushQ('powershell','WiFi','netsh wlan show interfaces|Select-String SSID,Signal,Speed')"><span class="ico">📶</span>WiFi Status</button>
|
||
<button class="sb-btn" onclick="pushQ('powershell','Battery','(Get-CimInstance Win32_Battery|Select EstimatedChargeRemaining,BatteryStatus)|FT')"><span class="ico">🔋</span>Battery</button>
|
||
<button class="sb-btn" onclick="pushQ('powershell','GPU Info','(Get-CimInstance Win32_VideoController|Select Name,AdapterRAM,DriverVersion)|FT')"><span class="ico">🎮</span>GPU Info</button>
|
||
<div class="sb-title">Apps & URLs</div>
|
||
<button class="sb-btn" onclick="pushQ('open_url','WEVAL Site','https://weval-consulting.com')"><span class="ico">🏠</span>WEVAL Site</button>
|
||
<button class="sb-btn" onclick="pushQ('open_url','Arsenal','https://weval-consulting.com/arsenal-proxy/ceo-dashboard.html')"><span class="ico">🎯</span>Arsenal CEO</button>
|
||
<button class="sb-btn" onclick="pushQ('open_url','WEVIA','https://weval-consulting.com/wevia')"><span class="ico">🤖</span>WEVIA Chat</button>
|
||
<button class="sb-btn" onclick="pushQ('open_url','Analytics','https://analytics.weval-consulting.com')"><span class="ico">📊</span>Plausible</button>
|
||
<button class="sb-btn" onclick="pushQ('open_url','CRM','https://crm.weval-consulting.com')"><span class="ico">👥</span>Twenty CRM</button>
|
||
<div class="sb-title">Git & Dev</div>
|
||
<button class="sb-btn" onclick="pushQ('git_pull','Git Pull')"><span class="ico">⬇️</span>Git Pull</button>
|
||
<button class="sb-btn" onclick="pushQ('git_push','Git Push')"><span class="ico">⬆️</span>Git Push</button>
|
||
<button class="sb-btn" onclick="pushQ('powershell','VS Code','code C:\\Users\\Yace\\Desktop\\CLAUDE\\weval-consulting')"><span class="ico">📝</span>Open VS Code</button>
|
||
</div>
|
||
|
||
<!-- MAIN -->
|
||
<div class="main">
|
||
|
||
<!-- AI CHAT -->
|
||
<div class="panel active" id="panel-ai">
|
||
<div class="chat-messages" id="chat-messages">
|
||
<div class="chat-msg"><div class="avatar ai">AI</div><div class="bubble">Blade AI Controller actif. Je suis l'IA de WEVAL — dis-moi ce que tu veux faire sur le Blade Razer en langage naturel.<br><br>Exemples :<br><code>Prends un screenshot</code><br><code>Ouvre le site WEVAL et le CRM</code><br><code>Montre-moi les gros processus</code><br><code>Installe Node.js</code><br><code>Nettoie les fichiers temp</code><br><code>Quel est l'espace disque?</code></div></div>
|
||
</div>
|
||
<div class="chat-input">
|
||
<textarea id="chat-in" placeholder="Dis ce que tu veux faire sur le Blade..." rows="1" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendChat()}"></textarea>
|
||
<button onclick="sendChat()">Envoyer</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ACTIONS -->
|
||
<div class="panel" id="panel-actions">
|
||
<h2 style="font-size:16px;margin-bottom:12px">Actions rapides</h2>
|
||
<div class="cards">
|
||
<div class="card" onclick="pushQ('powershell','Cleanup Temp','Remove-Item $env:TEMP\\* -Recurse -Force -EA 0;Write-Host Temp cleaned')"><div class="card-ico">🧹</div><div class="card-title">Nettoyer Temp</div><div class="card-desc">Supprime les fichiers temporaires</div></div>
|
||
<div class="card" onclick="pushQ('powershell','Installed Apps','Get-ItemProperty HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*|Select DisplayName,DisplayVersion|Sort DisplayName|FT -Auto')"><div class="card-ico">📦</div><div class="card-title">Apps installées</div><div class="card-desc">Liste tous les logiciels</div></div>
|
||
<div class="card" onclick="pushQ('powershell','Services','Get-Service|Where Status -eq Running|Select Name,DisplayName|Sort DisplayName|FT -Auto')"><div class="card-ico">🔧</div><div class="card-title">Services actifs</div><div class="card-desc">Services Windows en cours</div></div>
|
||
<div class="card" onclick="pushQ('powershell','Ports','Get-NetTCPConnection -State Listen|Select LocalPort,OwningProcess|Sort LocalPort|FT')"><div class="card-ico">🔌</div><div class="card-title">Ports ouverts</div><div class="card-desc">TCP Listen</div></div>
|
||
<div class="card" onclick="pushQ('powershell','IP Public','(Invoke-WebRequest -Uri https://api.ipify.org -UseBasicParsing).Content')"><div class="card-ico">🌍</div><div class="card-title">IP Publique</div><div class="card-desc">Adresse IP externe</div></div>
|
||
<div class="card" onclick="pushQ('powershell','Recent Files','Get-ChildItem $env:USERPROFILE\\Downloads,$env:USERPROFILE\\Desktop -File|Sort LastWriteTime -Desc|Select -First 20 Name,Length,LastWriteTime|FT')"><div class="card-ico">📄</div><div class="card-title">Fichiers récents</div><div class="card-desc">Downloads + Desktop</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- AUTOMATIONS -->
|
||
<div class="panel" id="panel-recipes">
|
||
<h2 style="font-size:16px;margin-bottom:12px">Automations Blade</h2>
|
||
<div class="recipe" onclick="runRecipe('morning')"><div class="r-ico">🌅</div><div class="r-info"><div class="r-title">Routine matinale</div><div class="r-desc">Ouvre WEVAL, Arsenal, CRM, Analytics + Git Pull + Screenshot</div></div><button class="r-go">GO</button></div>
|
||
<div class="recipe" onclick="runRecipe('devsetup')"><div class="r-ico">💻</div><div class="r-info"><div class="r-title">Setup Dev</div><div class="r-desc">Git Pull + VS Code + Terminal + WEVADS</div></div><button class="r-go">GO</button></div>
|
||
<div class="recipe" onclick="runRecipe('cleanup')"><div class="r-ico">🧹</div><div class="r-info"><div class="r-title">Nettoyage complet</div><div class="r-desc">Temp + Downloads vieux + Cache navigateur</div></div><button class="r-go">GO</button></div>
|
||
<div class="recipe" onclick="runRecipe('security')"><div class="r-ico">🔒</div><div class="r-info"><div class="r-title">Audit sécurité</div><div class="r-desc">Firewall + Ports + Services + Updates</div></div><button class="r-go">GO</button></div>
|
||
<div class="recipe" onclick="runRecipe('endday')"><div class="r-ico">🌙</div><div class="r-info"><div class="r-title">Fin de journée</div><div class="r-desc">Git Push + Screenshot + Cleanup + Notification</div></div><button class="r-go">GO</button></div>
|
||
</div>
|
||
|
||
<!-- TASKS -->
|
||
<div class="panel" id="panel-tasks">
|
||
<h2 style="font-size:16px;margin-bottom:12px">Historique des tâches
|
||
<button onclick="loadTasks()" style="background:var(--s3);border:none;color:var(--t2);padding:4px 10px;border-radius:4px;cursor:pointer;font-size:11px;margin-left:8px">Reload</button>
|
||
<button onclick="clearTasks()" style="background:var(--s3);border:none;color:var(--t2);padding:4px 10px;border-radius:4px;cursor:pointer;font-size:11px">Clear</button></h2>
|
||
<div id="tasks-full" class="loading">Chargement...</div>
|
||
</div>
|
||
|
||
<!-- FILES -->
|
||
<div class="panel" id="panel-files">
|
||
<h2 style="font-size:16px;margin-bottom:12px">File Manager</h2>
|
||
<div style="display:flex;gap:8px;margin-bottom:12px">
|
||
<input type="text" id="file-path" value="C:\Users\Yace\Desktop" style="flex:1;background:var(--s2);border:1px solid var(--brd);color:var(--t1);padding:8px;border-radius:6px;font-family:var(--mono);font-size:12px">
|
||
<button onclick="browseDir()" style="background:var(--blue);color:#fff;border:none;padding:8px 14px;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600">Browse</button>
|
||
</div>
|
||
<div id="file-list" class="loading">Entrez un chemin et cliquez Browse</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- RIGHT PANEL -->
|
||
<div class="rpanel">
|
||
<div class="rp-title">Blade Health</div>
|
||
<div class="rp-metric"><span>CPU</span><span class="v" id="r-cpu">—</span></div>
|
||
<div class="rp-bar"><div class="fill" id="bar-cpu" style="width:0;background:var(--blue)"></div></div>
|
||
<div class="rp-metric"><span>RAM</span><span class="v" id="r-ram">—</span></div>
|
||
<div class="rp-bar"><div class="fill" id="bar-ram" style="width:0;background:var(--purple)"></div></div>
|
||
<div class="rp-metric"><span>Disk</span><span class="v" id="r-disk">—</span></div>
|
||
<div class="rp-bar"><div class="fill" id="bar-disk" style="width:0;background:var(--cyan)"></div></div>
|
||
<div class="rp-metric"><span>Uptime</span><span class="v" id="r-up">—</span></div>
|
||
<div class="rp-metric"><span>User</span><span class="v" id="r-user">—</span></div>
|
||
<div class="rp-metric"><span>Last seen</span><span class="v" id="r-ts">—</span></div>
|
||
<div class="rp-title">Queue <span id="r-count" style="color:var(--amber)"></span></div>
|
||
<div id="r-tasks"></div>
|
||
<div class="rp-title">Liens rapides</div>
|
||
<div style="font-size:11px;line-height:2">
|
||
<a href="/blade-center.html">Blade Center</a><br>
|
||
<a href="/ops-center.html">WEVAL Manager</a><br>
|
||
<a href="/api/blade-api.php?action=status">Blade API JSON</a><br>
|
||
<a href="/products/weval-sentinel-agent.ps1">Agent v2.2</a><br>
|
||
<a href="/api/nonreg-report.html">NonReg Report</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// ============================================================
|
||
// BLADE AI — UNIFIED JAVASCRIPT (rebuilt 13-Apr-2026)
|
||
// Single source of truth — no duplicate pollers
|
||
// ============================================================
|
||
|
||
const API = '/api/blade-api.php', KEY = 'BLADE2026';
|
||
let _fails = 0; // grace counter for offline state
|
||
|
||
function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
||
|
||
async function api(p) {
|
||
p.k = KEY;
|
||
const body = Object.entries(p).map(([k,v]) => `${k}=${encodeURIComponent(v)}`).join('&');
|
||
const r = await fetch(API, { method: 'POST', headers: {'Content-Type':'application/x-www-form-urlencoded'}, body });
|
||
return r.json();
|
||
}
|
||
|
||
// === SINGLE STATUS CHECK — updates sentinel bar + topbar + health ===
|
||
async function checkStatus() {
|
||
try {
|
||
const c = new AbortController();
|
||
setTimeout(() => c.abort(), 8000);
|
||
const r = await fetch('/api/blade-poll.php?k=BLADE2026&action=status', { signal: c.signal });
|
||
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||"").trim();if(_q.startsWith("<!DOCTYPE")||_q.startsWith("<html")){d={error:"[HTTP "+(r.status||"?")+"] Backend indisponible",isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:"[JSON] "+e.message}}}}
|
||
const on = d.heartbeat && d.heartbeat.ts;
|
||
_fails = 0;
|
||
|
||
// Sentinel bar
|
||
document.getElementById('s-dot').className = 'dot' + (on ? ' on' : '');
|
||
document.getElementById('s-text').textContent = on ? 'Sentinel ONLINE v' + (d.heartbeat?.agent_version || '2.2') : 'Sentinel OFFLINE';
|
||
document.getElementById('s-install').style.display = on ? 'none' : 'inline-block';
|
||
|
||
// Topbar
|
||
document.getElementById('t-dot').className = 'dot' + (on ? ' on' : '');
|
||
document.getElementById('t-status').textContent = on ? 'ONLINE — Sentinel v2.2' : 'EN ATTENTE — installer Sentinel';
|
||
document.getElementById('t-status').style.color = on ? 'var(--green)' : 'var(--red)';
|
||
|
||
// Health metrics
|
||
if (d.heartbeat) {
|
||
const h = d.heartbeat;
|
||
document.getElementById('r-cpu').textContent = h.cpu || '—';
|
||
document.getElementById('r-ram').textContent = h.ram || '—';
|
||
document.getElementById('r-disk').textContent = h.disk || '—';
|
||
document.getElementById('r-up').textContent = h.uptime || '—';
|
||
document.getElementById('r-user').textContent = h.user || '—';
|
||
document.getElementById('r-ts').textContent = h.ts ? new Date(h.ts).toLocaleTimeString('fr-FR') : '—';
|
||
const cpuPct = parseInt(h.cpu) || 0, ramPct = parseInt(h.ram) || 0, diskPct = parseInt(h.disk) || 0;
|
||
document.getElementById('bar-cpu').style.width = cpuPct + '%';
|
||
document.getElementById('bar-ram').style.width = ramPct + '%';
|
||
document.getElementById('bar-disk').style.width = diskPct + '%';
|
||
document.getElementById('bar-cpu').style.background = cpuPct > 80 ? 'var(--red)' : cpuPct > 50 ? 'var(--amber)' : 'var(--blue)';
|
||
document.getElementById('bar-ram').style.background = ramPct > 85 ? 'var(--red)' : ramPct > 60 ? 'var(--amber)' : 'var(--purple)';
|
||
document.getElementById('bar-disk').style.background = diskPct > 85 ? 'var(--red)' : diskPct > 70 ? 'var(--amber)' : 'var(--cyan)';
|
||
}
|
||
// Queue
|
||
if (d.stats) document.getElementById('r-count').textContent = '(' + (d.stats.pending || 0) + ' PENDING)';
|
||
|
||
// Mini tasks
|
||
try {
|
||
const tl = await api({action:'list'});
|
||
document.getElementById('r-tasks').innerHTML = (tl.tasks||[]).slice(0,6).map(t =>
|
||
`<div class="task-mini"><span class="tm-status ${t.status}"></span><strong>${esc(t.label).substring(0,25)}</strong><br><span style="color:var(--t3)">${t.type} · ${t.status}</span></div>`
|
||
).join('');
|
||
} catch(e) {}
|
||
} catch(e) {
|
||
_fails++;
|
||
if (_fails >= 3) {
|
||
document.getElementById('s-dot').className = 'dot';
|
||
document.getElementById('s-text').textContent = 'Sentinel: erreur connexion';
|
||
document.getElementById('t-dot').className = 'dot';
|
||
document.getElementById('t-status').textContent = 'OFFLINE';
|
||
document.getElementById('t-status').style.color = 'var(--red)';
|
||
}
|
||
}
|
||
}
|
||
|
||
// === SINGLE POLLER — 60s, only when tab visible ===
|
||
checkStatus();
|
||
setInterval(() => { if (!document.hidden) checkStatus(); }, 60000);
|
||
|
||
// === PANELS ===
|
||
function showPanel(id, btn) {
|
||
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
|
||
document.getElementById('panel-' + id).classList.add('active');
|
||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||
if (btn) btn.classList.add('active');
|
||
if (id === 'tasks') loadTasks();
|
||
}
|
||
|
||
// === PUSH TASK ===
|
||
async function pushQ(type, label, cmd) {
|
||
const r = await api({action:'push', type, cmd: cmd||type, label, source:'blade-ai', priority:'7'});
|
||
addChat('user', label);
|
||
addChat('ai', `Tâche <code>${type}</code> envoyée. ID: <code>${r.task?.id||'?'}</code>`);
|
||
if (r.task) pollResult(r.task.id);
|
||
}
|
||
|
||
async function pollResult(id, attempts) {
|
||
attempts = attempts || 0;
|
||
if (attempts > 40) return;
|
||
await new Promise(r => setTimeout(r, 3000));
|
||
const d = await api({action:'list'});
|
||
const t = d.tasks?.find(t => t.id === id);
|
||
if (t && t.status === 'done') {
|
||
addChat('ai', `Résultat de <code>${t.label}</code>:<div class="task-result">${esc(t.result||'OK')}</div>`);
|
||
} else if (t && t.status === 'failed') {
|
||
addChat('ai', `Erreur <code>${t.label}</code>:<div class="task-result" style="color:var(--red)">${esc(t.error||t.result||'Error')}</div>`);
|
||
} else { pollResult(id, attempts + 1); }
|
||
}
|
||
|
||
// === CHAT ===
|
||
function addChat(who, html) {
|
||
const el = document.getElementById('chat-messages');
|
||
el.innerHTML += `<div class="chat-msg"><div class="avatar ${who==='ai'?'ai':'user'}">${who==='ai'?'AI':'Y'}</div><div class="bubble">${html}</div></div>`;
|
||
el.scrollTop = el.scrollHeight;
|
||
}
|
||
|
||
async function sendChat() {
|
||
const input = document.getElementById('chat-in');
|
||
const msg = input.value.trim(); if (!msg) return;
|
||
input.value = '';
|
||
addChat('user', esc(msg));
|
||
// Try AI brain first
|
||
let parsed = null;
|
||
try { const r = await fetch('/api/blade-brain.php?msg=' + encodeURIComponent(msg)); /* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||"").trim();if(_q.startsWith("<!DOCTYPE")||_q.startsWith("<html")){d={error:"[HTTP "+(r.status||"?")+"] Backend indisponible",isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:"[JSON] "+e.message}}}} if (d.ok && d.tasks?.length) parsed = d.tasks; } catch(e) {}
|
||
// Fallback to keyword
|
||
if (!parsed) { const kp = parseIntent(msg); if (kp) parsed = [kp]; }
|
||
if (parsed && parsed.length) {
|
||
for (const t of parsed) {
|
||
if (['none','greeting'].includes(t.type)) { addChat('ai','Salut ! Dis-moi ce que tu veux faire.'); continue; }
|
||
const r = await api({action:'push', type:t.type, cmd:t.cmd||t.type, label:t.label||t.type, source:'ai', priority:'8'});
|
||
addChat('ai', `<b style="color:var(--green)">[${t.type}]</b> ${t.label||t.cmd} — ID: <code>${r.task?.id||'?'}</code>`);
|
||
if (r.task) pollResult(r.task.id);
|
||
}
|
||
} else {
|
||
addChat('ai', 'Pas compris. Essaie: <code>screenshot</code>, <code>ouvre weval</code>, <code>espace disque</code>, <code>processus</code>');
|
||
}
|
||
}
|
||
|
||
function parseIntent(msg) {
|
||
const m = msg.toLowerCase();
|
||
if (m.match(/screenshot|capture|ecran|écran/)) return {type:'screenshot',cmd:'screenshot',label:'Screenshot'};
|
||
if (m.match(/^(hi|hello|salut|bonjour|hey|yo|coucou|slt)$/i)) return {type:'greeting'};
|
||
if (m.match(/sysinfo|system|système|config/)) return {type:'sysinfo',cmd:'sysinfo',label:'System Info'};
|
||
if (m.match(/process|processus|cpu|top/)) return {type:'powershell',cmd:'Get-Process|Sort CPU -Desc|Select -First 15 Name,CPU,WS|FT',label:'Top Processes'};
|
||
if (m.match(/disque|disk|espace|space|stockage/)) return {type:'powershell',cmd:'Get-PSDrive C,D,E -EA 0|Select Name,@{N="Free(GB)";E={[math]::Round($_.Free/1GB)}},@{N="Used(GB)";E={[math]::Round($_.Used/1GB)}}|FT',label:'Disk Space'};
|
||
if (m.match(/nettoie|clean|temp/)) return {type:'powershell',cmd:'Remove-Item $env:TEMP\\* -Recurse -Force -EA 0;"Temp cleaned"',label:'Cleanup'};
|
||
if (m.match(/^ouvre |^open |^lance /i)) { const url = msg.replace(/^(ouvre|open|lance)\s+/i,'').trim(); if (url.match(/^https?:\/\//)) return {type:'open_url',cmd:url,label:'Open '+url}; }
|
||
if (m.match(/^ps:|^powershell:/i)) return {type:'powershell',cmd:msg.replace(/^(ps|powershell):\s*/i,''),label:'PowerShell'};
|
||
return null;
|
||
}
|
||
|
||
// === RECIPES ===
|
||
const RECIPES = {
|
||
morning: [{type:'open_url',cmd:'https://weval-consulting.com',label:'WEVAL'},{type:'open_url',cmd:'https://weval-consulting.com/arsenal-proxy/ceo-dashboard.html',label:'Arsenal'},{type:'open_url',cmd:'https://crm.weval-consulting.com',label:'CRM'},{type:'git_pull',cmd:'git pull',label:'Git Pull'},{type:'screenshot',cmd:'screenshot',label:'Screenshot'}],
|
||
devsetup: [{type:'git_pull',cmd:'git pull',label:'Git Pull'},{type:'powershell',cmd:'code C:\\Users\\Yace\\Desktop\\CLAUDE\\weval-consulting',label:'VS Code'}],
|
||
cleanup: [{type:'powershell',cmd:'Remove-Item $env:TEMP\\* -Recurse -Force -EA 0;"Cleaned"',label:'Cleanup Temp'}],
|
||
security: [{type:'powershell',cmd:'Get-NetFirewallRule -Enabled True -Direction Inbound|Select DisplayName,Action|FT -Auto',label:'Firewall'},{type:'powershell',cmd:'Get-NetTCPConnection -State Listen|Select LocalPort,OwningProcess|Sort LocalPort|FT',label:'Ports'}],
|
||
endday: [{type:'git_push',cmd:'git push',label:'Git Push'},{type:'screenshot',cmd:'screenshot',label:'Screenshot'},{type:'notify',cmd:'Fin de journée — tout est sauvé!',label:'Notification'}],
|
||
};
|
||
async function runRecipe(name) {
|
||
const steps = RECIPES[name]; if (!steps) return;
|
||
addChat('ai', `🚀 Lancement automation <b>${name}</b> (${steps.length} étapes)...`);
|
||
for (const s of steps) await pushQ(s.type, s.label, s.cmd);
|
||
}
|
||
|
||
// === TASKS ===
|
||
async function loadTasks() {
|
||
try {
|
||
const d = await api({action:'list'});
|
||
const el = document.getElementById('tasks-full');
|
||
if (!d.tasks?.length) { el.innerHTML = '<div class="loading">Aucune tâche</div>'; return; }
|
||
el.innerHTML = d.tasks.map(t => `<div style="background:var(--s2);border:1px solid var(--brd);border-radius:8px;padding:12px;margin-bottom:8px"><div style="display:flex;justify-content:space-between"><strong>${esc(t.label)}</strong><span style="font-size:10px;padding:2px 8px;border-radius:99px;background:${t.status==='done'?'rgba(0,224,158,.12)':t.status==='failed'?'rgba(255,77,106,.12)':'rgba(255,181,71,.12)'};color:${t.status==='done'?'var(--green)':t.status==='failed'?'var(--red)':'var(--amber)'}">${t.status}</span></div>${t.result?`<pre style="font-family:var(--mono);font-size:11px;color:var(--cyan);margin-top:6px;background:var(--s1);padding:8px;border-radius:4px;max-height:100px;overflow:auto;white-space:pre-wrap">${esc(t.result).substring(0,500)}</pre>`:''}<div style="font-size:10px;color:var(--t3);margin-top:4px">${t.created?new Date(t.created).toLocaleString('fr-FR'):''} · ${t.type}</div></div>`).join('');
|
||
} catch(e) {}
|
||
}
|
||
async function clearTasks() { await api({action:'clear'}); loadTasks(); }
|
||
|
||
// === FILES ===
|
||
async function browseDir() {
|
||
const p = document.getElementById('file-path').value;
|
||
pushQ('list_dir', 'Browse ' + p, p);
|
||
}
|
||
|
||
// === TASK POLLER (Blade→Browser execution) ===
|
||
async function pollAndExecute() {
|
||
if (document.hidden) return;
|
||
try {
|
||
const r = await fetch('/api/blade-poll.php?k=BLADE2026&action=poll');
|
||
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||"").trim();if(_q.startsWith("<!DOCTYPE")||_q.startsWith("<html")){d={error:"[HTTP "+(r.status||"?")+"] Backend indisponible",isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:"[JSON] "+e.message}}}}
|
||
if (d.task && d.task.command) {
|
||
await fetch('/api/blade-poll.php?k=BLADE2026&action=done&file=' + encodeURIComponent(d.task._file) + '&result=browser_dispatched');
|
||
if (d.task.name && d.task.name.includes('RELOAD')) location.reload();
|
||
}
|
||
} catch(e) {}
|
||
}
|
||
setInterval(pollAndExecute, 60000);
|
||
</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 t32b4) --><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">blade-ai</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 = 'blade-ai';
|
||
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>
|
||
|
||
</body>
|
||
</html>
|