mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-23 02:36:12 +01:00
* 🗑️ chore: Remove unused Legacy Provider clients and related helpers * Deleted OpenAIClient and GoogleClient files along with their associated tests. * Removed references to these clients in the clients index file. * Cleaned up typedefs by removing the OpenAISpecClient export. * Updated chat controllers to use the OpenAI SDK directly instead of the removed client classes. * chore/remove-openapi-specs * 🗑️ chore: Remove unused mergeSort and misc utility functions * Deleted mergeSort.js and misc.js files as they are no longer needed. * Removed references to cleanUpPrimaryKeyValue in messages.js and adjusted related logic. * Updated mongoMeili.ts to eliminate local implementations of removed functions. * chore: remove legacy endpoints * chore: remove all plugins endpoint related code * chore: remove unused prompt handling code and clean up imports * Deleted handleInputs.js and instructions.js files as they are no longer needed. * Removed references to these files in the prompts index.js. * Updated docker-compose.yml to simplify reverse proxy configuration. * chore: remove unused LightningIcon import from Icons.tsx * chore: clean up translation.json by removing deprecated and unused keys * chore: update Jest configuration and remove unused mock file * Simplified the setupFiles array in jest.config.js by removing the fetchEventSource mock. * Deleted the fetchEventSource.js mock file as it is no longer needed. * fix: simplify endpoint type check in Landing and ConversationStarters components * Updated the endpoint type check to use strict equality for better clarity and performance. * Ensured consistency in the handling of the azureOpenAI endpoint across both components. * chore: remove unused dependencies from package.json and package-lock.json * chore: remove legacy EditController, associated routes and imports * chore: update banResponse logic to refine request handling for banned users * chore: remove unused validateEndpoint middleware and its references * chore: remove unused 'res' parameter from initializeClient in multiple endpoint files * chore: remove unused 'isSmallScreen' prop from BookmarkNav and NewChat components; clean up imports in ArchivedChatsTable and useSetIndexOptions hooks; enhance localization in PromptVersions * chore: remove unused import of Constants and TMessage from MobileNav; retain only necessary QueryKeys import * chore: remove unused TResPlugin type and related references; clean up imports in types and schemas
421 lines
12 KiB
TypeScript
421 lines
12 KiB
TypeScript
import { useEffect } from 'react';
|
|
import {
|
|
atom,
|
|
selector,
|
|
atomFamily,
|
|
DefaultValue,
|
|
selectorFamily,
|
|
useRecoilState,
|
|
useRecoilValue,
|
|
useSetRecoilState,
|
|
useRecoilCallback,
|
|
} from 'recoil';
|
|
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, createChatSearchParams } from '~/utils';
|
|
import { createSearchParams } from 'react-router-dom';
|
|
|
|
const latestMessageKeysAtom = atom<(string | number)[]>({
|
|
key: 'latestMessageKeys',
|
|
default: [],
|
|
});
|
|
|
|
const submissionKeysAtom = atom<(string | number)[]>({
|
|
key: 'submissionKeys',
|
|
default: [],
|
|
});
|
|
|
|
const latestMessageFamily = atomFamily<TMessage | null, string | number | null>({
|
|
key: 'latestMessageByIndex',
|
|
default: null,
|
|
effects: [
|
|
({ onSet, node }) => {
|
|
onSet(async (newValue) => {
|
|
const key = Number(node.key.split(Constants.COMMON_DIVIDER)[1]);
|
|
logger.log('Recoil Effect: Setting latestMessage', { key, newValue });
|
|
});
|
|
},
|
|
] as const,
|
|
});
|
|
|
|
const submissionByIndex = atomFamily<TSubmission | null, string | number>({
|
|
key: 'submissionByIndex',
|
|
default: null,
|
|
});
|
|
|
|
const latestMessageKeysSelector = selector<(string | number)[]>({
|
|
key: 'latestMessageKeysSelector',
|
|
get: ({ get }) => {
|
|
const keys = get(conversationKeysAtom);
|
|
return keys.filter((key) => get(latestMessageFamily(key)) !== null);
|
|
},
|
|
set: ({ set }, newKeys) => {
|
|
logger.log('setting latestMessageKeys', { newKeys });
|
|
set(latestMessageKeysAtom, newKeys);
|
|
},
|
|
});
|
|
|
|
const submissionKeysSelector = selector<(string | number)[]>({
|
|
key: 'submissionKeysSelector',
|
|
get: ({ get }) => {
|
|
const keys = get(conversationKeysAtom);
|
|
return keys.filter((key) => get(submissionByIndex(key)) !== null);
|
|
},
|
|
set: ({ set }, newKeys) => {
|
|
logger.log('setting submissionKeysAtom', newKeys);
|
|
set(submissionKeysAtom, newKeys);
|
|
},
|
|
});
|
|
|
|
const conversationByIndex = atomFamily<TConversation | null, string | number>({
|
|
key: 'conversationByIndex',
|
|
default: null,
|
|
effects: [
|
|
({ onSet, node }) => {
|
|
onSet(async (newValue, oldValue) => {
|
|
const index = Number(node.key.split('__')[1]);
|
|
logger.log('conversation', 'Setting conversation:', { index, newValue, oldValue });
|
|
if (newValue?.assistant_id != null && newValue.assistant_id) {
|
|
localStorage.setItem(
|
|
`${LocalStorageKeys.ASST_ID_PREFIX}${index}${newValue.endpoint}`,
|
|
newValue.assistant_id,
|
|
);
|
|
}
|
|
if (newValue?.agent_id != null && newValue.agent_id) {
|
|
localStorage.setItem(`${LocalStorageKeys.AGENT_ID_PREFIX}${index}`, newValue.agent_id);
|
|
}
|
|
if (newValue?.spec != null && newValue.spec) {
|
|
localStorage.setItem(LocalStorageKeys.LAST_SPEC, newValue.spec);
|
|
}
|
|
if (newValue?.tools && Array.isArray(newValue.tools)) {
|
|
localStorage.setItem(
|
|
LocalStorageKeys.LAST_TOOLS,
|
|
JSON.stringify(newValue.tools.filter((el) => !!el)),
|
|
);
|
|
}
|
|
|
|
if (!newValue) {
|
|
return;
|
|
}
|
|
|
|
storeEndpointSettings(newValue);
|
|
localStorage.setItem(
|
|
`${LocalStorageKeys.LAST_CONVO_SETUP}_${index}`,
|
|
JSON.stringify(newValue),
|
|
);
|
|
|
|
const disableParams = newValue.disableParams === true;
|
|
const shouldUpdateParams =
|
|
index === 0 &&
|
|
!disableParams &&
|
|
newValue.createdAt === '' &&
|
|
JSON.stringify(newValue) !== JSON.stringify(oldValue) &&
|
|
(oldValue as TConversation)?.conversationId === Constants.NEW_CONVO;
|
|
|
|
if (shouldUpdateParams) {
|
|
const newParams = createChatSearchParams(newValue);
|
|
const searchParams = createSearchParams(newParams);
|
|
const url = `${window.location.pathname}?${searchParams.toString()}`;
|
|
window.history.pushState({}, '', url);
|
|
}
|
|
});
|
|
},
|
|
] as const,
|
|
});
|
|
|
|
const filesByIndex = atomFamily<Map<string, ExtendedFile>, string | number>({
|
|
key: 'filesByIndex',
|
|
default: new Map(),
|
|
});
|
|
|
|
const conversationKeysAtom = atom<(string | number)[]>({
|
|
key: 'conversationKeys',
|
|
default: [],
|
|
});
|
|
|
|
const allConversationsSelector = selector({
|
|
key: 'allConversationsSelector',
|
|
get: ({ get }) => {
|
|
const keys = get(conversationKeysAtom);
|
|
return keys.map((key) => get(conversationByIndex(key))).map((convo) => convo?.conversationId);
|
|
},
|
|
});
|
|
|
|
const presetByIndex = atomFamily<TPreset | null, string | number>({
|
|
key: 'presetByIndex',
|
|
default: null,
|
|
});
|
|
|
|
const textByIndex = atomFamily<string, string | number>({
|
|
key: 'textByIndex',
|
|
default: '',
|
|
});
|
|
|
|
const showStopButtonByIndex = atomFamily<boolean, string | number>({
|
|
key: 'showStopButtonByIndex',
|
|
default: false,
|
|
});
|
|
|
|
const abortScrollFamily = atomFamily<boolean, string | number>({
|
|
key: 'abortScrollByIndex',
|
|
default: false,
|
|
effects: [
|
|
({ onSet, node }) => {
|
|
onSet(async (newValue) => {
|
|
const key = Number(node.key.split(Constants.COMMON_DIVIDER)[1]);
|
|
logger.log('message_scrolling', 'Recoil Effect: Setting abortScrollByIndex', {
|
|
key,
|
|
newValue,
|
|
});
|
|
});
|
|
},
|
|
] as const,
|
|
});
|
|
|
|
const isSubmittingFamily = atomFamily({
|
|
key: 'isSubmittingByIndex',
|
|
default: false,
|
|
effects: [
|
|
({ onSet, node }) => {
|
|
onSet(async (newValue) => {
|
|
const key = Number(node.key.split(Constants.COMMON_DIVIDER)[1]);
|
|
logger.log('message_stream', 'Recoil Effect: Setting isSubmittingByIndex', {
|
|
key,
|
|
newValue,
|
|
});
|
|
});
|
|
},
|
|
],
|
|
});
|
|
|
|
const anySubmittingSelector = selector<boolean>({
|
|
key: 'anySubmittingSelector',
|
|
get: ({ get }) => {
|
|
const keys = get(conversationKeysAtom);
|
|
return keys.some((key) => get(isSubmittingFamily(key)) === true);
|
|
},
|
|
});
|
|
|
|
const optionSettingsFamily = atomFamily<TOptionSettings, string | number>({
|
|
key: 'optionSettingsByIndex',
|
|
default: {},
|
|
});
|
|
|
|
const showPopoverFamily = atomFamily({
|
|
key: 'showPopoverByIndex',
|
|
default: false,
|
|
});
|
|
|
|
const activePromptByIndex = atomFamily<string | undefined, string | number | null>({
|
|
key: 'activePromptByIndex',
|
|
default: undefined,
|
|
});
|
|
|
|
const showMentionPopoverFamily = atomFamily<boolean, string | number | null>({
|
|
key: 'showMentionPopoverByIndex',
|
|
default: false,
|
|
});
|
|
|
|
const showPlusPopoverFamily = atomFamily<boolean, string | number | null>({
|
|
key: 'showPlusPopoverByIndex',
|
|
default: false,
|
|
});
|
|
|
|
const showPromptsPopoverFamily = atomFamily<boolean, string | number | null>({
|
|
key: 'showPromptsPopoverByIndex',
|
|
default: false,
|
|
});
|
|
|
|
const globalAudioURLFamily = atomFamily<string | null, string | number | null>({
|
|
key: 'globalAudioURLByIndex',
|
|
default: null,
|
|
});
|
|
|
|
const globalAudioFetchingFamily = atomFamily<boolean, string | number | null>({
|
|
key: 'globalAudioisFetchingByIndex',
|
|
default: false,
|
|
});
|
|
|
|
const globalAudioPlayingFamily = atomFamily<boolean, string | number | null>({
|
|
key: 'globalAudioisPlayingByIndex',
|
|
default: false,
|
|
});
|
|
|
|
const activeRunFamily = atomFamily<string | null, string | number | null>({
|
|
key: 'activeRunByIndex',
|
|
default: null,
|
|
});
|
|
|
|
const audioRunFamily = atomFamily<string | null, string | number | null>({
|
|
key: 'audioRunByIndex',
|
|
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));
|
|
|
|
useEffect(() => {
|
|
if (!keys.includes(key)) {
|
|
setKeys([...keys, key]);
|
|
}
|
|
}, [key, keys, setKeys]);
|
|
|
|
return { hasSetConversation, conversation, setConversation };
|
|
}
|
|
|
|
function useClearConvoState() {
|
|
/** Clears all active conversations. Pass `true` to skip the first or root conversation */
|
|
const clearAllConversations = useRecoilCallback(
|
|
({ reset, snapshot }) =>
|
|
async (skipFirst?: boolean) => {
|
|
const conversationKeys = await snapshot.getPromise(conversationKeysAtom);
|
|
|
|
for (const conversationKey of conversationKeys) {
|
|
if (skipFirst === true && conversationKey == 0) {
|
|
continue;
|
|
}
|
|
|
|
reset(conversationByIndex(conversationKey));
|
|
|
|
const conversation = await snapshot.getPromise(conversationByIndex(conversationKey));
|
|
if (conversation) {
|
|
reset(latestMessageFamily(conversationKey));
|
|
}
|
|
}
|
|
|
|
reset(conversationKeysAtom);
|
|
},
|
|
[],
|
|
);
|
|
|
|
return clearAllConversations;
|
|
}
|
|
|
|
const conversationByKeySelector = selectorFamily({
|
|
key: 'conversationByKeySelector',
|
|
get:
|
|
(index: string | number) =>
|
|
({ get }) => {
|
|
const conversation = get(conversationByIndex(index));
|
|
return conversation;
|
|
},
|
|
});
|
|
|
|
function useClearSubmissionState() {
|
|
const clearAllSubmissions = useRecoilCallback(
|
|
({ reset, set, snapshot }) =>
|
|
async (skipFirst?: boolean) => {
|
|
const submissionKeys = await snapshot.getPromise(submissionKeysSelector);
|
|
logger.log('submissionKeys', submissionKeys);
|
|
|
|
for (const key of submissionKeys) {
|
|
if (skipFirst === true && key == 0) {
|
|
continue;
|
|
}
|
|
|
|
logger.log('resetting submission', key);
|
|
reset(submissionByIndex(key));
|
|
}
|
|
|
|
set(submissionKeysSelector, []);
|
|
},
|
|
[],
|
|
);
|
|
|
|
return clearAllSubmissions;
|
|
}
|
|
|
|
function useClearLatestMessages(context?: string) {
|
|
const clearAllLatestMessages = useRecoilCallback(
|
|
({ reset, set, snapshot }) =>
|
|
async (skipFirst?: boolean) => {
|
|
const latestMessageKeys = await snapshot.getPromise(latestMessageKeysSelector);
|
|
logger.log('[clearAllLatestMessages] latestMessageKeys', latestMessageKeys);
|
|
if (context != null && context) {
|
|
logger.log(`[clearAllLatestMessages] context: ${context}`);
|
|
}
|
|
|
|
for (const key of latestMessageKeys) {
|
|
if (skipFirst === true && key == 0) {
|
|
continue;
|
|
}
|
|
|
|
logger.log(`[clearAllLatestMessages] resetting latest message; key: ${key}`);
|
|
reset(latestMessageFamily(key));
|
|
}
|
|
|
|
set(latestMessageKeysSelector, []);
|
|
},
|
|
[],
|
|
);
|
|
|
|
return clearAllLatestMessages;
|
|
}
|
|
|
|
const updateConversationSelector = selectorFamily({
|
|
key: 'updateConversationSelector',
|
|
get: () => () => null as Partial<TConversation> | null,
|
|
set:
|
|
(conversationId: string) =>
|
|
({ set, get }, newPartialConversation) => {
|
|
if (newPartialConversation instanceof DefaultValue) {
|
|
return;
|
|
}
|
|
|
|
const keys = get(conversationKeysAtom);
|
|
keys.forEach((key) => {
|
|
set(conversationByIndex(key), (prevConversation) => {
|
|
if (prevConversation && prevConversation.conversationId === conversationId) {
|
|
return {
|
|
...prevConversation,
|
|
...newPartialConversation,
|
|
};
|
|
}
|
|
return prevConversation;
|
|
});
|
|
});
|
|
},
|
|
});
|
|
|
|
export default {
|
|
conversationKeysAtom,
|
|
conversationByIndex,
|
|
filesByIndex,
|
|
presetByIndex,
|
|
submissionByIndex,
|
|
textByIndex,
|
|
showStopButtonByIndex,
|
|
abortScrollFamily,
|
|
isSubmittingFamily,
|
|
optionSettingsFamily,
|
|
showPopoverFamily,
|
|
latestMessageFamily,
|
|
messagesSiblingIdxFamily,
|
|
anySubmittingSelector,
|
|
allConversationsSelector,
|
|
conversationByKeySelector,
|
|
useClearConvoState,
|
|
useCreateConversationAtom,
|
|
showMentionPopoverFamily,
|
|
globalAudioURLFamily,
|
|
activeRunFamily,
|
|
audioRunFamily,
|
|
globalAudioPlayingFamily,
|
|
globalAudioFetchingFamily,
|
|
showPlusPopoverFamily,
|
|
activePromptByIndex,
|
|
useClearSubmissionState,
|
|
useClearLatestMessages,
|
|
showPromptsPopoverFamily,
|
|
updateConversationSelector,
|
|
};
|