- StatusChips.tsx: drop the stale 'useAgentState lands in a later commit' comment now that Root.tsx already wires it. - nix/checks.nix: add agent-typecheck, same shape as swarm-ui-typecheck, so a real TS error in the agent package fails CI instead of just esbuild-stripping silently.
146 lines
4 KiB
TypeScript
146 lines
4 KiB
TypeScript
// <StatusChips> — the agent page's status row: alive/turn-state, the
|
|
// model + effort pickers, context/cost usage, and pause/resume. This is
|
|
// the concrete fix for the design guide's own named anti-example (the
|
|
// old page's model/effort *pickers* lived in the `⋯` overflow menu while
|
|
// the current model/effort only showed as a disconnected chip) — every
|
|
// badge here IS the control for what it displays, composed from
|
|
// `@hive/shared`'s `Badge`/`Dropdown` (../../shared/src/badge,
|
|
// ../../shared/src/dropdown). Presentational only: formatting +
|
|
// selection state live in the caller (`Root.tsx`, via the
|
|
// `useAgentState` hook), so this component can still be demoed and
|
|
// reviewed against plain sample data independent of live `/api/state`.
|
|
import { useState } from 'preact/hooks';
|
|
import { Badge, type BadgeTone } from '@hive/shared/badge.js';
|
|
import { Dropdown, type DropdownOption } from '@hive/shared/dropdown.js';
|
|
import './StatusChips.css';
|
|
|
|
export interface StatusChipsProps {
|
|
aliveLabel: string;
|
|
aliveTone: BadgeTone;
|
|
stateLabel: string;
|
|
stateTone: BadgeTone;
|
|
stateTooltip?: string;
|
|
model: string;
|
|
availableModels: string[];
|
|
onSelectModel: (name: string) => void;
|
|
effort: string;
|
|
availableEfforts: string[];
|
|
onSelectEffort: (level: string) => void;
|
|
ctxLabel?: string;
|
|
costLabel?: string;
|
|
paused: boolean;
|
|
onTogglePause: () => void;
|
|
lastTurnLabel?: string;
|
|
}
|
|
|
|
const MODEL_DESCRIPTIONS: Record<string, string> = {
|
|
haiku: 'fast',
|
|
sonnet: 'balanced',
|
|
opus: 'powerful',
|
|
};
|
|
const EFFORT_DESCRIPTIONS: Record<string, string> = {
|
|
low: '',
|
|
medium: 'default',
|
|
high: '',
|
|
xhigh: '',
|
|
max: '',
|
|
};
|
|
|
|
function Picker({
|
|
label,
|
|
value,
|
|
options,
|
|
descriptions,
|
|
onSelect,
|
|
}: {
|
|
label: string;
|
|
value: string;
|
|
options: string[];
|
|
descriptions: Record<string, string>;
|
|
onSelect: (v: string) => void;
|
|
}) {
|
|
const [open, setOpen] = useState(false);
|
|
const dropdownOptions: DropdownOption[] = options.map((name) => ({
|
|
value: name,
|
|
label: name,
|
|
description: descriptions[name] || undefined,
|
|
}));
|
|
return (
|
|
<div class="status-chip-anchor">
|
|
<Badge label={label} value={value} onClick={() => setOpen((o) => !o)} expanded={open} />
|
|
<Dropdown
|
|
open={open}
|
|
options={dropdownOptions}
|
|
activeValue={value}
|
|
label={`select ${label}`}
|
|
onSelect={(v) => {
|
|
onSelect(v);
|
|
setOpen(false);
|
|
}}
|
|
onClose={() => setOpen(false)}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function StatusChips({
|
|
aliveLabel,
|
|
aliveTone,
|
|
stateLabel,
|
|
stateTone,
|
|
stateTooltip,
|
|
model,
|
|
availableModels,
|
|
onSelectModel,
|
|
effort,
|
|
availableEfforts,
|
|
onSelectEffort,
|
|
ctxLabel,
|
|
costLabel,
|
|
paused,
|
|
onTogglePause,
|
|
lastTurnLabel,
|
|
}: StatusChipsProps) {
|
|
return (
|
|
<div class="status-chips">
|
|
<Badge value={aliveLabel} tone={aliveTone} />
|
|
<Badge value={stateLabel} tone={stateTone} title={stateTooltip} />
|
|
<Picker
|
|
label="model"
|
|
value={model}
|
|
options={availableModels}
|
|
descriptions={MODEL_DESCRIPTIONS}
|
|
onSelect={onSelectModel}
|
|
/>
|
|
{availableEfforts.length ? (
|
|
<Picker
|
|
label="effort"
|
|
value={effort}
|
|
options={availableEfforts}
|
|
descriptions={EFFORT_DESCRIPTIONS}
|
|
onSelect={onSelectEffort}
|
|
/>
|
|
) : null}
|
|
{ctxLabel ? (
|
|
<Badge
|
|
label="ctx"
|
|
value={ctxLabel}
|
|
title="tokens used in the current context window"
|
|
/>
|
|
) : null}
|
|
{costLabel ? (
|
|
<Badge
|
|
label="cost"
|
|
value={costLabel}
|
|
title="cumulative tokens billed across the last turn (sum across every inference; tool-heavy turns rebill the cached prompt per call)"
|
|
/>
|
|
) : null}
|
|
<Badge
|
|
value={paused ? '▶ resume' : '⏸ pause'}
|
|
tone={paused ? 'warning' : 'neutral'}
|
|
onClick={onTogglePause}
|
|
/>
|
|
{lastTurnLabel ? <span class="status-chips-last-turn">{lastTurnLabel}</span> : null}
|
|
</div>
|
|
);
|
|
}
|