| | <!DOCTYPE html> |
| | <html lang="en"> |
| |
|
| | <head> |
| | <meta charset="UTF-8" /> |
| | <title>NexusGraph AI Analytics</title> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| | <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> |
| |
|
| | <style> |
| | :root { |
| | --bg: radial-gradient(1200px 600px at top, #e0e7ff 0%, #f8fafc 60%); |
| | --card: rgba(255, 255, 255, 0.9); |
| | --border: rgba(15, 23, 42, 0.08); |
| | --primary: #4f46e5; |
| | --secondary: #0ea5e9; |
| | --text: #0f172a; |
| | --muted: #64748b; |
| | --success: #16a34a; |
| | --error: #dc2626; |
| | } |
| | |
| | [data-theme="dark"] { |
| | --bg: radial-gradient(1200px 600px at top, #1e1b4b 0%, #0f172a 60%); |
| | --card: rgba(30, 41, 59, 0.9); |
| | --border: rgba(148, 163, 184, 0.1); |
| | --primary: #818cf8; |
| | --secondary: #38bdf8; |
| | --text: #f1f5f9; |
| | --muted: #94a3b8; |
| | --success: #4ade80; |
| | --error: #f87171; |
| | } |
| | |
| | * { |
| | box-sizing: border-box; |
| | font-family: Inter, sans-serif; |
| | } |
| | |
| | body { |
| | margin: 0; |
| | min-height: 100vh; |
| | background: var(--bg); |
| | padding: 40px 16px; |
| | color: var(--text); |
| | transition: background 0.3s ease, color 0.3s ease; |
| | } |
| | |
| | .container { |
| | max-width: 1200px; |
| | margin: 0 auto; |
| | } |
| | |
| | .header { |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | margin-bottom: 32px; |
| | } |
| | |
| | h1 { |
| | font-size: 2.2rem; |
| | margin: 0; |
| | font-weight: 700; |
| | background: linear-gradient(135deg, #4f46e5, #06b6d4); |
| | background-clip: text; |
| | -webkit-background-clip: text; |
| | -webkit-text-fill-color: transparent; |
| | } |
| | |
| | .back-btn { |
| | padding: 10px 20px; |
| | background: var(--primary); |
| | color: white; |
| | text-decoration: none; |
| | border-radius: 12px; |
| | font-weight: 600; |
| | transition: transform 0.2s ease; |
| | } |
| | |
| | .back-btn:hover { |
| | transform: translateY(-2px); |
| | } |
| | |
| | .stats-grid { |
| | display: grid; |
| | grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); |
| | gap: 20px; |
| | margin-bottom: 32px; |
| | } |
| | |
| | .stat-card { |
| | background: var(--card); |
| | backdrop-filter: blur(16px); |
| | border-radius: 18px; |
| | padding: 24px; |
| | border: 1px solid var(--border); |
| | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
| | } |
| | |
| | .stat-label { |
| | font-size: 0.85rem; |
| | color: var(--muted); |
| | margin-bottom: 8px; |
| | text-transform: uppercase; |
| | letter-spacing: 0.5px; |
| | } |
| | |
| | .stat-value { |
| | font-size: 2.5rem; |
| | font-weight: 700; |
| | color: var(--primary); |
| | } |
| | |
| | .card { |
| | background: var(--card); |
| | backdrop-filter: blur(16px); |
| | border-radius: 18px; |
| | padding: 28px; |
| | border: 1px solid var(--border); |
| | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); |
| | margin-bottom: 24px; |
| | } |
| | |
| | .card h2 { |
| | margin-top: 0; |
| | margin-bottom: 20px; |
| | font-size: 1.3rem; |
| | } |
| | |
| | table { |
| | width: 100%; |
| | border-collapse: collapse; |
| | } |
| | |
| | th, |
| | td { |
| | text-align: left; |
| | padding: 12px; |
| | border-bottom: 1px solid var(--border); |
| | } |
| | |
| | th { |
| | font-weight: 600; |
| | color: var(--muted); |
| | font-size: 0.85rem; |
| | text-transform: uppercase; |
| | letter-spacing: 0.5px; |
| | } |
| | |
| | .badge { |
| | display: inline-block; |
| | padding: 4px 10px; |
| | border-radius: 12px; |
| | font-size: 0.75rem; |
| | font-weight: 600; |
| | } |
| | |
| | .badge-success { |
| | background: #dcfce7; |
| | color: #166534; |
| | } |
| | |
| | .badge-error { |
| | background: #fee2e2; |
| | color: #991b1b; |
| | } |
| | |
| | .theme-toggle { |
| | position: fixed; |
| | top: 20px; |
| | right: 20px; |
| | background: var(--card); |
| | border: 1px solid var(--border); |
| | border-radius: 12px; |
| | padding: 10px; |
| | cursor: pointer; |
| | font-size: 1.4rem; |
| | transition: transform 0.2s ease; |
| | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | } |
| | |
| | .theme-toggle:hover { |
| | transform: scale(1.1); |
| | } |
| | |
| | .empty-state { |
| | text-align: center; |
| | padding: 60px 20px; |
| | color: var(--muted); |
| | } |
| | </style> |
| | </head> |
| |
|
| | <body> |
| | <button class="theme-toggle" onclick="toggleTheme()" title="Toggle dark mode">🌙</button> |
| |
|
| | <div class="container"> |
| | <div class="header"> |
| | <h1>📊 Analytics Dashboard</h1> |
| | <a href="/" class="back-btn">← Back to RAG</a> |
| | </div> |
| |
|
| | <div id="stats-container"> |
| | <div class="empty-state"> |
| | <h2>Loading analytics...</h2> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | // ===== THEME TOGGLE ===== |
| | function toggleTheme() { |
| | const html = document.documentElement; |
| | const currentTheme = html.getAttribute('data-theme'); |
| | const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; |
| | |
| | html.setAttribute('data-theme', newTheme); |
| | localStorage.setItem('theme', newTheme); |
| | |
| | const btn = document.querySelector('.theme-toggle'); |
| | btn.textContent = newTheme === 'dark' ? '☀️' : '🌙'; |
| | } |
| | |
| | // Load saved theme |
| | (function () { |
| | const savedTheme = localStorage.getItem('theme') || 'light'; |
| | document.documentElement.setAttribute('data-theme', savedTheme); |
| | const btn = document.querySelector('.theme-toggle'); |
| | if (btn) btn.textContent = savedTheme === 'dark' ? '☀️' : '🌙'; |
| | })(); |
| | |
| | // ===== LOAD ANALYTICS ===== |
| | async function loadAnalytics() { |
| | try { |
| | const res = await fetch('/analytics'); |
| | |
| | const data = await res.json(); |
| | |
| | if (data.total_queries === 0) { |
| | document.getElementById('stats-container').innerHTML = ` |
| | <div class="empty-state"> |
| | <h2>No data yet</h2> |
| | <p>Start asking questions to see analytics!</p> |
| | </div> |
| | `; |
| | return; |
| | } |
| | |
| | const html = ` |
| | <div class="stats-grid"> |
| | <div class="stat-card"> |
| | <div class="stat-label">Total Queries</div> |
| | <div class="stat-value">${data.total_queries}</div> |
| | </div> |
| | <div class="stat-card"> |
| | <div class="stat-label">Knowledge Rate</div> |
| | <div class="stat-value">${data.knowledge_rate}%</div> |
| | </div> |
| | <div class="stat-card"> |
| | <div class="stat-label">Avg Confidence</div> |
| | <div class="stat-value">${(data.avg_confidence * 100).toFixed(0)}%</div> |
| | </div> |
| | <div class="stat-card"> |
| | <div class="stat-label">Unknown Queries</div> |
| | <div class="stat-value" style="color: var(--error)">${data.unknown_count}</div> |
| | </div> |
| | </div> |
| | |
| | <div class="card"> |
| | <h2>Recent Queries</h2> |
| | <table> |
| | <thead> |
| | <tr> |
| | <th>Query</th> |
| | <th>Confidence</th> |
| | <th>Status</th> |
| | </tr> |
| | </thead> |
| | <tbody> |
| | ${data.top_queries.map(q => ` |
| | <tr> |
| | <td>${q.query}</td> |
| | <td>${(q.confidence * 100).toFixed(0)}%</td> |
| | <td> |
| | <span class="badge ${q.answer_known ? 'badge-success' : 'badge-error'}"> |
| | ${q.answer_known ? 'Known' : 'Unknown'} |
| | </span> |
| | </td> |
| | </tr> |
| | `).join('')} |
| | </tbody> |
| | </table> |
| | </div> |
| | |
| | ${data.recent_unknown.length > 0 ? ` |
| | <div class="card"> |
| | <h2>Recent "I Don't Know" Queries</h2> |
| | <table> |
| | <thead> |
| | <tr> |
| | <th>Query</th> |
| | <th>Time</th> |
| | </tr> |
| | </thead> |
| | <tbody> |
| | ${data.recent_unknown.map(q => ` |
| | <tr> |
| | <td>${q.query}</td> |
| | <td>${q.timestamp}</td> |
| | </tr> |
| | `).join('')} |
| | </tbody> |
| | </table> |
| | </div> |
| | ` : ''} |
| | `; |
| | |
| | document.getElementById('stats-container').innerHTML = html; |
| | } catch (e) { |
| | document.getElementById('stats-container').innerHTML = ` |
| | <div class="empty-state"> |
| | <h2>Error loading analytics</h2> |
| | <p>${e.message}</p> |
| | </div> |
| | `; |
| | } |
| | } |
| | |
| | // Load on page load |
| | loadAnalytics(); |
| | </script> |
| | </body> |
| |
|
| | </html> |