import { useState, useRef, useCallback, useEffect, useMemo, memo } from 'react'; import throttle from 'lodash/throttle'; import { useRecoilValue } from 'recoil'; import { getConfigDefaults } from 'librechat-data-provider'; import type { ImperativePanelHandle } from 'react-resizable-panels'; import { ResizableHandleAlt, ResizablePanel, ResizablePanelGroup } from '~/components/ui/Resizable'; import { useGetStartupConfig } from '~/data-provider'; import { normalizeLayout } from '~/utils'; import { useMediaQuery } from '~/hooks'; import SidePanel from './SidePanel'; import store from '~/store'; interface SidePanelProps { defaultLayout?: number[] | undefined; defaultCollapsed?: boolean; navCollapsedSize?: number; fullPanelCollapse?: boolean; artifacts?: React.ReactNode; children: React.ReactNode; } const defaultMinSize = 20; const defaultInterface = getConfigDefaults().interface; const SidePanelGroup = ({ defaultLayout = [97, 3], defaultCollapsed = false, fullPanelCollapse = false, navCollapsedSize = 3, artifacts, children, }: SidePanelProps) => { const { data: startupConfig } = useGetStartupConfig(); const interfaceConfig = useMemo( () => startupConfig?.interface ?? defaultInterface, [startupConfig], ); const panelRef = useRef(null); const [minSize, setMinSize] = useState(defaultMinSize); const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed); const [fullCollapse, setFullCollapse] = useState(fullPanelCollapse); const [collapsedSize, setCollapsedSize] = useState(navCollapsedSize); const isSmallScreen = useMediaQuery('(max-width: 767px)'); const hideSidePanel = useRecoilValue(store.hideSidePanel); const calculateLayout = useCallback(() => { if (artifacts == null) { const navSize = defaultLayout.length === 2 ? defaultLayout[1] : defaultLayout[2]; return [100 - navSize, navSize]; } else { const navSize = 0; const remainingSpace = 100 - navSize; const newMainSize = Math.floor(remainingSpace / 2); const artifactsSize = remainingSpace - newMainSize; return [newMainSize, artifactsSize, navSize]; } }, [artifacts, defaultLayout]); const currentLayout = useMemo(() => normalizeLayout(calculateLayout()), [calculateLayout]); const throttledSaveLayout = useCallback( throttle((sizes: number[]) => { const normalizedSizes = normalizeLayout(sizes); localStorage.setItem('react-resizable-panels:layout', JSON.stringify(normalizedSizes)); }, 350), [], ); useEffect(() => { if (isSmallScreen) { setIsCollapsed(true); setCollapsedSize(0); setMinSize(defaultMinSize); setFullCollapse(true); localStorage.setItem('fullPanelCollapse', 'true'); panelRef.current?.collapse(); return; } else { setIsCollapsed(defaultCollapsed); setCollapsedSize(navCollapsedSize); setMinSize(defaultMinSize); } }, [isSmallScreen, defaultCollapsed, navCollapsedSize, fullPanelCollapse]); const minSizeMain = useMemo(() => (artifacts != null ? 15 : 30), [artifacts]); return ( <> throttledSaveLayout(sizes)} className="transition-width relative h-full w-full flex-1 overflow-auto bg-presentation" > {children} {artifacts != null && ( <> {artifacts} )} {!hideSidePanel && interfaceConfig.sidePanel === true && ( )}