Skip to content

Commit 1d54796

Browse files
committed
feat(views): add current/previous/best run display and status indicators
Enhance task view to show current run tokens/cost, previous run summary, and best run of the batch (ante, round, tokens, cost, finish reason). Enhance responses view with animated thinking/executing status indicators, consecutive failure warnings, and finish reason display.
1 parent 5b0d95c commit 1d54796

2 files changed

Lines changed: 244 additions & 17 deletions

File tree

views/responses.html

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,26 @@
2525
.hide-scrollbar::-webkit-scrollbar {
2626
display: none;
2727
}
28+
2829
</style>
2930
</head>
3031
<body class="bg-black text-white w-[533px] h-[720px] flex flex-col items-center overflow-hidden">
3132
<div class="fade-overlay"></div>
32-
<div id="responses" class="hide-scrollbar flex-1 flex flex-col justify-end p-4 pb-16 space-y-3 overflow-y-auto w-full max-w-[480px]"></div>
33+
<div id="responses" class="hide-scrollbar flex-1 flex flex-col justify-end p-4 space-y-3 overflow-y-auto w-full max-w-[480px]"></div>
3334

3435
<script>
3536
let lastResponseId = null;
37+
let dotsState = 0;
38+
const dotsCycle = ['', '.', '..', '...'];
39+
40+
// Animate ellipsis dots
41+
setInterval(() => {
42+
dotsState = (dotsState + 1) % dotsCycle.length;
43+
const dotsEl = document.getElementById('dots');
44+
if (dotsEl) {
45+
dotsEl.textContent = dotsCycle[dotsState];
46+
}
47+
}, 500); // 500ms per dot = 2s full cycle
3648

3749
function formatTime(timestampMs) {
3850
const d = new Date(parseInt(timestampMs));
@@ -64,12 +76,63 @@
6476
`;
6577
}
6678

79+
async function countLines(url) {
80+
try {
81+
const res = await fetch(url);
82+
if (!res.ok) return 0;
83+
const text = await res.text();
84+
return text.trim().split('\n').filter(l => l).length;
85+
} catch (e) {
86+
return 0;
87+
}
88+
}
89+
90+
function formatFinishReason(reason) {
91+
const mapping = {
92+
'won': { text: 'Won!', colorClass: 'text-green-400' },
93+
'lost': { text: 'Lost', colorClass: 'text-red-400' },
94+
'llm_abort': { text: 'LLM Abort', colorClass: 'text-red-400' },
95+
'consecutive_error_calls': { text: 'Error Calls', colorClass: 'text-red-400' },
96+
'consecutive_failed_calls': { text: 'Failed Calls', colorClass: 'text-red-400' },
97+
'connection_abort': { text: 'Connection Lost', colorClass: 'text-yellow-400' },
98+
'unexpected_error': { text: 'Error', colorClass: 'text-red-400' },
99+
};
100+
return mapping[reason] || { text: reason, colorClass: 'text-gray-400' };
101+
}
102+
67103
async function fetchResponses() {
68104
try {
69105
const latestRes = await fetch('../../runs/latest.json?' + Date.now());
70106
if (!latestRes.ok) return;
71107
const latest = await latestRes.json();
72108

109+
// Determine status state
110+
let statusState = null;
111+
let failures = 0;
112+
let maxFailures = 3;
113+
let finishReason = null;
114+
115+
if (latest.requests && latest.responses && latest.gamestates) {
116+
const requestCount = await countLines('../../runs/' + latest.requests);
117+
const responseCount = await countLines('../../runs/' + latest.responses);
118+
const gamestateCount = await countLines('../../runs/' + latest.gamestates);
119+
120+
// Check if run has finished
121+
finishReason = latest.finish_reason || null;
122+
123+
if (finishReason === null) {
124+
// Run is still in progress
125+
if (requestCount > responseCount) {
126+
statusState = 'thinking';
127+
} else if (responseCount > gamestateCount) {
128+
statusState = 'executing';
129+
}
130+
}
131+
132+
failures = latest.consecutive_failures || 0;
133+
maxFailures = latest.max_failures || 3;
134+
}
135+
73136
const responsesRes = await fetch('../../runs/' + latest.responses + '?' + Date.now());
74137
if (!responsesRes.ok) return;
75138
const text = await responsesRes.text();
@@ -80,11 +143,42 @@
80143
const entries = lastLines.map(l => JSON.parse(l));
81144

82145
const newLastId = entries[entries.length - 1]?.id;
83-
if (newLastId === lastResponseId) return;
146+
if (newLastId === lastResponseId && statusState === null) return;
84147
lastResponseId = newLastId;
85148

149+
// Build entries HTML
150+
let html = entries.map(renderEntry).join('');
151+
152+
// Add status indicator
153+
if (finishReason) {
154+
// Run has finished - show finish reason
155+
const { text, colorClass } = formatFinishReason(finishReason);
156+
html += `
157+
<div id="status" class="font-mono text-base">
158+
<span class="${colorClass}">${text}</span>
159+
</div>
160+
`;
161+
} else if (statusState) {
162+
// Run in progress - show throbber
163+
const text = statusState === 'thinking' ? 'Thinking' : 'Executing';
164+
const failuresHtml = failures > 0
165+
? `<span class="float-right text-red-400">&#9888; <span>${failures}/${maxFailures}</span></span>`
166+
: '';
167+
html += `
168+
<div id="status" class="font-mono text-base">
169+
<span class="text-yellow-400">${text}<span id="dots"></span></span>
170+
${failuresHtml}
171+
</div>
172+
`;
173+
}
174+
86175
const container = document.getElementById('responses');
87-
container.innerHTML = entries.map(renderEntry).join('');
176+
container.innerHTML = html;
177+
// Sync dots to current animation state
178+
const dotsEl = document.getElementById('dots');
179+
if (dotsEl) {
180+
dotsEl.textContent = dotsCycle[dotsState];
181+
}
88182
// Scroll to the bottom (newest entry)
89183
container.scrollTop = container.scrollHeight;
90184
} catch (e) {

views/task.html

Lines changed: 147 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,100 @@
77
<script src="https://cdn.tailwindcss.com"></script>
88
</head>
99
<body class="bg-black text-white w-[747px] h-[267px] flex items-center">
10-
<div id="overlay" class="w-full h-full flex flex-col justify-center pl-16">
11-
<div id="model" class="font-mono text-3xl mb-6">
12-
<span class="text-gray-500">Loading...</span>
10+
<div id="overlay" class="w-full h-full flex flex-col justify-center pl-12 pr-8">
11+
<!-- Current Section -->
12+
<div class="font-mono text-xs text-gray-500 text-right mb-1 uppercase">Current</div>
13+
<div id="current-line1" class="font-mono text-xs text-gray-400 mb-1">
14+
<span>Model: <span id="current-model" class="text-white">Loading...</span></span>
15+
<span class="mx-2">|</span>
16+
<span>Tok: <span id="current-tokens" class="text-white">0</span></span>
17+
<span class="mx-2">|</span>
18+
<span>Cost: <span id="current-cost" class="text-white">$0.00</span></span>
1319
</div>
14-
<div class="space-y-3">
15-
<div class="font-mono text-2xl"><span class="text-gray-500">seed&nbsp; </span><span id="seed" class="text-white"></span></div>
16-
<div class="font-mono text-2xl"><span class="text-gray-500">deck&nbsp; </span><span id="deck" class="text-white"></span></div>
17-
<div class="font-mono text-2xl"><span class="text-gray-500">stake </span><span id="stake" class="text-white"></span></div>
20+
<div class="font-mono text-xs text-gray-400 mb-3">
21+
<span>Seed: <span id="current-seed" class="text-white">--</span></span>
22+
<span class="mx-2">|</span>
23+
<span>Deck: <span id="current-deck" class="text-white">--</span></span>
24+
<span class="mx-2">|</span>
25+
<span>Stake: <span id="current-stake" class="text-white">--</span></span>
26+
</div>
27+
28+
<!-- Divider -->
29+
<div class="border-b border-gray-700 mb-3"></div>
30+
31+
<!-- Previous Section -->
32+
<div id="previous-section" class="hidden mb-3">
33+
<div class="font-mono text-xs text-gray-500 text-right mb-1 uppercase">Previous</div>
34+
<div class="font-mono text-xs text-gray-400 mb-1">
35+
<span>Model: <span id="previous-model" class="text-white">--</span></span>
36+
<span class="mx-2">|</span>
37+
<span>Tok: <span id="previous-tokens" class="text-white">0</span></span>
38+
<span class="mx-2">|</span>
39+
<span>Cost: <span id="previous-cost" class="text-white">$0.00</span></span>
40+
</div>
41+
<div class="font-mono text-xs text-gray-400 mb-1">
42+
<span>Seed: <span id="previous-seed" class="text-white">--</span></span>
43+
<span class="mx-2">|</span>
44+
<span>Deck: <span id="previous-deck" class="text-white">--</span></span>
45+
<span class="mx-2">|</span>
46+
<span>Stake: <span id="previous-stake" class="text-white">--</span></span>
47+
<span class="mx-2">|</span>
48+
<span>Ante: <span id="previous-ante" class="text-white">--</span></span>
49+
<span class="mx-2">|</span>
50+
<span>Round: <span id="previous-round" class="text-white">--</span></span>
51+
</div>
52+
<div class="font-mono text-xs text-gray-400">
53+
<span>Finish Reason: <span id="previous-end" class="text-white">--</span></span>
54+
</div>
55+
</div>
56+
57+
<!-- Divider (only shown if previous exists) -->
58+
<div id="previous-divider" class="hidden border-b border-gray-700 mb-3"></div>
59+
60+
<!-- Best of the Batch Section -->
61+
<div id="batch-section" class="hidden">
62+
<div class="font-mono text-xs text-gray-500 text-right mb-1 uppercase">Best of the Batch</div>
63+
<div class="font-mono text-xs text-gray-400 mb-1">
64+
<span>Model: <span id="best-model" class="text-white">--</span></span>
65+
<span class="mx-2">|</span>
66+
<span>Tok: <span id="best-tokens" class="text-white">0</span></span>
67+
<span class="mx-2">|</span>
68+
<span>Cost: <span id="best-cost" class="text-white">$0.00</span></span>
69+
</div>
70+
<div class="font-mono text-xs text-gray-400 mb-1">
71+
<span>Seed: <span id="best-seed" class="text-white">--</span></span>
72+
<span class="mx-2">|</span>
73+
<span>Deck: <span id="best-deck" class="text-white">--</span></span>
74+
<span class="mx-2">|</span>
75+
<span>Stake: <span id="best-stake" class="text-white">--</span></span>
76+
<span class="mx-2">|</span>
77+
<span>Ante: <span id="best-ante" class="text-white">--</span></span>
78+
<span class="mx-2">|</span>
79+
<span>Round: <span id="best-round" class="text-white">--</span></span>
80+
</div>
81+
<div class="font-mono text-xs text-gray-400">
82+
<span>Finish Reason: <span id="best-end" class="text-white">--</span></span>
83+
</div>
1884
</div>
1985
</div>
2086

2187
<script>
2288
let lastTask = null;
2389

90+
function formatTokens(n) {
91+
if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
92+
if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
93+
return n.toString();
94+
}
95+
96+
function formatCost(n) {
97+
return '$' + n.toFixed(2);
98+
}
99+
100+
function formatModel(vendor, model) {
101+
return `<span class="text-white">${vendor}</span><span class="text-gray-500"> / </span><span class="text-white">${model}</span>`;
102+
}
103+
24104
async function fetchTask() {
25105
try {
26106
const latestRes = await fetch('../../runs/latest.json?' + Date.now());
@@ -33,15 +113,68 @@
33113

34114
const taskKey = task.model.vendor + task.model.name + task.seed + task.deck + task.stake;
35115
if (taskKey !== lastTask) {
36-
document.getElementById('model').innerHTML =
37-
`<span class="text-white">${task.model.vendor}</span>` +
38-
`<span class="text-gray-500"> / </span>` +
39-
`<span class="text-white">${task.model.name}</span>`;
40-
document.getElementById('seed').textContent = task.seed;
41-
document.getElementById('deck').textContent = task.deck;
42-
document.getElementById('stake').textContent = task.stake;
116+
document.getElementById('current-model').innerHTML = formatModel(task.model.vendor, task.model.name);
117+
document.getElementById('current-seed').textContent = task.seed;
118+
document.getElementById('current-deck').textContent = task.deck;
119+
document.getElementById('current-stake').textContent = task.stake;
43120
lastTask = taskKey;
44121
}
122+
123+
// Fetch responses for tokens/cost
124+
if (latest.responses) {
125+
const responsesRes = await fetch('../../runs/' + latest.responses + '?' + Date.now());
126+
if (responsesRes.ok) {
127+
const text = await responsesRes.text();
128+
const lines = text.trim().split('\n').filter(l => l);
129+
let totalIn = 0, totalOut = 0, totalCost = 0;
130+
for (const line of lines) {
131+
try {
132+
const entry = JSON.parse(line);
133+
const usage = entry.response?.body?.usage || {};
134+
totalIn += usage.prompt_tokens || 0;
135+
totalOut += usage.completion_tokens || 0;
136+
totalCost += usage.cost || 0;
137+
} catch (e) {}
138+
}
139+
document.getElementById('current-tokens').textContent = formatTokens(totalIn + totalOut);
140+
document.getElementById('current-cost').textContent = formatCost(totalCost);
141+
}
142+
}
143+
144+
// Fetch previous.json for the previous run
145+
const previousRes = await fetch('../../runs/previous.json?' + Date.now());
146+
if (previousRes.ok) {
147+
const previous = await previousRes.json();
148+
document.getElementById('previous-section').classList.remove('hidden');
149+
document.getElementById('previous-divider').classList.remove('hidden');
150+
document.getElementById('previous-model').innerHTML = formatModel(previous.vendor, previous.model);
151+
document.getElementById('previous-seed').textContent = previous.seed;
152+
document.getElementById('previous-deck').textContent = previous.deck;
153+
document.getElementById('previous-stake').textContent = previous.stake;
154+
document.getElementById('previous-ante').textContent = previous.ante;
155+
document.getElementById('previous-round').textContent = previous.round;
156+
document.getElementById('previous-tokens').textContent = formatTokens(previous.tokens);
157+
document.getElementById('previous-cost').textContent = formatCost(previous.cost);
158+
document.getElementById('previous-end').textContent = previous.finish_reason;
159+
}
160+
161+
// Fetch batch.json for best run
162+
const batchRes = await fetch('../../runs/batch.json?' + Date.now());
163+
if (batchRes.ok) {
164+
const batch = await batchRes.json();
165+
if (batch.runs_completed > 0 && batch.best_ante > 0) {
166+
document.getElementById('batch-section').classList.remove('hidden');
167+
document.getElementById('best-model').innerHTML = formatModel(batch.best_vendor || '', batch.best_model || '');
168+
document.getElementById('best-seed').textContent = batch.best_seed || '--';
169+
document.getElementById('best-deck').textContent = batch.best_deck || '--';
170+
document.getElementById('best-stake').textContent = batch.best_stake || '--';
171+
document.getElementById('best-ante').textContent = batch.best_ante || '--';
172+
document.getElementById('best-round').textContent = batch.best_round || '--';
173+
document.getElementById('best-tokens').textContent = formatTokens(batch.best_tokens || 0);
174+
document.getElementById('best-cost').textContent = formatCost(batch.best_cost || 0);
175+
document.getElementById('best-end').textContent = batch.best_finish_reason || '--';
176+
}
177+
}
45178
} catch (e) {
46179
// Ignore fetch errors
47180
}

0 commit comments

Comments
 (0)