mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
fix: terminal flicker when subagent is executing
This commit is contained in:
@@ -723,8 +723,11 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
||||
);
|
||||
|
||||
const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit);
|
||||
const pendingHistoryItems = [...pendingSlashCommandHistoryItems];
|
||||
pendingHistoryItems.push(...pendingGeminiHistoryItems);
|
||||
const pendingHistoryItems = useMemo(() => {
|
||||
const items = [...pendingSlashCommandHistoryItems];
|
||||
items.push(...pendingGeminiHistoryItems);
|
||||
return items.map((item, i) => ({ ...item, id: i }));
|
||||
}, [pendingSlashCommandHistoryItems, pendingGeminiHistoryItems]);
|
||||
|
||||
const { elapsedTime, currentLoadingPhrase } =
|
||||
useLoadingIndicator(streamingState);
|
||||
@@ -1070,16 +1073,14 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
||||
</Static>
|
||||
<OverflowProvider>
|
||||
<Box ref={pendingHistoryItemRef} flexDirection="column">
|
||||
{pendingHistoryItems.map((item, i) => (
|
||||
{pendingHistoryItems.map((item) => (
|
||||
<HistoryItemDisplay
|
||||
key={i}
|
||||
key={item.id}
|
||||
availableTerminalHeight={
|
||||
constrainHeight ? availableTerminalHeight : undefined
|
||||
}
|
||||
terminalWidth={mainAreaWidth}
|
||||
// TODO(taehykim): It seems like references to ids aren't necessary in
|
||||
// HistoryItemDisplay. Refactor later. Use a fake id for now.
|
||||
item={{ ...item, id: 0 }}
|
||||
item={item}
|
||||
isPending={true}
|
||||
config={config}
|
||||
isFocused={!isEditorDialogOpen}
|
||||
|
||||
@@ -35,7 +35,7 @@ interface HistoryItemDisplayProps {
|
||||
commands?: readonly SlashCommand[];
|
||||
}
|
||||
|
||||
export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
||||
const HistoryItemDisplayComponent: React.FC<HistoryItemDisplayProps> = ({
|
||||
item,
|
||||
availableTerminalHeight,
|
||||
terminalWidth,
|
||||
@@ -101,3 +101,7 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
||||
{item.type === 'summary' && <SummaryMessage summary={item.summary} />}
|
||||
</Box>
|
||||
);
|
||||
|
||||
HistoryItemDisplayComponent.displayName = 'HistoryItemDisplay';
|
||||
|
||||
export const HistoryItemDisplay = React.memo(HistoryItemDisplayComponent);
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface ToolConfirmationMessageProps {
|
||||
isFocused?: boolean;
|
||||
availableTerminalHeight?: number;
|
||||
terminalWidth: number;
|
||||
compactMode?: boolean;
|
||||
}
|
||||
|
||||
export const ToolConfirmationMessage: React.FC<
|
||||
@@ -39,6 +40,7 @@ export const ToolConfirmationMessage: React.FC<
|
||||
isFocused = true,
|
||||
availableTerminalHeight,
|
||||
terminalWidth,
|
||||
compactMode = false,
|
||||
}) => {
|
||||
const { onConfirm } = confirmationDetails;
|
||||
const childWidth = terminalWidth - 2; // 2 for padding
|
||||
@@ -70,6 +72,40 @@ export const ToolConfirmationMessage: React.FC<
|
||||
|
||||
const handleSelect = (item: ToolConfirmationOutcome) => handleConfirm(item);
|
||||
|
||||
// Compact mode: return simple 3-option display
|
||||
if (compactMode) {
|
||||
const compactOptions: Array<RadioSelectItem<ToolConfirmationOutcome>> = [
|
||||
{
|
||||
label: 'Yes, allow once',
|
||||
value: ToolConfirmationOutcome.ProceedOnce,
|
||||
},
|
||||
{
|
||||
label: 'Allow always',
|
||||
value: ToolConfirmationOutcome.ProceedAlways,
|
||||
},
|
||||
{
|
||||
label: 'No',
|
||||
value: ToolConfirmationOutcome.Cancel,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Box>
|
||||
<Text wrap="truncate">Do you want to proceed?</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<RadioButtonSelect
|
||||
items={compactOptions}
|
||||
onSelect={handleSelect}
|
||||
isFocused={isFocused}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Original logic continues unchanged below
|
||||
let bodyContent: React.ReactNode | null = null; // Removed contextDisplay here
|
||||
let question: string;
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ import { COLOR_OPTIONS } from '../constants.js';
|
||||
import { fmtDuration } from '../utils.js';
|
||||
import { ToolConfirmationMessage } from '../../messages/ToolConfirmationMessage.js';
|
||||
|
||||
export type DisplayMode = 'default' | 'verbose';
|
||||
export type DisplayMode = 'compact' | 'default' | 'verbose';
|
||||
|
||||
export interface AgentExecutionDisplayProps {
|
||||
data: TaskResultDisplay;
|
||||
availableHeight?: number;
|
||||
childWidth?: number;
|
||||
childWidth: number;
|
||||
}
|
||||
|
||||
const getStatusColor = (
|
||||
@@ -76,8 +76,8 @@ export const AgentExecutionDisplay: React.FC<AgentExecutionDisplayProps> = ({
|
||||
data,
|
||||
availableHeight,
|
||||
childWidth,
|
||||
}) => {
|
||||
const [displayMode, setDisplayMode] = React.useState<DisplayMode>('default');
|
||||
}: AgentExecutionDisplayProps) => {
|
||||
const [displayMode, setDisplayMode] = React.useState<DisplayMode>('compact');
|
||||
|
||||
const agentColor = useMemo(() => {
|
||||
const colorOption = COLOR_OPTIONS.find(
|
||||
@@ -90,8 +90,6 @@ export const AgentExecutionDisplay: React.FC<AgentExecutionDisplayProps> = ({
|
||||
// This component only listens to keyboard shortcut events when the subagent is running
|
||||
if (data.status !== 'running') return '';
|
||||
|
||||
if (displayMode === 'verbose') return 'Press ctrl+r to show less.';
|
||||
|
||||
if (displayMode === 'default') {
|
||||
const hasMoreLines =
|
||||
data.taskPrompt.split('\n').length > MAX_TASK_PROMPT_LINES;
|
||||
@@ -99,17 +97,28 @@ export const AgentExecutionDisplay: React.FC<AgentExecutionDisplayProps> = ({
|
||||
data.toolCalls && data.toolCalls.length > MAX_TOOL_CALLS;
|
||||
|
||||
if (hasMoreToolCalls || hasMoreLines) {
|
||||
return 'Press ctrl+r to show more.';
|
||||
return 'Press ctrl+r to show less, ctrl+e to show more.';
|
||||
}
|
||||
return '';
|
||||
return 'Press ctrl+r to show less.';
|
||||
}
|
||||
return '';
|
||||
}, [displayMode, data.toolCalls, data.taskPrompt, data.status]);
|
||||
|
||||
// Handle ctrl+r keypresses to control display mode
|
||||
if (displayMode === 'verbose') {
|
||||
return 'Press ctrl+e to show less.';
|
||||
}
|
||||
|
||||
return '';
|
||||
}, [displayMode, data]);
|
||||
|
||||
// Handle keyboard shortcuts to control display mode
|
||||
useKeypress(
|
||||
(key) => {
|
||||
if (key.ctrl && key.name === 'r') {
|
||||
// ctrl+r toggles between compact and default
|
||||
setDisplayMode((current) =>
|
||||
current === 'compact' ? 'default' : 'compact',
|
||||
);
|
||||
} else if (key.ctrl && key.name === 'e') {
|
||||
// ctrl+e toggles between default and verbose
|
||||
setDisplayMode((current) =>
|
||||
current === 'default' ? 'verbose' : 'default',
|
||||
);
|
||||
@@ -118,6 +127,44 @@ export const AgentExecutionDisplay: React.FC<AgentExecutionDisplayProps> = ({
|
||||
{ isActive: true },
|
||||
);
|
||||
|
||||
if (displayMode === 'compact') {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{data.toolCalls && data.toolCalls.length > 0 && (
|
||||
<Box flexDirection="column">
|
||||
<ToolCallItem
|
||||
toolCall={data.toolCalls[data.toolCalls.length - 1]}
|
||||
compact={true}
|
||||
/>
|
||||
{/* Show count of additional tool calls if there are more than 1 */}
|
||||
{data.toolCalls.length > 1 && !data.pendingConfirmation && (
|
||||
<Box flexDirection="row" paddingLeft={4}>
|
||||
<Text color={Colors.Gray}>
|
||||
+{data.toolCalls.length - 1} more tool calls{' '}
|
||||
{data.status === 'running' ? '(ctrl+r to expand)' : ''}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Inline approval prompt when awaiting confirmation */}
|
||||
{data.pendingConfirmation && (
|
||||
<Box flexDirection="column" marginTop={1} paddingLeft={1}>
|
||||
<ToolConfirmationMessage
|
||||
confirmationDetails={data.pendingConfirmation}
|
||||
isFocused={true}
|
||||
availableTerminalHeight={availableHeight}
|
||||
terminalWidth={childWidth}
|
||||
compactMode={true}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Default and verbose modes use normal layout
|
||||
return (
|
||||
<Box flexDirection="column" paddingX={1} gap={1}>
|
||||
{/* Header with subagent name and status */}
|
||||
@@ -154,7 +201,8 @@ export const AgentExecutionDisplay: React.FC<AgentExecutionDisplayProps> = ({
|
||||
confirmationDetails={data.pendingConfirmation}
|
||||
isFocused={true}
|
||||
availableTerminalHeight={availableHeight}
|
||||
terminalWidth={childWidth ?? 80}
|
||||
terminalWidth={childWidth}
|
||||
compactMode={true}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
@@ -276,7 +324,8 @@ const ToolCallItem: React.FC<{
|
||||
resultDisplay?: string;
|
||||
description?: string;
|
||||
};
|
||||
}> = ({ toolCall }) => {
|
||||
compact?: boolean;
|
||||
}> = ({ toolCall, compact = false }) => {
|
||||
const STATUS_INDICATOR_WIDTH = 3;
|
||||
|
||||
// Map subagent status to ToolCallStatus-like display
|
||||
@@ -331,8 +380,8 @@ const ToolCallItem: React.FC<{
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Second line: truncated returnDisplay output */}
|
||||
{truncatedOutput && (
|
||||
{/* Second line: truncated returnDisplay output - hidden in compact mode */}
|
||||
{!compact && truncatedOutput && (
|
||||
<Box flexDirection="row" paddingLeft={STATUS_INDICATOR_WIDTH}>
|
||||
<Text color={Colors.Gray}>{truncatedOutput}</Text>
|
||||
</Box>
|
||||
|
||||
@@ -901,10 +901,13 @@ export const useGeminiStream = (
|
||||
],
|
||||
);
|
||||
|
||||
const pendingHistoryItems = [
|
||||
pendingHistoryItemRef.current,
|
||||
pendingToolCallGroupDisplay,
|
||||
].filter((i) => i !== undefined && i !== null);
|
||||
const pendingHistoryItems = useMemo(
|
||||
() =>
|
||||
[pendingHistoryItemRef.current, pendingToolCallGroupDisplay].filter(
|
||||
(i) => i !== undefined && i !== null,
|
||||
),
|
||||
[pendingHistoryItemRef, pendingToolCallGroupDisplay],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const saveRestorableToolCalls = async () => {
|
||||
|
||||
Reference in New Issue
Block a user