mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Co-authored-by: jacob314 <jacob314@gmail.com>
This commit is contained in:
@@ -1572,4 +1572,59 @@ describe('App UI', () => {
|
|||||||
expect(mockSettings.merged.debugKeystrokeLogging).toBeUndefined();
|
expect(mockSettings.merged.debugKeystrokeLogging).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Ctrl+C behavior', () => {
|
||||||
|
it('should call cancel but only clear the prompt when a tool is executing', async () => {
|
||||||
|
const mockCancel = vi.fn();
|
||||||
|
|
||||||
|
// Simulate a tool in the "Executing" state.
|
||||||
|
vi.mocked(useGeminiStream).mockReturnValue({
|
||||||
|
streamingState: StreamingState.Responding,
|
||||||
|
submitQuery: vi.fn(),
|
||||||
|
initError: null,
|
||||||
|
pendingHistoryItems: [
|
||||||
|
{
|
||||||
|
type: 'tool_group',
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
name: 'test_tool',
|
||||||
|
status: 'Executing',
|
||||||
|
result: '',
|
||||||
|
args: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
thought: null,
|
||||||
|
cancelOngoingRequest: mockCancel,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { stdin, lastFrame, unmount } = renderWithProviders(
|
||||||
|
<App
|
||||||
|
config={mockConfig as unknown as ServerConfig}
|
||||||
|
settings={mockSettings}
|
||||||
|
version={mockVersion}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
currentUnmount = unmount;
|
||||||
|
|
||||||
|
// Simulate user typing something into the prompt while a tool is running.
|
||||||
|
stdin.write('some text');
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// Verify the text is in the prompt.
|
||||||
|
expect(lastFrame()).toContain('some text');
|
||||||
|
|
||||||
|
// Simulate Ctrl+C.
|
||||||
|
stdin.write('\x03');
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
|
// The main cancellation handler SHOULD be called.
|
||||||
|
expect(mockCancel).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// The prompt should now be empty as a result of the cancellation handler's logic.
|
||||||
|
// We can't directly test the buffer's state, but we can see the rendered output.
|
||||||
|
expect(lastFrame()).not.toContain('some text');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,9 +5,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
||||||
import type { DOMElement } from 'ink';
|
import {
|
||||||
import { Box, measureElement, Static, Text, useStdin, useStdout } from 'ink';
|
Box,
|
||||||
import { StreamingState, type HistoryItem, MessageType } from './types.js';
|
type DOMElement,
|
||||||
|
measureElement,
|
||||||
|
Static,
|
||||||
|
Text,
|
||||||
|
useStdin,
|
||||||
|
useStdout,
|
||||||
|
} from 'ink';
|
||||||
|
import {
|
||||||
|
StreamingState,
|
||||||
|
type HistoryItem,
|
||||||
|
MessageType,
|
||||||
|
ToolCallStatus,
|
||||||
|
type HistoryItemWithoutId,
|
||||||
|
} from './types.js';
|
||||||
import { useTerminalSize } from './hooks/useTerminalSize.js';
|
import { useTerminalSize } from './hooks/useTerminalSize.js';
|
||||||
import { useGeminiStream } from './hooks/useGeminiStream.js';
|
import { useGeminiStream } from './hooks/useGeminiStream.js';
|
||||||
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
|
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
|
||||||
@@ -102,6 +115,17 @@ interface AppProps {
|
|||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isToolExecuting(pendingHistoryItems: HistoryItemWithoutId[]) {
|
||||||
|
return pendingHistoryItems.some((item) => {
|
||||||
|
if (item && item.type === 'tool_group') {
|
||||||
|
return item.tools.some(
|
||||||
|
(tool) => ToolCallStatus.Executing === tool.status,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const AppWrapper = (props: AppProps) => {
|
export const AppWrapper = (props: AppProps) => {
|
||||||
const kittyProtocolStatus = useKittyKeyboardProtocol();
|
const kittyProtocolStatus = useKittyKeyboardProtocol();
|
||||||
return (
|
return (
|
||||||
@@ -564,6 +588,11 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
|||||||
() => cancelHandlerRef.current(),
|
() => cancelHandlerRef.current(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const pendingHistoryItems = useMemo(
|
||||||
|
() => [...pendingSlashCommandHistoryItems, ...pendingGeminiHistoryItems],
|
||||||
|
[pendingSlashCommandHistoryItems, pendingGeminiHistoryItems],
|
||||||
|
);
|
||||||
|
|
||||||
// Message queue for handling input during streaming
|
// Message queue for handling input during streaming
|
||||||
const { messageQueue, addMessage, clearQueue, getQueuedMessagesText } =
|
const { messageQueue, addMessage, clearQueue, getQueuedMessagesText } =
|
||||||
useMessageQueue({
|
useMessageQueue({
|
||||||
@@ -573,6 +602,11 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
|||||||
|
|
||||||
// Update the cancel handler with message queue support
|
// Update the cancel handler with message queue support
|
||||||
cancelHandlerRef.current = useCallback(() => {
|
cancelHandlerRef.current = useCallback(() => {
|
||||||
|
if (isToolExecuting(pendingHistoryItems)) {
|
||||||
|
buffer.setText(''); // Just clear the prompt
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const lastUserMessage = userMessages.at(-1);
|
const lastUserMessage = userMessages.at(-1);
|
||||||
let textToSet = lastUserMessage || '';
|
let textToSet = lastUserMessage || '';
|
||||||
|
|
||||||
@@ -586,7 +620,13 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
|||||||
if (textToSet) {
|
if (textToSet) {
|
||||||
buffer.setText(textToSet);
|
buffer.setText(textToSet);
|
||||||
}
|
}
|
||||||
}, [buffer, userMessages, getQueuedMessagesText, clearQueue]);
|
}, [
|
||||||
|
buffer,
|
||||||
|
userMessages,
|
||||||
|
getQueuedMessagesText,
|
||||||
|
clearQueue,
|
||||||
|
pendingHistoryItems,
|
||||||
|
]);
|
||||||
|
|
||||||
// Input handling - queue messages for processing
|
// Input handling - queue messages for processing
|
||||||
const handleFinalSubmit = useCallback(
|
const handleFinalSubmit = useCallback(
|
||||||
@@ -622,8 +662,6 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit);
|
const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit);
|
||||||
const pendingHistoryItems = [...pendingSlashCommandHistoryItems];
|
|
||||||
pendingHistoryItems.push(...pendingGeminiHistoryItems);
|
|
||||||
|
|
||||||
const { elapsedTime, currentLoadingPhrase } =
|
const { elapsedTime, currentLoadingPhrase } =
|
||||||
useLoadingIndicator(streamingState);
|
useLoadingIndicator(streamingState);
|
||||||
|
|||||||
Reference in New Issue
Block a user