429 lines
25 KiB
HTML
429 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>WEVIA Agent · Plan → Execute</title>
|
|
<style>
|
|
:root{
|
|
--bg:#0a0a0f;--panel:rgba(18,18,26,0.7);--border:rgba(255,255,255,0.08);
|
|
--ink:#e8e6e3;--ink-dim:#8b8680;--ink-faint:#5c5852;
|
|
--gold:#f6d572;--mint:#5cdb95;--coral:#ff6b6b;--cyan:#4ecdc4;--violet:#a78bfa;
|
|
--font-sans:"Inter",-apple-system,system-ui,sans-serif;
|
|
--font-mono:"SF Mono",Monaco,"Cascadia Code",monospace;
|
|
}
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
body{background:var(--bg);color:var(--ink);font-family:var(--font-sans);min-height:100vh;
|
|
background-image:radial-gradient(ellipse at 20% 30%,rgba(167,139,250,0.06) 0%,transparent 60%),
|
|
radial-gradient(ellipse at 80% 70%,rgba(78,205,196,0.04) 0%,transparent 60%);
|
|
display:flex;flex-direction:column;align-items:center;padding:0}
|
|
.app{width:100%;max-width:1100px;display:flex;flex-direction:column;height:100vh}
|
|
header{display:flex;align-items:center;justify-content:space-between;padding:16px 24px;border-bottom:1px solid var(--border);background:rgba(10,10,15,0.85);backdrop-filter:blur(24px)}
|
|
.brand{display:flex;align-items:center;gap:12px;font-size:13px;letter-spacing:.2em;text-transform:uppercase;color:var(--violet);font-weight:600}
|
|
.brand::before{content:"⚡";font-size:18px;animation:pulse 2s ease infinite}
|
|
@keyframes pulse{50%{opacity:.5}}
|
|
.nav-links{display:flex;gap:8px}
|
|
.nav-links a{padding:6px 12px;background:transparent;border:1px solid var(--border);color:var(--ink-dim);font-size:10px;letter-spacing:.14em;text-transform:uppercase;text-decoration:none;border-radius:4px;transition:.2s;font-family:var(--font-mono)}
|
|
.nav-links a:hover{border-color:var(--gold);color:var(--gold)}
|
|
|
|
.transcript{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:12px}
|
|
.welcome{text-align:center;padding:80px 20px;color:var(--ink-faint)}
|
|
.welcome h2{color:var(--violet);font-weight:300;margin-bottom:14px;font-size:24px}
|
|
.welcome p{font-size:13px;line-height:1.6;margin-bottom:8px}
|
|
.examples{margin-top:24px;display:flex;flex-direction:column;gap:8px;max-width:600px;margin-left:auto;margin-right:auto}
|
|
.ex-btn{padding:10px 14px;background:rgba(167,139,250,0.06);border:1px solid rgba(167,139,250,0.2);border-radius:4px;color:var(--ink-dim);text-align:left;cursor:pointer;font-size:12px;transition:.2s;font-family:var(--font-mono)}
|
|
.ex-btn:hover{border-color:var(--violet);color:var(--violet);background:rgba(167,139,250,0.1)}
|
|
|
|
.event{padding:12px 16px;border-radius:6px;background:var(--panel);border:1px solid var(--border);font-size:13px;line-height:1.5;animation:in .4s ease}
|
|
@keyframes in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
|
|
.event-header{display:flex;align-items:center;gap:10px;margin-bottom:8px;font-size:10px;letter-spacing:.15em;text-transform:uppercase;font-family:var(--font-mono);color:var(--ink-faint)}
|
|
.event-icon{font-size:14px}
|
|
.event.user{background:rgba(78,205,196,0.06);border-color:rgba(78,205,196,0.3);align-self:flex-end;max-width:80%}
|
|
.event.thinking{background:rgba(167,139,250,0.04);border-color:rgba(167,139,250,0.2)}
|
|
.event.thinking .event-header{color:var(--violet)}
|
|
.event.plan{background:rgba(246,213,114,0.04);border-left:3px solid var(--gold)}
|
|
.event.plan .event-header{color:var(--gold)}
|
|
.event.exec{background:rgba(0,0,0,0.4);border-color:rgba(78,205,196,0.2);font-family:var(--font-mono);font-size:11px}
|
|
.event.exec .event-header{color:var(--cyan)}
|
|
.event.exec .cmd{color:var(--cyan);background:rgba(78,205,196,0.06);padding:6px 10px;border-radius:3px;margin:4px 0;display:block;word-break:break-all;border-left:2px solid var(--cyan)}
|
|
.event.exec .output{color:var(--ink-dim);background:rgba(0,0,0,0.5);padding:8px 12px;border-radius:3px;margin-top:6px;white-space:pre-wrap;max-height:300px;overflow-y:auto;font-size:11px}
|
|
.event.exec.failed .cmd{border-left-color:var(--coral)}
|
|
.event.exec.failed .output{color:var(--coral)}
|
|
.event.recovery{background:rgba(246,213,114,0.06);border-left:3px solid var(--gold);font-style:italic}
|
|
.event.summary{background:rgba(92,219,149,0.06);border-left:3px solid var(--mint);padding:16px 20px}
|
|
.event.summary .event-header{color:var(--mint)}
|
|
.event.summary .content{color:var(--ink);line-height:1.6;white-space:pre-wrap}
|
|
.event.error{background:rgba(255,107,107,0.06);border-left:3px solid var(--coral)}
|
|
.event.done{background:rgba(92,219,149,0.04);text-align:center;color:var(--mint);font-size:11px;font-family:var(--font-mono);letter-spacing:.15em;text-transform:uppercase}
|
|
|
|
.plan-list{list-style:none;padding:0;margin:8px 0}
|
|
.plan-list li{padding:6px 0 6px 22px;position:relative;color:var(--ink);font-size:12px}
|
|
.plan-list li::before{content:"";position:absolute;left:6px;top:11px;width:6px;height:6px;border-radius:50%;background:var(--gold)}
|
|
.commands-preview{margin-top:10px;font-family:var(--font-mono);font-size:11px;color:var(--cyan);padding:8px 10px;background:rgba(0,0,0,0.4);border-radius:3px;display:block;word-break:break-all}
|
|
|
|
.composer{padding:16px 24px;border-top:1px solid var(--border);background:rgba(10,10,15,0.95);backdrop-filter:blur(24px)}
|
|
.composer-row{display:flex;gap:10px;align-items:flex-end}
|
|
.composer textarea{flex:1;min-height:54px;max-height:180px;padding:14px 16px;background:rgba(18,18,26,0.6);border:1px solid var(--border);border-radius:6px;color:var(--ink);font-family:var(--font-sans);font-size:13px;resize:none;line-height:1.4;outline:none;transition:.2s}
|
|
.composer textarea:focus{border-color:var(--violet);background:rgba(18,18,26,0.9)}
|
|
.composer-btn{padding:14px 22px;background:var(--violet);color:#0a0a0f;border:none;border-radius:6px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;font-size:11px;cursor:pointer;transition:.2s;font-family:var(--font-mono)}
|
|
.composer-btn:hover{transform:translateY(-1px);box-shadow:0 8px 20px rgba(167,139,250,0.4)}
|
|
.composer-btn:disabled{opacity:.4;cursor:not-allowed}
|
|
.composer-opts{display:flex;gap:16px;margin-top:8px;font-size:10px;color:var(--ink-faint);font-family:var(--font-mono);align-items:center}
|
|
.composer-opts label{display:flex;align-items:center;gap:6px;cursor:pointer}
|
|
.composer-opts input[type="number"]{width:38px;padding:2px 4px;background:rgba(0,0,0,0.4);border:1px solid var(--border);color:var(--violet);border-radius:3px;font-family:var(--font-mono)}
|
|
|
|
.loader{display:inline-block;width:8px;height:8px;border:2px solid var(--border);border-top-color:var(--violet);border-radius:50%;animation:spin .8s linear infinite}
|
|
@keyframes spin{to{transform:rotate(360deg)}}
|
|
.badge{display:inline-block;padding:2px 6px;background:rgba(167,139,250,0.15);color:var(--violet);font-size:9px;border-radius:2px;margin-left:6px;font-family:var(--font-mono);letter-spacing:.1em}
|
|
.event.consensus_resp{background:rgba(78,205,196,0.04);border-left:3px solid var(--cyan);font-size:12px}
|
|
.event.consensus_resp .vote-approve{color:var(--mint);font-weight:700}
|
|
.event.consensus_resp .vote-reject{color:var(--coral);font-weight:700}
|
|
.event.consensus_resp .vote-modify{color:var(--gold);font-weight:700}
|
|
.event.consensus_resp .vote-error,.event.consensus_resp .vote-unparseable{color:var(--ink-faint);font-style:italic}
|
|
.event.consensus_decision{background:rgba(167,139,250,0.08);border-left:3px solid var(--violet);padding:14px 18px}
|
|
.event.consensus_decision .tally{display:flex;gap:14px;margin:8px 0;font-family:var(--font-mono);font-size:12px}
|
|
.event.consensus_decision .tally .t{padding:4px 10px;background:rgba(0,0,0,0.4);border-radius:3px}
|
|
.event.consensus_decision .tally .t b{font-size:14px}
|
|
.event.consensus_decision .tally .approve b{color:var(--mint)}
|
|
.event.consensus_decision .tally .reject b{color:var(--coral)}
|
|
.event.consensus_decision .tally .modify b{color:var(--gold)}
|
|
.event.consensus_decision .decision-badge{display:inline-block;padding:4px 10px;border-radius:3px;font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;font-family:var(--font-mono);margin-bottom:8px}
|
|
.event.consensus_decision .decision-approved{background:rgba(92,219,149,0.15);color:var(--mint)}
|
|
.event.consensus_decision .decision-rejected_by_majority{background:rgba(255,107,107,0.15);color:var(--coral)}
|
|
.event.consensus_decision .decision-modify_recommended{background:rgba(246,213,114,0.15);color:var(--gold)}
|
|
.event.consensus_decision .decision-no_quorum{background:rgba(139,134,128,0.15);color:var(--ink-faint)}
|
|
.event.aborted{background:rgba(255,107,107,0.08);border-left:3px solid var(--coral);padding:14px}
|
|
.event.aborted .event-header{color:var(--coral)}
|
|
.event.plan_revised{background:rgba(246,213,114,0.06);border-left:3px solid var(--gold);padding:12px 16px}
|
|
.event.plan_revised .event-header{color:var(--gold)}
|
|
</style>
|
|
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-143216 -->
|
|
<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>
|
|
<div class="app">
|
|
<header>
|
|
<div class="brand">WEVIA Agent · Plan → Execute</div>
|
|
<div class="nav-links">
|
|
<a href="/ai-multichat.html">💬 Multi-Chat</a>
|
|
<a href="/vnc-picker.html">VNC Picker</a>
|
|
<a href="/weval-technology-platform.html">← WTP</a>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="transcript" id="transcript">
|
|
<div class="welcome">
|
|
<h2>⚡ Agent autonome plan → execute</h2>
|
|
<p>WEVIA Master agit comme Opus directement sur S204 : analyse ta demande, génère un plan,<br>exécute des commandes bash en mode autonome, et te résume le résultat.</p>
|
|
<p style="color:var(--ink-faint);font-size:11px">Powered by Cerebras-think (planning) + Cerebras-fast (recovery & summary) · Sovereign API · 0€ cost</p>
|
|
<div class="examples">
|
|
<button class="ex-btn" onclick="useExample(this)">Combien de pages HTML dans /var/www/html et taille totale ?</button>
|
|
<button class="ex-btn" onclick="useExample(this)">Vérifie quels intents PHP sont activés dans /var/www/html/api/wired-pending</button>
|
|
<button class="ex-btn" onclick="useExample(this)">Trouve les 5 plus gros fichiers .log de /var/log et leur date modification</button>
|
|
<button class="ex-btn" onclick="useExample(this)">Test si les 8 ports CDP 9222-9229 répondent et lesquels sont down</button>
|
|
<button class="ex-btn" onclick="useExample(this)">Combien de commits git sur les 24 dernières heures et leurs messages</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="composer">
|
|
<div class="composer-row">
|
|
<textarea id="task-input" placeholder="Décris ta tâche en langage naturel... (ex: 'Vérifie que les 8 chrome CDP tournent et redémarre ceux qui sont down')" rows="1"></textarea>
|
|
<button class="composer-btn" id="send-btn" onclick="executeTask()">Execute</button>
|
|
</div>
|
|
<div class="composer-opts">
|
|
<label><input type="checkbox" id="opt-dry-run"> Dry run (plan seul)</label>
|
|
<label><input type="checkbox" id="opt-consensus"> 🗳 Consensus N IA avant exec</label>
|
|
<label>Max steps: <input type="number" id="opt-max-steps" min="1" max="10" value="5"></label>
|
|
<label style="color:var(--ink-faint)">Enter · Shift+Enter</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const transcript = document.getElementById('transcript');
|
|
const input = document.getElementById('task-input');
|
|
|
|
function useExample(btn) {
|
|
input.value = btn.textContent;
|
|
input.focus();
|
|
input.style.height = 'auto';
|
|
input.style.height = Math.min(input.scrollHeight, 180) + 'px';
|
|
}
|
|
|
|
function clearWelcome() {
|
|
const w = transcript.querySelector('.welcome');
|
|
if (w) w.remove();
|
|
}
|
|
|
|
function addEvent(type, content, meta) {
|
|
const ev = document.createElement('div');
|
|
ev.className = `event ${type}`;
|
|
let header = '';
|
|
let body = '';
|
|
|
|
switch(type) {
|
|
case 'user':
|
|
header = `<span class="event-icon">👤</span><span>You</span>`;
|
|
body = `<div>${escapeHtml(content)}</div>`;
|
|
break;
|
|
case 'thinking':
|
|
header = `<span class="event-icon"><span class="loader"></span></span><span>Thinking</span>`;
|
|
body = `<div style="color:var(--violet);font-style:italic">${escapeHtml(content)}</div>`;
|
|
break;
|
|
case 'plan':
|
|
header = `<span class="event-icon">📋</span><span>Plan</span>${meta?.provider ? '<span class="badge">'+meta.provider+'</span>' : ''}`;
|
|
body = `<ul class="plan-list">${(content.plan || []).map(p => `<li>${escapeHtml(p)}</li>`).join('')}</ul>
|
|
<div style="margin-top:10px;font-size:10px;color:var(--ink-faint);text-transform:uppercase;letter-spacing:.15em">Commands</div>
|
|
${(content.commands || []).map(c => `<code class="commands-preview">$ ${escapeHtml(c)}</code>`).join('')}`;
|
|
break;
|
|
case 'exec':
|
|
const failed = !content.ok || (content.output && /^find:.*Permission/i.test(content.output) && !/\d/.test(content.output));
|
|
ev.className = `event exec${failed ? ' failed' : ''}`;
|
|
header = `<span class="event-icon">${content.ok ? '✓' : '✗'}</span><span>Step ${content.step}</span>${content.duration_ms ? '<span style="color:var(--gold);margin-left:auto">'+content.duration_ms+'ms</span>' : ''}`;
|
|
body = `<code class="cmd">$ ${escapeHtml(content.cmd)}</code>${content.output ? '<div class="output">'+escapeHtml(content.output)+'</div>' : ''}${content.error ? '<div class="output" style="color:var(--coral)">'+escapeHtml(content.error)+'</div>' : ''}`;
|
|
break;
|
|
case 'recovery':
|
|
header = `<span class="event-icon">🔧</span><span>Recovery</span>`;
|
|
body = `<code class="cmd">$ ${escapeHtml(content.fix_cmd || content.cmd || '')}</code>${content.output ? '<div class="output">'+escapeHtml(content.output)+'</div>' : ''}`;
|
|
break;
|
|
case 'summary':
|
|
header = `<span class="event-icon">📊</span><span>Summary</span>${meta?.provider ? '<span class="badge">'+meta.provider+'</span>' : ''}`;
|
|
body = `<div class="content">${escapeHtml(content.content || '').replace(/\*\*(.+?)\*\*/g, '<b>$1</b>').replace(/\n/g, '<br>')}</div>`;
|
|
break;
|
|
case 'error':
|
|
header = `<span class="event-icon">⚠</span><span>Error</span>`;
|
|
body = `<div style="color:var(--coral)">${escapeHtml(content.msg || JSON.stringify(content))}</div>`;
|
|
break;
|
|
case 'consensus_resp':
|
|
const voteCls = 'vote-' + (content.vote || 'unknown');
|
|
header = `<span class="event-icon">🗳</span><span>${content.model || "?"}</span>${content.provider ? '<span class="badge">'+content.provider+'</span>' : ''}<span style="margin-left:auto" class="${voteCls}">${(content.vote || "?").toUpperCase()}${content.confidence ? ' ('+content.confidence+'/10)' : ''}</span>`;
|
|
body = `<div>${escapeHtml(content.rationale || "")}</div>${(content.concerns || []).length ? '<div style="margin-top:6px;font-size:11px;color:var(--ink-faint)">⚠ '+content.concerns.map(c=>escapeHtml(c)).join(' · ')+'</div>' : ''}${(content.suggested_changes || []).length ? '<div style="margin-top:6px;font-size:11px;color:var(--gold)">💡 '+content.suggested_changes.map(s=>escapeHtml(s)).join(' / ')+'</div>' : ''}`;
|
|
break;
|
|
case 'consensus_decision':
|
|
const dec = content.decision || 'unknown';
|
|
const t = content.tally || {};
|
|
header = `<span class="event-icon">⚖️</span><span>Consensus Decision</span>`;
|
|
body = `<div class="decision-badge decision-${dec}">${dec.replace(/_/g, ' ')}</div>
|
|
<div class="tally">
|
|
<div class="t approve"><b>${t.approve||0}</b> approve</div>
|
|
<div class="t reject"><b>${t.reject||0}</b> reject</div>
|
|
<div class="t modify"><b>${t.modify||0}</b> modify</div>
|
|
</div>
|
|
${(content.concerns_aggregated || []).length ? '<div style="margin-top:8px;font-size:12px"><b style="color:var(--coral)">Concerns agrégés:</b><br>'+content.concerns_aggregated.map(c=>'• '+escapeHtml(c)).join('<br>')+'</div>' : ''}
|
|
${(content.suggestions_aggregated || []).length ? '<div style="margin-top:8px;font-size:12px"><b style="color:var(--gold)">Suggestions agrégées:</b><br>'+content.suggestions_aggregated.map(s=>'• '+escapeHtml(s)).join('<br>')+'</div>' : ''}`;
|
|
break;
|
|
case 'plan_revised':
|
|
header = `<span class="event-icon">🔄</span><span>Plan révisé (consensus)</span>`;
|
|
body = `<div style="font-size:11px;color:var(--ink-faint);margin-bottom:6px">${content.integrated_suggestions||0} suggestions intégrées</div><ul class="plan-list">${(content.plan || []).map(p => '<li>'+escapeHtml(p)+'</li>').join('')}</ul>${(content.commands || []).map(c => '<code class="commands-preview">$ '+escapeHtml(c)+'</code>').join('')}`;
|
|
break;
|
|
case 'aborted':
|
|
header = `<span class="event-icon">🛑</span><span>Execution Aborted</span>`;
|
|
body = `<div><b>Reason:</b> ${escapeHtml(content.reason || "unknown")}</div>${(content.concerns || []).length ? '<div style="margin-top:6px"><b>Concerns:</b><br>'+content.concerns.map(c=>'• '+escapeHtml(c)).join('<br>')+'</div>' : ''}`;
|
|
break;
|
|
case 'done':
|
|
header = '';
|
|
body = `✓ Done · ${content.steps_executed || 0} steps · ${content.success_count || 0} success${content.consensus_decision ? ' · consensus: '+content.consensus_decision : ''}${content.aborted ? ' · ABORTED' : ''}`;
|
|
break;
|
|
}
|
|
|
|
ev.innerHTML = (header ? `<div class="event-header">${header}</div>` : '') + body;
|
|
transcript.appendChild(ev);
|
|
transcript.scrollTop = transcript.scrollHeight;
|
|
return ev;
|
|
}
|
|
|
|
function escapeHtml(s) {
|
|
return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
}
|
|
|
|
let activeStream = null;
|
|
let pendingThinking = null;
|
|
|
|
async function executeTask() {
|
|
const task = input.value.trim();
|
|
if (!task) return;
|
|
|
|
clearWelcome();
|
|
|
|
const dry = document.getElementById('opt-dry-run').checked;
|
|
const maxSteps = parseInt(document.getElementById('opt-max-steps').value || '5');
|
|
const btn = document.getElementById('send-btn');
|
|
btn.disabled = true;
|
|
btn.textContent = 'Running...';
|
|
|
|
addEvent('user', task);
|
|
input.value = '';
|
|
input.style.height = 'auto';
|
|
|
|
try {
|
|
const resp = await fetch('/api/wevia-agent-exec.php', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({task, dry_run: dry, max_steps: maxSteps, consensus: document.getElementById('opt-consensus').checked, consensus_models: ['cerebras-fast','groq','gemini','mistral','cloudflare-ai']})
|
|
});
|
|
|
|
if (!resp.ok) {
|
|
addEvent('error', {msg: 'HTTP ' + resp.status});
|
|
return;
|
|
}
|
|
|
|
const reader = resp.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let buffer = '';
|
|
|
|
while (true) {
|
|
const {value, done} = await reader.read();
|
|
if (done) break;
|
|
buffer += decoder.decode(value, {stream: true});
|
|
const events = buffer.split('\n\n');
|
|
buffer = events.pop() || '';
|
|
|
|
for (const block of events) {
|
|
if (!block.trim()) continue;
|
|
const lines = block.split('\n');
|
|
let eventType = 'message', dataStr = '';
|
|
for (const line of lines) {
|
|
if (line.startsWith('event: ')) eventType = line.slice(7);
|
|
if (line.startsWith('data: ')) dataStr = line.slice(6);
|
|
}
|
|
|
|
let data;
|
|
try { data = JSON.parse(dataStr); } catch(e) { continue; }
|
|
|
|
// Remove pending thinking before showing concrete result
|
|
if (['plan','exec_result','summary','error'].includes(eventType) && pendingThinking) {
|
|
pendingThinking.remove();
|
|
pendingThinking = null;
|
|
}
|
|
|
|
if (eventType === 'start') {
|
|
// Skip - already shown user
|
|
} else if (eventType === 'thinking' || eventType === 'recovery_thinking') {
|
|
pendingThinking = addEvent('thinking', data.msg || 'Processing...');
|
|
} else if (eventType === 'plan') {
|
|
addEvent('plan', data, {provider: data.provider});
|
|
} else if (eventType === 'consensus_query') {
|
|
pendingThinking = addEvent('thinking', '🗳 Interroge ' + data.model + '...');
|
|
} else if (eventType === 'consensus_response') {
|
|
if (pendingThinking) { pendingThinking.remove(); pendingThinking = null; }
|
|
addEvent('consensus_resp', data);
|
|
} else if (eventType === 'consensus_decision') {
|
|
addEvent('consensus_decision', data);
|
|
} else if (eventType === 'plan_revised') {
|
|
addEvent('plan_revised', data);
|
|
} else if (eventType === 'aborted') {
|
|
addEvent('aborted', data);
|
|
} else if (eventType === 'exec_start') {
|
|
// Skip - covered by exec_result
|
|
} else if (eventType === 'exec_result') {
|
|
addEvent('exec', data);
|
|
} else if (eventType === 'recovery_exec' || eventType === 'recovery_result') {
|
|
addEvent('recovery', data);
|
|
} else if (eventType === 'summary') {
|
|
addEvent('summary', data, {provider: data.provider});
|
|
} else if (eventType === 'done') {
|
|
addEvent('done', data);
|
|
} else if (eventType === 'consensus_query') {
|
|
pendingThinking = addEvent('thinking', '🗳 Vote ' + data.model + '...');
|
|
} else if (eventType === 'consensus_response') {
|
|
const voteIcon = data.vote === 'approve' ? '✅' : (data.vote === 'reject' ? '❌' : (data.vote === 'modify' ? '✏️' : '⚠️'));
|
|
const ev = document.createElement('div');
|
|
ev.className = 'event plan';
|
|
ev.innerHTML = '<div class="event-header"><span class="event-icon">' + voteIcon + '</span><span>Vote ' + data.model + '</span><span class="badge">' + (data.provider || '?') + '</span><span style="margin-left:auto;color:var(--gold)">conf ' + (data.confidence||'?') + '/10</span></div>' +
|
|
'<div style="font-weight:700;color:var(--violet);margin-bottom:6px">' + data.vote.toUpperCase() + '</div>' +
|
|
(data.rationale ? '<div style="font-size:11px;color:var(--ink-dim);margin-bottom:6px">' + escapeHtml(data.rationale) + '</div>' : '') +
|
|
(data.concerns && data.concerns.length ? '<div style="font-size:11px;color:var(--coral);margin-top:6px">⚠ ' + data.concerns.map(escapeHtml).join('<br>⚠ ') + '</div>' : '') +
|
|
(data.suggested_changes && data.suggested_changes.length ? '<div style="font-size:10px;color:var(--cyan);margin-top:6px;font-family:var(--font-mono)">↻ ' + data.suggested_changes.map(escapeHtml).join('<br>↻ ') + '</div>' : '');
|
|
transcript.appendChild(ev);
|
|
transcript.scrollTop = transcript.scrollHeight;
|
|
} else if (eventType === 'consensus_decision') {
|
|
const decisionIcon = data.decision === 'approved' ? '✅' : (data.decision === 'rejected_by_majority' ? '❌' : (data.decision === 'modify_recommended' ? '✏️' : '⚠️'));
|
|
addEvent('summary', {content: decisionIcon + ' DÉCISION CONSENSUS: ' + data.decision.toUpperCase() + '\n\nVotes: approve=' + data.tally.approve + ' · reject=' + data.tally.reject + ' · modify=' + data.tally.modify + ' / ' + data.tally.total + '\n\n' + (data.concerns_aggregated.length ? 'Concerns:\n• ' + data.concerns_aggregated.join('\n• ') + '\n\n' : '') + (data.suggestions_aggregated.length ? 'Suggestions intégrées:\n• ' + data.suggestions_aggregated.join('\n• ') : '')});
|
|
} else if (eventType === 'plan_revised') {
|
|
addEvent('plan', data, {provider: 'WEVIA-revised'});
|
|
} else if (eventType === 'aborted') {
|
|
addEvent('error', {msg: '🛑 ABORTED: ' + data.reason + '\n\nConcerns: ' + (data.concerns||[]).join(' · ')});
|
|
} else if (eventType === 'error') {
|
|
addEvent('error', data);
|
|
}
|
|
}
|
|
}
|
|
} catch(e) {
|
|
addEvent('error', {msg: 'Network error: ' + e.message});
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = 'Execute';
|
|
if (pendingThinking) { pendingThinking.remove(); pendingThinking = null; }
|
|
}
|
|
}
|
|
|
|
// Input handling
|
|
input.addEventListener('input', () => {
|
|
input.style.height = 'auto';
|
|
input.style.height = Math.min(input.scrollHeight, 180) + 'px';
|
|
});
|
|
input.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
executeTask();
|
|
}
|
|
});
|
|
</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>
|