swarm-ui: fold the agent terminal into AgentsPage's detail panel, not a route
mara's steer on the issue, right as the previous commit's plan was posted: the swarm-level term isn't a separate page, it's part of the same pwa -- selecting an agent should show a small preview (no header, no input) below the existing detail fields. That's the whole MVP for this issue; the full terminal (+ sending input back to the agent) is explicit follow-up scope. AgentTermPage -> AgentTermPreview: drops the Panel wrapper, the "terminal" nav badge + wouter route, and the back-link -- just the inline preview, height-capped at 12em (@hive/shared/terminal.css's own default is a much taller 72vh/60em, sized for a dedicated page rather than a peek).
This commit is contained in:
parent
593923375c
commit
727bfb775b
5 changed files with 42 additions and 64 deletions
|
|
@ -0,0 +1,75 @@
|
|||
// <AgentTermPreview> — the MVP mara's own ruling asked for: not a separate
|
||||
// page, a small read-only preview embedded below `AgentsPage`'s detail
|
||||
// panel fields. No header, no input — those (plus sending input back
|
||||
// to the agent) are explicitly follow-up scope, not this issue's.
|
||||
// Consumes swarm-controller's `GET /api/agents/{name}/term/stream` via
|
||||
// `useSwarmTermStream`, rendering through the same `@hive/shared`
|
||||
// `Row` component the per-hive local terminal uses — "share components
|
||||
// between hive and swarm level term as much as possible" (mara).
|
||||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
import { Badge, type BadgeTone } from "@hive/shared/badge.js";
|
||||
import { Row } from "@hive/shared/term-row.js";
|
||||
import { useSwarmTermStream, type ConnectionState } from "./useSwarmTermStream.js";
|
||||
import "./AgentTermPreview.css";
|
||||
|
||||
const NEAR_BOTTOM_PX = 48;
|
||||
|
||||
const CONNECTION_LABEL: Record<ConnectionState, string> = {
|
||||
connecting: "connecting…",
|
||||
open: "live",
|
||||
reconnecting: "reconnecting…",
|
||||
closed: "disconnected",
|
||||
};
|
||||
|
||||
const CONNECTION_TONE: Record<ConnectionState, BadgeTone> = {
|
||||
connecting: "neutral",
|
||||
open: "positive",
|
||||
reconnecting: "warning",
|
||||
closed: "negative",
|
||||
};
|
||||
|
||||
export function AgentTermPreview({ agentName }: { agentName: string }) {
|
||||
const { rows, connection } = useSwarmTermStream(
|
||||
`/api/agents/${encodeURIComponent(agentName)}/term/stream`,
|
||||
);
|
||||
const logRef = useRef<HTMLDivElement>(null);
|
||||
const [stickToBottom, setStickToBottom] = useState(true);
|
||||
|
||||
function handleScroll() {
|
||||
const el = logRef.current;
|
||||
if (!el) return;
|
||||
setStickToBottom(
|
||||
el.scrollHeight - el.scrollTop - el.clientHeight <= NEAR_BOTTOM_PX,
|
||||
);
|
||||
}
|
||||
|
||||
// Runs after Preact has committed new rows, so `scrollHeight` is
|
||||
// already current — same ordering `@hive/agent`'s `LiveStream` uses.
|
||||
useLayoutEffect(() => {
|
||||
const el = logRef.current;
|
||||
if (el && stickToBottom) el.scrollTop = el.scrollHeight - el.clientHeight;
|
||||
}, [rows, stickToBottom]);
|
||||
|
||||
return (
|
||||
<div class="ui-agent-term-preview">
|
||||
<Badge
|
||||
class="ui-agent-term-preview-status"
|
||||
tone={CONNECTION_TONE[connection]}
|
||||
value={CONNECTION_LABEL[connection]}
|
||||
/>
|
||||
<div class="terminal-wrap ui-agent-term-preview-wrap">
|
||||
<div class="live terminal" ref={logRef} onScroll={handleScroll}>
|
||||
{rows.length === 0 ? (
|
||||
<div class="row note">
|
||||
{connection === "open"
|
||||
? "(connected — waiting for events)"
|
||||
: CONNECTION_LABEL[connection]}
|
||||
</div>
|
||||
) : (
|
||||
rows.map((row) => <Row key={row.key} row={row} />)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue