Flag hung seats and memory pressure
A turn that has run 15 minutes while producing under 50 tokens a minute is a stalled API stream: flagged on the page with an Esc button, and notified once. The container's memory use is shown in the header and notified near the limit. Claude-Session: https://claude.ai/code/session_01QhLkBGEip8wQdaZ54z4AqJ
This commit is contained in:
+19
-4
@@ -20,13 +20,16 @@ const ago = (iso) => {
|
||||
};
|
||||
|
||||
const seatTag = (s) => {
|
||||
if (s.stalled) return $('span', { class: 'wt-tag wt-tag--bad' }, `hænger (${s.stalled} min)`);
|
||||
if (s.waiting) return $('span', { class: 'wt-tag wt-tag--warn' }, 'venter på svar');
|
||||
if (s.status && s.status !== 'running') return $('span', { class: 'wt-tag wt-tag--bad' }, s.status);
|
||||
if (s.activity === 'running' || s.activity === 'working') return $('span', { class: 'wt-tag wt-tag--accent' }, 'arbejder');
|
||||
return $('span', { class: 'wt-tag' }, 'ledig');
|
||||
};
|
||||
|
||||
const QSTATE = { 'in-progress': 'wt-tag--accent', blocked: '', parked: '', done: 'wt-tag--good', failed: 'wt-tag--bad' };
|
||||
const gb = (bytes) => (bytes / 2 ** 30).toFixed(1);
|
||||
|
||||
const QSTATE ={ 'in-progress': 'wt-tag--accent', blocked: '', parked: '', done: 'wt-tag--good', failed: 'wt-tag--bad' };
|
||||
|
||||
let snap = null;
|
||||
let timer = null;
|
||||
@@ -67,8 +70,13 @@ function replyBox(placeholder, label, onSend) {
|
||||
// What a seat is asking, with the controls to answer it.
|
||||
function askBlock(s, after) {
|
||||
const a = s.ask;
|
||||
if (!a) return null;
|
||||
const done = (ok) => { if (after) after(); setTimeout(load, 1500); return ok; };
|
||||
if (!a && s.stalled) {
|
||||
return $('div', { class: 'wt-stack' },
|
||||
$('span', { class: 'wt-muted' }, `Samme tur i ${s.stalled} min uden at producere noget. Esc afbryder, bagefter kan du bede den fortsætte.`),
|
||||
$('button', { class: 'wt-btn', onclick: async () => done(await post('api/send', { seat: s.name, key: 'esc' })) }, 'Esc (afbryd turen)'));
|
||||
}
|
||||
if (!a) return null;
|
||||
if (a.kind === 'menu') {
|
||||
return $('div', { class: 'wt-stack' },
|
||||
a.title ? $('span', {}, a.title) : null,
|
||||
@@ -85,7 +93,14 @@ function askBlock(s, after) {
|
||||
|
||||
function overview() {
|
||||
const view = [];
|
||||
const waiting = snap.rigs.flatMap((r) => r.seats.filter((s) => s.waiting));
|
||||
const mem = snap.memory;
|
||||
if (mem && mem.fraction >= 0.9) {
|
||||
view.push($('section', { class: 'wt-card wt-card--raised wt-stack' },
|
||||
$('div', { class: 'wt-row wt-row--between' }, $('h2', { class: 'wt-card-title' }, 'Hukommelse'),
|
||||
$('span', { class: 'wt-tag wt-tag--bad' }, `${Math.round(mem.fraction * 100)} %`)),
|
||||
$('span', {}, `${gb(mem.used)} af ${gb(mem.limit)} GB brugt. Agenterne kan gå i stå; giv containeren mere RAM eller stop et team.`)));
|
||||
}
|
||||
const waiting = snap.rigs.flatMap((r) => r.seats.filter((s) => s.waiting || s.stalled));
|
||||
const gates = snap.queue.filter((q) => q.gate);
|
||||
if (waiting.length || gates.length) {
|
||||
view.push($('section', { class: 'wt-card wt-card--raised wt-stack' },
|
||||
@@ -160,7 +175,7 @@ function seatView(name) {
|
||||
|
||||
function render() {
|
||||
if (!snap) return;
|
||||
document.getElementById('updated').textContent = ago(snap.updated);
|
||||
document.getElementById('updated').textContent = ago(snap.updated) + (snap.memory ? ` · ${gb(snap.memory.used)}/${gb(snap.memory.limit)} GB` : '');
|
||||
const m = location.hash.match(/^#\/seat\/(.+)$/);
|
||||
const view = document.getElementById('view');
|
||||
// keep the open seat view, and never wipe an answer being typed
|
||||
|
||||
Reference in New Issue
Block a user