Files
html/learning-dashboard.html
Opus cf8108658d feat(chrome-cdp): wave 308 - 8/8 CDP live + doctrine no-overlap
- chrome-profile-launch.sh: CDP port mapping 9222-9229 per profile
- chrome-profile-launch.sh: --remote-debugging-port ajoute + address 0.0.0.0
- chrome-profile-launch.sh: PID extraction direct pgrep (no tmpfile)
- chrome-profile-launch.sh: JSON output include cdp_port + cdp_listening verify
- api/cdp-status.php NEW: proxy 8 CDP ports + bypass CORS 127.0.0.1
- vnc-picker.html: toast-stack BR->BL (doctrine zero overlap)
- vnc-picker.html: live polling 5s via /api/cdp-status.php
- vnc-picker.html: summary badge CDP LIVE x/8 coverage %
- 8/8 Chrome profiles running (openai/anthropic/google/deepseek/mistral/poe/perplexity/hf)
- 49 chrome processes active with CDP ports 9222-9229 listening
- Doctrine 308 wired: CDP port mapping + status proxy centralise
- GOLD: gold_vnc_picker_toast_fix + gold_chrome_launch_w308 + gold_vnc_picker_live_status_w308
2026-04-24 11:26:27 +02:00

212 lines
9.2 KiB
HTML

<!DOCTYPE html>
<html lang="fr"><head><meta charset="UTF-8"><title>🧠 Learning Analytics · WEVIA</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
*{margin:0;padding:0;box-sizing:border-box;font-family:-apple-system,'Segoe UI',sans-serif}
body{background:linear-gradient(135deg,#0b0d15 0%,#1a1f3a 100%);color:#e2e8f0;min-height:100vh;padding:20px}
.container{max-width:1400px;margin:0 auto}
h1{font-family:'Orbitron',sans-serif;font-weight:900;
background:linear-gradient(135deg,#10b981,#06b6d4,#8b5cf6);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
font-size:2rem;margin-bottom:6px;letter-spacing:1px;text-transform:uppercase}
.subtitle{color:#94a3b8;margin-bottom:20px;font-size:0.9rem}
.hero-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:14px;margin-bottom:24px}
.stat-card{background:linear-gradient(135deg,rgba(16,185,129,0.08),rgba(6,182,212,0.08));
border:1px solid rgba(16,185,129,0.2);border-radius:14px;padding:16px;
backdrop-filter:blur(12px);text-align:center}
.stat-val{font-family:'Orbitron',sans-serif;font-size:2.2rem;font-weight:900;line-height:1}
.stat-val.green{color:#22c55e}
.stat-val.blue{color:#06b6d4}
.stat-val.purple{color:#a855f7}
.stat-val.pink{color:#ec4899}
.stat-val.orange{color:#fb923c}
.stat-lbl{font-size:0.7rem;color:#64748b;text-transform:uppercase;letter-spacing:1.5px;margin-top:6px}
.section{background:rgba(15,23,42,0.8);border:1px solid rgba(100,116,139,0.15);
border-radius:14px;padding:18px;margin-bottom:16px}
.section h2{color:#06b6d4;font-size:1rem;font-weight:800;margin-bottom:12px;text-transform:uppercase;letter-spacing:1.5px}
.chatbots-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px}
.bot-card{background:rgba(11,13,21,0.8);border:1px solid rgba(100,116,139,0.15);
border-left:4px solid #06b6d4;border-radius:8px;padding:12px}
.bot-card.excellent{border-left-color:#22c55e}
.bot-card.good{border-left-color:#06b6d4}
.bot-card.ok{border-left-color:#f59e0b}
.bot-card.low{border-left-color:#ef4444}
.bot-name{font-weight:800;color:#e2e8f0;font-size:0.85rem;margin-bottom:6px}
.bot-stats{font-size:0.7rem;color:#94a3b8;display:flex;justify-content:space-between}
.bot-stats b{color:#e2e8f0}
.progress{background:rgba(100,116,139,0.15);border-radius:6px;height:4px;margin-top:6px;overflow:hidden}
.progress-bar{height:100%;background:linear-gradient(90deg,#22c55e,#06b6d4);transition:width 0.3s}
table{width:100%;border-collapse:collapse;font-size:0.78rem}
th{text-align:left;color:#06b6d4;padding:8px;border-bottom:1px solid rgba(100,116,139,0.2);text-transform:uppercase;font-size:0.7rem;letter-spacing:1px}
td{padding:8px;border-bottom:1px solid rgba(100,116,139,0.08);color:#cbd5e1}
.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:0.68rem;font-weight:700}
.badge.success{background:rgba(34,197,94,0.2);color:#22c55e}
.badge.partial{background:rgba(251,146,60,0.2);color:#fb923c}
.footer{margin-top:30px;padding:14px;text-align:center;color:#64748b;font-size:0.8rem;
border-top:1px solid rgba(100,116,139,0.15)}
.footer a{color:#10b981;text-decoration:none;margin:0 8px}
.refresh-pulse{display:inline-block;width:8px;height:8px;background:#22c55e;border-radius:50%;animation:pulse 2s ease infinite;margin-right:6px;vertical-align:middle}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.3}}
.loading{text-align:center;color:#64748b;padding:30px;font-size:0.9rem}
</style><!-- DOCTRINE-60-UX-ENRICH cerebras-qwen-235b 20260424-111257 --><style id="doctrine60-ux-learning-dashboard">
body::before {
content: '';
position: fixed;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(0,0,0,0.12), transparent 70%);
z-index: -1;
pointer-events: none;
}
.card, .btn, .kpi, .panel {
opacity: 0;
transform: translateY(20px);
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.enter-stagger {
opacity: 1;
transform: translateY(0);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.pulse, .active, .live-indicator, .online {
animation: pulse 3s ease-in-out infinite;
}
.card:hover {
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
border-color: var(--accent);
}
.modal, .chat, .speech, .overlay {
backdrop-filter: blur(12px);
}
</style>
</head><body>
<div class="container">
<h1>🧠 Learning Analytics · WEVIA</h1>
<p class="subtitle">
<span class="refresh-pulse"></span>
Source: ai_learning_log + wevia_conversations · Auto-refresh 15s · 24h window
</p>
<section class="hero-grid" id="hero">
<div class="stat-card"><div class="stat-val green" id="stat-learned"></div><div class="stat-lbl">Learned 24h</div></div>
<div class="stat-card"><div class="stat-val blue" id="stat-success"></div><div class="stat-lbl">Success Rate</div></div>
<div class="stat-card"><div class="stat-val purple" id="stat-bots"></div><div class="stat-lbl">Chatbots Actifs</div></div>
<div class="stat-card"><div class="stat-val pink" id="stat-conv"></div><div class="stat-lbl">Conversations</div></div>
<div class="stat-card"><div class="stat-val orange" id="stat-sessions"></div><div class="stat-lbl">Sessions</div></div>
<div class="stat-card"><div class="stat-val green" id="stat-avg-ms"></div><div class="stat-lbl">Avg Latency</div></div>
</section>
<section class="section">
<h2>📊 Stats par chatbot (24h)</h2>
<div id="chatbots-grid" class="chatbots-grid"><div class="loading">Chargement...</div></div>
</section>
<section class="section">
<h2>🎯 Intents Distribution (24h)</h2>
<table id="intents-table"><thead><tr><th>Intent</th><th>Count</th><th>Success</th><th>Rate</th></tr></thead>
<tbody><tr><td colspan="4" class="loading">Chargement...</td></tr></tbody>
</table>
</section>
<section class="section">
<h2>🕐 Latest learnings (10)</h2>
<table id="latest-table"><thead><tr><th>Time</th><th>Chatbot</th><th>Intent</th><th>Outcome</th></tr></thead>
<tbody><tr><td colspan="4" class="loading">Chargement...</td></tr></tbody>
</table>
</section>
<div class="footer">
<a href="/">Home</a> · <a href="/dashboards-index.html">Dashboards</a> · <a href="/wevia-multiagent-dashboard.html">Multi-Agent</a> · <a href="/api/learning-analytics.php" target="_blank">JSON</a>
<br><br>Opus session v22 · Cross-chatbot learning visualized
</div>
</div>
<script>
async function refresh() {
try {
const r = await fetch('/api/learning-analytics.php', {cache:'no-store'});
const d = await r.json();
// Hero
document.getElementById('stat-learned').textContent = d.summary.total_learned || 0;
document.getElementById('stat-success').textContent = (d.summary.success_pct || 0) + '%';
document.getElementById('stat-bots').textContent = d.summary.bots_count || 0;
document.getElementById('stat-conv').textContent = d.summary.conversations || 0;
document.getElementById('stat-sessions').textContent = d.summary.sessions || 0;
document.getElementById('stat-avg-ms').textContent = (d.summary.avg_ms || 0) + 'ms';
// Chatbots grid
const grid = document.getElementById('chatbots-grid');
if (d.chatbots && d.chatbots.length) {
grid.innerHTML = d.chatbots.map(b => {
const rate = b.success_pct || 0;
const cls = rate >= 90 ? 'excellent' : (rate >= 70 ? 'good' : (rate >= 50 ? 'ok' : 'low'));
return `<div class="bot-card ${cls}">
<div class="bot-name">${b.chatbot}</div>
<div class="bot-stats"><span>${b.total} queries</span><b>${rate}%</b></div>
<div class="progress"><div class="progress-bar" style="width:${rate}%"></div></div>
</div>`;
}).join('');
} else {
grid.innerHTML = '<div class="loading">Pas de data 24h</div>';
}
// Intents
const itbody = document.querySelector('#intents-table tbody');
if (d.intents && d.intents.length) {
itbody.innerHTML = d.intents.map(i => `<tr>
<td><b>${i.intent || '?'}</b></td>
<td>${i.count}</td>
<td>${i.success}</td>
<td>${i.rate}%</td>
</tr>`).join('');
} else {
itbody.innerHTML = '<tr><td colspan="4" class="loading">Pas de data</td></tr>';
}
// Latest
const lbody = document.querySelector('#latest-table tbody');
if (d.latest && d.latest.length) {
lbody.innerHTML = d.latest.map(l => {
const badge = l.outcome ? '<span class="badge success">success</span>' : '<span class="badge partial">partial</span>';
const time = (l.learned_at || '').substring(11, 19);
return `<tr><td>${time}</td><td>${l.chatbot || '?'}</td><td>${l.intent || 'query'}</td><td>${badge}</td></tr>`;
}).join('');
} else {
lbody.innerHTML = '<tr><td colspan="4" class="loading">Pas de data</td></tr>';
}
} catch (e) {
console.error('refresh err', e);
}
}
refresh();
setInterval(refresh, 15000);
</script>
<!-- WTP_UDOCK_V1 -->
<script src="/wtp-unified-dock.js" defer></script>
<!-- DOCTRINE-60-UX-JS --><script id="doctrine60-ux-js-learning-dashboard">
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry, index) => {
if (entry.isIntersecting) {
setTimeout(() => {
entry.target.classList.add('enter-stagger');
}, index * 80);
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.card, .btn, .kpi, .panel').forEach(el => observer.observe(el));
</script>
</body></html>