🚧 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

@ -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

@ -0,0 +1,108 @@
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, AgentForm } from '~/common';
import { useFileHandling, useLocalize, useLazyEffect } from '~/hooks';
import FileRow from '~/components/Chat/Input/Files/FileRow';
import { useGetFileConfig } from '~/data-provider';
import { useChatContext } from '~/Providers';
const tool_resource = EToolResources.execute_code;
export default function Files({
agent_id,
files: _files,
}: {
agent_id: string;
files?: [string, ExtendedFile][];
}) {
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 { abortUpload, handleFileChange } = useFileHandling({
fileSetter: setFiles,
overrideEndpoint: EModelEndpoint.agents,
additionalMetadata: { agent_id, tool_resource },
});
useLazyEffect(
() => {
if (_files) {
setFiles(new Map(_files));
}
},
[_files],
750,
);
const codeChecked = watch(AgentCapabilities.execute_code);
const endpointFileConfig = fileConfig.endpoints[EModelEndpoint.agents] as
| EndpointFileConfig
| undefined;
const isUploadDisabled = endpointFileConfig?.disabled ?? false;
if (isUploadDisabled) {
return null;
}
const handleButtonClick = () => {
// necessary to reset the input
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
fileInputRef.current?.click();
};
return (
<div className="mb-2 w-full">
<div className="flex flex-col gap-4">
<div className="text-token-text-tertiary rounded-lg text-xs">
{localize('com_assistants_code_interpreter_files')}
</div>
<FileRow
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>}
/>
<div>
<button
type="button"
disabled={!agent_id || codeChecked === false}
className="btn btn-neutral border-token-border-light relative h-8 w-full rounded-lg font-medium"
onClick={handleButtonClick}
>
<div className="flex w-full items-center justify-center gap-2">
<input
multiple={true}
type="file"
style={{ display: 'none' }}
tabIndex={-1}
ref={fileInputRef}
disabled={!agent_id || codeChecked === false}
onChange={handleFileChange}
/>
{localize('com_ui_upload_files')}
</div>
</button>
</div>
</div>
</div>
);
}

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>
);
}