feat: Optimize cache handling in Conversations and enhance FavoritesList to notify height changes on loading completion
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useMemo, memo, type FC, useCallback, useEffect } from 'react';
|
||||
import { useMemo, memo, type FC, useCallback, useEffect, useRef } from 'react';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { Spinner, useMediaQuery } from '@librechat/client';
|
||||
@@ -122,13 +122,21 @@ const Conversations: FC<ConversationsProps> = ({
|
||||
return items;
|
||||
}, [groupedConversations, isLoading, isChatsExpanded]);
|
||||
|
||||
// Store flattenedItems in a ref for keyMapper to access without recreating cache
|
||||
const flattenedItemsRef = useRef(flattenedItems);
|
||||
flattenedItemsRef.current = flattenedItems;
|
||||
|
||||
// Create a stable cache that doesn't depend on flattenedItems
|
||||
const cache = useMemo(
|
||||
() =>
|
||||
new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: convoHeight,
|
||||
keyMapper: (index) => {
|
||||
const item = flattenedItems[index];
|
||||
const item = flattenedItemsRef.current[index];
|
||||
if (!item) {
|
||||
return `unknown-${index}`;
|
||||
}
|
||||
if (item.type === 'favorites') {
|
||||
return 'favorites';
|
||||
}
|
||||
@@ -136,29 +144,37 @@ const Conversations: FC<ConversationsProps> = ({
|
||||
return 'chats-header';
|
||||
}
|
||||
if (item.type === 'header') {
|
||||
return `header-${index}`;
|
||||
return `header-${item.groupName}`;
|
||||
}
|
||||
if (item.type === 'convo') {
|
||||
return `convo-${item.convo.conversationId}`;
|
||||
}
|
||||
if (item.type === 'loading') {
|
||||
return `loading-${index}`;
|
||||
return 'loading';
|
||||
}
|
||||
return `unknown-${index}`;
|
||||
},
|
||||
}),
|
||||
[flattenedItems, convoHeight],
|
||||
[convoHeight],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Debounced function to clear cache and recompute heights
|
||||
const clearFavoritesCache = useCallback(() => {
|
||||
if (cache) {
|
||||
// Clear the favorites row when favorites change or finish loading
|
||||
cache.clear(0, 0);
|
||||
if (containerRef.current && 'recomputeRowHeights' in containerRef.current) {
|
||||
containerRef.current.recomputeRowHeights(0);
|
||||
}
|
||||
}
|
||||
}, [favorites.length, isFavoritesLoading, cache, containerRef]);
|
||||
}, [cache, containerRef]);
|
||||
|
||||
// Clear cache when favorites change - use requestAnimationFrame for smoother updates
|
||||
useEffect(() => {
|
||||
const frameId = requestAnimationFrame(() => {
|
||||
clearFavoritesCache();
|
||||
});
|
||||
return () => cancelAnimationFrame(frameId);
|
||||
}, [favorites.length, isFavoritesLoading, clearFavoritesCache]);
|
||||
|
||||
const rowRenderer = useCallback(
|
||||
({ index, key, parent, style }) => {
|
||||
@@ -176,7 +192,13 @@ const Conversations: FC<ConversationsProps> = ({
|
||||
}
|
||||
let rendering: JSX.Element;
|
||||
if (item.type === 'favorites') {
|
||||
rendering = <FavoritesList isSmallScreen={isSmallScreen} toggleNav={toggleNav} />;
|
||||
rendering = (
|
||||
<FavoritesList
|
||||
isSmallScreen={isSmallScreen}
|
||||
toggleNav={toggleNav}
|
||||
onHeightChange={clearFavoritesCache}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'chats-header') {
|
||||
rendering = (
|
||||
<button
|
||||
@@ -210,7 +232,7 @@ const Conversations: FC<ConversationsProps> = ({
|
||||
</CellMeasurer>
|
||||
);
|
||||
},
|
||||
[cache, flattenedItems, moveToTop, toggleNav],
|
||||
[cache, flattenedItems, moveToTop, toggleNav, clearFavoritesCache, isSmallScreen],
|
||||
);
|
||||
|
||||
const getRowHeight = useCallback(
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useRef, useCallback, useMemo, useContext, useEffect } from 'reac
|
||||
import { LayoutGrid } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Skeleton } from '@librechat/client';
|
||||
import { QueryKeys, dataService, PermissionTypes, Permissions } from 'librechat-data-provider';
|
||||
import { useQueries, useQueryClient } from '@tanstack/react-query';
|
||||
@@ -9,6 +10,7 @@ import type { InfiniteData } from '@tanstack/react-query';
|
||||
import type t from 'librechat-data-provider';
|
||||
import { useFavorites, useLocalize, useHasAccess, AuthContext } from '~/hooks';
|
||||
import FavoriteItem from './FavoriteItem';
|
||||
import store from '~/store';
|
||||
|
||||
/** Skeleton placeholder for a favorite item while loading */
|
||||
const FavoriteItemSkeleton = () => (
|
||||
@@ -109,14 +111,18 @@ const DraggableFavoriteItem = ({
|
||||
export default function FavoritesList({
|
||||
isSmallScreen,
|
||||
toggleNav,
|
||||
onHeightChange,
|
||||
}: {
|
||||
isSmallScreen?: boolean;
|
||||
toggleNav?: () => void;
|
||||
/** Callback when the list height might have changed (e.g., agents finished loading) */
|
||||
onHeightChange?: () => void;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const localize = useLocalize();
|
||||
const queryClient = useQueryClient();
|
||||
const authContext = useContext(AuthContext);
|
||||
const search = useRecoilValue(store.search);
|
||||
const { favorites, reorderFavorites, isLoading: isFavoritesLoading } = useFavorites();
|
||||
|
||||
const hasAccessToAgents = useHasAccess({
|
||||
@@ -157,6 +163,12 @@ export default function FavoritesList({
|
||||
// Check if any agent queries are still loading (not yet fetched)
|
||||
const isAgentsLoading = agentIds.length > 0 && agentQueries.some((q) => q.isLoading);
|
||||
|
||||
// Notify parent when agents finish loading (height might change)
|
||||
useEffect(() => {
|
||||
if (!isAgentsLoading && onHeightChange) {
|
||||
onHeightChange();
|
||||
}
|
||||
}, [isAgentsLoading, onHeightChange]);
|
||||
const agentsMap = useMemo(() => {
|
||||
const map: Record<string, t.Agent> = {};
|
||||
|
||||
@@ -214,6 +226,11 @@ export default function FavoritesList({
|
||||
draggedFavoritesRef.current = favorites;
|
||||
}, [favorites]);
|
||||
|
||||
// Hide favorites when search is active
|
||||
if (search.query) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If no favorites and no marketplace to show, and not loading, return null
|
||||
if (!isFavoritesLoading && favorites.length === 0 && !showAgentMarketplace) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user