import { useImperativeHandle, useRef, useState, type Ref, } from "react"; import { StopIcon } from "@heroicons/react/20/solid"; import { ApiError } from "../lib/api-client"; import { classNames } from "../lib/class-names"; import { useInterruptRun, useSteerRun } from "../lib/mutations"; import { DockComposer, RunDockShell, DOCK_HEADER_BUTTON, } from "./run-dock"; import { ErrorMessage } from "./ui"; const STEER_MAX_LENGTH = 8192; export interface SteerBarProps { runId: string; waitingForSteer?: boolean; ref?: Ref; } export interface SteerBarHandle { focus(): void; } export function isInterruptDisabled( waitingForSteer: boolean, mutationPending: boolean, ): boolean { return waitingForSteer || mutationPending; } /** * A run that is waiting for steering needs the operator, so the dock reopens * itself and stays open until the wait clears. Derived rather than stored, so * collapsing during the wait cannot hide the prompt. */ export function isSteerDockCollapsed( collapsePreferred: boolean, waitingForSteer: boolean, ): boolean { return collapsePreferred && !waitingForSteer; } export function steerStatusLabel(waitingForSteer: boolean): string { return waitingForSteer ? "Interrupted — waiting for steering" : "Steering"; } export function SteerBar({ runId, waitingForSteer = false, ref, }: SteerBarProps) { const [errorMessage, setErrorMessage] = useState(null); // Steering is an occasional control, so the dock starts collapsed and // stays out of the way until the operator opens it. const [collapsePreferred, setCollapsePreferred] = useState(true); const textareaRef = useRef(null); const steer = useSteerRun(runId); const interrupt = useInterruptRun(runId); const pending = steer.isMutating || interrupt.isMutating; const interruptDisabled = isInterruptDisabled(waitingForSteer, pending); const collapsed = isSteerDockCollapsed(collapsePreferred, waitingForSteer); useImperativeHandle( ref, () => ({ focus() { if (!collapsed) { textareaRef.current?.focus(); return; } setCollapsePreferred(false); setTimeout(() => textareaRef.current?.focus(), 0); }, }), [collapsed], ); async function sendSteering(text: string) { setErrorMessage(null); try { await steer.trigger({ text, interrupt: false }); return true; } catch (err) { setErrorMessage(formatSteerError(err)); return false; } } async function fireInterrupt() { if (interruptDisabled) return; setErrorMessage(null); try { await interrupt.trigger(); } catch (err) { setErrorMessage(formatInterruptError(err)); } } return ( void fireInterrupt()} disabled={interruptDisabled} className={classNames( DOCK_HEADER_BUTTON, "text-amber outline-amber/40 hover:bg-amber/15 hover:text-amber focus-visible:outline-amber disabled:hover:bg-overlay disabled:hover:text-amber", )} >