mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-10 19:44:23 +01:00
💾 chore: Enhance Local Storage Handling and Update MCP SDK (#6809)
* feat: Update MCP package version and dependencies; refactor ToolContentPart type * refactor: Change module type to commonjs and update rollup configuration, remove unused dev dependency * refactor: Change async calls to synchronous for MCP and FlowStateManager retrieval * chore: Add eslint disable comment for i18next rule in DropdownPopup component * fix: improve statefulness of mcp servers selected if some were removed since last session * feat: implement conversation storage cleanup functions and integrate them into mutation success handlers * feat: enhance storage condition logic in useLocalStorageAlt to prevent unnecessary local storage writes * refactor: streamline local storage update logic in useLocalStorageAlt
This commit is contained in:
parent
24c0433dcf
commit
e16a6190a5
18 changed files with 420 additions and 382 deletions
|
|
@ -17,6 +17,20 @@ import useLocalStorage from '~/hooks/useLocalStorageAlt';
|
|||
import { useVerifyAgentToolAuth } from '~/data-provider';
|
||||
import { ephemeralAgentByConvoId } from '~/store';
|
||||
|
||||
const storageCondition = (value: unknown, rawCurrentValue?: string | null) => {
|
||||
if (rawCurrentValue) {
|
||||
try {
|
||||
const currentValue = rawCurrentValue?.trim() ?? '';
|
||||
if (currentValue === 'true' && value === false) {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
return value !== undefined && value !== null && value !== '' && value !== false;
|
||||
};
|
||||
|
||||
function CodeInterpreter({ conversationId }: { conversationId?: string | null }) {
|
||||
const localize = useLocalize();
|
||||
const key = conversationId ?? Constants.NEW_CONVO;
|
||||
|
|
@ -55,6 +69,7 @@ function CodeInterpreter({ conversationId }: { conversationId?: string | null })
|
|||
`${LocalStorageKeys.LAST_CODE_TOGGLE_}${key}`,
|
||||
isCodeToggleEnabled,
|
||||
setValue,
|
||||
storageCondition,
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
|
|
|||
|
|
@ -8,14 +8,43 @@ import { ephemeralAgentByConvoId } from '~/store';
|
|||
import MCPIcon from '~/components/ui/MCPIcon';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const storageCondition = (value: unknown, rawCurrentValue?: string | null) => {
|
||||
if (rawCurrentValue) {
|
||||
try {
|
||||
const currentValue = rawCurrentValue?.trim() ?? '';
|
||||
if (currentValue.length > 2) {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
return Array.isArray(value) && value.length > 0;
|
||||
};
|
||||
|
||||
function MCPSelect({ conversationId }: { conversationId?: string | null }) {
|
||||
const localize = useLocalize();
|
||||
const hasSetFetched = useRef(false);
|
||||
const key = conversationId ?? Constants.NEW_CONVO;
|
||||
const hasSetFetched = useRef<string | null>(null);
|
||||
|
||||
const { data: mcpServerSet, isFetched } = useAvailableToolsQuery(EModelEndpoint.agents, {
|
||||
select: (data) => {
|
||||
const serverNames = new Set<string>();
|
||||
data.forEach((tool) => {
|
||||
if (tool.pluginKey.includes(Constants.mcp_delimiter)) {
|
||||
const parts = tool.pluginKey.split(Constants.mcp_delimiter);
|
||||
serverNames.add(parts[parts.length - 1]);
|
||||
}
|
||||
});
|
||||
return serverNames;
|
||||
},
|
||||
});
|
||||
|
||||
const [ephemeralAgent, setEphemeralAgent] = useRecoilState(ephemeralAgentByConvoId(key));
|
||||
const mcpState = useMemo(() => {
|
||||
return ephemeralAgent?.mcp ?? [];
|
||||
}, [ephemeralAgent?.mcp]);
|
||||
|
||||
const setSelectedValues = useCallback(
|
||||
(values: string[] | null | undefined) => {
|
||||
if (!values) {
|
||||
|
|
@ -35,33 +64,23 @@ function MCPSelect({ conversationId }: { conversationId?: string | null }) {
|
|||
`${LocalStorageKeys.LAST_MCP_}${key}`,
|
||||
mcpState,
|
||||
setSelectedValues,
|
||||
storageCondition,
|
||||
);
|
||||
const { data: mcpServers, isFetched } = useAvailableToolsQuery(EModelEndpoint.agents, {
|
||||
select: (data) => {
|
||||
const serverNames = new Set<string>();
|
||||
data.forEach((tool) => {
|
||||
if (tool.pluginKey.includes(Constants.mcp_delimiter)) {
|
||||
const parts = tool.pluginKey.split(Constants.mcp_delimiter);
|
||||
serverNames.add(parts[parts.length - 1]);
|
||||
}
|
||||
});
|
||||
return [...serverNames];
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (hasSetFetched.current) {
|
||||
if (hasSetFetched.current === key) {
|
||||
return;
|
||||
}
|
||||
if (!isFetched) {
|
||||
return;
|
||||
}
|
||||
hasSetFetched.current = true;
|
||||
if ((mcpServers?.length ?? 0) > 0) {
|
||||
hasSetFetched.current = key;
|
||||
if ((mcpServerSet?.size ?? 0) > 0) {
|
||||
setMCPValues(mcpValues.filter((mcp) => mcpServerSet?.has(mcp)));
|
||||
return;
|
||||
}
|
||||
setMCPValues([]);
|
||||
}, [isFetched, setMCPValues, mcpServers?.length]);
|
||||
}, [isFetched, setMCPValues, mcpServerSet, key, mcpValues]);
|
||||
|
||||
const renderSelectedValues = useCallback(
|
||||
(values: string[], placeholder?: string) => {
|
||||
|
|
@ -76,7 +95,11 @@ function MCPSelect({ conversationId }: { conversationId?: string | null }) {
|
|||
[localize],
|
||||
);
|
||||
|
||||
if (!mcpServers || mcpServers.length === 0) {
|
||||
const mcpServers = useMemo(() => {
|
||||
return Array.from(mcpServerSet ?? []);
|
||||
}, [mcpServerSet]);
|
||||
|
||||
if (!mcpServerSet || mcpServerSet.size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useClearConversationsMutation } from 'librechat-data-provider/react-que
|
|||
import { Label, Button, OGDialog, OGDialogTrigger, Spinner } from '~/components';
|
||||
import { useLocalize, useNewConvo } from '~/hooks';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { clearAllConversationStorage } from '~/utils';
|
||||
|
||||
export const ClearChats = () => {
|
||||
const localize = useLocalize();
|
||||
|
|
@ -15,6 +16,7 @@ export const ClearChats = () => {
|
|||
{},
|
||||
{
|
||||
onSuccess: () => {
|
||||
clearAllConversationStorage();
|
||||
newConversation();
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ const DropdownPopup: React.FC<DropdownProps> = ({
|
|||
)}
|
||||
{item.label}
|
||||
{item.kbd != null && (
|
||||
// eslint-disable-next-line i18next/no-literal-string
|
||||
<kbd className="ml-auto hidden font-sans text-xs text-black/50 group-hover:inline group-focus:inline dark:text-white/50">
|
||||
⌘{item.kbd}
|
||||
</kbd>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { MutationKeys, QueryKeys, dataService, request } from 'librechat-data-pr
|
|||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import type * as t from 'librechat-data-provider';
|
||||
import useClearStates from '~/hooks/Config/useClearStates';
|
||||
import { clearAllConversationStorage } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
/* login/logout */
|
||||
|
|
@ -79,6 +80,7 @@ export const useDeleteUserMutation = (
|
|||
onSuccess: (...args) => {
|
||||
resetDefaultPreset();
|
||||
clearStates();
|
||||
clearAllConversationStorage();
|
||||
queryClient.removeQueries();
|
||||
options?.onSuccess?.(...args);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
updateConvoFields,
|
||||
updateConversation,
|
||||
deleteConversation,
|
||||
clearConversationStorage,
|
||||
} from '~/utils';
|
||||
|
||||
export type TGenTitleMutation = UseMutationResult<
|
||||
|
|
@ -562,6 +563,7 @@ export const useDeleteConversationMutation = (
|
|||
const current = queryClient.getQueryData<t.ConversationData>([QueryKeys.allConversations]);
|
||||
refetch({ refetchPage: (page, index) => index === (current?.pages.length ?? 1) - 1 });
|
||||
onSuccess?.(_data, vars, context);
|
||||
clearConversationStorage(conversationId);
|
||||
},
|
||||
..._options,
|
||||
},
|
||||
|
|
@ -897,7 +899,7 @@ export const useUploadAssistantAvatarMutation = (
|
|||
unknown // context
|
||||
> => {
|
||||
return useMutation([MutationKeys.assistantAvatarUpload], {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
mutationFn: ({ postCreation, ...variables }: t.AssistantAvatarVariables) =>
|
||||
dataService.uploadAssistantAvatar(variables),
|
||||
...(options || {}),
|
||||
|
|
|
|||
|
|
@ -11,13 +11,16 @@ export default function useLocalStorage<T>(
|
|||
key: string,
|
||||
defaultValue: T,
|
||||
globalSetState?: (value: T) => void,
|
||||
storageCondition?: (value: T, rawCurrentValue?: string | null) => boolean,
|
||||
): [T, (value: T) => void] {
|
||||
const [value, setValue] = useState(defaultValue);
|
||||
|
||||
useEffect(() => {
|
||||
const item = localStorage.getItem(key);
|
||||
|
||||
if (!item) {
|
||||
if (!item && !storageCondition) {
|
||||
localStorage.setItem(key, JSON.stringify(defaultValue));
|
||||
} else if (!item && storageCondition && storageCondition(defaultValue)) {
|
||||
localStorage.setItem(key, JSON.stringify(defaultValue));
|
||||
}
|
||||
|
||||
|
|
@ -47,9 +50,14 @@ export default function useLocalStorage<T>(
|
|||
const setValueWrap = (value: T) => {
|
||||
try {
|
||||
setValue(value);
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new StorageEvent('storage', { key }));
|
||||
const storeLocal = () => {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
window?.dispatchEvent(new StorageEvent('storage', { key }));
|
||||
};
|
||||
if (!storageCondition) {
|
||||
storeLocal();
|
||||
} else if (storageCondition(value, localStorage.getItem(key))) {
|
||||
storeLocal();
|
||||
}
|
||||
globalSetState?.(value);
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { LocalStorageKeys, TConversation } from 'librechat-data-provider';
|
||||
import { LocalStorageKeys, TConversation, isUUID } from 'librechat-data-provider';
|
||||
|
||||
export function getLocalStorageItems() {
|
||||
const items = {
|
||||
|
|
@ -45,3 +45,36 @@ export function clearLocalStorage(skipFirst?: boolean) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function clearConversationStorage(conversationId?: string | null) {
|
||||
if (!conversationId) {
|
||||
return;
|
||||
}
|
||||
if (!isUUID.safeParse(conversationId)?.success) {
|
||||
console.warn(
|
||||
`Conversation ID ${conversationId} is not a valid UUID. Skipping local storage cleanup.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const keys = Object.keys(localStorage);
|
||||
keys.forEach((key) => {
|
||||
if (key.includes(conversationId)) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
export function clearAllConversationStorage() {
|
||||
const keys = Object.keys(localStorage);
|
||||
keys.forEach((key) => {
|
||||
if (
|
||||
key.startsWith(LocalStorageKeys.LAST_MCP_) ||
|
||||
key.startsWith(LocalStorageKeys.LAST_CODE_TOGGLE_) ||
|
||||
key.startsWith(LocalStorageKeys.TEXT_DRAFT) ||
|
||||
key.startsWith(LocalStorageKeys.ASST_ID_PREFIX) ||
|
||||
key.startsWith(LocalStorageKeys.AGENT_ID_PREFIX) ||
|
||||
key.startsWith(LocalStorageKeys.LAST_CONVO_SETUP)
|
||||
) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue