Compare commits

...

5 Commits

View File

@@ -1,4 +1,4 @@
import React, { memo, useState } from 'react';
import React, { memo, useState, useMemo, useRef, useCallback, useEffect } from 'react';
import { UserIcon, useAvatar } from '@librechat/client';
import type { TUser } from 'librechat-data-provider';
import type { IconProps } from '~/common';
@@ -15,26 +15,49 @@ type UserAvatarProps = {
className?: string;
};
/**
* Default avatar component - memoized outside to prevent recreation on every render
*/
const DefaultAvatar = memo(() => (
<div
style={{
backgroundColor: 'rgb(121, 137, 255)',
width: '20px',
height: '20px',
boxShadow: 'rgba(240, 246, 252, 0.1) 0px 0px 0px 1px',
}}
className="relative flex h-9 w-9 items-center justify-center rounded-sm p-1 text-white"
>
<UserIcon />
</div>
));
DefaultAvatar.displayName = 'DefaultAvatar';
const UserAvatar = memo(({ size, user, avatarSrc, username, className }: UserAvatarProps) => {
const [imageError, setImageError] = useState(false);
const imageLoadedRef = useRef(false);
const handleImageError = () => {
const imageSrc = useMemo(() => (user?.avatar ?? '') || avatarSrc, [user?.avatar, avatarSrc]);
/** Reset loaded state and error state if image source changes */
useEffect(() => {
imageLoadedRef.current = false;
setImageError(false);
}, [imageSrc]);
const handleImageError = useCallback(() => {
setImageError(true);
};
imageLoadedRef.current = false;
}, []);
const renderDefaultAvatar = () => (
<div
style={{
backgroundColor: 'rgb(121, 137, 255)',
width: '20px',
height: '20px',
boxShadow: 'rgba(240, 246, 252, 0.1) 0px 0px 0px 1px',
}}
className="relative flex h-9 w-9 items-center justify-center rounded-sm p-1 text-white"
>
<UserIcon />
</div>
);
const handleImageLoad = useCallback(() => {
imageLoadedRef.current = true;
setImageError(false);
}, []);
const hasAvatar = useMemo(() => imageSrc !== '', [imageSrc]);
const showImage = useMemo(() => hasAvatar && !imageError, [hasAvatar, imageError]);
return (
<div
@@ -45,14 +68,14 @@ const UserAvatar = memo(({ size, user, avatarSrc, username, className }: UserAva
}}
className={cn('relative flex items-center justify-center', className ?? '')}
>
{(!(user?.avatar ?? '') && (!(user?.username ?? '') || user?.username.trim() === '')) ||
imageError ? (
renderDefaultAvatar()
{!showImage ? (
<DefaultAvatar />
) : (
<img
className="rounded-full"
src={(user?.avatar ?? '') || avatarSrc}
src={imageSrc}
alt="avatar"
onLoad={handleImageLoad}
onError={handleImageError}
/>
)}
@@ -69,8 +92,12 @@ const Icon: React.FC<IconProps> = memo((props) => {
const avatarSrc = useAvatar(user);
const localize = useLocalize();
const username = useMemo(
() => user?.name ?? user?.username ?? localize('com_nav_user'),
[user?.name, user?.username, localize],
);
if (isCreatedByUser) {
const username = user?.name ?? user?.username ?? localize('com_nav_user');
return (
<UserAvatar
size={size}