import { useEffect } from 'react'; import { Search, X } from 'lucide-react'; import { useFormContext } from 'react-hook-form'; import { Constants, isAgentsEndpoint } from 'librechat-data-provider'; import { Dialog, DialogPanel, DialogTitle, Description } from '@headlessui/react'; import { useUpdateUserPluginsMutation } from 'librechat-data-provider/react-query'; import type { AssistantsEndpoint, EModelEndpoint, TPluginAction, AgentToolType, TError, } from 'librechat-data-provider'; import type { AgentForm, TPluginStoreDialogProps } from '~/common'; import { PluginPagination, PluginAuthForm } from '~/components/Plugins/Store'; import { useAgentPanelContext } from '~/Providers/AgentPanelContext'; import { useLocalize, usePluginDialogHelpers } from '~/hooks'; import { useAvailableToolsQuery } from '~/data-provider'; import ToolItem from './ToolItem'; function ToolSelectDialog({ isOpen, endpoint, setIsOpen, }: TPluginStoreDialogProps & { endpoint: AssistantsEndpoint | EModelEndpoint.agents; }) { const localize = useLocalize(); const { getValues, setValue } = useFormContext(); const { data: tools } = useAvailableToolsQuery(endpoint); const { groupedTools } = useAgentPanelContext(); const isAgentTools = isAgentsEndpoint(endpoint); const { maxPage, setMaxPage, currentPage, setCurrentPage, itemsPerPage, searchChanged, setSearchChanged, searchValue, setSearchValue, gridRef, handleSearch, handleChangePage, error, setError, errorMessage, setErrorMessage, showPluginAuthForm, setShowPluginAuthForm, selectedPlugin, setSelectedPlugin, } = usePluginDialogHelpers(); const updateUserPlugins = useUpdateUserPluginsMutation(); const handleInstallError = (error: TError) => { setError(true); const errorMessage = error.response?.data?.message ?? ''; if (errorMessage) { setErrorMessage(errorMessage); } setTimeout(() => { setError(false); setErrorMessage(''); }, 5000); }; const handleInstall = (pluginAction: TPluginAction) => { const addFunction = () => { const installedToolIds: string[] = getValues('tools') || []; // Add the parent installedToolIds.push(pluginAction.pluginKey); // If this tool is a group, add subtools too const groupObj = groupedTools?.[pluginAction.pluginKey]; if (groupObj?.tools && groupObj.tools.length > 0) { for (const sub of groupObj.tools) { if (!installedToolIds.includes(sub.tool_id)) { installedToolIds.push(sub.tool_id); } } } setValue('tools', Array.from(new Set(installedToolIds))); // no duplicates just in case }; if (!pluginAction.auth) { return addFunction(); } updateUserPlugins.mutate(pluginAction, { onError: (error: unknown) => { handleInstallError(error as TError); }, onSuccess: addFunction, }); setShowPluginAuthForm(false); }; const onRemoveTool = (toolId: string) => { const groupObj = groupedTools?.[toolId]; const toolIdsToRemove = [toolId]; if (groupObj?.tools && groupObj.tools.length > 0) { toolIdsToRemove.push(...groupObj.tools.map((sub) => sub.tool_id)); } // Remove these from the formTools updateUserPlugins.mutate( { pluginKey: toolId, action: 'uninstall', auth: {}, isEntityTool: true }, { onError: (error: unknown) => handleInstallError(error as TError), onSuccess: () => { const remainingToolIds = getValues('tools')?.filter((toolId) => !toolIdsToRemove.includes(toolId)) || []; setValue('tools', remainingToolIds); }, }, ); }; const onAddTool = (pluginKey: string) => { setShowPluginAuthForm(false); const getAvailablePluginFromKey = tools?.find((p) => p.pluginKey === pluginKey); setSelectedPlugin(getAvailablePluginFromKey); const isMCPTool = pluginKey.includes(Constants.mcp_delimiter); if (isMCPTool) { // MCP tools have their variables configured elsewhere (e.g., MCPPanel or MCPSelect), // so we directly proceed to install without showing the auth form. handleInstall({ pluginKey, action: 'install', auth: {} }); } else { const { authConfig, authenticated = false } = getAvailablePluginFromKey ?? {}; if (authConfig && authConfig.length > 0 && !authenticated) { setShowPluginAuthForm(true); } else { handleInstall({ pluginKey, action: 'install', auth: {}, }); } } }; const filteredTools = Object.values(groupedTools || {}).filter( (tool: AgentToolType & { tools?: AgentToolType[] }) => { // Check if the parent tool matches if (tool.metadata?.name?.toLowerCase().includes(searchValue.toLowerCase())) { return true; } // Check if any child tools match if (tool.tools) { return tool.tools.some((childTool) => childTool.metadata?.name?.toLowerCase().includes(searchValue.toLowerCase()), ); } return false; }, ); useEffect(() => { if (filteredTools) { setMaxPage(Math.ceil(Object.keys(filteredTools || {}).length / itemsPerPage)); if (searchChanged) { setCurrentPage(1); setSearchChanged(false); } } }, [ tools, itemsPerPage, searchValue, filteredTools, searchChanged, setMaxPage, setCurrentPage, setSearchChanged, ]); return ( { setIsOpen(false); setCurrentPage(1); setSearchValue(''); }} className="relative z-[102]" > {/* The backdrop, rendered as a fixed sibling to the panel container */}
{/* Full-screen container to center the panel */}
{isAgentTools ? localize('com_nav_tool_dialog_agents') : localize('com_nav_tool_dialog')} {localize('com_nav_tool_dialog_description')}
{error && (
{localize('com_nav_plugin_auth_error')} {errorMessage}
)} {showPluginAuthForm && (
handleInstall(installActionData)} isEntityTool={true} />
)}
{filteredTools && filteredTools .slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage) .map((tool, index) => ( onAddTool(tool.tool_id)} onRemoveTool={() => onRemoveTool(tool.tool_id)} /> ))}
{maxPage > 0 ? ( ) : (
)}
); } export default ToolSelectDialog;