Skip to content

Commit e85554a

Browse files
committed
feat(promptimprover): add swarm dashboard tooling and package manifest
- dashboard/View-SwarmDashboard.ps1: console-based live tail of trace log (C:\PersonalRepo\.planning\traces.jsonl), refreshed every 2s - dashboard/server.js: minimal Node HTTP server serving dashboard/index.html and a /data endpoint that streams the last 20 trace log entries as JSON - dashboard/index.html: browser table view polling /data every 2s to render swarm run traces (time, span, persona, action, tokens, reasoning) - package.json: manifest exposing `dashboard` (PowerShell) and `dashboard-web` (Node server) npm scripts - run-dashboard.ps1: entry point that launches View-SwarmDashboard.ps1
1 parent dd85f51 commit e85554a

5 files changed

Lines changed: 179 additions & 0 deletions

File tree

dashboard/View-SwarmDashboard.ps1

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
$traceFile = "C:\\PersonalRepo\\.planning\\traces.jsonl"
2+
$clearString = [char]27 + "[2J" + [char]27 + "[H"
3+
4+
while ($true) {
5+
Write-Host -NoNewline $clearString
6+
Write-Host "================================================================================" -ForegroundColor Cyan
7+
Write-Host " LIVE SWARM DASHBOARD (2026 OTel Architecture) " -ForegroundColor White -BackgroundColor DarkBlue
8+
Write-Host "================================================================================`n" -ForegroundColor Cyan
9+
10+
if (Test-Path $traceFile) {
11+
# Read the last 15 lines so the dashboard doesn't overflow
12+
$rawLines = Get-Content -Path $traceFile -Tail 15 -ErrorAction SilentlyContinue
13+
14+
$logs = @()
15+
foreach ($line in $rawLines) {
16+
try {
17+
if (-not [string]::IsNullOrWhiteSpace($line)) {
18+
$logs += $line | ConvertFrom-Json -ErrorAction Stop
19+
}
20+
} catch {
21+
# Skip invalid JSON lines
22+
}
23+
}
24+
25+
if ($logs.Count -gt 0) {
26+
$formattedLogs = foreach ($log in $logs) {
27+
$reason = $log.reasoning_path
28+
if ($null -ne $reason -and $reason.Length -gt 60) {
29+
$reason = $reason.Substring(0,57) + "..."
30+
}
31+
32+
[PSCustomObject]@{
33+
Time = [datetime]::Parse($log.timestamp).ToString("HH:mm:ss")
34+
Span = if ($log.span_id) { $log.span_id } else { "N/A" }
35+
Persona = if ($log.persona) { $log.persona } else { "Unknown" }
36+
Action = if ($log.action_type) { $log.action_type } else { "Unknown" }
37+
Tokens = if ($log.metrics.total_tokens) { $log.metrics.total_tokens } else { 0 }
38+
Reasoning = $reason
39+
}
40+
}
41+
$formattedLogs | Format-Table -AutoSize
42+
} else {
43+
Write-Host "No valid traces found yet." -ForegroundColor Yellow
44+
}
45+
} else {
46+
Write-Host "Waiting for traces... ($traceFile not found)" -ForegroundColor Yellow
47+
}
48+
49+
Write-Host "`nPress Ctrl+C to exit. Refreshing every 2 seconds..." -ForegroundColor DarkGray
50+
Start-Sleep -Seconds 2
51+
}

