mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 09:50:15 +01:00
🪦 refactor: Remove Legacy Code (#10533)
* 🗑️ chore: Remove unused Legacy Provider clients and related helpers * Deleted OpenAIClient and GoogleClient files along with their associated tests. * Removed references to these clients in the clients index file. * Cleaned up typedefs by removing the OpenAISpecClient export. * Updated chat controllers to use the OpenAI SDK directly instead of the removed client classes. * chore/remove-openapi-specs * 🗑️ chore: Remove unused mergeSort and misc utility functions * Deleted mergeSort.js and misc.js files as they are no longer needed. * Removed references to cleanUpPrimaryKeyValue in messages.js and adjusted related logic. * Updated mongoMeili.ts to eliminate local implementations of removed functions. * chore: remove legacy endpoints * chore: remove all plugins endpoint related code * chore: remove unused prompt handling code and clean up imports * Deleted handleInputs.js and instructions.js files as they are no longer needed. * Removed references to these files in the prompts index.js. * Updated docker-compose.yml to simplify reverse proxy configuration. * chore: remove unused LightningIcon import from Icons.tsx * chore: clean up translation.json by removing deprecated and unused keys * chore: update Jest configuration and remove unused mock file * Simplified the setupFiles array in jest.config.js by removing the fetchEventSource mock. * Deleted the fetchEventSource.js mock file as it is no longer needed. * fix: simplify endpoint type check in Landing and ConversationStarters components * Updated the endpoint type check to use strict equality for better clarity and performance. * Ensured consistency in the handling of the azureOpenAI endpoint across both components. * chore: remove unused dependencies from package.json and package-lock.json * chore: remove legacy EditController, associated routes and imports * chore: update banResponse logic to refine request handling for banned users * chore: remove unused validateEndpoint middleware and its references * chore: remove unused 'res' parameter from initializeClient in multiple endpoint files * chore: remove unused 'isSmallScreen' prop from BookmarkNav and NewChat components; clean up imports in ArchivedChatsTable and useSetIndexOptions hooks; enhance localization in PromptVersions * chore: remove unused import of Constants and TMessage from MobileNav; retain only necessary QueryKeys import * chore: remove unused TResPlugin type and related references; clean up imports in types and schemas
This commit is contained in:
parent
b6dcefc53a
commit
656e1abaea
161 changed files with 256 additions and 10513 deletions
|
|
@ -13,13 +13,7 @@ const ConversationStarters = () => {
|
|||
|
||||
const endpointType = useMemo(() => {
|
||||
let ep = conversation?.endpoint ?? '';
|
||||
if (
|
||||
[
|
||||
EModelEndpoint.chatGPTBrowser,
|
||||
EModelEndpoint.azureOpenAI,
|
||||
EModelEndpoint.gptPlugins,
|
||||
].includes(ep as EModelEndpoint)
|
||||
) {
|
||||
if (ep === EModelEndpoint.azureOpenAI) {
|
||||
ep = EModelEndpoint.openAI;
|
||||
}
|
||||
return getIconEndpoint({
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { EModelEndpoint, SettingsViews } from 'librechat-data-provider';
|
||||
import { Button, MessagesSquared, GPTIcon, AssistantIcon, DataIcon } from '@librechat/client';
|
||||
import { Button, MessagesSquared, AssistantIcon, DataIcon } from '@librechat/client';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils/';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
type TPopoverButton = {
|
||||
|
|
@ -28,14 +28,8 @@ export default function PopoverButtons({
|
|||
endpointType?: EModelEndpoint | string | null;
|
||||
model?: string | null;
|
||||
}) {
|
||||
const {
|
||||
conversation,
|
||||
optionSettings,
|
||||
setOptionSettings,
|
||||
showAgentSettings,
|
||||
setShowAgentSettings,
|
||||
} = useChatContext();
|
||||
const localize = useLocalize();
|
||||
const { conversation, optionSettings, setOptionSettings } = useChatContext();
|
||||
const [settingsView, setSettingsView] = useRecoilState(store.currentSettingsView);
|
||||
|
||||
const { model: _model, endpoint: _endpoint, endpointType } = conversation ?? {};
|
||||
|
|
@ -64,19 +58,6 @@ export default function PopoverButtons({
|
|||
icon: <MessagesSquared className={cn('mr-1 w-[14px]', iconClass)} />,
|
||||
},
|
||||
],
|
||||
[EModelEndpoint.gptPlugins]: [
|
||||
{
|
||||
label: localize(
|
||||
showAgentSettings ? 'com_show_completion_settings' : 'com_show_agent_settings',
|
||||
),
|
||||
buttonClass: '',
|
||||
handler: () => {
|
||||
setSettingsView(SettingsViews.default);
|
||||
setShowAgentSettings((prev) => !prev);
|
||||
},
|
||||
icon: <GPTIcon className={cn('mr-1 w-[14px]', iconClass)} size={24} />,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (!endpoint) {
|
||||
|
|
|
|||
|
|
@ -43,13 +43,7 @@ export default function Landing({ centerFormOnLanding }: { centerFormOnLanding:
|
|||
|
||||
const endpointType = useMemo(() => {
|
||||
let ep = conversation?.endpoint ?? '';
|
||||
if (
|
||||
[
|
||||
EModelEndpoint.chatGPTBrowser,
|
||||
EModelEndpoint.azureOpenAI,
|
||||
EModelEndpoint.gptPlugins,
|
||||
].includes(ep as EModelEndpoint)
|
||||
) {
|
||||
if (ep === EModelEndpoint.azureOpenAI) {
|
||||
ep = EModelEndpoint.openAI;
|
||||
}
|
||||
return getIconEndpoint({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useMemo } from 'react';
|
||||
import { SettingsIcon } from 'lucide-react';
|
||||
import { TooltipAnchor, Spinner } from '@librechat/client';
|
||||
import { Spinner } from '@librechat/client';
|
||||
import { EModelEndpoint, isAgentsEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { TModelSpec } from 'librechat-data-provider';
|
||||
import type { Endpoint } from '~/common';
|
||||
|
|
@ -82,7 +82,10 @@ export function EndpointItem({ endpoint }: EndpointItemProps) {
|
|||
}, [modelSpecs, endpoint.value]);
|
||||
|
||||
const searchValue = endpointSearchValues[endpoint.value] || '';
|
||||
const isUserProvided = useMemo(() => endpointRequiresUserKey(endpoint.value), [endpoint.value]);
|
||||
const isUserProvided = useMemo(
|
||||
() => endpointRequiresUserKey(endpoint.value),
|
||||
[endpointRequiresUserKey, endpoint.value],
|
||||
);
|
||||
|
||||
const renderIconLabel = () => (
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -99,18 +102,6 @@ export function EndpointItem({ endpoint }: EndpointItemProps) {
|
|||
>
|
||||
{endpoint.label}
|
||||
</span>
|
||||
{/* TODO: remove this after deprecation */}
|
||||
{endpoint.value === 'gptPlugins' && (
|
||||
<TooltipAnchor
|
||||
description={localize('com_endpoint_deprecated_info')}
|
||||
aria-label={localize('com_endpoint_deprecated_info_a11y')}
|
||||
render={
|
||||
<span className="ml-2 rounded bg-amber-600/70 px-2 py-0.5 text-xs font-semibold text-white">
|
||||
{localize('com_endpoint_deprecated')}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const EditPresetDialog = ({
|
|||
const localize = useLocalize();
|
||||
const queryClient = useQueryClient();
|
||||
const { preset, setPreset } = useChatContext();
|
||||
const { setOption, setOptions, setAgentOption } = useSetIndexOptions(preset);
|
||||
const { setOption, setOptions } = useSetIndexOptions(preset);
|
||||
const [onTitleChange, title] = useDebouncedInput({
|
||||
setOption,
|
||||
optionKey: 'title',
|
||||
|
|
@ -87,20 +87,7 @@ const EditPresetDialog = ({
|
|||
console.log('setting model', models[0]);
|
||||
setOption('model')(models[0]);
|
||||
}
|
||||
|
||||
if (preset.agentOptions?.model === models[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
preset.agentOptions?.model != null &&
|
||||
preset.agentOptions.model &&
|
||||
!models.includes(preset.agentOptions.model)
|
||||
) {
|
||||
console.log('setting agent model', models[0]);
|
||||
setAgentOption('model')(models[0]);
|
||||
}
|
||||
}, [preset, queryClient, setOption, setAgentOption]);
|
||||
}, [preset, queryClient, setOption]);
|
||||
|
||||
const switchEndpoint = useCallback(
|
||||
(newEndpoint: string) => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import PlaceholderRow from '~/components/Chat/Messages/ui/PlaceholderRow';
|
|||
import SiblingSwitch from '~/components/Chat/Messages/SiblingSwitch';
|
||||
import HoverButtons from '~/components/Chat/Messages/HoverButtons';
|
||||
import MessageIcon from '~/components/Chat/Messages/MessageIcon';
|
||||
import { Plugin } from '~/components/Messages/Content';
|
||||
import SubRow from '~/components/Chat/Messages/SubRow';
|
||||
import { fontSizeAtom } from '~/store/fontSize';
|
||||
import { MessageContext } from '~/Providers';
|
||||
|
|
@ -178,7 +177,6 @@ const MessageRender = memo(
|
|||
isLatestMessage,
|
||||
}}
|
||||
>
|
||||
{msg.plugin && <Plugin plugin={msg.plugin} />}
|
||||
<MessageContent
|
||||
ask={ask}
|
||||
edit={edit}
|
||||
|
|
|
|||
|
|
@ -57,16 +57,7 @@ function getGoogleModelName(model: string | null | undefined) {
|
|||
}
|
||||
|
||||
const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||
const {
|
||||
error,
|
||||
button,
|
||||
iconURL = '',
|
||||
endpoint,
|
||||
size = 30,
|
||||
model = '',
|
||||
assistantName,
|
||||
agentName,
|
||||
} = props;
|
||||
const { error, iconURL = '', endpoint, size = 30, model = '', assistantName, agentName } = props;
|
||||
|
||||
const assistantsIcon = {
|
||||
icon: iconURL ? (
|
||||
|
|
@ -142,11 +133,6 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
|||
bg: getOpenAIColor(model),
|
||||
name: 'ChatGPT',
|
||||
},
|
||||
[EModelEndpoint.gptPlugins]: {
|
||||
icon: <Plugin size={size * 0.7} />,
|
||||
bg: `rgba(69, 89, 164, ${button === true ? 0.75 : 1})`,
|
||||
name: 'Plugins',
|
||||
},
|
||||
[EModelEndpoint.google]: {
|
||||
icon: getGoogleIcon(model, size),
|
||||
name: getGoogleModelName(model),
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
import { Feather } from 'lucide-react';
|
||||
import { EModelEndpoint, alternateName } from 'librechat-data-provider';
|
||||
import {
|
||||
Sparkles,
|
||||
BedrockIcon,
|
||||
AnthropicIcon,
|
||||
AzureMinimalIcon,
|
||||
OpenAIMinimalIcon,
|
||||
LightningIcon,
|
||||
MinimalPlugin,
|
||||
GoogleMinimalIcon,
|
||||
CustomMinimalIcon,
|
||||
AnthropicIcon,
|
||||
BedrockIcon,
|
||||
Sparkles,
|
||||
} from '@librechat/client';
|
||||
import UnknownIcon from '~/hooks/Endpoint/UnknownIcon';
|
||||
import { IconProps } from '~/common';
|
||||
|
|
@ -33,7 +31,6 @@ const MinimalIcon: React.FC<IconProps> = (props) => {
|
|||
icon: <OpenAIMinimalIcon className={iconClassName} />,
|
||||
name: props.chatGptLabel ?? 'ChatGPT',
|
||||
},
|
||||
[EModelEndpoint.gptPlugins]: { icon: <MinimalPlugin />, name: 'Plugins' },
|
||||
[EModelEndpoint.google]: { icon: <GoogleMinimalIcon />, name: props.modelLabel ?? 'Google' },
|
||||
[EModelEndpoint.anthropic]: {
|
||||
icon: <AnthropicIcon className="icon-md shrink-0 dark:text-white" />,
|
||||
|
|
@ -43,7 +40,6 @@ const MinimalIcon: React.FC<IconProps> = (props) => {
|
|||
icon: <CustomMinimalIcon />,
|
||||
name: 'Custom',
|
||||
},
|
||||
[EModelEndpoint.chatGPTBrowser]: { icon: <LightningIcon />, name: 'ChatGPT' },
|
||||
[EModelEndpoint.assistants]: { icon: <Sparkles className="icon-sm" />, name: 'Assistant' },
|
||||
[EModelEndpoint.azureAssistants]: { icon: <Sparkles className="icon-sm" />, name: 'Assistant' },
|
||||
[EModelEndpoint.agents]: {
|
||||
|
|
|
|||
|
|
@ -1,248 +0,0 @@
|
|||
import {
|
||||
Switch,
|
||||
Label,
|
||||
Slider,
|
||||
HoverCard,
|
||||
InputNumber,
|
||||
SelectDropDown,
|
||||
HoverCardTrigger,
|
||||
} from '@librechat/client';
|
||||
import type { TModelSelectProps } from '~/common';
|
||||
import { cn, optionText, defaultTextProps, removeFocusRings } from '~/utils';
|
||||
import OptionHover from './OptionHover';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { ESide } from '~/common';
|
||||
|
||||
export default function Settings({ conversation, setOption, models, readonly }: TModelSelectProps) {
|
||||
const localize = useLocalize();
|
||||
if (!conversation) {
|
||||
return null;
|
||||
}
|
||||
const { agent, skipCompletion, model, temperature } = conversation.agentOptions ?? {};
|
||||
|
||||
const setModel = setOption('model');
|
||||
const setTemperature = setOption('temperature');
|
||||
const setAgent = setOption('agent');
|
||||
const setSkipCompletion = setOption('skipCompletion');
|
||||
const onCheckedChangeAgent = (checked: boolean) => {
|
||||
setAgent(checked ? 'functions' : 'classic');
|
||||
};
|
||||
|
||||
const onCheckedChangeSkip = (checked: boolean) => {
|
||||
setSkipCompletion(checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-5 gap-6">
|
||||
<div className="col-span-5 flex flex-col items-center justify-start gap-6 sm:col-span-3">
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<SelectDropDown
|
||||
title={localize('com_endpoint_agent_model')}
|
||||
value={model ?? ''}
|
||||
setValue={setModel}
|
||||
availableValues={models}
|
||||
disabled={readonly}
|
||||
className={cn(defaultTextProps, 'flex w-full resize-none', removeFocusRings)}
|
||||
containerClassName="flex w-full resize-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
|
||||
{localize('com_endpoint_temperature')}{' '}
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default_with_num', { 0: '0' })})
|
||||
</small>
|
||||
</Label>
|
||||
<InputNumber
|
||||
id="temp-int"
|
||||
disabled={readonly}
|
||||
value={temperature}
|
||||
onChange={(value) => setTemperature(Number(value))}
|
||||
max={2}
|
||||
min={0}
|
||||
step={0.01}
|
||||
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',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[temperature ?? 0]}
|
||||
onValueChange={(value: number[]) => setTemperature(value[0])}
|
||||
onDoubleClick={() => setTemperature(1)}
|
||||
max={2}
|
||||
min={0}
|
||||
step={0.01}
|
||||
className="flex h-4 w-full"
|
||||
aria-labelledby="temp-int"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<OptionHover endpoint={conversation.endpoint ?? ''} type="temp" side={ESide.Left} />
|
||||
</HoverCard>
|
||||
<div className="grid w-full grid-cols-2 items-center gap-10">
|
||||
<HoverCard openDelay={500}>
|
||||
<HoverCardTrigger className="flex w-[100px] flex-col items-center space-y-4 text-center">
|
||||
<label
|
||||
htmlFor="functions-agent"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
|
||||
>
|
||||
<small>{localize('com_endpoint_plug_use_functions')}</small>
|
||||
</label>
|
||||
<Switch
|
||||
id="functions-agent"
|
||||
checked={agent === 'functions'}
|
||||
onCheckedChange={onCheckedChangeAgent}
|
||||
disabled={readonly}
|
||||
className="ml-4 mt-2"
|
||||
aria-label={localize('com_endpoint_plug_use_functions')}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<OptionHover endpoint={conversation.endpoint ?? ''} type="func" side={ESide.Bottom} />
|
||||
</HoverCard>
|
||||
<HoverCard openDelay={500}>
|
||||
<HoverCardTrigger className="ml-[-60px] flex w-[100px] flex-col items-center space-y-4 text-center">
|
||||
<label
|
||||
htmlFor="skip-completion"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
|
||||
>
|
||||
<small>{localize('com_endpoint_plug_skip_completion')}</small>
|
||||
</label>
|
||||
<Switch
|
||||
id="skip-completion"
|
||||
checked={skipCompletion === true}
|
||||
onCheckedChange={onCheckedChangeSkip}
|
||||
disabled={readonly}
|
||||
className="ml-4 mt-2"
|
||||
aria-label={localize('com_endpoint_plug_skip_completion')}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<OptionHover endpoint={conversation.endpoint ?? ''} type="skip" side={ESide.Bottom} />
|
||||
</HoverCard>
|
||||
</div>
|
||||
{/* <HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
|
||||
Top P <small className="opacity-40">(default: 1)</small>
|
||||
</Label>
|
||||
<InputNumber
|
||||
id="top-p-int"
|
||||
disabled={readonly}
|
||||
value={topP}
|
||||
onChange={(value) => setTopP(value)}
|
||||
max={1}
|
||||
min={0}
|
||||
step={0.01}
|
||||
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'
|
||||
)
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[topP]}
|
||||
onValueChange={(value) => setTopP(value[0])}
|
||||
doubleClickHandler={() => setTopP(1)}
|
||||
max={1}
|
||||
min={0}
|
||||
step={0.01}
|
||||
className="flex h-4 w-full"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<OptionHover type="topp" side="left" />
|
||||
</HoverCard>
|
||||
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="freq-penalty-int" className="text-left text-sm font-medium">
|
||||
Frequency Penalty <small className="opacity-40">(default: 0)</small>
|
||||
</Label>
|
||||
<InputNumber
|
||||
id="freq-penalty-int"
|
||||
disabled={readonly}
|
||||
value={freqP}
|
||||
onChange={(value) => setFreqP(value)}
|
||||
max={2}
|
||||
min={-2}
|
||||
step={0.01}
|
||||
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'
|
||||
)
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[freqP]}
|
||||
onValueChange={(value) => setFreqP(value[0])}
|
||||
doubleClickHandler={() => setFreqP(0)}
|
||||
max={2}
|
||||
min={-2}
|
||||
step={0.01}
|
||||
className="flex h-4 w-full"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<OptionHover type="freq" side="left" />
|
||||
</HoverCard>
|
||||
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="pres-penalty-int" className="text-left text-sm font-medium">
|
||||
Presence Penalty <small className="opacity-40">(default: 0)</small>
|
||||
</Label>
|
||||
<InputNumber
|
||||
id="pres-penalty-int"
|
||||
disabled={readonly}
|
||||
value={presP}
|
||||
onChange={(value) => setPresP(value)}
|
||||
max={2}
|
||||
min={-2}
|
||||
step={0.01}
|
||||
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'
|
||||
)
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[presP]}
|
||||
onValueChange={(value) => setPresP(value[0])}
|
||||
doubleClickHandler={() => setPresP(0)}
|
||||
max={2}
|
||||
min={-2}
|
||||
step={0.01}
|
||||
className="flex h-4 w-full"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<OptionHover type="pres" side="left" />
|
||||
</HoverCard> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import Settings from '../Plugins';
|
||||
import AgentSettings from '../AgentSettings';
|
||||
import { useSetIndexOptions } from '~/hooks';
|
||||
import { useChatContext } from '~/Providers';
|
||||
|
||||
export default function PluginsView({ conversation, models, isPreset = false }) {
|
||||
const { showAgentSettings } = useChatContext();
|
||||
const { setOption, setTools, setAgentOption, checkPluginSelection } = useSetIndexOptions(
|
||||
isPreset ? conversation : null,
|
||||
);
|
||||
if (!conversation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return showAgentSettings ? (
|
||||
<AgentSettings conversation={conversation} setOption={setAgentOption} models={models} />
|
||||
) : (
|
||||
<Settings
|
||||
conversation={conversation}
|
||||
setOption={setOption}
|
||||
setTools={setTools}
|
||||
checkPluginSelection={checkPluginSelection}
|
||||
models={models}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,2 +1 @@
|
|||
export { default as GoogleSettings } from './GoogleSettings';
|
||||
export { default as PluginSettings } from './PluginSettings';
|
||||
|
|
|
|||
|
|
@ -36,11 +36,6 @@ const types = {
|
|||
},
|
||||
openAI,
|
||||
azureOpenAI: openAI,
|
||||
gptPlugins: {
|
||||
func: 'com_endpoint_func_hover',
|
||||
skip: 'com_endpoint_skip_hover',
|
||||
...openAI,
|
||||
},
|
||||
};
|
||||
|
||||
function OptionHover({ endpoint, type, side }: TOptionHoverProps) {
|
||||
|
|
|
|||
|
|
@ -1,392 +0,0 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
Input,
|
||||
Label,
|
||||
Slider,
|
||||
HoverCard,
|
||||
InputNumber,
|
||||
SelectDropDown,
|
||||
HoverCardTrigger,
|
||||
} from '@librechat/client';
|
||||
import type { TModelSelectProps, OnInputNumberChange } from '~/common';
|
||||
import type { TPlugin } from 'librechat-data-provider';
|
||||
import {
|
||||
removeFocusOutlines,
|
||||
defaultTextProps,
|
||||
removeFocusRings,
|
||||
processPlugins,
|
||||
selectPlugins,
|
||||
optionText,
|
||||
cn,
|
||||
} from '~/utils';
|
||||
import OptionHoverAlt from '~/components/SidePanel/Parameters/OptionHover';
|
||||
import MultiSelectDropDown from '~/components/Input/ModelSelect/MultiSelectDropDown';
|
||||
import { useLocalize, useDebouncedInput } from '~/hooks';
|
||||
import OptionHover from './OptionHover';
|
||||
import { ESide } from '~/common';
|
||||
import store from '~/store';
|
||||
|
||||
export default function Settings({
|
||||
conversation,
|
||||
setOption,
|
||||
setTools,
|
||||
checkPluginSelection,
|
||||
models,
|
||||
readonly,
|
||||
}: TModelSelectProps & {
|
||||
setTools: (newValue: string, remove?: boolean | undefined) => void;
|
||||
checkPluginSelection: (value: string) => boolean;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const availableTools = useRecoilValue(store.availableTools);
|
||||
const { data: allPlugins } = useAvailablePluginsQuery({
|
||||
select: selectPlugins,
|
||||
});
|
||||
|
||||
const conversationTools: TPlugin[] = useMemo(() => {
|
||||
if (!conversation?.tools) {
|
||||
return [];
|
||||
}
|
||||
return processPlugins(conversation.tools, allPlugins?.map);
|
||||
}, [conversation, allPlugins]);
|
||||
|
||||
const availablePlugins = useMemo(() => {
|
||||
if (!availableTools) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.values(availableTools);
|
||||
}, [availableTools]);
|
||||
|
||||
const {
|
||||
model,
|
||||
modelLabel,
|
||||
chatGptLabel,
|
||||
promptPrefix,
|
||||
temperature,
|
||||
top_p: topP,
|
||||
frequency_penalty: freqP,
|
||||
presence_penalty: presP,
|
||||
maxContextTokens,
|
||||
} = conversation ?? {};
|
||||
|
||||
const [setChatGptLabel, chatGptLabelValue] = useDebouncedInput<string | null | undefined>({
|
||||
setOption,
|
||||
optionKey: 'chatGptLabel',
|
||||
initialValue: modelLabel ?? chatGptLabel,
|
||||
});
|
||||
const [setPromptPrefix, promptPrefixValue] = useDebouncedInput<string | null | undefined>({
|
||||
setOption,
|
||||
optionKey: 'promptPrefix',
|
||||
initialValue: promptPrefix,
|
||||
});
|
||||
const [setTemperature, temperatureValue] = useDebouncedInput<number | null | undefined>({
|
||||
setOption,
|
||||
optionKey: 'temperature',
|
||||
initialValue: temperature,
|
||||
});
|
||||
const [setTopP, topPValue] = useDebouncedInput<number | null | undefined>({
|
||||
setOption,
|
||||
optionKey: 'top_p',
|
||||
initialValue: topP,
|
||||
});
|
||||
const [setFreqP, freqPValue] = useDebouncedInput<number | null | undefined>({
|
||||
setOption,
|
||||
optionKey: 'frequency_penalty',
|
||||
initialValue: freqP,
|
||||
});
|
||||
const [setPresP, presPValue] = useDebouncedInput<number | null | undefined>({
|
||||
setOption,
|
||||
optionKey: 'presence_penalty',
|
||||
initialValue: presP,
|
||||
});
|
||||
const [setMaxContextTokens, maxContextTokensValue] = useDebouncedInput<number | null | undefined>(
|
||||
{
|
||||
setOption,
|
||||
optionKey: 'maxContextTokens',
|
||||
initialValue: maxContextTokens,
|
||||
},
|
||||
);
|
||||
|
||||
const setModel = setOption('model');
|
||||
|
||||
if (!conversation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-5 gap-6">
|
||||
<div className="col-span-5 flex flex-col items-center justify-start gap-6 sm:col-span-3">
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<SelectDropDown
|
||||
title={localize('com_endpoint_completion_model')}
|
||||
value={model ?? ''}
|
||||
setValue={setModel}
|
||||
availableValues={models}
|
||||
disabled={readonly}
|
||||
className={cn(defaultTextProps, 'flex w-full resize-none', removeFocusRings)}
|
||||
containerClassName="flex w-full resize-none"
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="chatGptLabel" className="text-left text-sm font-medium">
|
||||
{localize('com_endpoint_custom_name')}{' '}
|
||||
<small className="opacity-40">{localize('com_endpoint_default_empty')}</small>
|
||||
</Label>
|
||||
<Input
|
||||
id="chatGptLabel"
|
||||
disabled={readonly}
|
||||
value={chatGptLabelValue || ''}
|
||||
onChange={(e) => setChatGptLabel(e.target.value ?? null)}
|
||||
placeholder={localize('com_endpoint_openai_custom_name_placeholder')}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
'flex h-10 max-h-10 w-full resize-none px-3 py-2',
|
||||
removeFocusOutlines,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="promptPrefix" className="text-left text-sm font-medium">
|
||||
{localize('com_endpoint_prompt_prefix')}{' '}
|
||||
<small className="opacity-40">{localize('com_endpoint_default_empty')}</small>
|
||||
</Label>
|
||||
<TextareaAutosize
|
||||
id="promptPrefix"
|
||||
disabled={readonly}
|
||||
value={promptPrefixValue || ''}
|
||||
onChange={(e) => setPromptPrefix(e.target.value ?? null)}
|
||||
placeholder={localize(
|
||||
'com_endpoint_plug_set_custom_instructions_for_gpt_placeholder',
|
||||
)}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
'flex max-h-[138px] min-h-[100px] w-full resize-none px-3 py-2',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
<div className="col-span-5 flex flex-col items-center justify-start gap-6 px-3 sm:col-span-2">
|
||||
<MultiSelectDropDown
|
||||
showAbove={false}
|
||||
showLabel={false}
|
||||
setSelected={setTools}
|
||||
value={conversationTools}
|
||||
optionValueKey="pluginKey"
|
||||
availableValues={availablePlugins}
|
||||
isSelected={checkPluginSelection}
|
||||
searchPlaceholder={localize('com_ui_select_search_plugin')}
|
||||
className={cn(defaultTextProps, 'flex w-full resize-none', removeFocusOutlines)}
|
||||
optionsClassName="w-full max-h-[275px] dark:bg-gray-700 z-10 border dark:border-gray-600"
|
||||
containerClassName="flex w-full resize-none border border-transparent"
|
||||
labelClassName="dark:text-white"
|
||||
/>
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="mt-1 flex w-full justify-between">
|
||||
<Label htmlFor="max-context-tokens" className="text-left text-sm font-medium">
|
||||
{localize('com_endpoint_context_tokens')}{' '}
|
||||
</Label>
|
||||
<InputNumber
|
||||
id="max-context-tokens"
|
||||
stringMode={false}
|
||||
disabled={readonly}
|
||||
value={maxContextTokensValue as number}
|
||||
onChange={setMaxContextTokens as OnInputNumberChange}
|
||||
placeholder={localize('com_nav_theme_system')}
|
||||
min={10}
|
||||
max={2000000}
|
||||
step={1000}
|
||||
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',
|
||||
'w-1/3',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<OptionHoverAlt
|
||||
description="com_endpoint_context_info"
|
||||
langCode={true}
|
||||
side={ESide.Left}
|
||||
/>
|
||||
</HoverCard>
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="temp-int" className="text-left text-sm font-medium">
|
||||
{localize('com_endpoint_temperature')}{' '}
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default_with_num', { 0: '0.8' })})
|
||||
</small>
|
||||
</Label>
|
||||
<InputNumber
|
||||
id="temp-int"
|
||||
disabled={readonly}
|
||||
value={temperatureValue}
|
||||
onChange={(value) => setTemperature(Number(value))}
|
||||
max={2}
|
||||
min={0}
|
||||
step={0.01}
|
||||
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',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[temperatureValue ?? 0.8]}
|
||||
onValueChange={(value) => setTemperature(value[0])}
|
||||
onDoubleClick={() => setTemperature(0.8)}
|
||||
max={2}
|
||||
min={0}
|
||||
step={0.01}
|
||||
className="flex h-4 w-full"
|
||||
aria-labelledby="temp-int"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<OptionHover endpoint={conversation.endpoint ?? ''} type="temp" side={ESide.Left} />
|
||||
</HoverCard>
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="top-p-int" className="text-left text-sm font-medium">
|
||||
{localize('com_endpoint_top_p')}{' '}
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default_with_num', { 0: '1' })})
|
||||
</small>
|
||||
</Label>
|
||||
<InputNumber
|
||||
id="top-p-int"
|
||||
disabled={readonly}
|
||||
value={topPValue}
|
||||
onChange={(value) => setTopP(Number(value))}
|
||||
max={1}
|
||||
min={0}
|
||||
step={0.01}
|
||||
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',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[topPValue ?? 1]}
|
||||
onValueChange={(value) => setTopP(value[0])}
|
||||
onDoubleClick={() => setTopP(1)}
|
||||
max={1}
|
||||
min={0}
|
||||
step={0.01}
|
||||
className="flex h-4 w-full"
|
||||
aria-labelledby="top-p-int"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<OptionHover endpoint={conversation.endpoint ?? ''} type="topp" side={ESide.Left} />
|
||||
</HoverCard>
|
||||
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="freq-penalty-int" className="text-left text-sm font-medium">
|
||||
{localize('com_endpoint_frequency_penalty')}{' '}
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default_with_num', { 0: '0' })})
|
||||
</small>
|
||||
</Label>
|
||||
<InputNumber
|
||||
id="freq-penalty-int"
|
||||
disabled={readonly}
|
||||
value={freqPValue}
|
||||
onChange={(value) => setFreqP(Number(value))}
|
||||
max={2}
|
||||
min={-2}
|
||||
step={0.01}
|
||||
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',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[freqPValue ?? 0]}
|
||||
onValueChange={(value) => setFreqP(value[0])}
|
||||
onDoubleClick={() => setFreqP(0)}
|
||||
max={2}
|
||||
min={-2}
|
||||
step={0.01}
|
||||
className="flex h-4 w-full"
|
||||
aria-labelledby="freq-penalty-int"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<OptionHover endpoint={conversation.endpoint ?? ''} type="freq" side={ESide.Left} />
|
||||
</HoverCard>
|
||||
|
||||
<HoverCard openDelay={300}>
|
||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||
<div className="flex justify-between">
|
||||
<Label htmlFor="pres-penalty-int" className="text-left text-sm font-medium">
|
||||
{localize('com_endpoint_presence_penalty')}{' '}
|
||||
<small className="opacity-40">
|
||||
({localize('com_endpoint_default_with_num', { 0: '0' })})
|
||||
</small>
|
||||
</Label>
|
||||
<InputNumber
|
||||
id="pres-penalty-int"
|
||||
disabled={readonly}
|
||||
value={presPValue}
|
||||
onChange={(value) => setPresP(Number(value))}
|
||||
max={2}
|
||||
min={-2}
|
||||
step={0.01}
|
||||
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',
|
||||
),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Slider
|
||||
disabled={readonly}
|
||||
value={[presPValue ?? 0]}
|
||||
onValueChange={(value) => setPresP(value[0])}
|
||||
onDoubleClick={() => setPresP(0)}
|
||||
max={2}
|
||||
min={-2}
|
||||
step={0.01}
|
||||
className="flex h-4 w-full"
|
||||
aria-labelledby="pres-penalty-int"
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<OptionHover endpoint={conversation.endpoint ?? ''} type="pres" side={ESide.Left} />
|
||||
</HoverCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,8 +3,6 @@ export { default as AssistantsSettings } from './Assistants';
|
|||
export { default as BedrockSettings } from './Bedrock';
|
||||
export { default as OpenAISettings } from './OpenAI';
|
||||
export { default as GoogleSettings } from './Google';
|
||||
export { default as PluginsSettings } from './Plugins';
|
||||
export { default as Examples } from './Examples';
|
||||
export { default as AgentSettings } from './AgentSettings';
|
||||
export { default as AnthropicSettings } from './Anthropic';
|
||||
export * from './settings';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { FC } from 'react';
|
||||
import type { TModelSelectProps } from '~/common';
|
||||
import { GoogleSettings, PluginSettings } from './MultiView';
|
||||
import AssistantsSettings from './Assistants';
|
||||
import { GoogleSettings } from './MultiView';
|
||||
import AnthropicSettings from './Anthropic';
|
||||
import BedrockSettings from './Bedrock';
|
||||
import OpenAISettings from './OpenAI';
|
||||
|
|
@ -23,7 +23,6 @@ export const getSettings = () => {
|
|||
settings,
|
||||
multiViewSettings: {
|
||||
[EModelEndpoint.google]: GoogleSettings,
|
||||
[EModelEndpoint.gptPlugins]: PluginSettings,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,110 +0,0 @@
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import { ChevronDownIcon } from 'lucide-react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
Button,
|
||||
SelectDropDown,
|
||||
SelectDropDownPop,
|
||||
MultiSelectDropDown,
|
||||
useMediaQuery,
|
||||
} from '@librechat/client';
|
||||
import type { TPlugin } from 'librechat-data-provider';
|
||||
import type { TModelSelectProps } from '~/common';
|
||||
import { useSetIndexOptions, useAuthContext, useLocalize } from '~/hooks';
|
||||
import { cn, cardStyle, selectPlugins, processPlugins } from '~/utils';
|
||||
import MultiSelectPop from './MultiSelectPop';
|
||||
import store from '~/store';
|
||||
|
||||
export default function PluginsByIndex({
|
||||
conversation,
|
||||
setOption,
|
||||
models,
|
||||
showAbove,
|
||||
popover = false,
|
||||
}: TModelSelectProps) {
|
||||
const localize = useLocalize();
|
||||
const { user } = useAuthContext();
|
||||
const [visible, setVisibility] = useState<boolean>(true);
|
||||
const isSmallScreen = useMediaQuery('(max-width: 640px)');
|
||||
const availableTools = useRecoilValue(store.availableTools);
|
||||
const { checkPluginSelection, setTools } = useSetIndexOptions();
|
||||
|
||||
const { data: allPlugins } = useAvailablePluginsQuery({
|
||||
enabled: !!user?.plugins,
|
||||
select: selectPlugins,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isSmallScreen) {
|
||||
setVisibility(false);
|
||||
}
|
||||
}, [isSmallScreen]);
|
||||
|
||||
const conversationTools: TPlugin[] = useMemo(() => {
|
||||
if (!conversation?.tools) {
|
||||
return [];
|
||||
}
|
||||
return processPlugins(conversation.tools, allPlugins?.map);
|
||||
}, [conversation, allPlugins]);
|
||||
|
||||
const availablePlugins = useMemo(() => {
|
||||
if (!availableTools) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.values(availableTools);
|
||||
}, [availableTools]);
|
||||
|
||||
if (!conversation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Menu = popover ? SelectDropDownPop : SelectDropDown;
|
||||
const PluginsMenu = popover ? MultiSelectPop : MultiSelectDropDown;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'z-40 flex h-[40px] min-w-4 flex-none items-center justify-center px-3 hover:bg-white focus:ring-0 focus:ring-offset-0 dark:hover:bg-gray-700',
|
||||
)}
|
||||
onClick={() => setVisibility((prev) => !prev)}
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
!visible ? '' : 'rotate-180 transform',
|
||||
'w-4 text-gray-600 dark:text-white',
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
{visible && (
|
||||
<>
|
||||
<Menu
|
||||
value={conversation.model ?? ''}
|
||||
setValue={setOption('model')}
|
||||
availableValues={models}
|
||||
showAbove={showAbove}
|
||||
showLabel={false}
|
||||
className={cn(
|
||||
cardStyle,
|
||||
'z-50 flex h-[40px] w-48 min-w-48 flex-none items-center justify-center px-4 hover:cursor-pointer',
|
||||
)}
|
||||
/>
|
||||
<PluginsMenu
|
||||
showAbove={false}
|
||||
showLabel={false}
|
||||
setSelected={setTools}
|
||||
value={conversationTools}
|
||||
optionValueKey="pluginKey"
|
||||
availableValues={availablePlugins}
|
||||
isSelected={checkPluginSelection}
|
||||
searchPlaceholder={localize('com_ui_select_search_plugin')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,9 +4,7 @@ import type { FC } from 'react';
|
|||
|
||||
import OpenAI from './OpenAI';
|
||||
import Google from './Google';
|
||||
import ChatGPT from './ChatGPT';
|
||||
import Anthropic from './Anthropic';
|
||||
import PluginsByIndex from './PluginsByIndex';
|
||||
|
||||
export const options: { [key: string]: FC<TModelSelectProps> } = {
|
||||
[EModelEndpoint.openAI]: OpenAI,
|
||||
|
|
@ -15,10 +13,8 @@ export const options: { [key: string]: FC<TModelSelectProps> } = {
|
|||
[EModelEndpoint.azureOpenAI]: OpenAI,
|
||||
[EModelEndpoint.google]: Google,
|
||||
[EModelEndpoint.anthropic]: Anthropic,
|
||||
[EModelEndpoint.chatGPTBrowser]: ChatGPT,
|
||||
};
|
||||
|
||||
export const multiChatOptions = {
|
||||
...options,
|
||||
[EModelEndpoint.gptPlugins]: PluginsByIndex,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,25 +1,24 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import {
|
||||
OGDialog,
|
||||
OGDialogContent,
|
||||
OGDialogHeader,
|
||||
OGDialogTitle,
|
||||
OGDialogFooter,
|
||||
Dropdown,
|
||||
useToastContext,
|
||||
Button,
|
||||
Label,
|
||||
OGDialogTrigger,
|
||||
Spinner,
|
||||
} from '@librechat/client';
|
||||
import { EModelEndpoint, alternateName, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import {
|
||||
useRevokeAllUserKeysMutation,
|
||||
useRevokeUserKeyMutation,
|
||||
useRevokeAllUserKeysMutation,
|
||||
} from 'librechat-data-provider/react-query';
|
||||
import {
|
||||
Label,
|
||||
Button,
|
||||
Spinner,
|
||||
OGDialog,
|
||||
Dropdown,
|
||||
OGDialogTitle,
|
||||
OGDialogHeader,
|
||||
OGDialogFooter,
|
||||
OGDialogContent,
|
||||
useToastContext,
|
||||
OGDialogTrigger,
|
||||
} from '@librechat/client';
|
||||
import type { TDialogProps } from '~/common';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
import { useUserKey, useLocalize } from '~/hooks';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import CustomConfig from './CustomEndpoint';
|
||||
|
|
@ -34,7 +33,6 @@ const endpointComponents = {
|
|||
[EModelEndpoint.openAI]: OpenAIConfig,
|
||||
[EModelEndpoint.custom]: CustomConfig,
|
||||
[EModelEndpoint.azureOpenAI]: OpenAIConfig,
|
||||
[EModelEndpoint.gptPlugins]: OpenAIConfig,
|
||||
[EModelEndpoint.assistants]: OpenAIConfig,
|
||||
[EModelEndpoint.azureAssistants]: OpenAIConfig,
|
||||
default: OtherConfig,
|
||||
|
|
@ -44,7 +42,6 @@ const formSet: Set<string> = new Set([
|
|||
EModelEndpoint.openAI,
|
||||
EModelEndpoint.custom,
|
||||
EModelEndpoint.azureOpenAI,
|
||||
EModelEndpoint.gptPlugins,
|
||||
EModelEndpoint.assistants,
|
||||
EModelEndpoint.azureAssistants,
|
||||
]);
|
||||
|
|
@ -174,7 +171,6 @@ const SetKeyDialog = ({
|
|||
});
|
||||
|
||||
const [userKey, setUserKey] = useState('');
|
||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||
const [expiresAtLabel, setExpiresAtLabel] = useState(EXPIRY.TWELVE_HOURS.label);
|
||||
const { getExpiry, saveUserKey } = useUserKey(endpoint);
|
||||
const { showToast } = useToastContext();
|
||||
|
|
@ -218,10 +214,7 @@ const SetKeyDialog = ({
|
|||
methods.handleSubmit((data) => {
|
||||
const isAzure = endpoint === EModelEndpoint.azureOpenAI;
|
||||
const isOpenAIBase =
|
||||
isAzure ||
|
||||
endpoint === EModelEndpoint.openAI ||
|
||||
endpoint === EModelEndpoint.gptPlugins ||
|
||||
isAssistantsEndpoint(endpoint);
|
||||
isAzure || endpoint === EModelEndpoint.openAI || isAssistantsEndpoint(endpoint);
|
||||
if (isAzure) {
|
||||
data.apiKey = 'n/a';
|
||||
}
|
||||
|
|
@ -280,7 +273,6 @@ const SetKeyDialog = ({
|
|||
const EndpointComponent =
|
||||
endpointComponents[endpointType ?? endpoint] ?? endpointComponents['default'];
|
||||
const expiryTime = getExpiry();
|
||||
const config = endpointsConfig?.[endpoint];
|
||||
|
||||
return (
|
||||
<OGDialog open={open} onOpenChange={onOpenChange}>
|
||||
|
|
@ -310,12 +302,8 @@ const SetKeyDialog = ({
|
|||
<FormProvider {...methods}>
|
||||
<EndpointComponent
|
||||
userKey={userKey}
|
||||
endpoint={endpoint}
|
||||
setUserKey={setUserKey}
|
||||
endpoint={
|
||||
endpoint === EModelEndpoint.gptPlugins && (config?.azure ?? false)
|
||||
? EModelEndpoint.azureOpenAI
|
||||
: endpoint
|
||||
}
|
||||
userProvideURL={userProvideURL}
|
||||
/>
|
||||
</FormProvider>
|
||||
|
|
|
|||
|
|
@ -1,130 +0,0 @@
|
|||
import { useCallback, memo, ReactNode } from 'react';
|
||||
import { Spinner } from '@librechat/client';
|
||||
import { ChevronDownIcon, LucideProps } from 'lucide-react';
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react';
|
||||
import type { TResPlugin, TInput } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
import { useShareContext } from '~/Providers';
|
||||
import { cn, formatJSON } from '~/utils';
|
||||
import CodeBlock from './CodeBlock';
|
||||
|
||||
type PluginIconProps = LucideProps & {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function formatInputs(inputs: TInput[]) {
|
||||
let output = '';
|
||||
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
const input = formatJSON(`${inputs[i]?.inputStr ?? inputs[i]}`);
|
||||
output += input;
|
||||
|
||||
if (inputs.length > 1 && i !== inputs.length - 1) {
|
||||
output += ',\n';
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
type PluginProps = {
|
||||
plugin: TResPlugin;
|
||||
};
|
||||
|
||||
const Plugin: React.FC<PluginProps> = ({ plugin }) => {
|
||||
const { isSharedConvo } = useShareContext();
|
||||
const { data: plugins = {} } = useGetEndpointsQuery({
|
||||
enabled: !isSharedConvo,
|
||||
select: (data) => data?.gptPlugins?.plugins,
|
||||
});
|
||||
|
||||
const getPluginName = useCallback(
|
||||
(pluginKey: string) => {
|
||||
if (!pluginKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pluginKey === 'n/a' || pluginKey === 'self reflection') {
|
||||
return pluginKey;
|
||||
}
|
||||
return plugins[pluginKey] ?? 'self reflection';
|
||||
},
|
||||
[plugins],
|
||||
);
|
||||
|
||||
if (!plugin || !plugin.latest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const latestPlugin = getPluginName(plugin.latest);
|
||||
|
||||
if (!latestPlugin || (latestPlugin && latestPlugin === 'n/a')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const generateStatus = (): ReactNode => {
|
||||
if (!plugin.loading && latestPlugin === 'self reflection') {
|
||||
return 'Finished';
|
||||
} else if (latestPlugin === 'self reflection') {
|
||||
return "I'm thinking...";
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{plugin.loading ? 'Using' : 'Used'} <b>{latestPlugin}</b>
|
||||
{plugin.loading ? '...' : ''}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="my-2 flex flex-col items-start">
|
||||
<Disclosure>
|
||||
{({ open }) => {
|
||||
const iconProps: PluginIconProps = {
|
||||
className: cn(open ? 'rotate-180 transform' : '', 'h-4 w-4'),
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
plugin.loading ? 'bg-green-100' : 'bg-gray-20',
|
||||
'my-1 flex items-center rounded p-3 text-xs text-gray-800',
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div>{generateStatus()}</div>
|
||||
</div>
|
||||
</div>
|
||||
{plugin.loading && <Spinner className="ml-1 text-black" />}
|
||||
<DisclosureButton className="ml-12 flex items-center gap-2">
|
||||
<ChevronDownIcon {...iconProps} />
|
||||
</DisclosureButton>
|
||||
</div>
|
||||
|
||||
<DisclosurePanel className="mt-3 flex max-w-full flex-col gap-3">
|
||||
<CodeBlock
|
||||
lang={latestPlugin ? `REQUEST TO ${latestPlugin.toUpperCase()}` : 'REQUEST'}
|
||||
codeChildren={formatInputs(plugin.inputs ?? [])}
|
||||
plugin={true}
|
||||
classProp="max-h-[450px]"
|
||||
/>
|
||||
{plugin.outputs && plugin.outputs.length > 0 && (
|
||||
<CodeBlock
|
||||
lang={latestPlugin ? `RESPONSE FROM ${latestPlugin.toUpperCase()}` : 'RESPONSE'}
|
||||
codeChildren={formatJSON(plugin.outputs ?? '')}
|
||||
plugin={true}
|
||||
classProp="max-h-[450px]"
|
||||
/>
|
||||
)}
|
||||
</DisclosurePanel>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Disclosure>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Plugin);
|
||||
|
|
@ -1,2 +1 @@
|
|||
export { default as SubRow } from './SubRow';
|
||||
export { default as Plugin } from './Plugin';
|
||||
|
|
|
|||
|
|
@ -12,10 +12,9 @@ import { cn } from '~/utils';
|
|||
type BookmarkNavProps = {
|
||||
tags: string[];
|
||||
setTags: (tags: string[]) => void;
|
||||
isSmallScreen: boolean;
|
||||
};
|
||||
|
||||
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags, isSmallScreen }: BookmarkNavProps) => {
|
||||
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps) => {
|
||||
const localize = useLocalize();
|
||||
const { data } = useGetConversationTags();
|
||||
const label = useMemo(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { QueryKeys } from 'librechat-data-provider';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryKeys, Constants } from 'librechat-data-provider';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { useLocalize, useNewConvo } from '~/hooks';
|
||||
import { clearMessagesCache } from '~/utils';
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { QueryKeys } from 'librechat-data-provider';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { QueryKeys, Constants } from 'librechat-data-provider';
|
||||
import { TooltipAnchor, NewChatIcon, MobileSidebar, Sidebar, Button } from '@librechat/client';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import { useLocalize, useNewConvo } from '~/hooks';
|
||||
import { clearMessagesCache } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
|
|
|||
|
|
@ -5,27 +5,27 @@ import { useRecoilValue } from 'recoil';
|
|||
import { TrashIcon, ArchiveRestore, ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-react';
|
||||
import {
|
||||
Button,
|
||||
OGDialog,
|
||||
OGDialogContent,
|
||||
OGDialogHeader,
|
||||
OGDialogTitle,
|
||||
Label,
|
||||
TooltipAnchor,
|
||||
Spinner,
|
||||
OGDialog,
|
||||
DataTable,
|
||||
useToastContext,
|
||||
TooltipAnchor,
|
||||
useMediaQuery,
|
||||
OGDialogTitle,
|
||||
OGDialogHeader,
|
||||
useToastContext,
|
||||
OGDialogContent,
|
||||
} from '@librechat/client';
|
||||
import type { ConversationListParams, TConversation } from 'librechat-data-provider';
|
||||
import {
|
||||
useArchiveConvoMutation,
|
||||
useConversationsInfiniteQuery,
|
||||
useDeleteConversationMutation,
|
||||
useArchiveConvoMutation,
|
||||
} from '~/data-provider';
|
||||
import { MinimalIcon } from '~/components/Endpoints';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { formatDate, logger } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { formatDate } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
const DEFAULT_PARAMS: ConversationListParams = {
|
||||
|
|
@ -43,7 +43,7 @@ export default function ArchivedChatsTable({
|
|||
const localize = useLocalize();
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
const { showToast } = useToastContext();
|
||||
const isSearchEnabled = useRecoilValue(store.search);
|
||||
const searchState = useRecoilValue(store.search);
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
const [queryParams, setQueryParams] = useState<ConversationListParams>(DEFAULT_PARAMS);
|
||||
const [deleteConversation, setDeleteConversation] = useState<TConversation | null>(null);
|
||||
|
|
@ -101,6 +101,7 @@ export default function ArchivedChatsTable({
|
|||
});
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
logger.error('Error deleting archived conversation:', error);
|
||||
showToast({
|
||||
message: localize('com_ui_archive_delete_error') as string,
|
||||
severity: NotificationSeverity.ERROR,
|
||||
|
|
@ -113,6 +114,7 @@ export default function ArchivedChatsTable({
|
|||
await refetch();
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
logger.error('Error unarchiving conversation', error);
|
||||
showToast({
|
||||
message: localize('com_ui_unarchive_error') as string,
|
||||
severity: NotificationSeverity.ERROR,
|
||||
|
|
@ -283,7 +285,7 @@ export default function ArchivedChatsTable({
|
|||
isFetchingNextPage={isFetchingNextPage}
|
||||
isLoading={isLoading}
|
||||
showCheckboxes={false}
|
||||
enableSearch={isSearchEnabled}
|
||||
enableSearch={searchState.enabled === true}
|
||||
/>
|
||||
|
||||
<OGDialog open={isDeleteOpen} onOpenChange={onOpenChange}>
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ function PluginAuthForm({ plugin, onSubmit, isEntityTool }: TPluginAuthFormProps
|
|||
</HoverCard>
|
||||
{errors[authField] && (
|
||||
<span role="alert" className="mt-1 text-sm text-red-400">
|
||||
{errors[authField].message as string}
|
||||
{errors?.[authField]?.message ?? ''}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,245 +0,0 @@
|
|||
import { Search, X } from 'lucide-react';
|
||||
import { Dialog, DialogPanel, DialogTitle } from '@headlessui/react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useAvailablePluginsQuery } from 'librechat-data-provider/react-query';
|
||||
import type { TError, TPlugin, TPluginAction } from 'librechat-data-provider';
|
||||
import type { TPluginStoreDialogProps } from '~/common/types';
|
||||
import {
|
||||
usePluginDialogHelpers,
|
||||
useSetIndexOptions,
|
||||
usePluginInstall,
|
||||
useAuthContext,
|
||||
useLocalize,
|
||||
} from '~/hooks';
|
||||
import PluginPagination from './PluginPagination';
|
||||
import PluginStoreItem from './PluginStoreItem';
|
||||
import PluginAuthForm from './PluginAuthForm';
|
||||
|
||||
function PluginStoreDialog({ isOpen, setIsOpen }: TPluginStoreDialogProps) {
|
||||
const localize = useLocalize();
|
||||
const { user } = useAuthContext();
|
||||
const { data: availablePlugins } = useAvailablePluginsQuery();
|
||||
const { setTools } = useSetIndexOptions();
|
||||
|
||||
const [userPlugins, setUserPlugins] = useState<string[]>([]);
|
||||
|
||||
const {
|
||||
maxPage,
|
||||
setMaxPage,
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
itemsPerPage,
|
||||
searchChanged,
|
||||
setSearchChanged,
|
||||
searchValue,
|
||||
setSearchValue,
|
||||
gridRef,
|
||||
handleSearch,
|
||||
handleChangePage,
|
||||
error,
|
||||
setError,
|
||||
errorMessage,
|
||||
setErrorMessage,
|
||||
showPluginAuthForm,
|
||||
setShowPluginAuthForm,
|
||||
selectedPlugin,
|
||||
setSelectedPlugin,
|
||||
} = usePluginDialogHelpers();
|
||||
|
||||
const handleInstallError = useCallback(
|
||||
(error: TError) => {
|
||||
setError(true);
|
||||
if (error.response?.data?.message) {
|
||||
setErrorMessage(error.response.data.message);
|
||||
}
|
||||
setTimeout(() => {
|
||||
setError(false);
|
||||
setErrorMessage('');
|
||||
}, 5000);
|
||||
},
|
||||
[setError, setErrorMessage],
|
||||
);
|
||||
|
||||
const { installPlugin, uninstallPlugin } = usePluginInstall({
|
||||
onInstallError: handleInstallError,
|
||||
onUninstallError: handleInstallError,
|
||||
onUninstallSuccess: (_data, variables) => {
|
||||
setTools(variables.pluginKey, true);
|
||||
},
|
||||
});
|
||||
|
||||
const handleInstall = (pluginAction: TPluginAction, plugin?: TPlugin) => {
|
||||
if (!plugin) {
|
||||
return;
|
||||
}
|
||||
installPlugin(pluginAction, plugin);
|
||||
setShowPluginAuthForm(false);
|
||||
};
|
||||
|
||||
const onPluginInstall = (pluginKey: string) => {
|
||||
const plugin = availablePlugins?.find((p) => p.pluginKey === pluginKey);
|
||||
if (!plugin) {
|
||||
return;
|
||||
}
|
||||
setSelectedPlugin(plugin);
|
||||
|
||||
const { authConfig, authenticated } = plugin ?? {};
|
||||
|
||||
if (authConfig && authConfig.length > 0 && !authenticated) {
|
||||
setShowPluginAuthForm(true);
|
||||
} else {
|
||||
handleInstall({ pluginKey, action: 'install', auth: null }, plugin);
|
||||
}
|
||||
};
|
||||
|
||||
const filteredPlugins = availablePlugins?.filter((plugin) =>
|
||||
plugin.name.toLowerCase().includes(searchValue.toLowerCase()),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (user && user.plugins) {
|
||||
setUserPlugins(user.plugins);
|
||||
}
|
||||
|
||||
if (filteredPlugins) {
|
||||
setMaxPage(Math.ceil(filteredPlugins.length / itemsPerPage));
|
||||
if (searchChanged) {
|
||||
setCurrentPage(1);
|
||||
setSearchChanged(false);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
availablePlugins,
|
||||
itemsPerPage,
|
||||
user,
|
||||
searchValue,
|
||||
filteredPlugins,
|
||||
searchChanged,
|
||||
setMaxPage,
|
||||
setCurrentPage,
|
||||
setSearchChanged,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={() => {
|
||||
setIsOpen(false);
|
||||
setCurrentPage(1);
|
||||
setSearchValue('');
|
||||
}}
|
||||
className="relative z-[102]"
|
||||
>
|
||||
{/* The backdrop, rendered as a fixed sibling to the panel container */}
|
||||
<div className="fixed inset-0 bg-gray-600/65 transition-opacity dark:bg-black/80" />
|
||||
{/* Full-screen container to center the panel */}
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<DialogPanel
|
||||
className="relative w-full transform overflow-hidden overflow-y-auto rounded-lg bg-white text-left shadow-xl transition-all dark:bg-gray-700 max-sm:h-full sm:mx-7 sm:my-8 sm:max-w-2xl lg:max-w-5xl xl:max-w-7xl"
|
||||
style={{ minHeight: '610px' }}
|
||||
>
|
||||
<div className="flex items-center justify-between border-b-[1px] border-black/10 p-6 pb-4 dark:border-white/10">
|
||||
<div className="flex items-center">
|
||||
<div className="text-center sm:text-left">
|
||||
<DialogTitle className="text-lg font-medium leading-6 text-gray-800 dark:text-gray-200">
|
||||
{localize('com_nav_plugin_store')}
|
||||
</DialogTitle>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="sm:mt-0">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="inline-block text-gray-500 hover:text-gray-200"
|
||||
tabIndex={0}
|
||||
>
|
||||
<X />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{error && (
|
||||
<div
|
||||
className="relative m-4 rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700"
|
||||
role="alert"
|
||||
>
|
||||
{localize('com_nav_plugin_auth_error')} {errorMessage}
|
||||
</div>
|
||||
)}
|
||||
{showPluginAuthForm && (
|
||||
<div className="p-4 sm:p-6 sm:pt-4">
|
||||
<PluginAuthForm
|
||||
plugin={selectedPlugin}
|
||||
onSubmit={(action: TPluginAction) => handleInstall(action, selectedPlugin)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-4 sm:p-6 sm:pt-4">
|
||||
<div className="mt-4 flex flex-col gap-4">
|
||||
<div className="flex items-center">
|
||||
<div className="relative flex items-center">
|
||||
<Search className="absolute left-2 h-6 w-6 text-gray-500" aria-hidden="true" />
|
||||
<input
|
||||
type="text"
|
||||
value={searchValue}
|
||||
onChange={handleSearch}
|
||||
placeholder={localize('com_nav_plugin_search')}
|
||||
className="text-token-text-primary flex rounded-md border border-border-heavy bg-surface-tertiary py-2 pl-10 pr-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref={gridRef}
|
||||
className="grid grid-cols-1 grid-rows-2 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
||||
style={{ minHeight: '410px' }}
|
||||
>
|
||||
{filteredPlugins &&
|
||||
filteredPlugins
|
||||
.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage)
|
||||
.map((plugin, index) => (
|
||||
<PluginStoreItem
|
||||
key={index}
|
||||
plugin={plugin}
|
||||
isInstalled={userPlugins.includes(plugin.pluginKey)}
|
||||
onInstall={() => onPluginInstall(plugin.pluginKey)}
|
||||
onUninstall={() => uninstallPlugin(plugin.pluginKey)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col items-center gap-2 sm:flex-row sm:justify-between">
|
||||
{maxPage > 0 ? (
|
||||
<PluginPagination
|
||||
currentPage={currentPage}
|
||||
maxPage={maxPage}
|
||||
onChangePage={handleChangePage}
|
||||
/>
|
||||
) : (
|
||||
<div style={{ height: '21px' }}></div>
|
||||
)}
|
||||
{/* API not yet implemented: */}
|
||||
{/* <div className="flex flex-col items-center gap-2 sm:flex-row">
|
||||
<PluginStoreLinkButton
|
||||
label="Install an unverified plugin"
|
||||
onClick={onInstallUnverifiedPlugin}
|
||||
/>
|
||||
<div className="hidden h-4 border-l border-black/30 dark:border-white/30 sm:block"></div>
|
||||
<PluginStoreLinkButton
|
||||
label="Develop your own plugin"
|
||||
onClick={onDevelopPluginClick}
|
||||
/>
|
||||
<div className="hidden h-4 border-l border-black/30 dark:border-white/30 sm:block"></div>
|
||||
<PluginStoreLinkButton label="About plugins" onClick={onAboutPluginsClick} />
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default PluginStoreDialog;
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
import { TPlugin } from 'librechat-data-provider';
|
||||
import { XCircle, DownloadCloud } from 'lucide-react';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type TPluginStoreItemProps = {
|
||||
plugin: TPlugin;
|
||||
onInstall: () => void;
|
||||
onUninstall: () => void;
|
||||
isInstalled?: boolean;
|
||||
};
|
||||
|
||||
function PluginStoreItem({ plugin, onInstall, onUninstall, isInstalled }: TPluginStoreItemProps) {
|
||||
const localize = useLocalize();
|
||||
const handleClick = () => {
|
||||
if (isInstalled) {
|
||||
onUninstall();
|
||||
} else {
|
||||
onInstall();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-4 rounded border border-black/10 bg-white p-6 dark:border-gray-500 dark:bg-gray-700">
|
||||
<div className="flex gap-4">
|
||||
<div className="h-[70px] w-[70px] shrink-0">
|
||||
<div className="relative h-full w-full">
|
||||
<img
|
||||
src={plugin.icon}
|
||||
alt={`${plugin.name} logo`}
|
||||
className="h-full w-full rounded-[5px]"
|
||||
/>
|
||||
<div className="absolute inset-0 rounded-[5px] ring-1 ring-inset ring-black/10"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-col items-start justify-between">
|
||||
<div className="mb-2 line-clamp-1 max-w-full text-lg leading-5 text-gray-700/80 dark:text-gray-50">
|
||||
{plugin.name}
|
||||
</div>
|
||||
{!isInstalled ? (
|
||||
<button
|
||||
className="btn btn-primary relative"
|
||||
aria-label={`${localize('com_nav_plugin_install')} ${plugin.name}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
{localize('com_nav_plugin_install')}
|
||||
<DownloadCloud
|
||||
className="flex h-4 w-4 items-center stroke-2"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="btn relative bg-gray-300 hover:bg-gray-400 dark:bg-gray-50 dark:hover:bg-gray-200"
|
||||
onClick={handleClick}
|
||||
aria-label={`${localize('com_nav_plugin_uninstall')} ${plugin.name}`}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
{localize('com_nav_plugin_uninstall')}
|
||||
<XCircle className="flex h-4 w-4 items-center stroke-2" aria-hidden="true" />
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="line-clamp-3 h-[60px] text-sm text-gray-700/70 dark:text-gray-50/70">
|
||||
{plugin.description}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PluginStoreItem;
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
type TPluginStoreLinkButtonProps = {
|
||||
onClick: () => void;
|
||||
label: string;
|
||||
};
|
||||
|
||||
function PluginStoreLinkButton({ onClick, label }: TPluginStoreLinkButtonProps) {
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
onClick={onClick}
|
||||
className="text-sm text-black/70 hover:text-black/50 dark:text-white/70 dark:hover:text-white/50"
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PluginStoreLinkButton;
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { HoverCardPortal, HoverCardContent } from '@librechat/client';
|
||||
import './styles.module.css';
|
||||
|
||||
type TPluginTooltipProps = {
|
||||
content: string;
|
||||
|
|
@ -9,11 +8,9 @@ type TPluginTooltipProps = {
|
|||
function PluginTooltip({ content, position }: TPluginTooltipProps) {
|
||||
return (
|
||||
<HoverCardPortal>
|
||||
<HoverCardContent side={position} className="w-80 ">
|
||||
<HoverCardContent side={position} className="w-80">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm text-gray-600 dark:text-gray-300">
|
||||
{content}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-300">{content}</div>
|
||||
</div>
|
||||
</HoverCardContent>
|
||||
</HoverCardPortal>
|
||||
|
|
|
|||
|
|
@ -1,223 +0,0 @@
|
|||
import { render, screen, fireEvent } from 'test/layout-test-utils';
|
||||
import PluginStoreDialog from '../PluginStoreDialog';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import * as mockDataProvider from 'librechat-data-provider/react-query';
|
||||
import * as authMutations from '~/data-provider/Auth/mutations';
|
||||
import * as authQueries from '~/data-provider/Auth/queries';
|
||||
|
||||
jest.mock('librechat-data-provider/react-query');
|
||||
|
||||
class ResizeObserver {
|
||||
observe() {
|
||||
// do nothing
|
||||
}
|
||||
unobserve() {
|
||||
// do nothing
|
||||
}
|
||||
disconnect() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
window.ResizeObserver = ResizeObserver;
|
||||
|
||||
const pluginsQueryResult = [
|
||||
{
|
||||
name: 'Google',
|
||||
pluginKey: 'google',
|
||||
description: 'Use Google Search to find information',
|
||||
icon: 'https://i.imgur.com/SMmVkNB.png',
|
||||
authConfig: [
|
||||
{
|
||||
authField: 'GOOGLE_CSE_ID',
|
||||
label: 'Google CSE ID',
|
||||
description: 'This is your Google Custom Search Engine ID.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Wolfram',
|
||||
pluginKey: 'wolfram',
|
||||
description:
|
||||
'Access computation, math, curated knowledge & real-time data through Wolfram|Alpha and Wolfram Language.',
|
||||
icon: 'https://www.wolframcdn.com/images/icons/Wolfram.png',
|
||||
authConfig: [
|
||||
{
|
||||
authField: 'WOLFRAM_APP_ID',
|
||||
label: 'Wolfram App ID',
|
||||
description: 'An AppID must be supplied in all calls to the Wolfram|Alpha API.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Calculator',
|
||||
pluginKey: 'calculator',
|
||||
description: 'A simple calculator plugin',
|
||||
icon: 'https://i.imgur.com/SMmVkNB.png',
|
||||
authConfig: [],
|
||||
},
|
||||
{
|
||||
name: 'Plugin 1',
|
||||
pluginKey: 'plugin1',
|
||||
description: 'description for Plugin 1.',
|
||||
icon: 'mock-icon',
|
||||
authConfig: [],
|
||||
},
|
||||
{
|
||||
name: 'Plugin 2',
|
||||
pluginKey: 'plugin2',
|
||||
description: 'description for Plugin 2.',
|
||||
icon: 'mock-icon',
|
||||
authConfig: [],
|
||||
},
|
||||
{
|
||||
name: 'Plugin 3',
|
||||
pluginKey: 'plugin3',
|
||||
description: 'description for Plugin 3.',
|
||||
icon: 'mock-icon',
|
||||
authConfig: [],
|
||||
},
|
||||
{
|
||||
name: 'Plugin 4',
|
||||
pluginKey: 'plugin4',
|
||||
description: 'description for Plugin 4.',
|
||||
icon: 'mock-icon',
|
||||
authConfig: [],
|
||||
},
|
||||
{
|
||||
name: 'Plugin 5',
|
||||
pluginKey: 'plugin5',
|
||||
description: 'description for Plugin 5.',
|
||||
icon: 'mock-icon',
|
||||
authConfig: [],
|
||||
},
|
||||
{
|
||||
name: 'Plugin 6',
|
||||
pluginKey: 'plugin6',
|
||||
description: 'description for Plugin 6.',
|
||||
icon: 'mock-icon',
|
||||
authConfig: [],
|
||||
},
|
||||
{
|
||||
name: 'Plugin 7',
|
||||
pluginKey: 'plugin7',
|
||||
description: 'description for Plugin 7.',
|
||||
icon: 'mock-icon',
|
||||
authConfig: [],
|
||||
},
|
||||
];
|
||||
|
||||
const setup = ({
|
||||
useGetUserQueryReturnValue = {
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
data: {
|
||||
plugins: ['wolfram'],
|
||||
},
|
||||
},
|
||||
useRefreshTokenMutationReturnValue = {
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
mutate: jest.fn(),
|
||||
data: {
|
||||
token: 'mock-token',
|
||||
user: {},
|
||||
},
|
||||
},
|
||||
useAvailablePluginsQueryReturnValue = {
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
data: pluginsQueryResult,
|
||||
},
|
||||
useUpdateUserPluginsMutationReturnValue = {
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
mutate: jest.fn(),
|
||||
data: {},
|
||||
},
|
||||
} = {}) => {
|
||||
const mockUseAvailablePluginsQuery = jest
|
||||
.spyOn(mockDataProvider, 'useAvailablePluginsQuery')
|
||||
//@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult
|
||||
.mockReturnValue(useAvailablePluginsQueryReturnValue);
|
||||
const mockUseUpdateUserPluginsMutation = jest
|
||||
.spyOn(mockDataProvider, 'useUpdateUserPluginsMutation')
|
||||
//@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult
|
||||
.mockReturnValue(useUpdateUserPluginsMutationReturnValue);
|
||||
const mockUseGetUserQuery = jest
|
||||
.spyOn(authQueries, 'useGetUserQuery')
|
||||
//@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult
|
||||
.mockReturnValue(useGetUserQueryReturnValue);
|
||||
const mockUseRefreshTokenMutation = jest
|
||||
.spyOn(authMutations, 'useRefreshTokenMutation')
|
||||
//@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult
|
||||
.mockReturnValue(useRefreshTokenMutationReturnValue);
|
||||
const mockSetIsOpen = jest.fn();
|
||||
const renderResult = render(<PluginStoreDialog isOpen={true} setIsOpen={mockSetIsOpen} />);
|
||||
|
||||
return {
|
||||
...renderResult,
|
||||
mockUseGetUserQuery,
|
||||
mockUseAvailablePluginsQuery,
|
||||
mockUseUpdateUserPluginsMutation,
|
||||
mockUseRefreshTokenMutation,
|
||||
mockSetIsOpen,
|
||||
};
|
||||
};
|
||||
|
||||
test('renders plugin store dialog with plugins from the available plugins query and shows install/uninstall buttons based on user plugins', () => {
|
||||
const { getByText, getByRole } = setup();
|
||||
expect(getByText(/Plugin Store/i)).toBeInTheDocument();
|
||||
expect(getByText(/Use Google Search to find information/i)).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: 'Install Google' })).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: 'Uninstall Wolfram' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Displays the plugin auth form when installing a plugin with auth', async () => {
|
||||
const { getByRole, getByText } = setup();
|
||||
const googleButton = getByRole('button', { name: 'Install Google' });
|
||||
await userEvent.click(googleButton);
|
||||
expect(getByText(/Google CSE ID/i)).toBeInTheDocument();
|
||||
expect(getByRole('button', { name: 'Save' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('allows the user to navigate between pages', async () => {
|
||||
const { getByRole, getByText } = setup();
|
||||
|
||||
expect(getByText('Google')).toBeInTheDocument();
|
||||
expect(getByText('Wolfram')).toBeInTheDocument();
|
||||
expect(getByText('Plugin 1')).toBeInTheDocument();
|
||||
|
||||
const nextPageButton = getByRole('button', { name: 'Next page' });
|
||||
await userEvent.click(nextPageButton);
|
||||
|
||||
expect(getByText('Plugin 6')).toBeInTheDocument();
|
||||
expect(getByText('Plugin 7')).toBeInTheDocument();
|
||||
// expect(getByText('Plugin 3')).toBeInTheDocument();
|
||||
// expect(getByText('Plugin 4')).toBeInTheDocument();
|
||||
// expect(getByText('Plugin 5')).toBeInTheDocument();
|
||||
|
||||
const previousPageButton = getByRole('button', { name: 'Previous page' });
|
||||
await userEvent.click(previousPageButton);
|
||||
|
||||
expect(getByText('Google')).toBeInTheDocument();
|
||||
expect(getByText('Wolfram')).toBeInTheDocument();
|
||||
expect(getByText('Plugin 1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('allows the user to search for plugins', async () => {
|
||||
setup();
|
||||
|
||||
const searchInput = screen.getByPlaceholderText('Search plugins');
|
||||
fireEvent.change(searchInput, { target: { value: 'Google' } });
|
||||
|
||||
expect(screen.getByText('Google')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Wolfram')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Plugin 1')).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.change(searchInput, { target: { value: 'Plugin 1' } });
|
||||
|
||||
expect(screen.getByText('Plugin 1')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Google')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Wolfram')).not.toBeInTheDocument();
|
||||
});
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
import 'test/matchMedia.mock';
|
||||
import { render, screen } from 'test/layout-test-utils';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TPlugin } from 'librechat-data-provider';
|
||||
import PluginStoreItem from '../PluginStoreItem';
|
||||
|
||||
const mockPlugin = {
|
||||
name: 'Test Plugin',
|
||||
description: 'This is a test plugin',
|
||||
icon: 'test-icon.png',
|
||||
};
|
||||
|
||||
describe('PluginStoreItem', () => {
|
||||
it('renders the plugin name and description', () => {
|
||||
render(
|
||||
<PluginStoreItem
|
||||
plugin={mockPlugin as TPlugin}
|
||||
onInstall={() => {
|
||||
return;
|
||||
}}
|
||||
onUninstall={() => {
|
||||
return;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText('Test Plugin')).toBeInTheDocument();
|
||||
expect(screen.getByText('This is a test plugin')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onInstall when the install button is clicked', async () => {
|
||||
const onInstall = jest.fn();
|
||||
render(
|
||||
<PluginStoreItem
|
||||
plugin={mockPlugin as TPlugin}
|
||||
onInstall={onInstall}
|
||||
onUninstall={() => {
|
||||
return;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
await userEvent.click(screen.getByText('Install'));
|
||||
expect(onInstall).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls onUninstall when the uninstall button is clicked', async () => {
|
||||
const onUninstall = jest.fn();
|
||||
render(
|
||||
<PluginStoreItem
|
||||
plugin={mockPlugin as TPlugin}
|
||||
onInstall={() => {
|
||||
return;
|
||||
}}
|
||||
onUninstall={onUninstall}
|
||||
isInstalled
|
||||
/>,
|
||||
);
|
||||
await userEvent.click(screen.getByText('Uninstall'));
|
||||
expect(onUninstall).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
export { default as PluginStoreDialog } from './PluginStoreDialog';
|
||||
export { default as PluginStoreItem } from './PluginStoreItem';
|
||||
export { default as PluginPagination } from './PluginPagination';
|
||||
export { default as PluginStoreLinkButton } from './PluginStoreLinkButton';
|
||||
export { default as PluginAuthForm } from './PluginAuthForm';
|
||||
export { default as PluginTooltip } from './PluginTooltip';
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
a {
|
||||
text-decoration: underline;
|
||||
color: white;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export * from './Store';
|
||||
|
|
@ -125,7 +125,9 @@ const VersionCard = ({
|
|||
|
||||
<div className="flex items-center gap-1 lg:flex-col xl:flex-row">
|
||||
{authorName && (
|
||||
<Label className="text-left text-xs text-text-secondary">by {authorName}</Label>
|
||||
<Label className="text-left text-xs text-text-secondary">
|
||||
{localize('com_ui_by_author', { 0: authorName })}
|
||||
</Label>
|
||||
)}
|
||||
|
||||
{tags.length > 0 && <VersionTags tags={tags} />}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import MinimalHoverButtons from '~/components/Chat/Messages/MinimalHoverButtons'
|
|||
import MessageContent from '~/components/Chat/Messages/Content/MessageContent';
|
||||
import SearchContent from '~/components/Chat/Messages/Content/SearchContent';
|
||||
import SiblingSwitch from '~/components/Chat/Messages/SiblingSwitch';
|
||||
import { Plugin } from '~/components/Messages/Content';
|
||||
import SubRow from '~/components/Chat/Messages/SubRow';
|
||||
import { fontSizeAtom } from '~/store/fontSize';
|
||||
import { MessageContext } from '~/Providers';
|
||||
|
|
@ -80,8 +79,6 @@ export default function Message(props: TMessageProps) {
|
|||
isLatestMessage: false, // No concept of latest message in share view
|
||||
}}
|
||||
>
|
||||
{/* Legacy Plugins */}
|
||||
{message.plugin && <Plugin plugin={message.plugin} />}
|
||||
{message.content ? (
|
||||
<SearchContent
|
||||
message={message}
|
||||
|
|
|
|||
|
|
@ -50,8 +50,6 @@ jest.mock('librechat-data-provider', () => {
|
|||
},
|
||||
EModelEndpoint: actualModule.EModelEndpoint || {
|
||||
agents: 'agents',
|
||||
chatGPTBrowser: 'chatGPTBrowser',
|
||||
gptPlugins: 'gptPlugins',
|
||||
},
|
||||
ResourceType: actualModule.ResourceType || {
|
||||
AGENT: 'agent',
|
||||
|
|
|
|||
|
|
@ -306,9 +306,7 @@ export default function AgentPanel() {
|
|||
(key) =>
|
||||
!isAssistantsEndpoint(key) &&
|
||||
(allowedProviders.size > 0 ? allowedProviders.has(key) : true) &&
|
||||
key !== EModelEndpoint.agents &&
|
||||
key !== EModelEndpoint.chatGPTBrowser &&
|
||||
key !== EModelEndpoint.gptPlugins,
|
||||
key !== EModelEndpoint.agents,
|
||||
)
|
||||
.map((provider) => createProviderOption(provider)),
|
||||
[endpointsConfig, allowedProviders],
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import type {
|
|||
TPluginAction,
|
||||
TError,
|
||||
} from 'librechat-data-provider';
|
||||
import type { TPluginStoreDialogProps } from '~/common/types';
|
||||
import type { ToolDialogProps } from '~/common/types';
|
||||
import { PluginPagination, PluginAuthForm } from '~/components/Plugins/Store';
|
||||
import { useLocalize, usePluginDialogHelpers } from '~/hooks';
|
||||
import { useAvailableToolsQuery } from '~/data-provider';
|
||||
|
|
@ -20,7 +20,7 @@ function AssistantToolsDialog({
|
|||
isOpen,
|
||||
endpoint,
|
||||
setIsOpen,
|
||||
}: TPluginStoreDialogProps & {
|
||||
}: ToolDialogProps & {
|
||||
endpoint: AssistantsEndpoint | EModelEndpoint.agents;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { Constants, EModelEndpoint, QueryKeys } from 'librechat-data-provider';
|
|||
import { Dialog, DialogPanel, DialogTitle, Description } from '@headlessui/react';
|
||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TError, AgentToolType } from 'librechat-data-provider';
|
||||
import type { AgentForm, TPluginStoreDialogProps } from '~/common';
|
||||
import type { AgentForm, ToolDialogProps } from '~/common';
|
||||
import {
|
||||
usePluginDialogHelpers,
|
||||
useMCPServerManager,
|
||||
|
|
@ -24,7 +24,7 @@ function MCPToolSelectDialog({
|
|||
agentId,
|
||||
setIsOpen,
|
||||
mcpServerNames,
|
||||
}: TPluginStoreDialogProps & {
|
||||
}: ToolDialogProps & {
|
||||
agentId: string;
|
||||
mcpServerNames?: string[];
|
||||
endpoint: EModelEndpoint.agents;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type {
|
|||
TPlugin,
|
||||
TError,
|
||||
} from 'librechat-data-provider';
|
||||
import type { AgentForm, TPluginStoreDialogProps } from '~/common';
|
||||
import type { AgentForm, ToolDialogProps } from '~/common';
|
||||
import { PluginPagination, PluginAuthForm } from '~/components/Plugins/Store';
|
||||
import { useAgentPanelContext } from '~/Providers/AgentPanelContext';
|
||||
import { useLocalize, usePluginDialogHelpers } from '~/hooks';
|
||||
|
|
@ -21,7 +21,7 @@ function ToolSelectDialog({
|
|||
isOpen,
|
||||
endpoint,
|
||||
setIsOpen,
|
||||
}: TPluginStoreDialogProps & {
|
||||
}: ToolDialogProps & {
|
||||
endpoint: AssistantsEndpoint | EModelEndpoint.agents;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue