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 (
-
+

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 &&
}
+ {!isLoaded && !hasError && (
+
+ )}
>
);
};
From 0cf71b9e18e251d8ee9e21c357f98e5fbef96cec Mon Sep 17 00:00:00 2001
From: Marco Beretta <81851188+berry-13@users.noreply.github.com>
Date: Sun, 14 Dec 2025 03:11:10 +0100
Subject: [PATCH 3/3] feat: Refactor image caching logic and integrate
getAgentAvatarUrl for improved avatar handling
---
.../src/components/Endpoints/EndpointIcon.tsx | 4 +--
client/src/components/Endpoints/URLIcon.tsx | 28 ++++++-------------
client/src/utils/agents.tsx | 28 ++++++++++---------
3 files changed, 25 insertions(+), 35 deletions(-)
diff --git a/client/src/components/Endpoints/EndpointIcon.tsx b/client/src/components/Endpoints/EndpointIcon.tsx
index 1320f1cc4e..9e4f2bb6a7 100644
--- a/client/src/components/Endpoints/EndpointIcon.tsx
+++ b/client/src/components/Endpoints/EndpointIcon.tsx
@@ -8,7 +8,7 @@ import type {
} from 'librechat-data-provider';
import ConvoIconURL from '~/components/Endpoints/ConvoIconURL';
import MinimalIcon from '~/components/Endpoints/MinimalIcon';
-import { getIconEndpoint } from '~/utils';
+import { getIconEndpoint, getAgentAvatarUrl } from '~/utils';
export default function EndpointIcon({
conversation,
@@ -42,7 +42,7 @@ export default function EndpointIcon({
const agent =
endpoint === EModelEndpoint.agents ? agentsMap?.[conversation?.agent_id ?? ''] : null;
- const agentAvatar = agent?.avatar?.filepath ?? '';
+ const agentAvatar = getAgentAvatarUrl(agent) ?? '';
const iconURL = assistantAvatar || agentAvatar || convoIconURL;
diff --git a/client/src/components/Endpoints/URLIcon.tsx b/client/src/components/Endpoints/URLIcon.tsx
index 0e25ab175b..366289a6e4 100644
--- a/client/src/components/Endpoints/URLIcon.tsx
+++ b/client/src/components/Endpoints/URLIcon.tsx
@@ -2,6 +2,7 @@ import React, { memo, useState, useEffect } from 'react';
import { AlertCircle } from 'lucide-react';
import { Skeleton } from '@librechat/client';
import { icons } from '~/hooks/Endpoint/Icons';
+import { isImageCached } from '~/utils';
export const URLIcon = memo(
({
@@ -20,28 +21,15 @@ 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;
- });
+ const [isLoaded, setIsLoaded] = useState(() => isImageCached(iconURL));
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);
- }
+ if (isImageCached(iconURL)) {
+ setIsLoaded(true);
+ setImageError(false);
+ } else {
+ setIsLoaded(false);
+ setImageError(false);
}
}, [iconURL]);
diff --git a/client/src/utils/agents.tsx b/client/src/utils/agents.tsx
index 5793b127bb..a65577d1f3 100644
--- a/client/src/utils/agents.tsx
+++ b/client/src/utils/agents.tsx
@@ -3,6 +3,19 @@ import { Feather } from 'lucide-react';
import { Skeleton } from '@librechat/client';
import type t from 'librechat-data-provider';
+/**
+ * Checks if an image is already cached in the browser
+ * Returns true if image is complete and has valid dimensions
+ */
+export const isImageCached = (url: string | null | undefined): boolean => {
+ if (typeof window === 'undefined' || !url) {
+ return false;
+ }
+ const img = new Image();
+ img.src = url;
+ return img.complete && img.naturalWidth > 0;
+};
+
/**
* Extracts the avatar URL from an agent's avatar property
* Handles both string and object formats
@@ -32,22 +45,11 @@ const LazyAgentAvatar = ({
alt: string;
imgClass: string;
}) => {
- 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 [isLoaded, setIsLoaded] = useState(() => isImageCached(url));
const [hasError, setHasError] = useState(false);
useEffect(() => {
- // When URL changes, check if new image is cached
- const img = new Image();
- img.src = url;
- if (img.complete && img.naturalWidth > 0) {
+ if (isImageCached(url)) {
setIsLoaded(true);
setHasError(false);
} else {