mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 10:20:15 +01:00
🚧 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:
parent
1909efd6ba
commit
95011ce349
58 changed files with 1418 additions and 1002 deletions
|
|
@ -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[];
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
|
|
|
|||
|
|
@ -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' });
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
151
client/src/components/SidePanel/Agents/Code/Action.tsx
Normal file
151
client/src/components/SidePanel/Agents/Code/Action.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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')}
|
||||
33
client/src/components/SidePanel/Agents/Code/Form.tsx
Normal file
33
client/src/components/SidePanel/Agents/Code/Form.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
2
client/src/data-provider/Tools/index.ts
Normal file
2
client/src/data-provider/Tools/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from './queries';
|
||||
// export * from './mutations';
|
||||
20
client/src/data-provider/Tools/queries.ts
Normal file
20
client/src/data-provider/Tools/queries.ts
Normal 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,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
export * from './Files';
|
||||
export * from './Tools';
|
||||
export * from './connection';
|
||||
export * from './mutations';
|
||||
export * from './prompts';
|
||||
|
|
|
|||
1
client/src/hooks/Generic/index.ts
Normal file
1
client/src/hooks/Generic/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from './useLazyEffect';
|
||||
18
client/src/hooks/Generic/useLazyEffect.ts
Normal file
18
client/src/hooks/Generic/useLazyEffect.ts
Normal 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);
|
||||
}, []);
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
export { default as useAuthCodeTool } from './useAuthCodeTool';
|
||||
export { default as usePluginInstall } from './usePluginInstall';
|
||||
export { default as usePluginDialogHelpers } from './usePluginDialogHelpers';
|
||||
|
|
|
|||
53
client/src/hooks/Plugins/useAuthCodeTool.ts
Normal file
53
client/src/hooks/Plugins/useAuthCodeTool.ts
Normal 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;
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue