diff options
Diffstat (limited to 'downloads/dashboard.html')
| -rw-r--r-- | downloads/dashboard.html | 643 |
1 files changed, 643 insertions, 0 deletions
diff --git a/downloads/dashboard.html b/downloads/dashboard.html new file mode 100644 index 0000000..d4275f2 --- /dev/null +++ b/downloads/dashboard.html @@ -0,0 +1,643 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>newOS4 Dashboard</title> + <style> + :root { + --bg: #0a0f0a; + --surface: #0f1a0f; + --border: #1a3320; + --accent: #00ff41; + --accent-dim: #00cc33; + --accent-glow: rgba(0, 255, 65, 0.15); + --text: #e0ffe6; + --text-dim: #88aa88; + --warn: #ffcc00; + --danger: #ff4444; + --info: #44aaff; + } + * { box-sizing: border-box; margin: 0; padding: 0; } + body { + background: var(--bg); + color: var(--text); + font-family: 'Segoe UI', system-ui, sans-serif; + line-height: 1.5; + min-height: 100vh; + } + header { + border-bottom: 1px solid var(--border); + padding: 1rem 1.5rem; + background: var(--surface); + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 0.5rem; + position: sticky; + top: 0; + z-index: 10; + } + header h1 { + font-size: 1.2rem; + color: var(--accent); + text-shadow: 0 0 8px var(--accent-glow); + } + header .meta { + font-size: 0.8rem; + color: var(--text-dim); + } + .header-controls { + display: flex; + gap: 0.5rem; + align-items: center; + } + .header-controls button { + background: var(--surface); + border: 1px solid var(--border); + color: var(--text); + padding: 0.3rem 0.8rem; + border-radius: 4px; + cursor: pointer; + font-size: 0.8rem; + font-family: inherit; + } + .header-controls button:hover { + border-color: var(--accent); + color: var(--accent); + } + .header-controls .status-dot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 4px; + } + .status-dot.green { background: var(--accent); box-shadow: 0 0 6px var(--accent); } + .status-dot.yellow { background: var(--warn); box-shadow: 0 0 6px var(--warn); } + .status-dot.red { background: var(--danger); box-shadow: 0 0 6px var(--danger); } + .status-dot.gray { background: var(--text-dim); } + + .container { max-width: 1200px; margin: 0 auto; padding: 1rem; } + + .grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 1rem; + margin-bottom: 1.5rem; + } + .panel { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 6px; + padding: 1rem; + transition: border-color 0.3s; + } + .panel:hover { border-color: var(--accent-dim); } + .panel h3 { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-dim); + margin-bottom: 0.5rem; + display: flex; + align-items: center; + gap: 0.4rem; + } + .panel .value { + font-size: 1.8rem; + font-weight: 700; + color: var(--text); + } + .panel .sub { + font-size: 0.75rem; + color: var(--text-dim); + margin-top: 0.2rem; + } + .panel .progress { + height: 4px; + background: #1a3320; + border-radius: 2px; + overflow: hidden; + margin-top: 0.5rem; + } + .panel .progress-fill { + height: 100%; + border-radius: 2px; + transition: width 0.5s; + } + .progress-fill.green { background: var(--accent); } + .progress-fill.yellow { background: var(--warn); } + .progress-fill.red { background: var(--danger); } + + .section { + margin: 1.5rem 0; + padding: 1rem; + background: var(--surface); + border-left: 3px solid var(--accent); + border-radius: 0 6px 6px 0; + } + .section h2 { + font-size: 1rem; + color: var(--accent); + margin-bottom: 0.75rem; + } + .section.warn { border-left-color: var(--warn); } + .section.danger { border-left-color: var(--danger); } + + table { + width: 100%; + border-collapse: collapse; + font-size: 0.85rem; + } + th, td { + text-align: left; + padding: 0.4rem 0.6rem; + border-bottom: 1px solid var(--border); + } + th { + color: var(--accent); + font-weight: 600; + text-transform: uppercase; + font-size: 0.7rem; + letter-spacing: 0.05em; + } + td { color: var(--text); } + tr:hover td { background: rgba(0,255,65,0.04); } + + .badge { + display: inline-block; + padding: 0.1rem 0.4rem; + border: 1px solid var(--border); + font-size: 0.7rem; + border-radius: 3px; + color: var(--text-dim); + } + .badge.green { border-color: var(--accent); color: var(--accent); } + .badge.yellow { border-color: var(--warn); color: var(--warn); } + .badge.red { border-color: var(--danger); color: var(--danger); } + .badge.blue { border-color: var(--info); color: var(--info); } + + .agent-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.4rem 0; + border-bottom: 1px dashed var(--border); + font-size: 0.85rem; + } + .agent-row:last-child { border-bottom: none; } + .agent-row .name { color: var(--text); } + .agent-row .status { font-size: 0.75rem; } + .agent-row .caps { font-size: 0.7rem; color: var(--text-dim); } + + .metric-row { + display: flex; + justify-content: space-between; + padding: 0.3rem 0; + border-bottom: 1px dashed var(--border); + font-size: 0.85rem; + } + .metric-row:last-child { border-bottom: none; } + + .loading { + text-align: center; + padding: 3rem; + color: var(--text-dim); + } + .loading .spinner { + display: inline-block; + width: 24px; + height: 24px; + border: 2px solid var(--border); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin-bottom: 0.5rem; + } + @keyframes spin { to { transform: rotate(360deg); } } + + .error-banner { + background: rgba(255,68,68,0.1); + border: 1px solid var(--danger); + color: var(--danger); + padding: 0.75rem; + border-radius: 6px; + margin-bottom: 1rem; + font-size: 0.85rem; + } + + .last-updated { + text-align: center; + font-size: 0.7rem; + color: var(--text-dim); + padding: 1rem; + border-top: 1px solid var(--border); + margin-top: 1rem; + } + + @media (max-width: 640px) { + header { padding: 0.75rem; flex-direction: column; align-items: flex-start; } + .container { padding: 0.75rem; } + .grid { grid-template-columns: 1fr 1fr; } + .panel .value { font-size: 1.4rem; } + } + @media (max-width: 400px) { + .grid { grid-template-columns: 1fr; } + } + </style> +</head> +<body> +<header> + <div> + <h1>⬡ newOS4 Dashboard</h1> + <div class="meta">Live system telemetry · <span id="version-display">v?</span></div> + </div> + <div class="header-controls"> + <span id="connection-status"><span class="status-dot gray"></span> Loading...</span> + <button onclick="refreshNow()">⟳ Refresh</button> + <button onclick="document.querySelector('.container').classList.toggle('compact')">⊞ Toggle</button> + </div> +</header> + +<div class="container"> + <div id="error-banner" class="error-banner" style="display:none"></div> + + <div id="loading" class="loading"> + <div class="spinner"></div> + <div>Connecting to newOS4...</div> + </div> + + <div id="dashboard-content" style="display:none"> + <!-- Metric Panels --> + <div class="grid" id="metric-grid"> + <div class="panel" id="panel-modules"> + <h3>🧩 Modules</h3> + <div class="value" id="val-modules">--</div> + <div class="sub" id="sub-modules"></div> + <div class="progress"><div class="progress-fill green" id="bar-modules" style="width:0%"></div></div> + </div> + <div class="panel" id="panel-agents"> + <h3>🤖 Agents</h3> + <div class="value" id="val-agents">--</div> + <div class="sub" id="sub-agents"></div> + <div class="progress"><div class="progress-fill" id="bar-agents" style="width:0%"></div></div> + </div> + <div class="panel" id="panel-memory"> + <h3>💾 Memory</h3> + <div class="value" id="val-memory">--</div> + <div class="sub" id="sub-memory"></div> + <div class="progress"><div class="progress-fill green" id="bar-memory" style="width:0%"></div></div> + </div> + <div class="panel" id="panel-vfs"> + <h3>📁 VFS</h3> + <div class="value" id="val-vfs">--</div> + <div class="sub" id="sub-vfs"></div> + <div class="progress"><div class="progress-fill green" id="bar-vfs" style="width:0%"></div></div> + </div> + <div class="panel" id="panel-ollama"> + <h3>🧠 Ollama</h3> + <div class="value" id="val-ollama">--</div> + <div class="sub" id="sub-ollama"></div> + <div class="progress"><div class="progress-fill green" id="bar-ollama" style="width:0%"></div></div> + </div> + <div class="panel" id="panel-skills"> + <h3>📚 Skills</h3> + <div class="value" id="val-skills">--</div> + <div class="sub" id="sub-skills"></div> + <div class="progress"><div class="progress-fill green" id="bar-skills" style="width:0%"></div></div> + </div> + </div> + + <!-- Agent Pool --> + <div class="section" id="section-agents"> + <h2>🤖 Agent Pool</h2> + <div id="agent-list"><div class="metric-row">No agents spawned</div></div> + </div> + + <!-- Token Usage --> + <div class="section" id="section-tokens"> + <h2>📊 LLM Token Usage</h2> + <div id="token-summary"></div> + <div style="margin-top:0.5rem"> + <table> + <thead><tr><th>Model</th><th>Requests</th><th>Tokens</th><th>%</th></tr></thead> + <tbody id="token-by-model"></tbody> + </table> + </div> + </div> + + <!-- Tool Latency --> + <div class="section warn" id="section-latency"> + <h2>⏱ Tool Latency (Top 10)</h2> + <table> + <thead><tr><th>Tool</th><th>Avg</th><th>Max</th><th>Calls</th><th>Errors</th></tr></thead> + <tbody id="latency-table"></tbody> + </table> + </div> + + <!-- World Model --> + <div class="section" id="section-world-model"> + <h2>🌐 World Model</h2> + <div id="world-model-metrics"> + <div class="metric-row"><span>Not synthesized yet</span><span>—</span></div> + </div> + </div> + + <!-- System Health --> + <div class="section" id="section-health"> + <h2>🩺 System Health</h2> + <div id="health-metrics"></div> + </div> + </div> + + <div class="last-updated"> + Last updated: <span id="last-updated-time">--</span> · Auto-refresh every 5s + </div> +</div> + +<script> +(function() { + 'use strict'; + + const DASHBOARD_KEY = 'webx11_dashboard_data'; + const REFRESH_INTERVAL = 5000; + let refreshTimer = null; + let lastData = null; + + function showError(msg) { + const banner = document.getElementById('error-banner'); + banner.textContent = '⚠️ ' + msg; + banner.style.display = 'block'; + } + + function hideError() { + document.getElementById('error-banner').style.display = 'none'; + } + + function showLoading() { + document.getElementById('loading').style.display = 'block'; + document.getElementById('dashboard-content').style.display = 'none'; + } + + function showContent() { + document.getElementById('loading').style.display = 'none'; + document.getElementById('dashboard-content').style.display = 'block'; + } + + function updateConnectionStatus(ok) { + const el = document.getElementById('connection-status'); + if (ok) { + el.innerHTML = '<span class="status-dot green"></span> Connected'; + } else { + el.innerHTML = '<span class="status-dot red"></span> Disconnected'; + } + } + + function fetchDashboardData() { + // Try to get data from localStorage (set by the AI tool or terminal command) + try { + const raw = localStorage.getItem(DASHBOARD_KEY); + if (raw) { + const data = JSON.parse(raw); + if (data && data.timestamp && (Date.now() - data.timestamp < 10000)) { + return data; // fresh enough + } + } + } catch (e) {} + return null; + } + + function renderDashboard(data) { + if (!data) { + showError('No dashboard data available. Open newOS4 terminal and run: dashboard'); + updateConnectionStatus(false); + return; + } + + hideError(); + updateConnectionStatus(true); + lastData = data; + + // Version + document.getElementById('version-display').textContent = data.version || 'v?'; + + // ── Metric Panels ── + // Modules + const modCount = data.modules?.loaded || 0; + const modTotal = data.modules?.total || modCount; + document.getElementById('val-modules').textContent = modCount + '/' + modTotal; + document.getElementById('sub-modules').textContent = (data.modules?.modifiedCount || 0) + ' modified'; + document.getElementById('bar-modules').style.width = (modTotal > 0 ? (modCount / modTotal * 100) : 0) + '%'; + + // Agents + const agents = data.agents || {}; + const agentTotal = agents.total || 0; + const agentBusy = agents.busy || 0; + const agentIdle = agents.idle || 0; + document.getElementById('val-agents').textContent = agentTotal; + document.getElementById('sub-agents').textContent = agentBusy + ' busy, ' + agentIdle + ' idle' + (agents.sleeping ? ', ' + agents.sleeping + ' sleeping' : ''); + const barAgents = document.getElementById('bar-agents'); + barAgents.style.width = (agentTotal > 0 ? (agentBusy / agentTotal * 100) : 0) + '%'; + barAgents.className = 'progress-fill ' + (agentBusy > 0 ? 'yellow' : 'green'); + + // Memory + const memStr = data.memory?.heap || 'N/A'; + const memNum = parseFloat(memStr) || 0; + document.getElementById('val-memory').textContent = memStr; + document.getElementById('sub-memory').textContent = data.memory?.graphNodes ? data.memory.graphNodes + ' graph nodes' : ''; + const barMem = document.getElementById('bar-memory'); + barMem.style.width = Math.min(memNum / 2, 100) + '%'; + barMem.className = 'progress-fill ' + (memNum > 100 ? 'yellow' : (memNum > 200 ? 'red' : 'green')); + + // VFS + const vfs = data.vfs || {}; + document.getElementById('val-vfs').textContent = vfs.files || 0; + document.getElementById('sub-vfs').textContent = (vfs.dirs || 0) + ' dirs, ' + (vfs.size || '0KB'); + document.getElementById('bar-vfs').style.width = Math.min((vfs.files || 0) / 2, 100) + '%'; + + // Ollama + const ollama = data.ollama || {}; + const ollamaOk = ollama.connected || false; + document.getElementById('val-ollama').textContent = ollamaOk ? (ollama.model || 'Connected') : 'Disconnected'; + document.getElementById('sub-ollama').textContent = ollamaOk ? (ollama.provider || '') : ''; + const barOllama = document.getElementById('bar-ollama'); + barOllama.style.width = ollamaOk ? '100%' : '0%'; + barOllama.className = 'progress-fill ' + (ollamaOk ? 'green' : 'red'); + + // Skills + const skillCount = data.skills?.count || 0; + document.getElementById('val-skills').textContent = skillCount; + document.getElementById('sub-skills').textContent = data.skills?.note || ''; + document.getElementById('bar-skills').style.width = Math.min(skillCount * 2.5, 100) + '%'; + + // ── Agent Pool ── + const agentList = document.getElementById('agent-list'); + if (agents.list && agents.list.length > 0) { + let html = ''; + for (const a of agents.list) { + const statusClass = a.status === 'busy' ? 'yellow' : a.status === 'idle' ? 'green' : a.status === 'sleeping' ? 'blue' : 'red'; + html += '<div class="agent-row">' + + '<div><span class="name">' + a.id + '</span> <span class="caps">[' + (a.role || 'worker') + ']</span></div>' + + '<div><span class="badge ' + statusClass + '">' + a.status + '</span></div>' + + '</div>'; + } + agentList.innerHTML = html; + } else { + agentList.innerHTML = '<div class="metric-row">No agents spawned</div>'; + } + + // ── Token Usage ── + const tokenSection = document.getElementById('token-summary'); + const usage = data.usage || {}; + if (usage.total && usage.total > 0) { + const avgLat = usage.avgLatency || 0; + const totalTok = (usage.totalPrompt || 0) + (usage.totalCompletion || 0); + tokenSection.innerHTML = + '<div class="metric-row"><span>Total requests</span><span>' + usage.total + '</span></div>' + + '<div class="metric-row"><span>Total tokens</span><span>' + totalTok.toLocaleString() + '</span></div>' + + '<div class="metric-row"><span>Avg latency</span><span>' + avgLat + 'ms</span></div>'; + + // Per-model breakdown + const tbody = document.getElementById('token-by-model'); + const byModel = usage.byModel || {}; + const modelKeys = Object.keys(byModel); + if (modelKeys.length > 0) { + let html = ''; + for (const model of modelKeys) { + const m = byModel[model]; + const pct = totalTok > 0 ? (m.tokens / totalTok * 100).toFixed(1) : '0'; + html += '<tr><td>' + model + '</td><td>' + m.requests + '</td><td>' + m.tokens.toLocaleString() + '</td><td>' + pct + '%</td></tr>'; + } + tbody.innerHTML = html; + } else { + tbody.innerHTML = '<tr><td colspan="4" style="color:var(--text-dim)">No usage data yet</td></tr>'; + } + } else { + tokenSection.innerHTML = '<div class="metric-row" style="color:var(--text-dim)">No usage data yet</div>'; + document.getElementById('token-by-model').innerHTML = '<tr><td colspan="4" style="color:var(--text-dim)">Run some AI queries to see usage</td></tr>'; + } + + // ── Tool Latency ── + const latencyTbody = document.getElementById('latency-table'); + const latency = data.latency || []; + if (latency.length > 0) { + let html = ''; + for (const t of latency.slice(0, 10)) { + const errClass = t.errors > 0 ? ' class="yellow"' : ''; + html += '<tr' + errClass + '><td>' + t.name + '</td><td>' + t.avg + 'ms</td><td>' + t.max + 'ms</td><td>' + t.calls + '</td><td>' + (t.errors > 0 ? '⚠️ ' + t.errors : '0') + '</td></tr>'; + } + latencyTbody.innerHTML = html; + } else { + latencyTbody.innerHTML = '<tr><td colspan="5" style="color:var(--text-dim)">No latency data yet</td></tr>'; + } + + // ── World Model ── + const wmDiv = document.getElementById('world-model-metrics'); + const wmData = data.worldModel; + if (wmData) { + const synTime = wmData.synthesized ? new Date(wmData.synthesized).toLocaleTimeString() : '?'; + let wmHtml = ''; + wmHtml += '<div class="metric-row"><span>Version</span><span>' + (wmData.version || '?') + '</span></div>'; + wmHtml += '<div class="metric-row"><span>Last synthesized</span><span>' + synTime + '</span></div>'; + wmHtml += '<div class="metric-row"><span>Modules</span><span>' + (wmData.modules || '?') + '</span></div>'; + wmHtml += '<div class="metric-row"><span>Skills</span><span>' + (wmData.skills || 0) + '</span></div>'; + wmHtml += '<div class="metric-row"><span>Files</span><span>' + (wmData.files || 0) + '</span></div>'; + wmHtml += '<div class="metric-row"><span>Agents</span><span>' + (wmData.agents || 0) + '</span></div>'; + wmHtml += '<div class="metric-row"><span>Memory graph</span><span>' + (wmData.memoryNodes || 0) + ' nodes, ' + (wmData.memoryEdges || 0) + ' edges</span></div>'; + wmHtml += '<div class="metric-row"><span>Health</span><span>' + (wmData.health || '?') + '</span></div>'; + if (wmData.activeProjects && wmData.activeProjects.length > 0) { + wmHtml += '<div class="metric-row"><span>Active projects</span><span>' + wmData.activeProjects.join(', ') + '</span></div>'; + } + if (wmData.recent && wmData.recent.length > 0) { + wmHtml += '<div class="metric-row"><span>Recent changes</span><span>' + wmData.recent.join('; ') + '</span></div>'; + } + wmDiv.innerHTML = wmHtml; + } else { + wmDiv.innerHTML = '<div class="metric-row" style="color:var(--text-dim)">World model not synthesized — run world-model-refresh in terminal</div>'; + } + + // ── System Health ── + const healthDiv = document.getElementById('health-metrics'); + const health = data.health || {}; + let healthHtml = ''; + healthHtml += '<div class="metric-row"><span>Modules loaded</span><span>' + (health.modules || '?') + '</span></div>'; + healthHtml += '<div class="metric-row"><span>VFS size</span><span>' + (health.vfsSize || '?') + '</span></div>'; + healthHtml += '<div class="metric-row"><span>Online</span><span>' + (health.online ? '✅ Online' : '🔴 Offline') + '</span></div>'; + healthHtml += '<div class="metric-row"><span>Errors</span><span>' + (health.errors > 0 ? '⚠️ ' + health.errors : '✅ 0') + '</span></div>'; + healthHtml += '<div class="metric-row"><span>Predictor accuracy</span><span>' + (health.predictorAccuracy || 'N/A') + '</span></div>'; + healthDiv.innerHTML = healthHtml; + + // Timestamp + document.getElementById('last-updated-time').textContent = new Date().toLocaleTimeString(); + } + + function refreshNow() { + // Signal the terminal to update dashboard data + try { + localStorage.setItem(DASHBOARD_KEY + '_request', Date.now().toString()); + } catch (e) {} + // Try reading fresh data + const data = fetchDashboardData(); + if (data) { + renderDashboard(data); + } else { + // If no data, try to fetch via the newOS4 API + fetchDashboardFromAPI(); + } + } + + async function fetchDashboardFromAPI() { + showLoading(); + try { + // Try the proxy API endpoint + const resp = await fetch('/api/dashboard', { signal: AbortSignal.timeout(5000) }); + if (resp.ok) { + const data = await resp.json(); + localStorage.setItem(DASHBOARD_KEY, JSON.stringify({...data, timestamp: Date.now()})); + renderDashboard(data); + showContent(); + return; + } + } catch (e) { + // API not available — try localStorage fallback + } + + // Fallback: check localStorage + const data = fetchDashboardData(); + if (data) { + renderDashboard(data); + showContent(); + } else { + showError('Cannot connect to newOS4. Open the terminal and run: dashboard'); + updateConnectionStatus(false); + showContent(); + } + } + + // Auto-refresh loop + function startAutoRefresh() { + if (refreshTimer) clearInterval(refreshTimer); + refreshTimer = setInterval(() => { + const data = fetchDashboardData(); + if (data) renderDashboard(data); + }, REFRESH_INTERVAL); + } + + // Init + const initialData = fetchDashboardData(); + if (initialData) { + renderDashboard(initialData); + showContent(); + } else { + fetchDashboardFromAPI(); + } + startAutoRefresh(); + + // Expose refresh for the button + window.refreshNow = refreshNow; +})(); +</script> +</body> +</html> |