mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
* chore: add i18n localization comment for AlwaysMakeProd component * feat: enhance accessibility by adding aria-label and aria-labelledby to Switch component * feat: add aria-labels for accessibility in Agent and Assistant avatar buttons * fix: add switch aria-labels for accessibility in various components * feat: add aria-labels and localization keys for accessibility in DataTable, DataTableColumnHeader, and OGDialogTemplate components * chore: refactor out nested ternary * feat: add aria-label to DataTable filter button for My Files modal * feat: add aria-labels for Buttons and localization strings * feat: add aria-labels to Checkboxes in Agent Builder * feat: enhance accessibility by adding aria-label and aria-labelledby to Checkbox component * feat: add aria-label to FileSearchCheckbox in Agent Builder * feat: add aria-label to Prompts text input area * feat: enhance accessibility by adding aria-label and aria-labelledby to TextAreaAutosize component * feat: remove improper role: "list" prop from List in Conversations.tsx to enhance accessibility and stop aria rules conflicting within react-virtualized component * feat: enhance accessibility by allowing tab navigation and adding ring highlights for conversation title editing accept/reject buttons * feat: add aria-label to Copy Link button in the conversation share modal * feat: add title to QR code svg in conversation share modal to describe the image content * feat: enhance accessibility by making Agent Avatar upload keyboard navigable and round out highlight border on focus * feat: enhance accessibility by adding aria attributes around alerting users with screen readers to invalid email address inputs in the Agent Builder * feat: add aria-labels to buttons in Advanced panel of Agent Builder * feat: enhance accessibility by making FileUpload and Clear All buttons in PresetItems keyboard navigable * feat: enchance accessiblity by indexing view and delete button aria-labels in shared links management modal to their specific chat titles * feat: add border highlighting on focus for AnimatedSearchInput * feat: add category description to aria-labels for prompts in ListCard * feat: add proper scoping to rows and columns in table headers * feat: add localized aria-labelling to EditTextPart's TextAreaAutosize component and base dynamic paramters panel components and their supporting translation keys * feat: add localized aria-labels and aria-labelledBy to Checkbox components without them * feat: add localized aria-labeledBy for endpoint settings Sliders * feat: add localized aria-labels for TextareaAutosize components * chore: remove unused i18n string * feat: add localized aria-label for BookmarkForm Checkbox * fix: add stopPropagation onKeyDown for Preview and Edit menu items in prompts that was causing the prompts to inadvertently be sent when triggered with keyboard navigation when Auto-send Prompts was toggled on * fix: switch TableCell to TableHead for title cells according to harvard issue #789 * fix: add more descriptive localization key for file filter button in DataTable * chore: remove self-explanatory code comment from RenameForm * fix: remove stray bg-yellow highlight that was left in during debugging * fix: add aria-label to model configurator panel back button * fix: undo incorrect hoist of tool name split for aria-label and span in MCPInput --------- Co-authored-by: Danny Avila <danny@librechat.ai>
190 lines
6.4 KiB
TypeScript
190 lines
6.4 KiB
TypeScript
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;
|