LibreChat/client/src/components/Input/ModelSelect/Plugins.tsx
Flynn f521040784
🔧 fix(menu): Menu Item Filter Improvements (#2153)
* small-fix: Ensure that fake seperators in model lists do not show in search

* Ensure Plugin search uses correct placeholder and key filtering in search
2024-03-21 09:15:25 -04:00

118 lines
3.7 KiB
TypeScript

import { useRecoilState } from 'recoil';
import { useState, useEffect } from 'react';
import { ChevronDownIcon } from 'lucide-react';
import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query';
import type { TPlugin } from 'librechat-data-provider';
import type { TModelSelectProps } from '~/common';
import { SelectDropDown, MultiSelectDropDown, SelectDropDownPop, Button } from '~/components/ui';
import { useSetOptions, useAuthContext, useMediaQuery, useLocalize } from '~/hooks';
import { cn, cardStyle } from '~/utils/';
import store from '~/store';
const pluginStore: TPlugin = {
name: 'Plugin store',
pluginKey: 'pluginStore',
isButton: true,
description: '',
icon: '',
authConfig: [],
authenticated: false,
};
export default function Plugins({
conversation,
setOption,
models,
showAbove,
popover = false,
}: TModelSelectProps) {
const localize = useLocalize();
const { data: allPlugins } = useAvailablePluginsQuery();
const [visible, setVisibility] = useState<boolean>(true);
const [availableTools, setAvailableTools] = useRecoilState(store.availableTools);
const { checkPluginSelection, setTools } = useSetOptions();
const { user } = useAuthContext();
const isSmallScreen = useMediaQuery('(max-width: 640px)');
const Menu = popover ? SelectDropDownPop : SelectDropDown;
useEffect(() => {
if (isSmallScreen) {
setVisibility(false);
}
}, [isSmallScreen]);
useEffect(() => {
if (!user) {
return;
}
if (!allPlugins) {
return;
}
if (!user.plugins || user.plugins.length === 0) {
setAvailableTools([pluginStore]);
return;
}
const tools = [...user.plugins]
.map((el) => allPlugins.find((plugin: TPlugin) => plugin.pluginKey === el))
.filter((el): el is TPlugin => el !== undefined);
/* Filter Last Selected Tools */
const localStorageItem = localStorage.getItem('lastSelectedTools');
if (!localStorageItem) {
return setAvailableTools([...tools, pluginStore]);
}
const lastSelectedTools = JSON.parse(localStorageItem);
const filteredTools = lastSelectedTools.filter((tool: TPlugin) =>
tools.some((existingTool) => existingTool.pluginKey === tool.pluginKey),
);
localStorage.setItem('lastSelectedTools', JSON.stringify(filteredTools));
setAvailableTools([...tools, pluginStore]);
// setAvailableTools is a recoil state setter, so it's safe to use it in useEffect
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [allPlugins, user]);
if (!conversation) {
return null;
}
return (
<>
<Button
type="button"
className={cn(
cardStyle,
'z-40 flex h-[40px] min-w-4 flex-none items-center justify-center px-3 hover:bg-white focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-700',
)}
onClick={() => setVisibility((prev) => !prev)}
>
<ChevronDownIcon
className={cn(
!visible ? 'rotate-180 transform' : '',
'w-4 text-gray-600 dark:text-white',
)}
/>
</Button>
<Menu
value={conversation.model ?? ''}
setValue={setOption('model')}
availableValues={models}
showAbove={showAbove}
className={cn(cardStyle, 'z-40 flex w-64 min-w-60 sm:w-48', visible ? '' : 'hidden')}
/>
<MultiSelectDropDown
value={conversation.tools || []}
isSelected={checkPluginSelection}
setSelected={setTools}
availableValues={availableTools}
optionValueKey="pluginKey"
showAbove={showAbove}
className={cn(cardStyle, 'z-50 w-64 min-w-60 sm:w-48', visible ? '' : 'hidden')}
searchPlaceholder={localize('com_ui_select_search_plugin')}
/>
</>
);
}