import debounce from 'lodash/debounce'; import { useRecoilValue } from 'recoil'; import { Menu, Rocket } from 'lucide-react'; import { useForm, FormProvider } from 'react-hook-form'; import { useParams, useOutletContext } from 'react-router-dom'; import { useEffect, useState, useMemo, useCallback, useRef } from 'react'; import type { TCreatePrompt } from 'librechat-data-provider'; import { SystemRoles, PermissionTypes, Permissions } from 'librechat-data-provider'; import { useCreatePrompt, useGetPrompts, useGetPromptGroup, useUpdatePromptGroup, useMakePromptProduction, useDeletePrompt, } from '~/data-provider'; import { useAuthContext, usePromptGroupsNav, useHasAccess, useLocalize } from '~/hooks'; import CategorySelector from './Groups/CategorySelector'; import NoPromptGroup from './Groups/NoPromptGroup'; import { Button, Skeleton } from '~/components/ui'; import PromptVariables from './PromptVariables'; import { cn, findPromptGroup } from '~/utils'; import { useToastContext } from '~/Providers'; import PromptVersions from './PromptVersions'; import { PromptsEditorMode } from '~/common'; import DeleteConfirm from './DeleteVersion'; import PromptDetails from './PromptDetails'; import PromptEditor from './PromptEditor'; import SkeletonForm from './SkeletonForm'; import Description from './Description'; import SharePrompt from './SharePrompt'; import PromptName from './PromptName'; import Command from './Command'; import store from '~/store'; const PromptForm = () => { const params = useParams(); const localize = useLocalize(); const { user } = useAuthContext(); const alwaysMakeProd = useRecoilValue(store.alwaysMakeProd); const { showToast } = useToastContext(); const promptId = params.promptId || ''; const [selectionIndex, setSelectionIndex] = useState(0); const editorMode = useRecoilValue(store.promptsEditorMode); const prevIsEditingRef = useRef(false); const [isEditing, setIsEditing] = useState(false); const [initialLoad, setInitialLoad] = useState(true); const [showSidePanel, setShowSidePanel] = useState(false); const sidePanelWidth = '320px'; // Fetch group early so it is available for later hooks. const { data: group, isLoading: isLoadingGroup } = useGetPromptGroup(promptId); const { data: prompts = [], isLoading: isLoadingPrompts } = useGetPrompts( { groupId: promptId }, { enabled: !!promptId }, ); const isOwner = useMemo(() => (user && group ? user.id === group.author : false), [user, group]); const methods = useForm({ defaultValues: { prompt: '', promptName: group ? group.name : '', category: group ? group.category : '', }, }); const { handleSubmit, setValue, reset, watch } = methods; const promptText = watch('prompt'); const selectedPrompt = useMemo( () => (prompts.length > 0 ? prompts[selectionIndex] : undefined), [prompts, selectionIndex], ); const { groupsQuery } = useOutletContext>(); const hasShareAccess = useHasAccess({ permissionType: PermissionTypes.PROMPTS, permission: Permissions.SHARED_GLOBAL, }); const updateGroupMutation = useUpdatePromptGroup({ onError: () => { showToast({ status: 'error', message: localize('com_ui_prompt_update_error'), }); }, }); const makeProductionMutation = useMakePromptProduction(); const deletePromptMutation = useDeletePrompt(); const createPromptMutation = useCreatePrompt({ onMutate: (variables) => { reset( { prompt: variables.prompt.prompt, category: variables.group ? variables.group.category : '', }, { keepDirtyValues: true }, ); }, onSuccess(data) { if (alwaysMakeProd && data.prompt._id != null && data.prompt._id && data.prompt.groupId) { makeProductionMutation.mutate({ id: data.prompt._id, groupId: data.prompt.groupId, productionPrompt: { prompt: data.prompt.prompt }, }); } reset({ prompt: data.prompt.prompt, promptName: data.group ? data.group.name : '', category: data.group ? data.group.category : '', }); }, }); const onSave = useCallback( (value: string) => { if (!value) { // TODO: show toast, cannot be empty. return; } if (!selectedPrompt) { return; } const tempPrompt: TCreatePrompt = { prompt: { type: selectedPrompt.type ?? 'text', groupId: selectedPrompt.groupId ?? '', prompt: value, }, }; if (value === selectedPrompt.prompt) { return; } createPromptMutation.mutate(tempPrompt); }, [selectedPrompt, createPromptMutation], ); const handleLoadingComplete = useCallback(() => { if (isLoadingGroup || isLoadingPrompts) { return; } setInitialLoad(false); }, [isLoadingGroup, isLoadingPrompts]); useEffect(() => { if (prevIsEditingRef.current && !isEditing) { handleSubmit((data) => onSave(data.prompt))(); } prevIsEditingRef.current = isEditing; }, [isEditing, onSave, handleSubmit]); useEffect(() => { handleLoadingComplete(); }, [params.promptId, editorMode, group?.productionId, prompts, handleLoadingComplete]); useEffect(() => { setValue('prompt', selectedPrompt ? selectedPrompt.prompt : '', { shouldDirty: false }); setValue('category', group ? group.category : '', { shouldDirty: false }); }, [selectedPrompt, group, setValue]); useEffect(() => { const handleResize = () => { if (window.matchMedia('(min-width: 1022px)').matches) { setShowSidePanel(false); } }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); const debouncedUpdateOneliner = useCallback( debounce((oneliner: string) => { if (!group || !group._id) { return console.warn('Group not found'); } updateGroupMutation.mutate({ id: group._id, payload: { oneliner } }); }, 950), [updateGroupMutation, group], ); const debouncedUpdateCommand = useCallback( debounce((command: string) => { if (!group || !group._id) { return console.warn('Group not found'); } updateGroupMutation.mutate({ id: group._id, payload: { command } }); }, 950), [updateGroupMutation, group], ); if (initialLoad) { return ; } if (!isOwner && groupsQuery.data && user?.role !== SystemRoles.ADMIN) { const fetchedPrompt = findPromptGroup( groupsQuery.data, (group) => group._id === params.promptId, ); if (!fetchedPrompt) { return ; } return ; } if (!group || group._id == null) { return null; } const groupId = group._id; const groupName = group.name; const groupCategory = group.category; const RightPanel = () => (
updateGroupMutation.mutate({ id: groupId, payload: { name: groupName, category: value }, }) } />
{hasShareAccess && } {editorMode === PromptsEditorMode.ADVANCED && ( )} { if (!selectedPrompt || !selectedPrompt._id) { console.warn('No prompt is selected or prompt _id is missing'); return; } deletePromptMutation.mutate({ _id: selectedPrompt._id, groupId, }); }} />
{editorMode === PromptsEditorMode.ADVANCED && (isLoadingPrompts ? Array.from({ length: 6 }).map((_, index: number) => (
)) : prompts.length > 0 && ( ))}
); return (
onSave(data.prompt))}>
{isLoadingGroup ? ( ) : ( <> { if (!group._id) { return; } updateGroupMutation.mutate({ id: group._id, payload: { name: value } }); }} />
{editorMode === PromptsEditorMode.SIMPLE && }
)}
{isLoadingPrompts ? ( ) : (
)}
{editorMode === PromptsEditorMode.ADVANCED && (
)}
); }; export default PromptForm;