From 087ffa972ebab4bded963ca866c3a6e7ee044cbc Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:43:28 +0100 Subject: [PATCH 1/3] feat: Integrate agentsMap into EndpointIcon for agent avatar rendering --- client/src/components/Conversations/Convo.tsx | 3 +++ client/src/components/Endpoints/EndpointIcon.tsx | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/client/src/components/Conversations/Convo.tsx b/client/src/components/Conversations/Convo.tsx index 382eace7a8..f4944ec7bb 100644 --- a/client/src/components/Conversations/Convo.tsx +++ b/client/src/components/Conversations/Convo.tsx @@ -7,6 +7,7 @@ import type { TConversation } from 'librechat-data-provider'; import { useUpdateConversationMutation } from '~/data-provider'; import EndpointIcon from '~/components/Endpoints/EndpointIcon'; import { useNavigateToConvo, useLocalize, useShiftKey } from '~/hooks'; +import { useAgentsMapContext } from '~/Providers/AgentsMapContext'; import { useGetEndpointsQuery } from '~/data-provider'; import { NotificationSeverity } from '~/common'; import { ConvoOptions } from './ConvoOptions'; @@ -25,6 +26,7 @@ export default function Conversation({ conversation, retainView, toggleNav }: Co const params = useParams(); const localize = useLocalize(); const { showToast } = useToastContext(); + const agentsMap = useAgentsMapContext(); const { navigateToConvo } = useNavigateToConvo(); const { data: endpointsConfig } = useGetEndpointsQuery(); const currentConvoId = useMemo(() => params.conversationId, [params.conversationId]); @@ -183,6 +185,7 @@ export default function Conversation({ conversation, retainView, toggleNav }: Co diff --git a/client/src/components/Endpoints/EndpointIcon.tsx b/client/src/components/Endpoints/EndpointIcon.tsx index c32ea12369..1320f1cc4e 100644 --- a/client/src/components/Endpoints/EndpointIcon.tsx +++ b/client/src/components/Endpoints/EndpointIcon.tsx @@ -1,6 +1,7 @@ -import { getEndpointField, isAssistantsEndpoint } from 'librechat-data-provider'; +import { getEndpointField, isAssistantsEndpoint, EModelEndpoint } from 'librechat-data-provider'; import type { TPreset, + TAgentsMap, TConversation, TAssistantsMap, TEndpointsConfig, @@ -14,6 +15,7 @@ export default function EndpointIcon({ endpointsConfig, className = 'mr-0', assistantMap, + agentsMap, context, }: { conversation: TConversation | TPreset | null; @@ -21,6 +23,7 @@ export default function EndpointIcon({ containerClassName?: string; context?: 'message' | 'nav' | 'landing' | 'menu-item'; assistantMap?: TAssistantsMap; + agentsMap?: TAgentsMap; className?: string; size?: number; }) { @@ -37,7 +40,11 @@ export default function EndpointIcon({ const assistantAvatar = (assistant && (assistant.metadata?.avatar as string)) || ''; const assistantName = assistant && (assistant.name ?? ''); - const iconURL = assistantAvatar || convoIconURL; + const agent = + endpoint === EModelEndpoint.agents ? agentsMap?.[conversation?.agent_id ?? ''] : null; + const agentAvatar = agent?.avatar?.filepath ?? ''; + + const iconURL = assistantAvatar || agentAvatar || convoIconURL; if (iconURL && (iconURL.includes('http') || iconURL.startsWith('/images/'))) { return ( From 96ea589881779e9d24c01a4e272878343da450ee Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 12 Dec 2025 22:45:41 +0100 Subject: [PATCH 2/3] feat: Enhance image loading and error handling in URLIcon and LazyAgentAvatar components --- client/src/components/Endpoints/URLIcon.tsx | 45 +++++++++++++++++++-- client/src/utils/agents.tsx | 32 ++++++++++++--- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/client/src/components/Endpoints/URLIcon.tsx b/client/src/components/Endpoints/URLIcon.tsx index ddb62aaded..0e25ab175b 100644 --- a/client/src/components/Endpoints/URLIcon.tsx +++ b/client/src/components/Endpoints/URLIcon.tsx @@ -1,5 +1,6 @@ -import React, { memo, useState } from 'react'; +import React, { memo, useState, useEffect } from 'react'; import { AlertCircle } from 'lucide-react'; +import { Skeleton } from '@librechat/client'; import { icons } from '~/hooks/Endpoint/Icons'; export const URLIcon = memo( @@ -19,9 +20,34 @@ export const URLIcon = memo( endpoint?: string; }) => { const [imageError, setImageError] = useState(false); + const [isLoaded, setIsLoaded] = useState(() => { + // Check if image is already cached + if (typeof window !== 'undefined' && iconURL) { + const img = new Image(); + img.src = iconURL; + return img.complete && img.naturalWidth > 0; + } + return false; + }); + + useEffect(() => { + // When URL changes, check if new image is cached + if (iconURL) { + const img = new Image(); + img.src = iconURL; + if (img.complete && img.naturalWidth > 0) { + setIsLoaded(true); + setImageError(false); + } else { + setIsLoaded(false); + setImageError(false); + } + } + }, [iconURL]); const handleImageError = () => { setImageError(true); + setIsLoaded(false); }; const DefaultIcon: React.ElementType = @@ -46,18 +72,29 @@ export const URLIcon = memo( } return ( -
+
{altName setIsLoaded(true)} onError={handleImageError} - loading="lazy" decoding="async" width={Number(containerStyle.width) || 20} height={Number(containerStyle.height) || 20} /> + {!isLoaded && !imageError && ( +
); }, diff --git a/client/src/utils/agents.tsx b/client/src/utils/agents.tsx index e83a94c1aa..5793b127bb 100644 --- a/client/src/utils/agents.tsx +++ b/client/src/utils/agents.tsx @@ -32,10 +32,28 @@ const LazyAgentAvatar = ({ alt: string; imgClass: string; }) => { - const [isLoaded, setIsLoaded] = useState(false); + const [isLoaded, setIsLoaded] = useState(() => { + // Check if image is already cached by creating a test image + if (typeof window !== 'undefined') { + const img = new Image(); + img.src = url; + return img.complete && img.naturalWidth > 0; + } + return false; + }); + const [hasError, setHasError] = useState(false); useEffect(() => { - setIsLoaded(false); + // When URL changes, check if new image is cached + const img = new Image(); + img.src = url; + if (img.complete && img.naturalWidth > 0) { + setIsLoaded(true); + setHasError(false); + } else { + setIsLoaded(false); + setHasError(false); + } }, [url]); return ( @@ -44,15 +62,19 @@ const LazyAgentAvatar = ({ src={url} alt={alt} className={imgClass} - loading="lazy" onLoad={() => setIsLoaded(true)} - onError={() => setIsLoaded(false)} + onError={() => { + setIsLoaded(false); + setHasError(true); + }} style={{ opacity: isLoaded ? 1 : 0, transition: 'opacity 0.2s ease-in-out', }} /> - {!isLoaded &&