mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Including a test harness for it, and making sure the cursor is always at the end.
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { Box, Text, useInput } from 'ink';
|
||||
import { Box, Text } from 'ink';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import type { HistoryItem } from './types.js';
|
||||
import { useGeminiStream } from './hooks/useGeminiStream.js';
|
||||
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
|
||||
import { useInputHistory } from './hooks/useInputHistory.js';
|
||||
import Header from './components/Header.js';
|
||||
import Tips from './components/Tips.js';
|
||||
import HistoryDisplay from './components/HistoryDisplay.js';
|
||||
@@ -22,11 +23,8 @@ interface AppProps {
|
||||
}
|
||||
|
||||
const App = ({ directory }: AppProps) => {
|
||||
const [query, setQuery] = useState('');
|
||||
const [history, setHistory] = useState<HistoryItem[]>([]);
|
||||
const [startupWarnings, setStartupWarnings] = useState<string[]>([]);
|
||||
const [historyIndex, setHistoryIndex] = useState<number>(-1);
|
||||
const [originalQueryBeforeNav, setOriginalQueryBeforeNav] = useState<string>('');
|
||||
const { streamingState, submitQuery, initError } =
|
||||
useGeminiStream(setHistory);
|
||||
const { elapsedTime, currentLoadingPhrase } =
|
||||
@@ -34,34 +32,54 @@ const App = ({ directory }: AppProps) => {
|
||||
|
||||
const userMessages = useMemo(() => {
|
||||
return history
|
||||
.filter((item): item is HistoryItem & { type: 'user'; text: string } =>
|
||||
item.type === 'user' && typeof item.text === 'string' && item.text.trim() !== ''
|
||||
)
|
||||
.map(item => item.text);
|
||||
.filter(
|
||||
(item): item is HistoryItem & { type: 'user'; text: string } =>
|
||||
item.type === 'user' &&
|
||||
typeof item.text === 'string' &&
|
||||
item.text.trim() !== '',
|
||||
)
|
||||
.map((item) => item.text);
|
||||
}, [history]);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
if (fs.existsSync(warningsFilePath)) {
|
||||
console.log('[App] Found warnings file:', warningsFilePath);
|
||||
const warningsContent = fs.readFileSync(warningsFilePath, 'utf-8');
|
||||
setStartupWarnings(warningsContent.split('\n').filter(line => line.trim() !== ''));
|
||||
setStartupWarnings(
|
||||
warningsContent.split('\n').filter((line) => line.trim() !== ''),
|
||||
);
|
||||
try {
|
||||
fs.unlinkSync(warningsFilePath);
|
||||
fs.unlinkSync(warningsFilePath);
|
||||
} catch (unlinkErr: any) {
|
||||
console.warn(`[App] Warning: Could not delete warnings file: ${unlinkErr.message}`);
|
||||
console.warn(
|
||||
`[App] Warning: Could not delete warnings file: ${unlinkErr.message}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log('[App] No warnings file found.');
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(`[App] Error checking/reading warnings file: ${err.message}`);
|
||||
console.error(
|
||||
`[App] Error checking/reading warnings file: ${err.message}`,
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const isWaitingForToolConfirmation = history.some(
|
||||
(item) =>
|
||||
item.type === 'tool_group' &&
|
||||
item.tools.some((tool) => tool.confirmationDetails !== undefined),
|
||||
);
|
||||
const isInputActive =
|
||||
streamingState === StreamingState.Idle &&
|
||||
!initError &&
|
||||
!isWaitingForToolConfirmation;
|
||||
|
||||
const { query, setQuery, resetHistoryNav, inputKey } = useInputHistory({
|
||||
userMessages,
|
||||
isActive: isInputActive,
|
||||
});
|
||||
|
||||
const handleInputSubmit = (value: PartListUnion) => {
|
||||
setHistoryIndex(-1);
|
||||
setOriginalQueryBeforeNav('');
|
||||
resetHistoryNav();
|
||||
submitQuery(value)
|
||||
.then(() => {
|
||||
setQuery('');
|
||||
@@ -89,47 +107,6 @@ const App = ({ directory }: AppProps) => {
|
||||
}
|
||||
}, [initError, history]);
|
||||
|
||||
const isWaitingForToolConfirmation = history.some(
|
||||
(item) =>
|
||||
item.type === 'tool_group' &&
|
||||
item.tools.some((tool) => tool.confirmationDetails !== undefined),
|
||||
);
|
||||
const isInputActive = streamingState === StreamingState.Idle && !initError;
|
||||
|
||||
useInput((input, key) => {
|
||||
if (!isInputActive || isWaitingForToolConfirmation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.upArrow) {
|
||||
if (userMessages.length === 0) return;
|
||||
if (historyIndex === -1) {
|
||||
setOriginalQueryBeforeNav(query);
|
||||
}
|
||||
const nextIndex = Math.min(historyIndex + 1, userMessages.length - 1);
|
||||
if (nextIndex !== historyIndex) {
|
||||
setHistoryIndex(nextIndex);
|
||||
setQuery(userMessages[userMessages.length - 1 - nextIndex]);
|
||||
}
|
||||
} else if (key.downArrow) {
|
||||
if (historyIndex < 0) return;
|
||||
const nextIndex = Math.max(historyIndex - 1, -1);
|
||||
setHistoryIndex(nextIndex);
|
||||
if (nextIndex === -1) {
|
||||
setQuery(originalQueryBeforeNav);
|
||||
} else {
|
||||
setQuery(userMessages[userMessages.length - 1 - nextIndex]);
|
||||
}
|
||||
} else {
|
||||
if (input || key.backspace || key.delete || key.leftArrow || key.rightArrow) {
|
||||
if (historyIndex !== -1) {
|
||||
setHistoryIndex(-1);
|
||||
setOriginalQueryBeforeNav('');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { isActive: isInputActive });
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" padding={1} marginBottom={1} width="100%">
|
||||
<Header cwd={directory} />
|
||||
@@ -193,12 +170,13 @@ const App = ({ directory }: AppProps) => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{!isWaitingForToolConfirmation && isInputActive && (
|
||||
{!isWaitingForToolConfirmation && (
|
||||
<InputPrompt
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
onSubmit={handleInputSubmit}
|
||||
isActive={isInputActive}
|
||||
forceKey={inputKey}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user