askbookie / assets /index.html
pmmdot's picture
Removed /upload!
396f15b
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AskBookie API</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background: #000;
color: #fafafa;
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid #27272a;
}
.header h1 {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.header p {
font-size: 0.875rem;
color: #71717a;
}
.status {
display: inline-block;
width: 8px;
height: 8px;
background: #22c55e;
border-radius: 50%;
margin-right: 0.5rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.card {
background: #0a0a0a;
border: 1px solid #27272a;
border-radius: 8px;
padding: 1.25rem;
}
.card:hover {
border-color: #3f3f46;
}
.card-label {
font-size: 0.75rem;
color: #71717a;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
}
.card-value {
font-size: 2rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.card-desc {
font-size: 0.875rem;
color: #a1a1aa;
}
.section {
background: #0a0a0a;
border: 1px solid #27272a;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
}
.section-title {
font-size: 1rem;
font-weight: 600;
margin-bottom: 1rem;
}
.user-card {
background: #000;
border: 1px solid #27272a;
border-radius: 6px;
padding: 1rem;
margin-bottom: 0.75rem;
}
.user-card:hover {
border-color: #3f3f46;
}
.user-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid #27272a;
}
.user-name {
font-size: 0.875rem;
font-weight: 500;
}
.badge {
font-size: 0.625rem;
padding: 0.125rem 0.5rem;
background: #27272a;
border-radius: 4px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #a1a1aa;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 0.75rem;
}
.stat {
text-align: center;
}
.stat-value {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.125rem;
}
.stat-label {
font-size: 0.75rem;
color: #71717a;
}
.btn {
position: fixed;
bottom: 3rem;
right: 2rem;
background: #fafafa;
color: #000;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
font-family: 'Inter', sans-serif;
}
.btn:hover {
background: #e5e5e5;
}
.btn:active {
background: #d4d4d4;
}
.footer {
text-align: center;
font-size: 0.75rem;
color: #52525b;
margin-top: 2rem;
}
@media (max-width: 768px) {
body {
padding: 1rem;
}
.grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>AskBookie API</h1>
<p><span class="status"></span>Live Dashboard</p>
</div>
<div class="grid">
<div class="card">
<div class="card-label">Uptime</div>
<div class="card-value" id="uptime">--</div>
<div class="card-desc" id="uptime-desc"></div>
</div>
<div class="card">
<div class="card-label">API Calls</div>
<div class="card-value" id="total-calls">--</div>
<div class="card-desc">total</div>
</div>
<div class="card">
<div class="card-label">Questions</div>
<div class="card-value" id="total-questions">--</div>
<div class="card-desc">asked</div>
</div>
<div class="card">
<div class="card-label">Memory</div>
<div class="card-value" id="memory">--</div>
<div class="card-desc">MB</div>
</div>
<div class="card">
<div class="card-label">Active Model</div>
<div class="card-value" id="model-name" style="font-size: 1rem;">--</div>
<div class="card-desc" id="model-desc">--</div>
</div>
</div>
<div class="section">
<div class="section-title">Per-User Analytics</div>
<div id="user-stats"></div>
</div>
</div>
<button class="btn" onclick="loadMetrics()">
<span id="refresh-text">Refresh</span>
</button>
<div class="footer" id="last-update"></div>
<script>
async function loadMetrics() {
const refreshText = document.getElementById('refresh-text');
const originalText = refreshText.textContent;
refreshText.textContent = 'Loading...';
try {
const response = await fetch('/health');
const health = await response.json();
// Format uptime as days, hours, mins
const hours = health.uptime_hours || 0;
const days = Math.floor(hours / 24);
const hrs = Math.floor(hours % 24);
const mins = Math.floor((hours * 60) % 60);
if (days > 0) {
document.getElementById('uptime').textContent = days;
document.getElementById('uptime-desc').textContent = `days, ${hrs}h ${mins}m`;
} else if (hrs > 0) {
document.getElementById('uptime').textContent = hrs;
document.getElementById('uptime-desc').textContent = `hours, ${mins}m`;
} else {
document.getElementById('uptime').textContent = mins;
document.getElementById('uptime-desc').textContent = 'minutes';
}
document.getElementById('total-calls').textContent = health.total_api_calls || 0;
document.getElementById('total-questions').textContent = health.total_questions || 0;
document.getElementById('memory').textContent = (health.memory_mb || 0).toFixed(0);
if (health.current_model) {
document.getElementById('model-name').textContent = health.current_model.name || '--';
document.getElementById('model-desc').textContent = health.current_model.description || '--';
}
renderUserStats(health.per_user || {});
document.getElementById('last-update').textContent =
`Updated ${new Date().toLocaleTimeString()}`;
} catch (error) {
console.error('Failed to load metrics:', error);
} finally {
refreshText.textContent = originalText;
}
}
function renderUserStats(users) {
const container = document.getElementById('user-stats');
container.innerHTML = '';
for (const [userName, stats] of Object.entries(users)) {
const userCard = document.createElement('div');
userCard.className = 'user-card';
userCard.innerHTML = `
<div class="user-header">
<span class="user-name">${userName}</span>
<span class="badge">${stats.role || 'user'}</span>
</div>
<div class="stats-grid">
<div class="stat">
<div class="stat-value">${stats.api_calls || 0}</div>
<div class="stat-label">calls</div>
</div>
<div class="stat">
<div class="stat-value">${stats.questions_asked || 0}</div>
<div class="stat-label">questions</div>
</div>
<div class="stat">
<div class="stat-value" style="color: ${stats.success_rate < 100 ? '#ef4444' : '#22c55e'}">${stats.success_rate || 100}%</div>
<div class="stat-label">success</div>
</div>
<div class="stat">
<div class="stat-value">${stats.average_latency_seconds || 0}s</div>
<div class="stat-label">latency</div>
</div>
</div>
${(stats.ask_fails > 0) ? `
<div style="margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid #27272a; font-size: 0.75rem; color: #ef4444;">
Failures: ${stats.ask_fails} ask
</div>
` : ''}
`;
container.appendChild(userCard);
}
}
setInterval(loadMetrics, 5000);
loadMetrics();
</script>
</body>
</html>