'use client' import type { FC } from 'react' import React, { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Link from 'next/link' import { RiArrowLeftLine, RiArrowRightUpLine, } from '@remixicon/react' 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 Button from '@/app/components/base/button' import Indicator from '@/app/components/header/indicator' import ToolCredentialForm from '@/app/components/plugins/plugin-detail-panel/tool-selector/tool-credentials-form' import Toast from '@/app/components/base/toast' 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 Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' import { generateFormValue, getPlainValue, getStructureValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { useAppContext } from '@/context/app-context' import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools, useInvalidateAllBuiltInTools, useUpdateProviderCredentials, } 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' type Props = { disabled?: boolean placement?: Placement offset?: OffsetOptions scope?: string value?: ToolValue selectedTools?: ToolValue[] onSelect: (tool: { provider_name: string tool_name: string tool_label: string settings?: Record parameters?: Record extra?: Record }) => void 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, } const ToolSelector: FC = ({ value, selectedTools, disabled, placement = 'left', offset = 4, onSelect, onDelete, scope, supportEnableSwitch, trigger, controlledState, onControlledStateChange, panelShowState, onPanelShowStateChange, nodeOutputVars, availableNodes, nodeId = '', }) => { 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 invalidateAllBuiltinTools = useInvalidateAllBuiltInTools() const invalidateInstalledPluginList = useInvalidateInstalledPluginList() // plugin info check const { inMarketPlace, manifest } = usePluginInstalledCheck(value?.provider_name) const currentProvider = useMemo(() => { const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || [])] return mergedTools.find((toolWithProvider) => { return toolWithProvider.id === value?.provider_name }) }, [value, buildInTools, customTools, workflowTools]) const [isShowChooseTool, setIsShowChooseTool] = useState(false) const handleSelectTool = (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) const toolValue = { provider_name: tool.provider_id, type: tool.provider_type, tool_name: tool.tool_name, tool_label: tool.tool_label, settings: settingValues, parameters: paramValues, enabled: tool.is_team_authorization, extra: { description: '', }, schemas: tool.paramSchemas, } onSelect(toolValue) // setIsShowChooseTool(false) } 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) } // authorization const { isCurrentWorkspaceManager } = useAppContext() const [isShowSettingAuth, setShowSettingAuth] = useState(false) const handleCredentialSettingUpdate = () => { invalidateAllBuiltinTools() Toast.notify({ type: 'success', message: t('common.api.actionSuccess'), }) setShowSettingAuth(false) onShowChange(false) } const { mutate: updatePermission } = useUpdateProviderCredentials({ onSuccess: handleCredentialSettingUpdate, }) // 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() } return ( <> { if (!currentProvider || !currentTool) return handleTriggerClick() }} > {trigger} {!trigger && !value?.provider_name && ( )} {!trigger && value?.provider_name && ( setShowSettingAuth(true)} uninstalled={!currentProvider && inMarketPlace} versionMismatch={currentProvider && inMarketPlace && !currentTool} installInfo={manifest?.latest_package_identifier} onInstall={() => 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')}

} /> )}
{!isShowSettingAuth && ( <>
{t('plugin.detailPanel.toolSelector.title')}
{/* base form */}
{t('plugin.detailPanel.toolSelector.toolLabel')}
} isShow={panelShowState || isShowChooseTool} onShowChange={trigger ? onPanelShowStateChange as any : setIsShowChooseTool} disabled={false} supportAddCustomTool onSelect={handleSelectTool} scope={scope} selectedTools={selectedTools} />
{t('plugin.detailPanel.toolSelector.descriptionLabel')}