agent page: dashboard back-link + last-turn timing chip
title bar grows a '↑ DASHB04RD' link next to the rebuild button — opens the host dashboard in a new tab so the operator can pivot between agents without losing the live tail. uses the dashboardPort already plumbed via /api/state. state row picks up a 'last turn 12.3s' chip that fills in when state transitions away from thinking. format: ms / s.s / m s. hidden until the first turn completes.
This commit is contained in:
parent
ee5b85716d
commit
bd7d2d4860
4 changed files with 55 additions and 4 deletions
|
|
@ -65,16 +65,25 @@
|
|||
`░▒▓█▓▒░ ${label} ░▒▓█▓▒░ hyperhive ag3nt ░▒▓█▓▒░`;
|
||||
const title = $('title');
|
||||
title.textContent = `◆ ${label} ◆ `;
|
||||
// ↑ DASHB04RD — back-link to the host dashboard. Opens in a new
|
||||
// tab to keep the agent page anchored where the operator is.
|
||||
const dashUrl = `${location.protocol}//${location.hostname}:${dashboardPort}/`;
|
||||
title.append(
|
||||
el('a', {
|
||||
href: dashUrl, target: '_blank', rel: 'noopener',
|
||||
class: 'btn-dashlink', title: 'host dashboard',
|
||||
}, '↑ DASHB04RD'),
|
||||
' ',
|
||||
);
|
||||
const btn = el('a', {
|
||||
href: '#', class: 'btn-rebuild', id: 'rebuild-btn',
|
||||
}, '↻ R3BU1LD');
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
if (!confirm(`rebuild ${label}? container will hot-reload.`)) return;
|
||||
const url = `${location.protocol}//${location.hostname}:${dashboardPort}/rebuild/${label}`;
|
||||
const f = document.createElement('form');
|
||||
f.method = 'POST';
|
||||
f.action = url;
|
||||
f.action = `${dashUrl}rebuild/${label}`;
|
||||
document.body.appendChild(f);
|
||||
f.submit();
|
||||
});
|
||||
|
|
@ -310,6 +319,13 @@
|
|||
}
|
||||
function setState(next) {
|
||||
if (next === stateName) return;
|
||||
// Capture the just-ending state's duration when leaving 'thinking'
|
||||
// so the operator can eyeball turn length without scrolling the
|
||||
// terminal back.
|
||||
if (stateName === 'thinking' && next !== 'thinking') {
|
||||
const elapsedMs = Date.now() - stateSince;
|
||||
renderLastTurn(elapsedMs);
|
||||
}
|
||||
stateName = next;
|
||||
stateSince = Date.now();
|
||||
const badge = $('state-badge');
|
||||
|
|
@ -321,6 +337,16 @@
|
|||
}
|
||||
renderStateBadge();
|
||||
}
|
||||
function renderLastTurn(ms) {
|
||||
const el_ = $('last-turn');
|
||||
if (!el_) return;
|
||||
let s = '';
|
||||
if (ms < 1000) s = ms + 'ms';
|
||||
else if (ms < 60_000) s = (ms / 1000).toFixed(1) + 's';
|
||||
else s = Math.floor(ms / 60_000) + 'm ' + Math.floor((ms / 1000) % 60) + 's';
|
||||
el_.textContent = '· last turn ' + s;
|
||||
el_.hidden = false;
|
||||
}
|
||||
function startStateTicker() {
|
||||
if (stateTickTimer) return;
|
||||
stateTickTimer = setInterval(renderStateBadge, 1000);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue