diff --git a/frontend/packages/agent/src/Root.tsx b/frontend/packages/agent/src/Root.tsx index cc3bb991..8d5fb605 100644 --- a/frontend/packages/agent/src/Root.tsx +++ b/frontend/packages/agent/src/Root.tsx @@ -25,6 +25,7 @@ import { fmtAge, fmtTokens } from './lib/format.js'; import { resolveDashboardBase } from './lib/dashboardBase.js'; import { submitPauseResume } from './lib/pauseAction.js'; import { postModel, postEffort } from './lib/modelEffort.js'; +import { postCancelTurn } from './lib/termActions.js'; import type { TokenUsage } from './types.js'; type OpenPanel = 'inbox' | 'todos' | null; @@ -153,6 +154,8 @@ export function Root() { onSelectEffort={() => {}} paused={false} onTogglePause={() => {}} + thinking={false} + onCancelTurn={() => {}} />
@@ -206,6 +209,13 @@ export function Root() { () => refresh(), ); }} + thinking={effectiveTurnState === 'thinking'} + onCancelTurn={() => { + postCancelTurn().then((r) => { + if (!r.ok) liveStreamRef.current?.pushNote(`✗ /cancel failed${r.detail ? ': ' + r.detail : ''}`); + refresh(); + }); + }} />
diff --git a/frontend/packages/agent/src/components/StatusChips.tsx b/frontend/packages/agent/src/components/StatusChips.tsx index 72ebe367..52cf5bcb 100644 --- a/frontend/packages/agent/src/components/StatusChips.tsx +++ b/frontend/packages/agent/src/components/StatusChips.tsx @@ -9,7 +9,7 @@ // 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 { useEffect, 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'; @@ -31,6 +31,8 @@ export interface StatusChipsProps { paused: boolean; onTogglePause: () => void; lastTurnLabel?: string; + thinking: boolean; + onCancelTurn: () => void; } const MODEL_DESCRIPTIONS: Record = { @@ -100,7 +102,17 @@ export function StatusChips({ paused, onTogglePause, lastTurnLabel, + thinking, + onCancelTurn, }: StatusChipsProps) { + const [cancelBusy, setCancelBusy] = useState(false); + // The button unmounts (not just hides) once `thinking` flips false, but + // `cancelBusy` lives in this component's own state across re-renders — + // reset it here so a *later* turn doesn't inherit a stale "busy" from a + // previous cancel click. + useEffect(() => { + if (!thinking) setCancelBusy(false); + }, [thinking]); return (
@@ -141,6 +153,19 @@ export function StatusChips({ onClick={onTogglePause} /> {lastTurnLabel ? {lastTurnLabel} : null} + {thinking ? ( + + ) : null}
); }