Files
letta-code/src/cli/components/InlineEnterPlanModeApproval.tsx
2026-01-08 17:58:07 -08:00

134 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Box, Text, useInput } from "ink";
import { memo, useState } from "react";
import { useProgressIndicator } from "../hooks/useProgressIndicator";
import { useTerminalWidth } from "../hooks/useTerminalWidth";
import { colors } from "./colors";
type Props = {
onApprove: () => void;
onReject: () => void;
isFocused?: boolean;
};
// Horizontal line character for Claude Code style
const SOLID_LINE = "─";
const OptionsRenderer = memo(
({
options,
selectedOption,
}: {
options: Array<{ label: string }>;
selectedOption: number;
}) => {
return (
<Box flexDirection="column">
{options.map((option, index) => {
const isSelected = index === selectedOption;
const color = isSelected ? colors.approval.header : undefined;
return (
<Box key={option.label} flexDirection="row">
<Text color={color}>
{isSelected ? "" : " "} {index + 1}. {option.label}
</Text>
</Box>
);
})}
</Box>
);
},
);
OptionsRenderer.displayName = "OptionsRenderer";
/**
* InlineEnterPlanModeApproval - Renders EnterPlanMode approval UI inline
*
* Uses horizontal lines instead of boxes for visual styling.
*/
export const InlineEnterPlanModeApproval = memo(
({ onApprove, onReject, isFocused = true }: Props) => {
const [selectedOption, setSelectedOption] = useState(0);
const columns = useTerminalWidth();
useProgressIndicator();
const options = [
{ label: "Yes, enter plan mode", action: onApprove },
{ label: "No, start implementing now", action: onReject },
];
useInput(
(input, key) => {
if (!isFocused) return;
// CTRL-C: immediately reject (cancel)
if (key.ctrl && input === "c") {
onReject();
return;
}
// ESC: reject (cancel)
if (key.escape) {
onReject();
return;
}
if (key.upArrow) {
setSelectedOption((prev) => Math.max(0, prev - 1));
} else if (key.downArrow) {
setSelectedOption((prev) => Math.min(options.length - 1, prev + 1));
} else if (key.return) {
options[selectedOption]?.action();
} else if (input === "1") {
onApprove();
} else if (input === "2") {
onReject();
}
},
{ isActive: isFocused },
);
// Generate horizontal line
const solidLine = SOLID_LINE.repeat(Math.max(columns - 2, 10));
return (
<Box flexDirection="column">
{/* Top solid line */}
<Text dimColor>{solidLine}</Text>
{/* Header */}
<Text bold color={colors.approval.header}>
Enter plan mode?
</Text>
<Box height={1} />
{/* Description */}
<Box flexDirection="column" paddingLeft={2}>
<Text>
Letta Code wants to enter plan mode to explore and design an
implementation approach.
</Text>
<Box height={1} />
<Text>In plan mode, Letta Code will:</Text>
<Text> · Explore the codebase thoroughly</Text>
<Text> · Identify existing patterns</Text>
<Text> · Design an implementation strategy</Text>
<Text> · Present a plan for your approval</Text>
<Box height={1} />
<Text dimColor>
No code changes will be made until you approve the plan.
</Text>
</Box>
{/* Options */}
<Box marginTop={1}>
<OptionsRenderer options={options} selectedOption={selectedOption} />
</Box>
</Box>
);
},
);
InlineEnterPlanModeApproval.displayName = "InlineEnterPlanModeApproval";