qwen-code/packages/cli/src/ui/components/Composer.tsx
tanzhenxin 411ebd03b8 fix(cli): improve ask_user_question tool handling and UI
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

- In ACP mode: allow ask_user_question in YOLO mode (user must respond)

- In ACP mode: allow ask_user_question in plan mode for clarifications

- Hide footer when confirmation dialog is active

- Fix tab index overflow with functional state updates

- Fix ask_user_question detection in VSCode companion (use rawInput)

- Add cleanup for pending ACP promises on panel/provider dispose

- Use theme.text.accent consistently for highlighted elements

- Remove unused 'answers' param from AskUserQuestionParams

This ensures users can always respond to clarification questions

in ACP mode regardless of approval mode, and improves dialog UX.
2026-03-08 22:15:18 +08:00

116 lines
4.3 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { Box, useIsScreenReaderEnabled } from 'ink';
import { useCallback, useState } from 'react';
import { LoadingIndicator } from './LoadingIndicator.js';
import { InputPrompt } from './InputPrompt.js';
import { Footer } from './Footer.js';
import { QueuedMessageDisplay } from './QueuedMessageDisplay.js';
import { KeyboardShortcuts } from './KeyboardShortcuts.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { useUIActions } from '../contexts/UIActionsContext.js';
import { useVimMode } from '../contexts/VimModeContext.js';
import { useConfig } from '../contexts/ConfigContext.js';
import { StreamingState } from '../types.js';
import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
import { FeedbackDialog } from '../FeedbackDialog.js';
import { t } from '../../i18n/index.js';
export const Composer = () => {
const config = useConfig();
const isScreenReaderEnabled = useIsScreenReaderEnabled();
const uiState = useUIState();
const uiActions = useUIActions();
const { vimEnabled } = useVimMode();
const { showAutoAcceptIndicator } = uiState;
// State for keyboard shortcuts display toggle
const [showShortcuts, setShowShortcuts] = useState(false);
const handleToggleShortcuts = useCallback(() => {
setShowShortcuts((prev) => !prev);
}, []);
// State for suggestions visibility
const [showSuggestions, setShowSuggestions] = useState(false);
const handleSuggestionsVisibilityChange = useCallback(
(visible: boolean) => {
setShowSuggestions(visible);
// Also notify AppContainer for Tab key handling
uiActions.onSuggestionsVisibilityChange(visible);
},
[uiActions],
);
return (
<Box flexDirection="column" marginTop={1}>
{!uiState.embeddedShellFocused && (
<LoadingIndicator
// Hide loading phrases when enableLoadingPhrases is explicitly false.
// Using === false ensures phrases show by default when undefined.
thought={
uiState.streamingState === StreamingState.WaitingForConfirmation ||
config.getAccessibility()?.enableLoadingPhrases === false
? undefined
: uiState.thought
}
currentLoadingPhrase={
config.getAccessibility()?.enableLoadingPhrases === false
? undefined
: uiState.currentLoadingPhrase
}
elapsedTime={uiState.elapsedTime}
/>
)}
{!uiState.isConfigInitialized && <ConfigInitDisplay />}
<QueuedMessageDisplay messageQueue={uiState.messageQueue} />
{uiState.isFeedbackDialogOpen && <FeedbackDialog />}
{uiState.isInputActive && (
<InputPrompt
buffer={uiState.buffer}
inputWidth={uiState.inputWidth}
suggestionsWidth={uiState.suggestionsWidth}
onSubmit={uiActions.handleFinalSubmit}
userMessages={uiState.userMessages}
onClearScreen={uiActions.handleClearScreen}
config={config}
slashCommands={uiState.slashCommands}
commandContext={uiState.commandContext}
shellModeActive={uiState.shellModeActive}
setShellModeActive={uiActions.setShellModeActive}
approvalMode={showAutoAcceptIndicator}
onEscapePromptChange={uiActions.onEscapePromptChange}
onToggleShortcuts={handleToggleShortcuts}
showShortcuts={showShortcuts}
onSuggestionsVisibilityChange={handleSuggestionsVisibilityChange}
focus={true}
vimHandleInput={uiActions.vimHandleInput}
isEmbeddedShellFocused={uiState.embeddedShellFocused}
placeholder={
vimEnabled
? ' ' + t("Press 'i' for INSERT mode and 'Esc' for NORMAL mode.")
: ' ' + t('Type your message or @path/to/file')
}
/>
)}
{/* Exclusive area: only one component visible at a time */}
{/* Hide footer when a confirmation dialog (e.g. ask_user_question) is active */}
{!showSuggestions &&
uiState.streamingState !== StreamingState.WaitingForConfirmation &&
(showShortcuts ? (
<KeyboardShortcuts />
) : (
!isScreenReaderEnabled && <Footer />
))}
</Box>
);
};