| | import filenamify from 'filenamify'; |
| | import exportFromJSON from 'export-from-json'; |
| | import { useToastContext } from '@librechat/client'; |
| | import { QueryKeys } from 'librechat-data-provider'; |
| | import { useCallback, useEffect, useRef } from 'react'; |
| | import { useQueryClient } from '@tanstack/react-query'; |
| | import { useRecoilState, useSetRecoilState, useRecoilValue } from 'recoil'; |
| | import { useCreatePresetMutation, useGetModelsQuery } from 'librechat-data-provider/react-query'; |
| | import type { TPreset, TEndpointsConfig } from 'librechat-data-provider'; |
| | import { |
| | useUpdatePresetMutation, |
| | useDeletePresetMutation, |
| | useGetPresetsQuery, |
| | } from '~/data-provider'; |
| | import { cleanupPreset, removeUnavailableTools, getConvoSwitchLogic } from '~/utils'; |
| | import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo'; |
| | import { useAuthContext } from '~/hooks/AuthContext'; |
| | import { NotificationSeverity } from '~/common'; |
| | import useNewConvo from '~/hooks/useNewConvo'; |
| | import { useChatContext } from '~/Providers'; |
| | import { useLocalize } from '~/hooks'; |
| | import store from '~/store'; |
| |
|
| | export default function usePresets() { |
| | const localize = useLocalize(); |
| | const hasLoaded = useRef(false); |
| | const queryClient = useQueryClient(); |
| | const { showToast } = useToastContext(); |
| | const { user, isAuthenticated } = useAuthContext(); |
| |
|
| | const modularChat = useRecoilValue(store.modularChat); |
| | const availableTools = useRecoilValue(store.availableTools); |
| | const setPresetModalVisible = useSetRecoilState(store.presetModalVisible); |
| | const [_defaultPreset, setDefaultPreset] = useRecoilState(store.defaultPreset); |
| | const presetsQuery = useGetPresetsQuery({ enabled: !!user && isAuthenticated }); |
| | const { preset, conversation, index, setPreset } = useChatContext(); |
| | const { data: modelsData } = useGetModelsQuery(); |
| | const { newConversation } = useNewConvo(index); |
| |
|
| | useEffect(() => { |
| | if (modelsData?.initial) { |
| | return; |
| | } |
| |
|
| | const { data: presets } = presetsQuery; |
| | if (_defaultPreset || !presets || hasLoaded.current) { |
| | return; |
| | } |
| |
|
| | if (presets && presets.length > 0 && user && presets[0].user !== user.id) { |
| | presetsQuery.refetch(); |
| | return; |
| | } |
| |
|
| | const defaultPreset = presets.find((p) => p.defaultPreset); |
| | if (!defaultPreset) { |
| | hasLoaded.current = true; |
| | return; |
| | } |
| | setDefaultPreset(defaultPreset); |
| | if (!conversation?.conversationId || conversation.conversationId === 'new') { |
| | newConversation({ preset: defaultPreset, modelsData, disableParams: true }); |
| | } |
| | hasLoaded.current = true; |
| | |
| | |
| | }, [presetsQuery.data, user, modelsData]); |
| |
|
| | const setPresets = useCallback( |
| | (presets: TPreset[]) => { |
| | queryClient.setQueryData<TPreset[]>([QueryKeys.presets], presets); |
| | }, |
| | [queryClient], |
| | ); |
| |
|
| | const deletePresetsMutation = useDeletePresetMutation({ |
| | onMutate: (preset) => { |
| | if (!preset) { |
| | setPresets([]); |
| | return; |
| | } |
| | const previousPresets = presetsQuery.data ?? []; |
| | if (previousPresets) { |
| | setPresets(previousPresets.filter((p) => p.presetId !== preset.presetId)); |
| | } |
| | }, |
| | onSuccess: () => { |
| | queryClient.invalidateQueries([QueryKeys.presets]); |
| | }, |
| | onError: (error) => { |
| | queryClient.invalidateQueries([QueryKeys.presets]); |
| | console.error('Error deleting the preset:', error); |
| | showToast({ |
| | message: localize('com_endpoint_preset_delete_error'), |
| | severity: NotificationSeverity.ERROR, |
| | }); |
| | }, |
| | }); |
| | const createPresetMutation = useCreatePresetMutation(); |
| | const updatePreset = useUpdatePresetMutation({ |
| | onSuccess: (data, preset) => { |
| | const toastTitle = data.title ? `"${data.title}"` : localize('com_endpoint_preset_title'); |
| | let message = `${toastTitle} ${localize('com_ui_saved')}`; |
| | if (data.defaultPreset && data.presetId !== _defaultPreset?.presetId) { |
| | message = `${toastTitle} ${localize('com_endpoint_preset_default')}`; |
| | setDefaultPreset(data); |
| | newConversation({ preset: data, disableParams: true }); |
| | } else if (preset.defaultPreset === false) { |
| | setDefaultPreset(null); |
| | message = `${toastTitle} ${localize('com_endpoint_preset_default_removed')}`; |
| | } |
| | showToast({ |
| | message, |
| | }); |
| | queryClient.invalidateQueries([QueryKeys.presets]); |
| | }, |
| | onError: (error) => { |
| | console.error('Error updating the preset:', error); |
| | showToast({ |
| | message: localize('com_endpoint_preset_save_error'), |
| | severity: NotificationSeverity.ERROR, |
| | }); |
| | }, |
| | }); |
| |
|
| | const getDefaultConversation = useDefaultConvo(); |
| |
|
| | const importPreset = (jsonPreset: TPreset) => { |
| | createPresetMutation.mutate( |
| | { ...jsonPreset }, |
| | { |
| | onSuccess: () => { |
| | showToast({ |
| | message: localize('com_endpoint_preset_import'), |
| | }); |
| | queryClient.invalidateQueries([QueryKeys.presets]); |
| | }, |
| | onError: (error) => { |
| | console.error('Error uploading the preset:', error); |
| | showToast({ |
| | message: localize('com_endpoint_preset_import_error'), |
| | severity: NotificationSeverity.ERROR, |
| | }); |
| | }, |
| | }, |
| | ); |
| | }; |
| |
|
| | const onFileSelected = (jsonData: Record<string, unknown>) => { |
| | const jsonPreset = { ...cleanupPreset({ preset: jsonData }), presetId: null }; |
| | importPreset(jsonPreset); |
| | }; |
| |
|
| | const onSelectPreset = (_newPreset: TPreset) => { |
| | if (!_newPreset) { |
| | return; |
| | } |
| |
|
| | const newPreset = removeUnavailableTools(_newPreset, availableTools); |
| |
|
| | const toastTitle = newPreset.title |
| | ? `"${newPreset.title}"` |
| | : localize('com_endpoint_preset_title'); |
| |
|
| | showToast({ |
| | message: `${toastTitle} ${localize('com_endpoint_preset_selected_title')}`, |
| | showIcon: false, |
| | duration: 750, |
| | }); |
| |
|
| | const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]); |
| |
|
| | const { |
| | shouldSwitch, |
| | isNewModular, |
| | newEndpointType, |
| | isCurrentModular, |
| | isExistingConversation, |
| | } = getConvoSwitchLogic({ |
| | newEndpoint: newPreset.endpoint ?? '', |
| | modularChat, |
| | conversation, |
| | endpointsConfig, |
| | }); |
| |
|
| | newPreset.spec = null; |
| | newPreset.iconURL = newPreset.iconURL ?? null; |
| | newPreset.modelLabel = newPreset.modelLabel ?? null; |
| | const isModular = isCurrentModular && isNewModular && shouldSwitch; |
| | const disableParams = newPreset.defaultPreset === true; |
| | if (isExistingConversation && isModular) { |
| | const currentConvo = getDefaultConversation({ |
| | |
| | conversation: { |
| | ...(conversation ?? {}), |
| | spec: null, |
| | iconURL: null, |
| | modelLabel: null, |
| | endpointType: newEndpointType, |
| | }, |
| | preset: { ...newPreset, endpointType: newEndpointType }, |
| | cleanInput: true, |
| | }); |
| |
|
| | |
| | newConversation({ |
| | template: currentConvo, |
| | preset: currentConvo, |
| | keepLatestMessage: true, |
| | keepAddedConvos: true, |
| | disableParams, |
| | }); |
| | return; |
| | } |
| |
|
| | newConversation({ preset: newPreset, keepAddedConvos: isModular, disableParams }); |
| | }; |
| |
|
| | const onChangePreset = (preset: TPreset) => { |
| | setPreset(preset); |
| | setPresetModalVisible(true); |
| | }; |
| |
|
| | const clearAllPresets = () => deletePresetsMutation.mutate(undefined); |
| |
|
| | const onDeletePreset = (preset: TPreset) => { |
| | if (!confirm(localize('com_endpoint_preset_delete_confirm'))) { |
| | return; |
| | } |
| | deletePresetsMutation.mutate(preset); |
| | }; |
| |
|
| | const submitPreset = () => { |
| | if (!preset) { |
| | return; |
| | } |
| |
|
| | updatePreset.mutate(cleanupPreset({ preset })); |
| | }; |
| |
|
| | const onSetDefaultPreset = (preset: TPreset, remove = false) => { |
| | updatePreset.mutate({ ...preset, defaultPreset: !remove }); |
| | }; |
| |
|
| | const exportPreset = () => { |
| | if (!preset) { |
| | return; |
| | } |
| | const fileName = filenamify(preset.title || 'preset'); |
| | exportFromJSON({ |
| | data: cleanupPreset({ preset }), |
| | fileName, |
| | exportType: exportFromJSON.types.json, |
| | }); |
| | }; |
| |
|
| | return { |
| | presetsQuery, |
| | onSetDefaultPreset, |
| | onFileSelected, |
| | onSelectPreset, |
| | onChangePreset, |
| | clearAllPresets, |
| | onDeletePreset, |
| | submitPreset, |
| | exportPreset, |
| | }; |
| | } |
| |
|