refactor(ui): Optimize rendering performance (#8239)

This commit is contained in:
Gal Zahavi
2025-09-17 15:37:13 -07:00
committed by GitHub
parent d54cdd8802
commit 6756a8b8a9
13 changed files with 499 additions and 85 deletions

View File

@@ -114,7 +114,7 @@ vi.mock('./useStateAndRef.js', () => ({
}
ref.current = val;
});
return [ref, setVal];
return [val, ref, setVal];
}),
}));
@@ -2216,6 +2216,72 @@ describe('useGeminiStream', () => {
});
});
it('should memoize pendingHistoryItems', () => {
mockUseReactToolScheduler.mockReturnValue([
[],
mockScheduleToolCalls,
mockCancelAllToolCalls,
mockMarkToolsAsSubmitted,
]);
const { result, rerender } = renderHook(() =>
useGeminiStream(
mockConfig.getGeminiClient(),
[],
mockAddItem,
mockConfig,
mockLoadedSettings,
mockOnDebugMessage,
mockHandleSlashCommand,
false,
() => 'vscode' as EditorType,
() => {},
() => Promise.resolve(),
false,
() => {},
() => {},
() => {},
() => {},
80,
24,
),
);
const firstResult = result.current.pendingHistoryItems;
rerender();
const secondResult = result.current.pendingHistoryItems;
expect(firstResult).toStrictEqual(secondResult);
const newToolCalls: TrackedToolCall[] = [
{
request: { callId: 'call1', name: 'tool1', args: {} },
status: 'executing',
tool: {
name: 'tool1',
displayName: 'tool1',
description: 'desc1',
build: vi.fn(),
},
invocation: {
getDescription: () => 'Mock description',
},
} as unknown as TrackedExecutingToolCall,
];
mockUseReactToolScheduler.mockReturnValue([
newToolCalls,
mockScheduleToolCalls,
mockCancelAllToolCalls,
mockMarkToolsAsSubmitted,
]);
rerender();
const thirdResult = result.current.pendingHistoryItems;
expect(thirdResult).not.toStrictEqual(secondResult);
});
it('should reset thought to null when user cancels', async () => {
// Mock a stream that yields a thought then gets cancelled
mockSendMessageStream.mockReturnValue(

View File

@@ -111,7 +111,7 @@ export const useGeminiStream = (
const turnCancelledRef = useRef(false);
const [isResponding, setIsResponding] = useState<boolean>(false);
const [thought, setThought] = useState<ThoughtSummary | null>(null);
const [pendingHistoryItemRef, setPendingHistoryItem] =
const [pendingHistoryItem, pendingHistoryItemRef, setPendingHistoryItem] =
useStateAndRef<HistoryItemWithoutId | null>(null);
const processedMemoryToolsRef = useRef<Set<string>>(new Set());
const { startNewPrompt, getPromptCount } = useSessionStats();
@@ -1015,10 +1015,13 @@ export const useGeminiStream = (
],
);
const pendingHistoryItems = [
pendingHistoryItemRef.current,
pendingToolCallGroupDisplay,
].filter((i) => i !== undefined && i !== null);
const pendingHistoryItems = useMemo(
() =>
[pendingHistoryItem, pendingToolCallGroupDisplay].filter(
(i) => i !== undefined && i !== null,
),
[pendingHistoryItem, pendingToolCallGroupDisplay],
);
useEffect(() => {
const saveRestorableToolCalls = async () => {

View File

@@ -15,7 +15,7 @@ export const useStateAndRef = <
>(
initialValue: T,
) => {
const [_, setState] = React.useState<T>(initialValue);
const [state, setState] = React.useState<T>(initialValue);
const ref = React.useRef<T>(initialValue);
const setStateInternal = React.useCallback<typeof setState>(
@@ -32,5 +32,5 @@ export const useStateAndRef = <
[],
);
return [ref, setStateInternal] as const;
return [state, ref, setStateInternal] as const;
};