mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-11 10:32:37 +01:00
feat: implement useMCPSelect hook for managing MCP selection state, refactor MCPSelect component to utilize new hook
This commit is contained in:
parent
7a190ee33a
commit
5ba51d99fe
3 changed files with 96 additions and 69 deletions
|
|
@ -1,17 +1,14 @@
|
||||||
import React, { memo, useRef, useMemo, useEffect, useCallback, useState } from 'react';
|
import React, { memo, useCallback, useState } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
|
||||||
import { Settings2 } from 'lucide-react';
|
import { Settings2 } from 'lucide-react';
|
||||||
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query';
|
||||||
import { Constants, EModelEndpoint, LocalStorageKeys } from 'librechat-data-provider';
|
import { Constants, EModelEndpoint } from 'librechat-data-provider';
|
||||||
import type { TPlugin, TPluginAuthConfig, TUpdateUserPlugins } from 'librechat-data-provider';
|
import type { TPlugin, TPluginAuthConfig, TUpdateUserPlugins } from 'librechat-data-provider';
|
||||||
import MCPConfigDialog, { type ConfigFieldDetail } from '~/components/ui/MCPConfigDialog';
|
import MCPConfigDialog, { type ConfigFieldDetail } from '~/components/ui/MCPConfigDialog';
|
||||||
import { useToastContext, useBadgeRowContext } from '~/Providers';
|
import { useToastContext, useBadgeRowContext } from '~/Providers';
|
||||||
import { useAvailableToolsQuery } from '~/data-provider';
|
import { useAvailableToolsQuery } from '~/data-provider';
|
||||||
import useLocalStorage from '~/hooks/useLocalStorageAlt';
|
import { useLocalize, useMCPSelect } from '~/hooks';
|
||||||
import MultiSelect from '~/components/ui/MultiSelect';
|
import MultiSelect from '~/components/ui/MultiSelect';
|
||||||
import { ephemeralAgentByConvoId } from '~/store';
|
|
||||||
import MCPIcon from '~/components/ui/MCPIcon';
|
import MCPIcon from '~/components/ui/MCPIcon';
|
||||||
import { useLocalize } from '~/hooks';
|
|
||||||
|
|
||||||
interface McpServerInfo {
|
interface McpServerInfo {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -26,26 +23,10 @@ const getBaseMCPPluginKey = (fullPluginKey: string): string => {
|
||||||
return Constants.mcp_prefix + parts[parts.length - 1];
|
return Constants.mcp_prefix + parts[parts.length - 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
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() {
|
function MCPSelect() {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { showToast } = useToastContext();
|
const { showToast } = useToastContext();
|
||||||
const { conversationId } = useBadgeRowContext();
|
const { conversationId } = useBadgeRowContext();
|
||||||
const key = conversationId ?? Constants.NEW_CONVO;
|
|
||||||
const hasSetFetched = useRef<string | null>(null);
|
|
||||||
const [isConfigModalOpen, setIsConfigModalOpen] = useState(false);
|
const [isConfigModalOpen, setIsConfigModalOpen] = useState(false);
|
||||||
const [selectedToolForConfig, setSelectedToolForConfig] = useState<McpServerInfo | null>(null);
|
const [selectedToolForConfig, setSelectedToolForConfig] = useState<McpServerInfo | null>(null);
|
||||||
|
|
||||||
|
|
@ -71,6 +52,12 @@ function MCPSelect() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { mcpValues, setMCPValues, mcpServerNames } = useMCPSelect({
|
||||||
|
conversationId,
|
||||||
|
mcpToolDetails,
|
||||||
|
isFetched,
|
||||||
|
});
|
||||||
|
|
||||||
const updateUserPluginsMutation = useUpdateUserPluginsMutation({
|
const updateUserPluginsMutation = useUpdateUserPluginsMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setIsConfigModalOpen(false);
|
setIsConfigModalOpen(false);
|
||||||
|
|
@ -85,48 +72,6 @@ function MCPSelect() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const [ephemeralAgent, setEphemeralAgent] = useRecoilState(ephemeralAgentByConvoId(key));
|
|
||||||
const mcpState = useMemo(() => {
|
|
||||||
return ephemeralAgent?.mcp ?? [];
|
|
||||||
}, [ephemeralAgent?.mcp]);
|
|
||||||
|
|
||||||
const setSelectedValues = useCallback(
|
|
||||||
(values: string[] | null | undefined) => {
|
|
||||||
if (!values) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!Array.isArray(values)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setEphemeralAgent((prev) => ({
|
|
||||||
...prev,
|
|
||||||
mcp: values,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[setEphemeralAgent],
|
|
||||||
);
|
|
||||||
const [mcpValues, setMCPValues] = useLocalStorage<string[]>(
|
|
||||||
`${LocalStorageKeys.LAST_MCP_}${key}`,
|
|
||||||
mcpState,
|
|
||||||
setSelectedValues,
|
|
||||||
storageCondition,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (hasSetFetched.current === key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isFetched) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hasSetFetched.current = key;
|
|
||||||
if ((mcpToolDetails?.length ?? 0) > 0) {
|
|
||||||
setMCPValues(mcpValues.filter((mcp) => mcpToolDetails?.some((tool) => tool.name === mcp)));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setMCPValues([]);
|
|
||||||
}, [isFetched, setMCPValues, mcpToolDetails, key, mcpValues]);
|
|
||||||
|
|
||||||
const renderSelectedValues = useCallback(
|
const renderSelectedValues = useCallback(
|
||||||
(values: string[], placeholder?: string) => {
|
(values: string[], placeholder?: string) => {
|
||||||
if (values.length === 0) {
|
if (values.length === 0) {
|
||||||
|
|
@ -140,10 +85,6 @@ function MCPSelect() {
|
||||||
[localize],
|
[localize],
|
||||||
);
|
);
|
||||||
|
|
||||||
const mcpServerNames = useMemo(() => {
|
|
||||||
return (mcpToolDetails ?? []).map((tool) => tool.name);
|
|
||||||
}, [mcpToolDetails]);
|
|
||||||
|
|
||||||
const handleConfigSave = useCallback(
|
const handleConfigSave = useCallback(
|
||||||
(targetName: string, authData: Record<string, string>) => {
|
(targetName: string, authData: Record<string, string>) => {
|
||||||
if (selectedToolForConfig && selectedToolForConfig.name === targetName) {
|
if (selectedToolForConfig && selectedToolForConfig.name === targetName) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
export * from './useMCPSelect';
|
||||||
|
export * from './useToolToggle';
|
||||||
export { default as useAuthCodeTool } from './useAuthCodeTool';
|
export { default as useAuthCodeTool } from './useAuthCodeTool';
|
||||||
export { default as usePluginInstall } from './usePluginInstall';
|
export { default as usePluginInstall } from './usePluginInstall';
|
||||||
export { default as useCodeApiKeyForm } from './useCodeApiKeyForm';
|
export { default as useCodeApiKeyForm } from './useCodeApiKeyForm';
|
||||||
export { default as useSearchApiKeyForm } from './useSearchApiKeyForm';
|
export { default as useSearchApiKeyForm } from './useSearchApiKeyForm';
|
||||||
export { default as usePluginDialogHelpers } from './usePluginDialogHelpers';
|
export { default as usePluginDialogHelpers } from './usePluginDialogHelpers';
|
||||||
export { useToolToggle } from './useToolToggle';
|
|
||||||
|
|
|
||||||
85
client/src/hooks/Plugins/useMCPSelect.ts
Normal file
85
client/src/hooks/Plugins/useMCPSelect.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { useRef, useEffect, useCallback, useMemo } from 'react';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
|
import { Constants, LocalStorageKeys } from 'librechat-data-provider';
|
||||||
|
import useLocalStorage from '~/hooks/useLocalStorageAlt';
|
||||||
|
import { ephemeralAgentByConvoId } from '~/store';
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UseMCPSelectOptions {
|
||||||
|
conversationId?: string | null;
|
||||||
|
mcpToolDetails?: Array<{ name: string }> | null;
|
||||||
|
isFetched: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMCPSelect({ conversationId, mcpToolDetails, isFetched }: UseMCPSelectOptions) {
|
||||||
|
const key = conversationId ?? Constants.NEW_CONVO;
|
||||||
|
const hasSetFetched = useRef<string | null>(null);
|
||||||
|
const [ephemeralAgent, setEphemeralAgent] = useRecoilState(ephemeralAgentByConvoId(key));
|
||||||
|
|
||||||
|
const mcpState = useMemo(() => {
|
||||||
|
return ephemeralAgent?.mcp ?? [];
|
||||||
|
}, [ephemeralAgent?.mcp]);
|
||||||
|
|
||||||
|
const setSelectedValues = useCallback(
|
||||||
|
(values: string[] | null | undefined) => {
|
||||||
|
if (!values) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(values)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setEphemeralAgent((prev) => ({
|
||||||
|
...prev,
|
||||||
|
mcp: values,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[setEphemeralAgent],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [mcpValues, setMCPValues] = useLocalStorage<string[]>(
|
||||||
|
`${LocalStorageKeys.LAST_MCP_}${key}`,
|
||||||
|
mcpState,
|
||||||
|
setSelectedValues,
|
||||||
|
storageCondition,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasSetFetched.current === key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isFetched) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hasSetFetched.current = key;
|
||||||
|
if ((mcpToolDetails?.length ?? 0) > 0) {
|
||||||
|
setMCPValues(mcpValues.filter((mcp) => mcpToolDetails?.some((tool) => tool.name === mcp)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMCPValues([]);
|
||||||
|
}, [isFetched, setMCPValues, mcpToolDetails, key, mcpValues]);
|
||||||
|
|
||||||
|
const mcpServerNames = useMemo(() => {
|
||||||
|
return (mcpToolDetails ?? []).map((tool) => tool.name);
|
||||||
|
}, [mcpToolDetails]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
mcpValues,
|
||||||
|
setMCPValues,
|
||||||
|
mcpServerNames,
|
||||||
|
ephemeralAgent,
|
||||||
|
setEphemeralAgent,
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue