import { useState, useCallback, useMemo, memo } from 'react'; import { getEndpointField } from 'librechat-data-provider'; import { useUserKeyQuery } from 'librechat-data-provider/react-query'; import { ResizableHandleAlt, ResizablePanel, useMediaQuery } from '@librechat/client'; import type { TEndpointsConfig, TInterfaceConfig } from 'librechat-data-provider'; import type { ImperativePanelHandle } from 'react-resizable-panels'; import useSideNavLinks from '~/hooks/Nav/useSideNavLinks'; import { useLocalStorage, useLocalize } from '~/hooks'; import { useGetEndpointsQuery } from '~/data-provider'; import NavToggle from '~/components/Nav/NavToggle'; import { useSidePanelContext } from '~/Providers'; import { cn } from '~/utils'; import Nav from './Nav'; const defaultMinSize = 20; const SidePanel = ({ defaultSize, panelRef, navCollapsedSize = 3, hasArtifacts, minSize, setMinSize, collapsedSize, setCollapsedSize, isCollapsed, setIsCollapsed, fullCollapse, setFullCollapse, interfaceConfig, }: { defaultSize?: number; hasArtifacts: boolean; navCollapsedSize?: number; minSize: number; setMinSize: React.Dispatch>; collapsedSize: number; setCollapsedSize: React.Dispatch>; isCollapsed: boolean; setIsCollapsed: React.Dispatch>; fullCollapse: boolean; setFullCollapse: React.Dispatch>; panelRef: React.RefObject; interfaceConfig: TInterfaceConfig; }) => { const localize = useLocalize(); const { endpoint } = useSidePanelContext(); const [isHovering, setIsHovering] = useState(false); const [newUser, setNewUser] = useLocalStorage('newUser', true); const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery(); const isSmallScreen = useMediaQuery('(max-width: 767px)'); const { data: keyExpiry = { expiresAt: undefined } } = useUserKeyQuery(endpoint ?? ''); const defaultActive = useMemo(() => { const activePanel = localStorage.getItem('side:active-panel'); return typeof activePanel === 'string' ? activePanel : undefined; }, []); const endpointType = useMemo( () => getEndpointField(endpointsConfig, endpoint, 'type'), [endpoint, endpointsConfig], ); const userProvidesKey = useMemo( () => !!(endpointsConfig?.[endpoint ?? '']?.userProvide ?? false), [endpointsConfig, endpoint], ); const keyProvided = useMemo( () => (userProvidesKey ? !!(keyExpiry.expiresAt ?? '') : true), [keyExpiry.expiresAt, userProvidesKey], ); const hidePanel = useCallback(() => { setIsCollapsed(true); setCollapsedSize(0); setMinSize(defaultMinSize); setFullCollapse(true); localStorage.setItem('fullPanelCollapse', 'true'); panelRef.current?.collapse(); }, [panelRef, setMinSize, setIsCollapsed, setFullCollapse, setCollapsedSize]); const Links = useSideNavLinks({ endpoint, hidePanel, keyProvided, endpointType, interfaceConfig, endpointsConfig, }); const toggleNavVisible = useCallback(() => { if (newUser) { setNewUser(false); } setIsCollapsed((prev: boolean) => { if (prev) { setMinSize(defaultMinSize); setCollapsedSize(navCollapsedSize); setFullCollapse(false); localStorage.setItem('fullPanelCollapse', 'false'); } return !prev; }); if (!isCollapsed) { panelRef.current?.collapse(); } else { panelRef.current?.expand(); } }, [ newUser, panelRef, setNewUser, setMinSize, isCollapsed, setIsCollapsed, setFullCollapse, setCollapsedSize, navCollapsedSize, ]); return ( <>
setIsHovering(true)} onMouseLeave={() => setIsHovering(false)} className="relative flex w-px items-center justify-center" >
{(!isCollapsed || minSize > 0) && !isSmallScreen && !fullCollapse && ( )} { setIsCollapsed(false); localStorage.setItem('react-resizable-panels:collapsed', 'false'); }} onCollapse={() => { setIsCollapsed(true); localStorage.setItem('react-resizable-panels:collapsed', 'true'); }} className={cn( 'sidenav hide-scrollbar border-l border-border-light bg-background py-1 transition-opacity', isCollapsed ? 'min-w-[50px]' : 'min-w-[340px] sm:min-w-[352px]', (isSmallScreen && isCollapsed && (minSize === 0 || collapsedSize === 0)) || fullCollapse ? 'hidden min-w-0' : 'opacity-100', )} >