feat: Enhance image loading and error handling in URLIcon and LazyAgentAvatar components

This commit is contained in:
Marco Beretta 2025-12-12 22:45:41 +01:00
parent 087ffa972e
commit 96ea589881
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
2 changed files with 68 additions and 9 deletions

View file

@ -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 (
<div className={className} style={containerStyle}>
<div className={`${className} relative`} style={containerStyle}>
<img
src={iconURL}
alt={altName ?? 'Icon'}
style={imageStyle}
style={{
...imageStyle,
opacity: isLoaded ? 1 : 0,
transition: 'opacity 0.2s ease-in-out',
}}
className="object-cover"
onLoad={() => setIsLoaded(true)}
onError={handleImageError}
loading="lazy"
decoding="async"
width={Number(containerStyle.width) || 20}
height={Number(containerStyle.height) || 20}
/>
{!isLoaded && !imageError && (
<Skeleton
className="absolute inset-0 rounded-full"
style={{ width: containerStyle.width, height: containerStyle.height }}
aria-hidden="true"
/>
)}
</div>
);
},

View file

@ -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 && <Skeleton className="absolute inset-0 rounded-full" aria-hidden="true" />}
{!isLoaded && !hasError && (
<Skeleton className="absolute inset-0 rounded-full" aria-hidden="true" />
)}
</>
);
};