Files
html/openclaw.html

538 lines
28 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="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenClaw — WEVAL AI Gateway</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--bg:#0a0a0f;--bg2:#12121a;--bg3:#1a1a28;--bg4:#222235;--tx:#e8e6f0;--tx2:#9896a8;--tx3:#5c5a6e;--ac:#00e5ff;--ac2:#7c4dff;--ac3:#00e676;--ac4:#ff6e40;--bd:#2a2a3e;--red:#ff5252}
html,body{height:100%;background:var(--bg);color:var(--tx);font-family:'Outfit',sans-serif;overflow:hidden}
.app{display:grid;grid-template-columns:260px 1fr;height:100vh}
.sidebar{background:var(--bg2);border-right:1px solid var(--bd);display:flex;flex-direction:column;overflow-y:auto}
.sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:var(--bd);border-radius:2px}
.sidebar-header{padding:16px 14px 10px;border-bottom:1px solid var(--bd)}
.logo{display:flex;align-items:center;gap:8px}
.logo-icon{width:32px;height:32px;background:linear-gradient(135deg,var(--ac),var(--ac2));border-radius:8px;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;color:#000;font-family:'JetBrains Mono',monospace}
.logo-text{font-size:16px;font-weight:600;letter-spacing:-.5px}
.logo-sub{font-size:9px;color:var(--tx3);text-transform:uppercase;letter-spacing:1.5px;margin-top:1px}
.logo-count{margin-left:auto;font-size:10px;color:var(--ac);font-family:'JetBrains Mono',monospace;background:rgba(0,229,255,.1);padding:2px 6px;border-radius:4px}
.section{padding:10px 14px 4px}
.section-label{font-size:9px;text-transform:uppercase;letter-spacing:1.5px;color:var(--tx3);margin-bottom:6px;font-weight:500;display:flex;align-items:center;gap:6px}
.tier-dot{width:6px;height:6px;border-radius:50%}
.tier-dot.free{background:var(--ac3)}.tier-dot.paid{background:var(--ac4)}.tier-dot.sovereign{background:var(--ac)}
.provider-btn{background:var(--bg3);border:1px solid transparent;border-radius:6px;padding:7px 10px;cursor:pointer;text-align:left;transition:all .12s;color:var(--tx2);font-family:'Outfit',sans-serif;font-size:12px;display:flex;align-items:center;gap:6px;width:100%;margin-bottom:3px}
.provider-btn:hover{background:var(--bg4);color:var(--tx)}
.provider-btn.active{background:rgba(0,229,255,.08);border-color:rgba(0,229,255,.3);color:var(--ac)}
.provider-btn .name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.speed{font-size:8px;padding:1px 4px;border-radius:3px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}
.speed.ultra{background:rgba(0,229,255,.15);color:var(--ac)}.speed.fast{background:rgba(0,230,118,.12);color:var(--ac3)}.speed.slow{background:rgba(255,110,64,.1);color:var(--ac4)}
.mcnt{font-size:9px;color:var(--tx3);font-family:'JetBrains Mono',monospace}
select{width:100%;background:var(--bg3);border:1px solid var(--bd);border-radius:6px;padding:6px 8px;color:var(--tx);font-family:'JetBrains Mono',monospace;font-size:11px;outline:none;cursor:pointer;-webkit-appearance:none}
select:focus{border-color:var(--ac)}
.ctrl-section{padding:8px 14px}
.ctrl-label{font-size:9px;text-transform:uppercase;letter-spacing:1px;color:var(--tx3);margin-bottom:4px;font-weight:500}
.temp-row{display:flex;align-items:center;gap:6px}
.temp-row input[type=range]{flex:1;accent-color:var(--ac);height:3px;-webkit-appearance:none;background:var(--bd);border-radius:2px;outline:none}
.temp-row input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:var(--ac);cursor:pointer}
.temp-val{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ac);min-width:24px}
textarea.sys{width:100%;background:var(--bg3);border:1px solid var(--bd);border-radius:6px;padding:6px 8px;color:var(--tx);font-family:'JetBrains Mono',monospace;font-size:10px;resize:vertical;min-height:50px;outline:none;line-height:1.4}
textarea.sys:focus{border-color:var(--ac)}
.stats{padding:10px 14px;border-top:1px solid var(--bd);margin-top:auto}
.stat-row{display:flex;justify-content:space-between;font-size:10px;color:var(--tx3);margin-bottom:3px}
.stat-val{color:var(--tx2);font-family:'JetBrains Mono',monospace}
.main{display:flex;flex-direction:column;height:100vh;background:var(--bg)}
.chat-header{padding:10px 20px;border-bottom:1px solid var(--bd);display:flex;align-items:center;gap:10px;background:var(--bg2)}
.active-badge{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--tx2)}
.active-badge .dot{width:5px;height:5px;border-radius:50%;background:var(--ac3);animation:pulse 2s infinite}
.model-name{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--ac);margin-left:auto;max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.clear-btn{background:none;border:1px solid var(--bd);border-radius:5px;padding:4px 10px;color:var(--tx3);font-size:10px;cursor:pointer;font-family:'Outfit',sans-serif;transition:all .12s}
.clear-btn:hover{border-color:var(--red);color:var(--red)}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
.messages{flex:1;overflow-y:auto;padding:16px 20px;display:flex;flex-direction:column;gap:14px}
.messages::-webkit-scrollbar{width:5px}.messages::-webkit-scrollbar-thumb{background:var(--bd);border-radius:3px}
.msg{display:flex;gap:10px;max-width:85%;animation:msgIn .2s ease-out}
.msg.user{align-self:flex-end;flex-direction:row-reverse}
.msg.assistant{align-self:flex-start}
@keyframes msgIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
.msg-avatar{width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;flex-shrink:0;font-family:'JetBrains Mono',monospace}
.msg.user .msg-avatar{background:var(--ac2);color:#fff}
.msg.assistant .msg-avatar{background:var(--ac);color:#000}
.msg-bubble{padding:10px 14px;border-radius:12px;font-size:13px;line-height:1.6;white-space:pre-wrap;word-break:break-word}
.msg.user .msg-bubble{background:var(--bg4);border-bottom-right-radius:4px}
.msg.assistant .msg-bubble{background:var(--bg2);border:1px solid var(--bd);border-bottom-left-radius:4px}
.msg-bubble code{background:var(--bg);padding:1px 4px;border-radius:3px;font-family:'JetBrains Mono',monospace;font-size:11px}
.msg-bubble pre{background:var(--bg);padding:10px;border-radius:6px;overflow-x:auto;margin:6px 0;font-family:'JetBrains Mono',monospace;font-size:11px;line-height:1.4}
.msg-meta{font-size:9px;color:var(--tx3);margin-top:3px;font-family:'JetBrains Mono',monospace}
.typing{display:flex;gap:3px;padding:6px 0}
.typing span{width:5px;height:5px;border-radius:50%;background:var(--ac);animation:blink 1.4s infinite}
.typing span:nth-child(2){animation-delay:.2s}
.typing span:nth-child(3){animation-delay:.4s}
@keyframes blink{0%,100%{opacity:.2}50%{opacity:1}}
.empty{flex:1;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;color:var(--tx3)}
.empty-icon{font-size:40px;opacity:.12;font-family:'JetBrains Mono',monospace}
.empty-text{font-size:13px;text-align:center;line-height:1.5}
.empty-stats{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--ac);opacity:.5}
.input-area{padding:12px 20px;border-top:1px solid var(--bd);background:var(--bg2)}
.input-row{display:flex;gap:8px;align-items:flex-end}
.input-box{flex:1;position:relative}
.input-box textarea{width:100%;background:var(--bg3);border:1px solid var(--bd);border-radius:10px;padding:10px 44px 10px 14px;color:var(--tx);font-family:'Outfit',sans-serif;font-size:13px;resize:none;outline:none;min-height:44px;max-height:140px;line-height:1.4;transition:border .12s}
.input-box textarea:focus{border-color:var(--ac)}
.input-box textarea::placeholder{color:var(--tx3)}
.send-btn{position:absolute;right:6px;bottom:6px;width:32px;height:32px;border-radius:7px;background:var(--ac);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .12s}
.send-btn:hover{background:#33ecff;transform:scale(1.05)}
.send-btn:disabled{background:var(--bg4);cursor:not-allowed;transform:none}
.send-btn svg{width:14px;height:14px}
.input-hint{font-size:9px;color:var(--tx3);margin-top:4px;text-align:center}
@media(max-width:768px){.app{grid-template-columns:1fr}.sidebar{display:none}}
</style>
<script src="/js/wevia-a11y-auto.js" defer></script>
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-143918 -->
<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>
<div class="app">
<div class="sidebar">
<div class="sidebar-header">
<div class="logo">
<div class="logo-icon">OC</div>
<div><div class="logo-text">OpenClaw</div><div class="logo-sub">WEVAL AI Gateway</div></div>
<div class="logo-count" id="totalModels"></div>
</div>
</div>
<div id="providerList"></div>
<div class="ctrl-section">
<div class="ctrl-label">Model</div>
<select id="modelSelect"></select>
</div>
<div class="ctrl-section">
<div class="ctrl-label">Temperature</div>
<div class="temp-row">
<input type="range" min="0" max="20" value="7" id="tempSlider" step="1" oninput="document.getElementById('tempVal').textContent=(this.value/10).toFixed(1)">
<span class="temp-val" id="tempVal">0.7</span>
</div>
</div>
<div class="ctrl-section">
<div class="ctrl-label">System prompt</div>
<textarea class="sys" id="sysPrompt" placeholder="Tu es un assistant IA expert..."></textarea>
</div>
<div class="stats">
<div class="stat-row"><span>Messages</span><span class="stat-val" id="statMsgs">0</span></div>
<div class="stat-row"><span>Tokens (est.)</span><span class="stat-val" id="statTokens">0</span></div>
<div class="stat-row"><span>Latency</span><span class="stat-val" id="statLatency"></span></div>
</div>
</div>
<div class="main">
<div class="chat-header">
<div class="active-badge"><span class="dot"></span> Connected</div>
<span class="model-name" id="headerModel"></span>
<button class="clear-btn" onclick="clearChat()">Clear</button>
</div>
<div class="messages" id="messages">
<div class="empty">
<div class="empty-icon">&gt;_</div>
<div class="empty-text">10 providers. 40 models. Zero cost.<br>Cloud + Sovereign AI — your choice.</div>
<div class="empty-stats" id="emptyStats"></div>
</div>
</div>
<div class="input-area">
<div class="input-row">
<div class="input-box">
<textarea id="userInput" rows="1" placeholder="Send a message..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMsg()}"></textarea>
<button class="send-btn" id="sendBtn" onclick="sendMsg()">
<svg viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
</button>
</div>
</div>
<div class="input-hint">Enter to send · Shift+Enter newline · Streaming</div>
</div>
</div>
</div>
<script>
const API='/api/openclaw-proxy.php';
let providers=[],activeProvider='groq',history=[],isStreaming=false;
const tierLabels={free:'Cloud free',paid:'Cloud paid',sovereign:'Sovereign'};
const tierOrder=['free','paid','sovereign'];
async function loadProviders(){
try{
const r=await fetch(API+'?action=providers');
const d=await r.json();
providers=d.providers;
document.getElementById('totalModels').textContent=d.total_models+' models';
document.getElementById('emptyStats').textContent=providers.length+' providers · '+d.total_models+' models';
renderProviders();
selectProvider('groq');
}catch(e){console.error(e)}
}
function renderProviders(){
const list=document.getElementById('providerList');
let html='';
for(const tier of tierOrder){
const group=providers.filter(p=>p.tier===tier);
if(!group.length) continue;
html+=`<div class="section"><div class="section-label"><span class="tier-dot ${tier}"></span>${tierLabels[tier]}</div>`;
for(const p of group){
const active=p.id===activeProvider?'active':'';
const nokey=p.has_key?'':`<span style="font-size:8px;color:var(--red)">NO KEY</span>`;
html+=`<button class="provider-btn ${active}" onclick="selectProvider('${p.id}')">
<span class="name">${p.name}</span>
${nokey}
<span class="speed ${p.speed}">${p.speed}</span>
<span class="mcnt">${p.models.length}</span>
</button>`;
}
html+=`</div>`;
}
list.innerHTML=html;
}
function selectProvider(id){
activeProvider=id;
renderProviders();
const p=providers.find(x=>x.id===id);
if(!p) return;
const sel=document.getElementById('modelSelect');
sel.innerHTML=p.models.map(m=>`<option value="${m.id}">${m.name}</option>`).join('');
document.getElementById('headerModel').textContent=p.name+' / '+p.models[0].name;
sel.onchange=()=>{
const m=p.models.find(x=>x.id===sel.value);
document.getElementById('headerModel').textContent=p.name+' / '+(m?m.name:sel.value);
};
}
function clearChat(){
history=[];
document.getElementById('messages').innerHTML=`<div class="empty"><div class="empty-icon">&gt;_</div><div class="empty-text">10 providers. 40 models. Zero cost.<br>Cloud + Sovereign AI — your choice.</div></div>`;
updateStats();
}
function addMsg(role,content,meta){
const el=document.getElementById('messages');
const empty=el.querySelector('.empty');
if(empty) empty.remove();
const div=document.createElement('div');
div.className='msg '+role;
const avatar=role==='user'?'Y':'AI';
div.innerHTML=`<div class="msg-avatar">${avatar}</div><div><div class="msg-bubble">${escapeHtml(content)}</div>${meta?`<div class="msg-meta">${meta}</div>`:''}</div>`;
el.appendChild(div);
el.scrollTop=el.scrollHeight;
return div;
}
function escapeHtml(t){return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
function updateStats(){
document.getElementById('statMsgs').textContent=history.length;
document.getElementById('statTokens').textContent=history.reduce((a,m)=>a+Math.ceil(m.content.length/4),0).toLocaleString();
}
async function sendMsg(){
if(isStreaming) return;
const input=document.getElementById('userInput');
const text=input.value.trim();
if(!text) return;
input.value='';input.style.height='auto';
history.push({role:'user',content:text});
addMsg('user',text);
updateStats();
const sel=document.getElementById('modelSelect');
const model=sel.value;
const temp=parseFloat(document.getElementById('tempSlider').value)/10;
const sys=document.getElementById('sysPrompt').value.trim();
const p=providers.find(x=>x.id===activeProvider);
const modelName=p?p.models.find(m=>m.id===model)?.name||model:model;
const msgDiv=addMsg('assistant','');
const bubble=msgDiv.querySelector('.msg-bubble');
bubble.innerHTML='<div class="typing"><span></span><span></span><span></span></div>';
document.getElementById('sendBtn').disabled=true;
isStreaming=true;
const t0=performance.now();
let fullText='';
try{
const resp=await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({provider:activeProvider,model,messages:history.filter(m=>m.role!=='system'),stream:true,temperature:temp,system:sys})});
const reader=resp.body.getReader();
const decoder=new TextDecoder();
let buf='';
while(true){
const{done,value}=await reader.read();
if(done) break;
buf+=decoder.decode(value,{stream:true});
const lines=buf.split('\n');
buf=lines.pop();
for(const line of lines){
if(!line.startsWith('data: ')) continue;
const data=line.slice(6).trim();
if(data==='[DONE]') continue;
try{
const j=JSON.parse(data);
const delta=j.choices?.[0]?.delta?.content||'';
if(delta){fullText+=delta;bubble.textContent=fullText;}
}catch(e){}
}
}
}catch(e){fullText='Error: '+e.message;bubble.textContent=fullText;bubble.style.color='var(--red)'}
if(!fullText||fullText.startsWith('Error')||fullText.length<2){const fallbacks=['mistral','cerebras','sambanova','nvidia'];const fb=fallbacks.find(f=>f!==activeProvider);if(fb){bubble.textContent='Retrying with '+fb+'...';try{const r2=await fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({provider:fb,model:'',messages:history.filter(m=>m.role!=='system'),stream:false,temperature:temp,system:sys})});const d=await r2.json();fullText=d.choices?.[0]?.message?.content||d.error?.message||'No response';bubble.textContent=fullText;const meta2=document.querySelector('.msg-meta:last-of-type');if(meta2)meta2.textContent=fb+' (fallback)'}catch(e2){bubble.textContent='All providers failed: '+e2.message;bubble.style.color='var(--red)'}}}
const latency=Math.round(performance.now()-t0);
const metaDiv=document.createElement('div');
metaDiv.className='msg-meta';
metaDiv.textContent=(p?p.name:activeProvider)+' / '+modelName+' · '+latency+'ms';
bubble.parentElement.appendChild(metaDiv);
if(fullText&&!fullText.startsWith('Error')){history.push({role:'assistant',content:fullText})}
isStreaming=false;
document.getElementById('sendBtn').disabled=false;
document.getElementById('statLatency').textContent=latency+'ms';
updateStats();
document.getElementById('messages').scrollTop=document.getElementById('messages').scrollHeight;
}
document.getElementById('userInput').addEventListener('input',function(){this.style.height='auto';this.style.height=Math.min(this.scrollHeight,140)+'px'});
loadProviders();
</script>
<!-- === 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 t33b6) --><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">openclaw</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 = 'openclaw';
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>
<!-- 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>