mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 01:10:14 +01:00
♻️ refactor: SidePanel Context to Optimize on ChatView Rerender (#8509)
This commit is contained in:
parent
0b1b0af741
commit
dfef7c31d2
5 changed files with 180 additions and 139 deletions
31
client/src/Providers/SidePanelContext.tsx
Normal file
31
client/src/Providers/SidePanelContext.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import React, { createContext, useContext, useMemo } from 'react';
|
||||||
|
import type { EModelEndpoint } from 'librechat-data-provider';
|
||||||
|
import { useChatContext } from './ChatContext';
|
||||||
|
|
||||||
|
interface SidePanelContextValue {
|
||||||
|
endpoint?: EModelEndpoint | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidePanelContext = createContext<SidePanelContextValue | undefined>(undefined);
|
||||||
|
|
||||||
|
export function SidePanelProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const { conversation } = useChatContext();
|
||||||
|
|
||||||
|
/** Context value only created when endpoint changes */
|
||||||
|
const contextValue = useMemo<SidePanelContextValue>(
|
||||||
|
() => ({
|
||||||
|
endpoint: conversation?.endpoint,
|
||||||
|
}),
|
||||||
|
[conversation?.endpoint],
|
||||||
|
);
|
||||||
|
|
||||||
|
return <SidePanelContext.Provider value={contextValue}>{children}</SidePanelContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSidePanelContext() {
|
||||||
|
const context = useContext(SidePanelContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useSidePanelContext must be used within SidePanelProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
@ -24,4 +24,5 @@ export * from './ToolCallsMapContext';
|
||||||
export * from './SetConvoContext';
|
export * from './SetConvoContext';
|
||||||
export * from './SearchContext';
|
export * from './SearchContext';
|
||||||
export * from './BadgeRowContext';
|
export * from './BadgeRowContext';
|
||||||
|
export * from './SidePanelContext';
|
||||||
export { default as BadgeRowProvider } from './BadgeRowContext';
|
export { default as BadgeRowProvider } from './BadgeRowContext';
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import { FileSources, LocalStorageKeys } from 'librechat-data-provider';
|
||||||
import type { ExtendedFile } from '~/common';
|
import type { ExtendedFile } from '~/common';
|
||||||
import { useDeleteFilesMutation } from '~/data-provider';
|
import { useDeleteFilesMutation } from '~/data-provider';
|
||||||
import DragDropWrapper from '~/components/Chat/Input/Files/DragDropWrapper';
|
import DragDropWrapper from '~/components/Chat/Input/Files/DragDropWrapper';
|
||||||
|
import { EditorProvider, SidePanelProvider } from '~/Providers';
|
||||||
import Artifacts from '~/components/Artifacts/Artifacts';
|
import Artifacts from '~/components/Artifacts/Artifacts';
|
||||||
import { SidePanelGroup } from '~/components/SidePanel';
|
import { SidePanelGroup } from '~/components/SidePanel';
|
||||||
import { useSetFilesToDelete } from '~/hooks';
|
import { useSetFilesToDelete } from '~/hooks';
|
||||||
import { EditorProvider } from '~/Providers';
|
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
export default function Presentation({ children }: { children: React.ReactNode }) {
|
export default function Presentation({ children }: { children: React.ReactNode }) {
|
||||||
|
|
@ -59,6 +59,7 @@ export default function Presentation({ children }: { children: React.ReactNode }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragDropWrapper className="relative flex w-full grow overflow-hidden bg-presentation">
|
<DragDropWrapper className="relative flex w-full grow overflow-hidden bg-presentation">
|
||||||
|
<SidePanelProvider>
|
||||||
<SidePanelGroup
|
<SidePanelGroup
|
||||||
defaultLayout={defaultLayout}
|
defaultLayout={defaultLayout}
|
||||||
fullPanelCollapse={fullCollapse}
|
fullPanelCollapse={fullCollapse}
|
||||||
|
|
@ -75,6 +76,7 @@ export default function Presentation({ children }: { children: React.ReactNode }
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</SidePanelGroup>
|
</SidePanelGroup>
|
||||||
|
</SidePanelProvider>
|
||||||
</DragDropWrapper>
|
</DragDropWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import { useMediaQuery, useLocalStorage, useLocalize } from '~/hooks';
|
||||||
import useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
|
import useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
|
||||||
import { useGetEndpointsQuery } from '~/data-provider';
|
import { useGetEndpointsQuery } from '~/data-provider';
|
||||||
import NavToggle from '~/components/Nav/NavToggle';
|
import NavToggle from '~/components/Nav/NavToggle';
|
||||||
|
import { useSidePanelContext } from '~/Providers';
|
||||||
import { cn, getEndpointField } from '~/utils';
|
import { cn, getEndpointField } from '~/utils';
|
||||||
import { useChatContext } from '~/Providers';
|
|
||||||
import Nav from './Nav';
|
import Nav from './Nav';
|
||||||
|
|
||||||
const defaultMinSize = 20;
|
const defaultMinSize = 20;
|
||||||
|
|
@ -43,13 +43,13 @@ const SidePanel = ({
|
||||||
interfaceConfig: TInterfaceConfig;
|
interfaceConfig: TInterfaceConfig;
|
||||||
}) => {
|
}) => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
const { endpoint } = useSidePanelContext();
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
const [newUser, setNewUser] = useLocalStorage('newUser', true);
|
const [newUser, setNewUser] = useLocalStorage('newUser', true);
|
||||||
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
||||||
|
|
||||||
const isSmallScreen = useMediaQuery('(max-width: 767px)');
|
const isSmallScreen = useMediaQuery('(max-width: 767px)');
|
||||||
const { conversation } = useChatContext();
|
|
||||||
const { endpoint } = conversation ?? {};
|
|
||||||
const { data: keyExpiry = { expiresAt: undefined } } = useUserKeyQuery(endpoint ?? '');
|
const { data: keyExpiry = { expiresAt: undefined } } = useUserKeyQuery(endpoint ?? '');
|
||||||
|
|
||||||
const defaultActive = useMemo(() => {
|
const defaultActive = useMemo(() => {
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,15 @@ interface SidePanelProps {
|
||||||
const defaultMinSize = 20;
|
const defaultMinSize = 20;
|
||||||
const defaultInterface = getConfigDefaults().interface;
|
const defaultInterface = getConfigDefaults().interface;
|
||||||
|
|
||||||
const SidePanelGroup = ({
|
const SidePanelGroup = memo(
|
||||||
|
({
|
||||||
defaultLayout = [97, 3],
|
defaultLayout = [97, 3],
|
||||||
defaultCollapsed = false,
|
defaultCollapsed = false,
|
||||||
fullPanelCollapse = false,
|
fullPanelCollapse = false,
|
||||||
navCollapsedSize = 3,
|
navCollapsedSize = 3,
|
||||||
artifacts,
|
artifacts,
|
||||||
children,
|
children,
|
||||||
}: SidePanelProps) => {
|
}: SidePanelProps) => {
|
||||||
const { data: startupConfig } = useGetStartupConfig();
|
const { data: startupConfig } = useGetStartupConfig();
|
||||||
const interfaceConfig = useMemo(
|
const interfaceConfig = useMemo(
|
||||||
() => startupConfig?.interface ?? defaultInterface,
|
() => startupConfig?.interface ?? defaultInterface,
|
||||||
|
|
@ -87,6 +88,18 @@ const SidePanelGroup = ({
|
||||||
|
|
||||||
const minSizeMain = useMemo(() => (artifacts != null ? 15 : 30), [artifacts]);
|
const minSizeMain = useMemo(() => (artifacts != null ? 15 : 30), [artifacts]);
|
||||||
|
|
||||||
|
/** Memoized close button handler to prevent re-creating it */
|
||||||
|
const handleClosePanel = useCallback(() => {
|
||||||
|
setIsCollapsed(() => {
|
||||||
|
localStorage.setItem('fullPanelCollapse', 'true');
|
||||||
|
setFullCollapse(true);
|
||||||
|
setCollapsedSize(0);
|
||||||
|
setMinSize(0);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
panelRef.current?.collapse();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ResizablePanelGroup
|
<ResizablePanelGroup
|
||||||
|
|
@ -135,19 +148,13 @@ const SidePanelGroup = ({
|
||||||
<button
|
<button
|
||||||
aria-label="Close right side panel"
|
aria-label="Close right side panel"
|
||||||
className={`nav-mask ${!isCollapsed ? 'active' : ''}`}
|
className={`nav-mask ${!isCollapsed ? 'active' : ''}`}
|
||||||
onClick={() => {
|
onClick={handleClosePanel}
|
||||||
setIsCollapsed(() => {
|
|
||||||
localStorage.setItem('fullPanelCollapse', 'true');
|
|
||||||
setFullCollapse(true);
|
|
||||||
setCollapsedSize(0);
|
|
||||||
setMinSize(0);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
panelRef.current?.collapse();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default memo(SidePanelGroup);
|
SidePanelGroup.displayName = 'SidePanelGroup';
|
||||||
|
|
||||||
|
export default SidePanelGroup;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue