import { memo, type PointerEventHandler } from "react"; import { ChevronDownIcon, ChevronLeftIcon } from "lucide-react"; import { useEnvironmentIdentificationMode } from "~/hooks/useSettings"; import { cn } from "~/lib/utils"; import { StageBackdropButtonArt, useSidebarStageBackdropVariant } from "../SidebarStageBackdrop"; import { Button } from "../ui/button"; import { Menu, MenuItem, MenuPopup, MenuTrigger } from "../ui/menu"; import { Spinner } from "../ui/spinner"; interface PendingActionState { questionIndex: number; isLastQuestion: boolean; canAdvance: boolean; isResponding: boolean; isComplete: boolean; } interface ComposerPrimaryActionsProps { compact: boolean; pendingAction: PendingActionState | null; isRunning: boolean; showPlanFollowUpPrompt: boolean; promptHasText: boolean; isSendBusy: boolean; sendDisabledReason: string | null; isConnecting: boolean; isEnvironmentUnavailable: boolean; isPreparingWorktree: boolean; hasSendableContent: boolean; preserveComposerFocusOnPointerDown?: boolean; /** Enter-to-send is disabled on mobile viewports, where stop would otherwise * be the only primary action and a running turn could not be steered. */ showSendWhileRunning?: boolean; onPreviousPendingQuestion: () => void; onInterrupt: () => void; onImplementPlanInNewThread: () => void; } export const formatPendingPrimaryActionLabel = (input: { compact: boolean; isLastQuestion: boolean; isResponding: boolean; questionIndex: number; }) => { if (input.isResponding) { return "Submitting..."; } if (input.compact) { return input.isLastQuestion ? "Submit" : "Next"; } if (!input.isLastQuestion) { return "Next question"; } return input.questionIndex > 0 ? "Submit answers" : "Submit answer"; }; const preventPointerFocus: PointerEventHandler = (event) => { event.preventDefault(); }; export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ compact, pendingAction, isRunning, showPlanFollowUpPrompt, promptHasText, isSendBusy, sendDisabledReason, isConnecting, isEnvironmentUnavailable, isPreparingWorktree, hasSendableContent, preserveComposerFocusOnPointerDown = false, showSendWhileRunning = false, onPreviousPendingQuestion, onInterrupt, onImplementPlanInNewThread, }: ComposerPrimaryActionsProps) { const pointerFocusProps = preserveComposerFocusOnPointerDown ? { onPointerDown: preventPointerFocus } : undefined; const environmentIdentificationMode = useEnvironmentIdentificationMode(); const isSendDisabled = sendDisabledReason !== null; const stageBackdropVariant = useSidebarStageBackdropVariant( environmentIdentificationMode === "artwork", ); const renderStopGenerationButton = (insidePendingAction: boolean) => ( ); if (pendingAction) { return (
{isRunning ? renderStopGenerationButton(true) : null} {pendingAction.questionIndex > 0 ? ( compact ? ( ) : ( ) ) : null}
); } if (showPlanFollowUpPrompt) { if (promptHasText) { return ( ); } return (
} > void onImplementPlanInNewThread()} > Implement in a new thread
); } const sendButton = ( ); if (!isRunning) { return sendButton; } return ( <> {renderStopGenerationButton(false)} {showSendWhileRunning && hasSendableContent ? sendButton : null} ); });