♻️ refactor: Logout UX, Improved State Teardown, & Remove Unused Code (#5292)

* refactor: SearchBar and Nav components to streamline search functionality and improve state management

* refactor: remove refresh conversations

* chore: update useNewConvo calls to remove hardcoded default index

* refactor: null check for submission in useSSE hook

* refactor: remove useConversation hook and update useSearch to utilize useNewConvo

* refactor: remove conversation and banner store files; consolidate state management into misc; improve typing of families and add messagesSiblingIdxFamily

* refactor: more effectively clear all user/convo state without side effects on logout/delete user

* refactor: replace useParams with useLocation in SearchBar to correctly load conversation

* refactor: update SearchButtons to use button element and improve conversation ID handling

* refactor: use named function for `newConversation` for better call stack tracing

* refactor: enhance TermsAndConditionsModal to support array content and improve type definitions for terms of service

* refactor: add SetConvoProvider and message invalidation when navigating from search results to prevent initial route rendering edge cases

* refactor: rename getLocalStorageItems to localStorage and update imports for consistency

* refactor: move clearLocalStorage function to utils and simplify localStorage clearing logic

* refactor: migrate authentication mutations to a dedicated Auth data provider and update related tests
This commit is contained in:
Danny Avila 2025-01-12 12:57:10 -05:00 committed by GitHub
parent 24beda3d69
commit aa80e4594e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 378 additions and 434 deletions

View file

@ -0,0 +1,14 @@
import { createContext, useContext, useRef } from 'react';
import type { MutableRefObject } from 'react';
type SetConvoContext = MutableRefObject<boolean>;
export const SetConvoContext = createContext<SetConvoContext>({} as SetConvoContext);
export const SetConvoProvider = ({ children }: { children: React.ReactNode }) => {
const hasSetConversation = useRef<boolean>(false);
return <SetConvoContext.Provider value={hasSetConversation}>{children}</SetConvoContext.Provider>;
};
export const useSetConvoContext = () => useContext(SetConvoContext);

View file

@ -18,3 +18,4 @@ export * from './AnnouncerContext';
export * from './AgentsMapContext';
export * from './CodeBlockContext';
export * from './ToolCallsMapContext';
export * from './SetConvoContext';

View file

@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event';
import { getByTestId, render, waitFor } from 'test/layout-test-utils';
import * as mockDataProvider from 'librechat-data-provider/react-query';
import type { TStartupConfig } from 'librechat-data-provider';
import * as authDataProvider from '~/data-provider/Auth/mutations';
import AuthLayout from '~/components/Auth/AuthLayout';
import Login from '~/components/Auth/Login';
@ -61,7 +62,7 @@ const setup = ({
},
} = {}) => {
const mockUseLoginUser = jest
.spyOn(mockDataProvider, 'useLoginUserMutation')
.spyOn(authDataProvider, 'useLoginUserMutation')
//@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult
.mockReturnValue(useLoginUserReturnValue);
const mockUseGetUserQuery = jest

View file

@ -2,6 +2,7 @@ import { render, getByTestId } from 'test/layout-test-utils';
import userEvent from '@testing-library/user-event';
import * as mockDataProvider from 'librechat-data-provider/react-query';
import type { TStartupConfig } from 'librechat-data-provider';
import * as authDataProvider from '~/data-provider/Auth/mutations';
import Login from '../LoginForm';
jest.mock('librechat-data-provider/react-query');
@ -66,7 +67,7 @@ const setup = ({
},
} = {}) => {
const mockUseLoginUser = jest
.spyOn(mockDataProvider, 'useLoginUserMutation')
.spyOn(authDataProvider, 'useLoginUserMutation')
//@ts-ignore - we don't need all parameters of the QueryObserverSuccessResult
.mockReturnValue(useLoginUserReturnValue);
const mockUseGetUserQuery = jest

View file

@ -8,33 +8,34 @@ export default function SearchButtons({ message }: { message: TMessage }) {
const localize = useLocalize();
const { searchQueryRes } = useSearchContext();
const { navigateWithLastTools } = useNavigateToConvo();
const conversationId = message.conversationId ?? '';
if (!message.conversationId) {
if (!conversationId) {
return null;
}
const clickHandler = (event: React.MouseEvent<HTMLAnchorElement>) => {
const clickHandler = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
const conversation = getConversationById(searchQueryRes?.data, message.conversationId);
const conversation = getConversationById(searchQueryRes?.data, conversationId);
if (!conversation) {
return;
}
document.title = message.title ?? '';
navigateWithLastTools(conversation);
navigateWithLastTools(conversation, true, true);
};
return (
<div className="visible mt-0 flex items-center justify-center gap-1 self-end text-gray-400 lg:justify-start">
<a
className="ml-0 flex cursor-pointer items-center gap-1.5 rounded-md p-1 text-xs hover:text-gray-900 hover:underline dark:text-gray-400/70 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400"
<div className="visible mt-0 flex items-center justify-center gap-1 self-end text-text-secondary lg:justify-start">
<button
className="ml-0 flex cursor-pointer items-center gap-1.5 rounded-md p-1 text-xs hover:text-text-primary hover:underline"
onClick={clickHandler}
title={localize('com_ui_go_to_conversation')}
>
<Link className="icon-sm" />
{message.title}
</a>
</button>
</div>
);
}

View file

@ -6,7 +6,7 @@ import { Constants } from 'librechat-data-provider';
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
import type { TConversation } from 'librechat-data-provider';
import { useConversations, useNavigateToConvo, useMediaQuery, useLocalize } from '~/hooks';
import { useNavigateToConvo, useMediaQuery, useLocalize } from '~/hooks';
import { useUpdateConversationMutation } from '~/data-provider';
import EndpointIcon from '~/components/Endpoints/EndpointIcon';
import { NotificationSeverity } from '~/common';
@ -36,7 +36,6 @@ export default function Conversation({
const activeConvos = useRecoilValue(store.allConversationsSelector);
const { data: endpointsConfig } = useGetEndpointsQuery();
const { navigateWithLastTools } = useNavigateToConvo();
const { refreshConversations } = useConversations();
const { showToast } = useToastContext();
const { conversationId, title } = conversation;
const inputRef = useRef<HTMLInputElement | null>(null);
@ -97,7 +96,6 @@ export default function Conversation({
updateConvoMutation.mutate(
{ conversationId, title: titleInput ?? '' },
{
onSuccess: () => refreshConversations(),
onError: () => {
setTitleInput(title);
showToast({
@ -109,7 +107,7 @@ export default function Conversation({
},
);
},
[title, titleInput, conversationId, showToast, refreshConversations, updateConvoMutation],
[title, titleInput, conversationId, showToast, updateConvoMutation],
);
const handleKeyDown = useCallback(

View file

@ -14,7 +14,7 @@ export default function MobileNav({
}) {
const localize = useLocalize();
const queryClient = useQueryClient();
const { newConversation } = useNewConvo(0);
const { newConversation } = useNewConvo();
const conversation = useRecoilValue(store.conversationByIndex(0));
const { title = 'New Chat' } = conversation || {};

View file

@ -1,6 +1,5 @@
import { useCallback, useEffect, useState, useMemo, memo } from 'react';
import { useRecoilValue } from 'recoil';
import { useParams } from 'react-router-dom';
import { PermissionTypes, Permissions } from 'librechat-data-provider';
import type { ConversationListResponse } from 'librechat-data-provider';
import {
@ -8,10 +7,8 @@ import {
useHasAccess,
useMediaQuery,
useAuthContext,
useConversation,
useLocalStorage,
useNavScrolling,
useConversations,
} from '~/hooks';
import { useConversationsInfiniteQuery } from '~/data-provider';
import { Conversations } from '~/components/Conversations';
@ -33,7 +30,6 @@ const Nav = ({
setNavVisible: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
const localize = useLocalize();
const { conversationId } = useParams();
const { isAuthenticated } = useAuthContext();
const [navWidth, setNavWidth] = useState('260px');
@ -67,11 +63,9 @@ const Nav = ({
}
}, [isSmallScreen]);
const { newConversation } = useConversation();
const [showLoading, setShowLoading] = useState(false);
const isSearchEnabled = useRecoilValue(store.isSearchEnabled);
const { refreshConversations } = useConversations();
const { pageNumber, searchQuery, setPageNumber, searchQueryRes } = useSearchContext();
const [tags, setTags] = useState<string[]>([]);
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch } =
@ -104,14 +98,6 @@ const Nav = ({
[data, searchQuery, searchQueryRes?.data],
);
const clearSearch = () => {
setPageNumber(1);
refreshConversations();
if (conversationId == 'search') {
newConversation();
}
};
const toggleNavVisible = () => {
setNavVisible((prev: boolean) => {
localStorage.setItem('navVisible', JSON.stringify(!prev));
@ -174,7 +160,10 @@ const Nav = ({
subHeaders={
<>
{isSearchEnabled === true && (
<SearchBar clearSearch={clearSearch} isSmallScreen={isSmallScreen} />
<SearchBar
setPageNumber={setPageNumber}
isSmallScreen={isSmallScreen}
/>
)}
{hasAccessToBookmarks === true && (
<>

View file

@ -1,27 +1,39 @@
import debounce from 'lodash/debounce';
import { Search, X } from 'lucide-react';
import { useSetRecoilState } from 'recoil';
import { useLocation } from 'react-router-dom';
import { QueryKeys } from 'librechat-data-provider';
import { useQueryClient } from '@tanstack/react-query';
import { forwardRef, useState, useCallback, useMemo, Ref } from 'react';
import { useLocalize } from '~/hooks';
import { useLocalize, useNewConvo } from '~/hooks';
import { cn } from '~/utils';
import store from '~/store';
type SearchBarProps = {
clearSearch: () => void;
isSmallScreen?: boolean;
setPageNumber: React.Dispatch<React.SetStateAction<number>>;
};
const SearchBar = forwardRef((props: SearchBarProps, ref: Ref<HTMLDivElement>) => {
const { clearSearch, isSmallScreen } = props;
const localize = useLocalize();
const location = useLocation();
const queryClient = useQueryClient();
const { setPageNumber, isSmallScreen } = props;
const [text, setText] = useState('');
const [showClearIcon, setShowClearIcon] = useState(false);
const { newConversation } = useNewConvo();
const clearConvoState = store.useClearConvoState();
const setSearchQuery = useSetRecoilState(store.searchQuery);
const [showClearIcon, setShowClearIcon] = useState(false);
const [text, setText] = useState('');
const setIsSearching = useSetRecoilState(store.isSearching);
const localize = useLocalize();
const clearSearch = useCallback(() => {
setPageNumber(1);
if (location.pathname.includes('/search')) {
newConversation();
}
}, [newConversation, setPageNumber, location.pathname]);
const clearText = useCallback(() => {
setShowClearIcon(false);

View file

@ -1,14 +1,13 @@
import React, { useState } from 'react';
import { useClearConversationsMutation } from 'librechat-data-provider/react-query';
import { Label, Button, OGDialog, OGDialogTrigger, Spinner } from '~/components';
import { useConversation, useConversations, useLocalize } from '~/hooks';
import { useLocalize, useNewConvo } from '~/hooks';
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
export const ClearChats = () => {
const localize = useLocalize();
const [open, setOpen] = useState(false);
const { newConversation } = useConversation();
const { refreshConversations } = useConversations();
const { newConversation } = useNewConvo();
const clearConvosMutation = useClearConversationsMutation();
const clearConvos = () => {
@ -17,7 +16,6 @@ export const ClearChats = () => {
{
onSuccess: () => {
newConversation();
refreshConversations();
},
},
);

View file

@ -2,9 +2,9 @@ import { useState, useRef } from 'react';
import { Import } from 'lucide-react';
import type { TError } from 'librechat-data-provider';
import { useUploadConversationsMutation } from '~/data-provider';
import { useLocalize, useConversations } from '~/hooks';
import { useToastContext } from '~/Providers';
import { Spinner } from '~/components/svg';
import { useLocalize } from '~/hooks';
import { cn } from '~/utils';
function ImportConversations() {
@ -15,11 +15,9 @@ function ImportConversations() {
const [, setErrors] = useState<string[]>([]);
const [allowImport, setAllowImport] = useState(true);
const setError = (error: string) => setErrors((prevErrors) => [...prevErrors, error]);
const { refreshConversations } = useConversations();
const uploadFile = useUploadConversationsMutation({
onSuccess: () => {
refreshConversations();
showToast({ message: localize('com_ui_import_conversation_success') });
setAllowImport(true);
},
@ -29,7 +27,7 @@ function ImportConversations() {
setError(
(error as TError).response?.data?.message ?? 'An error occurred while uploading the file.',
);
if (error?.toString().includes('Unsupported import type')) {
if (error?.toString().includes('Unsupported import type') === true) {
showToast({
message: localize('com_ui_import_conversation_file_type_error'),
status: 'error',

View file

@ -1,3 +1,5 @@
import { useMemo } from 'react';
import type { TTermsOfService } from 'librechat-data-provider';
import MarkdownLite from '~/components/Chat/Messages/Content/MarkdownLite';
import DialogTemplate from '~/components/ui/DialogTemplate';
import { useAcceptTermsMutation } from '~/data-provider';
@ -19,7 +21,7 @@ const TermsAndConditionsModal = ({
onDecline: () => void;
title?: string;
contentUrl?: string;
modalContent?: string;
modalContent?: TTermsOfService['modalContent'];
}) => {
const localize = useLocalize();
const { showToast } = useToastContext();
@ -49,6 +51,18 @@ const TermsAndConditionsModal = ({
onOpenChange(isOpen);
};
const content = useMemo(() => {
if (typeof modalContent === 'string') {
return modalContent;
}
if (Array.isArray(modalContent)) {
return modalContent.join('\n');
}
return '';
}, [modalContent]);
return (
<OGDialog open={open} onOpenChange={handleOpenChange}>
<DialogTemplate
@ -65,8 +79,8 @@ const TermsAndConditionsModal = ({
aria-label={localize('com_ui_terms_and_conditions')}
>
<div className="prose dark:prose-invert w-full max-w-none !text-text-primary">
{modalContent != null && modalContent ? (
<MarkdownLite content={modalContent} />
{content !== '' ? (
<MarkdownLite content={content} />
) : (
<p>{localize('com_ui_no_terms_content')}</p>
)}

View file

@ -0,0 +1 @@
export * from './mutations';

View file

@ -0,0 +1,65 @@
import { useResetRecoilState } from 'recoil';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { MutationKeys, dataService } from 'librechat-data-provider';
import type { UseMutationResult } from '@tanstack/react-query';
import type * as t from 'librechat-data-provider';
import useClearStates from '~/hooks/Config/useClearStates';
import store from '~/store';
/* login/logout */
export const useLogoutUserMutation = (
options?: t.LogoutOptions,
): UseMutationResult<unknown, unknown, undefined, unknown> => {
const queryClient = useQueryClient();
const clearStates = useClearStates();
const resetDefaultPreset = useResetRecoilState(store.defaultPreset);
return useMutation([MutationKeys.logoutUser], {
mutationFn: () => dataService.logout(),
...(options || {}),
onSuccess: (...args) => {
resetDefaultPreset();
clearStates();
queryClient.removeQueries();
options?.onSuccess?.(...args);
},
});
};
export const useLoginUserMutation = (): UseMutationResult<
t.TLoginResponse,
unknown,
t.TLoginUser,
unknown
> => {
const queryClient = useQueryClient();
const clearStates = useClearStates();
const resetDefaultPreset = useResetRecoilState(store.defaultPreset);
return useMutation((payload: t.TLoginUser) => dataService.login(payload), {
onMutate: () => {
resetDefaultPreset();
clearStates();
queryClient.removeQueries();
},
});
};
/* User */
export const useDeleteUserMutation = (
options?: t.MutationOptions<unknown, undefined>,
): UseMutationResult<unknown, unknown, undefined, unknown> => {
const queryClient = useQueryClient();
const clearStates = useClearStates();
const resetDefaultPreset = useResetRecoilState(store.defaultPreset);
return useMutation([MutationKeys.deleteUser], {
mutationFn: () => dataService.deleteUser(),
...(options || {}),
onSuccess: (...args) => {
resetDefaultPreset();
clearStates();
queryClient.removeQueries();
options?.onSuccess?.(...args);
},
});
};

View file

@ -1,3 +1,4 @@
export * from './Auth';
export * from './Agents';
export * from './Files';
export * from './Tools';

View file

@ -1,11 +1,9 @@
import {
Constants,
LocalStorageKeys,
InfiniteCollections,
defaultAssistantsVersion,
ConversationListResponse,
} from 'librechat-data-provider';
import { useSetRecoilState } from 'recoil';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { dataService, MutationKeys, QueryKeys, defaultOrderQuery } from 'librechat-data-provider';
import type * as t from 'librechat-data-provider';
@ -13,7 +11,6 @@ import type { InfiniteData, UseMutationResult } from '@tanstack/react-query';
import useUpdateTagsInConvo from '~/hooks/Conversations/useUpdateTagsInConvo';
import { updateConversationTag } from '~/utils/conversationTags';
import { normalizeData } from '~/utils/collection';
import store from '~/store';
import {
useConversationTagsQuery,
useConversationsInfiniteQuery,
@ -692,34 +689,6 @@ export const useDeletePresetMutation = (
});
};
/* login/logout */
export const useLogoutUserMutation = (
options?: t.LogoutOptions,
): UseMutationResult<unknown, unknown, undefined, unknown> => {
const queryClient = useQueryClient();
const setDefaultPreset = useSetRecoilState(store.defaultPreset);
return useMutation([MutationKeys.logoutUser], {
mutationFn: () => dataService.logout(),
...(options || {}),
onSuccess: (...args) => {
options?.onSuccess?.(...args);
},
onMutate: (...args) => {
setDefaultPreset(null);
queryClient.removeQueries();
localStorage.removeItem(LocalStorageKeys.LAST_CONVO_SETUP);
localStorage.removeItem(`${LocalStorageKeys.LAST_CONVO_SETUP}_0`);
localStorage.removeItem(`${LocalStorageKeys.LAST_CONVO_SETUP}_1`);
localStorage.removeItem(LocalStorageKeys.LAST_MODEL);
localStorage.removeItem(LocalStorageKeys.LAST_TOOLS);
localStorage.removeItem(LocalStorageKeys.FILES_TO_DELETE);
// localStorage.removeItem('lastAssistant');
options?.onMutate?.(...args);
},
});
};
/* Avatar upload */
export const useUploadAvatarMutation = (
options?: t.UploadAvatarOptions,
@ -735,32 +704,6 @@ export const useUploadAvatarMutation = (
});
};
export const useDeleteUserMutation = (
options?: t.MutationOptions<unknown, undefined>,
): UseMutationResult<unknown, unknown, undefined, unknown> => {
const queryClient = useQueryClient();
const setDefaultPreset = useSetRecoilState(store.defaultPreset);
return useMutation([MutationKeys.deleteUser], {
mutationFn: () => dataService.deleteUser(),
...(options || {}),
onSuccess: (...args) => {
options?.onSuccess?.(...args);
},
onMutate: (...args) => {
setDefaultPreset(null);
queryClient.removeQueries();
localStorage.removeItem(LocalStorageKeys.LAST_CONVO_SETUP);
localStorage.removeItem(`${LocalStorageKeys.LAST_CONVO_SETUP}_0`);
localStorage.removeItem(`${LocalStorageKeys.LAST_CONVO_SETUP}_1`);
localStorage.removeItem(LocalStorageKeys.LAST_MODEL);
localStorage.removeItem(LocalStorageKeys.LAST_TOOLS);
localStorage.removeItem(LocalStorageKeys.FILES_TO_DELETE);
options?.onMutate?.(...args);
},
});
};
/* Speech to text */
export const useSpeechToTextMutation = (
options?: t.SpeechToTextOptions,

View file

@ -10,14 +10,10 @@ import {
import { useRecoilState } from 'recoil';
import { useNavigate } from 'react-router-dom';
import { setTokenHeader, SystemRoles } from 'librechat-data-provider';
import {
useGetUserQuery,
useLoginUserMutation,
useRefreshTokenMutation,
} from 'librechat-data-provider/react-query';
import { useGetUserQuery, useRefreshTokenMutation } from 'librechat-data-provider/react-query';
import type { TLoginResponse, TLoginUser } from 'librechat-data-provider';
import { useLoginUserMutation, useLogoutUserMutation, useGetRole } from '~/data-provider';
import { TAuthConfig, TUserContext, TAuthContext, TResError } from '~/common';
import { useLogoutUserMutation, useGetRole } from '~/data-provider';
import useTimeout from './useTimeout';
import store from '~/store';

View file

@ -1 +1,2 @@
export { default as useAppStartup } from './useAppStartup';
export { default as useClearStates } from './useClearStates';

View file

@ -0,0 +1,52 @@
import { useRecoilCallback } from 'recoil';
import { clearLocalStorage } from '~/utils/localStorage';
import store from '~/store';
export default function useClearStates() {
const clearConversations = store.useClearConvoState();
const clearSubmissions = store.useClearSubmissionState();
const clearLatestMessages = store.useClearLatestMessages();
const clearStates = useRecoilCallback(
({ reset, snapshot }) =>
async (skipFirst?: boolean) => {
await clearSubmissions(skipFirst);
await clearConversations(skipFirst);
await clearLatestMessages(skipFirst);
const keys = await snapshot.getPromise(store.conversationKeysAtom);
for (const key of keys) {
if (skipFirst === true && key === 0) {
continue;
}
reset(store.filesByIndex(key));
reset(store.presetByIndex(key));
reset(store.textByIndex(key));
reset(store.showStopButtonByIndex(key));
reset(store.abortScrollFamily(key));
reset(store.isSubmittingFamily(key));
reset(store.optionSettingsFamily(key));
reset(store.showAgentSettingsFamily(key));
reset(store.showBingToneSettingFamily(key));
reset(store.showPopoverFamily(key));
reset(store.showMentionPopoverFamily(key));
reset(store.showPlusPopoverFamily(key));
reset(store.showPromptsPopoverFamily(key));
reset(store.activePromptByIndex(key));
reset(store.globalAudioURLFamily(key));
reset(store.globalAudioFetchingFamily(key));
reset(store.globalAudioPlayingFamily(key));
reset(store.activeRunFamily(key));
reset(store.audioRunFamily(key));
reset(store.messagesSiblingIdxFamily(key.toString()));
}
clearLocalStorage(skipFirst);
},
[],
);
return clearStates;
}

View file

@ -8,7 +8,7 @@ import store from '~/store';
type TempOverrideType = Record<string, unknown> & {
endpointsConfig: TEndpointsConfig;
modelsConfig: TModelsConfig;
modelsConfig?: TModelsConfig;
combinedOptions: unknown[];
combined: boolean;
};
@ -38,7 +38,7 @@ export default function useConfigOverride() {
);
useEffect(() => {
if (overrideQuery.data) {
if (overrideQuery.data != null) {
handleOverride(overrideQuery.data);
}
}, [overrideQuery.data, handleOverride]);

View file

@ -2,9 +2,7 @@ export { default as useSearch } from './useSearch';
export { default as usePresets } from './usePresets';
export { default as useGetSender } from './useGetSender';
export { default as useDefaultConvo } from './useDefaultConvo';
export { default as useConversation } from './useConversation';
export { default as useGenerateConvo } from './useGenerateConvo';
export { default as useConversations } from './useConversations';
export { default as useArchiveHandler } from './useArchiveHandler';
export { default as useDebouncedInput } from './useDebouncedInput';
export { default as useBookmarkSuccess } from './useBookmarkSuccess';

View file

@ -1,7 +1,6 @@
import { useParams, useNavigate } from 'react-router-dom';
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
import { useArchiveConversationMutation } from '~/data-provider';
import useConversations from './useConversations';
import { NotificationSeverity } from '~/common';
import { useToastContext } from '~/Providers';
import useLocalize from '../useLocalize';
@ -16,7 +15,6 @@ export default function useArchiveHandler(
const navigate = useNavigate();
const { showToast } = useToastContext();
const { newConversation } = useNewConvo();
const { refreshConversations } = useConversations();
const { conversationId: currentConvoId } = useParams();
const archiveConvoMutation = useArchiveConversationMutation(conversationId ?? '');
@ -38,7 +36,6 @@ export default function useArchiveHandler(
newConversation();
navigate('/c/new', { replace: true });
}
refreshConversations();
retainView();
},
onError: () => {

View file

@ -1,115 +0,0 @@
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { QueryKeys } from 'librechat-data-provider';
import { useQueryClient } from '@tanstack/react-query';
import { useSetRecoilState, useResetRecoilState, useRecoilCallback } from 'recoil';
import { useGetEndpointsQuery, useGetModelsQuery } from 'librechat-data-provider/react-query';
import type {
TConversation,
TMessagesAtom,
TSubmission,
TPreset,
TModelsConfig,
TEndpointsConfig,
} from 'librechat-data-provider';
import { buildDefaultConvo, getDefaultEndpoint, getEndpointField, logger } from '~/utils';
import store from '~/store';
const useConversation = () => {
const navigate = useNavigate();
const queryClient = useQueryClient();
const setConversation = useSetRecoilState(store.conversation);
const resetLatestMessage = useResetRecoilState(store.latestMessage);
const setMessages = useSetRecoilState<TMessagesAtom>(store.messages);
const setSubmission = useSetRecoilState<TSubmission | null>(store.submission);
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
const modelsQuery = useGetModelsQuery();
const switchToConversation = useRecoilCallback(
() =>
async (
conversation: TConversation,
messages: TMessagesAtom = null,
preset: TPreset | null = null,
modelsData?: TModelsConfig,
) => {
const modelsConfig = modelsData ?? modelsQuery.data;
const { endpoint = null } = conversation;
if (endpoint === null) {
const defaultEndpoint = getDefaultEndpoint({
convoSetup: preset ?? conversation,
endpointsConfig,
});
const endpointType = getEndpointField(endpointsConfig, defaultEndpoint, 'type');
if (!conversation.endpointType && endpointType) {
conversation.endpointType = endpointType;
}
const models = modelsConfig?.[defaultEndpoint] ?? [];
conversation = buildDefaultConvo({
conversation,
lastConversationSetup: preset as TConversation,
endpoint: defaultEndpoint,
models,
});
}
setConversation(conversation);
setMessages(messages);
setSubmission({} as TSubmission);
resetLatestMessage();
logger.log(
'[useConversation] Switched to conversation and reset Latest Message',
conversation,
);
if (conversation.conversationId === 'new' && !modelsData) {
queryClient.invalidateQueries([QueryKeys.messages, 'new']);
navigate('/c/new');
}
},
[endpointsConfig, modelsQuery.data],
);
const newConversation = useCallback(
(template = {}, preset?: TPreset, modelsData?: TModelsConfig) => {
switchToConversation(
{
conversationId: 'new',
title: 'New Chat',
...template,
endpoint: null,
createdAt: '',
updatedAt: '',
},
[],
preset,
modelsData,
);
},
[switchToConversation],
);
const searchPlaceholderConversation = useCallback(() => {
switchToConversation(
{
conversationId: 'search',
title: 'Search',
endpoint: null,
createdAt: '',
updatedAt: '',
},
[],
);
}, [switchToConversation]);
return {
switchToConversation,
newConversation,
searchPlaceholderConversation,
};
};
export default useConversation;

View file

@ -1,15 +0,0 @@
import { useSetRecoilState } from 'recoil';
import { useCallback } from 'react';
import store from '~/store';
const useConversations = () => {
const setRefreshConversationsHint = useSetRecoilState(store.refreshConversationsHint);
const refreshConversations = useCallback(() => {
setRefreshConversationsHint((prevState) => prevState + 1);
}, [setRefreshConversationsHint]);
return { refreshConversations };
};
export default useConversations;

View file

@ -12,20 +12,29 @@ const useNavigateToConvo = (index = 0) => {
const clearAllConversations = store.useClearConvoState();
const clearAllLatestMessages = store.useClearLatestMessages(`useNavigateToConvo ${index}`);
const setSubmission = useSetRecoilState(store.submissionByIndex(index));
const { setConversation } = store.useCreateConversationAtom(index);
const { hasSetConversation, setConversation } = store.useCreateConversationAtom(index);
const navigateToConvo = (conversation: TConversation, _resetLatestMessage = true) => {
const navigateToConvo = (
conversation?: TConversation | null,
_resetLatestMessage = true,
invalidateMessages = false,
) => {
if (!conversation) {
console.log('Conversation not provided');
return;
}
hasSetConversation.current = true;
setSubmission(null);
if (_resetLatestMessage) {
clearAllLatestMessages();
}
if (invalidateMessages && conversation.conversationId != null && conversation.conversationId) {
queryClient.setQueryData([QueryKeys.messages, Constants.NEW_CONVO], []);
queryClient.invalidateQueries([QueryKeys.messages, conversation.conversationId]);
}
let convo = { ...conversation };
if (!convo?.endpoint) {
if (!convo.endpoint) {
/* undefined endpoint edge case */
const modelsConfig = queryClient.getQueryData<TModelsConfig>([QueryKeys.models]);
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
@ -53,9 +62,17 @@ const useNavigateToConvo = (index = 0) => {
navigate(`/c/${convo.conversationId ?? Constants.NEW_CONVO}`);
};
const navigateWithLastTools = (conversation: TConversation, _resetLatestMessage?: boolean) => {
const navigateWithLastTools = (
conversation?: TConversation | null,
_resetLatestMessage?: boolean,
invalidateMessages?: boolean,
) => {
if (!conversation) {
console.log('Conversation not provided');
return;
}
// set conversation to the new conversation
if (conversation?.endpoint === EModelEndpoint.gptPlugins) {
if (conversation.endpoint === EModelEndpoint.gptPlugins) {
let lastSelectedTools = [];
try {
lastSelectedTools =
@ -63,15 +80,17 @@ const useNavigateToConvo = (index = 0) => {
} catch (e) {
// console.error(e);
}
const hasTools = (conversation.tools?.length ?? 0) > 0;
navigateToConvo(
{
...conversation,
tools: conversation?.tools?.length ? conversation?.tools : lastSelectedTools,
tools: hasTools ? conversation.tools : lastSelectedTools,
},
_resetLatestMessage,
invalidateMessages,
);
} else {
navigateToConvo(conversation, _resetLatestMessage);
navigateToConvo(conversation, _resetLatestMessage, invalidateMessages);
}
};

View file

@ -5,14 +5,23 @@ import { useGetSearchEnabledQuery } from 'librechat-data-provider/react-query';
import type { UseInfiniteQueryResult } from '@tanstack/react-query';
import type { ConversationListResponse } from 'librechat-data-provider';
import { useSearchInfiniteQuery } from '~/data-provider';
import useConversation from './useConversation';
import useNewConvo from '~/hooks/useNewConvo';
import store from '~/store';
export default function useSearchMessages({ isAuthenticated }: { isAuthenticated: boolean }) {
const navigate = useNavigate();
const location = useLocation();
const [pageNumber, setPageNumber] = useState(1);
const { searchPlaceholderConversation } = useConversation();
const { switchToConversation } = useNewConvo();
const searchPlaceholderConversation = useCallback(() => {
switchToConversation({
conversationId: 'search',
title: 'Search',
endpoint: null,
createdAt: '',
updatedAt: '',
});
}, [switchToConversation]);
const searchQuery = useRecoilValue(store.searchQuery);
const setIsSearchEnabled = useSetRecoilState(store.isSearchEnabled);

View file

@ -81,7 +81,7 @@ export default function useSSE(
});
useEffect(() => {
if (submission === null || Object.keys(submission).length === 0) {
if (submission == null || Object.keys(submission).length === 0) {
return;
}

View file

@ -117,7 +117,11 @@ const useNewConvo = (index = 0) => {
) ?? assistants[0]?.id;
}
if (currentAssistantId && isAssistantEndpoint && conversation.conversationId === Constants.NEW_CONVO) {
if (
currentAssistantId &&
isAssistantEndpoint &&
conversation.conversationId === Constants.NEW_CONVO
) {
const assistant = assistants.find((asst) => asst.id === currentAssistantId);
conversation.model = assistant?.model;
updateLastSelectedModel({
@ -168,7 +172,7 @@ const useNewConvo = (index = 0) => {
);
const newConversation = useCallback(
({
function createNewConvo({
template: _template = {},
preset: _preset,
modelsData,
@ -182,7 +186,7 @@ const useNewConvo = (index = 0) => {
buildDefault?: boolean;
keepLatestMessage?: boolean;
keepAddedConvos?: boolean;
} = {}) => {
} = {}) {
pauseGlobalAudio();
const templateConvoId = _template.conversationId ?? '';

View file

@ -1,4 +1,4 @@
import { useEffect, useRef } from 'react';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Constants, EModelEndpoint } from 'librechat-data-provider';
import {
@ -25,9 +25,8 @@ export default function ChatRoute() {
const index = 0;
const { conversationId = '' } = useParams();
const { conversation } = store.useCreateConversationAtom(index);
const { hasSetConversation, conversation } = store.useCreateConversationAtom(index);
const { newConversation } = useNewConvo();
const hasSetConversation = useRef(false);
const modelsQuery = useGetModelsQuery({
enabled: isAuthenticated,

View file

@ -1,30 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Outlet, useNavigate } from 'react-router-dom';
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
import { useUserTermsQuery } from '~/data-provider';
import type { ContextType } from '~/common';
import { AgentsMapContext, AssistantsMapContext, FileMapContext, SearchContext } from '~/Providers';
import {
AgentsMapContext,
AssistantsMapContext,
FileMapContext,
SearchContext,
SetConvoProvider,
} from '~/Providers';
import { useAuthContext, useAssistantsMap, useAgentsMap, useFileMap, useSearch } from '~/hooks';
import { Nav, MobileNav } from '~/components/Nav';
import TermsAndConditionsModal from '~/components/ui/TermsAndConditionsModal';
import { useUserTermsQuery } from '~/data-provider';
import { Nav, MobileNav } from '~/components/Nav';
import { Banner } from '~/components/Banners';
export default function Root() {
const { isAuthenticated, logout } = useAuthContext();
const navigate = useNavigate();
const [showTerms, setShowTerms] = useState(false);
const [bannerHeight, setBannerHeight] = useState(0);
const [navVisible, setNavVisible] = useState(() => {
const savedNavVisible = localStorage.getItem('navVisible');
return savedNavVisible !== null ? JSON.parse(savedNavVisible) : true;
});
const [bannerHeight, setBannerHeight] = useState(0);
const search = useSearch({ isAuthenticated });
const fileMap = useFileMap({ isAuthenticated });
const { isAuthenticated, logout } = useAuthContext();
const assistantsMap = useAssistantsMap({ isAuthenticated });
const agentsMap = useAgentsMap({ isAuthenticated });
const fileMap = useFileMap({ isAuthenticated });
const search = useSearch({ isAuthenticated });
const [showTerms, setShowTerms] = useState(false);
const { data: config } = useGetStartupConfig();
const { data: termsData } = useUserTermsQuery({
enabled: isAuthenticated && config?.interface?.termsOfService?.modalAcceptance === true,
@ -51,6 +56,7 @@ export default function Root() {
}
return (
<SetConvoProvider>
<SearchContext.Provider value={search}>
<FileMapContext.Provider value={fileMap}>
<AssistantsMapContext.Provider value={assistantsMap}>
@ -79,5 +85,6 @@ export default function Root() {
</AssistantsMapContext.Provider>
</FileMapContext.Provider>
</SearchContext.Provider>
</SetConvoProvider>
);
}

View file

@ -1,5 +0,0 @@
import { atomWithLocalStorage } from './utils';
const hideBannerHint = atomWithLocalStorage('hideBannerHint', [] as string[]);
export default { hideBannerHint };

View file

@ -1,47 +0,0 @@
import { atom, selector, atomFamily } from 'recoil';
import { TConversation, TMessagesAtom, TMessage, TAttachment } from 'librechat-data-provider';
import { buildTree } from '~/utils';
const conversation = atom<TConversation | null>({
key: 'conversation',
default: null,
});
// current messages of the conversation, must be an array
// sample structure
// [{text, sender, messageId, parentMessageId, isCreatedByUser}]
const messages = atom<TMessagesAtom>({
key: 'messages',
default: [],
});
const messagesTree = selector({
key: 'messagesTree',
get: ({ get }) => {
return buildTree({ messages: get(messages) });
},
});
const messageAttachmentsMap = atom<Record<string, TAttachment[] | undefined>>({
key: 'messageAttachmentsMap',
default: {},
});
const latestMessage = atom<TMessage | null>({
key: 'latestMessage',
default: null,
});
const messagesSiblingIdxFamily = atomFamily<number, string | null | undefined>({
key: 'messagesSiblingIdx',
default: 0,
});
export default {
messages,
conversation,
messagesTree,
latestMessage,
messageAttachmentsMap,
messagesSiblingIdxFamily,
};

View file

@ -1,8 +0,0 @@
import { atom } from 'recoil';
const refreshConversationsHint = atom<number>({
key: 'refreshConversationsHint',
default: 1,
});
export default { refreshConversationsHint };

View file

@ -1,3 +1,4 @@
import { useEffect } from 'react';
import {
atom,
selector,
@ -12,8 +13,8 @@ import {
import { LocalStorageKeys, Constants } from 'librechat-data-provider';
import type { TMessage, TPreset, TConversation, TSubmission } from 'librechat-data-provider';
import type { TOptionSettings, ExtendedFile } from '~/common';
import { useSetConvoContext } from '~/Providers/SetConvoContext';
import { storeEndpointSettings, logger } from '~/utils';
import { useEffect } from 'react';
const latestMessageKeysAtom = atom<(string | number)[]>({
key: 'latestMessageKeys',
@ -75,16 +76,16 @@ const conversationByIndex = atomFamily<TConversation | null, string | number>({
onSet(async (newValue) => {
const index = Number(node.key.split('__')[1]);
logger.log('conversation', 'Setting conversation:', { index, newValue });
if (newValue?.assistant_id) {
if (newValue?.assistant_id != null && newValue.assistant_id) {
localStorage.setItem(
`${LocalStorageKeys.ASST_ID_PREFIX}${index}${newValue.endpoint}`,
newValue.assistant_id,
);
}
if (newValue?.agent_id) {
if (newValue?.agent_id != null && newValue.agent_id) {
localStorage.setItem(`${LocalStorageKeys.AGENT_ID_PREFIX}${index}`, newValue.agent_id);
}
if (newValue?.spec) {
if (newValue?.spec != null && newValue.spec) {
localStorage.setItem(LocalStorageKeys.LAST_SPEC, newValue.spec);
}
if (newValue?.tools && Array.isArray(newValue.tools)) {
@ -238,7 +239,13 @@ const audioRunFamily = atomFamily<string | null, string | number | null>({
default: null,
});
const messagesSiblingIdxFamily = atomFamily<number, string | null | undefined>({
key: 'messagesSiblingIdx',
default: 0,
});
function useCreateConversationAtom(key: string | number) {
const hasSetConversation = useSetConvoContext();
const [keys, setKeys] = useRecoilState(conversationKeysAtom);
const setConversation = useSetRecoilState(conversationByIndex(key));
const conversation = useRecoilValue(conversationByIndex(key));
@ -249,7 +256,7 @@ function useCreateConversationAtom(key: string | number) {
}
}, [key, keys, setKeys]);
return { conversation, setConversation };
return { hasSetConversation, conversation, setConversation };
}
function useClearConvoState() {
@ -260,7 +267,7 @@ function useClearConvoState() {
const conversationKeys = await snapshot.getPromise(conversationKeysAtom);
for (const conversationKey of conversationKeys) {
if (skipFirst && conversationKey == 0) {
if (skipFirst === true && conversationKey == 0) {
continue;
}
@ -298,7 +305,7 @@ function useClearSubmissionState() {
logger.log('submissionKeys', submissionKeys);
for (const key of submissionKeys) {
if (skipFirst && key == 0) {
if (skipFirst === true && key == 0) {
continue;
}
@ -320,12 +327,12 @@ function useClearLatestMessages(context?: string) {
async (skipFirst?: boolean) => {
const latestMessageKeys = await snapshot.getPromise(latestMessageKeysSelector);
logger.log('[clearAllLatestMessages] latestMessageKeys', latestMessageKeys);
if (context) {
if (context != null && context) {
logger.log(`[clearAllLatestMessages] context: ${context}`);
}
for (const key of latestMessageKeys) {
if (skipFirst && key == 0) {
if (skipFirst === true && key == 0) {
continue;
}
@ -367,6 +374,7 @@ const updateConversationSelector = selectorFamily({
});
export default {
conversationKeysAtom,
conversationByIndex,
filesByIndex,
presetByIndex,
@ -380,6 +388,7 @@ export default {
showBingToneSettingFamily,
showPopoverFamily,
latestMessageFamily,
messagesSiblingIdxFamily,
allConversationsSelector,
conversationByKeySelector,
useClearConvoState,

View file

@ -1,6 +1,4 @@
import * as artifacts from './artifacts';
import conversation from './conversation';
import conversations from './conversations';
import families from './families';
import endpoints from './endpoints';
import user from './user';
@ -12,12 +10,10 @@ import preset from './preset';
import prompts from './prompts';
import lang from './language';
import settings from './settings';
import banner from './banner';
import misc from './misc';
export default {
...artifacts,
...families,
...conversation,
...conversations,
...endpoints,
...user,
...text,
@ -28,5 +24,5 @@ export default {
...preset,
...lang,
...settings,
...banner,
...misc,
};

12
client/src/store/misc.ts Normal file
View file

@ -0,0 +1,12 @@
import { atom } from 'recoil';
import { TAttachment } from 'librechat-data-provider';
import { atomWithLocalStorage } from './utils';
const hideBannerHint = atomWithLocalStorage('hideBannerHint', [] as string[]);
const messageAttachmentsMap = atom<Record<string, TAttachment[] | undefined>>({
key: 'messageAttachmentsMap',
default: {},
});
export default { hideBannerHint, messageAttachmentsMap };

View file

@ -1,16 +1,6 @@
import { atom } from 'recoil';
import { TPreset } from 'librechat-data-provider';
const presets = atom<TPreset[]>({
key: 'presets',
default: [],
});
const preset = atom<TPreset | null>({
key: 'preset',
default: null,
});
const defaultPreset = atom<TPreset | null>({
key: 'defaultPreset',
default: null,
@ -22,8 +12,6 @@ const presetModalVisible = atom<boolean>({
});
export default {
preset,
presets,
defaultPreset,
presetModalVisible,
};

View file

@ -5,7 +5,7 @@ import {
isAgentsEndpoint,
} from 'librechat-data-provider';
import type { TConversation } from 'librechat-data-provider';
import getLocalStorageItems from './getLocalStorageItems';
import { getLocalStorageItems } from './localStorage';
const buildDefaultConvo = ({
conversation,

View file

@ -4,16 +4,20 @@ import type {
TEndpointsConfig,
EModelEndpoint,
} from 'librechat-data-provider';
import getLocalStorageItems from './getLocalStorageItems';
import { getLocalStorageItems } from './localStorage';
import { mapEndpoints } from './endpoints';
type TConvoSetup = Partial<TPreset> | Partial<TConversation>;
type TDefaultEndpoint = { convoSetup: TConvoSetup; endpointsConfig: TEndpointsConfig };
const getEndpointFromSetup = (convoSetup: TConvoSetup, endpointsConfig: TEndpointsConfig) => {
const { endpoint: targetEndpoint } = convoSetup || {};
if (targetEndpoint && endpointsConfig?.[targetEndpoint ?? '']) {
const getEndpointFromSetup = (
convoSetup: TConvoSetup | null,
endpointsConfig: TEndpointsConfig,
) => {
let { endpoint: targetEndpoint = '' } = convoSetup || {};
targetEndpoint = targetEndpoint ?? '';
if (targetEndpoint && endpointsConfig?.[targetEndpoint]) {
return targetEndpoint;
} else if (targetEndpoint) {
console.warn(`Illegal target endpoint ${targetEndpoint} ${endpointsConfig}`);
@ -24,8 +28,8 @@ const getEndpointFromSetup = (convoSetup: TConvoSetup, endpointsConfig: TEndpoin
const getEndpointFromLocalStorage = (endpointsConfig: TEndpointsConfig) => {
try {
const { lastConversationSetup } = getLocalStorageItems();
const { endpoint } = lastConversationSetup;
const isDefaultConfig = Object.values(endpointsConfig ?? {})?.every((value) => !value);
const { endpoint } = lastConversationSetup ?? { endpoint: null };
const isDefaultConfig = Object.values(endpointsConfig ?? {}).every((value) => !value);
if (isDefaultConfig && endpoint) {
return endpoint;
@ -35,7 +39,7 @@ const getEndpointFromLocalStorage = (endpointsConfig: TEndpointsConfig) => {
return endpoint;
}
return endpoint && endpointsConfig?.[endpoint ?? ''] ? endpoint : null;
return endpoint && endpointsConfig?.[endpoint] != null ? endpoint : null;
} catch (error) {
console.error(error);
return null;
@ -47,9 +51,12 @@ const getDefinedEndpoint = (endpointsConfig: TEndpointsConfig) => {
return endpoints.find((e) => Object.hasOwn(endpointsConfig ?? {}, e));
};
const getDefaultEndpoint = ({ convoSetup, endpointsConfig }: TDefaultEndpoint): EModelEndpoint => {
const getDefaultEndpoint = ({
convoSetup,
endpointsConfig,
}: TDefaultEndpoint): EModelEndpoint | string | undefined => {
return (
getEndpointFromSetup(convoSetup, endpointsConfig) ||
(getEndpointFromSetup(convoSetup, endpointsConfig) ?? '') ||
getEndpointFromLocalStorage(endpointsConfig) ||
getDefinedEndpoint(endpointsConfig)
);

View file

@ -12,6 +12,7 @@ export * from './messages';
export * from './languages';
export * from './endpoints';
export * from './sharedLink';
export * from './localStorage';
export * from './promptGroups';
export { default as cn } from './cn';
export { default as logger } from './logger';
@ -21,7 +22,6 @@ export { default as cleanupPreset } from './cleanupPreset';
export { default as validateIframe } from './validateIframe';
export { default as buildDefaultConvo } from './buildDefaultConvo';
export { default as getDefaultEndpoint } from './getDefaultEndpoint';
export { default as getLocalStorageItems } from './getLocalStorageItems';
export const languages = [
'java',

View file

@ -1,6 +1,6 @@
import { LocalStorageKeys, TConversation } from 'librechat-data-provider';
export default function getLocalStorageItems() {
export function getLocalStorageItems() {
const items = {
lastSelectedModel: localStorage.getItem(LocalStorageKeys.LAST_MODEL) ?? '',
lastSelectedTools: localStorage.getItem(LocalStorageKeys.LAST_TOOLS) ?? '',
@ -23,3 +23,23 @@ export default function getLocalStorageItems() {
lastConversationSetup,
};
}
export function clearLocalStorage(skipFirst?: boolean) {
const keys = Object.keys(localStorage);
keys.forEach((key) => {
if (skipFirst === true && key.endsWith('0')) {
return;
}
if (
key.startsWith(LocalStorageKeys.ASST_ID_PREFIX) ||
key.startsWith(LocalStorageKeys.AGENT_ID_PREFIX) ||
key.startsWith(LocalStorageKeys.LAST_CONVO_SETUP) ||
key === LocalStorageKeys.LAST_SPEC ||
key === LocalStorageKeys.LAST_TOOLS ||
key === LocalStorageKeys.LAST_MODEL ||
key === LocalStorageKeys.FILES_TO_DELETE
) {
localStorage.removeItem(key);
}
});
}

View file

@ -427,6 +427,16 @@ export enum EImageOutputType {
JPEG = 'jpeg',
}
const termsOfServiceSchema = z.object({
externalUrl: z.string().optional(),
openNewTab: z.boolean().optional(),
modalAcceptance: z.boolean().optional(),
modalTitle: z.string().optional(),
modalContent: z.string().or(z.array(z.string())).optional(),
});
export type TTermsOfService = z.infer<typeof termsOfServiceSchema>;
export const intefaceSchema = z
.object({
privacyPolicy: z
@ -435,15 +445,7 @@ export const intefaceSchema = z
openNewTab: z.boolean().optional(),
})
.optional(),
termsOfService: z
.object({
externalUrl: z.string().optional(),
openNewTab: z.boolean().optional(),
modalAcceptance: z.boolean().optional(),
modalTitle: z.string().optional(),
modalContent: z.string().or(z.array(z.string())).optional(),
})
.optional(),
termsOfService: termsOfServiceSchema.optional(),
endpointsMenu: z.boolean().optional(),
modelSelect: z.boolean().optional(),
parameters: z.boolean().optional(),

View file

@ -183,7 +183,7 @@ export const parseConvo = ({
possibleValues,
}: {
endpoint: EModelEndpoint;
endpointType?: EModelEndpoint;
endpointType?: EModelEndpoint | null;
conversation: Partial<s.TConversation | s.TPreset> | null;
possibleValues?: TPossibleValues;
// TODO: POC for default schema
@ -338,7 +338,7 @@ export const parseCompactConvo = ({
possibleValues,
}: {
endpoint?: EModelEndpoint;
endpointType?: EModelEndpoint;
endpointType?: EModelEndpoint | null;
conversation: Partial<s.TConversation | s.TPreset>;
possibleValues?: TPossibleValues;
// TODO: POC for default schema
@ -348,7 +348,7 @@ export const parseCompactConvo = ({
throw new Error(`undefined endpoint: ${endpoint}`);
}
let schema = compactEndpointSchemas[endpoint];
let schema = compactEndpointSchemas[endpoint] as CompactEndpointSchema | undefined;
if (!schema && !endpointType) {
throw new Error(`Unknown endpoint: ${endpoint}`);
@ -356,7 +356,11 @@ export const parseCompactConvo = ({
schema = compactEndpointSchemas[endpointType];
}
const convo = schema.parse(conversation) as s.TConversation;
if (!schema) {
throw new Error(`Unknown endpointType: ${endpointType}`);
}
const convo = schema.parse(conversation) as s.TConversation | null;
// const { models, secondaryModels } = possibleValues ?? {};
const { models } = possibleValues ?? {};

View file

@ -4,7 +4,7 @@ import type {
UseMutationResult,
QueryObserverResult,
} from '@tanstack/react-query';
import { initialModelsConfig, LocalStorageKeys } from '../config';
import { initialModelsConfig } from '../config';
import type { TStartupConfig } from '../config';
import { defaultOrderQuery } from '../types/assistants';
import * as dataService from '../data-service';
@ -302,27 +302,6 @@ export const useUpdateTokenCountMutation = (): UseMutationResult<
});
};
export const useLoginUserMutation = (): UseMutationResult<
t.TLoginResponse,
unknown,
t.TLoginUser,
unknown
> => {
const queryClient = useQueryClient();
return useMutation((payload: t.TLoginUser) => dataService.login(payload), {
onMutate: () => {
queryClient.removeQueries();
localStorage.removeItem(LocalStorageKeys.LAST_CONVO_SETUP);
localStorage.removeItem(`${LocalStorageKeys.LAST_CONVO_SETUP}_0`);
localStorage.removeItem(`${LocalStorageKeys.LAST_CONVO_SETUP}_1`);
localStorage.removeItem(LocalStorageKeys.LAST_MODEL);
localStorage.removeItem(LocalStorageKeys.LAST_TOOLS);
localStorage.removeItem(LocalStorageKeys.FILES_TO_DELETE);
// localStorage.removeItem('lastAssistant');
},
});
};
export const useRegisterUserMutation = (
options?: m.RegistrationOptions,
): UseMutationResult<t.TError, unknown, t.TRegisterUser, unknown> => {

View file

@ -16,8 +16,6 @@ export * from './schemas';
export type TMessages = TMessage[];
export type TMessagesAtom = TMessages | null;
/* TODO: Cleanup EndpointOption types */
export type TEndpointOption = {
endpoint: EModelEndpoint;