mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 01:10:14 +01:00
🌟 fix: Handle Assistants Edge Cases, Improve Filter Styling (#2201)
* fix(assistants): default query to limit of 100 and `desc` order * refactor(useMultiSearch): use object as params and fix styling for assistants * feat: informative message for thread initialization failing due to long message
This commit is contained in:
parent
a4f4ec85f8
commit
8fc52348e8
11 changed files with 71 additions and 24 deletions
|
|
@ -149,7 +149,7 @@ router.delete('/:id', async (req, res) => {
|
||||||
*/
|
*/
|
||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { limit, order, after, before } = req.query;
|
const { limit = 100, order = 'desc', after, before } = req.query;
|
||||||
const query = { limit, order, after, before };
|
const query = { limit, order, after, before };
|
||||||
|
|
||||||
const azureConfig = req.app.locals[EModelEndpoint.azureOpenAI];
|
const azureConfig = req.app.locals[EModelEndpoint.azureOpenAI];
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,12 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
|
||||||
: ''
|
: ''
|
||||||
}`;
|
}`;
|
||||||
return sendResponse(res, messageData, errorMessage);
|
return sendResponse(res, messageData, errorMessage);
|
||||||
|
} else if (error?.message?.includes('string too long')) {
|
||||||
|
return sendResponse(
|
||||||
|
res,
|
||||||
|
messageData,
|
||||||
|
'Message too long. The Assistants API has a limit of 32,768 characters per message. Please shorten it and try again.',
|
||||||
|
);
|
||||||
} else if (error?.message?.includes(ViolationTypes.TOKEN_BALANCE)) {
|
} else if (error?.message?.includes(ViolationTypes.TOKEN_BALANCE)) {
|
||||||
return sendResponse(res, messageData, error.message);
|
return sendResponse(res, messageData, error.message);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState, useMemo, useEffect } from 'react';
|
import { useState, useMemo, useEffect } from 'react';
|
||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import { TPreset, defaultOrderQuery } from 'librechat-data-provider';
|
import { defaultOrderQuery } from 'librechat-data-provider';
|
||||||
|
import type { TPreset } from 'librechat-data-provider';
|
||||||
import type { TModelSelectProps, Option } from '~/common';
|
import type { TModelSelectProps, Option } from '~/common';
|
||||||
import { Label, HoverCard, SelectDropDown, HoverCardTrigger } from '~/components/ui';
|
import { Label, HoverCard, SelectDropDown, HoverCardTrigger } from '~/components/ui';
|
||||||
import { cn, defaultTextProps, removeFocusOutlines, mapAssistants } from '~/utils';
|
import { cn, defaultTextProps, removeFocusOutlines, mapAssistants } from '~/utils';
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,8 @@ export default function AssistantSelect({
|
||||||
showLabel={false}
|
showLabel={false}
|
||||||
emptyTitle={true}
|
emptyTitle={true}
|
||||||
containerClassName="flex-grow"
|
containerClassName="flex-grow"
|
||||||
|
searchClassName="dark:from-gray-850"
|
||||||
|
searchPlaceholder={localize('com_assistants_search_name')}
|
||||||
optionsClass="hover:bg-gray-20/50 dark:border-gray-700"
|
optionsClass="hover:bg-gray-20/50 dark:border-gray-700"
|
||||||
optionsListClass="rounded-lg shadow-lg dark:bg-gray-850 dark:border-gray-700 dark:last:border"
|
optionsListClass="rounded-lg shadow-lg dark:bg-gray-850 dark:border-gray-700 dark:last:border"
|
||||||
currentValueClass={cn(
|
currentValueClass={cn(
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@ export default function MultiSearch({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
className = '',
|
||||||
}: {
|
}: {
|
||||||
value: string | null;
|
value: string | null;
|
||||||
onChange: (filter: string) => void;
|
onChange: (filter: string) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const onChangeHandler: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
const onChangeHandler: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||||
|
|
@ -21,7 +23,12 @@ export default function MultiSearch({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group sticky left-0 top-0 z-10 flex h-12 items-center gap-2 bg-gradient-to-b from-white from-65% to-transparent px-2 px-3 py-2 text-black transition-colors duration-300 focus:bg-gradient-to-b focus:from-white focus:to-white/50 dark:from-gray-700 dark:to-transparent dark:text-white dark:focus:from-white/10 dark:focus:to-white/20">
|
<div
|
||||||
|
className={cn(
|
||||||
|
'group sticky left-0 top-0 z-10 flex h-12 items-center gap-2 bg-gradient-to-b from-white from-65% to-transparent px-2 px-3 py-2 text-black transition-colors duration-300 focus:bg-gradient-to-b focus:from-white focus:to-white/50 dark:from-gray-700 dark:to-transparent dark:text-white dark:focus:from-white/10 dark:focus:to-white/20',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Search className="h-4 w-4 text-gray-500 transition-colors duration-300 dark:group-focus-within:text-gray-300 dark:group-hover:text-gray-300" />
|
<Search className="h-4 w-4 text-gray-500 transition-colors duration-300 dark:group-focus-within:text-gray-300 dark:group-hover:text-gray-300" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -69,17 +76,27 @@ function defaultGetStringKey(node: unknown): string {
|
||||||
* @param availableOptions
|
* @param availableOptions
|
||||||
* @param placeholder
|
* @param placeholder
|
||||||
* @param getTextKeyOverride
|
* @param getTextKeyOverride
|
||||||
|
* @param className - Additional classnames to add to the search container
|
||||||
|
* @param disabled - If the search should be disabled
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function useMultiSearch<OptionsType extends unknown[]>(
|
export function useMultiSearch<OptionsType extends unknown[]>({
|
||||||
availableOptions: OptionsType,
|
availableOptions,
|
||||||
placeholder?: string,
|
placeholder,
|
||||||
getTextKeyOverride?: (node: OptionsType[0]) => string,
|
getTextKeyOverride,
|
||||||
): [OptionsType, React.ReactNode] {
|
className,
|
||||||
|
disabled = false,
|
||||||
|
}: {
|
||||||
|
availableOptions: OptionsType;
|
||||||
|
placeholder?: string;
|
||||||
|
getTextKeyOverride?: (node: OptionsType[0]) => string;
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}): [OptionsType, React.ReactNode] {
|
||||||
const [filterValue, setFilterValue] = useState<string | null>(null);
|
const [filterValue, setFilterValue] = useState<string | null>(null);
|
||||||
|
|
||||||
// We conditionally show the search when there's more than 10 elements in the menu
|
// We conditionally show the search when there's more than 10 elements in the menu
|
||||||
const shouldShowSearch = availableOptions.length > 10;
|
const shouldShowSearch = availableOptions.length > 10 && !disabled;
|
||||||
|
|
||||||
// Define the helper function used to enable search
|
// Define the helper function used to enable search
|
||||||
// If this is invalidly described, we will assume developer error - tf. avoid rendering
|
// If this is invalidly described, we will assume developer error - tf. avoid rendering
|
||||||
|
|
@ -103,7 +120,12 @@ export function useMultiSearch<OptionsType extends unknown[]>(
|
||||||
const onSearchChange = useCallback((nextFilterValue) => setFilterValue(nextFilterValue), []);
|
const onSearchChange = useCallback((nextFilterValue) => setFilterValue(nextFilterValue), []);
|
||||||
|
|
||||||
const searchRender = shouldShowSearch ? (
|
const searchRender = shouldShowSearch ? (
|
||||||
<MultiSearch value={filterValue} onChange={onSearchChange} placeholder={placeholder} />
|
<MultiSearch
|
||||||
|
value={filterValue}
|
||||||
|
className={className}
|
||||||
|
onChange={onSearchChange}
|
||||||
|
placeholder={placeholder}
|
||||||
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
return [filteredOptions, searchRender];
|
return [filteredOptions, searchRender];
|
||||||
|
|
|
||||||
|
|
@ -48,11 +48,12 @@ function MultiSelectDropDown({
|
||||||
|
|
||||||
// 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[]>(
|
const [filteredValues, searchRender] = useMultiSearch<TPlugin[]>({
|
||||||
availableValues,
|
availableOptions: availableValues,
|
||||||
searchPlaceholder,
|
placeholder: searchPlaceholder,
|
||||||
(option) => (option.name || '').toUpperCase(),
|
getTextKeyOverride: (option) => (option.name || '').toUpperCase(),
|
||||||
);
|
});
|
||||||
|
|
||||||
const hasSearchRender = Boolean(searchRender);
|
const hasSearchRender = Boolean(searchRender);
|
||||||
const options = hasSearchRender ? filteredValues : availableValues;
|
const options = hasSearchRender ? filteredValues : availableValues;
|
||||||
|
|
||||||
|
|
@ -65,6 +66,7 @@ function MultiSelectDropDown({
|
||||||
<div className={cn('flex items-center justify-center gap-2', containerClassName ?? '')}>
|
<div className={cn('flex items-center justify-center gap-2', containerClassName ?? '')}>
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
{/* the function typing is correct but there's still an issue here */}
|
{/* the function typing is correct but there's still an issue here */}
|
||||||
|
{/* @ts-ignore */}
|
||||||
<Listbox value={value} onChange={handleSelect} disabled={disabled}>
|
<Listbox value={value} onChange={handleSelect} disabled={disabled}>
|
||||||
{() => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -39,11 +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[]>(
|
const [filteredValues, searchRender] = useMultiSearch<TPlugin[]>({
|
||||||
availableValues,
|
availableOptions: availableValues,
|
||||||
searchPlaceholder,
|
placeholder: searchPlaceholder,
|
||||||
(option) => (option.name || '').toUpperCase(),
|
getTextKeyOverride: (option) => (option.name || '').toUpperCase(),
|
||||||
);
|
});
|
||||||
const hasSearchRender = Boolean(searchRender);
|
const hasSearchRender = Boolean(searchRender);
|
||||||
const options = hasSearchRender ? filteredValues : availableValues;
|
const options = hasSearchRender ? filteredValues : availableValues;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ type SelectDropDownProps = {
|
||||||
optionsClass?: string;
|
optionsClass?: string;
|
||||||
subContainerClassName?: string;
|
subContainerClassName?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
searchClassName?: string;
|
||||||
|
searchPlaceholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function SelectDropDown({
|
function SelectDropDown({
|
||||||
|
|
@ -43,6 +45,8 @@ function SelectDropDown({
|
||||||
subContainerClassName,
|
subContainerClassName,
|
||||||
className,
|
className,
|
||||||
renderOption,
|
renderOption,
|
||||||
|
searchClassName,
|
||||||
|
searchPlaceholder,
|
||||||
}: SelectDropDownProps) {
|
}: SelectDropDownProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const transitionProps = { className: 'top-full mt-3' };
|
const transitionProps = { className: 'top-full mt-3' };
|
||||||
|
|
@ -61,7 +65,12 @@ function SelectDropDown({
|
||||||
// Detemine if we should to convert this component into a searchable select. If we have enough elements, a search
|
// 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<string[] | Option[]>(availableValues);
|
const [filteredValues, searchRender] = useMultiSearch<string[] | Option[]>({
|
||||||
|
availableOptions: availableValues,
|
||||||
|
placeholder: searchPlaceholder,
|
||||||
|
getTextKeyOverride: (option) => ((option as Option)?.label || '').toUpperCase(),
|
||||||
|
className: searchClassName,
|
||||||
|
});
|
||||||
const hasSearchRender = Boolean(searchRender);
|
const hasSearchRender = Boolean(searchRender);
|
||||||
const options = hasSearchRender ? filteredValues : availableValues;
|
const options = hasSearchRender ? filteredValues : availableValues;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,9 @@ function SelectDropDownPop({
|
||||||
// Detemine if we should to convert this component into a searchable select. If we have enough elements, a search
|
// 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<string[] | Option[]>(availableValues);
|
const [filteredValues, searchRender] = useMultiSearch<string[] | Option[]>({
|
||||||
|
availableOptions: availableValues,
|
||||||
|
});
|
||||||
const hasSearchRender = Boolean(searchRender);
|
const hasSearchRender = Boolean(searchRender);
|
||||||
const options = hasSearchRender ? filteredValues : availableValues;
|
const options = hasSearchRender ? filteredValues : availableValues;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export default {
|
||||||
com_assistants_code_interpreter_files:
|
com_assistants_code_interpreter_files:
|
||||||
'The following files are only available for Code Interpreter:',
|
'The following files are only available for Code Interpreter:',
|
||||||
com_assistants_retrieval: 'Retrieval',
|
com_assistants_retrieval: 'Retrieval',
|
||||||
|
com_assistants_search_name: 'Search assistants by name',
|
||||||
com_assistants_tools: 'Tools',
|
com_assistants_tools: 'Tools',
|
||||||
com_assistants_actions: 'Actions',
|
com_assistants_actions: 'Actions',
|
||||||
com_assistants_add_tools: 'Add Tools',
|
com_assistants_add_tools: 'Add Tools',
|
||||||
|
|
|
||||||
|
|
@ -531,9 +531,11 @@ export enum Constants {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultOrderQuery: {
|
export const defaultOrderQuery: {
|
||||||
order: 'asc';
|
order: 'desc';
|
||||||
|
limit: 100;
|
||||||
} = {
|
} = {
|
||||||
order: 'asc',
|
order: 'desc',
|
||||||
|
limit: 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum AssistantStreamEvents {
|
export enum AssistantStreamEvents {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue