🔧 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
This commit is contained in:
Flynn 2024-03-21 09:15:25 -04:00 committed by GitHub
parent 30f6d90cfe
commit f521040784
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 33 additions and 9 deletions

View file

@ -5,7 +5,7 @@ import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query';
import type { TPlugin } from 'librechat-data-provider'; import type { TPlugin } from 'librechat-data-provider';
import type { TModelSelectProps } from '~/common'; import type { TModelSelectProps } from '~/common';
import { SelectDropDown, MultiSelectDropDown, SelectDropDownPop, Button } from '~/components/ui'; import { SelectDropDown, MultiSelectDropDown, SelectDropDownPop, Button } from '~/components/ui';
import { useSetOptions, useAuthContext, useMediaQuery } from '~/hooks'; import { useSetOptions, useAuthContext, useMediaQuery, useLocalize } from '~/hooks';
import { cn, cardStyle } from '~/utils/'; import { cn, cardStyle } from '~/utils/';
import store from '~/store'; import store from '~/store';
@ -26,6 +26,7 @@ export default function Plugins({
showAbove, showAbove,
popover = false, popover = false,
}: TModelSelectProps) { }: TModelSelectProps) {
const localize = useLocalize();
const { data: allPlugins } = useAvailablePluginsQuery(); const { data: allPlugins } = useAvailablePluginsQuery();
const [visible, setVisibility] = useState<boolean>(true); const [visible, setVisibility] = useState<boolean>(true);
const [availableTools, setAvailableTools] = useRecoilState(store.availableTools); const [availableTools, setAvailableTools] = useRecoilState(store.availableTools);
@ -84,7 +85,7 @@ export default function Plugins({
type="button" type="button"
className={cn( className={cn(
cardStyle, cardStyle,
'min-w-4 z-40 flex h-[40px] flex-none items-center justify-center px-3 hover:bg-white focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-700', '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)} onClick={() => setVisibility((prev) => !prev)}
> >
@ -100,7 +101,7 @@ export default function Plugins({
setValue={setOption('model')} setValue={setOption('model')}
availableValues={models} availableValues={models}
showAbove={showAbove} showAbove={showAbove}
className={cn(cardStyle, 'min-w-60 z-40 flex w-64 sm:w-48', visible ? '' : 'hidden')} className={cn(cardStyle, 'z-40 flex w-64 min-w-60 sm:w-48', visible ? '' : 'hidden')}
/> />
<MultiSelectDropDown <MultiSelectDropDown
value={conversation.tools || []} value={conversation.tools || []}
@ -109,7 +110,8 @@ export default function Plugins({
availableValues={availableTools} availableValues={availableTools}
optionValueKey="pluginKey" optionValueKey="pluginKey"
showAbove={showAbove} showAbove={showAbove}
className={cn(cardStyle, 'min-w-60 z-50 w-64 sm:w-48', visible ? '' : 'hidden')} className={cn(cardStyle, 'z-50 w-64 min-w-60 sm:w-48', visible ? '' : 'hidden')}
searchPlaceholder={localize('com_ui_select_search_plugin')}
/> />
</> </>
); );

View file

@ -11,7 +11,7 @@ import {
MultiSelectPop, MultiSelectPop,
Button, Button,
} from '~/components/ui'; } from '~/components/ui';
import { useSetIndexOptions, useAuthContext, useMediaQuery } from '~/hooks'; import { useSetIndexOptions, useAuthContext, useMediaQuery, useLocalize } from '~/hooks';
import { cn, cardStyle } from '~/utils/'; import { cn, cardStyle } from '~/utils/';
import store from '~/store'; import store from '~/store';
@ -32,6 +32,7 @@ export default function PluginsByIndex({
showAbove, showAbove,
popover = false, popover = false,
}: TModelSelectProps) { }: TModelSelectProps) {
const localize = useLocalize();
const { data: allPlugins } = useAvailablePluginsQuery(); const { data: allPlugins } = useAvailablePluginsQuery();
const [visible, setVisibility] = useState<boolean>(true); const [visible, setVisibility] = useState<boolean>(true);
const [availableTools, setAvailableTools] = useRecoilState(store.availableTools); const [availableTools, setAvailableTools] = useRecoilState(store.availableTools);
@ -92,7 +93,7 @@ export default function PluginsByIndex({
type="button" type="button"
className={cn( className={cn(
cardStyle, cardStyle,
'min-w-4 z-40 flex h-[40px] flex-none items-center justify-center px-3 hover:bg-white focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-700', '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)} onClick={() => setVisibility((prev) => !prev)}
> >
@ -120,6 +121,7 @@ export default function PluginsByIndex({
optionValueKey="pluginKey" optionValueKey="pluginKey"
showAbove={false} showAbove={false}
showLabel={false} showLabel={false}
searchPlaceholder={localize('com_ui_select_search_plugin')}
/> />
</> </>
)} )}

View file

@ -49,6 +49,14 @@ export default function MultiSearch({
*/ */
function defaultGetStringKey(node: unknown): string { function defaultGetStringKey(node: unknown): string {
if (typeof node === 'string') { if (typeof node === 'string') {
// BUGFIX: Detect psedeo separators and make sure they don't appear in the list when filtering items
// it makes sure (for the most part) that the model name starts and ends with dashes
// The long-term fix here would be to enable seperators (model groupings) but there's no
// feature mocks for such a thing yet
if (node.startsWith('---') && node.endsWith('---')) {
return '';
}
return node.toUpperCase(); return node.toUpperCase();
} }
// This should be a noop, but it's here for redundancy // This should be a noop, but it's here for redundancy

View file

@ -18,6 +18,7 @@ export type TMultiSelectDropDownProps = {
containerClassName?: string; containerClassName?: string;
isSelected: (value: string) => boolean; isSelected: (value: string) => boolean;
className?: string; className?: string;
searchPlaceholder?: string;
optionValueKey?: string; optionValueKey?: string;
}; };
@ -32,6 +33,7 @@ function MultiSelectDropDown({
containerClassName, containerClassName,
isSelected, isSelected,
className, className,
searchPlaceholder,
optionValueKey = 'value', optionValueKey = 'value',
}: TMultiSelectDropDownProps) { }: TMultiSelectDropDownProps) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -44,10 +46,13 @@ function MultiSelectDropDown({
setIsOpen(true); setIsOpen(true);
}; };
// Detemine if we should to convert this component into a searchable select. If we have enough elements, a search
// input will appear near the top of the menu, allowing correct filtering of different model menu items. This will // input will appear near the top of the menu, allowing correct filtering of different model menu items. This will
// reset once the component is unmounted (as per a normal search) // reset once the component is unmounted (as per a normal search)
const [filteredValues, searchRender] = useMultiSearch<TPlugin[]>(availableValues); const [filteredValues, searchRender] = useMultiSearch<TPlugin[]>(
availableValues,
searchPlaceholder,
(option) => (option.name || '').toUpperCase(),
);
const hasSearchRender = Boolean(searchRender); const hasSearchRender = Boolean(searchRender);
const options = hasSearchRender ? filteredValues : availableValues; const options = hasSearchRender ? filteredValues : availableValues;

View file

@ -18,6 +18,7 @@ type SelectDropDownProps = {
isSelected: (value: string) => boolean; isSelected: (value: string) => boolean;
className?: string; className?: string;
optionValueKey?: string; optionValueKey?: string;
searchPlaceholder?: string;
}; };
function MultiSelectPop({ function MultiSelectPop({
@ -30,6 +31,7 @@ function MultiSelectPop({
containerClassName, containerClassName,
isSelected, isSelected,
optionValueKey = 'value', optionValueKey = 'value',
searchPlaceholder,
}: SelectDropDownProps) { }: SelectDropDownProps) {
// const localize = useLocalize(); // const localize = useLocalize();
@ -37,7 +39,11 @@ function MultiSelectPop({
const excludeIds = ['select-plugin', 'plugins-label', 'selected-plugins']; const excludeIds = ['select-plugin', 'plugins-label', 'selected-plugins'];
// Detemine if we should to convert this component into a searchable select // Detemine if we should to convert this component into a searchable select
const [filteredValues, searchRender] = useMultiSearch<TPlugin[]>(availableValues); const [filteredValues, searchRender] = useMultiSearch<TPlugin[]>(
availableValues,
searchPlaceholder,
(option) => (option.name || '').toUpperCase(),
);
const hasSearchRender = Boolean(searchRender); const hasSearchRender = Boolean(searchRender);
const options = hasSearchRender ? filteredValues : availableValues; const options = hasSearchRender ? filteredValues : availableValues;

View file

@ -59,6 +59,7 @@ export default {
com_ui_model: 'Model', com_ui_model: 'Model',
com_ui_select_model: 'Select a model', com_ui_select_model: 'Select a model',
com_ui_select_search_model: 'Search model by name', com_ui_select_search_model: 'Search model by name',
com_ui_select_search_plugin: 'Search plugin by name',
com_ui_use_prompt: 'Use prompt', com_ui_use_prompt: 'Use prompt',
com_ui_prev: 'Prev', com_ui_prev: 'Prev',
com_ui_next: 'Next', com_ui_next: 'Next',