The small preview in the detail panel keeps only the connection badge and expand, and no longer follows the agent's state stream; the full terminal tab keeps all its badges. The table gets a state row with the turn state, a paused badge, and the context and billed tokens (not the model, which the inference row shows). The preview's top padding drops to one badge row, and the showHeaderBadges prop goes since the full-tab mount is now the only one with badges.
88 lines
3.5 KiB
TypeScript
88 lines
3.5 KiB
TypeScript
// <AgentTerminalPage> — the swarm terminal tab's full page, mounted at
|
|
// `/agents/:name/terminal`. The MVP `AgentTermPreview` (below `AgentsPage`'s
|
|
// detail panel) is height-capped; this is that same
|
|
// component's `fullHeight` mount — no 12em cap, floating header badges
|
|
// shown (there's no detail-panel `<dl>` here to duplicate them).
|
|
//
|
|
// Registers itself in the dynamic-tabs row on mount (not just when opened
|
|
// via the preview's expand trigger) — a bookmark, a shared link, or a
|
|
// plain page reload should all re-open the tab instead of landing on a
|
|
// page with no way back into the tab row. `useDynamicTabs().open()` is
|
|
// idempotent (dedupes by href), so this is safe to call every mount.
|
|
import { useEffect, useRef, useState } from "preact/hooks";
|
|
import type { RouteComponentProps } from "wouter-preact";
|
|
import { Badge } from "@hive/shared/badge.js";
|
|
import { AgentDetailPopover } from "../../agent/detail/AgentDetailPopover.js";
|
|
import { AgentIcon } from "../../agent/AgentIcon.js";
|
|
import { SubagentsMenu } from "../../agent/terminal/SubagentsMenu.js";
|
|
import {
|
|
AgentTermPreview,
|
|
termPaths,
|
|
} from "../../agent/terminal/AgentTermPreview.js";
|
|
import { useAgentFavicon } from "../../agent/terminal/useAgentFavicon.js";
|
|
import { useDynamicTabs } from "../../shell/useDynamicTabs.js";
|
|
import "./AgentTerminalPage.css";
|
|
|
|
// Also mounted at `/agents/:name/subagents/:subagent/terminal`, for one of
|
|
// the agent's subagents.
|
|
export function AgentTerminalPage({
|
|
params,
|
|
}: RouteComponentProps<{ name: string; subagent?: string }>) {
|
|
const { open } = useDynamicTabs();
|
|
const name = decodeURIComponent(params.name);
|
|
const subagent =
|
|
params.subagent === undefined
|
|
? undefined
|
|
: decodeURIComponent(params.subagent);
|
|
const { page, label } = termPaths(name, subagent);
|
|
// A subagent has no icon of its own: its tab wears its agent's.
|
|
useAgentFavicon(name);
|
|
|
|
// Runs once per mounted terminal — re-registering on every render
|
|
// would just be redundant work against an already-deduped list, but
|
|
// there's no reason to pay it more than once per identity.
|
|
useEffect(() => {
|
|
open({ href: page, label });
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- `open` is a
|
|
// fresh function identity every render (`useDynamicTabs` doesn't
|
|
// memoize it); including it would re-fire this effect every render
|
|
// instead of once per terminal `page`.
|
|
}, [page]);
|
|
|
|
// The agent's details open in a popover under its badge, so reading or
|
|
// changing them never means leaving the terminal, or dimming it. A
|
|
// subagent's terminal opens its agent's details: a subagent has none of its
|
|
// own.
|
|
const [detailOpen, setDetailOpen] = useState(false);
|
|
const badgeRef = useRef<HTMLSpanElement>(null);
|
|
|
|
return (
|
|
<div class="ui-agent-terminal-page">
|
|
<AgentTermPreview
|
|
agentName={name}
|
|
subagent={subagent}
|
|
fullHeight
|
|
leading={
|
|
<>
|
|
<span ref={badgeRef}>
|
|
<Badge
|
|
icon={<AgentIcon name={name} small />}
|
|
value={name}
|
|
title={`${detailOpen ? "close" : "open"} ${name}'s details`}
|
|
expanded={detailOpen}
|
|
onClick={() => setDetailOpen((o) => !o)}
|
|
/>
|
|
</span>
|
|
<SubagentsMenu key={name} agentName={name} current={subagent} />
|
|
</>
|
|
}
|
|
/>
|
|
<AgentDetailPopover
|
|
agent={name}
|
|
open={detailOpen}
|
|
onClose={() => setDetailOpen(false)}
|
|
anchorRef={badgeRef}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|