diff --git a/api/server/controllers/FavoritesController.js b/api/server/controllers/FavoritesController.js index cc34cb1e56..0d3bd7f08c 100644 --- a/api/server/controllers/FavoritesController.js +++ b/api/server/controllers/FavoritesController.js @@ -9,7 +9,6 @@ const updateFavoritesController = async (req, res) => { return res.status(400).json({ message: 'Favorites data is required' }); } - // Validate favorites structure if (!Array.isArray(favorites)) { return res.status(400).json({ message: 'Favorites must be an array' }); } diff --git a/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx b/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx index c344133166..cdd88d6799 100644 --- a/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx +++ b/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx @@ -13,7 +13,7 @@ interface EndpointModelItemProps { isSelected: boolean; } -export function EndpointModelItem({ modelId, endpoint }: EndpointModelItemProps) { +export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointModelItemProps) { const { handleSelectModel } = useModelSelectorContext(); const { isFavoriteModel, toggleFavoriteModel, isFavoriteAgent, toggleFavoriteAgent } = useFavorites(); @@ -43,34 +43,42 @@ export function EndpointModelItem({ modelId, endpoint }: EndpointModelItemProps) const handleFavoriteClick = (e: React.MouseEvent) => { e.stopPropagation(); - if (modelId) { - if (isAgent) { - toggleFavoriteAgent(modelId); - } else { - toggleFavoriteModel({ model: modelId, endpoint: endpoint.value }); - } + if (!modelId) { + return; + } + + if (isAgent) { + toggleFavoriteAgent(modelId); + } else { + toggleFavoriteModel({ model: modelId, endpoint: endpoint.value }); } }; const renderAvatar = () => { - if (avatarUrl) { - return ( -
- {modelName -
- ); + const isAgentOrAssistant = + isAgentsEndpoint(endpoint.value) || isAssistantsEndpoint(endpoint.value); + const showEndpointIcon = isAgentOrAssistant && endpoint.icon; + + const getContent = () => { + if (avatarUrl) { + return {modelName; + } + if (showEndpointIcon) { + return endpoint.icon; + } + return null; + }; + + const content = getContent(); + if (!content) { + return null; } - if ( - (isAgentsEndpoint(endpoint.value) || isAssistantsEndpoint(endpoint.value)) && - endpoint.icon - ) { - return ( -
- {endpoint.icon} -
- ); - } - return null; + + return ( +
+ {content} +
+ ); }; return ( @@ -97,6 +105,25 @@ export function EndpointModelItem({ modelId, endpoint }: EndpointModelItemProps) )} + {isSelected && ( +
+ + + +
+ )} ); } diff --git a/client/src/components/Conversations/Conversations.tsx b/client/src/components/Conversations/Conversations.tsx index 4e10f90563..fd23bbfc1d 100644 --- a/client/src/components/Conversations/Conversations.tsx +++ b/client/src/components/Conversations/Conversations.tsx @@ -1,12 +1,12 @@ import { useMemo, memo, type FC, useCallback, useEffect, useRef } from 'react'; import throttle from 'lodash/throttle'; import { ChevronRight } from 'lucide-react'; +import { TConversation } from 'librechat-data-provider'; import { Spinner, useMediaQuery } from '@librechat/client'; import { List, AutoSizer, CellMeasurer, CellMeasurerCache } from 'react-virtualized'; -import { TConversation } from 'librechat-data-provider'; +import FavoritesList from '~/components/Nav/Favorites/FavoritesList'; import { useLocalize, TranslationKeys, useFavorites } from '~/hooks'; import { groupConversationsByDate, cn } from '~/utils'; -import FavoritesList from '~/components/Nav/Favorites/FavoritesList'; import Convo from './Convo'; interface ConversationsProps { @@ -17,7 +17,6 @@ interface ConversationsProps { loadMoreConversations: () => void; isLoading: boolean; isSearchLoading: boolean; - scrollElement?: HTMLElement | null; isChatsExpanded: boolean; setIsChatsExpanded: (expanded: boolean) => void; } @@ -85,7 +84,6 @@ const Conversations: FC = ({ loadMoreConversations, isLoading, isSearchLoading, - scrollElement, isChatsExpanded, setIsChatsExpanded, }) => { @@ -168,7 +166,7 @@ const Conversations: FC = ({ } }, [cache, containerRef]); - // Clear cache when favorites change - use requestAnimationFrame for smoother updates + // Clear cache when favorites change useEffect(() => { const frameId = requestAnimationFrame(() => { clearFavoritesCache(); diff --git a/client/src/components/Nav/AgentMarketplaceButton.tsx b/client/src/components/Nav/AgentMarketplaceButton.tsx index 72f6cc12db..ce34fc236a 100644 --- a/client/src/components/Nav/AgentMarketplaceButton.tsx +++ b/client/src/components/Nav/AgentMarketplaceButton.tsx @@ -1,9 +1,9 @@ -import React, { useCallback, useContext } from 'react'; +import React, { useCallback } from 'react'; import { LayoutGrid } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; -import { PermissionTypes, Permissions } from 'librechat-data-provider'; import { TooltipAnchor, Button } from '@librechat/client'; -import { useLocalize, useHasAccess, AuthContext } from '~/hooks'; +import { PermissionTypes, Permissions } from 'librechat-data-provider'; +import { useLocalize, useHasAccess } from '~/hooks'; interface AgentMarketplaceButtonProps { isSmallScreen?: boolean; @@ -16,7 +16,6 @@ export default function AgentMarketplaceButton({ }: AgentMarketplaceButtonProps) { const navigate = useNavigate(); const localize = useLocalize(); - const authContext = useContext(AuthContext); const hasAccessToAgents = useHasAccess({ permissionType: PermissionTypes.AGENTS, @@ -35,13 +34,7 @@ export default function AgentMarketplaceButton({ } }, [navigate, isSmallScreen, toggleNav]); - // Check if auth is ready (avoid race conditions) - const authReady = - authContext?.isAuthenticated !== undefined && - (authContext?.isAuthenticated === false || authContext?.user !== undefined); - - // Show agent marketplace when marketplace permission is enabled, auth is ready, and user has access to agents - const showAgentMarketplace = authReady && hasAccessToAgents && hasAccessToMarketplace; + const showAgentMarketplace = hasAccessToAgents && hasAccessToMarketplace; if (!showAgentMarketplace) { return null; diff --git a/client/src/components/Nav/Favorites/FavoritesList.tsx b/client/src/components/Nav/Favorites/FavoritesList.tsx index 08b5c96fe7..0f3f34eea2 100644 --- a/client/src/components/Nav/Favorites/FavoritesList.tsx +++ b/client/src/components/Nav/Favorites/FavoritesList.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useCallback, useMemo, useContext, useEffect } from 'react'; +import React, { useRef, useCallback, useMemo, useEffect } from 'react'; import { LayoutGrid } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import { useDrag, useDrop } from 'react-dnd'; @@ -8,11 +8,10 @@ import { QueryKeys, dataService, PermissionTypes, Permissions } from 'librechat- import { useQueries, useQueryClient } from '@tanstack/react-query'; import type { InfiniteData } from '@tanstack/react-query'; import type t from 'librechat-data-provider'; -import { useFavorites, useLocalize, useHasAccess, AuthContext } from '~/hooks'; +import { useFavorites, useLocalize, useHasAccess } from '~/hooks'; import FavoriteItem from './FavoriteItem'; import store from '~/store'; -/** Skeleton placeholder for a favorite item while loading */ const FavoriteItemSkeleton = () => (
@@ -20,7 +19,6 @@ const FavoriteItemSkeleton = () => (
); -/** Skeleton placeholder for the Agent Marketplace button while loading */ const MarketplaceSkeleton = () => (
@@ -121,7 +119,6 @@ export default function FavoritesList({ const navigate = useNavigate(); const localize = useLocalize(); const queryClient = useQueryClient(); - const authContext = useContext(AuthContext); const search = useRecoilValue(store.search); const { favorites, reorderFavorites, isLoading: isFavoritesLoading } = useFavorites(); @@ -135,13 +132,8 @@ export default function FavoritesList({ permission: Permissions.USE, }); - // Check if auth is ready (avoid race conditions) - const authReady = - authContext?.isAuthenticated !== undefined && - (authContext?.isAuthenticated === false || authContext?.user !== undefined); - - // Show agent marketplace when marketplace permission is enabled, auth is ready, and user has access to agents - const showAgentMarketplace = authReady && hasAccessToAgents && hasAccessToMarketplace; + // Show agent marketplace when marketplace permission is enabled, and user has access to agents + const showAgentMarketplace = hasAccessToAgents && hasAccessToMarketplace; const handleAgentMarketplace = useCallback(() => { navigate('/agents'); @@ -160,10 +152,8 @@ 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(); @@ -226,18 +216,14 @@ 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; } - // While favorites are initially loading, show a minimal placeholder - // This prevents the "null to content" jump if (isFavoritesLoading) { return (
diff --git a/client/src/components/Nav/Nav.tsx b/client/src/components/Nav/Nav.tsx index 7da8e87b0c..aa251e9357 100644 --- a/client/src/components/Nav/Nav.tsx +++ b/client/src/components/Nav/Nav.tsx @@ -25,16 +25,9 @@ const AccountSettings = lazy(() => import('./AccountSettings')); const NAV_WIDTH_DESKTOP = '260px'; const NAV_WIDTH_MOBILE = '320px'; -/** Skeleton placeholder for SearchBar while loading */ -const SearchBarSkeleton = memo(({ isSmallScreen }: { isSmallScreen: boolean }) => ( -
- - +const SearchBarSkeleton = memo(() => ( +
+
)); diff --git a/client/src/utils/agents.tsx b/client/src/utils/agents.tsx index 5b1f10016d..e83a94c1aa 100644 --- a/client/src/utils/agents.tsx +++ b/client/src/utils/agents.tsx @@ -114,7 +114,6 @@ export const renderAgentAvatar = ( ); } - // Fallback placeholder with Agent (Feather) icon - consistent with MessageEndpointIcon return (