From f884c2cfcd65dae69839c2f798579a73314e4922 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 11 Feb 2025 14:24:01 -0500 Subject: [PATCH 1/9] refactor: remove artifacts toggle --- client/src/components/Chat/Presentation.tsx | 13 +++++------- .../Nav/SettingsTabs/Beta/CodeArtifacts.tsx | 21 ++----------------- client/src/hooks/Chat/useChatFunctions.ts | 4 +--- client/src/store/settings.ts | 1 - client/src/utils/artifacts.ts | 6 +----- 5 files changed, 9 insertions(+), 36 deletions(-) diff --git a/client/src/components/Chat/Presentation.tsx b/client/src/components/Chat/Presentation.tsx index c92f604ee10..68ec6f0f2e0 100644 --- a/client/src/components/Chat/Presentation.tsx +++ b/client/src/components/Chat/Presentation.tsx @@ -23,7 +23,6 @@ export default function Presentation({ }) { const { data: startupConfig } = useGetStartupConfig(); const artifacts = useRecoilValue(store.artifactsState); - const codeArtifacts = useRecoilValue(store.codeArtifacts); const hideSidePanel = useRecoilValue(store.hideSidePanel); const artifactsVisible = useRecoilValue(store.artifactsVisible); @@ -91,13 +90,11 @@ export default function Presentation({ defaultCollapsed={defaultCollapsed} fullPanelCollapse={fullCollapse} artifacts={ - artifactsVisible === true && - codeArtifacts === true && - Object.keys(artifacts ?? {}).length > 0 ? ( - - - - ) : null + artifactsVisible === true && Object.keys(artifacts ?? {}).length > 0 ? ( + + + + ) : null } >
diff --git a/client/src/components/Nav/SettingsTabs/Beta/CodeArtifacts.tsx b/client/src/components/Nav/SettingsTabs/Beta/CodeArtifacts.tsx index dd985a86af5..7b924507c11 100644 --- a/client/src/components/Nav/SettingsTabs/Beta/CodeArtifacts.tsx +++ b/client/src/components/Nav/SettingsTabs/Beta/CodeArtifacts.tsx @@ -5,18 +5,9 @@ import { useLocalize } from '~/hooks'; import store from '~/store'; export default function CodeArtifacts() { - const [codeArtifacts, setCodeArtifacts] = useRecoilState(store.codeArtifacts); + const localize = useLocalize(); const [includeShadcnui, setIncludeShadcnui] = useRecoilState(store.includeShadcnui); const [customPromptMode, setCustomPromptMode] = useRecoilState(store.customPromptMode); - const localize = useLocalize(); - - const handleCodeArtifactsChange = (value: boolean) => { - setCodeArtifacts(value); - if (!value) { - setIncludeShadcnui(false); - setCustomPromptMode(false); - } - }; const handleIncludeShadcnuiChange = (value: boolean) => { setIncludeShadcnui(value); @@ -33,20 +24,13 @@ export default function CodeArtifacts() {

{localize('com_ui_artifacts')}

-
diff --git a/client/src/hooks/Chat/useChatFunctions.ts b/client/src/hooks/Chat/useChatFunctions.ts index 3b7b11132ef..09c70a02604 100644 --- a/client/src/hooks/Chat/useChatFunctions.ts +++ b/client/src/hooks/Chat/useChatFunctions.ts @@ -64,7 +64,6 @@ export default function useChatFunctions({ setSubmission: SetterOrUpdater; setLatestMessage?: SetterOrUpdater; }) { - const codeArtifacts = useRecoilValue(store.codeArtifacts); const includeShadcnui = useRecoilValue(store.includeShadcnui); const customPromptMode = useRecoilValue(store.customPromptMode); const resetLatestMultiMessage = useResetRecoilState(store.latestMessageFamily(index + 1)); @@ -170,7 +169,7 @@ export default function useChatFunctions({ endpointType, overrideConvoId, overrideUserMessageId, - artifacts: getArtifactsMode({ codeArtifacts, includeShadcnui, customPromptMode }), + artifacts: getArtifactsMode({ includeShadcnui, customPromptMode }), }, convo, ) as TEndpointOption; @@ -228,7 +227,6 @@ export default function useChatFunctions({ conversationId, unfinished: false, isCreatedByUser: false, - isEdited: isEditOrContinue, iconURL: convo?.iconURL, model: convo?.model, error: false, diff --git a/client/src/store/settings.ts b/client/src/store/settings.ts index 901c48c2c4d..9258e59c7aa 100644 --- a/client/src/store/settings.ts +++ b/client/src/store/settings.ts @@ -42,7 +42,6 @@ const localStorageAtoms = { // Beta features settings modularChat: atomWithLocalStorage('modularChat', true), LaTeXParsing: atomWithLocalStorage('LaTeXParsing', true), - codeArtifacts: atomWithLocalStorage('codeArtifacts', false), includeShadcnui: atomWithLocalStorage('includeShadcnui', false), customPromptMode: atomWithLocalStorage('customPromptMode', false), diff --git a/client/src/utils/artifacts.ts b/client/src/utils/artifacts.ts index 69e2b91ee50..0d571a760dd 100644 --- a/client/src/utils/artifacts.ts +++ b/client/src/utils/artifacts.ts @@ -6,17 +6,13 @@ import type { } from '@codesandbox/sandpack-react'; export const getArtifactsMode = ({ - codeArtifacts, includeShadcnui, customPromptMode, }: { - codeArtifacts: boolean; includeShadcnui: boolean; customPromptMode: boolean; }): ArtifactModes | undefined => { - if (!codeArtifacts) { - return undefined; - } else if (customPromptMode) { + if (customPromptMode) { return ArtifactModes.CUSTOM; } else if (includeShadcnui) { return ArtifactModes.SHADCNUI; From 8d84017a0ce9f1f2af6cda2d23df18f16d3bc376 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 11 Feb 2025 14:51:46 -0500 Subject: [PATCH 2/9] refactor: allow hiding side panel while allowing artifacts view --- client/src/components/Chat/ChatView.tsx | 4 +- client/src/components/Chat/Presentation.tsx | 66 ++-- client/src/components/SidePanel/SidePanel.tsx | 309 ++++++------------ .../components/SidePanel/SidePanelGroup.tsx | 152 +++++++++ client/src/components/SidePanel/index.ts | 2 +- client/src/utils/index.ts | 18 + 6 files changed, 296 insertions(+), 255 deletions(-) create mode 100644 client/src/components/SidePanel/SidePanelGroup.tsx diff --git a/client/src/components/Chat/ChatView.tsx b/client/src/components/Chat/ChatView.tsx index 0ee64ef62b6..dbf39ee8453 100644 --- a/client/src/components/Chat/ChatView.tsx +++ b/client/src/components/Chat/ChatView.tsx @@ -28,7 +28,7 @@ function ChatView({ index = 0 }: { index?: number }) { select: useCallback( (data: TMessage[]) => { const dataTree = buildTree({ messages: data, fileMap }); - return dataTree?.length === 0 ? null : dataTree ?? null; + return dataTree?.length === 0 ? null : (dataTree ?? null); }, [fileMap], ), @@ -62,7 +62,7 @@ function ChatView({ index = 0 }: { index?: number }) { - + {content}
diff --git a/client/src/components/Chat/Presentation.tsx b/client/src/components/Chat/Presentation.tsx index 68ec6f0f2e0..9228fd880ce 100644 --- a/client/src/components/Chat/Presentation.tsx +++ b/client/src/components/Chat/Presentation.tsx @@ -1,36 +1,19 @@ import { useRecoilValue } from 'recoil'; import { useEffect, useMemo } from 'react'; -import { FileSources, LocalStorageKeys, getConfigDefaults } from 'librechat-data-provider'; +import { FileSources, LocalStorageKeys } from 'librechat-data-provider'; import type { ExtendedFile } from '~/common'; -import { useDeleteFilesMutation, useGetStartupConfig } from '~/data-provider'; +import { useDeleteFilesMutation } from '~/data-provider'; import DragDropWrapper from '~/components/Chat/Input/Files/DragDropWrapper'; import Artifacts from '~/components/Artifacts/Artifacts'; -import { SidePanel } from '~/components/SidePanel'; +import { SidePanelGroup } from '~/components/SidePanel'; import { useSetFilesToDelete } from '~/hooks'; import { EditorProvider } from '~/Providers'; import store from '~/store'; -const defaultInterface = getConfigDefaults().interface; - -export default function Presentation({ - children, - useSidePanel = false, - panel, -}: { - children: React.ReactNode; - panel?: React.ReactNode; - useSidePanel?: boolean; -}) { - const { data: startupConfig } = useGetStartupConfig(); +export default function Presentation({ children }: { children: React.ReactNode }) { const artifacts = useRecoilValue(store.artifactsState); - const hideSidePanel = useRecoilValue(store.hideSidePanel); const artifactsVisible = useRecoilValue(store.artifactsVisible); - const interfaceConfig = useMemo( - () => startupConfig?.interface ?? defaultInterface, - [startupConfig], - ); - const setFilesToDelete = useSetFilesToDelete(); const { mutateAsync } = useDeleteFilesMutation({ @@ -82,33 +65,24 @@ export default function Presentation({
); - if (useSidePanel && !hideSidePanel && interfaceConfig.sidePanel === true) { - return ( - - 0 ? ( - - - - ) : null - } - > -
- {children} -
-
-
- ); - } - return ( - {layout()} - {panel != null && panel} + 0 ? ( + + + + ) : null + } + > +
+ {children} +
+
); } diff --git a/client/src/components/SidePanel/SidePanel.tsx b/client/src/components/SidePanel/SidePanel.tsx index 51977ab1572..47b3f58d51e 100644 --- a/client/src/components/SidePanel/SidePanel.tsx +++ b/client/src/components/SidePanel/SidePanel.tsx @@ -1,78 +1,58 @@ -import throttle from 'lodash/throttle'; -import { getConfigDefaults } from 'librechat-data-provider'; +import { useState, useCallback, useMemo, memo } from 'react'; import { useUserKeyQuery } from 'librechat-data-provider/react-query'; -import { useState, useRef, useCallback, useEffect, useMemo, memo } from 'react'; import type { TEndpointsConfig, TInterfaceConfig } from 'librechat-data-provider'; import type { ImperativePanelHandle } from 'react-resizable-panels'; -import { ResizableHandleAlt, ResizablePanel, ResizablePanelGroup } from '~/components/ui/Resizable'; -import { useGetEndpointsQuery, useGetStartupConfig } from '~/data-provider'; +import { ResizableHandleAlt, ResizablePanel } from '~/components/ui/Resizable'; import { useMediaQuery, useLocalStorage, useLocalize } from '~/hooks'; import useSideNavLinks from '~/hooks/Nav/useSideNavLinks'; +import { useGetEndpointsQuery } from '~/data-provider'; import NavToggle from '~/components/Nav/NavToggle'; import { cn, getEndpointField } from '~/utils'; import { useChatContext } from '~/Providers'; import Switcher from './Switcher'; import Nav from './Nav'; -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 normalizeLayout = (layout: number[]) => { - const sum = layout.reduce((acc, size) => acc + size, 0); - if (Math.abs(sum - 100) < 0.01) { - return layout.map((size) => Number(size.toFixed(2))); - } - - const factor = 100 / sum; - const normalizedLayout = layout.map((size) => Number((size * factor).toFixed(2))); - - const adjustedSum = normalizedLayout.reduce( - (acc, size, index) => (index === layout.length - 1 ? acc : acc + size), - 0, - ); - normalizedLayout[normalizedLayout.length - 1] = Number((100 - adjustedSum).toFixed(2)); - - return normalizedLayout; -}; -const SidePanel = ({ - defaultLayout = [97, 3], - defaultCollapsed = false, - fullPanelCollapse = false, +const SidePanelGroup = ({ + defaultSize, + panelRef, navCollapsedSize = 3, - artifacts, - children, -}: SidePanelProps) => { + 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 [isHovering, setIsHovering] = useState(false); - const [minSize, setMinSize] = useState(defaultMinSize); const [newUser, setNewUser] = useLocalStorage('newUser', true); - const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed); - const [fullCollapse, setFullCollapse] = useState(fullPanelCollapse); - const [collapsedSize, setCollapsedSize] = useState(navCollapsedSize); const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery(); - const { data: startupConfig } = useGetStartupConfig(); - const interfaceConfig = useMemo( - () => (startupConfig?.interface ?? defaultInterface) as Partial, - [startupConfig], - ); const isSmallScreen = useMediaQuery('(max-width: 767px)'); const { conversation } = useChatContext(); const { endpoint } = conversation ?? {}; const { data: keyExpiry = { expiresAt: undefined } } = useUserKeyQuery(endpoint ?? ''); - const panelRef = useRef(null); - const defaultActive = useMemo(() => { const activePanel = localStorage.getItem('side:active-panel'); return typeof activePanel === 'string' ? activePanel : undefined; @@ -113,46 +93,6 @@ const SidePanel = ({ interfaceConfig, }); - 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]); - - // eslint-disable-next-line react-hooks/exhaustive-deps - 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 toggleNavVisible = useCallback(() => { if (newUser) { setNewUser(false); @@ -173,129 +113,86 @@ const SidePanel = ({ } }, [isCollapsed, newUser, setNewUser, navCollapsedSize]); - 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" +
setIsHovering(true)} + onMouseLeave={() => setIsHovering(false)} + className="relative flex w-px items-center justify-center" > - - {children} - - {artifacts != null && ( - <> - - - {artifacts} - - + +
+ {(!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 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', )} -
setIsHovering(true)} - onMouseLeave={() => setIsHovering(false)} - className="relative flex w-px items-center justify-center" - > - + {interfaceConfig.modelSelect === true && ( +
-
- {(!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 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', - )} - > - {interfaceConfig.modelSelect === true && ( -
- -
- )} -