🎭 refactor: Avatar Loading UX and Fix Initials Rendering Bugs (#9261)

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Marco Beretta 2025-08-25 18:06:00 +02:00 committed by GitHub
parent e559f0f4dc
commit 94426a3cae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 562 additions and 31 deletions

View file

@ -1,10 +1,9 @@
import React, { memo, useState } from 'react';
import { UserIcon } from '@librechat/client';
import { UserIcon, useAvatar } from '@librechat/client';
import type { TUser } from 'librechat-data-provider';
import type { IconProps } from '~/common';
import MessageEndpointIcon from './MessageEndpointIcon';
import { useAuthContext } from '~/hooks/AuthContext';
import useAvatar from '~/hooks/Messages/useAvatar';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';

View file

@ -2,11 +2,10 @@ import { useState, memo } from 'react';
import { useRecoilState } from 'recoil';
import * as Select from '@ariakit/react/select';
import { FileText, LogOut } from 'lucide-react';
import { LinkIcon, GearIcon, DropdownMenuSeparator, UserIcon } from '@librechat/client';
import { LinkIcon, GearIcon, DropdownMenuSeparator, Avatar } from '@librechat/client';
import { useGetStartupConfig, useGetUserBalance } from '~/data-provider';
import FilesView from '~/components/Chat/Input/Files/FilesView';
import { useAuthContext } from '~/hooks/AuthContext';
import useAvatar from '~/hooks/Messages/useAvatar';
import { useLocalize } from '~/hooks';
import Settings from './Settings';
import store from '~/store';
@ -21,9 +20,6 @@ function AccountSettings() {
const [showSettings, setShowSettings] = useState(false);
const [showFiles, setShowFiles] = useRecoilState(store.showFiles);
const avatarSrc = useAvatar(user);
const avatarSeed = user?.avatar || user?.name || user?.username || '';
return (
<Select.SelectProvider>
<Select.Select
@ -33,26 +29,7 @@ function AccountSettings() {
>
<div className="-ml-0.9 -mt-0.8 h-8 w-8 flex-shrink-0">
<div className="relative flex">
{avatarSeed.length === 0 ? (
<div
style={{
backgroundColor: 'rgb(121, 137, 255)',
width: '32px',
height: '32px',
boxShadow: 'rgba(240, 246, 252, 0.1) 0px 0px 0px 1px',
}}
className="relative flex items-center justify-center rounded-full p-1 text-text-primary"
aria-hidden="true"
>
<UserIcon />
</div>
) : (
<img
className="rounded-full"
src={(user?.avatar ?? '') || avatarSrc}
alt={`${user?.name || user?.username || user?.email || ''}'s avatar`}
/>
)}
<Avatar user={user} size={32} />
</div>
</div>
<div

View file

@ -1,4 +1,3 @@
export { default as useAvatar } from './useAvatar';
export { default as useProgress } from './useProgress';
export { default as useAttachments } from './useAttachments';
export { default as useSubmitMessage } from './useSubmitMessage';

View file

@ -1,44 +0,0 @@
import { useMemo } from 'react';
import { createAvatar } from '@dicebear/core';
import { initials } from '@dicebear/collection';
import type { TUser } from 'librechat-data-provider';
const avatarCache: Record<string, string> = {};
const useAvatar = (user: TUser | undefined) => {
return useMemo(() => {
const { username, name } = user ?? {};
const seed = name || username;
if (!seed) {
return '';
}
if (user?.avatar && user?.avatar !== '') {
return user.avatar;
}
if (avatarCache[seed]) {
return avatarCache[seed];
}
const avatar = createAvatar(initials, {
seed,
fontFamily: ['Verdana'],
fontSize: 36,
});
let avatarDataUri = '';
try {
avatarDataUri = avatar.toDataUri();
if (avatarDataUri) {
avatarCache[seed] = avatarDataUri;
}
} catch (error) {
console.error('Failed to generate avatar:', error);
}
return avatarDataUri;
}, [user]);
};
export default useAvatar;