'use client' import type { FC } from 'react' import React, { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Link from 'next/link' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import ToolTrigger from '@/app/components/plugins/plugin-detail-panel/tool-selector/tool-trigger' import ToolItem from '@/app/components/plugins/plugin-detail-panel/tool-selector/tool-item' import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' import ToolForm from '@/app/components/workflow/nodes/tool/components/tool-form' import Textarea from '@/app/components/base/textarea' import Divider from '@/app/components/base/divider' import TabSlider from '@/app/components/base/tab-slider-plain' import ReasoningConfigForm from '@/app/components/plugins/plugin-detail-panel/tool-selector/reasoning-config-form' import { generateFormValue, getPlainValue, getStructureValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools, } from '@/service/use-tools' import { useInvalidateInstalledPluginList } from '@/service/use-plugins' import { usePluginInstalledCheck } from '@/app/components/plugins/plugin-detail-panel/tool-selector/hooks' import { CollectionType } from '@/app/components/tools/types' import type { ToolDefaultValue, ToolValue } from '@/app/components/workflow/block-selector/types' import type { OffsetOptions, Placement, } from '@floating-ui/react' import { MARKETPLACE_API_PREFIX } from '@/config' import type { Node } from 'reactflow' import type { NodeOutPutVar } from '@/app/components/workflow/types' import cn from '@/utils/classnames' import { AuthCategory, PluginAuthInAgent, } from '@/app/components/plugins/plugin-auth' type Props = { disabled?: boolean placement?: Placement offset?: OffsetOptions scope?: string value?: ToolValue selectedTools?: ToolValue[] onSelect: (tool: ToolValue) => void onSelectMultiple?: (tool: ToolValue[]) => void isEdit?: boolean onDelete?: () => void supportEnableSwitch?: boolean supportAddCustomTool?: boolean trigger?: React.ReactNode controlledState?: boolean onControlledStateChange?: (state: boolean) => void panelShowState?: boolean onPanelShowStateChange?: (state: boolean) => void nodeOutputVars: NodeOutPutVar[], availableNodes: Node[], nodeId?: string, canChooseMCPTool?: boolean, } const ToolSelector: FC = ({ value, selectedTools, isEdit, disabled, placement = 'left', offset = 4, onSelect, onSelectMultiple, onDelete, scope, supportEnableSwitch, trigger, controlledState, onControlledStateChange, panelShowState, onPanelShowStateChange, nodeOutputVars, availableNodes, nodeId = '', canChooseMCPTool, }) => { const { t } = useTranslation() const [isShow, onShowChange] = useState(false) const handleTriggerClick = () => { if (disabled) return onShowChange(true) } const { data: buildInTools } = useAllBuiltInTools() const { data: customTools } = useAllCustomTools() const { data: workflowTools } = useAllWorkflowTools() const { data: mcpTools } = useAllMCPTools() const invalidateAllBuiltinTools = useInvalidateAllBuiltInTools() const invalidateInstalledPluginList = useInvalidateInstalledPluginList() // plugin info check const { inMarketPlace, manifest } = usePluginInstalledCheck(value?.provider_name) const currentProvider = useMemo(() => { const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || []), ...(mcpTools || [])] return mergedTools.find((toolWithProvider) => { return toolWithProvider.id === value?.provider_name }) }, [value, buildInTools, customTools, workflowTools, mcpTools]) const [isShowChooseTool, setIsShowChooseTool] = useState(false) const getToolValue = (tool: ToolDefaultValue) => { const settingValues = generateFormValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form !== 'llm') as any)) const paramValues = generateFormValue(tool.params, toolParametersToFormSchemas(tool.paramSchemas.filter(param => param.form === 'llm') as any), true) return { provider_name: tool.provider_id, provider_show_name: tool.provider_name, type: tool.provider_type, tool_name: tool.tool_name, tool_label: tool.tool_label, tool_description: tool.tool_description, settings: settingValues, parameters: paramValues, enabled: tool.is_team_authorization, extra: { description: tool.tool_description, }, schemas: tool.paramSchemas, } } const handleSelectTool = (tool: ToolDefaultValue) => { const toolValue = getToolValue(tool) onSelect(toolValue) // setIsShowChooseTool(false) } const handleSelectMultipleTool = (tool: ToolDefaultValue[]) => { const toolValues = tool.map(item => getToolValue(item)) onSelectMultiple?.(toolValues) } const handleDescriptionChange = (e: React.ChangeEvent) => { onSelect({ ...value, extra: { ...value?.extra, description: e.target.value || '', }, } as any) } // tool settings & params const currentToolSettings = useMemo(() => { if (!currentProvider) return [] return currentProvider.tools.find(tool => tool.name === value?.tool_name)?.parameters.filter(param => param.form !== 'llm') || [] }, [currentProvider, value]) const currentToolParams = useMemo(() => { if (!currentProvider) return [] return currentProvider.tools.find(tool => tool.name === value?.tool_name)?.parameters.filter(param => param.form === 'llm') || [] }, [currentProvider, value]) const [currType, setCurrType] = useState('settings') const showTabSlider = currentToolSettings.length > 0 && currentToolParams.length > 0 const userSettingsOnly = currentToolSettings.length > 0 && !currentToolParams.length const reasoningConfigOnly = currentToolParams.length > 0 && !currentToolSettings.length const settingsFormSchemas = useMemo(() => toolParametersToFormSchemas(currentToolSettings), [currentToolSettings]) const paramsFormSchemas = useMemo(() => toolParametersToFormSchemas(currentToolParams), [currentToolParams]) const handleSettingsFormChange = (v: Record) => { const newValue = getStructureValue(v) const toolValue = { ...value, settings: newValue, } onSelect(toolValue as any) } const handleParamsFormChange = (v: Record) => { const toolValue = { ...value, parameters: v, } onSelect(toolValue as any) } const handleEnabledChange = (state: boolean) => { onSelect({ ...value, enabled: state, } as any) } // install from marketplace const currentTool = useMemo(() => { return currentProvider?.tools.find(tool => tool.name === value?.tool_name) }, [currentProvider?.tools, value?.tool_name]) const manifestIcon = useMemo(() => { if (!manifest) return '' return `${MARKETPLACE_API_PREFIX}/plugins/${(manifest as any).plugin_id}/icon` }, [manifest]) const handleInstall = async () => { invalidateAllBuiltinTools() invalidateInstalledPluginList() } const handleAuthorizationItemClick = (id: string) => { onSelect({ ...value, credential_id: id, } as any) } return ( <> { if (!currentProvider || !currentTool) return handleTriggerClick() }} > {trigger} {!trigger && !value?.provider_name && ( )} {!trigger && value?.provider_name && ( handleInstall()} isError={(!currentProvider || !currentTool) && !inMarketPlace} errorTip={

{currentTool ? t('plugin.detailPanel.toolSelector.uninstalledTitle') : t('plugin.detailPanel.toolSelector.unsupportedTitle')}

{currentTool ? t('plugin.detailPanel.toolSelector.uninstalledContent') : t('plugin.detailPanel.toolSelector.unsupportedContent')}

{t('plugin.detailPanel.toolSelector.uninstalledLink')}

} canChooseMCPTool={canChooseMCPTool} /> )}
<>
{t(`plugin.detailPanel.toolSelector.${isEdit ? 'toolSetting' : 'title'}`)}
{/* base form */}
{t('plugin.detailPanel.toolSelector.toolLabel')}
} isShow={panelShowState || isShowChooseTool} onShowChange={trigger ? onPanelShowStateChange as any : setIsShowChooseTool} disabled={false} supportAddCustomTool onSelect={handleSelectTool} onSelectMultiple={handleSelectMultipleTool} scope={scope} selectedTools={selectedTools} canChooseMCPTool={canChooseMCPTool} />
{t('plugin.detailPanel.toolSelector.descriptionLabel')}