membra-agent-workforce / app /static /dashboard.html
josephrw's picture
Upload app/static/dashboard.html with huggingface_hub
daf1409 verified
Raw
History Blame Contribute Delete
6.74 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Workforce Dashboard</title>
<style>
:root {
--bg: #0b0f1a;
--card: #111827;
--border: #1f2937;
--text: #e5e7eb;
--muted: #9ca3af;
--accent: #3b82f6;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.6;
}
header {
background: var(--card);
border-bottom: 1px solid var(--border);
padding: 1.5rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
header h1 { font-size: 1.5rem; }
header .status {
background: var(--success);
color: #fff;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
}
main { padding: 2rem; max-width: 1400px; margin: 0 auto; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 0.75rem;
padding: 1.5rem;
}
.card h2 { font-size: 1rem; margin-bottom: 1rem; color: var(--muted); }
.stat { font-size: 2rem; font-weight: 700; }
.stat-label { font-size: 0.875rem; color: var(--muted); margin-top: 0.25rem; }
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
th, td { text-align: left; padding: 0.75rem; border-bottom: 1px solid var(--border); font-size: 0.875rem; }
th { color: var(--muted); font-weight: 500; text-transform: uppercase; font-size: 0.75rem; letter-spacing: 0.05em; }
.badge {
display: inline-block;
padding: 0.125rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
font-weight: 600;
}
.badge-success { background: rgba(16, 185, 129, 0.15); color: var(--success); }
.badge-error { background: rgba(239, 68, 68, 0.15); color: var(--danger); }
.badge-pending { background: rgba(245, 158, 11, 0.15); color: var(--warning); }
.agent-list { display: flex; flex-direction: column; gap: 0.5rem; max-height: 500px; overflow-y: auto; }
.agent-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
background: var(--bg);
border-radius: 0.5rem;
border: 1px solid var(--border);
}
.agent-item:hover { border-color: var(--accent); }
.agent-name { font-weight: 500; }
.agent-meta { font-size: 0.75rem; color: var(--muted); }
.run-row:hover { background: rgba(59, 130, 246, 0.05); }
</style>
</head>
<body>
<header>
<h1>Agent Workforce Platform</h1>
<span class="status" id="status">Operational</span>
</header>
<main>
<div class="grid">
<div class="card">
<h2>Total Agents</h2>
<div class="stat" id="total-agents">30</div>
<div class="stat-label">Active workers</div>
</div>
<div class="card">
<h2>Total Runs</h2>
<div class="stat" id="total-runs">-</div>
<div class="stat-label">All-time executions</div>
</div>
<div class="card">
<h2>Avg Cost / Run</h2>
<div class="stat" id="avg-cost">-</div>
<div class="stat-label">USD per execution</div>
</div>
<div class="card">
<h2>Portfolio Value</h2>
<div class="stat" id="portfolio-value">-</div>
<div class="stat-label">Worker appraisal total</div>
</div>
</div>
<div class="grid" style="margin-top: 2rem;">
<div class="card" style="grid-column: 1 / -1;">
<h2>Recent Runs</h2>
<table>
<thead>
<tr><th>Agent</th><th>Query</th><th>Status</th><th>Tokens</th><th>Cost</th><th>Time</th></tr>
</thead>
<tbody id="runs-table"></tbody>
</table>
</div>
</div>
<div class="grid" style="margin-top: 2rem;">
<div class="card" style="grid-column: 1 / -1;">
<h2>All Agents</h2>
<div class="agent-list" id="agent-list"></div>
</div>
</div>
</main>
<script>
const API = window.location.origin;
async function fetchAgents() {
const res = await fetch(`${API}/agents/`);
const agents = await res.json();
const list = document.getElementById('agent-list');
list.innerHTML = agents.map(a => `
<div class="agent-item">
<div>
<div class="agent-name">${a.name}</div>
<div class="agent-meta">${a.category} · $${a.price_per_run}/run · ${a.tags.slice(0,3).join(', ')}</div>
</div>
<span class="badge badge-${a.status === 'active' ? 'success' : 'pending'}">${a.status || 'active'}</span>
</div>
`).join('');
document.getElementById('total-agents').textContent = agents.length;
}
async function fetchRuns() {
const res = await fetch(`${API}/agents/runs/recent?limit=20`);
const runs = await res.json();
const tbody = document.getElementById('runs-table');
tbody.innerHTML = runs.map(r => `
<tr class="run-row">
<td>${r.agent_name}</td>
<td>${r.query}</td>
<td><span class="badge badge-${r.status === 'success' ? 'success' : r.status === 'error' ? 'error' : 'pending'}">${r.status}</span></td>
<td>${r.tokens_used || 0}</td>
<td>$${(r.cost || 0).toFixed(4)}</td>
<td>${r.created_at ? new Date(r.created_at).toLocaleTimeString() : '-'}</td>
</tr>
`).join('');
document.getElementById('total-runs').textContent = runs.length;
const avg = runs.length ? runs.reduce((s, r) => s + (r.cost || 0), 0) / runs.length : 0;
document.getElementById('avg-cost').textContent = '$' + avg.toFixed(4);
}
async function fetchAppraisal() {
const res = await fetch(`${API}/agents/portfolio/appraisal`);
const data = await res.json();
document.getElementById('portfolio-value').textContent = '$' + (data.total_valuation || 0).toLocaleString();
}
async function init() {
try {
await Promise.all([fetchAgents(), fetchRuns(), fetchAppraisal()]);
} catch (e) {
console.error(e);
document.getElementById('status').textContent = 'Error';
document.getElementById('status').style.background = 'var(--danger)';
}
}
init();
setInterval(fetchRuns, 10000);
</script>
</body>
</html>