mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-22 03:10:15 +01:00
* chore: remove unused code * refactor: Update NewChatButtonIcon component to use JSX syntax The NewChatButtonIcon component in the Nav folder has been updated to use JSX syntax instead of calling the Icon function directly. This change improves code readability and maintainability. * remove use memo * refactor: allow passing `select` to messages db query * fix: initial fix for non-recursive messages * ci: first pass, importers test rewrite * fix(groupConversationsByDate): handle edge case of conversation.updatedAt being null * fix: correctly handle non-recursive uploads * feat: imports non-recursive conversations with branches correctly * feat: support retaining original options on import * refactor: Allow `messageTree` field for Import of non-recursive conversations
131 lines
5.6 KiB
TypeScript
131 lines
5.6 KiB
TypeScript
import { FileText } from 'lucide-react';
|
|
import { useRecoilState } from 'recoil';
|
|
import { Fragment, useState, memo } from 'react';
|
|
import { Menu, Transition } from '@headlessui/react';
|
|
import { useGetUserBalance, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
|
import FilesView from '~/components/Chat/Input/Files/FilesView';
|
|
import { useAuthContext } from '~/hooks/AuthContext';
|
|
import useAvatar from '~/hooks/Messages/useAvatar';
|
|
import { LinkIcon, GearIcon } from '~/components';
|
|
import { UserIcon } from '~/components/svg';
|
|
import { useLocalize } from '~/hooks';
|
|
import Settings from './Settings';
|
|
import NavLink from './NavLink';
|
|
import Logout from './Logout';
|
|
import { cn } from '~/utils/';
|
|
import store from '~/store';
|
|
|
|
function NavLinks() {
|
|
const localize = useLocalize();
|
|
const { user, isAuthenticated } = useAuthContext();
|
|
const { data: startupConfig } = useGetStartupConfig();
|
|
const balanceQuery = useGetUserBalance({
|
|
enabled: !!isAuthenticated && startupConfig?.checkBalance,
|
|
});
|
|
const [showSettings, setShowSettings] = useState(false);
|
|
const [showFiles, setShowFiles] = useRecoilState(store.showFiles);
|
|
|
|
const avatarSrc = useAvatar(user);
|
|
|
|
return (
|
|
<>
|
|
<Menu as="div" className="group relative">
|
|
{({ open }) => (
|
|
<>
|
|
<Menu.Button
|
|
className={cn(
|
|
'group-ui-open:bg-gray-100 dark:group-ui-open:bg-gray-700 duration-350 mt-text-sm flex h-auto w-full items-center gap-2 rounded-lg p-2 text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-800',
|
|
open ? 'bg-gray-100 dark:bg-gray-800' : '',
|
|
)}
|
|
data-testid="nav-user"
|
|
>
|
|
<div className="-ml-0.9 -mt-0.8 h-8 w-8 flex-shrink-0">
|
|
<div className="relative flex">
|
|
{!user?.avatar && !user?.username ? (
|
|
<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-white"
|
|
>
|
|
<UserIcon />
|
|
</div>
|
|
) : (
|
|
<img className="rounded-full" src={user?.avatar || avatarSrc} alt="avatar" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div
|
|
className="mt-2 grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-black dark:text-gray-100"
|
|
style={{ marginTop: '0', marginLeft: '0' }}
|
|
>
|
|
{user?.name || user?.username || localize('com_nav_user')}
|
|
</div>
|
|
</Menu.Button>
|
|
|
|
<Transition
|
|
as={Fragment}
|
|
enter="transition ease-out duration-110 transform"
|
|
enterFrom="translate-y-2 opacity-0"
|
|
enterTo="translate-y-0 opacity-100"
|
|
leave="transition ease-in duration-100 transform"
|
|
leaveFrom="translate-y-0 opacity-100"
|
|
leaveTo="translate-y-2 opacity-0"
|
|
>
|
|
<Menu.Items className="absolute bottom-full left-0 z-[100] mb-1 mt-1 w-full translate-y-0 overflow-hidden rounded-lg border border-gray-300 bg-white p-1.5 opacity-100 shadow-lg outline-none dark:border-gray-600 dark:bg-gray-700">
|
|
<div className="text-token-text-secondary ml-3 mr-2 py-2 text-sm" role="none">
|
|
{user?.email || localize('com_nav_user')}
|
|
</div>
|
|
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
|
{startupConfig?.checkBalance &&
|
|
balanceQuery.data &&
|
|
!isNaN(parseFloat(balanceQuery.data)) && (
|
|
<>
|
|
<div className="text-token-text-secondary ml-3 mr-2 py-2 text-sm">
|
|
{`Balance: ${parseFloat(balanceQuery.data).toFixed(2)}`}
|
|
</div>
|
|
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
|
</>
|
|
)}
|
|
<Menu.Item as="div">
|
|
<NavLink
|
|
svg={() => <FileText className="icon-md" />}
|
|
text={localize('com_nav_my_files')}
|
|
clickHandler={() => setShowFiles(true)}
|
|
/>
|
|
</Menu.Item>
|
|
{startupConfig?.helpAndFaqURL !== '/' && (
|
|
<Menu.Item as="div">
|
|
<NavLink
|
|
svg={() => <LinkIcon />}
|
|
text={localize('com_nav_help_faq')}
|
|
clickHandler={() => window.open(startupConfig?.helpAndFaqURL, '_blank')}
|
|
/>
|
|
</Menu.Item>
|
|
)}
|
|
<Menu.Item as="div">
|
|
<NavLink
|
|
svg={() => <GearIcon className="icon-md" />}
|
|
text={localize('com_nav_settings')}
|
|
clickHandler={() => setShowSettings(true)}
|
|
/>
|
|
</Menu.Item>
|
|
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
|
<Menu.Item as="div">
|
|
<Logout />
|
|
</Menu.Item>
|
|
</Menu.Items>
|
|
</Transition>
|
|
</>
|
|
)}
|
|
</Menu>
|
|
{showFiles && <FilesView open={showFiles} onOpenChange={setShowFiles} />}
|
|
{showSettings && <Settings open={showSettings} onOpenChange={setShowSettings} />}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default memo(NavLinks);
|