Watch
0
0
Fork
You've already forked hyperhive
0

swarm-ui: split pages/agents into two pages and the agent components

pages/agents held the roster page, the terminal page and every component under them. It keeps the two pages; the components move to src/agent, by concern: roster (card, wanted menu, actions), detail (panel body, dialog, inference, linked accounts, link menu) and terminal (preview, input, subagents, stream hooks, favicon), with the shared row type and icon at the top. Moves and import rewrites only.
This commit is contained in:
iris 2026-10-12 00:02:15 +02:00
commit 74e588415b
27 changed files with 28 additions and 25 deletions

View file

@ -1,88 +0,0 @@
// Live SSE consumer for the swarm-relayed agent turn-state header
// (`GET /api/agents/{name}/state/stream`, swarm-controller's
// `agent_state_stream.rs`). Same transport shape as `useSwarmTermStream`
// — one core-NATS subject relayed as bare SSE `data`, no envelope, no
// replay — but the payload here is a full header that *replaces* the
// last one rather than an appendable row, so there is nothing to
// coalesce: each event is just the latest value.
//
// **No seed on attach.** `agent_state_stream.rs`'s own module doc names
// this limitation: the subject is published on transition and re-sent
// once a minute, so an agent that has sat idle since before this hook
// mounted announces nothing for up to a minute. `header` stays `null`
// until the first event lands — callers should render no badges in that
// gap rather than a stale or guessed one (there is no swarm-level
// endpoint today that can seed turn_state/model/ctx/cost synchronously —
// `GET /api/agents/status`'s snapshot carries only
// `status_text`/`running`).
import { useEffect, useState } from "preact/hooks";
export type ConnectionState = "connecting" | "open" | "reconnecting" | "closed";
export interface TokenUsage {
input_tokens?: number;
output_tokens?: number;
cache_read_input_tokens?: number;
cache_creation_input_tokens?: number;
}
// `swarm_queue_client::agent_inference::Source`.
export type InferenceSource = "swarm" | "agent_nix" | "local_file" | "default";
// Mirrors `hive_agent::swarm_agent_state::AgentStateMsg` field-for-field
// — see that struct's own doc comment for why `turn_state_since` (an
// ISO 8601 string here, unlike the per-agent page's unix-seconds
// `/api/state`) and `agent_state` (the swarm `AgentState` enum, not a
// bare `paused: bool`) diverge from the per-agent page's own shape.
export interface AgentStateHeader {
turn_state: "idle" | "thinking" | "compacting";
turn_state_since: string;
agent_state: "up" | "offline" | "paused" | "destroyed";
model: string;
effort: string;
model_source: InferenceSource;
effort_source: InferenceSource;
resolved_model: string | null;
context_window_tokens: number;
ctx_usage: TokenUsage | null;
cost_usage: TokenUsage | null;
}
export interface UseSwarmAgentStateStreamResult {
header: AgentStateHeader | null;
connection: ConnectionState;
}
export function useSwarmAgentStateStream(
streamUrl: string | null,
): UseSwarmAgentStateStreamResult {
const [header, setHeader] = useState<AgentStateHeader | null>(null);
const [connection, setConnection] = useState<ConnectionState>("connecting");
useEffect(() => {
setHeader(null);
setConnection("connecting");
// `null`: a terminal with no header to follow (a subagent's).
if (streamUrl === null) return;
const es = new EventSource(streamUrl);
es.onopen = () => setConnection("open");
es.onmessage = (e) => {
try {
setHeader(JSON.parse(e.data) as AgentStateHeader);
} catch {
// Malformed payload: drop it silently. Unlike the terminal
// preview there is no row list to append a "[parse err]" line
// to here, and one bad header isn't worth degrading the whole
// badge cluster over — the next valid header replaces it.
}
};
es.onerror = () => {
setConnection(
es.readyState === 0 /* CONNECTING */ ? "reconnecting" : "closed",
);
};
return () => es.close();
}, [streamUrl]);
return { header, connection };
}