summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/App.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/App.tsx')
-rw-r--r--packages/cli/src/ui/App.tsx86
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"