import { Rocket } from 'lucide-react'; import debounce from 'lodash/debounce'; import { useRecoilValue } from 'recoil'; import { useForm, FormProvider } from 'react-hook-form'; import { useEffect, useState, useMemo, useCallback, useRef } from 'react'; import { useNavigate, useParams, useOutletContext } from 'react-router-dom'; import { PermissionTypes, Permissions, SystemRoles } from 'librechat-data-provider'; import type { TCreatePrompt } from 'librechat-data-provider'; import { useGetPrompts, useCreatePrompt, useDeletePrompt, useGetPromptGroup, useUpdatePromptGroup, useMakePromptProduction, } from '~/data-provider'; import { useAuthContext, usePromptGroupsNav, useHasAccess } from '~/hooks'; import CategorySelector from './Groups/CategorySelector'; import AlwaysMakeProd from './Groups/AlwaysMakeProd'; import NoPromptGroup from './Groups/NoPromptGroup'; import { Button, Skeleton } from '~/components/ui'; import PromptVariables from './PromptVariables'; import PromptVersions from './PromptVersions'; import DeleteConfirm from './DeleteVersion'; import PromptDetails from './PromptDetails'; import { findPromptGroup } from '~/utils'; import PromptEditor from './PromptEditor'; import SkeletonForm from './SkeletonForm'; import Description from './Description'; import SharePrompt from './SharePrompt'; import PromptName from './PromptName'; import store from '~/store'; const { PromptsEditorMode, promptsEditorMode } = store; const PromptForm = () => { const params = useParams(); const navigate = useNavigate(); const { user } = useAuthContext(); const editorMode = useRecoilValue(promptsEditorMode); const alwaysMakeProd = useRecoilValue(store.alwaysMakeProd); const { data: group, isLoading: isLoadingGroup } = useGetPromptGroup(params.promptId || ''); const { data: prompts = [], isLoading: isLoadingPrompts } = useGetPrompts( { groupId: params.promptId ?? '' }, { enabled: !!params.promptId }, ); const prevIsEditingRef = useRef(false); const [isEditing, setIsEditing] = useState(false); const [initialLoad, setInitialLoad] = useState(true); const [selectionIndex, setSelectionIndex] = useState(0); const isOwner = useMemo(() => user?.id === group?.author, [user, group]); const selectedPrompt = useMemo(() => prompts[selectionIndex], [prompts, selectionIndex]); const hasShareAccess = useHasAccess({ permissionType: PermissionTypes.PROMPTS, permission: Permissions.SHARED_GLOBAL, }); const methods = useForm({ defaultValues: { prompt: '', promptName: group?.name || '', category: group?.category || '', }, }); const { handleSubmit, setValue, reset, watch } = methods; const promptText = watch('prompt'); const createPromptMutation = useCreatePrompt({ onMutate: (variables) => { reset( { prompt: variables.prompt.prompt, category: variables.group?.category || '', }, { keepDirtyValues: true }, ); }, onSuccess(data) { if (alwaysMakeProd && data.prompt._id && data.prompt.groupId) { makeProductionMutation.mutate( { id: data.prompt._id, groupId: data.prompt.groupId, productionPrompt: { prompt: data.prompt.prompt }, }, { onSuccess: () => setSelectionIndex(0), }, ); } reset({ prompt: data.prompt.prompt, promptName: data.group?.name || '', category: data.group?.category || '', }); setSelectionIndex(0); }, }); const updateGroupMutation = useUpdatePromptGroup(); const makeProductionMutation = useMakePromptProduction(); const deletePromptMutation = useDeletePrompt({ onSuccess: (response) => { if (response.promptGroup) { navigate('/d/prompts'); } else { setSelectionIndex(0); } }, }); const onSave = useCallback( (value: string) => { if (!value) { // TODO: show toast, cannot be empty. 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(() => { if (editorMode === PromptsEditorMode.SIMPLE) { const productionIndex = prompts.findIndex((prompt) => prompt._id === group?.productionId); setSelectionIndex(productionIndex !== -1 ? productionIndex : 0); } handleLoadingComplete(); }, [params.promptId, editorMode, group?.productionId, prompts, handleLoadingComplete]); useEffect(() => { setValue('prompt', selectedPrompt?.prompt || '', { shouldDirty: false }); setValue('category', group?.category || '', { shouldDirty: false }); }, [selectedPrompt, group?.category, setValue]); const debouncedUpdateOneliner = useCallback( debounce((oneliner: string) => { if (!group) { return console.warn('Group not found'); } updateGroupMutation.mutate({ id: group._id || '', payload: { oneliner } }); }, 950), [updateGroupMutation, group], ); const { groupsQuery } = useOutletContext>(); 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) { return null; } return (
onSave(data.prompt))}>
{isLoadingGroup ? ( ) : ( { if (!group) { return console.warn('Group not found'); } updateGroupMutation.mutate({ id: group._id || '', payload: { name: value } }); }} /> )}
updateGroupMutation.mutate({ id: group?._id || '', payload: { name: group?.name || '', category: value }, }) } /> {hasShareAccess && } {editorMode === PromptsEditorMode.ADVANCED && ( )} { deletePromptMutation.mutate({ _id: selectedPrompt?._id || '', groupId: group?._id || '', }); }} />
{editorMode === PromptsEditorMode.ADVANCED && (
)}
{/* Left Section */}
{isLoadingPrompts ? ( ) : ( <> )}
{/* Right Section */} {editorMode === PromptsEditorMode.ADVANCED && (
{isLoadingPrompts ? ( ) : ( !!prompts.length && ( ) )}
)}
); }; export default PromptForm;