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