swarm-ui: expand trigger on the right, connection badge as tall as it
The floating row over the terminal now spans its top edge: the state badges (connection first, then the turn-state ones, which move over from the top-right corner) on the left, the expand trigger on the right. The connection badge takes the trigger's height. The row passes pointer events through its empty middle.
This commit is contained in:
parent
9064d8fbd2
commit
51da879db6
2 changed files with 94 additions and 70 deletions
|
|
@ -76,35 +76,40 @@
|
||||||
padding-block-start: 3.4em;
|
padding-block-start: 3.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Floating turn_state/agent_state/model/ctx/cost badge cluster —
|
/* The floating row over the terminal's top edge: state badges (connection
|
||||||
positioned by `.terminal-wrap`'s own `position: relative`, same
|
first, then turn_state/agent_state/model/ctx/cost) on the left, the
|
||||||
anchor `@hive/shared/terminal.css`'s `.tail-pill` already uses, rather
|
expand trigger on the right. Positioned by `.terminal-wrap`'s own
|
||||||
than a second positioned ancestor of our own. Top-right corner: the
|
`position: relative`, same anchor `@hive/shared/terminal.css`'s
|
||||||
scroll-to-bottom `.tail-pill` (that same stylesheet) anchors
|
`.tail-pill` uses, which anchors bottom-right and so never competes for
|
||||||
bottom-right, so the two overlays never compete for the same corner.
|
this edge. The row itself takes no pointer events, so the terminal
|
||||||
`flex-wrap` rather than a fixed width — a badge cluster this narrow
|
under its empty middle still scrolls and selects; only the badges and
|
||||||
can still overflow the box at small viewport widths, and wrapping
|
the trigger do. `flex-wrap` on the badges rather than a fixed width —
|
||||||
beats clipping or shrinking the terminal underneath. */
|
at small viewport widths wrapping beats clipping. */
|
||||||
.ui-agent-term-preview-header-badges {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.4em;
|
|
||||||
right: 0.4em;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 0.25em;
|
|
||||||
max-width: calc(100% - 0.8em);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Connection badge and expand trigger — top-left floating twin of
|
|
||||||
`.ui-agent-term-preview-header-badges` (top-right), same anchor. */
|
|
||||||
.ui-agent-term-preview-controls {
|
.ui-agent-term-preview-controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.4em;
|
top: 0.4em;
|
||||||
left: 0.4em;
|
left: 0.4em;
|
||||||
|
right: 0.4em;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5em;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.ui-agent-term-preview-controls > * {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.ui-agent-term-preview-header-badges {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Same height as the expand trigger beside it, which keeps an interactive
|
||||||
|
badge's 2.75em touch-target floor. */
|
||||||
|
.ui-agent-term-preview-live {
|
||||||
|
min-height: 2.75em;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,60 @@ export function termPaths(agentName: string, subagent?: string): TermPaths {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Turn state, agent state, model, ctx and cost, as the badges beside the
|
||||||
|
// connection one. Siblings only, no wrapper: they sit in the caller's row.
|
||||||
|
function StateBadges({
|
||||||
|
header,
|
||||||
|
ctx,
|
||||||
|
cost,
|
||||||
|
}: {
|
||||||
|
header: NonNullable<ReturnType<typeof useSwarmAgentStateStream>["header"]>;
|
||||||
|
ctx: number | null;
|
||||||
|
cost: number | null;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Badge
|
||||||
|
tone={TURN_STATE_TONE[header.turn_state] ?? "neutral"}
|
||||||
|
value={header.turn_state}
|
||||||
|
/>
|
||||||
|
{/* Only the non-boring case gets a badge — a healthy running
|
||||||
|
agent (`agent_state: "up"`) doesn't need one for the
|
||||||
|
obvious default. In practice this only ever fires for
|
||||||
|
"paused": `hive-agent` can only honestly publish "up" or
|
||||||
|
"paused" on this subject (see `AgentStateMsg::agent_state`'s
|
||||||
|
own doc), never "offline"/"destroyed" — a stopped or
|
||||||
|
destroyed agent publishes nothing at all. */}
|
||||||
|
{header.agent_state !== "up" && (
|
||||||
|
<Badge tone="warning" value={header.agent_state} />
|
||||||
|
)}
|
||||||
|
<Badge
|
||||||
|
label="model"
|
||||||
|
value={header.model}
|
||||||
|
title={
|
||||||
|
header.resolved_model
|
||||||
|
? `resolved to ${header.resolved_model}`
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{ctx !== null && (
|
||||||
|
<Badge
|
||||||
|
label="ctx"
|
||||||
|
value={fmtTokens(ctx)}
|
||||||
|
title="tokens used in the current context window"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{cost !== null && (
|
||||||
|
<Badge
|
||||||
|
label="cost"
|
||||||
|
value={fmtTokens(cost)}
|
||||||
|
title="cumulative tokens billed across the last turn"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function AgentTermPreview({
|
export function AgentTermPreview({
|
||||||
agentName,
|
agentName,
|
||||||
subagent,
|
subagent,
|
||||||
|
|
@ -176,15 +230,21 @@ export function AgentTermPreview({
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class="terminal-wrap ui-agent-term-preview-wrap">
|
<div class="terminal-wrap ui-agent-term-preview-wrap">
|
||||||
{/* The connection badge and the expand trigger float in the
|
{/* One floating row over the terminal's top edge: the state
|
||||||
terminal's top-left corner (the other badges own the
|
badges (connection first) on the left, the expand trigger on
|
||||||
top-right). The full-tab mount is already the destination, so
|
the right. The full-tab mount is already the destination, so
|
||||||
it has no expand trigger. */}
|
it has no expand trigger. */}
|
||||||
<div class="ui-agent-term-preview-controls">
|
<div class="ui-agent-term-preview-controls">
|
||||||
<Badge
|
<div class="ui-agent-term-preview-header-badges">
|
||||||
tone={CONNECTION_TONE[connection]}
|
<Badge
|
||||||
value={CONNECTION_LABEL[connection]}
|
class="ui-agent-term-preview-live"
|
||||||
/>
|
tone={CONNECTION_TONE[connection]}
|
||||||
|
value={CONNECTION_LABEL[connection]}
|
||||||
|
/>
|
||||||
|
{header && showHeaderBadges && (
|
||||||
|
<StateBadges header={header} ctx={ctx} cost={cost} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
{!fullHeight && (
|
{!fullHeight && (
|
||||||
<Badge
|
<Badge
|
||||||
icon={<ExpandIcon />}
|
icon={<ExpandIcon />}
|
||||||
|
|
@ -194,47 +254,6 @@ export function AgentTermPreview({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{header && showHeaderBadges && (
|
|
||||||
<div class="ui-agent-term-preview-header-badges">
|
|
||||||
<Badge
|
|
||||||
tone={TURN_STATE_TONE[header.turn_state] ?? "neutral"}
|
|
||||||
value={header.turn_state}
|
|
||||||
/>
|
|
||||||
{/* Only the non-boring case gets a badge — a healthy running
|
|
||||||
agent (`agent_state: "up"`) doesn't need one for the
|
|
||||||
obvious default. In practice this only ever fires for
|
|
||||||
"paused": `hive-agent` can only honestly publish "up" or
|
|
||||||
"paused" on this subject (see `AgentStateMsg::agent_state`'s
|
|
||||||
own doc), never "offline"/"destroyed" — a stopped or
|
|
||||||
destroyed agent publishes nothing at all. */}
|
|
||||||
{header.agent_state !== "up" && (
|
|
||||||
<Badge tone="warning" value={header.agent_state} />
|
|
||||||
)}
|
|
||||||
<Badge
|
|
||||||
label="model"
|
|
||||||
value={header.model}
|
|
||||||
title={
|
|
||||||
header.resolved_model
|
|
||||||
? `resolved to ${header.resolved_model}`
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{ctx !== null && (
|
|
||||||
<Badge
|
|
||||||
label="ctx"
|
|
||||||
value={fmtTokens(ctx)}
|
|
||||||
title="tokens used in the current context window"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{cost !== null && (
|
|
||||||
<Badge
|
|
||||||
label="cost"
|
|
||||||
value={fmtTokens(cost)}
|
|
||||||
title="cumulative tokens billed across the last turn"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div class="live terminal" ref={logRef} onScroll={handleScroll}>
|
<div class="live terminal" ref={logRef} onScroll={handleScroll}>
|
||||||
{rows.length === 0 ? (
|
{rows.length === 0 ? (
|
||||||
<div class="row note">
|
<div class="row note">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue