Files
html/wevia-widget.html
Ambre Opus c9dabf21a9 wave-261 · Confidential Shield · supprime fuites Claude Pattern + WTP dock + Dashboards sur pages PUBLIQUES
URGENCE YACINE : Yacine signale dock WTP/IA Hub/Master/Orch/WevCode/Arena/Droid/Admin/WEVIA Engine visible sur / (root) et badge Claude Pattern visible sur /wevia.html PUBLIC

CAUSE RACINE :
1. opus-pattern-badge (Claude Pattern) injected inline dans de nombreuses pages via <script> Opus v17 Claude Pattern SSE auto-injected
2. opus-udrill / opus-dashboard-link injected inline sur plusieurs pages
3. wtp-unified-dock.js avait un guard PUBLIC_PATHS mais autres scripts inline bypassed
4. Le script se chargeait AVANT le guard sur certains paths

FIX DEFINITIF :
- Cree /api/ambre-confidential-shield.js (4822B)
- CSS injection immediate {display:none!important} sur IDs confidentiels
- MutationObserver pour supprimer injections dynamiques
- Scrub des fixed elements contenant 4+ keywords internes (WTP+IA Hub+Master+Droid+Admin)
- Supprime liens fixed vers weval-technology-platform, wevia-master, all-ia-hub, wevia-orchestrator, wevcode, droid, admin-saas
- Sweep periodique 2s + stop 60s
- Wire dans <head> EARLY sur pages PUBLIQUES : wevia.html, index.html, wevia-widget.html, register.html, consent.html

PROOF V50 Playwright :
- / root: has_claude_pattern=false, has_wtp_dock_visible=false, has_dashboards_fixed=false, visible_fixed_internal=0, shield_loaded=true
- /wevia.html: opus_pattern_badge_visible=false, opus_pattern_style_present=false
- Console: [ambre-confidential-shield] active on public page confirmed

LISTE IDs CONFIDENTIELS MASQUES :
- opus-pattern-badge, opus-pattern-style, opus-pattern-panel
- opus-udrill, opus-udrill-in
- opus-dashboard-entry, opus-dashboard-link
- wtp-udock, v130-xnav, opus-xlinks, wtp-sidebar
- opus-claude-pattern, opus-dashboards

ZERO : fuite confidentielle · regression pages privees · ecrasement
2026-04-22 10:42:44 +02:00

468 lines
24 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>
<script src="/api/ambre-confidential-shield.js"></script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>WEVIA Chat</title>
<style>
:root { --bg:#0a0f1a; --card:#141a2e; --tx:#e8e8f0; --tx2:#8888a0; --acc:#7c3aed; --acc2:#9b8afb; --border:#1e293b; }
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; background:var(--bg); color:var(--tx); height:100vh; display:flex; flex-direction:column; overflow:hidden; }
/* Header */
.header { background:linear-gradient(135deg,#667eea,#764ba2); padding:10px 16px; display:flex; align-items:center; gap:10px; flex-shrink:0; }
.header-logo { width:28px; height:28px; border-radius:8px; background:rgba(255,255,255,0.15); display:flex; align-items:center; justify-content:center; font-size:14px; }
.header-title { font-size:14px; font-weight:700; color:#fff; }
.header-sub { font-size:10px; color:rgba(255,255,255,0.7); }
.header-status { margin-left:auto; display:flex; align-items:center; gap:5px; font-size:10px; color:rgba(255,255,255,0.8); }
.header-status::before { content:''; width:6px; height:6px; border-radius:50%; background:#34d399; }
/* Messages */
.messages { flex:1; overflow-y:auto; padding:12px; display:flex; flex-direction:column; gap:8px; }
.messages::-webkit-scrollbar { width:4px; }
.messages::-webkit-scrollbar-thumb { background:rgba(255,255,255,0.1); border-radius:2px; }
.msg { max-width:88%; padding:10px 14px; border-radius:14px; font-size:13px; line-height:1.5; word-wrap:break-word; animation:fadeIn .2s ease; }
@keyframes fadeIn { from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:translateY(0)} }
.msg.user { align-self:flex-end; background:var(--acc); color:#fff; border-bottom-right-radius:4px; }
.msg.ai { align-self:flex-start; background:var(--card); color:var(--tx); border:1px solid var(--border); border-bottom-left-radius:4px; }
.msg.ai .provider { font-size:9px; color:var(--acc2); margin-bottom:4px; font-weight:600; letter-spacing:0.5px; text-transform:uppercase; }
.msg.greeting { text-align:center; align-self:center; background:transparent; border:none; padding:20px; max-width:100%; }
.msg.greeting .logo { font-size:36px; margin-bottom:8px; }
.msg.greeting .title { font-size:15px; font-weight:700; background:linear-gradient(135deg,#a78bfa,#06b6d4); -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
.msg.greeting .sub { font-size:12px; color:var(--tx2); margin-top:4px; }
.typing { display:flex; gap:4px; padding:6px 0; }
.typing span { width:6px; height:6px; border-radius:50%; background:var(--acc2); animation:bounce .6s infinite alternate; }
.typing span:nth-child(2) { animation-delay:.2s; }
.typing span:nth-child(3) { animation-delay:.4s; }
@keyframes bounce { to{opacity:.3;transform:translateY(-4px)} }
/* Quick actions */
.quick-actions { display:none; gap:6px; padding:4px 12px 8px; flex-wrap:wrap; flex-shrink:0; }
.quick-btn { background:var(--card); border:1px solid var(--border); color:var(--tx2); font-size:11px; padding:5px 10px; border-radius:16px; cursor:pointer; transition:all .15s; white-space:nowrap; }
.quick-btn:hover { border-color:var(--acc); color:var(--acc2); background:rgba(124,58,237,0.1); }
/* Input */
.input-area { padding:8px 12px 12px; flex-shrink:0; border-top:1px solid var(--border); }
.input-row { display:flex; gap:8px; align-items:center; background:var(--card); border:1px solid var(--border); border-radius:20px; padding:4px 6px 4px 14px; transition:border-color .2s; }
.input-row:focus-within { border-color:var(--acc); }
.input-row input { flex:1; background:none; border:none; outline:none; color:var(--tx); font-size:13px; font-family:inherit; }
.input-row input::placeholder { color:var(--tx2); }
.send-btn { width:32px; height:32px; border-radius:50%; background:var(--acc); border:none; color:#fff; font-size:14px; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all .15s; flex-shrink:0; }
.send-btn:hover { background:#6d28d9; transform:scale(1.05); }
.send-btn:disabled { opacity:.4; cursor:default; transform:none; }
/* Markdown basic */
.msg.ai p { margin:4px 0; }
.msg.ai strong, .msg.ai b { color:#a78bfa; }
.msg.ai code { background:rgba(255,255,255,0.06); padding:1px 4px; border-radius:3px; font-size:12px; font-family:monospace; }
.msg.ai ul, .msg.ai ol { padding-left:18px; margin:4px 0; }
.msg.ai a { color:var(--acc2); text-decoration:underline; }
</style>
<style>
.wevia-progress{width:100%;background:rgba(99,102,241,.15);border-radius:8px;overflow:hidden;height:6px;margin:8px 0}
.wevia-progress-bar{height:100%;background:linear-gradient(90deg,#6366f1,#a78bfa,#6366f1);background-size:200%;animation:weviaShimmer 1.5s infinite;border-radius:8px;transition:width .3s}
.wevia-progress-text{font-size:11px;color:#a0a0b0;margin-top:4px;text-align:center}
@keyframes weviaShimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
</style>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<script>mermaid.initialize({startOnLoad:false,theme:'dark',themeVariables:{primaryColor:'#7c3aed',primaryTextColor:'#e8e8f0',lineColor:'#9b8afb',secondaryColor:'#1e293b',tertiaryColor:'#141a2e'}});</script>
<style>
.mermaid-container{background:#0d1117;border:1px solid var(--border);border-radius:10px;padding:16px;margin:8px 0;overflow-x:auto}
.mermaid-container svg{max-width:100%;height:auto}
.code-block-wrap{background:#0d1117;border:1px solid var(--border);border-radius:10px;padding:12px;margin:8px 0;overflow-x:auto;position:relative}
.code-block-wrap pre{margin:0;white-space:pre-wrap;word-break:break-word;font-family:monospace;font-size:12px;color:#c9d1d9;line-height:1.5}
.code-block-wrap .cb-lang{position:absolute;top:6px;right:10px;font-size:9px;color:#7c3aed;text-transform:uppercase;letter-spacing:1px}
.code-block-wrap .cb-copy{position:absolute;top:4px;right:50px;background:rgba(124,58,237,0.2);border:1px solid rgba(124,58,237,0.3);color:#a78bfa;font-size:10px;padding:2px 8px;border-radius:4px;cursor:pointer}
.svg-logo-container{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:16px;margin:8px 0;text-align:center}
.svg-logo-container svg{max-width:200px;max-height:200px}
</style>
<style>.in-widget .header{display:none!important}.in-widget #chat{height:100vh!important;padding-top:8px}</style><script>if(window!==window.parent)document.documentElement.classList.add("in-widget")</script>
<script>
if (window !== window.top) {
document.addEventListener('DOMContentLoaded', function() {
var h = document.querySelector('.header');
if (h) h.style.display = 'none';
});
}
</script>
</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="header">
<div class="header-logo">🧠</div>
<div>
<div class="header-title">WEVIA</div>
<div class="header-sub">Assistant IA</div>
</div>
<div class="header-status">En ligne</div><a href="/wevia" target="_top" style="margin-left:6px;color:rgba(255,255,255,.7);font-size:11px;text-decoration:none">[Plein ecran]</a>
</div>
<div class="messages" id="messages">
<div class="msg greeting">
<div class="logo">🧠</div>
<div class="sub">Comment puis-je vous aider ?<br><a href="/wevia" target="_top" style="display:inline-flex;align-items:center;gap:6px;margin-top:10px;padding:8px 16px;background:linear-gradient(135deg,#667eea,#764ba2);color:white;text-decoration:none;border-radius:20px;font-size:11px;font-weight:600">Ouvrir en plein ecran</a></div>
</div>
</div>
<div class="quick-actions" id="quickActions">
<button class="quick-btn" onclick="sendQuick('Qui est WEVAL ?')">WEVAL</button>
<button class="quick-btn" onclick="sendQuick('Vos expertises ?')">Expertises</button>
<button class="quick-btn" onclick="sendQuick('Solutions IA et Cloud')">IA & Cloud</button>
<button class="quick-btn" onclick="sendQuick('Transformation digitale')">Transfo Digitale</button>
<button class="quick-btn" onclick="sendQuick('ERP SAP et Vistex')">ERP & SAP</button>
<button class="quick-btn" onclick="sendQuick('Life Sciences et Pharma')">Life Sciences</button>
<button class="quick-btn" onclick="sendQuick('Marketing et recrutement')">Marketing</button>
<button class="quick-btn" onclick="sendQuick('Contactez-nous')">Contact</button>
</div>
<div class="input-area">
<div class="input-row">
<input type="text" id="input" placeholder="Posez votre question..." autocomplete="off" autofocus>
<button class="send-btn" id="sendBtn" onclick="send()"></button>
</div>
</div>
<script>
var convId = null;
var busy = false;
var msgEl = document.getElementById('messages');
var inputEl = document.getElementById('input');
var sendBtnEl = document.getElementById('sendBtn');
var quickEl = document.getElementById('quickActions');
inputEl.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } });
function sendQuick(txt) { inputEl.value = txt; send(); }
function addMsg(text, cls, showProvider) {
var div = document.createElement('div');
div.className = 'msg ' + cls;
if (showProvider) {
var prov = document.createElement('div');
prov.className = 'provider';
prov.textContent = '';
div.appendChild(prov);
}
if (cls === 'ai') {
div.innerHTML += formatMd(text);
} else {
div.textContent = text;
}
msgEl.appendChild(div);
msgEl.scrollTop = msgEl.scrollHeight;
if (cls === 'ai') {
var mms = div.querySelectorAll('pre.mermaid:not([data-processed])');
mms.forEach(function(el) {
try {
var code = el.textContent.trim();
code = code.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
code = code.replace(/'/g, ' ');
el.textContent = code;
mermaid.run({nodes:[el]}).then(function(){
el.setAttribute('data-processed','true');
}).catch(function(e){
console.warn('Mermaid err:', e);
});
} catch(e) { console.warn('Mermaid parse:', e); }
});
}
return div;
}
function showTyping() {
var div = document.createElement('div');
div.className = 'msg ai';
div.id = 'typing';
div.innerHTML = '<div class="typing"><span></span><span></span><span></span></div>';
msgEl.appendChild(div);
msgEl.scrollTop = msgEl.scrollHeight;
}
function hideTyping() {
var el = document.getElementById('typing');
if (el) el.remove();
}
function formatMd(text) {
if (!text) return '';
var blocks = [];
text = text.replace(/```(\w*)\n([\s\S]*?)```/g, function(m, lang, code) {
var idx = blocks.length;
blocks.push({lang: lang.toLowerCase(), code: code.trim()});
return '%%BLOCK_' + idx + '%%';
});
text = text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>')
.replace(/\*(.+?)\*/g,'<em>$1</em>')
.replace(/`(.+?)`/g,'<code>$1</code>')
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g,'<img src="$2" alt="$1" style="max-width:100%;border-radius:8px;margin:8px 0">')
.replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank">$1</a>')
.replace(/^[\-\*] (.+)/gm,'<li>$1</li>')
.replace(/(<li>.*<\/li>)/gs,'<ul>$1</ul>')
.replace(/\n{2,}/g,'</p><p>')
.replace(/\n/g,'<br>')
.replace(/^/,'<p>').replace(/$/,'</p>');
blocks.forEach(function(b, i) {
var ph = '%%BLOCK_' + i + '%%';
var rpl = '';
if (b.lang === 'mermaid') {
var mid = 'mm_' + Date.now() + '_' + i;
rpl = '<div class="mermaid-container"><pre class="mermaid" id="' + mid + '">' + b.code + '</pre></div>';
} else if (b.lang === 'svg') {
rpl = '<div class="svg-logo-container">' + b.code + '</div>';
} else {
var esc = b.code.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
rpl = '<div class="code-block-wrap"><span class="cb-lang">' + (b.lang||'code') + '</span><button class="cb-copy" onclick="navigator.clipboard.writeText(this.nextElementSibling.textContent)">Copier</button><pre>' + esc + '</pre></div>';
}
text = text.replace(ph, rpl);
});
return text;
}
function send() {
if (busy) return;
var text = inputEl.value.trim();
if (!text) return;
inputEl.value = '';
addMsg(text, 'user');
quickEl.style.display = 'none';
busy = true;
sendBtnEl.disabled = true;
showTyping();
var body = { message: text, lang: 'fr' };
if (convId) body.conversation_id = convId;
fetch('/api/wevia-json-api.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
})
.then(function(r) { return r.json(); })
.then(function(d) {
hideTyping();
if (d.conversation_id) convId = d.conversation_id;
addMsg(d.response || 'Désolé, une erreur est survenue.', 'ai', true);
})
.catch(function(err) {
hideTyping();
addMsg('Erreur de connexion. Réessayez.', 'ai', false);
})
.finally(function() {
busy = false;
sendBtnEl.disabled = false;
inputEl.focus();
});
}
</script>
<script>
function weviaProgress(container,startMs){
if(!container)return;
var el=document.createElement('div');
el.className='wevia-pb-wrap';
el.innerHTML='<div class="wevia-progress"><div class="wevia-progress-bar" style="width:5%"></div></div><div class="wevia-progress-text">Analyse en cours...</div>';
container.appendChild(el);
var bar=el.querySelector('.wevia-progress-bar'),txt=el.querySelector('.wevia-progress-text');
var steps=['Connexion au moteur IA...','Analyse de votre demande...','Génération de la réponse...','Finalisation...'];
var iv=setInterval(function(){
var elapsed=(Date.now()-startMs)/1000;
var pct=Math.min(95,5+elapsed*3);
bar.style.width=pct+'%';
var si=Math.min(3,Math.floor(elapsed/3));
txt.textContent=steps[si]+' ('+Math.round(elapsed)+'s)';
if(elapsed>25)txt.textContent='Presque termine... ('+Math.round(elapsed)+'s)';
},300);
return {el:el,stop:function(){clearInterval(iv);bar.style.width='100%';txt.textContent='Termine!';}};
}
</script>
<script>
if (window !== window.top) {
document.addEventListener('DOMContentLoaded', function() {
var h = document.querySelector('.header');
if (h) h.style.display = 'none';
});
}
</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) {
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 (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);} });
}
}
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 === -->
<!-- V29 SECURITY: archi-meta-badge.js removed from public iframe -->
<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">wevia-widget</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 = 'wevia-widget';
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 = sessionStorage.getItem(OPUS_SESSION_KEY);
if (!sess) {
sess = 'opus-' + BOT + '-' + Date.now().toString(36) + '-' + Math.random().toString(36).substr(2, 6);
sessionStorage.setItem(OPUS_SESSION_KEY, sess);
}
const url = '/api/claude-pattern-sse.php?message=' + encodeURIComponent(msg) + '&chatbot=' + encodeURIComponent(BOT) + '&session=' + encodeURIComponent(sess);
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>