feat: Add Welcome Back Dialog, Project Summary, and Enhanced Quit Options (#553)

This commit is contained in:
pomelo
2025-09-11 16:59:26 +08:00
committed by GitHub
parent 03eb1faf0a
commit b1a1ce530e
28 changed files with 1322 additions and 24 deletions

View File

@@ -23,6 +23,9 @@ import { useAuthCommand } from './hooks/useAuthCommand.js';
import { useQwenAuth } from './hooks/useQwenAuth.js';
import { useFolderTrust } from './hooks/useFolderTrust.js';
import { useEditorSettings } from './hooks/useEditorSettings.js';
import { useQuitConfirmation } from './hooks/useQuitConfirmation.js';
import { useWelcomeBack } from './hooks/useWelcomeBack.js';
import { useDialogClose } from './hooks/useDialogClose.js';
import { useSlashCommandProcessor } from './hooks/slashCommandProcessor.js';
import { useAutoAcceptIndicator } from './hooks/useAutoAcceptIndicator.js';
import { useMessageQueue } from './hooks/useMessageQueue.js';
@@ -40,6 +43,7 @@ import { QwenOAuthProgress } from './components/QwenOAuthProgress.js';
import { EditorSettingsDialog } from './components/EditorSettingsDialog.js';
import { FolderTrustDialog } from './components/FolderTrustDialog.js';
import { ShellConfirmationDialog } from './components/ShellConfirmationDialog.js';
import { QuitConfirmationDialog } from './components/QuitConfirmationDialog.js';
import { RadioButtonSelect } from './components/shared/RadioButtonSelect.js';
import { Colors } from './colors.js';
import { loadHierarchicalGeminiMemory } from '../config/config.js';
@@ -103,8 +107,8 @@ import { SettingsDialog } from './components/SettingsDialog.js';
import { setUpdateHandler } from '../utils/handleAutoUpdate.js';
import { appEvents, AppEvent } from '../utils/events.js';
import { isNarrowWidth } from './utils/isNarrowWidth.js';
import { WelcomeBackDialog } from './components/WelcomeBackDialog.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;
@@ -274,6 +278,9 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
setIsTrustedFolder,
);
const { showQuitConfirmation, handleQuitConfirmationSelect } =
useQuitConfirmation();
const {
isAuthDialogOpen,
openAuthDialog,
@@ -550,6 +557,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
commandContext,
shellConfirmationRequest,
confirmationRequest,
quitConfirmationRequest,
} = useSlashCommandProcessor(
config,
settings,
@@ -568,6 +576,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
toggleVimEnabled,
setIsProcessing,
setGeminiMdFileCount,
showQuitConfirmation,
);
const buffer = useTextBuffer({
@@ -608,6 +617,34 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
() => cancelHandlerRef.current(),
);
// Welcome back functionality
const {
welcomeBackInfo,
showWelcomeBackDialog,
welcomeBackChoice,
handleWelcomeBackSelection,
handleWelcomeBackClose,
} = useWelcomeBack(config, submitQuery, buffer, settings.merged);
// Dialog close functionality
const { closeAnyOpenDialog } = useDialogClose({
isThemeDialogOpen,
handleThemeSelect,
isAuthDialogOpen,
handleAuthSelect,
selectedAuthType: settings.merged.selectedAuthType,
isEditorDialogOpen,
exitEditorDialog,
isSettingsDialogOpen,
closeSettingsDialog,
isFolderTrustDialogOpen,
showPrivacyNotice,
setShowPrivacyNotice,
showWelcomeBackDialog,
handleWelcomeBackClose,
quitConfirmationRequest,
});
// Message queue for handling input during streaming
const { messageQueue, addMessage, clearQueue, getQueuedMessagesText } =
useMessageQueue({
@@ -679,21 +716,52 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
setPressedOnce: (value: boolean) => void,
timerRef: ReturnType<typeof useRef<NodeJS.Timeout | null>>,
) => {
// Fast double-press: Direct quit (preserve user habit)
if (pressedOnce) {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
// Directly invoke the central command handler.
// Exit directly without showing confirmation dialog
handleSlashCommand('/quit');
} else {
setPressedOnce(true);
timerRef.current = setTimeout(() => {
setPressedOnce(false);
timerRef.current = null;
}, CTRL_EXIT_PROMPT_DURATION_MS);
return;
}
// First press: Prioritize cleanup tasks
// Special case: If quit-confirm dialog is open, Ctrl+C means "quit immediately"
if (quitConfirmationRequest) {
handleSlashCommand('/quit');
return;
}
// 1. Close other dialogs (highest priority)
if (closeAnyOpenDialog()) {
return; // Dialog closed, end processing
}
// 2. Cancel ongoing requests
if (streamingState === StreamingState.Responding) {
cancelOngoingRequest?.();
return; // Request cancelled, end processing
}
// 3. Clear input buffer (if has content)
if (buffer.text.length > 0) {
buffer.setText('');
return; // Input cleared, end processing
}
// All cleanup tasks completed, show quit confirmation dialog
handleSlashCommand('/quit-confirm');
},
[handleSlashCommand],
[
handleSlashCommand,
quitConfirmationRequest,
closeAnyOpenDialog,
streamingState,
cancelOngoingRequest,
buffer,
],
);
const handleGlobalKeypress = useCallback(
@@ -726,9 +794,6 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
if (isAuthenticating) {
return;
}
if (!ctrlCPressedOnce) {
cancelOngoingRequest?.();
}
handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef);
} else if (keyMatchers[Command.EXIT](key)) {
if (buffer.text.length > 0) {
@@ -760,7 +825,6 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
ctrlDTimerRef,
handleSlashCommand,
isAuthenticating,
cancelOngoingRequest,
],
);
@@ -816,7 +880,8 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
(streamingState === StreamingState.Idle ||
streamingState === StreamingState.Responding) &&
!initError &&
!isProcessing;
!isProcessing &&
!showWelcomeBackDialog;
const handleClearScreen = useCallback(() => {
clearItems();
@@ -895,6 +960,8 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
!isThemeDialogOpen &&
!isEditorDialogOpen &&
!showPrivacyNotice &&
!showWelcomeBackDialog &&
welcomeBackChoice !== 'restart' &&
geminiClient?.isInitialized?.()
) {
submitQuery(initialPrompt);
@@ -908,6 +975,8 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
isThemeDialogOpen,
isEditorDialogOpen,
showPrivacyNotice,
showWelcomeBackDialog,
welcomeBackChoice,
geminiClient,
]);
@@ -1016,6 +1085,13 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
))}
</Box>
)}
{showWelcomeBackDialog && welcomeBackInfo?.hasHistory && (
<WelcomeBackDialog
welcomeBackInfo={welcomeBackInfo}
onSelect={handleWelcomeBackSelection}
onClose={handleWelcomeBackClose}
/>
)}
{shouldShowIdePrompt && currentIDE ? (
<IdeIntegrationNudge
@@ -1024,6 +1100,17 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
/>
) : isFolderTrustDialogOpen ? (
<FolderTrustDialog onSelect={handleFolderTrustSelect} />
) : quitConfirmationRequest ? (
<QuitConfirmationDialog
onSelect={(choice) => {
const result = handleQuitConfirmationSelect(choice);
if (result?.shouldQuit) {
quitConfirmationRequest.onConfirm(true, result.action);
} else {
quitConfirmationRequest.onConfirm(false);
}
}}
/>
) : shellConfirmationRequest ? (
<ShellConfirmationDialog request={shellConfirmationRequest} />
) : confirmationRequest ? (
@@ -1204,7 +1291,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
)}
{ctrlCPressedOnce ? (
<Text color={Colors.AccentYellow}>
Press Ctrl+C again to exit.
Press Ctrl+C again to confirm exit.
</Text>
) : ctrlDPressedOnce ? (
<Text color={Colors.AccentYellow}>