mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-19 02:20:42 -07:00
Fix/issue 17070 (#17242)
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
This commit is contained in:
@@ -24,7 +24,7 @@ export const useSessionBrowser = (
|
||||
uiHistory: HistoryItemWithoutId[],
|
||||
clientHistory: Array<{ role: 'user' | 'model'; parts: Part[] }>,
|
||||
resumedSessionData: ResumedSessionData,
|
||||
) => void,
|
||||
) => Promise<void>,
|
||||
) => {
|
||||
const [isSessionBrowserOpen, setIsSessionBrowserOpen] = useState(false);
|
||||
|
||||
@@ -73,7 +73,7 @@ export const useSessionBrowser = (
|
||||
const historyData = convertSessionToHistoryFormats(
|
||||
conversation.messages,
|
||||
);
|
||||
onLoadHistory(
|
||||
await onLoadHistory(
|
||||
historyData.uiHistory,
|
||||
historyData.clientHistory,
|
||||
resumedSessionData,
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('useSessionResume', () => {
|
||||
expect(result.current.loadHistoryForResume).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
it('should clear history and add items when loading history', () => {
|
||||
it('should clear history and add items when loading history', async () => {
|
||||
const { result } = renderHook(() => useSessionResume(getDefaultProps()));
|
||||
|
||||
const uiHistory: HistoryItemWithoutId[] = [
|
||||
@@ -86,8 +86,8 @@ describe('useSessionResume', () => {
|
||||
filePath: '/path/to/session.json',
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.loadHistoryForResume(
|
||||
await act(async () => {
|
||||
await result.current.loadHistoryForResume(
|
||||
uiHistory,
|
||||
clientHistory,
|
||||
resumedData,
|
||||
@@ -116,7 +116,7 @@ describe('useSessionResume', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should not load history if Gemini client is not initialized', () => {
|
||||
it('should not load history if Gemini client is not initialized', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useSessionResume({
|
||||
...getDefaultProps(),
|
||||
@@ -141,8 +141,8 @@ describe('useSessionResume', () => {
|
||||
filePath: '/path/to/session.json',
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.loadHistoryForResume(
|
||||
await act(async () => {
|
||||
await result.current.loadHistoryForResume(
|
||||
uiHistory,
|
||||
clientHistory,
|
||||
resumedData,
|
||||
@@ -154,7 +154,7 @@ describe('useSessionResume', () => {
|
||||
expect(mockGeminiClient.resumeChat).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle empty history arrays', () => {
|
||||
it('should handle empty history arrays', async () => {
|
||||
const { result } = renderHook(() => useSessionResume(getDefaultProps()));
|
||||
|
||||
const resumedData: ResumedSessionData = {
|
||||
@@ -168,8 +168,8 @@ describe('useSessionResume', () => {
|
||||
filePath: '/path/to/session.json',
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.loadHistoryForResume([], [], resumedData);
|
||||
await act(async () => {
|
||||
await result.current.loadHistoryForResume([], [], resumedData);
|
||||
});
|
||||
|
||||
expect(mockHistoryManager.clearItems).toHaveBeenCalled();
|
||||
@@ -311,15 +311,17 @@ describe('useSessionResume', () => {
|
||||
] as MessageRecord[],
|
||||
};
|
||||
|
||||
renderHook(() =>
|
||||
useSessionResume({
|
||||
...getDefaultProps(),
|
||||
resumedSessionData: {
|
||||
conversation,
|
||||
filePath: '/path/to/session.json',
|
||||
},
|
||||
}),
|
||||
);
|
||||
await act(async () => {
|
||||
renderHook(() =>
|
||||
useSessionResume({
|
||||
...getDefaultProps(),
|
||||
resumedSessionData: {
|
||||
conversation,
|
||||
filePath: '/path/to/session.json',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryManager.clearItems).toHaveBeenCalled();
|
||||
@@ -358,20 +360,24 @@ describe('useSessionResume', () => {
|
||||
] as MessageRecord[],
|
||||
};
|
||||
|
||||
const { rerender } = renderHook(
|
||||
({ refreshStatic }: { refreshStatic: () => void }) =>
|
||||
useSessionResume({
|
||||
...getDefaultProps(),
|
||||
refreshStatic,
|
||||
resumedSessionData: {
|
||||
conversation,
|
||||
filePath: '/path/to/session.json',
|
||||
},
|
||||
}),
|
||||
{
|
||||
initialProps: { refreshStatic: mockRefreshStatic },
|
||||
},
|
||||
);
|
||||
let rerenderFunc: (props: { refreshStatic: () => void }) => void;
|
||||
await act(async () => {
|
||||
const { rerender } = renderHook(
|
||||
({ refreshStatic }: { refreshStatic: () => void }) =>
|
||||
useSessionResume({
|
||||
...getDefaultProps(),
|
||||
refreshStatic,
|
||||
resumedSessionData: {
|
||||
conversation,
|
||||
filePath: '/path/to/session.json',
|
||||
},
|
||||
}),
|
||||
{
|
||||
initialProps: { refreshStatic: mockRefreshStatic as () => void },
|
||||
},
|
||||
);
|
||||
rerenderFunc = rerender;
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockHistoryManager.clearItems).toHaveBeenCalled();
|
||||
@@ -383,7 +389,9 @@ describe('useSessionResume', () => {
|
||||
|
||||
// Rerender with different refreshStatic
|
||||
const newRefreshStatic = vi.fn();
|
||||
rerender({ refreshStatic: newRefreshStatic });
|
||||
await act(async () => {
|
||||
rerenderFunc({ refreshStatic: newRefreshStatic });
|
||||
});
|
||||
|
||||
// Should not resume again
|
||||
expect(mockHistoryManager.clearItems).toHaveBeenCalledTimes(
|
||||
@@ -413,15 +421,17 @@ describe('useSessionResume', () => {
|
||||
] as MessageRecord[],
|
||||
};
|
||||
|
||||
renderHook(() =>
|
||||
useSessionResume({
|
||||
...getDefaultProps(),
|
||||
resumedSessionData: {
|
||||
conversation,
|
||||
filePath: '/path/to/session.json',
|
||||
},
|
||||
}),
|
||||
);
|
||||
await act(async () => {
|
||||
renderHook(() =>
|
||||
useSessionResume({
|
||||
...getDefaultProps(),
|
||||
resumedSessionData: {
|
||||
conversation,
|
||||
filePath: '/path/to/session.json',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockGeminiClient.resumeChat).toHaveBeenCalled();
|
||||
|
||||
@@ -4,8 +4,12 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import type { Config, ResumedSessionData } from '@google/gemini-cli-core';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
coreEvents,
|
||||
type Config,
|
||||
type ResumedSessionData,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { Part } from '@google/genai';
|
||||
import type { HistoryItemWithoutId } from '../types.js';
|
||||
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
|
||||
@@ -35,6 +39,8 @@ export function useSessionResume({
|
||||
resumedSessionData,
|
||||
isAuthenticating,
|
||||
}: UseSessionResumeParams) {
|
||||
const [isResuming, setIsResuming] = useState(false);
|
||||
|
||||
// Use refs to avoid dependency chain that causes infinite loop
|
||||
const historyManagerRef = useRef(historyManager);
|
||||
const refreshStaticRef = useRef(refreshStatic);
|
||||
@@ -45,7 +51,7 @@ export function useSessionResume({
|
||||
});
|
||||
|
||||
const loadHistoryForResume = useCallback(
|
||||
(
|
||||
async (
|
||||
uiHistory: HistoryItemWithoutId[],
|
||||
clientHistory: Array<{ role: 'user' | 'model'; parts: Part[] }>,
|
||||
resumedData: ResumedSessionData,
|
||||
@@ -55,17 +61,27 @@ export function useSessionResume({
|
||||
return;
|
||||
}
|
||||
|
||||
// Now that we have the client, load the history into the UI and the client.
|
||||
setQuittingMessages(null);
|
||||
historyManagerRef.current.clearItems();
|
||||
uiHistory.forEach((item, index) => {
|
||||
historyManagerRef.current.addItem(item, index, true);
|
||||
});
|
||||
refreshStaticRef.current(); // Force Static component to re-render with the updated history.
|
||||
setIsResuming(true);
|
||||
try {
|
||||
// Now that we have the client, load the history into the UI and the client.
|
||||
setQuittingMessages(null);
|
||||
historyManagerRef.current.clearItems();
|
||||
uiHistory.forEach((item, index) => {
|
||||
historyManagerRef.current.addItem(item, index, true);
|
||||
});
|
||||
refreshStaticRef.current(); // Force Static component to re-render with the updated history.
|
||||
|
||||
// Give the history to the Gemini client.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
config.getGeminiClient()?.resumeChat(clientHistory, resumedData);
|
||||
// Give the history to the Gemini client.
|
||||
await config.getGeminiClient()?.resumeChat(clientHistory, resumedData);
|
||||
} catch (error) {
|
||||
coreEvents.emitFeedback(
|
||||
'error',
|
||||
'Failed to resume session. Please try again.',
|
||||
error,
|
||||
);
|
||||
} finally {
|
||||
setIsResuming(false);
|
||||
}
|
||||
},
|
||||
[config, isGeminiClientInitialized, setQuittingMessages],
|
||||
);
|
||||
@@ -84,7 +100,7 @@ export function useSessionResume({
|
||||
const historyData = convertSessionToHistoryFormats(
|
||||
resumedSessionData.conversation.messages,
|
||||
);
|
||||
loadHistoryForResume(
|
||||
void loadHistoryForResume(
|
||||
historyData.uiHistory,
|
||||
historyData.clientHistory,
|
||||
resumedSessionData,
|
||||
@@ -97,5 +113,5 @@ export function useSessionResume({
|
||||
loadHistoryForResume,
|
||||
]);
|
||||
|
||||
return { loadHistoryForResume };
|
||||
return { loadHistoryForResume, isResuming };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user