// Import useInput from vendored Ink for bracketed paste support import { Box, Text, useInput } from "ink"; import { useEffect, useRef, useState } from "react"; import { CommandPreview } from "./CommandPreview"; import { PasteAwareTextInput } from "./PasteAwareTextInput"; // Only show token count when it exceeds this threshold const COUNTER_VISIBLE_THRESHOLD = 1000; // Stable reference to prevent re-renders during typing const EMPTY_STATUS = " "; export function Input({ streaming, tokenCount, thinkingMessage, onSubmit, }: { streaming: boolean; tokenCount: number; thinkingMessage: string; onSubmit: (message?: string) => void; }) { const [value, setValue] = useState(""); const [escapePressed, setEscapePressed] = useState(false); const escapeTimerRef = useRef | null>(null); const [ctrlCPressed, setCtrlCPressed] = useState(false); const ctrlCTimerRef = useRef | null>(null); const previousValueRef = useRef(value); // Handle escape key for double-escape-to-clear useInput((_input, key) => { if (key.escape && value) { // Only work when input is non-empty if (escapePressed) { // Second escape - clear input setValue(""); setEscapePressed(false); if (escapeTimerRef.current) clearTimeout(escapeTimerRef.current); } else { // First escape - start 1-second timer setEscapePressed(true); if (escapeTimerRef.current) clearTimeout(escapeTimerRef.current); escapeTimerRef.current = setTimeout(() => { setEscapePressed(false); }, 1000); } } }); // Handle CTRL-C for double-ctrl-c-to-exit useInput((input, key) => { if (input === "c" && key.ctrl) { if (ctrlCPressed) { // Second CTRL-C - exit application process.exit(0); } else { // First CTRL-C - start 1-second timer setCtrlCPressed(true); if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current); ctrlCTimerRef.current = setTimeout(() => { setCtrlCPressed(false); }, 1000); } } }); // Reset escape and ctrl-c state when user types (value changes) useEffect(() => { if (value !== previousValueRef.current && value !== "") { setEscapePressed(false); if (escapeTimerRef.current) clearTimeout(escapeTimerRef.current); setCtrlCPressed(false); if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current); } previousValueRef.current = value; }, [value]); // Clean up timers on unmount useEffect(() => { return () => { if (escapeTimerRef.current) clearTimeout(escapeTimerRef.current); if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current); }; }, []); const handleSubmit = () => { if (streaming) { return; } onSubmit(value); setValue(""); }; const footerText = ctrlCPressed ? "Press CTRL-C again to exit" : escapePressed ? "Press Esc again to clear" : "Press / for commands"; const thinkingText = streaming ? tokenCount > COUNTER_VISIBLE_THRESHOLD ? `${thinkingMessage}… (${tokenCount}↑)` : `${thinkingMessage}…` : EMPTY_STATUS; return ( {/* Live status / token counter (per-turn) - always takes up space to prevent layout shift */} {thinkingText} {"> "} {value.startsWith("/") ? ( ) : ( {footerText} Letta Code v0.1 )} ); }