diff options
Diffstat (limited to 'packages/cli/src/ui/App.tsx')
| -rw-r--r-- | packages/cli/src/ui/App.tsx | 86 |
1 files changed, 71 insertions, 15 deletions
diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index e0dbcba2..1aeb3bd0 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -24,6 +24,7 @@ import { useFolderTrust } from './hooks/useFolderTrust.js'; import { useEditorSettings } from './hooks/useEditorSettings.js'; import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js'; import { useAutoAcceptIndicator } from './hooks/useAutoAcceptIndicator.js'; +import { useMessageQueue } from './hooks/useMessageQueue.js'; import { useConsoleMessages } from './hooks/useConsoleMessages.js'; import { Header } from './components/Header.js'; import { LoadingIndicator } from './components/LoadingIndicator.js'; @@ -102,6 +103,8 @@ import { appEvents, AppEvent } from '../utils/events.js'; import { isNarrowWidth } from './utils/isNarrowWidth.js'; const CTRL_EXIT_PROMPT_DURATION_MS = 1000; +// Maximum number of queued messages to display in UI to prevent performance issues +const MAX_DISPLAYED_QUEUED_MESSAGES = 3; interface AppProps { config: Config; @@ -546,12 +549,8 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { const [userMessages, setUserMessages] = useState<string[]>([]); - const handleUserCancel = useCallback(() => { - const lastUserMessage = userMessages.at(-1); - if (lastUserMessage) { - buffer.setText(lastUserMessage); - } - }, [buffer, userMessages]); + // Stable reference for cancel handler to avoid circular dependency + const cancelHandlerRef = useRef<() => void>(() => {}); const { streamingState, @@ -574,18 +573,39 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { modelSwitchedFromQuotaError, setModelSwitchedFromQuotaError, refreshStatic, - handleUserCancel, + () => cancelHandlerRef.current(), ); - // Input handling + // Message queue for handling input during streaming + const { messageQueue, addMessage, clearQueue, getQueuedMessagesText } = + useMessageQueue({ + streamingState, + submitQuery, + }); + + // Update the cancel handler with message queue support + cancelHandlerRef.current = useCallback(() => { + const lastUserMessage = userMessages.at(-1); + let textToSet = lastUserMessage || ''; + + // Append queued messages if any exist + const queuedText = getQueuedMessagesText(); + if (queuedText) { + textToSet = textToSet ? `${textToSet}\n\n${queuedText}` : queuedText; + clearQueue(); + } + + if (textToSet) { + buffer.setText(textToSet); + } + }, [buffer, userMessages, getQueuedMessagesText, clearQueue]); + + // Input handling - queue messages for processing const handleFinalSubmit = useCallback( (submittedValue: string) => { - const trimmedValue = submittedValue.trim(); - if (trimmedValue.length > 0) { - submitQuery(trimmedValue); - } + addMessage(submittedValue); }, - [submitQuery], + [addMessage], ); const handleIdePromptComplete = useCallback( @@ -625,7 +645,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { ( pressedOnce: boolean, setPressedOnce: (value: boolean) => void, - timerRef: React.MutableRefObject<NodeJS.Timeout | null>, + timerRef: ReturnType<typeof useRef<NodeJS.Timeout | null>>, ) => { if (pressedOnce) { if (timerRef.current) { @@ -761,7 +781,10 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { }, [history, logger]); const isInputActive = - streamingState === StreamingState.Idle && !initError && !isProcessing; + (streamingState === StreamingState.Idle || + streamingState === StreamingState.Responding) && + !initError && + !isProcessing; const handleClearScreen = useCallback(() => { clearItems(); @@ -1081,6 +1104,39 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { elapsedTime={elapsedTime} /> + {/* Display queued messages below loading indicator */} + {messageQueue.length > 0 && ( + <Box flexDirection="column" marginTop={1}> + {messageQueue + .slice(0, MAX_DISPLAYED_QUEUED_MESSAGES) + .map((message, index) => { + // Ensure multi-line messages are collapsed for the preview. + // Replace all whitespace (including newlines) with a single space. + const preview = message.replace(/\s+/g, ' '); + + return ( + // Ensure the Box takes full width so truncation calculates correctly + <Box key={index} paddingLeft={2} width="100%"> + {/* Use wrap="truncate" to ensure it fits the terminal width and doesn't wrap */} + <Text dimColor wrap="truncate"> + {preview} + </Text> + </Box> + ); + })} + {messageQueue.length > MAX_DISPLAYED_QUEUED_MESSAGES && ( + <Box paddingLeft={2}> + <Text dimColor> + ... (+ + {messageQueue.length - + MAX_DISPLAYED_QUEUED_MESSAGES}{' '} + more) + </Text> + </Box> + )} + </Box> + )} + <Box marginTop={1} justifyContent="space-between" |
