LibreChat/client/src/components/Prompts/Groups/CreatePromptForm.tsx
Danny Avila 83619de158
🗨️ feat: Prompt Slash Commands (#3219)
* chore: Update prompt description placeholder text

* fix: promptsPathPattern to not include new

* feat: command input and styling change for prompt views

* fix: intended validation

* feat: prompts slash command

* chore: localizations and fix add command during creation

* refactor(PromptsCommand): better label

* feat: update `allPrompGroups` cache on all promptGroups mutations

* refactor: ensure assistants builder is first within sidepanel

* refactor: allow defining emailVerified via create-user script
2024-06-27 17:34:48 -04:00

185 lines
6 KiB
TypeScript

import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useForm, Controller, FormProvider } from 'react-hook-form';
import { LocalStorageKeys, PermissionTypes, Permissions } from 'librechat-data-provider';
import CategorySelector from '~/components/Prompts/Groups/CategorySelector';
import PromptVariables from '~/components/Prompts/PromptVariables';
import { Button, TextareaAutosize, Input } from '~/components/ui';
import Description from '~/components/Prompts/Description';
import { useLocalize, useHasAccess } from '~/hooks';
import Command from '~/components/Prompts/Command';
import { useCreatePrompt } from '~/data-provider';
import { cn } from '~/utils';
type CreateFormValues = {
name: string;
prompt: string;
type: 'text' | 'chat';
category: string;
oneliner?: string;
command?: string;
};
const defaultPrompt: CreateFormValues = {
name: '',
prompt: '',
type: 'text',
category: '',
oneliner: undefined,
command: undefined,
};
const CreatePromptForm = ({
defaultValues = defaultPrompt,
}: {
defaultValues?: CreateFormValues;
}) => {
const localize = useLocalize();
const navigate = useNavigate();
const hasAccess = useHasAccess({
permissionType: PermissionTypes.PROMPTS,
permission: Permissions.CREATE,
});
useEffect(() => {
let timeoutId: ReturnType<typeof setTimeout>;
if (!hasAccess) {
timeoutId = setTimeout(() => {
navigate('/c/new');
}, 1000);
}
return () => {
clearTimeout(timeoutId);
};
}, [hasAccess, navigate]);
const methods = useForm({
defaultValues: {
...defaultValues,
category: localStorage.getItem(LocalStorageKeys.LAST_PROMPT_CATEGORY) ?? '',
},
});
const {
watch,
control,
handleSubmit,
formState: { isDirty, isSubmitting, errors, isValid },
} = methods;
const createPromptMutation = useCreatePrompt({
onSuccess: (response) => {
navigate(`/d/prompts/${response.prompt.groupId}`, { replace: true });
},
});
const promptText = watch('prompt');
const onSubmit = (data: CreateFormValues) => {
const { name, category, oneliner, command, ...rest } = data;
const groupData = { name, category } as Pick<
CreateFormValues,
'name' | 'category' | 'oneliner' | 'command'
>;
if ((oneliner?.length || 0) > 0) {
groupData.oneliner = oneliner;
}
if ((command?.length || 0) > 0) {
groupData.command = command;
}
createPromptMutation.mutate({
prompt: rest,
group: groupData,
});
};
if (!hasAccess) {
return null;
}
return (
<FormProvider {...methods}>
<form onSubmit={handleSubmit(onSubmit)} className="w-full px-4 py-2">
<div className="mb-1 flex flex-col items-center justify-between font-bold sm:text-xl md:mb-0 md:text-2xl">
<div className="flex w-full flex-col items-center justify-between sm:flex-row">
<Controller
name="name"
control={control}
rules={{ required: localize('com_ui_prompt_name_required') }}
render={({ field }) => (
<div className="mb-1 flex items-center md:mb-0">
<Input
{...field}
type="text"
className="mr-2 w-full border border-gray-300 p-2 text-2xl dark:border-gray-600"
placeholder={`${localize('com_ui_prompt_name')}*`}
tabIndex={1}
autoFocus={true}
/>
<div
className={cn(
'mt-1 w-56 text-sm text-red-500',
errors.name ? 'visible h-auto' : 'invisible h-0',
)}
>
{errors.name ? errors.name.message : ' '}
</div>
</div>
)}
/>
<CategorySelector tabIndex={5} />
</div>
</div>
<div className="flex w-full flex-col gap-4 md:mt-[1.075rem]">
<div>
<h2 className="flex items-center justify-between rounded-t-lg border border-gray-300 py-2 pl-4 pr-1 text-base font-semibold dark:border-gray-600 dark:text-gray-200">
{localize('com_ui_prompt_text')}*
</h2>
<div className="min-h-32 rounded-b-lg border border-gray-300 p-4 transition-all duration-150 dark:border-gray-600">
<Controller
name="prompt"
control={control}
rules={{ required: localize('com_ui_prompt_text_required') }}
render={({ field }) => (
<div>
<TextareaAutosize
{...field}
className="w-full rounded border border-gray-300 px-2 py-1 focus:outline-none dark:border-gray-600 dark:bg-transparent dark:text-gray-200"
minRows={6}
tabIndex={2}
/>
<div
className={`mt-1 text-sm text-red-500 ${
errors.prompt ? 'visible h-auto' : 'invisible h-0'
}`}
>
{errors.prompt ? errors.prompt.message : ' '}
</div>
</div>
)}
/>
</div>
</div>
<PromptVariables promptText={promptText} />
<Description
onValueChange={(value) => methods.setValue('oneliner', value)}
tabIndex={3}
/>
<Command onValueChange={(value) => methods.setValue('command', value)} tabIndex={4} />
<div className="mt-4 flex justify-end">
<Button
tabIndex={6}
type="submit"
variant="default"
disabled={!isDirty || isSubmitting || !isValid}
>
{localize('com_ui_create_prompt')}
</Button>
</div>
</div>
</form>
</FormProvider>
);
};
export default CreatePromptForm;