📦 feat: Model & Assistants Combobox for Side Panel (#2380)

* WIP: dynamic settings

* WIP: update tests and validations

* refactor(SidePanel): use hook for Links

* WIP: dynamic settings, slider implemented

* feat(useDebouncedInput): dynamic typing with generic

* refactor(generate): add `custom` optionType to be non-conforming to conversation schema

* feat: DynamicDropdown

* refactor(DynamicSlider): custom optionType handling and useEffect for conversation updates elsewhere

* refactor(Panel): add more test cases

* chore(DynamicSlider): note

* refactor(useDebouncedInput): import defaultDebouncedDelay from ~/common`

* WIP: implement remaining ComponentTypes

* chore: add com_sidepanel_parameters

* refactor: add langCode handling for dynamic settings

* chore(useOriginNavigate): change path to '/c/'

* refactor: explicit textarea focus on new convo, share textarea idea via ~/common

* refactor: useParameterEffects: reset if convo or preset Ids change, share and maintain statefulness in side panel

* wip: combobox

* chore: minor styling for Select components

* wip: combobox select styling for side panel

* feat: complete combobox

* refactor: model select for side panel switcher

* refactor(Combobox): add portal

* chore: comment out dynamic parameters panel for future PR and delete prompt files

* refactor(Combobox): add icon field for options, change hover bg-color, add displayValue

* fix(useNewConvo): proper textarea focus with setTimeout

* refactor(AssistantSwitcher): use Combobox

* refactor(ModelSwitcher): add textarea focus on model switch
This commit is contained in:
Danny Avila 2024-04-10 14:27:22 -04:00 committed by GitHub
parent f64a2cb0b0
commit 8e5f1ad575
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 2850 additions and 462 deletions

View file

@ -1,18 +1,15 @@
import throttle from 'lodash/throttle';
import { ArrowRightToLine } from 'lucide-react';
import { useState, useRef, useCallback, useEffect, useMemo, memo } from 'react';
import { useGetEndpointsQuery, useUserKeyQuery } from 'librechat-data-provider/react-query';
import type { ImperativePanelHandle } from 'react-resizable-panels';
import { EModelEndpoint, type TEndpointsConfig } from 'librechat-data-provider';
import type { NavLink } from '~/common';
import { ResizableHandleAlt, ResizablePanel, ResizablePanelGroup } from '~/components/ui/Resizable';
import { TooltipProvider, Tooltip } from '~/components/ui/Tooltip';
import { Blocks, AttachmentIcon } from '~/components/svg';
import useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
import { useMediaQuery, useLocalStorage } from '~/hooks';
import { Separator } from '~/components/ui/Separator';
import NavToggle from '~/components/Nav/NavToggle';
import PanelSwitch from './Builder/PanelSwitch';
import FilesPanel from './Files/Panel';
import { useChatContext } from '~/Providers';
import Switcher from './Switcher';
import { cn } from '~/utils';
import Nav from './Nav';
@ -43,6 +40,8 @@ const SidePanel = ({
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
const { data: keyExpiry = { expiresAt: undefined } } = useUserKeyQuery(EModelEndpoint.assistants);
const isSmallScreen = useMediaQuery('(max-width: 767px)');
const { conversation } = useChatContext();
const { endpoint } = conversation ?? {};
const panelRef = useRef<ImperativePanelHandle>(null);
@ -52,49 +51,25 @@ const SidePanel = ({
}, []);
const assistants = useMemo(() => endpointsConfig?.[EModelEndpoint.assistants], [endpointsConfig]);
const userProvidesKey = useMemo(() => !!assistants?.userProvide, [assistants]);
const userProvidesKey = useMemo(
() => !!endpointsConfig?.[endpoint ?? '']?.userProvide,
[endpointsConfig, endpoint],
);
const keyProvided = useMemo(
() => (userProvidesKey ? !!keyExpiry?.expiresAt : true),
[keyExpiry?.expiresAt, userProvidesKey],
);
const Links = useMemo(() => {
const links: NavLink[] = [];
if (assistants && assistants.disableBuilder !== true && keyProvided) {
links.push({
title: 'com_sidepanel_assistant_builder',
label: '',
icon: Blocks,
id: 'assistants',
Component: PanelSwitch,
});
}
const hidePanel = useCallback(() => {
setIsCollapsed(true);
setCollapsedSize(0);
setMinSize(defaultMinSize);
setFullCollapse(true);
localStorage.setItem('fullPanelCollapse', 'true');
panelRef.current?.collapse();
}, []);
links.push({
title: 'com_sidepanel_attach_files',
label: '',
icon: AttachmentIcon,
id: 'files',
Component: FilesPanel,
});
links.push({
title: 'com_sidepanel_hide_panel',
label: '',
icon: ArrowRightToLine,
onClick: () => {
setIsCollapsed(true);
setCollapsedSize(0);
setMinSize(defaultMinSize);
setFullCollapse(true);
localStorage.setItem('fullPanelCollapse', 'true');
panelRef.current?.collapse();
},
id: 'hide-panel',
});
return links;
}, [assistants, keyProvided]);
const Links = useSideNavLinks({ hidePanel, assistants, keyProvided, endpoint });
// eslint-disable-next-line react-hooks/exhaustive-deps
const throttledSaveLayout = useCallback(
@ -206,18 +181,15 @@ const SidePanel = ({
: 'opacity-100',
)}
>
{keyProvided && (
<div
className={cn(
'sticky left-0 right-0 top-0 z-[100] flex h-[52px] flex-wrap items-center justify-center bg-white dark:bg-gray-850',
isCollapsed ? 'h-[52px]' : 'px-2',
)}
>
<Switcher isCollapsed={isCollapsed} />
<Separator className="bg-gray-100/50 dark:bg-gray-600" />
</div>
)}
<div
className={cn(
'sticky left-0 right-0 top-0 z-[100] flex h-[52px] flex-wrap items-center justify-center bg-white dark:bg-gray-850',
isCollapsed ? 'h-[52px]' : 'px-2',
)}
>
<Switcher isCollapsed={isCollapsed} endpointKeyProvided={keyProvided} />
<Separator className="bg-gray-100/50 dark:bg-gray-600" />
</div>
<Nav
resize={panelRef.current?.resize}
isCollapsed={isCollapsed}