| <!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> |