mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-26 04:06:12 +01:00
🎨 feat: Create Avatars of Initials Locally (#1869)
This commit is contained in:
parent
4012dea4ab
commit
5f6d1f3db0
6 changed files with 477 additions and 32 deletions
|
|
@ -27,6 +27,8 @@
|
|||
},
|
||||
"homepage": "https://librechat.ai",
|
||||
"dependencies": {
|
||||
"@dicebear/collection": "^7.0.4",
|
||||
"@dicebear/core": "^7.0.4",
|
||||
"@headlessui/react": "^1.7.13",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
CustomMinimalIcon,
|
||||
} from '~/components/svg';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import useAvatar from '~/hooks/Messages/useAvatar';
|
||||
import { IconProps } from '~/common';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -27,6 +28,8 @@ const Icon: React.FC<IconProps> = (props) => {
|
|||
assistantName,
|
||||
} = props;
|
||||
|
||||
const avatarSrc = useAvatar(user);
|
||||
|
||||
if (isCreatedByUser) {
|
||||
const username = user?.name || 'User';
|
||||
|
||||
|
|
@ -39,14 +42,7 @@ const Icon: React.FC<IconProps> = (props) => {
|
|||
}}
|
||||
className={cn('relative flex items-center justify-center', props.className ?? '')}
|
||||
>
|
||||
<img
|
||||
className="rounded-sm"
|
||||
src={
|
||||
user?.avatar ||
|
||||
`https://api.dicebear.com/6.x/initials/svg?seed=${username}&fontFamily=Verdana&fontSize=36`
|
||||
}
|
||||
alt="avatar"
|
||||
/>
|
||||
<img className="rounded-sm" src={user?.avatar || avatarSrc} alt="avatar" />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { useGetUserBalance, useGetStartupConfig } from 'librechat-data-provider/
|
|||
import type { TConversation } from 'librechat-data-provider';
|
||||
import FilesView from '~/components/Chat/Input/Files/FilesView';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import useAvatar from '~/hooks/Messages/useAvatar';
|
||||
import { ExportModal } from './ExportConversation';
|
||||
import { LinkIcon, GearIcon } from '~/components';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
|
@ -28,9 +29,12 @@ function NavLinks() {
|
|||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [showFiles, setShowFiles] = useRecoilState(store.showFiles);
|
||||
|
||||
let conversation;
|
||||
const activeConvo = useRecoilValue(store.conversationByIndex(0));
|
||||
const globalConvo = useRecoilValue(store.conversation) ?? ({} as TConversation);
|
||||
|
||||
const avatarSrc = useAvatar(user);
|
||||
|
||||
let conversation: TConversation | null | undefined;
|
||||
if (location.state?.from?.pathname.includes('/chat')) {
|
||||
conversation = globalConvo;
|
||||
} else {
|
||||
|
|
@ -68,16 +72,7 @@ function NavLinks() {
|
|||
>
|
||||
<div className="-ml-0.9 -mt-0.8 h-8 w-7 flex-shrink-0">
|
||||
<div className="relative flex">
|
||||
<img
|
||||
className="rounded-full"
|
||||
src={
|
||||
user?.avatar ||
|
||||
`https://api.dicebear.com/6.x/initials/svg?seed=${
|
||||
user?.name || 'User'
|
||||
}&fontFamily=Verdana&fontSize=36`
|
||||
}
|
||||
alt=""
|
||||
/>
|
||||
<img className="rounded-full" src={user?.avatar || avatarSrc} alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export { default as useAvatar } from './useAvatar';
|
||||
export { default as useProgress } from './useProgress';
|
||||
export { default as useMessageHelpers } from './useMessageHelpers';
|
||||
export { default as useMessageScrolling } from './useMessageScrolling';
|
||||
|
|
|
|||
50
client/src/hooks/Messages/useAvatar.ts
Normal file
50
client/src/hooks/Messages/useAvatar.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
import { createAvatar } from '@dicebear/core';
|
||||
import { initials } from '@dicebear/collection';
|
||||
import type { TUser } from 'librechat-data-provider';
|
||||
|
||||
const useAvatar = (user: TUser | undefined) => {
|
||||
const [avatarSrc, setAvatarSrc] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (avatarSrc.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (user?.avatar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user?.username) {
|
||||
return;
|
||||
}
|
||||
|
||||
const generateAvatar = async () => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { username } = user;
|
||||
|
||||
const avatar = createAvatar(initials, {
|
||||
seed: username,
|
||||
fontFamily: ['Verdana'],
|
||||
fontSize: 36,
|
||||
});
|
||||
|
||||
try {
|
||||
const avatarDataUri = await avatar.toDataUri();
|
||||
setAvatarSrc(avatarDataUri);
|
||||
} catch (error) {
|
||||
console.error('Failed to generate avatar:', error);
|
||||
setAvatarSrc('');
|
||||
}
|
||||
};
|
||||
|
||||
generateAvatar();
|
||||
}, [user, avatarSrc.length]);
|
||||
|
||||
return avatarSrc;
|
||||
};
|
||||
|
||||
export default useAvatar;
|
||||
Loading…
Add table
Add a link
Reference in a new issue