diff --git a/web/app/components/plugins/plugin-detail-panel/index.tsx b/web/app/components/plugins/plugin-detail-panel/index.tsx index 57aa24bf848d32..4d20c0877d923e 100644 --- a/web/app/components/plugins/plugin-detail-panel/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/index.tsx @@ -7,7 +7,6 @@ import ActionList from './action-list' import ModelList from './model-list' import AgentStrategyList from './agent-strategy-list' import Drawer from '@/app/components/base/drawer' -import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector' import type { PluginDetail } from '@/app/components/plugins/types' import cn from '@/utils/classnames' @@ -28,12 +27,6 @@ const PluginDetailPanel: FC = ({ onUpdate() } - const [value, setValue] = React.useState(undefined) - const testChange = (val: any) => { - console.log('tool change', val) - setValue(val) - } - if (!detail) return null @@ -59,15 +52,6 @@ const PluginDetailPanel: FC = ({ {!!detail.declaration.agent_strategy && } {!!detail.declaration.endpoint && } {!!detail.declaration.model && } - {false && ( -
- -
- )} )} diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/hooks.ts b/web/app/components/plugins/plugin-detail-panel/tool-selector/hooks.ts new file mode 100644 index 00000000000000..57c1fbd7c3ceb4 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/hooks.ts @@ -0,0 +1,14 @@ +import { + usePluginManifestInfo, +} from '@/service/use-plugins' + +export const usePluginInstalledCheck = (providerName = '') => { + const pluginID = providerName?.split('/').splice(0, 2).join('/') + + const { data: manifest } = usePluginManifestInfo(pluginID) + + return { + inMarketPlace: !!manifest, + manifest: manifest?.data.plugin, + } +} diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx index d21a0a1ded18d0..a5b96e4eeaf660 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/index.tsx @@ -32,12 +32,15 @@ import { useInvalidateAllBuiltInTools, useUpdateProviderCredentials, } from '@/service/use-tools' +import { useInstallPackageFromMarketPlace } 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 } from '@/app/components/workflow/block-selector/types' import type { OffsetOptions, Placement, } from '@floating-ui/react' +import { MARKETPLACE_API_PREFIX } from '@/config' import cn from '@/utils/classnames' export type ToolValue = { @@ -92,6 +95,9 @@ const ToolSelector: FC = ({ const { data: workflowTools } = useAllWorkflowTools() const invalidateAllBuiltinTools = useInvalidateAllBuiltInTools() + // plugin info check + const { inMarketPlace, manifest } = usePluginInstalledCheck(value?.provider_name) + const currentProvider = useMemo(() => { const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || [])] return mergedTools.find((toolWithProvider) => { @@ -164,6 +170,25 @@ const ToolSelector: FC = ({ onSuccess: handleCredentialSettingUpdate, }) + // install from marketplace + const { mutateAsync: installPackageFromMarketPlace, isPending } = useInstallPackageFromMarketPlace() + const manifestIcon = useMemo(() => { + if (!manifest) + return '' + return `${MARKETPLACE_API_PREFIX}/plugins/${(manifest as any).plugin_id}/icon` + }, [manifest]) + const handleInstall = async () => { + if (!manifest) + return + try { + await installPackageFromMarketPlace(manifest.latest_package_identifier) + invalidateAllBuiltinTools() + } + catch (e: any) { + Toast.notify({ type: 'error', message: `${e.message || e}` }) + } + } + return ( <> = ({ {!trigger && value?.provider_name && ( = ({ onDelete={onDelete} noAuth={currentProvider && !currentProvider.is_team_authorization} onAuth={() => setShowSettingAuth(true)} - // uninstalled TODO - // isError TODO - errorTip={
-

{t('workflow.nodes.agent.pluginNotInstalled')}

-

{t('workflow.nodes.agent.pluginNotInstalledDesc')}

+ uninstalled={!currentProvider && inMarketPlace} + isInstalling={isPending} + onInstall={() => handleInstall()} + isError={!currentProvider && !inMarketPlace} + errorTip={
+

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

+

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

- {t('workflow.nodes.agent.linkToPlugin')} + {t('plugin.detailPanel.toolSelector.uninstalledLink')}

} /> diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx index f2ff56d07bd71f..c393d70a2513ba 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/tool-item.tsx @@ -6,6 +6,7 @@ import { RiEqualizer2Line, RiErrorWarningFill, } from '@remixicon/react' +import { Group } from '@/app/components/base/icons/src/vender/other' import AppIcon from '@/app/components/base/app-icon' import Switch from '@/app/components/base/switch' import Button from '@/app/components/base/button' @@ -61,10 +62,21 @@ const ToolItem = ({ open && 'bg-components-panel-on-panel-item-bg-hover shadow-sm', isDeleting && 'hover:bg-state-destructive-hover border-state-destructive-border shadow-xs', )}> -
- {typeof icon === 'string' &&
} - {typeof icon !== 'string' && } -
+ {icon && ( +
+ {typeof icon === 'string' &&
} + {typeof icon !== 'string' && } +
+ )} + {!icon && ( +
+
+ +
+
+ )}
{providerNameText}
{toolName}
diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts index a706f8f0420cea..f3807baf53be10 100644 --- a/web/i18n/en-US/plugin.ts +++ b/web/i18n/en-US/plugin.ts @@ -74,6 +74,9 @@ const translation = { auth: 'AUTHORIZATION', settings: 'TOOL SETTINGS', empty: 'Click the \'+\' button to add tools. You can add multiple tools.', + uninstalledTitle: 'Tool not installed', + uninstalledContent: 'This plugin is installed from the local/GitHub repository. Please use after installation.', + uninstalledLink: 'Manage in Plugins', }, configureApp: 'Configure App', configureModel: 'Configure model', diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts index 4b7c764d9f18eb..f1eb6e954117d3 100644 --- a/web/i18n/zh-Hans/plugin.ts +++ b/web/i18n/zh-Hans/plugin.ts @@ -74,6 +74,9 @@ const translation = { auth: '授权', settings: '工具设置', empty: '点击 "+" 按钮添加工具。您可以添加多个工具。', + uninstalledTitle: '工具未安装', + uninstalledContent: '此插件安装自 本地 / GitHub 仓库,请安装后使用。', + uninstalledLink: '在插件中管理', }, configureApp: '应用设置', configureModel: '模型设置', diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index a901d98c8e4ef4..5ad7d831d938ec 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -9,6 +9,7 @@ import type { Permissions, Plugin, PluginDetail, + PluginInfoFromMarketPlace, PluginTask, PluginsFromMarketplaceByInfoResponse, PluginsFromMarketplaceResponse, @@ -91,6 +92,7 @@ export const useUpdatePackageFromMarketPlace = () => { export const useVersionListOfPlugin = (pluginID: string) => { return useQuery<{ data: VersionListResponse }>({ + enabled: !!pluginID, queryKey: [NAME_SPACE, 'versions', pluginID], queryFn: () => getMarketplace<{ data: VersionListResponse }>(`/plugins/${pluginID}/versions`, { params: { page: 1, page_size: 100 } }), }) @@ -399,6 +401,15 @@ export const useMutationClearAllTaskPlugin = () => { }) } +export const usePluginManifestInfo = (pluginUID: string) => { + return useQuery({ + enabled: !!pluginUID, + queryKey: [[NAME_SPACE, 'manifest', pluginUID]], + queryFn: () => getMarketplace<{ data: { plugin: PluginInfoFromMarketPlace, version: { version: string } } }>(`/plugins/${pluginUID}`), + retry: 0, + }) +} + export const useDownloadPlugin = (info: { organization: string; pluginName: string; version: string }, needDownload: boolean) => { return useQuery({ queryKey: [NAME_SPACE, 'downloadPlugin', info],