dashboard/index.html

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Promptimprover Swarm Dashboard</title>
7+
<link rel="stylesheet" href="style.css" />
8+
</head>
9+
<body>
10+
<header>
11+
<h1>Promptimprover Swarm Dashboard</h1>
12+
</header>
13+
<main>
14+
<table id="dashboard">
15+
<thead>
16+
<tr>
17+
<th>Time</th>
18+
<th>Span</th>
19+
<th>Persona</th>
20+
<th>Action</th>
21+
<th>Tokens</th>
22+
<th>Reasoning</th>
23+
</tr>
24+
</thead>
25+
<tbody></tbody>
26+
</table>
27+
</main>
28+
<script>
29+
async function fetchData() {
30+
const resp = await fetch('/data');
31+
const logs = await resp.json();
32+
const tbody = document.querySelector('#dashboard tbody');
33+
tbody.innerHTML = '';
34+
logs.reverse().forEach(log => {
35+
const tr = document.createElement('tr');
36+
const fmtTime = new Date(log.timestamp).toLocaleTimeString();
37+
const reason = log.reasoning_path && log.reasoning_path.length > 60 ? log.reasoning_path.slice(0,57) + '...' : log.reasoning_path;
38+
tr.innerHTML = `
39+
<td>${fmtTime}</td>
40+
<td>${log.span_id || 'N/A'}</td>
41+
<td>${log.persona || 'Unknown'}</td>
42+
<td>${log.action_type || 'Unknown'}</td>
43+
<td>${log.metrics?.total_tokens || 0}</td>
44+
<td>${reason || ''}</td>
45+
`;
46+
tbody.appendChild(tr);
47+
});
48+
}
49+
setInterval(fetchData, 2000);
50+
fetchData();
51+
</script>
52+
</body>
53+
</html>

dashboard/server.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// C:\PersonalRepo\portfolio\Promptimprover\dashboard\server.js
2+
const http = require('http');
3+
const fs = require('fs');
4+
const path = require('path');
5+
const url = require('url');
6+
const { once } = require('events');
7+
8+
const TRACE_FILE = path.resolve('C:/PersonalRepo/.planning/traces.jsonl');
9+
const STATIC_DIR = path.join(__dirname);
10+
const PORT = 3000;
11+
12+
// Helper: read last N lines from trace file
13+
async function readLastLines(filePath, maxLines = 20) {
14+
const stream = fs.createReadStream(filePath, { encoding: 'utf8' });
15+
let data = '';
16+
stream.on('data', chunk => { data += chunk; });
17+
await once(stream, 'end');
18+
const lines = data.trim().split(/\r?\n/);
19+
const recent = lines.slice(-maxLines);
20+
// parse each JSON line safely
21+
return recent.map(l => {
22+
try { return JSON.parse(l); } catch (_) { return null; }
23+
}).filter(Boolean);
24+
}
25+
26+
function serveStatic(res, filePath, contentType) {
27+
fs.readFile(filePath, (err, content) => {
28+
if (err) {
29+
res.writeHead(404);
30+
res.end('Not found');
31+
} else {
32+
res.writeHead(200, { 'Content-Type': contentType });
33+
res.end(content);
34+
}
35+
});
36+
}
37+
38+
const server = http.createServer(async (req, res) => {
39+
const parsed = url.parse(req.url, true);
40+
if (parsed.pathname === '/' || parsed.pathname === '/index.html') {
41+
serveStatic(res, path.join(STATIC_DIR, 'index.html'), 'text/html');
42+
} else if (parsed.pathname === '/style.css') {
43+
serveStatic(res, path.join(STATIC_DIR, 'style.css'), 'text/css');
44+
} else if (parsed.pathname === '/data') {
45+
try {
46+
const logs = await readLastLines(TRACE_FILE, 20);
47+
res.writeHead(200, { 'Content-Type': 'application/json' });
48+
res.end(JSON.stringify(logs));
49+
} catch (e) {
50+
res.writeHead(500);
51+
res.end('Error reading trace file');
52+
}
53+
} else {
54+
res.writeHead(404);
55+
res.end('Not found');
56+
}
57+
});
58+
59+
server.listen(PORT, () => {
60+
console.log(`Promptimprover dashboard listening on http://localhost:${PORT}`);
61+
});

package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "promptimprover",
3+
"version": "0.1.0",
4+
"scripts": {
5+
"dashboard": "powershell -NoProfile -ExecutionPolicy Bypass -File ./run-dashboard.ps1",
6+
"dashboard-web": "node dashboard/server.js"
7+
}
8+
}

run-dashboard.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
$script = Join-Path $PSScriptRoot "dashboard\View-SwarmDashboard.ps1"
2+
if (Test-Path $script) {
3+
powershell -NoProfile -ExecutionPolicy Bypass -File $script
4+
} else {
5+
Write-Host "Dashboard script not found at $script" -ForegroundColor Red
6+
}

0 commit comments

Comments
 (0)