🚧 WIP: Merge Dev Build (#4611)

* refactor: Agent CodeFiles, abortUpload WIP

* feat: code environment file upload

* refactor: useLazyEffect

* refactor:
- Add `watch` from `useFormContext` to check if code execution is enabled
- Disable file upload button if `agent_id` is not selected or code execution is disabled

* WIP: primeCodeFiles; refactor: rename sessionId to session_id for uniformity

* Refactor: Rename session_id to sessionId for uniformity in AuthService.js

* chore: bump @librechat/agents to version 1.7.1

* WIP: prime code files

* refactor: Update code env file upload method to use read stream

* feat: reupload code env file if no longer active

* refactor: isAssistantTool -> isEntityTool + address type issues

* feat: execute code tool hook

* refactor: Rename isPluginAuthenticated to checkPluginAuth in PluginController.js

* refactor: Update PluginController.js to use AuthType constant for comparison

* feat: verify tool authentication (execute_code)

* feat: enter librechat_code_api_key

* refactor: Remove unused imports in BookmarkForm.tsx

* feat: authenticate code tool

* refactor: Update Action.tsx to conditionally render the key and revoke key buttons

* refactor(Code/Action): prevent uncheck-able 'Run Code' capability when key is revoked

* refactor(Code/Action): Update Action.tsx to conditionally render the key and revoke key buttons

* fix: agent file upload edge cases

* chore: bump @librechat/agents

* fix: custom endpoint providerValue icon

* feat: ollama meta modal token values + context

* feat: ollama agents

* refactor: Update token models for Ollama models

* chore: Comment out CodeForm

* refactor: Update token models for Ollama and Meta models
This commit is contained in:
Danny Avila 2024-11-01 18:36:39 -04:00 committed by GitHub
parent 1909efd6ba
commit 95011ce349
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 1418 additions and 1002 deletions

View file

@ -7,12 +7,12 @@ import type {
TConversationTag,
TConversationTagRequest,
} from 'librechat-data-provider';
import { cn, removeFocusOutlines, defaultTextProps, logger } from '~/utils';
import { Checkbox, Label, TextareaAutosize, Input } from '~/components';
import { useBookmarkContext } from '~/Providers/BookmarkContext';
import { useConversationTagMutation } from '~/data-provider';
import { useToastContext } from '~/Providers';
import { useLocalize } from '~/hooks';
import { cn, logger } from '~/utils';
type TBookmarkFormProps = {
tags?: string[];

View file

@ -47,26 +47,28 @@ const getKnownClass = ({
export default function UnknownIcon({
className = '',
endpoint,
iconURL,
endpoint: _endpoint,
iconURL = '',
context,
}: {
iconURL?: string;
className?: string;
endpoint: EModelEndpoint | string | null;
endpoint?: EModelEndpoint | string | null;
context?: 'landing' | 'menu-item' | 'nav' | 'message';
}) {
const endpoint = _endpoint ?? '';
if (!endpoint) {
return <CustomMinimalIcon className={className} />;
}
console.log('UnknownIcon', endpoint);
const currentEndpoint = endpoint.toLowerCase();
if (iconURL) {
return <img className={className} src={iconURL} alt={`${endpoint} Icon`} />;
}
const assetPath = knownEndpointAssets[currentEndpoint];
const assetPath: string = knownEndpointAssets[currentEndpoint] ?? '';
if (!assetPath) {
return <CustomMinimalIcon className={className} />;

View file

@ -1,22 +1,26 @@
import { TPlugin, TPluginAuthConfig, TPluginAction } from 'librechat-data-provider';
import { Save } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { TPlugin, TPluginAuthConfig, TPluginAction } from 'librechat-data-provider';
import { HoverCard, HoverCardTrigger } from '~/components/ui';
import PluginTooltip from './PluginTooltip';
import { useLocalize } from '~/hooks';
type TPluginAuthFormProps = {
plugin: TPlugin | undefined;
onSubmit: (installActionData: TPluginAction) => void;
isAssistantTool?: boolean;
isEntityTool?: boolean;
};
function PluginAuthForm({ plugin, onSubmit, isAssistantTool }: TPluginAuthFormProps) {
function PluginAuthForm({ plugin, onSubmit, isEntityTool }: TPluginAuthFormProps) {
const {
register,
handleSubmit,
formState: { errors, isDirty, isValid, isSubmitting },
} = useForm();
const localize = useLocalize();
const authConfig = plugin?.authConfig ?? [];
return (
<div className="flex w-full flex-col items-center gap-2">
<div className="grid w-full gap-6 sm:grid-cols-2">
@ -28,11 +32,11 @@ function PluginAuthForm({ plugin, onSubmit, isAssistantTool }: TPluginAuthFormPr
pluginKey: plugin?.pluginKey ?? '',
action: 'install',
auth,
isAssistantTool,
isEntityTool,
}),
)}
>
{plugin?.authConfig?.map((config: TPluginAuthConfig, i: number) => {
{authConfig.map((config: TPluginAuthConfig, i: number) => {
const authField = config.authField.split('||')[0];
return (
<div key={`${authField}-${i}`} className="flex w-full flex-col gap-1">
@ -66,8 +70,7 @@ function PluginAuthForm({ plugin, onSubmit, isAssistantTool }: TPluginAuthFormPr
</HoverCard>
{errors[authField] && (
<span role="alert" className="mt-1 text-sm text-red-400">
{/* @ts-ignore - Type 'string | FieldError | Merge<FieldError, FieldErrorsImpl<any>> | undefined' is not assignable to type 'ReactNode' */}
{errors[authField].message}
{errors[authField].message as string}
</span>
)}
</div>
@ -79,7 +82,7 @@ function PluginAuthForm({ plugin, onSubmit, isAssistantTool }: TPluginAuthFormPr
className="btn btn-primary relative"
>
<div className="flex items-center justify-center gap-2">
Save
{localize('com_ui_save')}
<Save className="flex h-4 w-4 items-center stroke-2" />
</div>
</button>

View file

@ -11,7 +11,6 @@ import { icons } from '~/components/Chat/Menus/Endpoints/Icons';
import Action from '~/components/SidePanel/Builder/Action';
import { ToolSelectDialog } from '~/components/Tools';
import { useLocalize, useAuthContext } from '~/hooks';
import CapabilitiesForm from './CapabilitiesForm';
import { processAgentOption } from '~/utils';
import { Spinner } from '~/components/svg';
import DeleteButton from './DeleteButton';
@ -19,6 +18,7 @@ import AgentAvatar from './AgentAvatar';
import FileSearch from './FileSearch';
import ShareAgent from './ShareAgent';
import AgentTool from './AgentTool';
// import CodeForm from './Code/Form';
import { Panel } from '~/common';
const labelClass = 'mb-2 text-token-text-primary block font-medium';
@ -92,6 +92,26 @@ export default function AgentConfig({
return _agent.knowledge_files ?? [];
}, [agent, agent_id, fileMap]);
const code_files = useMemo(() => {
if (typeof agent === 'string') {
return [];
}
if (agent?.id !== agent_id) {
return [];
}
if (agent.code_files) {
return agent.code_files;
}
const _agent = processAgentOption({
agent,
fileMap,
});
return _agent.code_files ?? [];
}, [agent, agent_id, fileMap]);
/* Mutations */
const update = useUpdateAgentMutation({
onSuccess: (data) => {
@ -293,7 +313,7 @@ export default function AgentConfig({
<div className="shadow-stroke relative flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-white text-black dark:bg-white">
<Icon
className="h-2/3 w-2/3"
endpoint={provider as string}
endpoint={providerValue as string}
endpointType={endpointType}
iconURL={endpointIconURL}
/>
@ -303,11 +323,8 @@ export default function AgentConfig({
</div>
</button>
</div>
<CapabilitiesForm
codeEnabled={codeEnabled}
agentsConfig={agentsConfig}
retrievalEnabled={false}
/>
{/* Code Execution */}
{/* {codeEnabled && <CodeForm agent_id={agent_id} files={code_files} />} */}
{/* File Search */}
{fileSearchEnabled && <FileSearch agent_id={agent_id} files={knowledge_files} />}
{/* Agent Tools & Actions */}

View file

@ -12,7 +12,7 @@ import { cn } from '~/utils';
export default function AgentTool({
tool,
allTools,
agent_id,
agent_id = '',
}: {
tool: string;
allTools: TPlugin[];
@ -28,7 +28,7 @@ export default function AgentTool({
const removeTool = (tool: string) => {
if (tool) {
updateUserPlugins.mutate(
{ pluginKey: tool, action: 'uninstall', auth: null, isAgentTool: true },
{ pluginKey: tool, action: 'uninstall', auth: null, isEntityTool: true },
{
onError: (error: unknown) => {
showToast({ message: `Error while deleting the tool: ${error}`, status: 'error' });

View file

@ -1,56 +0,0 @@
import { useMemo } from 'react';
// import { Capabilities } from 'librechat-data-provider';
// import { useFormContext, useWatch } from 'react-hook-form';
import type { TConfig } from 'librechat-data-provider';
// import type { AgentForm } from '~/common';
// import ImageVision from './ImageVision';
import { useLocalize } from '~/hooks';
import Retrieval from './Retrieval';
// import CodeFiles from './CodeFiles';
import Code from './Code';
export default function CapabilitiesForm({
codeEnabled,
retrievalEnabled,
agentsConfig,
}: {
codeEnabled?: boolean;
retrievalEnabled?: boolean;
agentsConfig?: TConfig | null;
}) {
const localize = useLocalize();
// const methods = useFormContext<AgentForm>();
// const { control } = methods;
// const agent = useWatch({ control, name: 'agent' });
// const agent_id = useWatch({ control, name: 'id' });
// const files = useMemo(() => {
// if (typeof agent === 'string') {
// return [];
// }
// return agent?.code_files;
// }, [agent]);
const retrievalModels = useMemo(
() => new Set(agentsConfig?.retrievalModels ?? []),
[agentsConfig],
);
return (
<div className="mb-4">
<div className="mb-1.5 flex items-center">
<span>
<label className="text-token-text-primary block font-medium">
{localize('com_assistants_capabilities')}
</label>
</span>
</div>
<div className="flex flex-col items-start gap-2">
{codeEnabled === true && <Code />}
{retrievalEnabled === true && <Retrieval retrievalModels={retrievalModels} />}
{/* {imageVisionEnabled && version == 1 && <ImageVision />} */}
{/* {codeEnabled && <CodeFiles agent_id={agent_id} files={files} />} */}
</div>
</div>
);
}

View file

@ -1,70 +0,0 @@
import { AgentCapabilities } from 'librechat-data-provider';
import { useFormContext, Controller } from 'react-hook-form';
import type { AgentForm } from '~/common';
import {
Checkbox,
HoverCard,
HoverCardContent,
HoverCardPortal,
HoverCardTrigger,
} from '~/components/ui';
import { CircleHelpIcon } from '~/components/svg';
import { useLocalize } from '~/hooks';
import { ESide } from '~/common';
export default function Code() {
const localize = useLocalize();
const methods = useFormContext<AgentForm>();
const { control, setValue, getValues } = methods;
return (
<>
<HoverCard openDelay={50}>
<div className="flex items-center">
<Controller
name={AgentCapabilities.execute_code}
control={control}
render={({ field }) => (
<Checkbox
{...field}
checked={field.value}
onCheckedChange={field.onChange}
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
value={field.value.toString()}
/>
)}
/>
<button
type="button"
className="flex items-center space-x-2"
onClick={() =>
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
setValue(AgentCapabilities.execute_code, !getValues(AgentCapabilities.execute_code), {
shouldDirty: true,
})
}
>
<label
className="form-check-label text-token-text-primary w-full cursor-pointer"
htmlFor={AgentCapabilities.execute_code}
>
{localize('com_agents_execute_code')}
</label>
<HoverCardTrigger>
<CircleHelpIcon className="h-5 w-5 text-gray-500" />
</HoverCardTrigger>
</button>
<HoverCardPortal>
<HoverCardContent side={ESide.Top} className="w-80">
<div className="space-y-2">
<p className="text-sm text-text-secondary">
{/* // TODO: add a Code Interpreter description */}
</p>
</div>
</HoverCardContent>
</HoverCardPortal>
</div>
</HoverCard>
</>
);
}

View file

@ -0,0 +1,151 @@
import { useState } from 'react';
import { KeyRoundIcon } from 'lucide-react';
import { AuthType, AgentCapabilities } from 'librechat-data-provider';
import { useFormContext, Controller, useForm, useWatch } from 'react-hook-form';
import type { AgentForm } from '~/common';
import {
Input,
OGDialog,
Checkbox,
HoverCard,
HoverCardContent,
HoverCardPortal,
HoverCardTrigger,
Button,
} from '~/components/ui';
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
import { useLocalize, useAuthCodeTool } from '~/hooks';
import { CircleHelpIcon } from '~/components/svg';
import { ESide } from '~/common';
type ApiKeyFormData = {
apiKey: string;
authType?: string | AuthType;
};
export default function Action({ authType = '', isToolAuthenticated = false }) {
const localize = useLocalize();
const methods = useFormContext<AgentForm>();
const { control, setValue, getValues } = methods;
const [isDialogOpen, setIsDialogOpen] = useState(false);
const runCodeIsEnabled = useWatch({ control, name: AgentCapabilities.execute_code });
const { installTool, removeTool } = useAuthCodeTool({ isEntityTool: true });
const { reset, register, handleSubmit } = useForm<ApiKeyFormData>();
const isUserProvided = authType === AuthType.USER_PROVIDED;
const handleCheckboxChange = (checked: boolean) => {
if (isToolAuthenticated) {
setValue(AgentCapabilities.execute_code, checked, { shouldDirty: true });
} else if (runCodeIsEnabled) {
setValue(AgentCapabilities.execute_code, false, { shouldDirty: true });
} else {
setIsDialogOpen(true);
}
};
const onSubmit = (data: { apiKey: string }) => {
reset();
installTool(data.apiKey);
setIsDialogOpen(false);
};
const handleRevokeApiKey = () => {
reset();
removeTool();
setIsDialogOpen(false);
};
return (
<>
<HoverCard openDelay={50}>
<div className="flex items-center">
<Controller
name={AgentCapabilities.execute_code}
control={control}
render={({ field }) => (
<Checkbox
{...field}
checked={runCodeIsEnabled ? runCodeIsEnabled : isToolAuthenticated && field.value}
onCheckedChange={handleCheckboxChange}
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
value={field.value.toString()}
disabled={runCodeIsEnabled ? false : !isToolAuthenticated}
/>
)}
/>
<button
type="button"
className="flex items-center space-x-2"
onClick={() => {
const value = !getValues(AgentCapabilities.execute_code);
handleCheckboxChange(value);
}}
>
<label
className="form-check-label text-token-text-primary w-full cursor-pointer"
htmlFor={AgentCapabilities.execute_code}
>
{localize('com_agents_execute_code')}
</label>
</button>
<div className="ml-2 flex gap-2">
{isUserProvided && (isToolAuthenticated || runCodeIsEnabled) && (
<button type="button" onClick={() => setIsDialogOpen(true)}>
<KeyRoundIcon className="h-5 w-5 text-text-primary" />
</button>
)}
<HoverCardTrigger>
<CircleHelpIcon className="h-5 w-5 text-gray-500" />
</HoverCardTrigger>
</div>
<HoverCardPortal>
<HoverCardContent side={ESide.Top} className="w-80">
<div className="space-y-2">
<p className="text-sm text-text-secondary">
{/* // TODO: add a Code Interpreter description */}
</p>
</div>
</HoverCardContent>
</HoverCardPortal>
</div>
</HoverCard>
<OGDialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<OGDialogTemplate
className="w-11/12 sm:w-1/4"
title={localize('com_agents_tool_not_authenticated')}
main={
<form onSubmit={handleSubmit(onSubmit)}>
<Input
type="password"
placeholder="Enter API Key"
autoComplete="one-time-code"
readOnly={true}
onFocus={(e) => (e.target.readOnly = false)}
{...register('apiKey', { required: true })}
/>
</form>
}
selection={{
selectHandler: handleSubmit(onSubmit),
selectClasses: 'bg-green-500 hover:bg-green-600 text-white',
selectText: localize('com_ui_save'),
}}
buttons={
isUserProvided &&
isToolAuthenticated && (
<Button
onClick={handleRevokeApiKey}
className="bg-destructive text-white transition-all duration-200 hover:bg-destructive/80"
>
{localize('com_ui_revoke')}
</Button>
)
}
showCancelButton={true}
/>
</OGDialog>
</>
);
}

View file

@ -1,21 +1,22 @@
import { useState, useRef, useEffect } from 'react';
import { useState, useRef } from 'react';
import { useFormContext } from 'react-hook-form';
import {
EToolResources,
EModelEndpoint,
mergeFileConfig,
AgentCapabilities,
fileConfig as defaultFileConfig,
} from 'librechat-data-provider';
import type { EndpointFileConfig } from 'librechat-data-provider';
import type { ExtendedFile } from '~/common';
import type { ExtendedFile, AgentForm } from '~/common';
import { useFileHandling, useLocalize, useLazyEffect } from '~/hooks';
import FileRow from '~/components/Chat/Input/Files/FileRow';
import { useGetFileConfig } from '~/data-provider';
import { useFileHandling } from '~/hooks/Files';
import useLocalize from '~/hooks/useLocalize';
import { useChatContext } from '~/Providers';
const tool_resource = EToolResources.code_interpreter;
const tool_resource = EToolResources.execute_code;
export default function CodeFiles({
export default function Files({
agent_id,
files: _files,
}: {
@ -24,22 +25,29 @@ export default function CodeFiles({
}) {
const localize = useLocalize();
const { setFilesLoading } = useChatContext();
const { watch } = useFormContext<AgentForm>();
const fileInputRef = useRef<HTMLInputElement>(null);
const [files, setFiles] = useState<Map<string, ExtendedFile>>(new Map());
const { data: fileConfig = defaultFileConfig } = useGetFileConfig({
select: (data) => mergeFileConfig(data),
});
const { handleFileChange } = useFileHandling({
const { abortUpload, handleFileChange } = useFileHandling({
fileSetter: setFiles,
overrideEndpoint: EModelEndpoint.agents,
additionalMetadata: { agent_id, tool_resource },
fileSetter: setFiles,
});
useEffect(() => {
if (_files) {
setFiles(new Map(_files));
}
}, [_files]);
useLazyEffect(
() => {
if (_files) {
setFiles(new Map(_files));
}
},
[_files],
750,
);
const codeChecked = watch(AgentCapabilities.execute_code);
const endpointFileConfig = fileConfig.endpoints[EModelEndpoint.agents] as
| EndpointFileConfig
@ -68,6 +76,7 @@ export default function CodeFiles({
files={files}
setFiles={setFiles}
agent_id={agent_id}
abortUpload={abortUpload}
tool_resource={tool_resource}
setFilesLoading={setFilesLoading}
Wrapper={({ children }) => <div className="flex flex-wrap gap-2">{children}</div>}
@ -75,7 +84,7 @@ export default function CodeFiles({
<div>
<button
type="button"
disabled={!agent_id}
disabled={!agent_id || codeChecked === false}
className="btn btn-neutral border-token-border-light relative h-8 w-full rounded-lg font-medium"
onClick={handleButtonClick}
>
@ -86,7 +95,7 @@ export default function CodeFiles({
style={{ display: 'none' }}
tabIndex={-1}
ref={fileInputRef}
disabled={!agent_id}
disabled={!agent_id || codeChecked === false}
onChange={handleFileChange}
/>
{localize('com_ui_upload_files')}

View file

@ -0,0 +1,33 @@
import { Tools } from 'librechat-data-provider';
import type { ExtendedFile } from '~/common';
import { useVerifyAgentToolAuth } from '~/data-provider';
import { useLocalize } from '~/hooks';
import Action from './Action';
import Files from './Files';
export default function CodeForm({
agent_id,
files,
}: {
agent_id: string;
files?: [string, ExtendedFile][];
}) {
const localize = useLocalize();
const { data } = useVerifyAgentToolAuth({ toolId: Tools.execute_code });
return (
<div className="mb-4">
<div className="mb-1.5 flex items-center">
<span>
<label className="text-token-text-primary block font-medium">
{localize('com_assistants_capabilities')}
</label>
</span>
</div>
<div className="flex flex-col items-start gap-2">
<Action authType={data?.message} isToolAuthenticated={data?.authenticated} />
<Files agent_id={agent_id} files={files} />
</div>
</div>
);
}

View file

@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from 'react';
import { useState, useRef } from 'react';
import { useFormContext } from 'react-hook-form';
import {
EModelEndpoint,
@ -9,12 +9,11 @@ import {
fileConfig as defaultFileConfig,
} from 'librechat-data-provider';
import type { ExtendedFile, AgentForm } from '~/common';
import { useFileHandling, useLocalize, useLazyEffect } from '~/hooks';
import FileRow from '~/components/Chat/Input/Files/FileRow';
import FileSearchCheckbox from './FileSearchCheckbox';
import { useGetFileConfig } from '~/data-provider';
import { AttachmentIcon } from '~/components/svg';
import { useFileHandling } from '~/hooks/Files';
import useLocalize from '~/hooks/useLocalize';
import { useChatContext } from '~/Providers';
export default function FileSearch({
@ -40,18 +39,22 @@ export default function FileSearch({
fileSetter: setFiles,
});
useEffect(() => {
if (_files) {
setFiles(new Map(_files));
}
}, [_files]);
useLazyEffect(
() => {
if (_files) {
setFiles(new Map(_files));
}
},
[_files],
750,
);
const fileSearchChecked = watch(AgentCapabilities.file_search);
const endpointFileConfig = fileConfig.endpoints[EModelEndpoint.agents];
const disabled = endpointFileConfig.disabled ?? false;
const isUploadDisabled = endpointFileConfig.disabled ?? false;
if (disabled === true) {
if (isUploadDisabled) {
return null;
}

View file

@ -12,7 +12,7 @@ import { cn } from '~/utils';
export default function AssistantTool({
tool,
allTools,
assistant_id,
assistant_id = '',
}: {
tool: string;
allTools: TPlugin[];
@ -28,7 +28,7 @@ export default function AssistantTool({
const removeTool = (tool: string) => {
if (tool) {
updateUserPlugins.mutate(
{ pluginKey: tool, action: 'uninstall', auth: null, isAssistantTool: true },
{ pluginKey: tool, action: 'uninstall', auth: null, isEntityTool: true },
{
onError: (error: unknown) => {
showToast({ message: `Error while deleting the tool: ${error}`, status: 'error' });
@ -78,7 +78,7 @@ export default function AssistantTool({
<OGDialogTrigger asChild>
<button
type="button"
className="transition-colors flex h-9 w-9 min-w-9 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
className="flex h-9 w-9 min-w-9 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
>
<TrashIcon />
</button>

View file

@ -90,7 +90,7 @@ function ToolSelectDialog({
const onRemoveTool = (tool: string) => {
setShowPluginAuthForm(false);
updateUserPlugins.mutate(
{ pluginKey: tool, action: 'uninstall', auth: null, isAssistantTool: true },
{ pluginKey: tool, action: 'uninstall', auth: null, isEntityTool: true },
{
onError: (error: unknown) => {
handleInstallError(error as TError);
@ -199,7 +199,7 @@ function ToolSelectDialog({
<PluginAuthForm
plugin={selectedPlugin}
onSubmit={(installActionData: TPluginAction) => handleInstall(installActionData)}
isAssistantTool={true}
isEntityTool={true}
/>
</div>
)}

View file

@ -10,13 +10,6 @@ import {
import type * as t from 'librechat-data-provider';
import type { UseMutationResult } from '@tanstack/react-query';
export type TGenTitleMutation = UseMutationResult<
t.TGenTitleResponse,
unknown,
t.TGenTitleRequest,
unknown
>;
export const useUploadFileMutation = (
_options?: t.UploadMutationOptions,
signal?: AbortSignal | null,
@ -152,9 +145,9 @@ export const useDeleteFilesMutation = (
return useMutation([MutationKeys.fileDelete], {
mutationFn: (body: t.DeleteFilesBody) => dataService.deleteFiles(body),
...options,
onSuccess: (data, ...args) => {
onSuccess: (data, vars, context) => {
queryClient.setQueryData<t.TFile[] | undefined>([QueryKeys.files], (cachefiles) => {
const { files: filesDeleted } = args[0];
const { files: filesDeleted } = vars;
const fileMap = filesDeleted.reduce((acc, file) => {
acc.set(file.file_id, file);
@ -163,7 +156,10 @@ export const useDeleteFilesMutation = (
return (cachefiles ?? []).filter((file) => !fileMap.has(file.file_id));
});
onSuccess?.(data, ...args);
onSuccess?.(data, vars, context);
if (vars.agent_id != null && vars.agent_id) {
queryClient.refetchQueries([QueryKeys.agent, vars.agent_id]);
}
},
});
};

View file

@ -0,0 +1,2 @@
export * from './queries';
// export * from './mutations';

View file

@ -0,0 +1,20 @@
import { QueryKeys, dataService } from 'librechat-data-provider';
import { useQuery } from '@tanstack/react-query';
import type { QueryObserverResult, UseQueryOptions } from '@tanstack/react-query';
import type t from 'librechat-data-provider';
export const useVerifyAgentToolAuth = (
params: t.VerifyToolAuthParams,
config?: UseQueryOptions<t.VerifyToolAuthResponse>,
): QueryObserverResult<t.VerifyToolAuthResponse> => {
return useQuery<t.VerifyToolAuthResponse>(
[QueryKeys.toolAuth, params.toolId],
() => dataService.getVerifyAgentToolAuth(params),
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
},
);
};

View file

@ -1,4 +1,5 @@
export * from './Files';
export * from './Tools';
export * from './connection';
export * from './mutations';
export * from './prompts';

View file

@ -0,0 +1 @@
export * from './useLazyEffect';

View file

@ -0,0 +1,18 @@
/* eslint-disable react-hooks/exhaustive-deps */
// https://stackoverflow.com/a/67504622/51500
import { DependencyList, EffectCallback, useCallback, useEffect, useRef } from 'react';
import debounce from 'lodash/debounce';
export function useLazyEffect(effect: EffectCallback, deps: DependencyList = [], wait = 300) {
const cleanUp = useRef<void | (() => void)>();
const effectRef = useRef<EffectCallback>();
effectRef.current = useCallback(effect, deps);
const lazyEffect = useCallback(
debounce(() => (cleanUp.current = effectRef.current?.()), wait),
[],
);
useEffect(lazyEffect, deps);
useEffect(() => {
return () => (cleanUp.current instanceof Function ? cleanUp.current() : undefined);
}, []);
}

View file

@ -1,2 +1,3 @@
export { default as useAuthCodeTool } from './useAuthCodeTool';
export { default as usePluginInstall } from './usePluginInstall';
export { default as usePluginDialogHelpers } from './usePluginDialogHelpers';

View file

@ -0,0 +1,53 @@
import { useCallback } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { AuthType, Tools, QueryKeys } from 'librechat-data-provider';
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
// import { useToastContext } from '~/Providers';
const useAuthCodeTool = (options?: { isEntityTool: boolean }) => {
// const { showToast } = useToastContext();
const queryClient = useQueryClient();
const isEntityTool = options?.isEntityTool ?? true;
const updateUserPlugins = useUpdateUserPluginsMutation({
onMutate: (vars) => {
queryClient.setQueryData([QueryKeys.toolAuth, Tools.execute_code], () => ({
authenticated: vars.action === 'install',
message: AuthType.USER_PROVIDED,
}));
},
onSuccess: () => {
queryClient.invalidateQueries([QueryKeys.toolAuth, Tools.execute_code]);
},
onError: () => {
queryClient.invalidateQueries([QueryKeys.toolAuth, Tools.execute_code]);
},
});
const installTool = useCallback(
(apiKey: string) => {
updateUserPlugins.mutate({
pluginKey: Tools.execute_code,
action: 'install',
auth: { LIBRECHAT_CODE_API_KEY: apiKey },
isEntityTool,
});
},
[updateUserPlugins, isEntityTool],
);
const removeTool = useCallback(() => {
updateUserPlugins.mutate({
pluginKey: Tools.execute_code,
action: 'uninstall',
auth: { LIBRECHAT_CODE_API_KEY: null },
isEntityTool,
});
}, [updateUserPlugins, isEntityTool]);
return {
removeTool,
installTool,
};
};
export default useAuthCodeTool;

View file

@ -6,6 +6,7 @@ export * from './Config';
export * from './Conversations';
export * from './Nav';
export * from './Files';
export * from './Generic';
export * from './Input';
export * from './Messages';
export * from './Plugins';