agent: wire Header/StatusChips to real /api/state polling
- useAgentState hook: polls GET /api/state (4s interval for now — see its file comment for why this isn't yet the SSE-triggered + login- only-timer cadence the old page used; that lands with the live stream + term-input commit, which is when clobbering the operator's in-progress input actually becomes a risk). - format.ts: fmtTokens/fmtAge, same output shapes as app.js's. - modelEffort.ts: POST /api/model + /api/effort (same-origin, plain fetch). - dashboardBase.ts + pauseAction.ts: pause/resume POST to the *dashboard's* origin via a real <form> submit, kept unchanged from app.js — a cross-origin fetch needs CORS headers hive-c0re doesn't send, a form submit sidesteps that same as it already did. - Root.tsx: wires it all together, including app.js's "any non-online status forces the turn-state badge to offline" behavior. Screenshot-verified against a mock GET /api/state (real fetch, not hardcoded props) + the real agent.css/theme.css/colors.css. Builds + tsc --noEmit clean.
This commit is contained in:
parent
93b34ebb9b
commit
908f479372
6 changed files with 239 additions and 26 deletions
78
frontend/packages/agent/src/hooks/useAgentState.ts
Normal file
78
frontend/packages/agent/src/hooks/useAgentState.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// useAgentState — polls `GET /api/state` and exposes the latest
|
||||
// snapshot + loading/error status. Mirrors app.js's old `refreshState`
|
||||
// data fetch (not yet its exact re-poll cadence — see the interval
|
||||
// comment below).
|
||||
//
|
||||
// Cadence note: the old page only re-polls on a timer while a login is
|
||||
// in flight, and otherwise waits for an SSE `turn_end` event to trigger
|
||||
// one-shot refreshes — this avoids clobbering the operator's half-typed
|
||||
// message in the term-input field. That field doesn't exist in this
|
||||
// rewrite yet (a later commit on this same PR), so there's nothing to
|
||||
// clobber yet; this hook uses a flat interval for now and switches to
|
||||
// the SSE-triggered model in the commit that adds TermInput + the live
|
||||
// stream, matching the original behavior once it's actually needed.
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import type { AgentState } from '../types.js';
|
||||
|
||||
const POLL_MS = 4000;
|
||||
const RETRY_MS = 5000;
|
||||
|
||||
export interface UseAgentStateResult {
|
||||
state: AgentState | null;
|
||||
error: string | null;
|
||||
/** Force an immediate refresh (e.g. right after a model/effort POST). */
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
export function useAgentState(): UseAgentStateResult {
|
||||
const [state, setState] = useState<AgentState | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const stoppedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
stoppedRef.current = false;
|
||||
|
||||
async function poll() {
|
||||
try {
|
||||
const resp = await fetch('api/state');
|
||||
if (!resp.ok) throw new Error(`http ${resp.status}`);
|
||||
const s = (await resp.json()) as AgentState;
|
||||
if (stoppedRef.current) return;
|
||||
setState(s);
|
||||
setError(null);
|
||||
timerRef.current = setTimeout(poll, POLL_MS);
|
||||
} catch (err) {
|
||||
if (stoppedRef.current) return;
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
timerRef.current = setTimeout(poll, RETRY_MS);
|
||||
}
|
||||
}
|
||||
poll();
|
||||
|
||||
return () => {
|
||||
stoppedRef.current = true;
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
function refresh() {
|
||||
if (timerRef.current) clearTimeout(timerRef.current);
|
||||
// Re-trigger the same poll loop immediately; the effect's closure
|
||||
// owns `poll`, so the simplest safe re-trigger from outside it is a
|
||||
// fresh fetch here rather than reaching back into the effect.
|
||||
stoppedRef.current = false;
|
||||
fetch('api/state')
|
||||
.then((resp) => {
|
||||
if (!resp.ok) throw new Error(`http ${resp.status}`);
|
||||
return resp.json();
|
||||
})
|
||||
.then((s: AgentState) => {
|
||||
setState(s);
|
||||
setError(null);
|
||||
})
|
||||
.catch((err) => setError(err instanceof Error ? err.message : String(err)));
|
||||
}
|
||||
|
||||
return { state, error, refresh };
|
||||
}
|
||||
Loading…
Reference in a new issue