From 94426a3cae107ffcd2b8707c4bd3d0861694fc5b Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Mon, 25 Aug 2025 18:06:00 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=AD=20refactor:=20Avatar=20Loading=20U?= =?UTF-8?q?X=20and=20Fix=20Initials=20Rendering=20Bugs=20(#9261)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Danny Avila --- client/src/components/Endpoints/Icon.tsx | 3 +- client/src/components/Nav/AccountSettings.tsx | 27 +- client/src/hooks/Messages/index.ts | 1 - package-lock.json | 452 +++++++++++++++++- packages/client/package.json | 6 +- packages/client/src/components/Avatar.tsx | 102 ++++ packages/client/src/components/index.ts | 1 + packages/client/src/hooks/index.ts | 1 + .../client/src/hooks}/useAvatar.ts | 0 9 files changed, 562 insertions(+), 31 deletions(-) create mode 100644 packages/client/src/components/Avatar.tsx rename {client/src/hooks/Messages => packages/client/src/hooks}/useAvatar.ts (100%) diff --git a/client/src/components/Endpoints/Icon.tsx b/client/src/components/Endpoints/Icon.tsx index acc6027697..3256145bfb 100644 --- a/client/src/components/Endpoints/Icon.tsx +++ b/client/src/components/Endpoints/Icon.tsx @@ -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'; diff --git a/client/src/components/Nav/AccountSettings.tsx b/client/src/components/Nav/AccountSettings.tsx index 9b1f4ea92e..6bf48e6fb2 100644 --- a/client/src/components/Nav/AccountSettings.tsx +++ b/client/src/components/Nav/AccountSettings.tsx @@ -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 (
- {avatarSeed.length === 0 ? ( - - ) : ( - {`${user?.name - )} +
=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/adventurer-neutral": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/adventurer-neutral/-/adventurer-neutral-9.2.4.tgz", + "integrity": "sha512-I9IrB4ZYbUHSOUpWoUbfX3vG8FrjcW8htoQ4bEOR7TYOKKE11Mo1nrGMuHZ7GPfwN0CQeK1YVJhWqLTmtYn7Pg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/avataaars": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/avataaars/-/avataaars-9.2.4.tgz", + "integrity": "sha512-QKNBtA/1QGEzR+JjS4XQyrFHYGbzdOp0oa6gjhGhUDrMegDFS8uyjdRfDQsFTebVkyLWjgBQKZEiDqKqHptB6A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/avataaars-neutral": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/avataaars-neutral/-/avataaars-neutral-9.2.4.tgz", + "integrity": "sha512-HtBvA7elRv50QTOOsBdtYB1GVimCpGEDlDgWsu1snL5Z3d1+3dIESoXQd3mXVvKTVT8Z9ciA4TEaF09WfxDjAA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/big-ears": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/big-ears/-/big-ears-9.2.4.tgz", + "integrity": "sha512-U33tbh7Io6wG6ViUMN5fkWPER7hPKMaPPaYgafaYQlCT4E7QPKF2u8X1XGag3jCKm0uf4SLXfuZ8v+YONcHmNQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/big-ears-neutral": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/big-ears-neutral/-/big-ears-neutral-9.2.4.tgz", + "integrity": "sha512-pPjYu80zMFl43A9sa5+tAKPkhp4n9nd7eN878IOrA1HAowh/XePh5JN8PTkNFS9eM+rnN9m8WX08XYFe30kLYw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/big-smile": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/big-smile/-/big-smile-9.2.4.tgz", + "integrity": "sha512-zeEfXOOXy7j9tfkPLzfQdLBPyQsctBetTdEfKRArc1k3RUliNPxfJG9j88+cXQC6GXrVW2pcT2X50NSPtugCFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/bottts": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/bottts/-/bottts-9.2.4.tgz", + "integrity": "sha512-4CTqrnVg+NQm6lZ4UuCJish8gGWe8EqSJrzvHQRO5TEyAKjYxbTdVqejpkycG1xkawha4FfxsYgtlSx7UwoVMw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/bottts-neutral": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/bottts-neutral/-/bottts-neutral-9.2.4.tgz", + "integrity": "sha512-eMVdofdD/udHsKIaeWEXShDRtiwk7vp4FjY7l0f79vIzfhkIsXKEhPcnvHKOl/yoArlDVS3Uhgjj0crWTO9RJA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/collection": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/collection/-/collection-9.2.4.tgz", + "integrity": "sha512-I1wCUp0yu5qSIeMQHmDYXQIXKkKjcja/SYBxppPkYFXpR2alxb0k9/swFDdMbkY6a1c9AT1kI1y+Pg6ywQ2rTA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@dicebear/adventurer": "9.2.4", + "@dicebear/adventurer-neutral": "9.2.4", + "@dicebear/avataaars": "9.2.4", + "@dicebear/avataaars-neutral": "9.2.4", + "@dicebear/big-ears": "9.2.4", + "@dicebear/big-ears-neutral": "9.2.4", + "@dicebear/big-smile": "9.2.4", + "@dicebear/bottts": "9.2.4", + "@dicebear/bottts-neutral": "9.2.4", + "@dicebear/croodles": "9.2.4", + "@dicebear/croodles-neutral": "9.2.4", + "@dicebear/dylan": "9.2.4", + "@dicebear/fun-emoji": "9.2.4", + "@dicebear/glass": "9.2.4", + "@dicebear/icons": "9.2.4", + "@dicebear/identicon": "9.2.4", + "@dicebear/initials": "9.2.4", + "@dicebear/lorelei": "9.2.4", + "@dicebear/lorelei-neutral": "9.2.4", + "@dicebear/micah": "9.2.4", + "@dicebear/miniavs": "9.2.4", + "@dicebear/notionists": "9.2.4", + "@dicebear/notionists-neutral": "9.2.4", + "@dicebear/open-peeps": "9.2.4", + "@dicebear/personas": "9.2.4", + "@dicebear/pixel-art": "9.2.4", + "@dicebear/pixel-art-neutral": "9.2.4", + "@dicebear/rings": "9.2.4", + "@dicebear/shapes": "9.2.4", + "@dicebear/thumbs": "9.2.4" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/core": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/core/-/core-9.2.4.tgz", + "integrity": "sha512-hz6zArEcUwkZzGOSJkWICrvqnEZY7BKeiq9rqKzVJIc1tRVv0MkR0FGvIxSvXiK9TTIgKwu656xCWAGAl6oh+w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.11" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@dicebear/croodles": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/croodles/-/croodles-9.2.4.tgz", + "integrity": "sha512-CqT0NgVfm+5kd+VnjGY4WECNFeOrj5p7GCPTSEA7tCuN72dMQOX47P9KioD3wbExXYrIlJgOcxNrQeb/FMGc3A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/croodles-neutral": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/croodles-neutral/-/croodles-neutral-9.2.4.tgz", + "integrity": "sha512-8vAS9lIEKffSUVx256GSRAlisB8oMX38UcPWw72venO/nitLVsyZ6hZ3V7eBdII0Onrjqw1RDndslQODbVcpTw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/dylan": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/dylan/-/dylan-9.2.4.tgz", + "integrity": "sha512-tiih1358djAq0jDDzmW3N3S4C3ynC2yn4hhlTAq/MaUAQtAi47QxdHdFGdxH0HBMZKqA4ThLdVk3yVgN4xsukg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/fun-emoji": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/fun-emoji/-/fun-emoji-9.2.4.tgz", + "integrity": "sha512-Od729skczse1HvHekgEFv+mSuJKMC4sl5hENGi/izYNe6DZDqJrrD0trkGT/IVh/SLXUFbq1ZFY9I2LoUGzFZg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/glass": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/glass/-/glass-9.2.4.tgz", + "integrity": "sha512-5lxbJode1t99eoIIgW0iwZMoZU4jNMJv/6vbsgYUhAslYFX5zP0jVRscksFuo89TTtS7YKqRqZAL3eNhz4bTDw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/icons": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/icons/-/icons-9.2.4.tgz", + "integrity": "sha512-bRsK1qj8u9Z76xs8XhXlgVr/oHh68tsHTJ/1xtkX9DeTQTSamo2tS26+r231IHu+oW3mePtFnwzdG9LqEPRd4A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/identicon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/identicon/-/identicon-9.2.4.tgz", + "integrity": "sha512-R9nw/E8fbu9HltHOqI9iL/o9i7zM+2QauXWMreQyERc39oGR9qXiwgBxsfYGcIS4C85xPyuL5B3I2RXrLBlJPg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/initials": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/initials/-/initials-9.2.4.tgz", + "integrity": "sha512-4SzHG5WoQZl1TGcpEZR4bdsSkUVqwNQCOwWSPAoBJa3BNxbVsvL08LF7I97BMgrCoknWZjQHUYt05amwTPTKtg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/lorelei": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/lorelei/-/lorelei-9.2.4.tgz", + "integrity": "sha512-eS4mPYUgDpo89HvyFAx/kgqSSKh8W4zlUA8QJeIUCWTB0WpQmeqkSgIyUJjGDYSrIujWi+zEhhckksM5EwW0Dg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/lorelei-neutral": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/lorelei-neutral/-/lorelei-neutral-9.2.4.tgz", + "integrity": "sha512-bWq2/GonbcJULtT+B/MGcM2UnA7kBQoH+INw8/oW83WI3GNTZ6qEwe3/W4QnCgtSOhUsuwuiSULguAFyvtkOZQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/micah": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/micah/-/micah-9.2.4.tgz", + "integrity": "sha512-XNWJ8Mx+pncIV8Ye0XYc/VkMiax8kTxcP3hLTC5vmELQyMSLXzg/9SdpI+W/tCQghtPZRYTT3JdY9oU9IUlP2g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/miniavs": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/miniavs/-/miniavs-9.2.4.tgz", + "integrity": "sha512-k7IYTAHE/4jSO6boMBRrNlqPT3bh7PLFM1atfe0nOeCDwmz/qJUBP3HdONajbf3fmo8f2IZYhELrNWTOE7Ox3Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/notionists": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/notionists/-/notionists-9.2.4.tgz", + "integrity": "sha512-zcvpAJ93EfC0xQffaPZQuJPShwPhnu9aTcoPsaYGmw0oEDLcv2XYmDhUUdX84QYCn6LtCZH053rHLVazRW+OGw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/notionists-neutral": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/notionists-neutral/-/notionists-neutral-9.2.4.tgz", + "integrity": "sha512-fskWzBVxQzJhCKqY24DGZbYHSBaauoRa1DgXM7+7xBuksH7mfbTmZTvnUAsAqJYBkla8IPb4ERKduDWtlWYYjQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/open-peeps": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/open-peeps/-/open-peeps-9.2.4.tgz", + "integrity": "sha512-s6nwdjXFsplqEI7imlsel4Gt6kFVJm6YIgtZSpry0UdwDoxUUudei5bn957j9lXwVpVUcRjJW+TuEKztYjXkKQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/personas": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/personas/-/personas-9.2.4.tgz", + "integrity": "sha512-JNim8RfZYwb0MfxW6DLVfvreCFIevQg+V225Xe5tDfbFgbcYEp4OU/KaiqqO2476OBjCw7i7/8USbv2acBhjwA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/pixel-art": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/pixel-art/-/pixel-art-9.2.4.tgz", + "integrity": "sha512-4Ao45asieswUdlCTBZqcoF/0zHR3OWUWB0Mvhlu9b1Fbc6IlPBiOfx2vsp6bnVGVnMag58tJLecx2omeXdECBQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/pixel-art-neutral": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/pixel-art-neutral/-/pixel-art-neutral-9.2.4.tgz", + "integrity": "sha512-ZITPLD1cPN4GjKkhWi80s7e5dcbXy34ijWlvmxbc4eb/V7fZSsyRa9EDUW3QStpo+xrCJLcLR+3RBE5iz0PC/A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/rings": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/rings/-/rings-9.2.4.tgz", + "integrity": "sha512-teZxELYyV2ogzgb5Mvtn/rHptT0HXo9SjUGS4A52mOwhIdHSGGU71MqA1YUzfae9yJThsw6K7Z9kzuY2LlZZHA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/shapes": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/shapes/-/shapes-9.2.4.tgz", + "integrity": "sha512-MhK9ZdFm1wUnH4zWeKPRMZ98UyApolf5OLzhCywfu38tRN6RVbwtBRHc/42ZwoN1JU1JgXr7hzjYucMqISHtbA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, + "node_modules/@dicebear/thumbs": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@dicebear/thumbs/-/thumbs-9.2.4.tgz", + "integrity": "sha512-EL4sMqv9p2+1Xy3d8e8UxyeKZV2+cgt3X2x2RTRzEOIIhobtkL8u6lJxmJbiGbpVtVALmrt5e7gjmwqpryYDpg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@dicebear/core": "^9.0.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.0.tgz", @@ -51484,7 +51932,7 @@ }, "packages/client": { "name": "@librechat/client", - "version": "0.2.6", + "version": "0.2.7", "devDependencies": { "@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-commonjs": "^25.0.2", @@ -51512,6 +51960,8 @@ "peerDependencies": { "@ariakit/react": "^0.4.16", "@ariakit/react-core": "^0.4.17", + "@dicebear/collection": "^9.2.2", + "@dicebear/core": "^9.2.2", "@headlessui/react": "^2.1.2", "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.0.2", diff --git a/packages/client/package.json b/packages/client/package.json index ab01830499..4f86e4977f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@librechat/client", - "version": "0.2.6", + "version": "0.2.7", "description": "React components for LibreChat", "main": "dist/index.js", "module": "dist/index.es.js", @@ -64,7 +64,9 @@ "react-hook-form": "^7.56.4", "react-resizable-panels": "^3.0.2", "react-textarea-autosize": "^8.4.0", - "tailwind-merge": "^1.9.1" + "tailwind-merge": "^1.9.1", + "@dicebear/core": "^9.2.2", + "@dicebear/collection": "^9.2.2" }, "devDependencies": { "@rollup/plugin-alias": "^5.1.0", diff --git a/packages/client/src/components/Avatar.tsx b/packages/client/src/components/Avatar.tsx new file mode 100644 index 0000000000..1f70032d40 --- /dev/null +++ b/packages/client/src/components/Avatar.tsx @@ -0,0 +1,102 @@ +import React, { useState, useMemo, useCallback } from 'react'; +import type { TUser } from 'librechat-data-provider'; +import { Skeleton } from './Skeleton'; +import { useAvatar } from '~/hooks'; +import { UserIcon } from '~/svgs'; + +export interface AvatarProps { + user?: TUser; + size?: number; + className?: string; + alt?: string; + showDefaultWhenEmpty?: boolean; +} + +const Avatar: React.FC = ({ + user, + size = 32, + className = '', + alt, + showDefaultWhenEmpty = true, +}) => { + const avatarSrc = useAvatar(user); + const [imageLoaded, setImageLoaded] = useState(false); + const [imageError, setImageError] = useState(false); + + const avatarSeed = useMemo( + () => user?.avatar || user?.username || user?.email || '', + [user?.avatar, user?.username, user?.email], + ); + + const altText = useMemo( + () => alt || `${user?.name || user?.username || user?.email || ''}'s avatar`, + [alt, user?.name, user?.username, user?.email], + ); + + const imageSrc = useMemo(() => { + if (!avatarSeed || imageError) return ''; + return (user?.avatar ?? '') || avatarSrc || ''; + }, [user?.avatar, avatarSrc, avatarSeed, imageError]); + + const handleImageLoad = useCallback(() => { + setImageLoaded(true); + }, []); + + const handleImageError = useCallback(() => { + setImageError(true); + setImageLoaded(false); + }, []); + + const DefaultAvatar = useCallback( + () => ( + + ), + [size, className], + ); + + if (avatarSeed.length === 0 && showDefaultWhenEmpty) { + return ; + } + + if (avatarSeed.length > 0 && !imageError) { + return ( +
+ {!imageLoaded && ( + + )} + + {altText} +
+ ); + } + + if (imageError && showDefaultWhenEmpty) { + return ; + } + + return null; +}; + +export default Avatar; diff --git a/packages/client/src/components/index.ts b/packages/client/src/components/index.ts index 3ef9361a9a..d1de8a8530 100644 --- a/packages/client/src/components/index.ts +++ b/packages/client/src/components/index.ts @@ -33,6 +33,7 @@ export * from './Resizable'; export * from './Select'; export { default as Radio } from './Radio'; export { default as Badge } from './Badge'; +export { default as Avatar } from './Avatar'; export { default as Combobox } from './Combobox'; export { default as Dropdown } from './Dropdown'; export { default as SplitText } from './SplitText'; diff --git a/packages/client/src/hooks/index.ts b/packages/client/src/hooks/index.ts index 019dae0ee4..9a140087ca 100644 --- a/packages/client/src/hooks/index.ts +++ b/packages/client/src/hooks/index.ts @@ -3,6 +3,7 @@ export type { TranslationKeys } from './useLocalize'; export { default as useToast } from './useToast'; +export { default as useAvatar } from './useAvatar'; export { default as useCombobox } from './useCombobox'; export { default as useLocalize } from './useLocalize'; export { default as useMediaQuery } from './useMediaQuery'; diff --git a/client/src/hooks/Messages/useAvatar.ts b/packages/client/src/hooks/useAvatar.ts similarity index 100% rename from client/src/hooks/Messages/useAvatar.ts rename to packages/client/src/hooks/useAvatar.ts