mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 09:20:15 +01:00
📌 feat: Pin Agents and Models in the Sidebar (#10634)
* 🪦 refactor: Remove Legacy Code (#10533) * 🗑️ chore: Remove unused Legacy Provider clients and related helpers * Deleted OpenAIClient and GoogleClient files along with their associated tests. * Removed references to these clients in the clients index file. * Cleaned up typedefs by removing the OpenAISpecClient export. * Updated chat controllers to use the OpenAI SDK directly instead of the removed client classes. * chore/remove-openapi-specs * 🗑️ chore: Remove unused mergeSort and misc utility functions * Deleted mergeSort.js and misc.js files as they are no longer needed. * Removed references to cleanUpPrimaryKeyValue in messages.js and adjusted related logic. * Updated mongoMeili.ts to eliminate local implementations of removed functions. * chore: remove legacy endpoints * chore: remove all plugins endpoint related code * chore: remove unused prompt handling code and clean up imports * Deleted handleInputs.js and instructions.js files as they are no longer needed. * Removed references to these files in the prompts index.js. * Updated docker-compose.yml to simplify reverse proxy configuration. * chore: remove unused LightningIcon import from Icons.tsx * chore: clean up translation.json by removing deprecated and unused keys * chore: update Jest configuration and remove unused mock file * Simplified the setupFiles array in jest.config.js by removing the fetchEventSource mock. * Deleted the fetchEventSource.js mock file as it is no longer needed. * fix: simplify endpoint type check in Landing and ConversationStarters components * Updated the endpoint type check to use strict equality for better clarity and performance. * Ensured consistency in the handling of the azureOpenAI endpoint across both components. * chore: remove unused dependencies from package.json and package-lock.json * chore: remove legacy EditController, associated routes and imports * chore: update banResponse logic to refine request handling for banned users * chore: remove unused validateEndpoint middleware and its references * chore: remove unused 'res' parameter from initializeClient in multiple endpoint files * chore: remove unused 'isSmallScreen' prop from BookmarkNav and NewChat components; clean up imports in ArchivedChatsTable and useSetIndexOptions hooks; enhance localization in PromptVersions * chore: remove unused import of Constants and TMessage from MobileNav; retain only necessary QueryKeys import * chore: remove unused TResPlugin type and related references; clean up imports in types and schemas * 🪦 refactor: Remove Legacy Code (#10533) * 🗑️ chore: Remove unused Legacy Provider clients and related helpers * Deleted OpenAIClient and GoogleClient files along with their associated tests. * Removed references to these clients in the clients index file. * Cleaned up typedefs by removing the OpenAISpecClient export. * Updated chat controllers to use the OpenAI SDK directly instead of the removed client classes. * chore/remove-openapi-specs * 🗑️ chore: Remove unused mergeSort and misc utility functions * Deleted mergeSort.js and misc.js files as they are no longer needed. * Removed references to cleanUpPrimaryKeyValue in messages.js and adjusted related logic. * Updated mongoMeili.ts to eliminate local implementations of removed functions. * chore: remove legacy endpoints * chore: remove all plugins endpoint related code * chore: remove unused prompt handling code and clean up imports * Deleted handleInputs.js and instructions.js files as they are no longer needed. * Removed references to these files in the prompts index.js. * Updated docker-compose.yml to simplify reverse proxy configuration. * chore: remove unused LightningIcon import from Icons.tsx * chore: clean up translation.json by removing deprecated and unused keys * chore: update Jest configuration and remove unused mock file * Simplified the setupFiles array in jest.config.js by removing the fetchEventSource mock. * Deleted the fetchEventSource.js mock file as it is no longer needed. * fix: simplify endpoint type check in Landing and ConversationStarters components * Updated the endpoint type check to use strict equality for better clarity and performance. * Ensured consistency in the handling of the azureOpenAI endpoint across both components. * chore: remove unused dependencies from package.json and package-lock.json * chore: remove legacy EditController, associated routes and imports * chore: update banResponse logic to refine request handling for banned users * chore: remove unused validateEndpoint middleware and its references * chore: remove unused 'res' parameter from initializeClient in multiple endpoint files * chore: remove unused 'isSmallScreen' prop from BookmarkNav and NewChat components; clean up imports in ArchivedChatsTable and useSetIndexOptions hooks; enhance localization in PromptVersions * chore: remove unused import of Constants and TMessage from MobileNav; retain only necessary QueryKeys import * chore: remove unused TResPlugin type and related references; clean up imports in types and schemas * 📦 chore: Bump Express.js to v5 (#10671) * chore: update express to version 5.1.0 in package.json * chore: update express-rate-limit to version 8.2.1 in package.json and package-lock.json * fix: Enhance server startup error handling in experimental and index files * Added error handling for server startup in both experimental.js and index.js to log errors and exit the process if the server fails to start. * Updated comments in openidStrategy.js to clarify the purpose of the CustomOpenIDStrategy class and its relation to Express version changes. * chore: Implement rate limiting for all POST routes excluding /speech, required for express v5 * Added middleware to apply IP and user rate limiters to all POST requests, ensuring that the /speech route remains unaffected. * Enhanced code clarity with comments explaining the new rate limiting logic. * chore: Enable writable req.query for mongoSanitize compatibility in Express 5 * chore: Ensure req.body exists in multiple middleware and route files for Express 5 compatibility * 🗣 feat: MCP Status Accessibility Improvements (#10738) * feat: make MultiSelect highlight same opacity as other focus highlights in app * feat: add better screenreader announcements for mcp server and variable states * feat: memoize fullTitle calculation * 🪨 feat: Add PROXY support for AWS Bedrock endpoints (#8871) * feat: added PROXY support for AWS Bedrock endpoint * chore: explicit install of new packages required for bedrock proxy --------- Co-authored-by: Danny Avila <danny@librechat.ai> * ✨ feat: Implement Favorites functionality with controllers, hooks, and UI components * ✨ feat: Refactor Favorites functionality to support new data structure and enhance UI interactions * ✨ feat: Add endpoint to new conversation for agent favorites * ✨ feat: Enhance Conversations and Favorites components with expanded functionality and improved UI interactions * ✨ feat: Remove 'Pinned' label from UI translations for cleaner interface * feat: clean up comments and improve code readability in favorites and agent components; bump @librechat/data-schemas to 0.0.24 * ✨ feat: Enhance favorites management with validation, update data structure, and improve UI interactions * ✨ feat: Simplify rendering logic in EndpointModelItem and optimize useEffect dependencies in Conversations component * ✨ test: Update favorites mock implementation and improve button focus styles in AgentDetail tests * ✨ feat: Enhance favorites management by adding loading and error states, and refactor related hooks and components * ✨ feat: Add loading skeletons for favorites while agents are being fetched * ✨ feat: Improve loading experience in FavoritesList by adding skeleton placeholders for favorites and marketplace * feat: Optimize cache handling in Conversations and enhance FavoritesList to notify height changes on loading completion * ✨ feat: Add loading skeleton for SearchBar in Nav component and update agent avatar fallback icon to Feather * feat: Refactor FavoritesController validation, streamline ModelSelector component, and enhance EndpointModelItem with selection state * feat: Adjust padding in Conversations and FavoritesList components for improved layout consistency * feat: Refactor FavoritesController to use model methods for user updates and retrieval * feat: Enhance Favorites functionality with validation, cleanup, and improved error handling * tests: Update AgentCard and agent utilities to use Feather icon fallback instead of Bot icon * refactor: Remove collapsible animation styles from CSS * feat: Migrate favorites state management from Recoil to Jotai * fix: Correct type definition in useGetFavoritesQuery and ensure useFavorites is exported * refactor: Simplify AuthField component by removing TooltipAnchor and directly rendering Label * fix: Ensure favorites are always an array and update references in FavoritesList * style: Update Conversation component styles for improved UI consistency * feat: re-integrate AuthContext to manage agent marketplace visibility based on authentication state * fix: Improve optimistic updates in favorites mutation handling * feat: Implement error handling for favorites limit and consolidate marketplace access logic * fix: package-lock --------- Co-authored-by: Danny Avila <danny@librechat.ai> Co-authored-by: Dustin Healy <54083382+dustinhealy@users.noreply.github.com> Co-authored-by: Arthur Barrett <abarrett@fas.harvard.edu>
This commit is contained in:
parent
cea4f57a73
commit
b6e5ea5d33
31 changed files with 1310 additions and 184 deletions
|
|
@ -1,20 +1,26 @@
|
|||
import { useMemo, memo, type FC, useCallback } from 'react';
|
||||
import { useMemo, memo, type FC, useCallback, useEffect, useRef } from 'react';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Spinner, useMediaQuery } from '@librechat/client';
|
||||
import { List, AutoSizer, CellMeasurer, CellMeasurerCache } from 'react-virtualized';
|
||||
import { TConversation } from 'librechat-data-provider';
|
||||
import { useLocalize, TranslationKeys } from '~/hooks';
|
||||
import { groupConversationsByDate } from '~/utils';
|
||||
import { useLocalize, TranslationKeys, useFavorites, useShowMarketplace } from '~/hooks';
|
||||
import FavoritesList from '~/components/Nav/Favorites/FavoritesList';
|
||||
import { groupConversationsByDate, cn } from '~/utils';
|
||||
import Convo from './Convo';
|
||||
import store from '~/store';
|
||||
|
||||
interface ConversationsProps {
|
||||
conversations: Array<TConversation | null>;
|
||||
moveToTop: () => void;
|
||||
toggleNav: () => void;
|
||||
containerRef: React.RefObject<HTMLDivElement | List>;
|
||||
containerRef: React.RefObject<List>;
|
||||
loadMoreConversations: () => void;
|
||||
isLoading: boolean;
|
||||
isSearchLoading: boolean;
|
||||
isChatsExpanded: boolean;
|
||||
setIsChatsExpanded: (expanded: boolean) => void;
|
||||
}
|
||||
|
||||
const LoadingSpinner = memo(() => {
|
||||
|
|
@ -30,10 +36,13 @@ const LoadingSpinner = memo(() => {
|
|||
|
||||
LoadingSpinner.displayName = 'LoadingSpinner';
|
||||
|
||||
const DateLabel: FC<{ groupName: string }> = memo(({ groupName }) => {
|
||||
const DateLabel: FC<{ groupName: string; isFirst?: boolean }> = memo(({ groupName, isFirst }) => {
|
||||
const localize = useLocalize();
|
||||
return (
|
||||
<h2 className="mt-2 pl-2 pt-1 text-text-secondary" style={{ fontSize: '0.7rem' }}>
|
||||
<h2
|
||||
className={cn('pl-1 pt-1 text-text-secondary', isFirst === true ? 'mt-0' : 'mt-2')}
|
||||
style={{ fontSize: '0.7rem' }}
|
||||
>
|
||||
{localize(groupName as TranslationKeys) || groupName}
|
||||
</h2>
|
||||
);
|
||||
|
|
@ -42,6 +51,8 @@ const DateLabel: FC<{ groupName: string }> = memo(({ groupName }) => {
|
|||
DateLabel.displayName = 'DateLabel';
|
||||
|
||||
type FlattenedItem =
|
||||
| { type: 'favorites' }
|
||||
| { type: 'chats-header' }
|
||||
| { type: 'header'; groupName: string }
|
||||
| { type: 'convo'; convo: TConversation }
|
||||
| { type: 'loading' };
|
||||
|
|
@ -75,10 +86,19 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
loadMoreConversations,
|
||||
isLoading,
|
||||
isSearchLoading,
|
||||
isChatsExpanded,
|
||||
setIsChatsExpanded,
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const search = useRecoilValue(store.search);
|
||||
const { favorites, isLoading: isFavoritesLoading } = useFavorites();
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
const convoHeight = isSmallScreen ? 44 : 34;
|
||||
const showAgentMarketplace = useShowMarketplace();
|
||||
|
||||
// Determine if FavoritesList will render content
|
||||
const shouldShowFavorites =
|
||||
!search.query && (isFavoritesLoading || favorites.length > 0 || showAgentMarketplace);
|
||||
|
||||
const filteredConversations = useMemo(
|
||||
() => rawConversations.filter(Boolean) as TConversation[],
|
||||
|
|
@ -92,39 +112,79 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
|
||||
const flattenedItems = useMemo(() => {
|
||||
const items: FlattenedItem[] = [];
|
||||
groupedConversations.forEach(([groupName, convos]) => {
|
||||
items.push({ type: 'header', groupName });
|
||||
items.push(...convos.map((convo) => ({ type: 'convo' as const, convo })));
|
||||
});
|
||||
// Only include favorites row if FavoritesList will render content
|
||||
if (shouldShowFavorites) {
|
||||
items.push({ type: 'favorites' });
|
||||
}
|
||||
items.push({ type: 'chats-header' });
|
||||
|
||||
if (isLoading) {
|
||||
items.push({ type: 'loading' } as any);
|
||||
if (isChatsExpanded) {
|
||||
groupedConversations.forEach(([groupName, convos]) => {
|
||||
items.push({ type: 'header', groupName });
|
||||
items.push(...convos.map((convo) => ({ type: 'convo' as const, convo })));
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
items.push({ type: 'loading' } as any);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}, [groupedConversations, isLoading]);
|
||||
}, [groupedConversations, isLoading, isChatsExpanded, shouldShowFavorites]);
|
||||
|
||||
// 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';
|
||||
}
|
||||
if (item.type === 'chats-header') {
|
||||
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],
|
||||
);
|
||||
|
||||
// Debounced function to clear cache and recompute heights
|
||||
const clearFavoritesCache = useCallback(() => {
|
||||
if (cache) {
|
||||
cache.clear(0, 0);
|
||||
if (containerRef.current && 'recomputeRowHeights' in containerRef.current) {
|
||||
containerRef.current.recomputeRowHeights(0);
|
||||
}
|
||||
}
|
||||
}, [cache, containerRef]);
|
||||
|
||||
// Clear cache when favorites change
|
||||
useEffect(() => {
|
||||
const frameId = requestAnimationFrame(() => {
|
||||
clearFavoritesCache();
|
||||
});
|
||||
return () => cancelAnimationFrame(frameId);
|
||||
}, [favorites.length, isFavoritesLoading, clearFavoritesCache]);
|
||||
|
||||
const rowRenderer = useCallback(
|
||||
({ index, key, parent, style }) => {
|
||||
const item = flattenedItems[index];
|
||||
|
|
@ -140,8 +200,36 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
);
|
||||
}
|
||||
let rendering: JSX.Element;
|
||||
if (item.type === 'header') {
|
||||
rendering = <DateLabel groupName={item.groupName} />;
|
||||
if (item.type === 'favorites') {
|
||||
rendering = (
|
||||
<FavoritesList
|
||||
isSmallScreen={isSmallScreen}
|
||||
toggleNav={toggleNav}
|
||||
onHeightChange={clearFavoritesCache}
|
||||
/>
|
||||
);
|
||||
} else if (item.type === 'chats-header') {
|
||||
rendering = (
|
||||
<button
|
||||
onClick={() => setIsChatsExpanded(!isChatsExpanded)}
|
||||
className="group flex w-full items-center justify-between px-1 py-2 text-xs font-bold text-text-secondary"
|
||||
type="button"
|
||||
>
|
||||
<span className="select-none">{localize('com_ui_chats')}</span>
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
'h-3 w-3 transition-transform duration-200',
|
||||
isChatsExpanded ? 'rotate-90' : '',
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
} else if (item.type === 'header') {
|
||||
// First date header index depends on whether favorites row is included
|
||||
// With favorites: [favorites, chats-header, first-header] → index 2
|
||||
// Without favorites: [chats-header, first-header] → index 1
|
||||
const firstHeaderIndex = shouldShowFavorites ? 2 : 1;
|
||||
rendering = <DateLabel groupName={item.groupName} isFirst={index === firstHeaderIndex} />;
|
||||
} else if (item.type === 'convo') {
|
||||
rendering = (
|
||||
<MemoizedConvo conversation={item.convo} retainView={moveToTop} toggleNav={toggleNav} />
|
||||
|
|
@ -150,14 +238,25 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
return (
|
||||
<CellMeasurer cache={cache} columnIndex={0} key={key} parent={parent} rowIndex={index}>
|
||||
{({ registerChild }) => (
|
||||
<div ref={registerChild} style={style} className="px-2">
|
||||
<div ref={registerChild} style={style} className="">
|
||||
{rendering}
|
||||
</div>
|
||||
)}
|
||||
</CellMeasurer>
|
||||
);
|
||||
},
|
||||
[cache, flattenedItems, moveToTop, toggleNav],
|
||||
[
|
||||
cache,
|
||||
flattenedItems,
|
||||
moveToTop,
|
||||
toggleNav,
|
||||
clearFavoritesCache,
|
||||
isSmallScreen,
|
||||
isChatsExpanded,
|
||||
localize,
|
||||
setIsChatsExpanded,
|
||||
shouldShowFavorites,
|
||||
],
|
||||
);
|
||||
|
||||
const getRowHeight = useCallback(
|
||||
|
|
@ -180,7 +279,7 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full flex-col pb-2 text-sm text-text-primary">
|
||||
<div className="relative flex h-full min-h-0 flex-col pb-2 text-sm text-text-primary">
|
||||
{isSearchLoading ? (
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<Spinner className="text-text-primary" />
|
||||
|
|
@ -191,7 +290,7 @@ const Conversations: FC<ConversationsProps> = ({
|
|||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
<List
|
||||
ref={containerRef as React.RefObject<List>}
|
||||
ref={containerRef}
|
||||
width={width}
|
||||
height={height}
|
||||
deferredMeasurementCache={cache}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue