mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
♻️ 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:
parent
24beda3d69
commit
aa80e4594e
45 changed files with 378 additions and 434 deletions
14
client/src/Providers/SetConvoContext.tsx
Normal file
14
client/src/Providers/SetConvoContext.tsx
Normal 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);
|
||||
|
|
@ -18,3 +18,4 @@ export * from './AnnouncerContext';
|
|||
export * from './AgentsMapContext';
|
||||
export * from './CodeBlockContext';
|
||||
export * from './ToolCallsMapContext';
|
||||
export * from './SetConvoContext';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 || {};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
1
client/src/data-provider/Auth/index.ts
Normal file
1
client/src/data-provider/Auth/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from './mutations';
|
||||
65
client/src/data-provider/Auth/mutations.ts
Normal file
65
client/src/data-provider/Auth/mutations.ts
Normal 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);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export * from './Auth';
|
||||
export * from './Agents';
|
||||
export * from './Files';
|
||||
export * from './Tools';
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
export { default as useAppStartup } from './useAppStartup';
|
||||
export { default as useClearStates } from './useClearStates';
|
||||
|
|
|
|||
52
client/src/hooks/Config/useClearStates.ts
Normal file
52
client/src/hooks/Config/useClearStates.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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: () => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ?? '';
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,33 +56,35 @@ export default function Root() {
|
|||
}
|
||||
|
||||
return (
|
||||
<SearchContext.Provider value={search}>
|
||||
<FileMapContext.Provider value={fileMap}>
|
||||
<AssistantsMapContext.Provider value={assistantsMap}>
|
||||
<AgentsMapContext.Provider value={agentsMap}>
|
||||
<Banner onHeightChange={setBannerHeight} />
|
||||
<div className="flex" style={{ height: `calc(100dvh - ${bannerHeight}px)` }}>
|
||||
<div className="relative z-0 flex h-full w-full overflow-hidden">
|
||||
<Nav navVisible={navVisible} setNavVisible={setNavVisible} />
|
||||
<div className="relative flex h-full max-w-full flex-1 flex-col overflow-hidden">
|
||||
<MobileNav setNavVisible={setNavVisible} />
|
||||
<Outlet context={{ navVisible, setNavVisible } satisfies ContextType} />
|
||||
<SetConvoProvider>
|
||||
<SearchContext.Provider value={search}>
|
||||
<FileMapContext.Provider value={fileMap}>
|
||||
<AssistantsMapContext.Provider value={assistantsMap}>
|
||||
<AgentsMapContext.Provider value={agentsMap}>
|
||||
<Banner onHeightChange={setBannerHeight} />
|
||||
<div className="flex" style={{ height: `calc(100dvh - ${bannerHeight}px)` }}>
|
||||
<div className="relative z-0 flex h-full w-full overflow-hidden">
|
||||
<Nav navVisible={navVisible} setNavVisible={setNavVisible} />
|
||||
<div className="relative flex h-full max-w-full flex-1 flex-col overflow-hidden">
|
||||
<MobileNav setNavVisible={setNavVisible} />
|
||||
<Outlet context={{ navVisible, setNavVisible } satisfies ContextType} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AgentsMapContext.Provider>
|
||||
{config?.interface?.termsOfService?.modalAcceptance === true && (
|
||||
<TermsAndConditionsModal
|
||||
open={showTerms}
|
||||
onOpenChange={setShowTerms}
|
||||
onAccept={handleAcceptTerms}
|
||||
onDecline={handleDeclineTerms}
|
||||
title={config.interface.termsOfService.modalTitle}
|
||||
modalContent={config.interface.termsOfService.modalContent}
|
||||
/>
|
||||
)}
|
||||
</AssistantsMapContext.Provider>
|
||||
</FileMapContext.Provider>
|
||||
</SearchContext.Provider>
|
||||
</AgentsMapContext.Provider>
|
||||
{config?.interface?.termsOfService?.modalAcceptance === true && (
|
||||
<TermsAndConditionsModal
|
||||
open={showTerms}
|
||||
onOpenChange={setShowTerms}
|
||||
onAccept={handleAcceptTerms}
|
||||
onDecline={handleDeclineTerms}
|
||||
title={config.interface.termsOfService.modalTitle}
|
||||
modalContent={config.interface.termsOfService.modalContent}
|
||||
/>
|
||||
)}
|
||||
</AssistantsMapContext.Provider>
|
||||
</FileMapContext.Provider>
|
||||
</SearchContext.Provider>
|
||||
</SetConvoProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
import { atomWithLocalStorage } from './utils';
|
||||
|
||||
const hideBannerHint = atomWithLocalStorage('hideBannerHint', [] as string[]);
|
||||
|
||||
export default { hideBannerHint };
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { atom } from 'recoil';
|
||||
|
||||
const refreshConversationsHint = atom<number>({
|
||||
key: 'refreshConversationsHint',
|
||||
default: 1,
|
||||
});
|
||||
|
||||
export default { refreshConversationsHint };
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
12
client/src/store/misc.ts
Normal 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 };
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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 ?? {};
|
||||
|
||||
|
|
|
|||
|
|
@ -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> => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue