Files
html/learning-dashboard.html
Opus Wire 160b2a57bd
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
feat(learning-dashboard-v22): analytics apprentissage universel · 24h window
NEW:
- /learning-dashboard.html (8.2KB) · UX premium dashboard
- /api/learning-analytics.php (3.3KB) · PG aggregator
- dashboards-index enriched · section Learning & Analytics

METRICS LIVE (24h window):
- Hero: total_learned, success_pct, bots_count, conversations, sessions, avg_ms
- Per chatbot: total queries, success rate, progress bar colored
- Intents distribution: count, success rate per intent
- Latest 10 learnings: time, chatbot, intent, outcome badge

AUTO-REFRESH 15s · fetch /api/learning-analytics.php
Color-coded cards: excellent (>=90pct green), good (>=70 blue), ok (>=50 orange), low (<50 red)

Data sources:
- ai_learning_log (table v20) avec experience jsonb + outcome_success
- wevia_conversations (table v18) conversations count + sessions distinct

Validation LIVE:
- HTTP 200 on /learning-dashboard.html (9870b served)
- API returns structured JSON in <200ms
- 6 rows learned · 83pct success · 2 bots used (wevia-master 100pct, blade-ai 0pct)
- 10 conversations · 9 sessions

Doctrine:
- Point verite unique (1 dashboard = tout apprentissage)
- UX PREMIUM (gradients, badges colorés, progress bars, refresh pulse)
- RELIER toutes pages (dashboards-index -> learning-dashboard)
- Zero regression
- Additif pur
2026-04-22 05:18:23 +02:00

158 lines
8.0 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></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>
</body></html>