| import { useEffect } from 'react'; | |
| import { useNavigate } from 'react-router-dom'; | |
| import { Button, TextareaAutosize, Input } from '@librechat/client'; | |
| import { useForm, Controller, FormProvider } from 'react-hook-form'; | |
| import { LocalStorageKeys, PermissionTypes, Permissions } from 'librechat-data-provider'; | |
| import CategorySelector from '~/components/Prompts/Groups/CategorySelector'; | |
| import VariablesDropdown from '~/components/Prompts/VariablesDropdown'; | |
| import PromptVariables from '~/components/Prompts/PromptVariables'; | |
| import Description from '~/components/Prompts/Description'; | |
| import { usePromptGroupsContext } from '~/Providers'; | |
| 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: hasUseAccess } = usePromptGroupsContext(); | |
| const hasCreateAccess = useHasAccess({ | |
| permissionType: PermissionTypes.PROMPTS, | |
| permission: Permissions.CREATE, | |
| }); | |
| const hasAccess = hasUseAccess && hasCreateAccess; | |
| 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-border-medium p-2 text-2xl text-text-primary placeholder:text-text-tertiary dark:placeholder:text-text-secondary" | |
| placeholder={`${localize('com_ui_prompt_name')}*`} | |
| tabIndex={0} | |
| /> | |
| <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 /> | |
| </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-border-medium py-2 pl-4 pr-1 text-base font-semibold dark:text-gray-200"> | |
| <span>{localize('com_ui_prompt_text')}*</span> | |
| <VariablesDropdown fieldName="prompt" className="mr-2" /> | |
| </h2> | |
| <div className="min-h-32 rounded-b-lg border border-border-medium p-4 transition-all duration-150"> | |
| <Controller | |
| name="prompt" | |
| control={control} | |
| rules={{ required: localize('com_ui_prompt_text_required') }} | |
| render={({ field }) => ( | |
| <div> | |
| <TextareaAutosize | |
| {...field} | |
| className="w-full rounded border border-border-medium px-2 py-1 focus:outline-none dark:bg-transparent dark:text-gray-200" | |
| minRows={6} | |
| tabIndex={0} | |
| aria-label={localize('com_ui_prompt_input_field')} | |
| /> | |
| <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={0} | |
| /> | |
| <Command onValueChange={(value) => methods.setValue('command', value)} tabIndex={0} /> | |
| <div className="mt-4 flex justify-end"> | |
| <Button | |
| aria-label={localize('com_ui_create_prompt')} | |
| tabIndex={0} | |
| type="submit" | |
| disabled={!isDirty || isSubmitting || !isValid} | |
| > | |
| {localize('com_ui_create_prompt')} | |
| </Button> | |
| </div> | |
| </div> | |
| </form> | |
| </FormProvider> | |
| ); | |
| }; | |
| export default CreatePromptForm; | |