{children} diff --git a/client/src/components/SidePanel/ArtifactsPanel.tsx b/client/src/components/SidePanel/ArtifactsPanel.tsx new file mode 100644 index 0000000000..1ea644e9b9 --- /dev/null +++ b/client/src/components/SidePanel/ArtifactsPanel.tsx @@ -0,0 +1,82 @@ +import { useRef, useEffect, memo } from 'react'; +import { ResizableHandleAlt, ResizablePanel } from '@librechat/client'; +import type { ImperativePanelHandle } from 'react-resizable-panels'; + +const ANIMATION_DURATION = 500; + +interface ArtifactsPanelProps { + artifacts: React.ReactNode | null; + currentLayout: number[]; + minSizeMain: number; + shouldRender: boolean; + onRenderChange: (shouldRender: boolean) => void; +} + +/** + * ArtifactsPanel component - memoized to prevent unnecessary re-renders + * Only re-renders when artifacts visibility or layout changes + */ +const ArtifactsPanel = memo(function ArtifactsPanel({ + artifacts, + currentLayout, + minSizeMain, + shouldRender, + onRenderChange, +}: ArtifactsPanelProps) { + const artifactsPanelRef = useRef(null); + const timeoutRef = useRef(null); + + useEffect(() => { + if (artifacts != null) { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + onRenderChange(true); + requestAnimationFrame(() => { + requestAnimationFrame(() => { + artifactsPanelRef.current?.expand(); + }); + }); + } else if (shouldRender) { + artifactsPanelRef.current?.collapse(); + timeoutRef.current = setTimeout(() => { + onRenderChange(false); + }, ANIMATION_DURATION); + } + + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, [artifacts, shouldRender, onRenderChange]); + + if (!shouldRender) { + return null; + } + + return ( + <> + {artifacts != null && ( + + )} + +
{artifacts}
+
+ + ); +}); + +ArtifactsPanel.displayName = 'ArtifactsPanel'; + +export default ArtifactsPanel; diff --git a/client/src/components/SidePanel/SidePanelGroup.tsx b/client/src/components/SidePanel/SidePanelGroup.tsx index ad2b6d7e11..11761d75d9 100644 --- a/client/src/components/SidePanel/SidePanelGroup.tsx +++ b/client/src/components/SidePanel/SidePanelGroup.tsx @@ -2,14 +2,10 @@ 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 { - ResizableHandleAlt, - ResizablePanel, - ResizablePanelGroup, - useMediaQuery, -} from '@librechat/client'; +import { ResizablePanel, ResizablePanelGroup, useMediaQuery } from '@librechat/client'; import type { ImperativePanelHandle } from 'react-resizable-panels'; import { useGetStartupConfig } from '~/data-provider'; +import ArtifactsPanel from './ArtifactsPanel'; import { normalizeLayout } from '~/utils'; import SidePanel from './SidePanel'; import store from '~/store'; @@ -46,6 +42,7 @@ const SidePanelGroup = memo( const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed); const [fullCollapse, setFullCollapse] = useState(fullPanelCollapse); const [collapsedSize, setCollapsedSize] = useState(navCollapsedSize); + const [shouldRenderArtifacts, setShouldRenderArtifacts] = useState(artifacts != null); const isSmallScreen = useMediaQuery('(max-width: 767px)'); const hideSidePanel = useRecoilValue(store.hideSidePanel); @@ -109,7 +106,7 @@ const SidePanelGroup = memo( throttledSaveLayout(sizes)} - className="transition-width relative h-full w-full flex-1 overflow-auto bg-presentation" + className="relative h-full w-full flex-1 overflow-auto bg-presentation" > {children} - {artifacts != null && ( - <> - - - {artifacts} - - + + {!isSmallScreen && ( + )} + {!hideSidePanel && interfaceConfig.sidePanel === true && ( )} + {artifacts != null && isSmallScreen && ( +
{artifacts}
+ )} ))} diff --git a/packages/client/src/components/Resizable.tsx b/packages/client/src/components/Resizable.tsx index 19d3c50e34..b44d42be58 100644 --- a/packages/client/src/components/Resizable.tsx +++ b/packages/client/src/components/Resizable.tsx @@ -52,7 +52,7 @@ const ResizableHandleAlt = ({ {...props} > {withHandle && ( -
+
)} diff --git a/packages/data-provider/src/api-endpoints.ts b/packages/data-provider/src/api-endpoints.ts index 8c1447abee..abcd5a1ec9 100644 --- a/packages/data-provider/src/api-endpoints.ts +++ b/packages/data-provider/src/api-endpoints.ts @@ -64,7 +64,7 @@ export const messages = (params: q.MessagesListParams) => { return `${messagesRoot}${buildQuery(rest)}`; }; -export const messagesArtifacts = (messageId: string) => `${messagesRoot}/artifacts/${messageId}`; +export const messagesArtifacts = (messageId: string) => `${messagesRoot}/artifact/${messageId}`; const shareRoot = `${BASE_URL}/api/share`; export const shareMessages = (shareId: string) => `${shareRoot}/${shareId}`;