mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-18 10:01:29 -07:00
feat(sessions): add /resume slash command to open the session browser (#13621)
This commit is contained in:
@@ -20,6 +20,7 @@ import { PrivacyNotice } from '../privacy/PrivacyNotice.js';
|
||||
import { ProQuotaDialog } from './ProQuotaDialog.js';
|
||||
import { runExitCleanup } from '../../utils/cleanup.js';
|
||||
import { RELAUNCH_EXIT_CODE } from '../../utils/processUtils.js';
|
||||
import { SessionBrowser } from './SessionBrowser.js';
|
||||
import { PermissionsModifyTrustDialog } from './PermissionsModifyTrustDialog.js';
|
||||
import { ModelDialog } from './ModelDialog.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
@@ -210,6 +211,16 @@ export const DialogManager = ({
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (uiState.isSessionBrowserOpen) {
|
||||
return (
|
||||
<SessionBrowser
|
||||
config={config}
|
||||
onResumeSession={uiActions.handleResumeSession}
|
||||
onDeleteSession={uiActions.handleDeleteSession}
|
||||
onExit={uiActions.closeSessionBrowser}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (uiState.isPermissionsDialogOpen) {
|
||||
return (
|
||||
|
||||
@@ -86,6 +86,12 @@ const mockSlashCommands: SlashCommand[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'resume',
|
||||
description: 'Browse and resume sessions',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
action: vi.fn(),
|
||||
},
|
||||
];
|
||||
|
||||
describe('InputPrompt', () => {
|
||||
|
||||
@@ -57,7 +57,10 @@ vi.mock('./SessionBrowser.js', async (importOriginal) => {
|
||||
moveSelection,
|
||||
cycleSortOrder,
|
||||
props.onResumeSession,
|
||||
props.onDeleteSession,
|
||||
props.onDeleteSession ??
|
||||
(async () => {
|
||||
// no-op delete handler for tests that don't care about deletion
|
||||
}),
|
||||
props.onExit,
|
||||
);
|
||||
|
||||
@@ -146,12 +149,14 @@ describe('SessionBrowser component', () => {
|
||||
it('shows empty state when no sessions exist', () => {
|
||||
const config = createMockConfig();
|
||||
const onResumeSession = vi.fn();
|
||||
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
|
||||
const onExit = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<TestSessionBrowser
|
||||
config={config}
|
||||
onResumeSession={onResumeSession}
|
||||
onDeleteSession={onDeleteSession}
|
||||
onExit={onExit}
|
||||
testSessions={[]}
|
||||
/>,
|
||||
@@ -181,12 +186,14 @@ describe('SessionBrowser component', () => {
|
||||
|
||||
const config = createMockConfig();
|
||||
const onResumeSession = vi.fn();
|
||||
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
|
||||
const onExit = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<TestSessionBrowser
|
||||
config={config}
|
||||
onResumeSession={onResumeSession}
|
||||
onDeleteSession={onDeleteSession}
|
||||
onExit={onExit}
|
||||
testSessions={[session1, session2]}
|
||||
/>,
|
||||
@@ -230,12 +237,14 @@ describe('SessionBrowser component', () => {
|
||||
|
||||
const config = createMockConfig();
|
||||
const onResumeSession = vi.fn();
|
||||
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
|
||||
const onExit = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<TestSessionBrowser
|
||||
config={config}
|
||||
onResumeSession={onResumeSession}
|
||||
onDeleteSession={onDeleteSession}
|
||||
onExit={onExit}
|
||||
testSessions={[searchSession, otherSession]}
|
||||
/>,
|
||||
@@ -279,12 +288,14 @@ describe('SessionBrowser component', () => {
|
||||
|
||||
const config = createMockConfig();
|
||||
const onResumeSession = vi.fn();
|
||||
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
|
||||
const onExit = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<TestSessionBrowser
|
||||
config={config}
|
||||
onResumeSession={onResumeSession}
|
||||
onDeleteSession={onDeleteSession}
|
||||
onExit={onExit}
|
||||
testSessions={[session1, session2]}
|
||||
/>,
|
||||
@@ -323,7 +334,7 @@ describe('SessionBrowser component', () => {
|
||||
|
||||
const config = createMockConfig();
|
||||
const onResumeSession = vi.fn();
|
||||
const onDeleteSession = vi.fn();
|
||||
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
|
||||
const onExit = vi.fn();
|
||||
|
||||
render(
|
||||
@@ -348,12 +359,14 @@ describe('SessionBrowser component', () => {
|
||||
it('shows an error state when loading sessions fails', () => {
|
||||
const config = createMockConfig();
|
||||
const onResumeSession = vi.fn();
|
||||
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
|
||||
const onExit = vi.fn();
|
||||
|
||||
const { lastFrame } = render(
|
||||
<TestSessionBrowser
|
||||
config={config}
|
||||
onResumeSession={onResumeSession}
|
||||
onDeleteSession={onDeleteSession}
|
||||
onExit={onExit}
|
||||
testError="storage failure"
|
||||
/>,
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface SessionBrowserProps {
|
||||
/** Callback when user selects a session to resume */
|
||||
onResumeSession: (session: SessionInfo) => void;
|
||||
/** Callback when user deletes a session */
|
||||
onDeleteSession?: (session: SessionInfo) => void;
|
||||
onDeleteSession: (session: SessionInfo) => Promise<void>;
|
||||
/** Callback when user exits the session browser */
|
||||
onExit: () => void;
|
||||
}
|
||||
@@ -463,9 +463,11 @@ const SessionItem = ({
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve a few characters for metadata like " (current)" so the name doesn't wrap awkwardly.
|
||||
const reservedForMeta = additionalInfo ? additionalInfo.length + 1 : 0;
|
||||
const availableMessageWidth = Math.max(
|
||||
20,
|
||||
terminalWidth - FIXED_SESSION_COLUMNS_WIDTH,
|
||||
terminalWidth - FIXED_SESSION_COLUMNS_WIDTH - reservedForMeta,
|
||||
);
|
||||
|
||||
const truncatedMessage =
|
||||
@@ -759,7 +761,7 @@ export const useSessionBrowserInput = (
|
||||
moveSelection: (delta: number) => void,
|
||||
cycleSortOrder: () => void,
|
||||
onResumeSession: (session: SessionInfo) => void,
|
||||
onDeleteSession: ((session: SessionInfo) => void) | undefined,
|
||||
onDeleteSession: (session: SessionInfo) => Promise<void>,
|
||||
onExit: () => void,
|
||||
) => {
|
||||
useKeypress(
|
||||
@@ -817,38 +819,35 @@ export const useSessionBrowserInput = (
|
||||
else if (key.sequence === 'x' || key.sequence === 'X') {
|
||||
const selectedSession =
|
||||
state.filteredAndSortedSessions[state.activeIndex];
|
||||
if (
|
||||
selectedSession &&
|
||||
!selectedSession.isCurrentSession &&
|
||||
onDeleteSession
|
||||
) {
|
||||
try {
|
||||
onDeleteSession(selectedSession);
|
||||
// Remove the session from the state
|
||||
state.setSessions(
|
||||
state.sessions.filter((s) => s.id !== selectedSession.id),
|
||||
);
|
||||
|
||||
// Adjust active index if needed
|
||||
if (
|
||||
state.activeIndex >=
|
||||
state.filteredAndSortedSessions.length - 1
|
||||
) {
|
||||
state.setActiveIndex(
|
||||
Math.max(0, state.filteredAndSortedSessions.length - 2),
|
||||
if (selectedSession && !selectedSession.isCurrentSession) {
|
||||
onDeleteSession(selectedSession)
|
||||
.then(() => {
|
||||
// Remove the session from the state
|
||||
state.setSessions(
|
||||
state.sessions.filter((s) => s.id !== selectedSession.id),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
state.setError(
|
||||
`Failed to delete session: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Adjust active index if needed
|
||||
if (
|
||||
state.activeIndex >=
|
||||
state.filteredAndSortedSessions.length - 1
|
||||
) {
|
||||
state.setActiveIndex(
|
||||
Math.max(0, state.filteredAndSortedSessions.length - 2),
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
state.setError(
|
||||
`Failed to delete session: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
// less-like u/d controls.
|
||||
else if (key.sequence === 'd') {
|
||||
else if (key.sequence === 'u') {
|
||||
moveSelection(-Math.round(SESSIONS_PER_PAGE / 2));
|
||||
} else if (key.sequence === 'u') {
|
||||
} else if (key.sequence === 'd') {
|
||||
moveSelection(Math.round(SESSIONS_PER_PAGE / 2));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user