| ... | ... | @@ -214,6 +214,7 @@ import { |
| 214 | 214 | useComposerDraftStore, |
| 215 | 215 | useComposerThreadDraft, |
| 216 | 216 | } from "../composerDraftStore"; |
| 217 | import { shouldUseCompactComposerFooter } from "./composerFooterLayout"; |
| 217 | 218 | import { selectThreadTerminalState, useTerminalStateStore } from "../terminalStateStore"; |
| 218 | 219 | import { clamp } from "effect/Number"; |
| 219 | 220 | import { ComposerPromptEditor, type ComposerPromptEditorHandle } from "./ComposerPromptEditor"; |
| ... | ... | @@ -639,6 +640,7 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 639 | 640 | useState<Record<string, number>>({}); |
| 640 | 641 | const [expandedWorkGroups, setExpandedWorkGroups] = useState<Record<string, boolean>>({}); |
| 641 | 642 | const [planSidebarOpen, setPlanSidebarOpen] = useState(false); |
| 643 | const [isComposerFooterCompact, setIsComposerFooterCompact] = useState(false); |
| 642 | 644 | // Tracks whether the user explicitly dismissed the sidebar for the active turn. |
| 643 | 645 | const planSidebarDismissedForTurnRef = useRef<string | null>(null); |
| 644 | 646 | // When set, the thread-change reset effect will open the sidebar instead of closing it. |
| ... | ... | @@ -935,6 +937,7 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 935 | 937 | isComposerApprovalState || |
| 936 | 938 | pendingUserInputs.length > 0 || |
| 937 | 939 | (showPlanFollowUpPrompt && activeProposedPlan !== null); |
| 940 | const composerFooterHasWideActions = showPlanFollowUpPrompt || activePendingProgress !== null; |
| 938 | 941 | useEffect(() => { |
| 939 | 942 | if (!activePendingProgress) { |
| 940 | 943 | return; |
| ... | ... | @@ -1672,6 +1675,19 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 1672 | 1675 | const toggleInteractionMode = useCallback(() => { |
| 1673 | 1676 | handleInteractionModeChange(interactionMode === "plan" ? "default" : "plan"); |
| 1674 | 1677 | }, [handleInteractionModeChange, interactionMode]); |
| 1678 | const togglePlanSidebar = useCallback(() => { |
| 1679 | setPlanSidebarOpen((open) => { |
| 1680 | if (open) { |
| 1681 | const turnKey = activePlan?.turnId ?? activeProposedPlan?.turnId ?? null; |
| 1682 | if (turnKey) { |
| 1683 | planSidebarDismissedForTurnRef.current = turnKey; |
| 1684 | } |
| 1685 | } else { |
| 1686 | planSidebarDismissedForTurnRef.current = null; |
| 1687 | } |
| 1688 | return !open; |
| 1689 | }); |
| 1690 | }, [activePlan?.turnId, activeProposedPlan?.turnId]); |
| 1675 | 1691 | |
| 1676 | 1692 | const persistThreadSettingsForNextTurn = useCallback( |
| 1677 | 1693 | async (input: { |
| ... | ... | @@ -1890,12 +1906,22 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 1890 | 1906 | if (!composerForm) return; |
| 1891 | 1907 | |
| 1892 | 1908 | composerFormHeightRef.current = composerForm.getBoundingClientRect().height; |
| 1909 | setIsComposerFooterCompact( |
| 1910 | shouldUseCompactComposerFooter(composerForm.clientWidth, { |
| 1911 | hasWideActions: composerFooterHasWideActions, |
| 1912 | }), |
| 1913 | ); |
| 1893 | 1914 | if (typeof ResizeObserver === "undefined") return; |
| 1894 | 1915 | |
| 1895 | 1916 | const observer = new ResizeObserver((entries) => { |
| 1896 | 1917 | const [entry] = entries; |
| 1897 | 1918 | if (!entry) return; |
| 1898 | 1919 | |
| 1920 | const nextCompact = shouldUseCompactComposerFooter(entry.contentRect.width, { |
| 1921 | hasWideActions: composerFooterHasWideActions, |
| 1922 | }); |
| 1923 | setIsComposerFooterCompact((previous) => (previous === nextCompact ? previous : nextCompact)); |
| 1924 | |
| 1899 | 1925 | const nextHeight = entry.contentRect.height; |
| 1900 | 1926 | const previousHeight = composerFormHeightRef.current; |
| 1901 | 1927 | composerFormHeightRef.current = nextHeight; |
| ... | ... | @@ -1909,7 +1935,7 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 1909 | 1935 | return () => { |
| 1910 | 1936 | observer.disconnect(); |
| 1911 | 1937 | }; |
| 1912 | | }, [activeThread?.id, scheduleStickToBottom]); |
| 1938 | }, [activeThread?.id, composerFooterHasWideActions, scheduleStickToBottom]); |
| 1913 | 1939 | useEffect(() => { |
| 1914 | 1940 | if (!shouldAutoScrollRef.current) return; |
| 1915 | 1941 | scheduleStickToBottom(); |
| ... | ... | @@ -3679,10 +3705,23 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 3679 | 3705 | /> |
| 3680 | 3706 | </div> |
| 3681 | 3707 | ) : ( |
| 3682 | | <div className="flex flex-wrap items-center justify-between gap-2 px-2.5 pb-2.5 sm:flex-nowrap sm:gap-0 sm:px-3 sm:pb-3"> |
| 3683 | | <div className="flex min-w-0 flex-1 items-center gap-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden sm:min-w-max sm:overflow-visible"> |
| 3708 | <div |
| 3709 | className={cn( |
| 3710 | "flex items-center justify-between px-2.5 pb-2.5 sm:px-3 sm:pb-3", |
| 3711 | isComposerFooterCompact ? "gap-1.5" : "flex-wrap gap-2 sm:flex-nowrap sm:gap-0", |
| 3712 | )} |
| 3713 | > |
| 3714 | <div |
| 3715 | className={cn( |
| 3716 | "flex min-w-0 flex-1 items-center", |
| 3717 | isComposerFooterCompact |
| 3718 | ? "gap-1 overflow-hidden" |
| 3719 | : "gap-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden sm:min-w-max sm:overflow-visible", |
| 3720 | )} |
| 3721 | > |
| 3684 | 3722 | {/* Provider/model picker */} |
| 3685 | 3723 | <ProviderModelPicker |
| 3724 | compact={isComposerFooterCompact} |
| 3686 | 3725 | provider={selectedProvider} |
| 3687 | 3726 | model={selectedModelForPickerWithCustomFallback} |
| 3688 | 3727 | lockedProvider={lockedProvider} |
| ... | ... | @@ -3690,103 +3729,108 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 3690 | 3729 | onProviderModelChange={onProviderModelSelect} |
| 3691 | 3730 | /> |
| 3692 | 3731 | |
| 3693 | | {selectedProvider === "codex" && selectedEffort != null ? ( |
| 3732 | {isComposerFooterCompact ? ( |
| 3733 | <CompactComposerControlsMenu |
| 3734 | activePlan={Boolean(activePlan || activeProposedPlan || planSidebarOpen)} |
| 3735 | interactionMode={interactionMode} |
| 3736 | planSidebarOpen={planSidebarOpen} |
| 3737 | runtimeMode={runtimeMode} |
| 3738 | selectedEffort={selectedEffort} |
| 3739 | selectedProvider={selectedProvider} |
| 3740 | selectedCodexFastModeEnabled={selectedCodexFastModeEnabled} |
| 3741 | reasoningOptions={reasoningOptions} |
| 3742 | onEffortSelect={onEffortSelect} |
| 3743 | onCodexFastModeChange={onCodexFastModeChange} |
| 3744 | onToggleInteractionMode={toggleInteractionMode} |
| 3745 | onTogglePlanSidebar={togglePlanSidebar} |
| 3746 | onToggleRuntimeMode={() => |
| 3747 | void handleRuntimeModeChange( |
| 3748 | runtimeMode === "full-access" ? "approval-required" : "full-access", |
| 3749 | ) |
| 3750 | } |
| 3751 | /> |
| 3752 | ) : ( |
| 3694 | 3753 | <> |
| 3754 | {selectedProvider === "codex" && selectedEffort != null ? ( |
| 3755 | <> |
| 3756 | <Separator orientation="vertical" className="mx-0.5 hidden h-4 sm:block" /> |
| 3757 | <CodexTraitsPicker |
| 3758 | effort={selectedEffort} |
| 3759 | fastModeEnabled={selectedCodexFastModeEnabled} |
| 3760 | options={reasoningOptions} |
| 3761 | onEffortChange={onEffortSelect} |
| 3762 | onFastModeChange={onCodexFastModeChange} |
| 3763 | /> |
| 3764 | </> |
| 3765 | ) : null} |
| 3766 | |
| 3695 | 3767 | <Separator orientation="vertical" className="mx-0.5 hidden h-4 sm:block" /> |
| 3696 | | <CodexTraitsPicker |
| 3697 | | effort={selectedEffort} |
| 3698 | | fastModeEnabled={selectedCodexFastModeEnabled} |
| 3699 | | options={reasoningOptions} |
| 3700 | | onEffortChange={onEffortSelect} |
| 3701 | | onFastModeChange={onCodexFastModeChange} |
| 3702 | | /> |
| 3703 | | </> |
| 3704 | | ) : null} |
| 3705 | 3768 | |
| 3706 | | {/* Divider */} |
| 3707 | | <Separator orientation="vertical" className="mx-0.5 hidden h-4 sm:block" /> |
| 3769 | <Button |
| 3770 | variant="ghost" |
| 3771 | className="shrink-0 whitespace-nowrap px-2 text-muted-foreground/70 hover:text-foreground/80 sm:px-3" |
| 3772 | size="sm" |
| 3773 | type="button" |
| 3774 | onClick={toggleInteractionMode} |
| 3775 | title={ |
| 3776 | interactionMode === "plan" |
| 3777 | ? "Plan mode — click to return to normal chat mode" |
| 3778 | : "Default mode — click to enter plan mode" |
| 3779 | } |
| 3780 | > |
| 3781 | <BotIcon /> |
| 3782 | <span className="sr-only sm:not-sr-only"> |
| 3783 | {interactionMode === "plan" ? "Plan" : "Chat"} |
| 3784 | </span> |
| 3785 | </Button> |
| 3708 | 3786 | |
| 3709 | | {/* Interaction mode toggle */} |
| 3710 | | <Button |
| 3711 | | variant="ghost" |
| 3712 | | className="shrink-0 whitespace-nowrap px-2 text-muted-foreground/70 hover:text-foreground/80 sm:px-3" |
| 3713 | | size="sm" |
| 3714 | | type="button" |
| 3715 | | onClick={toggleInteractionMode} |
| 3716 | | title={ |
| 3717 | | interactionMode === "plan" |
| 3718 | | ? "Plan mode — click to return to normal chat mode" |
| 3719 | | : "Default mode — click to enter plan mode" |
| 3720 | | } |
| 3721 | | > |
| 3722 | | <BotIcon /> |
| 3723 | | <span className="sr-only sm:not-sr-only"> |
| 3724 | | {interactionMode === "plan" ? "Plan" : "Chat"} |
| 3725 | | </span> |
| 3726 | | </Button> |
| 3727 | | |
| 3728 | | {/* Divider */} |
| 3729 | | <Separator orientation="vertical" className="mx-0.5 hidden h-4 sm:block" /> |
| 3730 | | |
| 3731 | | {/* Runtime mode toggle */} |
| 3732 | | <Button |
| 3733 | | variant="ghost" |
| 3734 | | className="shrink-0 whitespace-nowrap px-2 text-muted-foreground/70 hover:text-foreground/80 sm:px-3" |
| 3735 | | size="sm" |
| 3736 | | type="button" |
| 3737 | | onClick={() => |
| 3738 | | void handleRuntimeModeChange( |
| 3739 | | runtimeMode === "full-access" ? "approval-required" : "full-access", |
| 3740 | | ) |
| 3741 | | } |
| 3742 | | title={ |
| 3743 | | runtimeMode === "full-access" |
| 3744 | | ? "Full access — click to require approvals" |
| 3745 | | : "Approval required — click for full access" |
| 3746 | | } |
| 3747 | | > |
| 3748 | | {runtimeMode === "full-access" ? <LockOpenIcon /> : <LockIcon />} |
| 3749 | | <span className="sr-only sm:not-sr-only"> |
| 3750 | | {runtimeMode === "full-access" ? "Full access" : "Supervised"} |
| 3751 | | </span> |
| 3752 | | </Button> |
| 3753 | | |
| 3754 | | {/* Plan sidebar toggle */} |
| 3755 | | {(activePlan || activeProposedPlan || planSidebarOpen) ? ( |
| 3756 | | <> |
| 3757 | 3787 | <Separator orientation="vertical" className="mx-0.5 hidden h-4 sm:block" /> |
| 3788 | |
| 3758 | 3789 | <Button |
| 3759 | 3790 | variant="ghost" |
| 3760 | | className={cn( |
| 3761 | | "shrink-0 whitespace-nowrap px-2 sm:px-3", |
| 3762 | | planSidebarOpen |
| 3763 | | ? "text-blue-400 hover:text-blue-300" |
| 3764 | | : "text-muted-foreground/70 hover:text-foreground/80", |
| 3765 | | )} |
| 3791 | className="shrink-0 whitespace-nowrap px-2 text-muted-foreground/70 hover:text-foreground/80 sm:px-3" |
| 3766 | 3792 | size="sm" |
| 3767 | 3793 | type="button" |
| 3768 | | onClick={() => { |
| 3769 | | setPlanSidebarOpen((open) => { |
| 3770 | | if (open) { |
| 3771 | | // Closing: track dismissal for current turn |
| 3772 | | const turnKey = activePlan?.turnId ?? activeProposedPlan?.turnId ?? null; |
| 3773 | | if (turnKey) { |
| 3774 | | planSidebarDismissedForTurnRef.current = turnKey; |
| 3775 | | } |
| 3776 | | } else { |
| 3777 | | // Re-opening: clear dismissal tracking |
| 3778 | | planSidebarDismissedForTurnRef.current = null; |
| 3779 | | } |
| 3780 | | return !open; |
| 3781 | | }); |
| 3782 | | }} |
| 3783 | | title={planSidebarOpen ? "Hide plan sidebar" : "Show plan sidebar"} |
| 3794 | onClick={() => |
| 3795 | void handleRuntimeModeChange( |
| 3796 | runtimeMode === "full-access" ? "approval-required" : "full-access", |
| 3797 | ) |
| 3798 | } |
| 3799 | title={ |
| 3800 | runtimeMode === "full-access" |
| 3801 | ? "Full access — click to require approvals" |
| 3802 | : "Approval required — click for full access" |
| 3803 | } |
| 3784 | 3804 | > |
| 3785 | | <ListTodoIcon /> |
| 3786 | | <span className="sr-only sm:not-sr-only">Plan</span> |
| 3805 | {runtimeMode === "full-access" ? <LockOpenIcon /> : <LockIcon />} |
| 3806 | <span className="sr-only sm:not-sr-only"> |
| 3807 | {runtimeMode === "full-access" ? "Full access" : "Supervised"} |
| 3808 | </span> |
| 3787 | 3809 | </Button> |
| 3810 | |
| 3811 | {(activePlan || activeProposedPlan || planSidebarOpen) ? ( |
| 3812 | <> |
| 3813 | <Separator orientation="vertical" className="mx-0.5 hidden h-4 sm:block" /> |
| 3814 | <Button |
| 3815 | variant="ghost" |
| 3816 | className={cn( |
| 3817 | "shrink-0 whitespace-nowrap px-2 sm:px-3", |
| 3818 | planSidebarOpen |
| 3819 | ? "text-blue-400 hover:text-blue-300" |
| 3820 | : "text-muted-foreground/70 hover:text-foreground/80", |
| 3821 | )} |
| 3822 | size="sm" |
| 3823 | type="button" |
| 3824 | onClick={togglePlanSidebar} |
| 3825 | title={planSidebarOpen ? "Hide plan sidebar" : "Show plan sidebar"} |
| 3826 | > |
| 3827 | <ListTodoIcon /> |
| 3828 | <span className="sr-only sm:not-sr-only">Plan</span> |
| 3829 | </Button> |
| 3830 | </> |
| 3831 | ) : null} |
| 3788 | 3832 | </> |
| 3789 | | ) : null} |
| 3833 | )} |
| 3790 | 3834 | </div> |
| 3791 | 3835 | |
| 3792 | 3836 | {/* Right side: send / stop button */} |
| ... | ... | @@ -5534,6 +5578,8 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: { |
| 5534 | 5578 | model: ModelSlug; |
| 5535 | 5579 | lockedProvider: ProviderKind | null; |
| 5536 | 5580 | modelOptionsByProvider: Record<ProviderKind, ReadonlyArray<{ slug: string; name: string }>>; |
| 5581 | serviceTierSetting: AppServiceTier; |
| 5582 | compact?: boolean; |
| 5537 | 5583 | disabled?: boolean; |
| 5538 | 5584 | onProviderModelChange: (provider: ProviderKind, model: ModelSlug) => void; |
| 5539 | 5585 | }) { |
| ... | ... | @@ -5559,12 +5605,20 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: { |
| 5559 | 5605 | <Button |
| 5560 | 5606 | size="sm" |
| 5561 | 5607 | variant="ghost" |
| 5562 | | className="shrink-0 whitespace-nowrap px-2 text-muted-foreground/70 hover:text-foreground/80 sm:px-3" |
| 5608 | className={cn( |
| 5609 | "min-w-0 shrink-0 whitespace-nowrap px-2 text-muted-foreground/70 hover:text-foreground/80", |
| 5610 | props.compact ? "max-w-[10.5rem]" : "sm:px-3", |
| 5611 | )} |
| 5563 | 5612 | disabled={props.disabled} |
| 5564 | 5613 | /> |
| 5565 | 5614 | } |
| 5566 | 5615 | > |
| 5567 | | <span className="flex min-w-0 items-center gap-2"> |
| 5616 | <span |
| 5617 | className={cn( |
| 5618 | "flex min-w-0 items-center gap-2", |
| 5619 | props.compact ? "max-w-[9rem]" : undefined, |
| 5620 | )} |
| 5621 | > |
| 5568 | 5622 | <ProviderIcon aria-hidden="true" className="size-4 shrink-0 text-muted-foreground/70" /> |
| 5569 | 5623 | <span className="truncate">{selectedModelLabel}</span> |
| 5570 | 5624 | <ChevronDownIcon aria-hidden="true" className="size-3 opacity-60" /> |
| ... | ... | @@ -5654,6 +5708,122 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: { |
| 5654 | 5708 | ); |
| 5655 | 5709 | }); |
| 5656 | 5710 | |
| 5711 | const CompactComposerControlsMenu = memo(function CompactComposerControlsMenu(props: { |
| 5712 | activePlan: boolean; |
| 5713 | interactionMode: ProviderInteractionMode; |
| 5714 | planSidebarOpen: boolean; |
| 5715 | runtimeMode: RuntimeMode; |
| 5716 | selectedEffort: CodexReasoningEffort | null; |
| 5717 | selectedProvider: ProviderKind; |
| 5718 | selectedCodexFastModeEnabled: boolean; |
| 5719 | reasoningOptions: ReadonlyArray<CodexReasoningEffort>; |
| 5720 | onEffortSelect: (effort: CodexReasoningEffort) => void; |
| 5721 | onCodexFastModeChange: (enabled: boolean) => void; |
| 5722 | onToggleInteractionMode: () => void; |
| 5723 | onTogglePlanSidebar: () => void; |
| 5724 | onToggleRuntimeMode: () => void; |
| 5725 | }) { |
| 5726 | const defaultReasoningEffort = getDefaultReasoningEffort("codex"); |
| 5727 | const reasoningLabelByOption: Record<CodexReasoningEffort, string> = { |
| 5728 | low: "Low", |
| 5729 | medium: "Medium", |
| 5730 | high: "High", |
| 5731 | xhigh: "Extra High", |
| 5732 | }; |
| 5733 | |
| 5734 | return ( |
| 5735 | <Menu> |
| 5736 | <MenuTrigger |
| 5737 | render={ |
| 5738 | <Button |
| 5739 | size="sm" |
| 5740 | variant="ghost" |
| 5741 | className="shrink-0 px-2 text-muted-foreground/70 hover:text-foreground/80" |
| 5742 | aria-label="More composer controls" |
| 5743 | /> |
| 5744 | } |
| 5745 | > |
| 5746 | <EllipsisIcon aria-hidden="true" className="size-4" /> |
| 5747 | </MenuTrigger> |
| 5748 | <MenuPopup align="start"> |
| 5749 | {props.selectedProvider === "codex" && props.selectedEffort != null ? ( |
| 5750 | <> |
| 5751 | <MenuGroup> |
| 5752 | <div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">Reasoning</div> |
| 5753 | <MenuRadioGroup |
| 5754 | value={props.selectedEffort} |
| 5755 | onValueChange={(value) => { |
| 5756 | if (!value) return; |
| 5757 | const nextEffort = props.reasoningOptions.find((option) => option === value); |
| 5758 | if (!nextEffort) return; |
| 5759 | props.onEffortSelect(nextEffort); |
| 5760 | }} |
| 5761 | > |
| 5762 | {props.reasoningOptions.map((effort) => ( |
| 5763 | <MenuRadioItem key={effort} value={effort}> |
| 5764 | {reasoningLabelByOption[effort]} |
| 5765 | {effort === defaultReasoningEffort ? " (default)" : ""} |
| 5766 | </MenuRadioItem> |
| 5767 | ))} |
| 5768 | </MenuRadioGroup> |
| 5769 | </MenuGroup> |
| 5770 | <MenuDivider /> |
| 5771 | <MenuGroup> |
| 5772 | <div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">Fast Mode</div> |
| 5773 | <MenuRadioGroup |
| 5774 | value={props.selectedCodexFastModeEnabled ? "on" : "off"} |
| 5775 | onValueChange={(value) => { |
| 5776 | props.onCodexFastModeChange(value === "on"); |
| 5777 | }} |
| 5778 | > |
| 5779 | <MenuRadioItem value="off">off</MenuRadioItem> |
| 5780 | <MenuRadioItem value="on">on</MenuRadioItem> |
| 5781 | </MenuRadioGroup> |
| 5782 | </MenuGroup> |
| 5783 | <MenuDivider /> |
| 5784 | </> |
| 5785 | ) : null} |
| 5786 | <MenuGroup> |
| 5787 | <div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">Mode</div> |
| 5788 | <MenuRadioGroup |
| 5789 | value={props.interactionMode} |
| 5790 | onValueChange={(value) => { |
| 5791 | if (!value || value === props.interactionMode) return; |
| 5792 | props.onToggleInteractionMode(); |
| 5793 | }} |
| 5794 | > |
| 5795 | <MenuRadioItem value="default">Chat</MenuRadioItem> |
| 5796 | <MenuRadioItem value="plan">Plan</MenuRadioItem> |
| 5797 | </MenuRadioGroup> |
| 5798 | </MenuGroup> |
| 5799 | <MenuDivider /> |
| 5800 | <MenuGroup> |
| 5801 | <div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">Access</div> |
| 5802 | <MenuRadioGroup |
| 5803 | value={props.runtimeMode} |
| 5804 | onValueChange={(value) => { |
| 5805 | if (!value || value === props.runtimeMode) return; |
| 5806 | props.onToggleRuntimeMode(); |
| 5807 | }} |
| 5808 | > |
| 5809 | <MenuRadioItem value="approval-required">Supervised</MenuRadioItem> |
| 5810 | <MenuRadioItem value="full-access">Full access</MenuRadioItem> |
| 5811 | </MenuRadioGroup> |
| 5812 | </MenuGroup> |
| 5813 | {props.activePlan ? ( |
| 5814 | <> |
| 5815 | <MenuDivider /> |
| 5816 | <MenuItem onClick={props.onTogglePlanSidebar}> |
| 5817 | <ListTodoIcon className="size-4 shrink-0" /> |
| 5818 | {props.planSidebarOpen ? "Hide plan sidebar" : "Show plan sidebar"} |
| 5819 | </MenuItem> |
| 5820 | </> |
| 5821 | ) : null} |
| 5822 | </MenuPopup> |
| 5823 | </Menu> |
| 5824 | ); |
| 5825 | }); |
| 5826 | |
| 5657 | 5827 | const CodexTraitsPicker = memo(function CodexTraitsPicker(props: { |
| 5658 | 5828 | effort: CodexReasoningEffort; |
| 5659 | 5829 | fastModeEnabled: boolean; |