mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
📦 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:
parent
f64a2cb0b0
commit
8e5f1ad575
33 changed files with 2850 additions and 462 deletions
|
|
@ -27,6 +27,7 @@
|
|||
},
|
||||
"homepage": "https://librechat.ai",
|
||||
"dependencies": {
|
||||
"@ariakit/react": "^0.4.5",
|
||||
"@dicebear/collection": "^7.0.4",
|
||||
"@dicebear/core": "^7.0.4",
|
||||
"@headlessui/react": "^1.7.13",
|
||||
|
|
@ -65,6 +66,7 @@
|
|||
"librechat-data-provider": "*",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.220.0",
|
||||
"match-sorter": "^6.3.4",
|
||||
"rc-input-number": "^7.4.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { FileSources } from 'librechat-data-provider';
|
|||
import type { ColumnDef } from '@tanstack/react-table';
|
||||
import type { SetterOrUpdater } from 'recoil';
|
||||
import type {
|
||||
TSetOption as SetOption,
|
||||
TConversation,
|
||||
TMessage,
|
||||
TPreset,
|
||||
|
|
@ -20,6 +21,8 @@ export type GenericSetter<T> = (value: T | ((currentValue: T) => T)) => void;
|
|||
|
||||
export type LastSelectedModels = Record<EModelEndpoint, string>;
|
||||
|
||||
export const mainTextareaId = 'prompt-textarea';
|
||||
|
||||
export enum IconContext {
|
||||
landing = 'landing',
|
||||
menuItem = 'menu-item',
|
||||
|
|
@ -89,15 +92,16 @@ export type AssistantPanelProps = {
|
|||
|
||||
export type AugmentedColumnDef<TData, TValue> = ColumnDef<TData, TValue> & ColumnMeta;
|
||||
|
||||
export type TSetOption = (
|
||||
param: number | string,
|
||||
) => (newValue: number | string | boolean | Partial<TPreset>) => void;
|
||||
export type TSetOption = SetOption;
|
||||
|
||||
export type TSetExample = (
|
||||
i: number,
|
||||
type: string,
|
||||
newValue: number | string | boolean | null,
|
||||
) => void;
|
||||
|
||||
export const defaultDebouncedDelay = 450;
|
||||
|
||||
export enum ESide {
|
||||
Top = 'top',
|
||||
Right = 'right',
|
||||
|
|
@ -304,6 +308,8 @@ export type Option = Record<string, unknown> & {
|
|||
value: string | number | null;
|
||||
};
|
||||
|
||||
export type OptionWithIcon = Option & { icon?: React.ReactNode };
|
||||
|
||||
export type TOptionSettings = {
|
||||
showExamples?: boolean;
|
||||
isCodeChat?: boolean;
|
||||
|
|
@ -327,3 +333,8 @@ export interface ExtendedFile {
|
|||
}
|
||||
|
||||
export type ContextType = { navVisible: boolean; setNavVisible: (visible: boolean) => void };
|
||||
|
||||
export interface SwitcherProps {
|
||||
endpointKeyProvided: boolean;
|
||||
isCollapsed: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { TextareaAutosize } from '~/components/ui';
|
|||
import { useGetFileConfig } from '~/data-provider';
|
||||
import { cn, removeFocusOutlines } from '~/utils';
|
||||
import AttachFile from './Files/AttachFile';
|
||||
import { mainTextareaId } from '~/common';
|
||||
import StopButton from './StopButton';
|
||||
import SendButton from './SendButton';
|
||||
import FileRow from './Files/FileRow';
|
||||
|
|
@ -119,7 +120,7 @@ const ChatForm = ({ index = 0 }) => {
|
|||
onKeyDown={handleKeyDown}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
id="prompt-textarea"
|
||||
id={mainTextareaId}
|
||||
tabIndex={0}
|
||||
data-testid="text-input"
|
||||
style={{ height: 44, overflowY: 'auto' }}
|
||||
|
|
|
|||
84
client/src/components/SidePanel/AssistantSwitcher.tsx
Normal file
84
client/src/components/SidePanel/AssistantSwitcher.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { useEffect, useMemo } from 'react';
|
||||
import { Combobox } from '~/components/ui';
|
||||
import { EModelEndpoint, defaultOrderQuery } from 'librechat-data-provider';
|
||||
import type { SwitcherProps } from '~/common';
|
||||
import { useSetIndexOptions, useSelectAssistant, useLocalize } from '~/hooks';
|
||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||
import { useListAssistantsQuery } from '~/data-provider';
|
||||
import Icon from '~/components/Endpoints/Icon';
|
||||
|
||||
export default function AssistantSwitcher({ isCollapsed }: SwitcherProps) {
|
||||
const localize = useLocalize();
|
||||
const { setOption } = useSetIndexOptions();
|
||||
const { index, conversation } = useChatContext();
|
||||
|
||||
/* `selectedAssistant` must be defined with `null` to cause re-render on update */
|
||||
const { assistant_id: selectedAssistant = null, endpoint } = conversation ?? {};
|
||||
|
||||
const { data: assistants = [] } = useListAssistantsQuery(defaultOrderQuery, {
|
||||
select: (res) => res.data.map(({ id, name, metadata }) => ({ id, name, metadata })),
|
||||
});
|
||||
|
||||
const assistantMap = useAssistantsMapContext();
|
||||
const { onSelect } = useSelectAssistant();
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedAssistant && assistants && assistants.length && assistantMap) {
|
||||
const assistant_id =
|
||||
localStorage.getItem(`assistant_id__${index}`) ?? assistants[0]?.id ?? '';
|
||||
const assistant = assistantMap?.[assistant_id];
|
||||
if (!assistant) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (endpoint !== EModelEndpoint.assistants) {
|
||||
return;
|
||||
}
|
||||
setOption('model')(assistant.model);
|
||||
setOption('assistant_id')(assistant_id);
|
||||
}
|
||||
}, [index, assistants, selectedAssistant, assistantMap, endpoint, setOption]);
|
||||
|
||||
const currentAssistant = assistantMap?.[selectedAssistant ?? ''];
|
||||
|
||||
const assistantOptions = useMemo(() => {
|
||||
return assistants.map((assistant) => {
|
||||
return {
|
||||
label: assistant.name ?? '',
|
||||
value: assistant.id,
|
||||
icon: (
|
||||
<Icon
|
||||
isCreatedByUser={false}
|
||||
endpoint={EModelEndpoint.assistants}
|
||||
assistantName={assistant.name ?? ''}
|
||||
iconURL={(assistant.metadata?.avatar as string) ?? ''}
|
||||
/>
|
||||
),
|
||||
};
|
||||
});
|
||||
}, [assistants]);
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
selectedValue={currentAssistant?.id ?? ''}
|
||||
displayValue={
|
||||
assistants.find((assistant) => assistant.id === selectedAssistant)?.name ??
|
||||
localize('com_sidepanel_select_assistant')
|
||||
}
|
||||
selectPlaceholder={localize('com_sidepanel_select_assistant')}
|
||||
searchPlaceholder={localize('com_assistants_search_name')}
|
||||
isCollapsed={isCollapsed}
|
||||
ariaLabel={'assistant'}
|
||||
setValue={onSelect}
|
||||
items={assistantOptions}
|
||||
SelectIcon={
|
||||
<Icon
|
||||
isCreatedByUser={false}
|
||||
endpoint={EModelEndpoint.assistants}
|
||||
assistantName={currentAssistant?.name ?? ''}
|
||||
iconURL={(currentAssistant?.metadata?.avatar as string) ?? ''}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
54
client/src/components/SidePanel/ModelSwitcher.tsx
Normal file
54
client/src/components/SidePanel/ModelSwitcher.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { useMemo, useRef, useCallback } from 'react';
|
||||
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import MinimalIcon from '~/components/Endpoints/MinimalIcon';
|
||||
import { useSetIndexOptions, useLocalize } from '~/hooks';
|
||||
import type { SwitcherProps } from '~/common';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { Combobox } from '~/components/ui';
|
||||
import { mainTextareaId } from '~/common';
|
||||
|
||||
export default function ModelSwitcher({ isCollapsed }: SwitcherProps) {
|
||||
const localize = useLocalize();
|
||||
const modelsQuery = useGetModelsQuery();
|
||||
const { conversation } = useChatContext();
|
||||
const { setOption } = useSetIndexOptions();
|
||||
const timeoutIdRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
const { endpoint, model = null } = conversation ?? {};
|
||||
const models = useMemo(() => {
|
||||
return modelsQuery?.data?.[endpoint ?? ''] ?? [];
|
||||
}, [modelsQuery, endpoint]);
|
||||
|
||||
const setModel = useCallback(
|
||||
(model: string) => {
|
||||
setOption('model')(model);
|
||||
clearTimeout(timeoutIdRef.current);
|
||||
timeoutIdRef.current = setTimeout(() => {
|
||||
const textarea = document.getElementById(mainTextareaId);
|
||||
if (textarea) {
|
||||
textarea.focus();
|
||||
}
|
||||
}, 150);
|
||||
},
|
||||
[setOption],
|
||||
);
|
||||
|
||||
return (
|
||||
<Combobox
|
||||
selectPlaceholder={localize('com_ui_select_model')}
|
||||
searchPlaceholder={localize('com_ui_select_search_model')}
|
||||
isCollapsed={isCollapsed}
|
||||
ariaLabel={'model'}
|
||||
selectedValue={model ?? ''}
|
||||
setValue={setModel}
|
||||
items={models}
|
||||
SelectIcon={
|
||||
<MinimalIcon
|
||||
isCreatedByUser={false}
|
||||
endpoint={endpoint}
|
||||
// iconURL={} // for future preset icons
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// client/src/components/SidePanel/Parameters/DynamicCheckbox.tsx
|
||||
import { useMemo, useState } from 'react';
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { Label, Checkbox, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import { useLocalize, useParameterEffects } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
|
||||
function DynamicCheckbox({
|
||||
label,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description,
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { conversation = { conversationId: null }, preset } = useChatContext();
|
||||
const [inputValue, setInputValue] = useState<boolean>(!!(defaultValue as boolean | undefined));
|
||||
|
||||
const selectedValue = useMemo(() => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
return inputValue;
|
||||
}
|
||||
|
||||
return conversation?.[settingKey] ?? defaultValue;
|
||||
}, [conversation, defaultValue, optionType, settingKey, inputValue]);
|
||||
|
||||
const handleCheckedChange = (checked: boolean) => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
setInputValue(checked);
|
||||
return;
|
||||
}
|
||||
setOption(settingKey)(checked);
|
||||
};
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
preventDelayedUpdate: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col items-center justify-start gap-6 ${
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
}`}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center">
|
||||
<div className="flex justify-start gap-4">
|
||||
<Label
|
||||
htmlFor={`${settingKey}-dynamic-checkbox`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}:{' '}
|
||||
{defaultValue ? localize('com_ui_yes') : localize('com_ui_no')})
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
<Checkbox
|
||||
id={`${settingKey}-dynamic-checkbox`}
|
||||
disabled={readonly}
|
||||
checked={selectedValue}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
className="mt-[2px] focus:ring-opacity-20 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:focus:ring-gray-600 dark:focus:ring-opacity-50 dark:focus:ring-offset-0"
|
||||
/>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
</HoverCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DynamicCheckbox;
|
||||
106
client/src/components/SidePanel/Parameters/DynamicDropdown.tsx
Normal file
106
client/src/components/SidePanel/Parameters/DynamicDropdown.tsx
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { Label, HoverCard, HoverCardTrigger, SelectDropDown } from '~/components/ui';
|
||||
import { useLocalize, useParameterEffects } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
function DynamicDropdown({
|
||||
label,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description,
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
options,
|
||||
// type: _type,
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { conversation = { conversationId: null }, preset } = useChatContext();
|
||||
const [inputValue, setInputValue] = useState<string | null>(null);
|
||||
|
||||
const selectedValue = useMemo(() => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
return inputValue;
|
||||
}
|
||||
|
||||
return conversation?.[settingKey] ?? defaultValue;
|
||||
}, [conversation, defaultValue, optionType, settingKey, inputValue]);
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
setInputValue(value);
|
||||
return;
|
||||
}
|
||||
setOption(settingKey)(value);
|
||||
};
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
preventDelayedUpdate: true,
|
||||
});
|
||||
|
||||
if (!options || options.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-start gap-6',
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full',
|
||||
)}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex w-full justify-between">
|
||||
<Label
|
||||
htmlFor={`${settingKey}-dynamic-dropdown`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}: {defaultValue})
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
<SelectDropDown
|
||||
showLabel={false}
|
||||
emptyTitle={true}
|
||||
disabled={readonly}
|
||||
value={selectedValue}
|
||||
setValue={handleChange}
|
||||
availableValues={options}
|
||||
containerClassName="w-full"
|
||||
id={`${settingKey}-dynamic-dropdown`}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
</HoverCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DynamicDropdown;
|
||||
93
client/src/components/SidePanel/Parameters/DynamicInput.tsx
Normal file
93
client/src/components/SidePanel/Parameters/DynamicInput.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// client/src/components/SidePanel/Parameters/DynamicInput.tsx
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { useLocalize, useDebouncedInput, useParameterEffects } from '~/hooks';
|
||||
import { Label, Input, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import { cn, defaultTextProps } from '~/utils';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
|
||||
function DynamicInput({
|
||||
label,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description,
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
placeholder,
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
placeholderCode,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { conversation = { conversationId: null }, preset } = useChatContext();
|
||||
|
||||
const [setInputValue, inputValue] = useDebouncedInput<string | null>({
|
||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||
initialValue:
|
||||
optionType !== OptionTypes.Custom
|
||||
? (conversation?.[settingKey] as string)
|
||||
: (defaultValue as string),
|
||||
setter: () => ({}),
|
||||
setOption,
|
||||
});
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col items-center justify-start gap-6 ${
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
}`}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex w-full justify-between">
|
||||
<Label
|
||||
htmlFor={`${settingKey}-dynamic-input`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
(
|
||||
{typeof defaultValue === 'undefined' || !(defaultValue as string)?.length
|
||||
? localize('com_endpoint_default_blank')
|
||||
: `${localize('com_endpoint_default')}: ${defaultValue}`}
|
||||
)
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
<Input
|
||||
id={`${settingKey}-dynamic-input`}
|
||||
disabled={readonly}
|
||||
value={inputValue ?? ''}
|
||||
onChange={setInputValue}
|
||||
placeholder={placeholderCode ? localize(placeholder ?? '') || placeholder : placeholder}
|
||||
className={cn(defaultTextProps, 'flex h-10 max-h-10 w-full resize-none px-3 py-2')}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
</HoverCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DynamicInput;
|
||||
175
client/src/components/SidePanel/Parameters/DynamicSlider.tsx
Normal file
175
client/src/components/SidePanel/Parameters/DynamicSlider.tsx
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import { useMemo, useCallback } from 'react';
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { Label, Slider, HoverCard, Input, InputNumber, HoverCardTrigger } from '~/components/ui';
|
||||
import { useLocalize, useDebouncedInput, useParameterEffects } from '~/hooks';
|
||||
import { cn, defaultTextProps, optionText } from '~/utils';
|
||||
import { ESide, defaultDebouncedDelay } from '~/common';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
|
||||
function DynamicSlider({
|
||||
label,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
range,
|
||||
description,
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
options,
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
includeInput = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { conversation = { conversationId: null }, preset } = useChatContext();
|
||||
const isEnum = useMemo(() => !range && options && options.length > 0, [options, range]);
|
||||
|
||||
const [setInputValue, inputValue] = useDebouncedInput<string | number>({
|
||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||
initialValue: optionType !== OptionTypes.Custom ? conversation?.[settingKey] : defaultValue,
|
||||
setter: () => ({}),
|
||||
setOption,
|
||||
delay: isEnum ? 0 : defaultDebouncedDelay,
|
||||
});
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
preventDelayedUpdate: isEnum,
|
||||
});
|
||||
|
||||
const selectedValue = useMemo(() => {
|
||||
if (isEnum) {
|
||||
return conversation?.[settingKey] ?? defaultValue;
|
||||
}
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
|
||||
return inputValue;
|
||||
}, [conversation, defaultValue, settingKey, inputValue, isEnum]);
|
||||
|
||||
const enumToNumeric = useMemo(() => {
|
||||
if (isEnum && options) {
|
||||
return options.reduce((acc, mapping, index) => {
|
||||
acc[mapping] = index;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
}
|
||||
return {};
|
||||
}, [isEnum, options]);
|
||||
|
||||
const valueToEnumOption = useMemo(() => {
|
||||
if (isEnum && options) {
|
||||
return options.reduce((acc, option, index) => {
|
||||
acc[index] = option;
|
||||
return acc;
|
||||
}, {} as Record<number, string>);
|
||||
}
|
||||
return {};
|
||||
}, [isEnum, options]);
|
||||
|
||||
const handleValueChange = useCallback(
|
||||
(value: number) => {
|
||||
if (isEnum) {
|
||||
setInputValue(valueToEnumOption[value]);
|
||||
} else {
|
||||
setInputValue(value);
|
||||
}
|
||||
},
|
||||
[isEnum, setInputValue, valueToEnumOption],
|
||||
);
|
||||
|
||||
if (!range && !isEnum) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-start gap-6',
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full',
|
||||
)}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label
|
||||
htmlFor={`${settingKey}-dynamic-setting`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}: {defaultValue})
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
{includeInput && !isEnum ? (
|
||||
<InputNumber
|
||||
id={`${settingKey}-dynamic-setting-input-number`}
|
||||
disabled={readonly}
|
||||
value={inputValue ?? defaultValue}
|
||||
onChange={(value) => setInputValue(Number(value))}
|
||||
max={range ? range.max : (options?.length ?? 0) - 1}
|
||||
min={range ? range.min : 0}
|
||||
step={range ? range.step ?? 1 : 1}
|
||||
controls={false}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
cn(
|
||||
optionText,
|
||||
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
id={`${settingKey}-dynamic-setting-input`}
|
||||
disabled={readonly}
|
||||
value={selectedValue ?? defaultValue}
|
||||
onChange={() => ({})}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
cn(
|
||||
optionText,
|
||||
'reset-rc-number-input reset-rc-number-input-text-right h-auto w-12 border-0 group-hover/temp:border-gray-200',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Slider
|
||||
id={`${settingKey}-dynamic-setting-slider`}
|
||||
disabled={readonly}
|
||||
value={[
|
||||
isEnum
|
||||
? enumToNumeric[(selectedValue as number) ?? '']
|
||||
: (inputValue as number) ?? (defaultValue as number),
|
||||
]}
|
||||
onValueChange={(value) => handleValueChange(value[0])}
|
||||
doubleClickHandler={() => setInputValue(defaultValue as string | number)}
|
||||
max={isEnum && options ? options.length - 1 : range ? range.max : 0}
|
||||
min={range ? range.min : 0}
|
||||
step={range ? range.step ?? 1 : 1}
|
||||
className="flex h-4 w-full"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
</HoverCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DynamicSlider;
|
||||
94
client/src/components/SidePanel/Parameters/DynamicSwitch.tsx
Normal file
94
client/src/components/SidePanel/Parameters/DynamicSwitch.tsx
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { useState, useMemo } from 'react';
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { Label, Switch, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import { useLocalize, useParameterEffects } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
|
||||
function DynamicSwitch({
|
||||
label,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description,
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { conversation = { conversationId: null }, preset } = useChatContext();
|
||||
const [inputValue, setInputValue] = useState<boolean>(!!(defaultValue as boolean | undefined));
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
preventDelayedUpdate: true,
|
||||
});
|
||||
|
||||
const selectedValue = useMemo(() => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
return inputValue;
|
||||
}
|
||||
|
||||
return conversation?.[settingKey] ?? defaultValue;
|
||||
}, [conversation, defaultValue, optionType, settingKey, inputValue]);
|
||||
|
||||
const handleCheckedChange = (checked: boolean) => {
|
||||
if (optionType === OptionTypes.Custom) {
|
||||
// TODO: custom logic, add to payload but not to conversation
|
||||
setInputValue(checked);
|
||||
return;
|
||||
}
|
||||
setOption(settingKey)(checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col items-center justify-start gap-6 ${
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
}`}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label
|
||||
htmlFor={`${settingKey}-dynamic-switch`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default')}: {defaultValue ? 'com_ui_on' : 'com_ui_off'})
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
<Switch
|
||||
id={`${settingKey}-dynamic-switch`}
|
||||
checked={selectedValue}
|
||||
onCheckedChange={handleCheckedChange}
|
||||
disabled={readonly}
|
||||
className="flex"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
</HoverCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DynamicSwitch;
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// client/src/components/SidePanel/Parameters/DynamicTextarea.tsx
|
||||
import { OptionTypes } from 'librechat-data-provider';
|
||||
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||
import { Label, TextareaAutosize, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||
import { useLocalize, useDebouncedInput, useParameterEffects } from '~/hooks';
|
||||
import { cn, defaultTextProps } from '~/utils';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
|
||||
function DynamicTextarea({
|
||||
label,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
description,
|
||||
columnSpan,
|
||||
setOption,
|
||||
optionType,
|
||||
placeholder,
|
||||
readonly = false,
|
||||
showDefault = true,
|
||||
labelCode,
|
||||
descriptionCode,
|
||||
placeholderCode,
|
||||
}: DynamicSettingProps) {
|
||||
const localize = useLocalize();
|
||||
const { conversation = { conversationId: null }, preset } = useChatContext();
|
||||
|
||||
const [setInputValue, inputValue] = useDebouncedInput<string | null>({
|
||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||
initialValue:
|
||||
optionType !== OptionTypes.Custom
|
||||
? (conversation?.[settingKey] as string)
|
||||
: (defaultValue as string),
|
||||
setter: () => ({}),
|
||||
setOption,
|
||||
});
|
||||
|
||||
useParameterEffects({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col items-center justify-start gap-6 ${
|
||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
||||
}`}
|
||||
>
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex w-full justify-between">
|
||||
<Label
|
||||
htmlFor={`${settingKey}-dynamic-textarea`}
|
||||
className="text-left text-sm font-medium"
|
||||
>
|
||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
||||
{showDefault && (
|
||||
<small className="opacity-40">
|
||||
(
|
||||
{typeof defaultValue === 'undefined' || !(defaultValue as string)?.length
|
||||
? localize('com_endpoint_default_blank')
|
||||
: `${localize('com_endpoint_default')}: ${defaultValue}`}
|
||||
)
|
||||
</small>
|
||||
)}
|
||||
</Label>
|
||||
</div>
|
||||
<TextareaAutosize
|
||||
id={`${settingKey}-dynamic-textarea`}
|
||||
disabled={readonly}
|
||||
value={inputValue ?? ''}
|
||||
onChange={setInputValue}
|
||||
placeholder={placeholderCode ? localize(placeholder ?? '') || placeholder : placeholder}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
// TODO: configurable max height
|
||||
'flex max-h-[138px] min-h-[100px] w-full resize-none px-3 py-2',
|
||||
)}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
{description && (
|
||||
<OptionHover
|
||||
description={descriptionCode ? localize(description) || description : description}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
)}
|
||||
</HoverCard>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DynamicTextarea;
|
||||
26
client/src/components/SidePanel/Parameters/OptionHover.tsx
Normal file
26
client/src/components/SidePanel/Parameters/OptionHover.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import { HoverCardPortal, HoverCardContent } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { ESide } from '~/common';
|
||||
|
||||
type TOptionHoverProps = {
|
||||
description: string;
|
||||
langCode?: boolean;
|
||||
side: ESide;
|
||||
};
|
||||
|
||||
function OptionHover({ side, description, langCode }: TOptionHoverProps) {
|
||||
const localize = useLocalize();
|
||||
const text = langCode ? localize(description) : description;
|
||||
return (
|
||||
<HoverCardPortal>
|
||||
<HoverCardContent side={side} className="z-[999] w-80 dark:bg-gray-700" sideOffset={30}>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">{text}</p>
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCardPortal>
|
||||
);
|
||||
}
|
||||
|
||||
export default OptionHover;
|
||||
215
client/src/components/SidePanel/Parameters/Panel.tsx
Normal file
215
client/src/components/SidePanel/Parameters/Panel.tsx
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
import { ComponentTypes } from 'librechat-data-provider';
|
||||
import type {
|
||||
DynamicSettingProps,
|
||||
SettingDefinition,
|
||||
SettingsConfiguration,
|
||||
} from 'librechat-data-provider';
|
||||
import { useSetIndexOptions } from '~/hooks';
|
||||
import DynamicDropdown from './DynamicDropdown';
|
||||
import DynamicCheckbox from './DynamicCheckbox';
|
||||
import DynamicTextarea from './DynamicTextarea';
|
||||
import DynamicSlider from './DynamicSlider';
|
||||
import DynamicSwitch from './DynamicSwitch';
|
||||
import DynamicInput from './DynamicInput';
|
||||
|
||||
const settingsConfiguration: SettingsConfiguration = [
|
||||
{
|
||||
key: 'temperature',
|
||||
label: 'com_endpoint_temperature',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_openai_temp',
|
||||
descriptionCode: true,
|
||||
type: 'number',
|
||||
default: 1,
|
||||
range: {
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
},
|
||||
component: 'slider',
|
||||
optionType: 'model',
|
||||
// columnSpan: 2,
|
||||
// includeInput: false,
|
||||
},
|
||||
{
|
||||
key: 'top_p',
|
||||
label: 'com_endpoint_top_p',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_openai_topp',
|
||||
descriptionCode: true,
|
||||
type: 'number',
|
||||
default: 1,
|
||||
range: {
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
},
|
||||
component: 'slider',
|
||||
optionType: 'model',
|
||||
},
|
||||
{
|
||||
key: 'presence_penalty',
|
||||
label: 'com_endpoint_presence_penalty',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_openai_pres',
|
||||
descriptionCode: true,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
range: {
|
||||
min: -2,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
},
|
||||
component: 'slider',
|
||||
optionType: 'model',
|
||||
},
|
||||
{
|
||||
key: 'frequency_penalty',
|
||||
label: 'com_endpoint_frequency_penalty',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_openai_freq',
|
||||
descriptionCode: true,
|
||||
type: 'number',
|
||||
default: 0,
|
||||
range: {
|
||||
min: -2,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
},
|
||||
component: 'slider',
|
||||
optionType: 'model',
|
||||
},
|
||||
{
|
||||
key: 'chatGptLabel',
|
||||
label: 'com_endpoint_custom_name',
|
||||
labelCode: true,
|
||||
type: 'string',
|
||||
default: '',
|
||||
component: 'input',
|
||||
placeholder: 'com_endpoint_openai_custom_name_placeholder',
|
||||
placeholderCode: true,
|
||||
optionType: 'conversation',
|
||||
},
|
||||
{
|
||||
key: 'promptPrefix',
|
||||
label: 'com_endpoint_prompt_prefix',
|
||||
labelCode: true,
|
||||
type: 'string',
|
||||
default: '',
|
||||
component: 'textarea',
|
||||
placeholder: 'com_endpoint_openai_prompt_prefix_placeholder',
|
||||
placeholderCode: true,
|
||||
optionType: 'conversation',
|
||||
// columnSpan: 2,
|
||||
},
|
||||
{
|
||||
key: 'resendFiles',
|
||||
label: 'com_endpoint_plug_resend_files',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_openai_resend_files',
|
||||
descriptionCode: true,
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
component: 'switch',
|
||||
optionType: 'conversation',
|
||||
showDefault: false,
|
||||
columnSpan: 2,
|
||||
},
|
||||
{
|
||||
key: 'imageDetail',
|
||||
label: 'com_endpoint_plug_image_detail',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_openai_detail',
|
||||
descriptionCode: true,
|
||||
type: 'enum',
|
||||
default: 'auto',
|
||||
options: ['low', 'auto', 'high'],
|
||||
optionType: 'conversation',
|
||||
component: 'slider',
|
||||
showDefault: false,
|
||||
columnSpan: 2,
|
||||
},
|
||||
];
|
||||
|
||||
const componentMapping: Record<ComponentTypes, React.ComponentType<DynamicSettingProps>> = {
|
||||
[ComponentTypes.Slider]: DynamicSlider,
|
||||
[ComponentTypes.Dropdown]: DynamicDropdown,
|
||||
[ComponentTypes.Switch]: DynamicSwitch,
|
||||
[ComponentTypes.Textarea]: DynamicTextarea,
|
||||
[ComponentTypes.Input]: DynamicInput,
|
||||
[ComponentTypes.Checkbox]: DynamicCheckbox,
|
||||
};
|
||||
|
||||
export default function Parameters() {
|
||||
const { setOption } = useSetIndexOptions();
|
||||
|
||||
const temperature = settingsConfiguration.find(
|
||||
(setting) => setting.key === 'temperature',
|
||||
) as SettingDefinition;
|
||||
const TempComponent = componentMapping[temperature.component];
|
||||
const { key: temp, default: tempDefault, ...tempSettings } = temperature;
|
||||
|
||||
const imageDetail = settingsConfiguration.find(
|
||||
(setting) => setting.key === 'imageDetail',
|
||||
) as SettingDefinition;
|
||||
const DetailComponent = componentMapping[imageDetail.component];
|
||||
const { key: detail, default: detailDefault, ...detailSettings } = imageDetail;
|
||||
|
||||
const resendFiles = settingsConfiguration.find(
|
||||
(setting) => setting.key === 'resendFiles',
|
||||
) as SettingDefinition;
|
||||
const Switch = componentMapping[resendFiles.component];
|
||||
const { key: switchKey, default: switchDefault, ...switchSettings } = resendFiles;
|
||||
|
||||
const promptPrefix = settingsConfiguration.find(
|
||||
(setting) => setting.key === 'promptPrefix',
|
||||
) as SettingDefinition;
|
||||
const Textarea = componentMapping[promptPrefix.component];
|
||||
const { key: textareaKey, default: textareaDefault, ...textareaSettings } = promptPrefix;
|
||||
|
||||
const chatGptLabel = settingsConfiguration.find(
|
||||
(setting) => setting.key === 'chatGptLabel',
|
||||
) as SettingDefinition;
|
||||
const Input = componentMapping[chatGptLabel.component];
|
||||
const { key: inputKey, default: inputDefault, ...inputSettings } = chatGptLabel;
|
||||
|
||||
return (
|
||||
<div className="h-auto max-w-full overflow-x-hidden p-3">
|
||||
<div className="grid grid-cols-4 gap-6">
|
||||
{' '}
|
||||
{/* This is the parent element containing all settings */}
|
||||
{/* Below is an example of an applied dynamic setting, each be contained by a div with the column span specified */}
|
||||
<Input
|
||||
settingKey={inputKey}
|
||||
defaultValue={inputDefault}
|
||||
{...inputSettings}
|
||||
setOption={setOption}
|
||||
/>
|
||||
<Textarea
|
||||
settingKey={textareaKey}
|
||||
defaultValue={textareaDefault}
|
||||
{...textareaSettings}
|
||||
setOption={setOption}
|
||||
/>
|
||||
<TempComponent
|
||||
settingKey={temp}
|
||||
defaultValue={tempDefault}
|
||||
{...tempSettings}
|
||||
setOption={setOption}
|
||||
/>
|
||||
<Switch
|
||||
settingKey={switchKey}
|
||||
defaultValue={switchDefault}
|
||||
{...switchSettings}
|
||||
setOption={setOption}
|
||||
/>
|
||||
<DetailComponent
|
||||
settingKey={detail}
|
||||
defaultValue={detailDefault}
|
||||
{...detailSettings}
|
||||
setOption={setOption}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -1,104 +1,20 @@
|
|||
import { useEffect } from 'react';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '~/components/ui/Select';
|
||||
import { EModelEndpoint, defaultOrderQuery } from 'librechat-data-provider';
|
||||
import { useSetIndexOptions, useSelectAssistant, useLocalize } from '~/hooks';
|
||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||
import { useListAssistantsQuery } from '~/data-provider';
|
||||
import Icon from '~/components/Endpoints/Icon';
|
||||
import { cn } from '~/utils';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { SwitcherProps } from '~/common';
|
||||
import AssistantSwitcher from './AssistantSwitcher';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import ModelSwitcher from './ModelSwitcher';
|
||||
|
||||
interface SwitcherProps {
|
||||
isCollapsed: boolean;
|
||||
}
|
||||
|
||||
export default function Switcher({ isCollapsed }: SwitcherProps) {
|
||||
const localize = useLocalize();
|
||||
const { setOption } = useSetIndexOptions();
|
||||
const { index, conversation } = useChatContext();
|
||||
|
||||
/* `selectedAssistant` must be defined with `null` to cause re-render on update */
|
||||
const { assistant_id: selectedAssistant = null, endpoint } = conversation ?? {};
|
||||
|
||||
const { data: assistants = [] } = useListAssistantsQuery(defaultOrderQuery, {
|
||||
select: (res) => res.data.map(({ id, name, metadata }) => ({ id, name, metadata })),
|
||||
});
|
||||
|
||||
const assistantMap = useAssistantsMapContext();
|
||||
const { onSelect } = useSelectAssistant();
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedAssistant && assistants && assistants.length && assistantMap) {
|
||||
const assistant_id =
|
||||
localStorage.getItem(`assistant_id__${index}`) ?? assistants[0]?.id ?? '';
|
||||
const assistant = assistantMap?.[assistant_id];
|
||||
if (!assistant) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (endpoint !== EModelEndpoint.assistants) {
|
||||
return;
|
||||
}
|
||||
setOption('model')(assistant.model);
|
||||
setOption('assistant_id')(assistant_id);
|
||||
}
|
||||
}, [index, assistants, selectedAssistant, assistantMap, endpoint, setOption]);
|
||||
|
||||
const currentAssistant = assistantMap?.[selectedAssistant ?? ''];
|
||||
|
||||
return (
|
||||
<Select value={selectedAssistant as string | undefined} onValueChange={onSelect}>
|
||||
<SelectTrigger
|
||||
className={cn(
|
||||
'flex items-center gap-2 [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0',
|
||||
isCollapsed
|
||||
? 'flex h-9 w-9 shrink-0 items-center justify-center p-0 [&>span]:w-auto [&>svg]:hidden'
|
||||
: '',
|
||||
'bg-white text-black hover:bg-gray-50 dark:bg-gray-850 dark:text-white',
|
||||
)}
|
||||
aria-label={localize('com_sidepanel_select_assistant')}
|
||||
>
|
||||
<SelectValue placeholder={localize('com_sidepanel_select_assistant')}>
|
||||
<div className="assistant-item flex items-center justify-center overflow-hidden rounded-full">
|
||||
<Icon
|
||||
isCreatedByUser={false}
|
||||
endpoint={EModelEndpoint.assistants}
|
||||
assistantName={currentAssistant?.name ?? ''}
|
||||
iconURL={(currentAssistant?.metadata?.avatar as string) ?? ''}
|
||||
/>
|
||||
</div>
|
||||
<span className={cn('ml-2', isCollapsed ? 'hidden' : '')} style={{ userSelect: 'none' }}>
|
||||
{assistants.find((assistant) => assistant.id === selectedAssistant)?.name ??
|
||||
localize('com_sidepanel_select_assistant')}
|
||||
</span>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-white dark:bg-gray-800">
|
||||
{assistants.map((assistant) => (
|
||||
<SelectItem
|
||||
key={assistant.id}
|
||||
value={assistant.id}
|
||||
className="hover:bg-gray-50 dark:text-white"
|
||||
>
|
||||
<div className="[&_svg]:text-foreground flex items-center justify-center gap-3 dark:text-white [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0">
|
||||
<div className="assistant-item overflow-hidden rounded-full ">
|
||||
<Icon
|
||||
isCreatedByUser={false}
|
||||
endpoint={EModelEndpoint.assistants}
|
||||
assistantName={assistant.name ?? ''}
|
||||
iconURL={(assistant.metadata?.avatar as string) ?? ''}
|
||||
/>
|
||||
</div>
|
||||
{assistant.name}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
export default function Switcher(props: SwitcherProps) {
|
||||
const { conversation } = useChatContext();
|
||||
const { endpoint } = conversation ?? {};
|
||||
|
||||
if (!props.endpointKeyProvided) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (endpoint === EModelEndpoint.assistants) {
|
||||
return <AssistantSwitcher {...props} />;
|
||||
}
|
||||
|
||||
return <ModelSwitcher {...props} />;
|
||||
}
|
||||
|
|
|
|||
168
client/src/components/ui/Combobox.tsx
Normal file
168
client/src/components/ui/Combobox.tsx
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import { startTransition, useMemo } from 'react';
|
||||
import * as RadixSelect from '@radix-ui/react-select';
|
||||
import { Search as SearchIcon } from 'lucide-react';
|
||||
import { CheckIcon, ChevronDownIcon } from '@radix-ui/react-icons';
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
ComboboxProvider,
|
||||
ComboboxCancel,
|
||||
} from '@ariakit/react';
|
||||
import type { OptionWithIcon } from '~/common';
|
||||
import { SelectTrigger, SelectValue } from './Select';
|
||||
import useCombobox from '~/hooks/Input/useCombobox';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
export default function ComboboxComponent({
|
||||
selectedValue,
|
||||
displayValue,
|
||||
items,
|
||||
setValue,
|
||||
ariaLabel,
|
||||
searchPlaceholder,
|
||||
selectPlaceholder,
|
||||
isCollapsed,
|
||||
SelectIcon,
|
||||
}: {
|
||||
ariaLabel: string;
|
||||
displayValue?: string;
|
||||
selectedValue: string;
|
||||
searchPlaceholder?: string;
|
||||
selectPlaceholder?: string;
|
||||
items: OptionWithIcon[] | string[];
|
||||
setValue: (value: string) => void;
|
||||
isCollapsed: boolean;
|
||||
SelectIcon?: React.ReactNode;
|
||||
}) {
|
||||
const options: OptionWithIcon[] = useMemo(() => {
|
||||
if (!items) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return items.map((option: string | OptionWithIcon) => {
|
||||
if (typeof option === 'string') {
|
||||
return { label: option, value: option };
|
||||
}
|
||||
|
||||
return option;
|
||||
});
|
||||
}, [items]);
|
||||
|
||||
const { open, setOpen, setSearchValue, matches } = useCombobox({
|
||||
value: selectedValue,
|
||||
options,
|
||||
});
|
||||
|
||||
return (
|
||||
<RadixSelect.Root
|
||||
value={selectedValue}
|
||||
onValueChange={setValue}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<ComboboxProvider
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
resetValueOnHide
|
||||
includesBaseElement={false}
|
||||
setValue={(value) => {
|
||||
startTransition(() => {
|
||||
setSearchValue(value);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger
|
||||
aria-label={ariaLabel}
|
||||
className={cn(
|
||||
'flex items-center gap-2 [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0',
|
||||
isCollapsed
|
||||
? 'flex h-9 w-9 shrink-0 items-center justify-center p-0 [&>span]:w-auto [&>svg]:hidden'
|
||||
: '',
|
||||
'bg-white text-black hover:bg-gray-50 dark:bg-gray-850 dark:text-white',
|
||||
)}
|
||||
>
|
||||
<SelectValue placeholder={selectPlaceholder}>
|
||||
<div className="assistant-item flex items-center justify-center overflow-hidden rounded-full">
|
||||
{SelectIcon ? SelectIcon : <ChevronDownIcon />}
|
||||
</div>
|
||||
<span
|
||||
className={cn('ml-2', isCollapsed ? 'hidden' : '')}
|
||||
style={{ userSelect: 'none' }}
|
||||
>
|
||||
{selectedValue
|
||||
? displayValue ?? selectedValue
|
||||
: selectPlaceholder && selectPlaceholder}
|
||||
</span>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<RadixSelect.Portal>
|
||||
<RadixSelect.Content
|
||||
role="dialog"
|
||||
aria-label={ariaLabel + 's'}
|
||||
position="popper"
|
||||
sideOffset={4}
|
||||
alignOffset={-16}
|
||||
className={cn(
|
||||
'bg-popover text-popover-foreground relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-gray-200 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-gray-600',
|
||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||
'bg-white dark:bg-gray-700',
|
||||
)}
|
||||
>
|
||||
<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">
|
||||
<SearchIcon 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" />
|
||||
<Combobox
|
||||
autoSelect
|
||||
placeholder={searchPlaceholder}
|
||||
className="flex-1 rounded-md border-none bg-transparent px-2.5 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-gray-700/10 dark:focus:ring-gray-200/10"
|
||||
// Ariakit's Combobox manually triggers a blur event on virtually
|
||||
// blurred items, making them work as if they had actual DOM
|
||||
// focus. These blur events might happen after the corresponding
|
||||
// focus events in the capture phase, leading Radix Select to
|
||||
// close the popover. This happens because Radix Select relies on
|
||||
// the order of these captured events to discern if the focus was
|
||||
// outside the element. Since we don't have access to the
|
||||
// onInteractOutside prop in the Radix SelectContent component to
|
||||
// stop this behavior, we can turn off Ariakit's behavior here.
|
||||
onBlurCapture={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
<ComboboxCancel
|
||||
hideWhenEmpty={true}
|
||||
className="relative flex h-5 w-5 items-center justify-end text-gray-500 transition-colors duration-300 dark:group-focus-within:text-gray-300 dark:group-hover:text-gray-300"
|
||||
/>
|
||||
</div>
|
||||
<ComboboxList className="overflow-y-auto p-1 py-2">
|
||||
{matches.map(({ label, value, icon }) => (
|
||||
<RadixSelect.Item key={value} value={`${value ?? ''}`} asChild>
|
||||
<ComboboxItem
|
||||
className={cn(
|
||||
'focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'rounded-lg hover:bg-gray-100/50 hover:bg-gray-50 dark:text-white dark:hover:bg-gray-600',
|
||||
)}
|
||||
>
|
||||
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<RadixSelect.ItemIndicator>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</RadixSelect.ItemIndicator>
|
||||
</span>
|
||||
<RadixSelect.ItemText>
|
||||
<div className="[&_svg]:text-foreground flex items-center justify-center gap-3 dark:text-white [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0">
|
||||
<div className="assistant-item overflow-hidden rounded-full ">
|
||||
{icon && icon}
|
||||
</div>
|
||||
{label}
|
||||
</div>
|
||||
</RadixSelect.ItemText>
|
||||
</ComboboxItem>
|
||||
</RadixSelect.Item>
|
||||
))}
|
||||
</ComboboxList>
|
||||
</RadixSelect.Content>
|
||||
</RadixSelect.Portal>
|
||||
</ComboboxProvider>
|
||||
</RadixSelect.Root>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { CaretSortIcon, CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons';
|
||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
import { CaretSortIcon, CheckIcon, ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons';
|
||||
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
|
@ -39,7 +39,10 @@ const SelectScrollUpButton = React.forwardRef<
|
|||
>(({ className = '', ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1 dark:text-white',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon />
|
||||
|
|
@ -53,7 +56,10 @@ const SelectScrollDownButton = React.forwardRef<
|
|||
>(({ className = '', ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn('flex cursor-default items-center justify-center py-1', className)}
|
||||
className={cn(
|
||||
'flex cursor-default items-center justify-center py-1 dark:text-white',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
|
|
|
|||
|
|
@ -20,11 +20,12 @@ export * from './Templates';
|
|||
export * from './Textarea';
|
||||
export * from './TextareaAutosize';
|
||||
export * from './Tooltip';
|
||||
export { default as Combobox } from './Combobox';
|
||||
export { default as Dropdown } from './Dropdown';
|
||||
export { default as FileUpload } from './FileUpload';
|
||||
export { default as DelayedRender } from './DelayedRender';
|
||||
export { default as ThemeSelector } from './ThemeSelector';
|
||||
export { default as SelectDropDown } from './SelectDropDown';
|
||||
export { default as MultiSelectPop } from './MultiSelectPop';
|
||||
export { default as SelectDropDownPop } from './SelectDropDownPop';
|
||||
export { default as MultiSelectDropDown } from './MultiSelectDropDown';
|
||||
export { default as ThemeSelector } from './ThemeSelector';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export { default as usePresets } from './usePresets';
|
||||
export { default as useGetSender } from './useGetSender';
|
||||
export { default as useDebouncedInput } from './useDebouncedInput';
|
||||
export { default as useParameterEffects } from './useParameterEffects';
|
||||
|
|
|
|||
|
|
@ -2,29 +2,30 @@ import debounce from 'lodash/debounce';
|
|||
import React, { useState, useCallback } from 'react';
|
||||
import type { SetterOrUpdater } from 'recoil';
|
||||
import type { TSetOption } from '~/common';
|
||||
import { defaultDebouncedDelay } from '~/common';
|
||||
|
||||
/** A custom hook that accepts a setOption function and an option key (e.g., 'title').
|
||||
It manages a local state for the option value, a debounced setter function for that value,
|
||||
and returns the local state value, its setter, and an onChange handler suitable for inputs. */
|
||||
function useDebouncedInput({
|
||||
function useDebouncedInput<T = unknown>({
|
||||
setOption,
|
||||
setter,
|
||||
optionKey,
|
||||
initialValue,
|
||||
delay = 450,
|
||||
delay = defaultDebouncedDelay,
|
||||
}: {
|
||||
setOption?: TSetOption;
|
||||
setter?: SetterOrUpdater<string>;
|
||||
setter?: SetterOrUpdater<T>;
|
||||
optionKey?: string | number;
|
||||
initialValue: unknown;
|
||||
initialValue: T;
|
||||
delay?: number;
|
||||
}): [
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | unknown) => void,
|
||||
unknown,
|
||||
SetterOrUpdater<string>,
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | T) => void,
|
||||
T,
|
||||
SetterOrUpdater<T>,
|
||||
// (newValue: string) => void,
|
||||
] {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const [value, setValue] = useState<T>(initialValue);
|
||||
|
||||
/** A debounced function to call the passed setOption with the optionKey and new value.
|
||||
*
|
||||
|
|
@ -36,11 +37,12 @@ function useDebouncedInput({
|
|||
|
||||
/** An onChange handler that updates the local state and the debounced option */
|
||||
const onChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | unknown) => {
|
||||
const newValue: unknown =
|
||||
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | T) => {
|
||||
const newValue: T =
|
||||
typeof e !== 'object'
|
||||
? e
|
||||
: (e as React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>)?.target.value;
|
||||
: ((e as React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>)?.target
|
||||
.value as unknown as T);
|
||||
setValue(newValue);
|
||||
setDebouncedOption(newValue);
|
||||
},
|
||||
|
|
|
|||
68
client/src/hooks/Conversations/useParameterEffects.ts
Normal file
68
client/src/hooks/Conversations/useParameterEffects.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { useEffect, useRef } from 'react';
|
||||
import type { DynamicSettingProps, TConversation, TPreset } from 'librechat-data-provider';
|
||||
import { defaultDebouncedDelay } from '~/common';
|
||||
|
||||
function useParameterEffects<T = unknown>({
|
||||
preset,
|
||||
settingKey,
|
||||
defaultValue,
|
||||
conversation,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
preventDelayedUpdate = false,
|
||||
}: Pick<DynamicSettingProps, 'settingKey' | 'defaultValue'> & {
|
||||
preset: TPreset | null;
|
||||
conversation: TConversation | { conversationId: null } | null;
|
||||
inputValue: T;
|
||||
setInputValue: (inputValue: T) => void;
|
||||
preventDelayedUpdate?: boolean;
|
||||
}) {
|
||||
const idRef = useRef<string | null>(null);
|
||||
const presetIdRef = useRef<string | null>(null);
|
||||
|
||||
/** Updates the local state inputValue if global (conversation) is updated elsewhere */
|
||||
useEffect(() => {
|
||||
if (preventDelayedUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (conversation?.[settingKey] === inputValue) {
|
||||
return;
|
||||
}
|
||||
setInputValue(conversation?.[settingKey]);
|
||||
}, defaultDebouncedDelay * 1.25);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, [setInputValue, preventDelayedUpdate, conversation, inputValue, settingKey]);
|
||||
|
||||
/** Resets the local state if conversationId changed */
|
||||
useEffect(() => {
|
||||
if (!conversation?.conversationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (idRef.current === conversation?.conversationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
idRef.current = conversation?.conversationId;
|
||||
setInputValue(defaultValue as T);
|
||||
}, [setInputValue, conversation?.conversationId, defaultValue]);
|
||||
|
||||
/** Resets the local state if presetId changed */
|
||||
useEffect(() => {
|
||||
if (!preset?.presetId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (presetIdRef.current === preset?.presetId) {
|
||||
return;
|
||||
}
|
||||
|
||||
presetIdRef.current = preset?.presetId;
|
||||
setInputValue(defaultValue as T);
|
||||
}, [setInputValue, preset?.presetId, defaultValue]);
|
||||
}
|
||||
|
||||
export default useParameterEffects;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
export { default as useUserKey } from './useUserKey';
|
||||
export { default as useDebounce } from './useDebounce';
|
||||
export { default as useTextarea } from './useTextarea';
|
||||
export { default as useCombobox } from './useCombobox';
|
||||
export { default as useRequiresKey } from './useRequiresKey';
|
||||
export { default as useMultipleKeys } from './useMultipleKeys';
|
||||
|
|
|
|||
37
client/src/hooks/Input/useCombobox.ts
Normal file
37
client/src/hooks/Input/useCombobox.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { matchSorter } from 'match-sorter';
|
||||
import type { OptionWithIcon } from '~/common';
|
||||
|
||||
export default function useCombobox({
|
||||
value,
|
||||
options,
|
||||
}: {
|
||||
value: string;
|
||||
options: OptionWithIcon[];
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
|
||||
const matches = useMemo(() => {
|
||||
if (!searchValue) {
|
||||
return options;
|
||||
}
|
||||
const keys = ['label', 'value'];
|
||||
const matches = matchSorter(options, searchValue, { keys });
|
||||
// Radix Select does not work if we don't render the selected item, so we
|
||||
// make sure to include it in the list of matches.
|
||||
const selectedItem = options.find((currentItem) => currentItem.value === value);
|
||||
if (selectedItem && !matches.includes(selectedItem)) {
|
||||
matches.push(selectedItem);
|
||||
}
|
||||
return matches;
|
||||
}, [searchValue, value, options]);
|
||||
|
||||
return {
|
||||
open,
|
||||
setOpen,
|
||||
searchValue,
|
||||
setSearchValue,
|
||||
matches,
|
||||
};
|
||||
}
|
||||
71
client/src/hooks/Nav/useSideNavLinks.ts
Normal file
71
client/src/hooks/Nav/useSideNavLinks.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { useMemo } from 'react';
|
||||
import {
|
||||
ArrowRightToLine,
|
||||
// Settings2,
|
||||
} from 'lucide-react';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { TConfig } from 'librechat-data-provider';
|
||||
import type { NavLink } from '~/common';
|
||||
import PanelSwitch from '~/components/SidePanel/Builder/PanelSwitch';
|
||||
// import Parameters from '~/components/SidePanel/Parameters/Panel';
|
||||
import FilesPanel from '~/components/SidePanel/Files/Panel';
|
||||
import { Blocks, AttachmentIcon } from '~/components/svg';
|
||||
|
||||
export default function useSideNavLinks({
|
||||
hidePanel,
|
||||
assistants,
|
||||
keyProvided,
|
||||
endpoint,
|
||||
}: {
|
||||
hidePanel: () => void;
|
||||
assistants?: TConfig | null;
|
||||
keyProvided: boolean;
|
||||
endpoint?: EModelEndpoint | null;
|
||||
}) {
|
||||
const Links = useMemo(() => {
|
||||
const links: NavLink[] = [];
|
||||
// if (endpoint !== EModelEndpoint.assistants) {
|
||||
// links.push({
|
||||
// title: 'com_sidepanel_parameters',
|
||||
// label: '',
|
||||
// icon: Settings2,
|
||||
// id: 'parameters',
|
||||
// Component: Parameters,
|
||||
// });
|
||||
// }
|
||||
if (
|
||||
endpoint === EModelEndpoint.assistants &&
|
||||
assistants &&
|
||||
assistants.disableBuilder !== true &&
|
||||
keyProvided
|
||||
) {
|
||||
links.push({
|
||||
title: 'com_sidepanel_assistant_builder',
|
||||
label: '',
|
||||
icon: Blocks,
|
||||
id: 'assistants',
|
||||
Component: PanelSwitch,
|
||||
});
|
||||
}
|
||||
|
||||
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: hidePanel,
|
||||
id: 'hide-panel',
|
||||
});
|
||||
|
||||
return links;
|
||||
}, [assistants, keyProvided, hidePanel, endpoint]);
|
||||
|
||||
return Links;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { EModelEndpoint, FileSources, defaultOrderQuery } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery, useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
|
|
@ -24,6 +24,7 @@ import {
|
|||
import { useDeleteFilesMutation, useListAssistantsQuery } from '~/data-provider';
|
||||
import useOriginNavigate from './useOriginNavigate';
|
||||
import useSetStorage from './useSetStorage';
|
||||
import { mainTextareaId } from '~/common';
|
||||
import store from '~/store';
|
||||
|
||||
const useNewConvo = (index = 0) => {
|
||||
|
|
@ -36,6 +37,7 @@ const useNewConvo = (index = 0) => {
|
|||
const resetLatestMessage = useResetRecoilState(store.latestMessageFamily(index));
|
||||
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
||||
const modelsQuery = useGetModelsQuery();
|
||||
const timeoutIdRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
const { data: assistants = [] } = useListAssistantsQuery(defaultOrderQuery, {
|
||||
select: (res) =>
|
||||
|
|
@ -137,6 +139,14 @@ const useNewConvo = (index = 0) => {
|
|||
}
|
||||
navigate('new');
|
||||
}
|
||||
|
||||
clearTimeout(timeoutIdRef.current);
|
||||
timeoutIdRef.current = setTimeout(() => {
|
||||
const textarea = document.getElementById(mainTextareaId);
|
||||
if (textarea) {
|
||||
textarea.focus();
|
||||
}
|
||||
}, 150);
|
||||
},
|
||||
[endpointsConfig, defaultPreset, assistants, modelsQuery.data],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const useOriginNavigate = () => {
|
|||
return;
|
||||
}
|
||||
const path = location.pathname.match(/^\/[^/]+\//);
|
||||
_navigate(`${path ? path[0] : '/chat/'}${url}`, opts);
|
||||
_navigate(`${path ? path[0] : '/c/'}${url}`, opts);
|
||||
};
|
||||
|
||||
return navigate;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export default {
|
|||
com_files_filter: 'Filter files...',
|
||||
com_files_number_selected: '{0} of {1} file(s) selected',
|
||||
com_sidepanel_select_assistant: 'Select an Assistant',
|
||||
com_sidepanel_parameters: 'Parameters',
|
||||
com_sidepanel_assistant_builder: 'Assistant Builder',
|
||||
com_sidepanel_hide_panel: 'Hide Panel',
|
||||
com_sidepanel_attach_files: 'Attach Files',
|
||||
|
|
@ -68,6 +69,10 @@ export default {
|
|||
'May occasionally produce harmful instructions or biased content',
|
||||
com_ui_limitation_limited_2021: 'Limited knowledge of world and events after 2021',
|
||||
com_ui_experimental: 'Experimental Features',
|
||||
com_ui_on: 'On',
|
||||
com_ui_off: 'Off',
|
||||
com_ui_yes: 'Yes',
|
||||
com_ui_no: 'No',
|
||||
com_ui_ascending: 'Asc',
|
||||
com_ui_descending: 'Desc',
|
||||
com_ui_show_all: 'Show All',
|
||||
|
|
@ -261,7 +266,7 @@ export default {
|
|||
'Resend all previously attached files. Note: this will increase token cost and you may experience errors with many attachments.',
|
||||
com_endpoint_openai_detail:
|
||||
'The resolution for Vision requests. "Low" is cheaper and faster, "High" is more detailed and expensive, and "Auto" will automatically choose between the two based on the image resolution.',
|
||||
com_endpoint_openai_custom_name_placeholder: 'Set a custom name for ChatGPT',
|
||||
com_endpoint_openai_custom_name_placeholder: 'Set a custom name for the AI',
|
||||
com_endpoint_openai_prompt_prefix_placeholder:
|
||||
'Set custom instructions to include in System Message. Default: none',
|
||||
com_endpoint_anthropic_temp:
|
||||
|
|
|
|||
|
|
@ -60,3 +60,7 @@ export const optionText =
|
|||
|
||||
export const defaultTextPropsLabel =
|
||||
'rounded-md border border-gray-300 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none placeholder:text-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-700 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-600 dark:focus:outline-none';
|
||||
|
||||
export function capitalizeFirstLetter(string: string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
|
|
|||
413
package-lock.json
generated
413
package-lock.json
generated
|
|
@ -703,6 +703,7 @@
|
|||
"version": "0.7.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ariakit/react": "^0.4.5",
|
||||
"@dicebear/collection": "^7.0.4",
|
||||
"@dicebear/core": "^7.0.4",
|
||||
"@headlessui/react": "^1.7.13",
|
||||
|
|
@ -741,6 +742,7 @@
|
|||
"librechat-data-provider": "*",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.220.0",
|
||||
"match-sorter": "^6.3.4",
|
||||
"rc-input-number": "^7.4.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
|
|
@ -868,6 +870,41 @@
|
|||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@ariakit/core": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.4.5.tgz",
|
||||
"integrity": "sha512-e294+bEcyzt/H/kO4fS5/czLAlkF7PY+Kul3q2z54VY+GGay8NlVs9UezAB7L4jUBlYRAXwp7/1Sq3R7b+MZ7w=="
|
||||
},
|
||||
"node_modules/@ariakit/react": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.4.5.tgz",
|
||||
"integrity": "sha512-GUHxaOY1JZrJUHkuV20IY4NWcgknhqTQM0qCQcVZDCi+pJiWchUjTG+UyIr/Of02hU569qnQ7yovskCf+V3tNg==",
|
||||
"dependencies": {
|
||||
"@ariakit/react-core": "0.4.5"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ariakit"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ariakit/react-core": {
|
||||
"version": "0.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.4.5.tgz",
|
||||
"integrity": "sha512-ciTYPwpj/+mdA+EstveEnoygbx5e4PXQJxfkLKy4lkTkDJJUS9GcbYhdnIFJVUta6P1YFvzkIKo+/y9mcbMKJg==",
|
||||
"dependencies": {
|
||||
"@ariakit/core": "0.4.5",
|
||||
"@floating-ui/dom": "^1.0.0",
|
||||
"use-sync-external-store": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-crypto/crc32": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz",
|
||||
|
|
@ -4933,9 +4970,9 @@
|
|||
"integrity": "sha512-941kjlHjfI97l6NuH/AwuXV4mHuVnRooDcHNSlzi98hz+4ug3wT4gJcWjSwSZHqeGAEn90lC9sFD+8a9d5Jvxg=="
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
|
||||
"integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
|
|
@ -4949,9 +4986,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
|
||||
"integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
|
@ -4965,9 +5002,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -4981,9 +5018,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -4997,9 +5034,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -5013,9 +5050,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -5029,9 +5066,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -5045,9 +5082,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -5061,9 +5098,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
|
||||
"integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
||||
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
|
@ -5077,9 +5114,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -5093,9 +5130,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
|
||||
"integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
|
|
@ -5109,9 +5146,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
|
||||
"integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
||||
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
|
|
@ -5125,9 +5162,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
|
||||
"integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
||||
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
|
|
@ -5141,9 +5178,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
|
||||
"integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
||||
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
|
|
@ -5157,9 +5194,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
|
||||
"integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
||||
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
|
|
@ -5173,9 +5210,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
|
||||
"integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
||||
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
|
|
@ -5189,9 +5226,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -5205,9 +5242,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -5221,9 +5258,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -5237,9 +5274,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -5253,9 +5290,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
|
||||
"integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
||||
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -5269,9 +5306,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
|
||||
"integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
||||
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
|
|
@ -5285,9 +5322,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
|
||||
"integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
||||
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -8165,9 +8202,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz",
|
||||
"integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.1.tgz",
|
||||
"integrity": "sha512-fH8/o8nSUek8ceQnT7K4EQbSiV7jgkHq81m9lWZFIXjJ7lJzpWXbQFpT/Zh6OZYnpFykvzC3fbEvEAFZu03dPA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
|
@ -8178,9 +8215,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz",
|
||||
"integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.1.tgz",
|
||||
"integrity": "sha512-Y/9OHLjzkunF+KGEoJr3heiD5X9OLa8sbT1lm0NYeKyaM3oMhhQFvPB0bNZYJwlq93j8Z6wSxh9+cyKQaxS7PQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -8191,9 +8228,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz",
|
||||
"integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.1.tgz",
|
||||
"integrity": "sha512-+kecg3FY84WadgcuSVm6llrABOdQAEbNdnpi5X3UwWiFVhZIZvKgGrF7kmLguvxHNQy+UuRV66cLVl3S+Rkt+Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -8204,9 +8241,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz",
|
||||
"integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.1.tgz",
|
||||
"integrity": "sha512-2pYRzEjVqq2TB/UNv47BV/8vQiXkFGVmPFwJb+1E0IFFZbIX8/jo1olxqqMbo6xCXf8kabANhp5bzCij2tFLUA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -8217,9 +8254,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz",
|
||||
"integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.1.tgz",
|
||||
"integrity": "sha512-mS6wQ6Do6/wmrF9aTFVpIJ3/IDXhg1EZcQFYHZLHqw6AzMBjTHWnCG35HxSqUNphh0EHqSM6wRTT8HsL1C0x5g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
|
@ -8230,9 +8267,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz",
|
||||
"integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.1.tgz",
|
||||
"integrity": "sha512-p9rGKYkHdFMzhckOTFubfxgyIO1vw//7IIjBBRVzyZebWlzRLeNhqxuSaZ7kCEKVkm/kuC9fVRW9HkC/zNRG2w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -8243,9 +8280,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz",
|
||||
"integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.1.tgz",
|
||||
"integrity": "sha512-nDY6Yz5xS/Y4M2i9JLQd3Rofh5OR8Bn8qe3Mv/qCVpHFlwtZSBYSPaU4mrGazWkXrdQ98GB//H0BirGR/SKFSw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -8255,10 +8292,23 @@
|
|||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.1.tgz",
|
||||
"integrity": "sha512-im7HE4VBL+aDswvcmfx88Mp1soqL9OBsdDBU8NqDEYtkri0qV0THhQsvZtZeNNlLeCUQ16PZyv7cqutjDF35qw==",
|
||||
"cpu": [
|
||||
"ppc64le"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz",
|
||||
"integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.1.tgz",
|
||||
"integrity": "sha512-RWdiHuAxWmzPJgaHJdpvUUlDz8sdQz4P2uv367T2JocdDa98iRw2UjIJ4QxSyt077mXZT2X6pKfT2iYtVEvOFw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
|
|
@ -8268,10 +8318,23 @@
|
|||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.1.tgz",
|
||||
"integrity": "sha512-VMgaGQ5zRX6ZqV/fas65/sUGc9cPmsntq2FiGmayW9KMNfWVG/j0BAqImvU4KTeOOgYSf1F+k6at1UfNONuNjA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz",
|
||||
"integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.1.tgz",
|
||||
"integrity": "sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -8282,9 +8345,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz",
|
||||
"integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.1.tgz",
|
||||
"integrity": "sha512-JNEG/Ti55413SsreTguSx0LOVKX902OfXIKVg+TCXO6Gjans/k9O6ww9q3oLGjNDaTLxM+IHFMeXy/0RXL5R/g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -8295,9 +8358,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz",
|
||||
"integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.1.tgz",
|
||||
"integrity": "sha512-ryS22I9y0mumlLNwDFYZRDFLwWh3aKaC72CWjFcFvxK0U6v/mOkM5Up1bTbCRAhv3kEIwW2ajROegCIQViUCeA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
|
@ -8308,9 +8371,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz",
|
||||
"integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.1.tgz",
|
||||
"integrity": "sha512-TdloItiGk+T0mTxKx7Hp279xy30LspMso+GzQvV2maYePMAWdmrzqSNZhUpPj3CGw12aGj57I026PgLCTu8CGg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
|
|
@ -8321,9 +8384,9 @@
|
|||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz",
|
||||
"integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.1.tgz",
|
||||
"integrity": "sha512-wQGI+LY/Py20zdUPq+XCem7JcPOyzIJBm3dli+56DJsQOHbnXZFEwgmnC6el1TPAfC8lBT3m+z69RmLykNUbew==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
|
@ -13198,9 +13261,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.19.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
|
||||
"integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
||||
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
|
|
@ -13210,29 +13273,29 @@
|
|||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.19.12",
|
||||
"@esbuild/android-arm": "0.19.12",
|
||||
"@esbuild/android-arm64": "0.19.12",
|
||||
"@esbuild/android-x64": "0.19.12",
|
||||
"@esbuild/darwin-arm64": "0.19.12",
|
||||
"@esbuild/darwin-x64": "0.19.12",
|
||||
"@esbuild/freebsd-arm64": "0.19.12",
|
||||
"@esbuild/freebsd-x64": "0.19.12",
|
||||
"@esbuild/linux-arm": "0.19.12",
|
||||
"@esbuild/linux-arm64": "0.19.12",
|
||||
"@esbuild/linux-ia32": "0.19.12",
|
||||
"@esbuild/linux-loong64": "0.19.12",
|
||||
"@esbuild/linux-mips64el": "0.19.12",
|
||||
"@esbuild/linux-ppc64": "0.19.12",
|
||||
"@esbuild/linux-riscv64": "0.19.12",
|
||||
"@esbuild/linux-s390x": "0.19.12",
|
||||
"@esbuild/linux-x64": "0.19.12",
|
||||
"@esbuild/netbsd-x64": "0.19.12",
|
||||
"@esbuild/openbsd-x64": "0.19.12",
|
||||
"@esbuild/sunos-x64": "0.19.12",
|
||||
"@esbuild/win32-arm64": "0.19.12",
|
||||
"@esbuild/win32-ia32": "0.19.12",
|
||||
"@esbuild/win32-x64": "0.19.12"
|
||||
"@esbuild/aix-ppc64": "0.20.2",
|
||||
"@esbuild/android-arm": "0.20.2",
|
||||
"@esbuild/android-arm64": "0.20.2",
|
||||
"@esbuild/android-x64": "0.20.2",
|
||||
"@esbuild/darwin-arm64": "0.20.2",
|
||||
"@esbuild/darwin-x64": "0.20.2",
|
||||
"@esbuild/freebsd-arm64": "0.20.2",
|
||||
"@esbuild/freebsd-x64": "0.20.2",
|
||||
"@esbuild/linux-arm": "0.20.2",
|
||||
"@esbuild/linux-arm64": "0.20.2",
|
||||
"@esbuild/linux-ia32": "0.20.2",
|
||||
"@esbuild/linux-loong64": "0.20.2",
|
||||
"@esbuild/linux-mips64el": "0.20.2",
|
||||
"@esbuild/linux-ppc64": "0.20.2",
|
||||
"@esbuild/linux-riscv64": "0.20.2",
|
||||
"@esbuild/linux-s390x": "0.20.2",
|
||||
"@esbuild/linux-x64": "0.20.2",
|
||||
"@esbuild/netbsd-x64": "0.20.2",
|
||||
"@esbuild/openbsd-x64": "0.20.2",
|
||||
"@esbuild/sunos-x64": "0.20.2",
|
||||
"@esbuild/win32-arm64": "0.20.2",
|
||||
"@esbuild/win32-ia32": "0.20.2",
|
||||
"@esbuild/win32-x64": "0.20.2"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
|
|
@ -17990,9 +18053,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.16.9",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.9.tgz",
|
||||
"integrity": "sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==",
|
||||
"version": "0.16.10",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz",
|
||||
"integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==",
|
||||
"funding": [
|
||||
"https://opencollective.com/katex",
|
||||
"https://github.com/sponsors/katex"
|
||||
|
|
@ -19037,6 +19100,20 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/match-sorter": {
|
||||
"version": "6.3.4",
|
||||
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz",
|
||||
"integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.8",
|
||||
"remove-accents": "0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/match-sorter/node_modules/remove-accents": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
|
||||
"integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="
|
||||
},
|
||||
"node_modules/md5": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
|
||||
|
|
@ -21896,9 +21973,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.35",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
|
||||
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
|
||||
"version": "8.4.38",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
|
@ -21916,7 +21993,7 @@
|
|||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
|
|
@ -25136,9 +25213,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -27108,14 +27185,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz",
|
||||
"integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==",
|
||||
"version": "5.2.8",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
|
||||
"integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.19.3",
|
||||
"postcss": "^8.4.35",
|
||||
"rollup": "^4.2.0"
|
||||
"esbuild": "^0.20.1",
|
||||
"postcss": "^8.4.38",
|
||||
"rollup": "^4.13.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
|
|
@ -27252,9 +27329,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vite/node_modules/rollup": {
|
||||
"version": "4.9.6",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz",
|
||||
"integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==",
|
||||
"version": "4.14.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.1.tgz",
|
||||
"integrity": "sha512-4LnHSdd3QK2pa1J6dFbfm1HN0D7vSK/ZuZTsdyUAlA6Rr1yTouUTL13HaDOGJVgby461AhrNGBS7sCGXXtT+SA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.5"
|
||||
|
|
@ -27267,19 +27344,21 @@
|
|||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.9.6",
|
||||
"@rollup/rollup-android-arm64": "4.9.6",
|
||||
"@rollup/rollup-darwin-arm64": "4.9.6",
|
||||
"@rollup/rollup-darwin-x64": "4.9.6",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.9.6",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.9.6",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.9.6",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.9.6",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.9.6",
|
||||
"@rollup/rollup-linux-x64-musl": "4.9.6",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.9.6",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.9.6",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.9.6",
|
||||
"@rollup/rollup-android-arm-eabi": "4.14.1",
|
||||
"@rollup/rollup-android-arm64": "4.14.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.14.1",
|
||||
"@rollup/rollup-darwin-x64": "4.14.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.14.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.14.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.14.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.14.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.14.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.14.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.14.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.14.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.14.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.14.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.14.1",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
|
|
@ -28080,7 +28159,7 @@
|
|||
},
|
||||
"packages/data-provider": {
|
||||
"name": "librechat-data-provider",
|
||||
"version": "0.5.1",
|
||||
"version": "0.5.2",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
|
|
|
|||
525
packages/data-provider/specs/generate.spec.ts
Normal file
525
packages/data-provider/specs/generate.spec.ts
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
/* eslint-disable jest/no-conditional-expect */
|
||||
import { ZodError, z } from 'zod';
|
||||
import { generateDynamicSchema, validateSettingDefinitions } from '../src/generate';
|
||||
import type { SettingsConfiguration } from '../src/generate';
|
||||
|
||||
describe('generateDynamicSchema', () => {
|
||||
it('should generate a schema for number settings with range', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'testNumber',
|
||||
description: 'A test number setting',
|
||||
type: 'number',
|
||||
default: 5,
|
||||
range: { min: 1, max: 10, step: 1 },
|
||||
component: 'slider',
|
||||
optionType: 'conversation',
|
||||
columnSpan: 2,
|
||||
label: 'Test Number Slider',
|
||||
},
|
||||
];
|
||||
|
||||
const schema = generateDynamicSchema(settings);
|
||||
const result = schema.safeParse({ testNumber: 6 });
|
||||
|
||||
expect(result.success).toBeTruthy();
|
||||
expect(result['data']).toEqual({ testNumber: 6 });
|
||||
});
|
||||
|
||||
it('should generate a schema for boolean settings', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'testBoolean',
|
||||
description: 'A test boolean setting',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
component: 'switch',
|
||||
optionType: 'model', // Only if relevant to your application's context
|
||||
columnSpan: 1,
|
||||
label: 'Test Boolean Switch',
|
||||
},
|
||||
];
|
||||
|
||||
const schema = generateDynamicSchema(settings);
|
||||
const result = schema.safeParse({ testBoolean: false });
|
||||
|
||||
expect(result.success).toBeTruthy();
|
||||
expect(result['data']).toEqual({ testBoolean: false });
|
||||
});
|
||||
|
||||
it('should generate a schema for string settings', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'testString',
|
||||
description: 'A test string setting',
|
||||
type: 'string',
|
||||
default: 'default value',
|
||||
component: 'input',
|
||||
optionType: 'model', // Optional and only if relevant
|
||||
columnSpan: 3,
|
||||
label: 'Test String Input',
|
||||
placeholder: 'Enter text here...',
|
||||
minText: 0, // Optional
|
||||
maxText: 100, // Optional
|
||||
},
|
||||
];
|
||||
|
||||
const schema = generateDynamicSchema(settings);
|
||||
const result = schema.safeParse({ testString: 'custom value' });
|
||||
|
||||
expect(result.success).toBeTruthy();
|
||||
expect(result['data']).toEqual({ testString: 'custom value' });
|
||||
});
|
||||
|
||||
it('should generate a schema for enum settings', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'testEnum',
|
||||
description: 'A test enum setting',
|
||||
type: 'enum',
|
||||
default: 'option1',
|
||||
options: ['option1', 'option2', 'option3'],
|
||||
enumMappings: {
|
||||
option1: 'First Option',
|
||||
option2: 'Second Option',
|
||||
option3: 'Third Option',
|
||||
},
|
||||
component: 'dropdown',
|
||||
columnSpan: 2,
|
||||
label: 'Test Enum Dropdown',
|
||||
},
|
||||
];
|
||||
|
||||
const schema = generateDynamicSchema(settings);
|
||||
const result = schema.safeParse({ testEnum: 'option2' });
|
||||
|
||||
expect(result.success).toBeTruthy();
|
||||
expect(result['data']).toEqual({ testEnum: 'option2' });
|
||||
});
|
||||
|
||||
it('should fail for incorrect enum value', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'testEnum',
|
||||
description: 'A test enum setting',
|
||||
type: 'enum',
|
||||
default: 'option1',
|
||||
options: ['option1', 'option2', 'option3'],
|
||||
component: 'dropdown',
|
||||
},
|
||||
];
|
||||
|
||||
const schema = generateDynamicSchema(settings);
|
||||
const result = schema.safeParse({ testEnum: 'option4' }); // This option does not exist
|
||||
|
||||
expect(result.success).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateSettingDefinitions', () => {
|
||||
// Test for valid setting configurations
|
||||
test('should not throw error for valid settings', () => {
|
||||
const validSettings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'themeColor',
|
||||
component: 'input',
|
||||
type: 'string',
|
||||
default: '#ffffff',
|
||||
label: 'Theme Color',
|
||||
columns: 2,
|
||||
columnSpan: 1,
|
||||
optionType: 'model',
|
||||
},
|
||||
{
|
||||
key: 'fontSize',
|
||||
component: 'slider',
|
||||
type: 'number',
|
||||
range: { min: 8, max: 36 },
|
||||
default: 14,
|
||||
columnSpan: 2,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(validSettings)).not.toThrow();
|
||||
});
|
||||
|
||||
// Test for incorrectly configured columns
|
||||
test('should throw error for invalid columns configuration', () => {
|
||||
const invalidSettings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'themeColor',
|
||||
component: 'input',
|
||||
type: 'string',
|
||||
columns: 5,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(invalidSettings)).toThrow(ZodError);
|
||||
});
|
||||
|
||||
test('should correctly handle columnSpan defaulting based on columns', () => {
|
||||
const settingsWithColumnAdjustment: SettingsConfiguration = [
|
||||
{
|
||||
key: 'fontSize',
|
||||
component: 'slider',
|
||||
type: 'number',
|
||||
columns: 4,
|
||||
range: { min: 8, max: 14 },
|
||||
default: 11,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settingsWithColumnAdjustment)).not.toThrow();
|
||||
});
|
||||
|
||||
// Test for label defaulting to key if not provided
|
||||
test('label should default to key if not explicitly set', () => {
|
||||
const settingsWithDefaultLabel: SettingsConfiguration = [
|
||||
{ key: 'fontWeight', component: 'dropdown', type: 'string', options: ['normal', 'bold'] },
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settingsWithDefaultLabel)).not.toThrow();
|
||||
expect(settingsWithDefaultLabel[0].label).toBe('fontWeight');
|
||||
});
|
||||
|
||||
// Test for minText and maxText in input/textarea component
|
||||
test('should throw error for negative minText or maxText', () => {
|
||||
const settingsWithNegativeTextLimits: SettingsConfiguration = [
|
||||
{ key: 'biography', component: 'textarea', type: 'string', minText: -1 },
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settingsWithNegativeTextLimits)).toThrow(ZodError);
|
||||
});
|
||||
|
||||
// Validate optionType with tConversationSchema
|
||||
test('should throw error for optionType "conversation" not matching schema', () => {
|
||||
const settingsWithInvalidConversationOptionType: SettingsConfiguration = [
|
||||
{ key: 'userAge', component: 'input', type: 'number', optionType: 'conversation' },
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settingsWithInvalidConversationOptionType)).toThrow(
|
||||
ZodError,
|
||||
);
|
||||
});
|
||||
|
||||
// Test for columnSpan defaulting and label defaulting to key
|
||||
test('columnSpan defaults based on columns and label defaults to key if not set', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'textSize',
|
||||
type: 'number',
|
||||
component: 'slider',
|
||||
range: { min: 10, max: 20 },
|
||||
columns: 4,
|
||||
},
|
||||
];
|
||||
|
||||
validateSettingDefinitions(settings); // Perform validation which also mutates settings with default values
|
||||
|
||||
expect(settings[0].columnSpan).toBe(2); // Expects columnSpan to default based on columns
|
||||
expect(settings[0].label).toBe('textSize'); // Expects label to default to key
|
||||
});
|
||||
|
||||
// Test for errors thrown due to invalid columns value
|
||||
test('throws error if columns value is out of range', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'themeMode',
|
||||
type: 'string',
|
||||
component: 'dropdown',
|
||||
options: ['dark', 'light'],
|
||||
columns: 5,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
||||
});
|
||||
|
||||
// Test range validation for slider component
|
||||
test('slider component range validation', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{ key: 'volume', type: 'number', component: 'slider' }, // Missing range
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
||||
});
|
||||
|
||||
// Test options validation for enum type in slider component
|
||||
test('slider component with enum type requires at least 2 options', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{ key: 'color', type: 'enum', component: 'slider', options: ['red'] }, // Not enough options
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
||||
});
|
||||
|
||||
// Test checkbox component options validation
|
||||
test('checkbox component must have 1-2 options if options are provided', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'agreeToTerms',
|
||||
type: 'boolean',
|
||||
component: 'checkbox',
|
||||
options: ['Yes', 'No', 'Maybe'],
|
||||
}, // Too many options
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
||||
});
|
||||
|
||||
// Test dropdown component options validation
|
||||
test('dropdown component requires at least 2 options', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{ key: 'country', type: 'enum', component: 'dropdown', options: ['USA'] }, // Not enough options
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
||||
});
|
||||
|
||||
// Validate minText and maxText constraints in input and textarea
|
||||
test('validate minText and maxText constraints', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{ key: 'biography', type: 'string', component: 'textarea', minText: 10, maxText: 5 }, // Incorrect minText and maxText
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
||||
});
|
||||
|
||||
// Validate optionType constraint with tConversationSchema
|
||||
test('validate optionType constraint with tConversationSchema', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{ key: 'userAge', type: 'number', component: 'input', optionType: 'conversation' }, // No corresponding schema in tConversationSchema
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settings)).toThrow(ZodError);
|
||||
});
|
||||
|
||||
// Validate correct handling of boolean settings with default values
|
||||
test('correct handling of boolean settings with defaults', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{ key: 'enableFeatureX', type: 'boolean', component: 'switch' }, // Missing default, should default to false
|
||||
];
|
||||
|
||||
validateSettingDefinitions(settings); // This would populate default values where missing
|
||||
|
||||
expect(settings[0].default).toBe(false); // Expects default to be false for boolean without explicit default
|
||||
});
|
||||
|
||||
// Validate that number slider without default uses middle of range
|
||||
test('number slider without default uses middle of range', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{ key: 'brightness', type: 'number', component: 'slider', range: { min: 0, max: 100 } }, // Missing default
|
||||
];
|
||||
|
||||
validateSettingDefinitions(settings); // This would populate default values where missing
|
||||
|
||||
expect(settings[0].default).toBe(50); // Expects default to be midpoint of range
|
||||
});
|
||||
});
|
||||
|
||||
const settingsConfiguration: SettingsConfiguration = [
|
||||
{
|
||||
key: 'temperature',
|
||||
description:
|
||||
'Higher values = more random, while lower values = more focused and deterministic. We recommend altering this or Top P but not both.',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
range: {
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
},
|
||||
component: 'slider',
|
||||
},
|
||||
{
|
||||
key: 'top_p',
|
||||
description:
|
||||
'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We recommend altering this or temperature but not both.',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
range: {
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
},
|
||||
component: 'slider',
|
||||
},
|
||||
{
|
||||
key: 'presence_penalty',
|
||||
description:
|
||||
'Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
range: {
|
||||
min: -2,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
},
|
||||
component: 'slider',
|
||||
},
|
||||
{
|
||||
key: 'frequency_penalty',
|
||||
description:
|
||||
'Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
range: {
|
||||
min: -2,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
},
|
||||
component: 'slider',
|
||||
},
|
||||
{
|
||||
key: 'resendFiles',
|
||||
description:
|
||||
'Resend all previously attached files. Note: this will increase token cost and you may experience errors with many attachments.',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
component: 'switch',
|
||||
},
|
||||
{
|
||||
key: 'imageDetail',
|
||||
description:
|
||||
'The resolution for Vision requests. "Low" is cheaper and faster, "High" is more detailed and expensive, and "Auto" will automatically choose between the two based on the image resolution.',
|
||||
type: 'enum',
|
||||
default: 'auto',
|
||||
options: ['low', 'high', 'auto'],
|
||||
component: 'slider',
|
||||
},
|
||||
{
|
||||
key: 'promptPrefix',
|
||||
type: 'string',
|
||||
default: '',
|
||||
component: 'input',
|
||||
placeholder: 'Set custom instructions to include in System Message. Default: none',
|
||||
},
|
||||
{
|
||||
key: 'chatGptLabel',
|
||||
type: 'string',
|
||||
default: '',
|
||||
component: 'input',
|
||||
placeholder: 'Set a custom name for your AI',
|
||||
},
|
||||
];
|
||||
|
||||
describe('Settings Validation and Schema Generation', () => {
|
||||
// Test 1: Validate settings definitions do not throw for valid configuration
|
||||
test('validateSettingDefinitions does not throw for valid configuration', () => {
|
||||
expect(() => validateSettingDefinitions(settingsConfiguration)).not.toThrow();
|
||||
});
|
||||
|
||||
test('validateSettingDefinitions throws for invalid type in settings', () => {
|
||||
const settingsWithInvalidType = [
|
||||
...settingsConfiguration,
|
||||
{
|
||||
key: 'newSetting',
|
||||
description: 'A setting with an unsupported type',
|
||||
type: 'unsupportedType', // Assuming 'unsupportedType' is not supported
|
||||
component: 'input',
|
||||
},
|
||||
];
|
||||
|
||||
expect(() =>
|
||||
validateSettingDefinitions(settingsWithInvalidType as SettingsConfiguration),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test('validateSettingDefinitions throws for missing required fields', () => {
|
||||
const settingsMissingRequiredField = [
|
||||
...settingsConfiguration,
|
||||
{
|
||||
key: 'incompleteSetting',
|
||||
type: 'number',
|
||||
// Missing 'component',
|
||||
},
|
||||
];
|
||||
|
||||
expect(() =>
|
||||
validateSettingDefinitions(settingsMissingRequiredField as SettingsConfiguration),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test('validateSettingDefinitions throws for default value out of range', () => {
|
||||
const settingsOutOfRange = [
|
||||
...settingsConfiguration,
|
||||
{
|
||||
key: 'rangeTestSetting',
|
||||
description: 'A setting with default value out of specified range',
|
||||
type: 'number',
|
||||
default: 5,
|
||||
range: {
|
||||
min: 0,
|
||||
max: 1,
|
||||
},
|
||||
component: 'slider',
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settingsOutOfRange as SettingsConfiguration)).toThrow();
|
||||
});
|
||||
|
||||
test('validateSettingDefinitions throws for enum setting with incorrect default', () => {
|
||||
const settingsWithIncorrectEnumDefault = [
|
||||
...settingsConfiguration,
|
||||
{
|
||||
key: 'enumSetting',
|
||||
description: 'Enum setting with a default not in options',
|
||||
type: 'enum',
|
||||
default: 'unlistedOption',
|
||||
options: ['option1', 'option2'],
|
||||
component: 'dropdown',
|
||||
},
|
||||
];
|
||||
|
||||
expect(() =>
|
||||
validateSettingDefinitions(settingsWithIncorrectEnumDefault as SettingsConfiguration),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
// Test 2: Generate dynamic schema and validate correct input
|
||||
test('generateDynamicSchema generates a schema that validates correct input', () => {
|
||||
const schema = generateDynamicSchema(settingsConfiguration);
|
||||
const validInput = {
|
||||
temperature: 0.5,
|
||||
top_p: 0.8,
|
||||
presence_penalty: 1,
|
||||
frequency_penalty: -1,
|
||||
resendFiles: true,
|
||||
imageDetail: 'high',
|
||||
promptPrefix: 'Hello, AI.',
|
||||
chatGptLabel: 'My Custom AI',
|
||||
};
|
||||
|
||||
expect(schema.parse(validInput)).toEqual(validInput);
|
||||
});
|
||||
|
||||
// Test 3: Generate dynamic schema and catch invalid input
|
||||
test('generateDynamicSchema generates a schema that catches invalid input and provides detailed errors', async () => {
|
||||
const schema = generateDynamicSchema(settingsConfiguration);
|
||||
const invalidInput: z.infer<typeof schema> = {
|
||||
temperature: 2.5, // Out of range
|
||||
top_p: -0.5, // Out of range
|
||||
presence_penalty: 3, // Out of range
|
||||
frequency_penalty: -3, // Out of range
|
||||
resendFiles: 'yes', // Wrong type
|
||||
imageDetail: 'ultra', // Invalid option
|
||||
promptPrefix: 123, // Wrong type
|
||||
chatGptLabel: true, // Wrong type
|
||||
};
|
||||
|
||||
const result = schema.safeParse(invalidInput);
|
||||
expect(result.success).toBeFalsy();
|
||||
if (!result.success) {
|
||||
const errorPaths = result.error.issues.map((issue) => issue.path.join('.'));
|
||||
expect(errorPaths).toContain('temperature');
|
||||
expect(errorPaths).toContain('top_p');
|
||||
expect(errorPaths).toContain('presence_penalty');
|
||||
expect(errorPaths).toContain('frequency_penalty');
|
||||
expect(errorPaths).toContain('resendFiles');
|
||||
expect(errorPaths).toContain('imageDetail');
|
||||
expect(errorPaths).toContain('promptPrefix');
|
||||
expect(errorPaths).toContain('chatGptLabel');
|
||||
}
|
||||
});
|
||||
});
|
||||
474
packages/data-provider/src/generate.ts
Normal file
474
packages/data-provider/src/generate.ts
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
import { z, ZodError, ZodIssueCode } from 'zod';
|
||||
import { tConversationSchema, googleSettings as google, openAISettings as openAI } from './schemas';
|
||||
import type { ZodIssue } from 'zod';
|
||||
import type { TConversation, TSetOption } from './schemas';
|
||||
|
||||
export type GoogleSettings = Partial<typeof google>;
|
||||
export type OpenAISettings = Partial<typeof google>;
|
||||
|
||||
export type ComponentType = 'input' | 'textarea' | 'slider' | 'checkbox' | 'switch' | 'dropdown';
|
||||
|
||||
export type OptionType = 'conversation' | 'model' | 'custom';
|
||||
|
||||
export enum ComponentTypes {
|
||||
Input = 'input',
|
||||
Textarea = 'textarea',
|
||||
Slider = 'slider',
|
||||
Checkbox = 'checkbox',
|
||||
Switch = 'switch',
|
||||
Dropdown = 'dropdown',
|
||||
}
|
||||
|
||||
export enum OptionTypes {
|
||||
Conversation = 'conversation',
|
||||
Model = 'model',
|
||||
Custom = 'custom',
|
||||
}
|
||||
export interface SettingDefinition {
|
||||
key: string;
|
||||
description?: string;
|
||||
type: 'number' | 'boolean' | 'string' | 'enum';
|
||||
default?: number | boolean | string;
|
||||
showDefault?: boolean;
|
||||
options?: string[];
|
||||
range?: SettingRange;
|
||||
enumMappings?: Record<string, number | boolean | string>;
|
||||
component: ComponentType;
|
||||
optionType?: OptionType;
|
||||
columnSpan?: number;
|
||||
columns?: number;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
labelCode?: boolean;
|
||||
placeholderCode?: boolean;
|
||||
descriptionCode?: boolean;
|
||||
minText?: number;
|
||||
maxText?: number;
|
||||
includeInput?: boolean; // Specific to slider component
|
||||
}
|
||||
|
||||
export type DynamicSettingProps = Partial<SettingDefinition> & {
|
||||
readonly?: boolean;
|
||||
settingKey: string;
|
||||
setOption: TSetOption;
|
||||
defaultValue?: number | boolean | string;
|
||||
};
|
||||
|
||||
const requiredSettingFields = ['key', 'type', 'component'];
|
||||
|
||||
export interface SettingRange {
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
}
|
||||
|
||||
export type SettingsConfiguration = SettingDefinition[];
|
||||
|
||||
export function generateDynamicSchema(settings: SettingsConfiguration) {
|
||||
const schemaFields: { [key: string]: z.ZodTypeAny } = {};
|
||||
|
||||
for (const setting of settings) {
|
||||
const { key, type, default: defaultValue, range, options, minText, maxText } = setting;
|
||||
|
||||
if (type === 'number') {
|
||||
let schema = z.number();
|
||||
if (range) {
|
||||
schema = schema.min(range.min);
|
||||
schema = schema.max(range.max);
|
||||
}
|
||||
if (typeof defaultValue === 'number') {
|
||||
schemaFields[key] = schema.default(defaultValue);
|
||||
} else {
|
||||
schemaFields[key] = schema;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type === 'boolean') {
|
||||
const schema = z.boolean();
|
||||
if (typeof defaultValue === 'boolean') {
|
||||
schemaFields[key] = schema.default(defaultValue);
|
||||
} else {
|
||||
schemaFields[key] = schema;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type === 'string') {
|
||||
let schema = z.string();
|
||||
if (minText) {
|
||||
schema = schema.min(minText);
|
||||
}
|
||||
if (maxText) {
|
||||
schema = schema.max(maxText);
|
||||
}
|
||||
if (typeof defaultValue === 'string') {
|
||||
schemaFields[key] = schema.default(defaultValue);
|
||||
} else {
|
||||
schemaFields[key] = schema;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type === 'enum') {
|
||||
if (!options || options.length === 0) {
|
||||
console.warn(`Missing or empty 'options' for enum setting '${key}'.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const schema = z.enum(options as [string, ...string[]]);
|
||||
if (typeof defaultValue === 'string') {
|
||||
schemaFields[key] = schema.default(defaultValue);
|
||||
} else {
|
||||
schemaFields[key] = schema;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
console.warn(`Unsupported setting type: ${type}`);
|
||||
}
|
||||
|
||||
return z.object(schemaFields);
|
||||
}
|
||||
|
||||
const ZodTypeToSettingType: Record<string, string | undefined> = {
|
||||
ZodString: 'string',
|
||||
ZodNumber: 'number',
|
||||
ZodBoolean: 'boolean',
|
||||
};
|
||||
|
||||
const minColumns = 1;
|
||||
const maxColumns = 4;
|
||||
const minSliderOptions = 2;
|
||||
const minDropdownOptions = 2;
|
||||
|
||||
/**
|
||||
* Validates the provided setting using the constraints unique to each component type.
|
||||
* @throws {ZodError} Throws a ZodError if any validation fails.
|
||||
*/
|
||||
export function validateSettingDefinitions(settings: SettingsConfiguration): void {
|
||||
const errors: ZodIssue[] = [];
|
||||
// Validate columns
|
||||
const columnsSet = new Set<number>();
|
||||
for (const setting of settings) {
|
||||
if (setting.columns !== undefined) {
|
||||
if (setting.columns < minColumns || setting.columns > maxColumns) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Invalid columns value for setting ${setting.key}. Must be between ${minColumns} and ${maxColumns}.`,
|
||||
path: ['columns'],
|
||||
});
|
||||
} else {
|
||||
columnsSet.add(setting.columns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const columns = columnsSet.size === 1 ? columnsSet.values().next().value : 2;
|
||||
|
||||
for (const setting of settings) {
|
||||
for (const field of requiredSettingFields) {
|
||||
if (setting[field as keyof SettingDefinition] === undefined) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Missing required field ${field} for setting ${setting.key}.`,
|
||||
path: [field],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// check accepted types
|
||||
if (!['number', 'boolean', 'string', 'enum'].includes(setting.type)) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Invalid type for setting ${setting.key}. Must be one of 'number', 'boolean', 'string', 'enum'.`,
|
||||
path: ['type'],
|
||||
});
|
||||
}
|
||||
|
||||
// Predefined constraints based on components
|
||||
if (setting.component === 'input' || setting.component === 'textarea') {
|
||||
if (setting.type === 'number' && setting.component === 'textarea') {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Textarea component for setting ${setting.key} must have type string.`,
|
||||
path: ['type'],
|
||||
});
|
||||
// continue;
|
||||
}
|
||||
|
||||
if (
|
||||
setting.minText !== undefined &&
|
||||
setting.maxText !== undefined &&
|
||||
setting.minText > setting.maxText
|
||||
) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `For setting ${setting.key}, minText cannot be greater than maxText.`,
|
||||
path: [setting.key, 'minText', 'maxText'],
|
||||
});
|
||||
// continue;
|
||||
}
|
||||
if (!setting.placeholder) {
|
||||
setting.placeholder = '';
|
||||
} // Default placeholder
|
||||
}
|
||||
|
||||
if (setting.component === 'slider') {
|
||||
if (setting.type === 'number' && !setting.range) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Slider component for setting ${setting.key} must have a range if type is number.`,
|
||||
path: ['range'],
|
||||
});
|
||||
// continue;
|
||||
}
|
||||
if (
|
||||
setting.type === 'enum' &&
|
||||
(!setting.options || setting.options.length < minSliderOptions)
|
||||
) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Slider component for setting ${setting.key} requires at least ${minSliderOptions} options for enum type.`,
|
||||
path: ['options'],
|
||||
});
|
||||
// continue;
|
||||
}
|
||||
setting.includeInput = setting.type === 'number' ? setting.includeInput ?? true : false; // Default to true if type is number
|
||||
}
|
||||
|
||||
if (setting.component === 'slider' && setting.type === 'number') {
|
||||
if (setting.default === undefined && setting.range) {
|
||||
// Set default to the middle of the range if unspecified
|
||||
setting.default = Math.round((setting.range.min + setting.range.max) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (setting.component === 'checkbox' || setting.component === 'switch') {
|
||||
if (setting.options && setting.options.length > 2) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Checkbox/Switch component for setting ${setting.key} must have 1-2 options.`,
|
||||
path: ['options'],
|
||||
});
|
||||
// continue;
|
||||
}
|
||||
if (!setting.default && setting.type === 'boolean') {
|
||||
setting.default = false; // Default to false if type is boolean
|
||||
}
|
||||
}
|
||||
|
||||
if (setting.component === 'dropdown') {
|
||||
if (!setting.options || setting.options.length < minDropdownOptions) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Dropdown component for setting ${setting.key} requires at least ${minDropdownOptions} options.`,
|
||||
path: ['options'],
|
||||
});
|
||||
// continue;
|
||||
}
|
||||
if (!setting.default && setting.options && setting.options.length > 0) {
|
||||
setting.default = setting.options[0]; // Default to first option if not specified
|
||||
}
|
||||
}
|
||||
|
||||
// Default columnSpan
|
||||
if (!setting.columnSpan) {
|
||||
setting.columnSpan = Math.floor(columns / 2);
|
||||
}
|
||||
|
||||
// Default label to key
|
||||
if (!setting.label) {
|
||||
setting.label = setting.key;
|
||||
}
|
||||
|
||||
// Validate minText and maxText for input/textarea
|
||||
if (setting.component === 'input' || setting.component === 'textarea') {
|
||||
if (setting.minText !== undefined && setting.minText < 0) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Invalid minText value for setting ${setting.key}. Must be non-negative.`,
|
||||
path: ['minText'],
|
||||
});
|
||||
}
|
||||
if (setting.maxText !== undefined && setting.maxText < 0) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Invalid maxText value for setting ${setting.key}. Must be non-negative.`,
|
||||
path: ['maxText'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Validate optionType and conversation schema
|
||||
if (setting.optionType !== OptionTypes.Custom) {
|
||||
const conversationSchema = tConversationSchema.shape[setting.key as keyof TConversation];
|
||||
if (!conversationSchema) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Setting ${setting.key} with optionType "${setting.optionType}" must be defined in tConversationSchema.`,
|
||||
path: ['optionType'],
|
||||
});
|
||||
} else {
|
||||
const zodType = conversationSchema._def.typeName;
|
||||
const settingTypeEquivalent = ZodTypeToSettingType[zodType] || null;
|
||||
if (settingTypeEquivalent !== setting.type) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Setting ${setting.key} with optionType "${setting.optionType}" must match the type defined in tConversationSchema.`,
|
||||
path: ['optionType'],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Default value checks */
|
||||
if (setting.type === 'number' && isNaN(setting.default as number)) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Invalid default value for setting ${setting.key}. Must be a number.`,
|
||||
path: ['default'],
|
||||
});
|
||||
}
|
||||
|
||||
if (setting.type === 'boolean' && typeof setting.default !== 'boolean') {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Invalid default value for setting ${setting.key}. Must be a boolean.`,
|
||||
path: ['default'],
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
(setting.type === 'string' || setting.type === 'enum') &&
|
||||
typeof setting.default !== 'string'
|
||||
) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Invalid default value for setting ${setting.key}. Must be a string.`,
|
||||
path: ['default'],
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
setting.type === 'enum' &&
|
||||
setting.options &&
|
||||
!setting.options.includes(setting.default as string)
|
||||
) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Invalid default value for setting ${
|
||||
setting.key
|
||||
}. Must be one of the options: [${setting.options.join(', ')}].`,
|
||||
path: ['default'],
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
setting.type === 'number' &&
|
||||
setting.range &&
|
||||
typeof setting.default === 'number' &&
|
||||
(setting.default < setting.range.min || setting.default > setting.range.max)
|
||||
) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Invalid default value for setting ${setting.key}. Must be within the range [${setting.range.min}, ${setting.range.max}].`,
|
||||
path: ['default'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new ZodError(errors);
|
||||
}
|
||||
}
|
||||
|
||||
export const generateOpenAISchema = (customOpenAI: OpenAISettings) => {
|
||||
const defaults = { ...openAI, ...customOpenAI };
|
||||
return tConversationSchema
|
||||
.pick({
|
||||
model: true,
|
||||
chatGptLabel: true,
|
||||
promptPrefix: true,
|
||||
temperature: true,
|
||||
top_p: true,
|
||||
presence_penalty: true,
|
||||
frequency_penalty: true,
|
||||
resendFiles: true,
|
||||
imageDetail: true,
|
||||
})
|
||||
.transform((obj) => ({
|
||||
...obj,
|
||||
model: obj.model ?? defaults.model.default,
|
||||
chatGptLabel: obj.chatGptLabel ?? null,
|
||||
promptPrefix: obj.promptPrefix ?? null,
|
||||
temperature: obj.temperature ?? defaults.temperature.default,
|
||||
top_p: obj.top_p ?? defaults.top_p.default,
|
||||
presence_penalty: obj.presence_penalty ?? defaults.presence_penalty.default,
|
||||
frequency_penalty: obj.frequency_penalty ?? defaults.frequency_penalty.default,
|
||||
resendFiles:
|
||||
typeof obj.resendFiles === 'boolean' ? obj.resendFiles : defaults.resendFiles.default,
|
||||
imageDetail: obj.imageDetail ?? defaults.imageDetail.default,
|
||||
}))
|
||||
.catch(() => ({
|
||||
model: defaults.model.default,
|
||||
chatGptLabel: null,
|
||||
promptPrefix: null,
|
||||
temperature: defaults.temperature.default,
|
||||
top_p: defaults.top_p.default,
|
||||
presence_penalty: defaults.presence_penalty.default,
|
||||
frequency_penalty: defaults.frequency_penalty.default,
|
||||
resendFiles: defaults.resendFiles.default,
|
||||
imageDetail: defaults.imageDetail.default,
|
||||
}));
|
||||
};
|
||||
|
||||
export const generateGoogleSchema = (customGoogle: GoogleSettings) => {
|
||||
const defaults = { ...google, ...customGoogle };
|
||||
return tConversationSchema
|
||||
.pick({
|
||||
model: true,
|
||||
modelLabel: true,
|
||||
promptPrefix: true,
|
||||
examples: true,
|
||||
temperature: true,
|
||||
maxOutputTokens: true,
|
||||
topP: true,
|
||||
topK: true,
|
||||
})
|
||||
.transform((obj) => {
|
||||
const isGeminiPro = obj?.model?.toLowerCase()?.includes('gemini-pro');
|
||||
|
||||
const maxOutputTokensMax = isGeminiPro
|
||||
? defaults.maxOutputTokens.maxGeminiPro
|
||||
: defaults.maxOutputTokens.max;
|
||||
const maxOutputTokensDefault = isGeminiPro
|
||||
? defaults.maxOutputTokens.defaultGeminiPro
|
||||
: defaults.maxOutputTokens.default;
|
||||
|
||||
let maxOutputTokens = obj.maxOutputTokens ?? maxOutputTokensDefault;
|
||||
maxOutputTokens = Math.min(maxOutputTokens, maxOutputTokensMax);
|
||||
|
||||
return {
|
||||
...obj,
|
||||
model: obj.model ?? defaults.model.default,
|
||||
modelLabel: obj.modelLabel ?? null,
|
||||
promptPrefix: obj.promptPrefix ?? null,
|
||||
examples: obj.examples ?? [{ input: { content: '' }, output: { content: '' } }],
|
||||
temperature: obj.temperature ?? defaults.temperature.default,
|
||||
maxOutputTokens,
|
||||
topP: obj.topP ?? defaults.topP.default,
|
||||
topK: obj.topK ?? defaults.topK.default,
|
||||
};
|
||||
})
|
||||
.catch(() => ({
|
||||
model: defaults.model.default,
|
||||
modelLabel: null,
|
||||
promptPrefix: null,
|
||||
examples: [{ input: { content: '' }, output: { content: '' } }],
|
||||
temperature: defaults.temperature.default,
|
||||
maxOutputTokens: defaults.maxOutputTokens.default,
|
||||
topP: defaults.topP.default,
|
||||
topK: defaults.topK.default,
|
||||
}));
|
||||
};
|
||||
|
|
@ -4,6 +4,7 @@ export * from './config';
|
|||
export * from './file-config';
|
||||
/* schema helpers */
|
||||
export * from './parsers';
|
||||
export * from './generate';
|
||||
/* types (exports schemas from `./types` as they contain needed in other defs) */
|
||||
export * from './types';
|
||||
export * from './types/assistants';
|
||||
|
|
|
|||
|
|
@ -17,6 +17,26 @@ export enum EModelEndpoint {
|
|||
custom = 'custom',
|
||||
}
|
||||
|
||||
export enum ImageDetail {
|
||||
low = 'low',
|
||||
auto = 'auto',
|
||||
high = 'high',
|
||||
}
|
||||
|
||||
export const imageDetailNumeric = {
|
||||
[ImageDetail.low]: 0,
|
||||
[ImageDetail.auto]: 1,
|
||||
[ImageDetail.high]: 2,
|
||||
};
|
||||
|
||||
export const imageDetailValue = {
|
||||
0: ImageDetail.low,
|
||||
1: ImageDetail.auto,
|
||||
2: ImageDetail.high,
|
||||
};
|
||||
|
||||
export const eImageDetailSchema = z.nativeEnum(ImageDetail);
|
||||
|
||||
export const defaultAssistantFormValues = {
|
||||
assistant: '',
|
||||
id: '',
|
||||
|
|
@ -46,38 +66,77 @@ export const ImageVisionTool: FunctionTool = {
|
|||
export const isImageVisionTool = (tool: FunctionTool | FunctionToolCall) =>
|
||||
tool.type === 'function' && tool.function?.name === ImageVisionTool?.function?.name;
|
||||
|
||||
export const endpointSettings = {
|
||||
[EModelEndpoint.google]: {
|
||||
model: {
|
||||
default: 'chat-bison',
|
||||
},
|
||||
maxOutputTokens: {
|
||||
min: 1,
|
||||
max: 2048,
|
||||
step: 1,
|
||||
default: 1024,
|
||||
maxGeminiPro: 8192,
|
||||
defaultGeminiPro: 8192,
|
||||
},
|
||||
temperature: {
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
default: 0.2,
|
||||
},
|
||||
topP: {
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
default: 0.8,
|
||||
},
|
||||
topK: {
|
||||
min: 1,
|
||||
max: 40,
|
||||
step: 0.01,
|
||||
default: 40,
|
||||
},
|
||||
export const openAISettings = {
|
||||
model: {
|
||||
default: 'gpt-3.5-turbo',
|
||||
},
|
||||
temperature: {
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
default: 1,
|
||||
},
|
||||
top_p: {
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
default: 1,
|
||||
},
|
||||
presence_penalty: {
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
default: 0,
|
||||
},
|
||||
frequency_penalty: {
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.01,
|
||||
default: 0,
|
||||
},
|
||||
resendFiles: {
|
||||
default: true,
|
||||
},
|
||||
imageDetail: {
|
||||
default: ImageDetail.auto,
|
||||
},
|
||||
};
|
||||
|
||||
export const googleSettings = {
|
||||
model: {
|
||||
default: 'chat-bison',
|
||||
},
|
||||
maxOutputTokens: {
|
||||
min: 1,
|
||||
max: 2048,
|
||||
step: 1,
|
||||
default: 1024,
|
||||
maxGeminiPro: 8192,
|
||||
defaultGeminiPro: 8192,
|
||||
},
|
||||
temperature: {
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
default: 0.2,
|
||||
},
|
||||
topP: {
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.01,
|
||||
default: 0.8,
|
||||
},
|
||||
topK: {
|
||||
min: 1,
|
||||
max: 40,
|
||||
step: 0.01,
|
||||
default: 40,
|
||||
},
|
||||
};
|
||||
|
||||
export const endpointSettings = {
|
||||
[EModelEndpoint.openAI]: openAISettings,
|
||||
[EModelEndpoint.google]: googleSettings,
|
||||
};
|
||||
|
||||
const google = endpointSettings[EModelEndpoint.google];
|
||||
|
|
@ -86,26 +145,6 @@ export const eModelEndpointSchema = z.nativeEnum(EModelEndpoint);
|
|||
|
||||
export const extendedModelEndpointSchema = z.union([eModelEndpointSchema, z.string()]);
|
||||
|
||||
export enum ImageDetail {
|
||||
low = 'low',
|
||||
auto = 'auto',
|
||||
high = 'high',
|
||||
}
|
||||
|
||||
export const imageDetailNumeric = {
|
||||
[ImageDetail.low]: 0,
|
||||
[ImageDetail.auto]: 1,
|
||||
[ImageDetail.high]: 2,
|
||||
};
|
||||
|
||||
export const imageDetailValue = {
|
||||
0: ImageDetail.low,
|
||||
1: ImageDetail.auto,
|
||||
2: ImageDetail.high,
|
||||
};
|
||||
|
||||
export const eImageDetailSchema = z.nativeEnum(ImageDetail);
|
||||
|
||||
export const tPluginAuthConfigSchema = z.object({
|
||||
authField: z.string(),
|
||||
label: z.string(),
|
||||
|
|
@ -278,12 +317,14 @@ export const tPresetUpdateSchema = tConversationSchema.merge(
|
|||
|
||||
export type TPreset = z.infer<typeof tPresetSchema>;
|
||||
|
||||
export type TSetOption = (
|
||||
param: number | string,
|
||||
) => (newValue: number | string | boolean | Partial<TPreset>) => void;
|
||||
|
||||
export type TConversation = z.infer<typeof tConversationSchema> & {
|
||||
presetOverride?: Partial<TPreset>;
|
||||
};
|
||||
|
||||
// type DefaultSchemaValues = Partial<typeof google>;
|
||||
|
||||
export const openAISchema = tConversationSchema
|
||||
.pick({
|
||||
model: true,
|
||||
|
|
@ -298,26 +339,27 @@ export const openAISchema = tConversationSchema
|
|||
})
|
||||
.transform((obj) => ({
|
||||
...obj,
|
||||
model: obj.model ?? 'gpt-3.5-turbo',
|
||||
model: obj.model ?? openAISettings.model.default,
|
||||
chatGptLabel: obj.chatGptLabel ?? null,
|
||||
promptPrefix: obj.promptPrefix ?? null,
|
||||
temperature: obj.temperature ?? 1,
|
||||
top_p: obj.top_p ?? 1,
|
||||
presence_penalty: obj.presence_penalty ?? 0,
|
||||
frequency_penalty: obj.frequency_penalty ?? 0,
|
||||
resendFiles: typeof obj.resendFiles === 'boolean' ? obj.resendFiles : true,
|
||||
imageDetail: obj.imageDetail ?? ImageDetail.auto,
|
||||
temperature: obj.temperature ?? openAISettings.temperature.default,
|
||||
top_p: obj.top_p ?? openAISettings.top_p.default,
|
||||
presence_penalty: obj.presence_penalty ?? openAISettings.presence_penalty.default,
|
||||
frequency_penalty: obj.frequency_penalty ?? openAISettings.frequency_penalty.default,
|
||||
resendFiles:
|
||||
typeof obj.resendFiles === 'boolean' ? obj.resendFiles : openAISettings.resendFiles.default,
|
||||
imageDetail: obj.imageDetail ?? openAISettings.imageDetail.default,
|
||||
}))
|
||||
.catch(() => ({
|
||||
model: 'gpt-3.5-turbo',
|
||||
model: openAISettings.model.default,
|
||||
chatGptLabel: null,
|
||||
promptPrefix: null,
|
||||
temperature: 1,
|
||||
top_p: 1,
|
||||
presence_penalty: 0,
|
||||
frequency_penalty: 0,
|
||||
resendFiles: true,
|
||||
imageDetail: ImageDetail.auto,
|
||||
temperature: openAISettings.temperature.default,
|
||||
top_p: openAISettings.top_p.default,
|
||||
presence_penalty: openAISettings.presence_penalty.default,
|
||||
frequency_penalty: openAISettings.frequency_penalty.default,
|
||||
resendFiles: openAISettings.resendFiles.default,
|
||||
imageDetail: openAISettings.imageDetail.default,
|
||||
}));
|
||||
|
||||
export const googleSchema = tConversationSchema
|
||||
|
|
@ -674,53 +716,3 @@ export const compactPluginsSchema = tConversationSchema
|
|||
return removeNullishValues(newObj);
|
||||
})
|
||||
.catch(() => ({}));
|
||||
|
||||
// const createGoogleSchema = (customGoogle: DefaultSchemaValues) => {
|
||||
// const defaults = { ...google, ...customGoogle };
|
||||
// return tConversationSchema
|
||||
// .pick({
|
||||
// model: true,
|
||||
// modelLabel: true,
|
||||
// promptPrefix: true,
|
||||
// examples: true,
|
||||
// temperature: true,
|
||||
// maxOutputTokens: true,
|
||||
// topP: true,
|
||||
// topK: true,
|
||||
// })
|
||||
// .transform((obj) => {
|
||||
// const isGeminiPro = obj?.model?.toLowerCase()?.includes('gemini-pro');
|
||||
|
||||
// const maxOutputTokensMax = isGeminiPro
|
||||
// ? defaults.maxOutputTokens.maxGeminiPro
|
||||
// : defaults.maxOutputTokens.max;
|
||||
// const maxOutputTokensDefault = isGeminiPro
|
||||
// ? defaults.maxOutputTokens.defaultGeminiPro
|
||||
// : defaults.maxOutputTokens.default;
|
||||
|
||||
// let maxOutputTokens = obj.maxOutputTokens ?? maxOutputTokensDefault;
|
||||
// maxOutputTokens = Math.min(maxOutputTokens, maxOutputTokensMax);
|
||||
|
||||
// return {
|
||||
// ...obj,
|
||||
// model: obj.model ?? defaults.model.default,
|
||||
// modelLabel: obj.modelLabel ?? null,
|
||||
// promptPrefix: obj.promptPrefix ?? null,
|
||||
// examples: obj.examples ?? [{ input: { content: '' }, output: { content: '' } }],
|
||||
// temperature: obj.temperature ?? defaults.temperature.default,
|
||||
// maxOutputTokens,
|
||||
// topP: obj.topP ?? defaults.topP.default,
|
||||
// topK: obj.topK ?? defaults.topK.default,
|
||||
// };
|
||||
// })
|
||||
// .catch(() => ({
|
||||
// model: defaults.model.default,
|
||||
// modelLabel: null,
|
||||
// promptPrefix: null,
|
||||
// examples: [{ input: { content: '' }, output: { content: '' } }],
|
||||
// temperature: defaults.temperature.default,
|
||||
// maxOutputTokens: defaults.maxOutputTokens.default,
|
||||
// topP: defaults.topP.default,
|
||||
// topK: defaults.topK.default,
|
||||
// }));
|
||||
// };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue