| import { useCallback } from 'react'; |
| import { useNavigate, useSearchParams } from 'react-router-dom'; |
| import { useGetModelsQuery } from 'librechat-data-provider/react-query'; |
| import { useRecoilState, useRecoilValue, useSetRecoilState, useRecoilCallback } from 'recoil'; |
| import { |
| Constants, |
| FileSources, |
| EModelEndpoint, |
| isParamEndpoint, |
| getEndpointField, |
| LocalStorageKeys, |
| isAssistantsEndpoint, |
| } from 'librechat-data-provider'; |
| import type { |
| TPreset, |
| TSubmission, |
| TModelsConfig, |
| TConversation, |
| TEndpointsConfig, |
| } from 'librechat-data-provider'; |
| import type { AssistantListItem } from '~/common'; |
| import { |
| updateLastSelectedModel, |
| getDefaultModelSpec, |
| getDefaultEndpoint, |
| getModelSpecPreset, |
| buildDefaultConvo, |
| logger, |
| } from '~/utils'; |
| import { useDeleteFilesMutation, useGetEndpointsQuery, useGetStartupConfig } from '~/data-provider'; |
| import useAssistantListMap from './Assistants/useAssistantListMap'; |
| import { useResetChatBadges } from './useChatBadges'; |
| import { useApplyModelSpecEffects } from './Agents'; |
| import { usePauseGlobalAudio } from './Audio'; |
| import store from '~/store'; |
|
|
| const useNewConvo = (index = 0) => { |
| const navigate = useNavigate(); |
| const [searchParams] = useSearchParams(); |
| const { data: startupConfig } = useGetStartupConfig(); |
| const applyModelSpecEffects = useApplyModelSpecEffects(); |
| const clearAllConversations = store.useClearConvoState(); |
| const defaultPreset = useRecoilValue(store.defaultPreset); |
| const { setConversation } = store.useCreateConversationAtom(index); |
| const [files, setFiles] = useRecoilState(store.filesByIndex(index)); |
| const saveBadgesState = useRecoilValue<boolean>(store.saveBadgesState); |
| const clearAllLatestMessages = store.useClearLatestMessages(`useNewConvo ${index}`); |
| const setSubmission = useSetRecoilState<TSubmission | null>(store.submissionByIndex(index)); |
| const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery(); |
|
|
| const modelsQuery = useGetModelsQuery(); |
| const assistantsListMap = useAssistantListMap(); |
| const { pauseGlobalAudio } = usePauseGlobalAudio(index); |
| const saveDrafts = useRecoilValue<boolean>(store.saveDrafts); |
| const resetBadges = useResetChatBadges(); |
|
|
| const { mutateAsync } = useDeleteFilesMutation({ |
| onSuccess: () => { |
| console.log('Files deleted'); |
| }, |
| onError: (error) => { |
| console.log('Error deleting files:', error); |
| }, |
| }); |
|
|
| const switchToConversation = useRecoilCallback( |
| () => |
| async ( |
| conversation: TConversation, |
| preset: Partial<TPreset> | null = null, |
| modelsData?: TModelsConfig, |
| buildDefault?: boolean, |
| keepLatestMessage?: boolean, |
| keepAddedConvos?: boolean, |
| disableFocus?: boolean, |
| _disableParams?: boolean, |
| ) => { |
| const modelsConfig = modelsData ?? modelsQuery.data; |
| const { endpoint = null } = conversation; |
| const buildDefaultConversation = (endpoint === null || buildDefault) ?? false; |
| const activePreset = |
| |
| |
| |
| |
| defaultPreset && |
| !preset && |
| (defaultPreset.endpoint === endpoint || !endpoint) && |
| buildDefaultConversation |
| ? defaultPreset |
| : preset; |
|
|
| const disableParams = |
| _disableParams ?? |
| (activePreset?.presetId != null && |
| activePreset.presetId && |
| activePreset.presetId === defaultPreset?.presetId); |
|
|
| if (buildDefaultConversation) { |
| let defaultEndpoint = getDefaultEndpoint({ |
| convoSetup: activePreset ?? conversation, |
| endpointsConfig, |
| }); |
|
|
| if (!defaultEndpoint) { |
| defaultEndpoint = Object.keys(endpointsConfig ?? {})[0] as EModelEndpoint; |
| } |
|
|
| const endpointType = getEndpointField(endpointsConfig, defaultEndpoint, 'type'); |
| if (!conversation.endpointType && endpointType) { |
| conversation.endpointType = endpointType; |
| } else if (conversation.endpointType && !endpointType) { |
| conversation.endpointType = undefined; |
| } |
|
|
| const isAssistantEndpoint = isAssistantsEndpoint(defaultEndpoint); |
| const assistants: AssistantListItem[] = assistantsListMap[defaultEndpoint] ?? []; |
| const currentAssistantId = conversation.assistant_id ?? ''; |
| const currentAssistant = assistantsListMap[defaultEndpoint]?.[currentAssistantId] as |
| | AssistantListItem |
| | undefined; |
|
|
| if (currentAssistantId && !currentAssistant) { |
| conversation.assistant_id = undefined; |
| } |
|
|
| if (!currentAssistantId && isAssistantEndpoint) { |
| conversation.assistant_id = |
| localStorage.getItem( |
| `${LocalStorageKeys.ASST_ID_PREFIX}${index}${defaultEndpoint}`, |
| ) ?? assistants[0]?.id; |
| } |
|
|
| if ( |
| currentAssistantId && |
| isAssistantEndpoint && |
| conversation.conversationId === Constants.NEW_CONVO |
| ) { |
| const assistant = assistants.find((asst) => asst.id === currentAssistantId); |
| conversation.model = assistant?.model; |
| updateLastSelectedModel({ |
| endpoint: defaultEndpoint, |
| model: conversation.model, |
| }); |
| } |
|
|
| if (currentAssistantId && !isAssistantEndpoint) { |
| conversation.assistant_id = undefined; |
| } |
|
|
| const models = modelsConfig?.[defaultEndpoint] ?? []; |
| conversation = buildDefaultConvo({ |
| conversation, |
| lastConversationSetup: activePreset as TConversation, |
| endpoint: defaultEndpoint, |
| models, |
| }); |
| } |
|
|
| if (disableParams === true) { |
| conversation.disableParams = true; |
| } |
|
|
| if (!(keepAddedConvos ?? false)) { |
| clearAllConversations(true); |
| } |
| const isCancelled = conversation.conversationId?.startsWith('_'); |
| if (isCancelled) { |
| logger.log( |
| 'conversation', |
| 'Cancelled conversation, setting to `new` in `useNewConvo`', |
| conversation, |
| ); |
| setConversation({ |
| ...conversation, |
| conversationId: Constants.NEW_CONVO as string, |
| }); |
| } else { |
| logger.log('conversation', 'Setting conversation from `useNewConvo`', conversation); |
| setConversation(conversation); |
| } |
| setSubmission({} as TSubmission); |
| if (!(keepLatestMessage ?? false)) { |
| logger.log('latest_message', 'Clearing all latest messages'); |
| clearAllLatestMessages(); |
| } |
| if (isCancelled) { |
| return; |
| } |
|
|
| const searchParamsString = searchParams?.toString(); |
| const getParams = () => (searchParamsString ? `?${searchParamsString}` : ''); |
|
|
| if (conversation.conversationId === Constants.NEW_CONVO && !modelsData) { |
| const appTitle = localStorage.getItem(LocalStorageKeys.APP_TITLE) ?? ''; |
| if (appTitle) { |
| document.title = appTitle; |
| } |
| const path = `/c/${Constants.NEW_CONVO}${getParams()}`; |
| navigate(path, { state: { focusChat: true } }); |
| return; |
| } |
|
|
| const path = `/c/${conversation.conversationId}${getParams()}`; |
| navigate(path, { |
| replace: true, |
| state: disableFocus ? {} : { focusChat: true }, |
| }); |
| }, |
| [endpointsConfig, defaultPreset, assistantsListMap, modelsQuery.data], |
| ); |
|
|
| const newConversation = useCallback( |
| function createNewConvo({ |
| template: _template = {}, |
| preset: _preset, |
| modelsData, |
| disableFocus, |
| buildDefault = true, |
| keepLatestMessage = false, |
| keepAddedConvos = false, |
| disableParams, |
| }: { |
| template?: Partial<TConversation>; |
| preset?: Partial<TPreset>; |
| modelsData?: TModelsConfig; |
| buildDefault?: boolean; |
| disableFocus?: boolean; |
| keepLatestMessage?: boolean; |
| keepAddedConvos?: boolean; |
| disableParams?: boolean; |
| } = {}) { |
| pauseGlobalAudio(); |
| if (!saveBadgesState) { |
| resetBadges(); |
| } |
|
|
| const templateConvoId = _template.conversationId ?? ''; |
| const paramEndpoint = |
| isParamEndpoint(_template.endpoint ?? '', _template.endpointType ?? '') === true || |
| isParamEndpoint(_preset?.endpoint ?? '', _preset?.endpointType ?? ''); |
| const template = |
| paramEndpoint === true && templateConvoId && templateConvoId === Constants.NEW_CONVO |
| ? { endpoint: _template.endpoint } |
| : _template; |
|
|
| const conversation = { |
| conversationId: Constants.NEW_CONVO as string, |
| title: 'New Chat', |
| endpoint: null, |
| ...template, |
| createdAt: '', |
| updatedAt: '', |
| }; |
|
|
| let preset = _preset; |
| const result = getDefaultModelSpec(startupConfig); |
| const defaultModelSpec = result?.default ?? result?.last; |
| if ( |
| !preset && |
| startupConfig && |
| (startupConfig.modelSpecs?.prioritize === true || |
| (startupConfig.interface?.modelSelect ?? true) !== true || |
| (result?.last != null && Object.keys(_template).length === 0)) && |
| defaultModelSpec |
| ) { |
| preset = getModelSpecPreset(defaultModelSpec); |
| } |
|
|
| applyModelSpecEffects({ |
| startupConfig, |
| specName: preset?.spec, |
| convoId: conversation.conversationId, |
| }); |
|
|
| if (conversation.conversationId === Constants.NEW_CONVO && !modelsData) { |
| const filesToDelete = Array.from(files.values()) |
| .filter( |
| (file) => |
| file.filepath != null && |
| file.filepath !== '' && |
| file.source && |
| !(file.embedded ?? false) && |
| file.temp_file_id, |
| ) |
| .map((file) => ({ |
| file_id: file.file_id, |
| embedded: !!(file.embedded ?? false), |
| filepath: file.filepath as string, |
| source: file.source as FileSources, |
| })); |
|
|
| setFiles(new Map()); |
| localStorage.setItem(LocalStorageKeys.FILES_TO_DELETE, JSON.stringify({})); |
|
|
| if (!saveDrafts && filesToDelete.length > 0) { |
| mutateAsync({ files: filesToDelete }); |
| } |
| } |
|
|
| switchToConversation( |
| conversation, |
| preset, |
| modelsData, |
| buildDefault, |
| keepLatestMessage, |
| keepAddedConvos, |
| disableFocus, |
| disableParams, |
| ); |
| }, |
| [ |
| files, |
| setFiles, |
| saveDrafts, |
| mutateAsync, |
| resetBadges, |
| startupConfig, |
| saveBadgesState, |
| pauseGlobalAudio, |
| switchToConversation, |
| applyModelSpecEffects, |
| ], |
| ); |
|
|
| return { |
| switchToConversation, |
| newConversation, |
| }; |
| }; |
|
|
| export default useNewConvo; |
|
|