89 lines
3.5 KiB
TypeScript
89 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
|
|
showHeaderBadges
|
|
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>
|
|
);
|
|
}
|