import React, { useState, useMemo, useCallback } from 'react'; import * as Ariakit from '@ariakit/react'; import { Globe, Settings, Settings2, TerminalSquareIcon } from 'lucide-react'; import { TooltipAnchor, DropdownPopup, PinIcon, VectorIcon } from '@librechat/client'; import type { MenuItemProps } from '~/common'; import { AuthType, Permissions, ArtifactModes, PermissionTypes, defaultAgentCapabilities, } from 'librechat-data-provider'; import { useLocalize, useHasAccess, useAgentCapabilities } from '~/hooks'; import ArtifactsSubMenu from '~/components/Chat/Input/ArtifactsSubMenu'; import MCPSubMenu from '~/components/Chat/Input/MCPSubMenu'; import { useBadgeRowContext } from '~/Providers'; import { cn } from '~/utils'; interface ToolsDropdownProps { disabled?: boolean; } const ToolsDropdown = ({ disabled }: ToolsDropdownProps) => { const localize = useLocalize(); const isDisabled = disabled ?? false; const [isPopoverActive, setIsPopoverActive] = useState(false); const { webSearch, mcpSelect, artifacts, fileSearch, agentsConfig, startupConfig, codeApiKeyForm, codeInterpreter, searchApiKeyForm, } = useBadgeRowContext(); const { codeEnabled, webSearchEnabled, artifactsEnabled, fileSearchEnabled } = useAgentCapabilities(agentsConfig?.capabilities ?? defaultAgentCapabilities); const { setIsDialogOpen: setIsCodeDialogOpen, menuTriggerRef: codeMenuTriggerRef } = codeApiKeyForm; const { setIsDialogOpen: setIsSearchDialogOpen, menuTriggerRef: searchMenuTriggerRef } = searchApiKeyForm; const { isPinned: isSearchPinned, setIsPinned: setIsSearchPinned, authData: webSearchAuthData, } = webSearch; const { isPinned: isCodePinned, setIsPinned: setIsCodePinned, authData: codeAuthData, } = codeInterpreter; const { isPinned: isFileSearchPinned, setIsPinned: setIsFileSearchPinned } = fileSearch; const { isPinned: isArtifactsPinned, setIsPinned: setIsArtifactsPinned } = artifacts; const { mcpServerNames } = mcpSelect; const canUseWebSearch = useHasAccess({ permissionType: PermissionTypes.WEB_SEARCH, permission: Permissions.USE, }); const canRunCode = useHasAccess({ permissionType: PermissionTypes.RUN_CODE, permission: Permissions.USE, }); const canUseFileSearch = useHasAccess({ permissionType: PermissionTypes.FILE_SEARCH, permission: Permissions.USE, }); const showWebSearchSettings = useMemo(() => { const authTypes = webSearchAuthData?.authTypes ?? []; if (authTypes.length === 0) return true; return !authTypes.every(([, authType]) => authType === AuthType.SYSTEM_DEFINED); }, [webSearchAuthData?.authTypes]); const showCodeSettings = useMemo( () => codeAuthData?.message !== AuthType.SYSTEM_DEFINED, [codeAuthData?.message], ); const handleWebSearchToggle = useCallback(() => { const newValue = !webSearch.toggleState; webSearch.debouncedChange({ value: newValue }); }, [webSearch]); const handleCodeInterpreterToggle = useCallback(() => { const newValue = !codeInterpreter.toggleState; codeInterpreter.debouncedChange({ value: newValue }); }, [codeInterpreter]); const handleFileSearchToggle = useCallback(() => { const newValue = !fileSearch.toggleState; fileSearch.debouncedChange({ value: newValue }); }, [fileSearch]); const handleArtifactsToggle = useCallback(() => { const currentState = artifacts.toggleState; if (!currentState || currentState === '') { artifacts.debouncedChange({ value: ArtifactModes.DEFAULT }); } else { artifacts.debouncedChange({ value: '' }); } }, [artifacts]); const handleShadcnToggle = useCallback(() => { const currentState = artifacts.toggleState; if (currentState === ArtifactModes.SHADCNUI) { artifacts.debouncedChange({ value: ArtifactModes.DEFAULT }); } else { artifacts.debouncedChange({ value: ArtifactModes.SHADCNUI }); } }, [artifacts]); const handleCustomToggle = useCallback(() => { const currentState = artifacts.toggleState; if (currentState === ArtifactModes.CUSTOM) { artifacts.debouncedChange({ value: ArtifactModes.DEFAULT }); } else { artifacts.debouncedChange({ value: ArtifactModes.CUSTOM }); } }, [artifacts]); const mcpPlaceholder = startupConfig?.interface?.mcpServers?.placeholder; const dropdownItems: MenuItemProps[] = []; if (fileSearchEnabled && canUseFileSearch) { dropdownItems.push({ onClick: handleFileSearchToggle, hideOnClick: false, render: (props) => (
{localize('com_assistants_file_search')}
), }); } if (canUseWebSearch && webSearchEnabled) { dropdownItems.push({ onClick: handleWebSearchToggle, hideOnClick: false, render: (props) => (
{localize('com_ui_web_search')}
{showWebSearchSettings && ( )}
), }); } if (canRunCode && codeEnabled) { dropdownItems.push({ onClick: handleCodeInterpreterToggle, hideOnClick: false, render: (props) => (
{localize('com_assistants_code_interpreter')}
{showCodeSettings && ( )}
), }); } if (artifactsEnabled) { dropdownItems.push({ hideOnClick: false, render: (props) => ( ), }); } if (mcpServerNames && mcpServerNames.length > 0) { dropdownItems.push({ hideOnClick: false, render: (props) => , }); } const menuTrigger = (
} id="tools-dropdown-button" description={localize('com_ui_tools')} disabled={isDisabled} /> ); return ( ); }; export default React.memo(ToolsDropdown);