diff --git a/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx b/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx index c21c3a8e4e..1679d0285a 100644 --- a/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx +++ b/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx @@ -14,6 +14,7 @@ import { cn } from '~/utils'; interface EndpointItemProps { endpoint: Endpoint; + endpointIndex: number; } const SettingsButton = ({ @@ -54,7 +55,7 @@ const SettingsButton = ({ ); }; -export function EndpointItem({ endpoint }: EndpointItemProps) { +export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) { const localize = useLocalize(); const { agentsMap, @@ -153,8 +154,21 @@ export function EndpointItem({ endpoint }: EndpointItemProps) { ))} {/* Render endpoint models */} {filteredModels - ? renderEndpointModels(endpoint, endpoint.models || [], selectedModel, filteredModels) - : endpoint.models && renderEndpointModels(endpoint, endpoint.models, selectedModel)} + ? renderEndpointModels( + endpoint, + endpoint.models || [], + selectedModel, + filteredModels, + endpointIndex, + ) + : endpoint.models && + renderEndpointModels( + endpoint, + endpoint.models, + selectedModel, + undefined, + endpointIndex, + )} )} @@ -198,7 +212,11 @@ export function EndpointItem({ endpoint }: EndpointItemProps) { } export function renderEndpoints(mappedEndpoints: Endpoint[]) { - return mappedEndpoints.map((endpoint) => ( - + return mappedEndpoints.map((endpoint, index) => ( + )); } diff --git a/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx b/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx index aab5b5889f..cb9d24eb61 100644 --- a/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx +++ b/client/src/components/Chat/Menus/Endpoints/components/EndpointModelItem.tsx @@ -109,7 +109,6 @@ export function EndpointModelItem({ modelId, endpoint, isSelected }: EndpointMod return ( handleSelectModel(endpoint, modelId ?? '')} className="group flex w-full cursor-pointer items-center justify-between rounded-lg px-2 text-sm" > @@ -161,14 +160,16 @@ export function renderEndpointModels( models: Array<{ name: string; isGlobal?: boolean }>, selectedModel: string | null, filteredModels?: string[], + endpointIndex?: number, ) { const modelsToRender = filteredModels || models.map((model) => model.name); + const indexSuffix = endpointIndex != null ? `-${endpointIndex}` : ''; return modelsToRender.map( - (modelId) => + (modelId, modelIndex) => endpoint && ( (-1); + const scrollToBottomRef = useRef(null); const { conversation, @@ -87,8 +88,9 @@ function MessagesViewContent({ classNames="scroll-animation" unmountOnExit={true} appear={true} + nodeRef={scrollToBottomRef} > - + diff --git a/client/src/components/Conversations/Conversations.tsx b/client/src/components/Conversations/Conversations.tsx index 63ee52ee9b..64b804b2d6 100644 --- a/client/src/components/Conversations/Conversations.tsx +++ b/client/src/components/Conversations/Conversations.tsx @@ -250,7 +250,7 @@ const Conversations: FC = ({ if (item.type === 'loading') { return ( - + ); @@ -258,7 +258,7 @@ const Conversations: FC = ({ if (item.type === 'favorites') { return ( - + = ({ if (item.type === 'chats-header') { return ( - + setIsChatsExpanded(!isChatsExpanded)} @@ -285,7 +285,7 @@ const Conversations: FC = ({ // Without favorites: [chats-header, first-header] → index 1 const firstHeaderIndex = shouldShowFavorites ? 2 : 1; return ( - + ); @@ -293,7 +293,7 @@ const Conversations: FC = ({ if (item.type === 'convo') { return ( - + ); diff --git a/client/src/components/Messages/ScrollToBottom.tsx b/client/src/components/Messages/ScrollToBottom.tsx index bb67cfe3df..0b99df0a61 100644 --- a/client/src/components/Messages/ScrollToBottom.tsx +++ b/client/src/components/Messages/ScrollToBottom.tsx @@ -1,12 +1,13 @@ -import React from 'react'; +import { forwardRef } from 'react'; type Props = { scrollHandler: React.MouseEventHandler; }; -export default function ScrollToBottom({ scrollHandler }: Props) { +const ScrollToBottom = forwardRef(({ scrollHandler }, ref) => { return ( ); -} +}); + +ScrollToBottom.displayName = 'ScrollToBottom'; + +export default ScrollToBottom; diff --git a/client/src/components/Nav/Favorites/FavoritesList.tsx b/client/src/components/Nav/Favorites/FavoritesList.tsx index b8af6a99b1..84c0283602 100644 --- a/client/src/components/Nav/Favorites/FavoritesList.tsx +++ b/client/src/components/Nav/Favorites/FavoritesList.tsx @@ -4,14 +4,13 @@ import { LayoutGrid } from 'lucide-react'; import { useDrag, useDrop } from 'react-dnd'; import { Skeleton } from '@librechat/client'; import { useNavigate } from 'react-router-dom'; +import { useQueries } from '@tanstack/react-query'; import { QueryKeys, dataService } from 'librechat-data-provider'; -import { useQueries, useQueryClient } from '@tanstack/react-query'; -import type { InfiniteData } from '@tanstack/react-query'; import type t from 'librechat-data-provider'; import { useFavorites, useLocalize, useShowMarketplace, useNewConvo } from '~/hooks'; +import { useAssistantsMapContext, useAgentsMapContext } from '~/Providers'; import useSelectMention from '~/hooks/Input/useSelectMention'; import { useGetEndpointsQuery } from '~/data-provider'; -import { useAssistantsMapContext } from '~/Providers'; import FavoriteItem from './FavoriteItem'; import store from '~/store'; @@ -121,13 +120,13 @@ export default function FavoritesList({ }) { const navigate = useNavigate(); const localize = useLocalize(); - const queryClient = useQueryClient(); const search = useRecoilValue(store.search); const { favorites, reorderFavorites, isLoading: isFavoritesLoading } = useFavorites(); const showAgentMarketplace = useShowMarketplace(); const { newConversation } = useNewConvo(); const assistantsMap = useAssistantsMapContext(); + const agentsMap = useAgentsMapContext(); const conversation = useRecoilValue(store.conversationByIndex(0)); const { data: endpointsConfig = {} as t.TEndpointsConfig } = useGetEndpointsQuery(); @@ -168,59 +167,56 @@ export default function FavoritesList({ newChatButton?.focus(); }, []); - // Ensure favorites is always an array (could be corrupted in localStorage) const safeFavorites = useMemo(() => (Array.isArray(favorites) ? favorites : []), [favorites]); - const agentIds = safeFavorites.map((f) => f.agentId).filter(Boolean) as string[]; + const allAgentIds = useMemo( + () => safeFavorites.map((f) => f.agentId).filter(Boolean) as string[], + [safeFavorites], + ); - const agentQueries = useQueries({ - queries: agentIds.map((agentId) => ({ + const missingAgentIds = useMemo(() => { + if (agentsMap === undefined) { + return []; + } + return allAgentIds.filter((id) => !agentsMap[id]); + }, [allAgentIds, agentsMap]); + + const missingAgentQueries = useQueries({ + queries: missingAgentIds.map((agentId) => ({ queryKey: [QueryKeys.agent, agentId], queryFn: () => dataService.getAgentById({ agent_id: agentId }), staleTime: 1000 * 60 * 5, + enabled: missingAgentIds.length > 0, })), }); - const isAgentsLoading = agentIds.length > 0 && agentQueries.some((q) => q.isLoading); + const combinedAgentsMap = useMemo(() => { + if (agentsMap === undefined) { + return undefined; + } + const combined: Record = {}; + for (const [key, value] of Object.entries(agentsMap)) { + if (value) { + combined[key] = value; + } + } + missingAgentQueries.forEach((query) => { + if (query.data) { + combined[query.data.id] = query.data; + } + }); + return combined; + }, [agentsMap, missingAgentQueries]); + + const isAgentsLoading = + (allAgentIds.length > 0 && agentsMap === undefined) || + (missingAgentIds.length > 0 && missingAgentQueries.some((q) => q.isLoading)); useEffect(() => { if (!isAgentsLoading && onHeightChange) { onHeightChange(); } }, [isAgentsLoading, onHeightChange]); - const agentsMap = useMemo(() => { - const map: Record = {}; - - const addToMap = (agent: t.Agent) => { - if (agent && agent.id && !map[agent.id]) { - map[agent.id] = agent; - } - }; - - const marketplaceData = queryClient.getQueriesData>([ - QueryKeys.marketplaceAgents, - ]); - marketplaceData.forEach(([_, data]) => { - data?.pages.forEach((page) => { - page.data.forEach(addToMap); - }); - }); - - const agentsListData = queryClient.getQueriesData([QueryKeys.agents]); - agentsListData.forEach(([_, data]) => { - if (data && Array.isArray(data.data)) { - data.data.forEach(addToMap); - } - }); - - agentQueries.forEach((query) => { - if (query.data) { - map[query.data.id] = query.data; - } - }); - - return map; - }, [agentQueries, queryClient]); const draggedFavoritesRef = useRef(safeFavorites); @@ -306,7 +302,7 @@ export default function FavoritesList({ )} {safeFavorites.map((fav, index) => { if (fav.agentId) { - const agent = agentsMap[fav.agentId]; + const agent = combinedAgentsMap?.[fav.agentId]; if (!agent) { return null; } diff --git a/client/src/utils/logger.ts b/client/src/utils/logger.ts index 6bc1d21db6..b025f4926c 100644 --- a/client/src/utils/logger.ts +++ b/client/src/utils/logger.ts @@ -9,7 +9,7 @@ const createLogFunction = ( type?: 'log' | 'warn' | 'error' | 'info' | 'debug' | 'dir', ): LogFunction => { return (...args: unknown[]) => { - if (isDevelopment || isLoggerEnabled) { + if (isLoggerEnabled || (import.meta.env.VITE_ENABLE_LOGGER == null && isDevelopment)) { const tag = typeof args[0] === 'string' ? args[0] : ''; if (shouldLog(tag)) { if (tag && typeof args[1] === 'string' && type === 'error') {