mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 02:10:15 +01:00
feat: Enhance image loading and error handling in URLIcon and LazyAgentAvatar components
This commit is contained in:
parent
087ffa972e
commit
96ea589881
2 changed files with 68 additions and 9 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { memo, useState } from 'react';
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
import { AlertCircle } from 'lucide-react';
|
import { AlertCircle } from 'lucide-react';
|
||||||
|
import { Skeleton } from '@librechat/client';
|
||||||
import { icons } from '~/hooks/Endpoint/Icons';
|
import { icons } from '~/hooks/Endpoint/Icons';
|
||||||
|
|
||||||
export const URLIcon = memo(
|
export const URLIcon = memo(
|
||||||
|
|
@ -19,9 +20,34 @@ export const URLIcon = memo(
|
||||||
endpoint?: string;
|
endpoint?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [imageError, setImageError] = useState(false);
|
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 = () => {
|
const handleImageError = () => {
|
||||||
setImageError(true);
|
setImageError(true);
|
||||||
|
setIsLoaded(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DefaultIcon: React.ElementType =
|
const DefaultIcon: React.ElementType =
|
||||||
|
|
@ -46,18 +72,29 @@ export const URLIcon = memo(
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} style={containerStyle}>
|
<div className={`${className} relative`} style={containerStyle}>
|
||||||
<img
|
<img
|
||||||
src={iconURL}
|
src={iconURL}
|
||||||
alt={altName ?? 'Icon'}
|
alt={altName ?? 'Icon'}
|
||||||
style={imageStyle}
|
style={{
|
||||||
|
...imageStyle,
|
||||||
|
opacity: isLoaded ? 1 : 0,
|
||||||
|
transition: 'opacity 0.2s ease-in-out',
|
||||||
|
}}
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
|
onLoad={() => setIsLoaded(true)}
|
||||||
onError={handleImageError}
|
onError={handleImageError}
|
||||||
loading="lazy"
|
|
||||||
decoding="async"
|
decoding="async"
|
||||||
width={Number(containerStyle.width) || 20}
|
width={Number(containerStyle.width) || 20}
|
||||||
height={Number(containerStyle.height) || 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>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,28 @@ const LazyAgentAvatar = ({
|
||||||
alt: string;
|
alt: string;
|
||||||
imgClass: 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(() => {
|
useEffect(() => {
|
||||||
|
// 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);
|
setIsLoaded(false);
|
||||||
|
setHasError(false);
|
||||||
|
}
|
||||||
}, [url]);
|
}, [url]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -44,15 +62,19 @@ const LazyAgentAvatar = ({
|
||||||
src={url}
|
src={url}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
className={imgClass}
|
className={imgClass}
|
||||||
loading="lazy"
|
|
||||||
onLoad={() => setIsLoaded(true)}
|
onLoad={() => setIsLoaded(true)}
|
||||||
onError={() => setIsLoaded(false)}
|
onError={() => {
|
||||||
|
setIsLoaded(false);
|
||||||
|
setHasError(true);
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
opacity: isLoaded ? 1 : 0,
|
opacity: isLoaded ? 1 : 0,
|
||||||
transition: 'opacity 0.2s ease-in-out',
|
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" />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue