diff --git a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx index 62bd57c5d1..604b5532b0 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx @@ -214,6 +214,7 @@ const SettingBuiltInTool: FC = ({ pluginPayload={{ provider: collection.name, category: AuthCategory.tool, + providerType: collection.type, }} credentialId={credentialId} onAuthorizationItemClick={onAuthorizationItemClick} diff --git a/web/app/components/base/chip/index.stories.tsx b/web/app/components/base/chip/index.stories.tsx index 0ea018ef95..46d91c8cd6 100644 --- a/web/app/components/base/chip/index.stories.tsx +++ b/web/app/components/base/chip/index.stories.tsx @@ -23,6 +23,10 @@ const meta = { args: { items: ITEMS, value: 'all', + // eslint-disable-next-line no-empty-function + onSelect: () => {}, + // eslint-disable-next-line no-empty-function + onClear: () => {}, }, } satisfies Meta @@ -69,6 +73,13 @@ const [selection, setSelection] = useState('all') } export const WithoutLeftIcon: Story = { + args: { + showLeftIcon: false, + // eslint-disable-next-line no-empty-function + onSelect: () => {}, + // eslint-disable-next-line no-empty-function + onClear: () => {}, + }, render: args => ( ), - children: null, }, } @@ -112,7 +111,6 @@ export const WithoutFooter: Story = { args: { footer: undefined, title: 'Read-only summary', - children: null, }, parameters: { docs: { @@ -130,7 +128,6 @@ export const CustomStyling: Story = { bodyClassName: 'bg-gray-50 rounded-xl p-5', footerClassName: 'justify-between px-4 pb-4 pt-4', titleClassName: 'text-lg text-primary-600', - children: null, footer: ( <> Last synced 2 minutes ago @@ -144,7 +141,6 @@ export const CustomStyling: Story = { ), - children: null, }, parameters: { docs: { diff --git a/web/app/components/header/account-setting/data-source-page-new/card.tsx b/web/app/components/header/account-setting/data-source-page-new/card.tsx index 7a8790e76d..1e2e60bb7a 100644 --- a/web/app/components/header/account-setting/data-source-page-new/card.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/card.tsx @@ -20,6 +20,7 @@ import { useDataSourceAuthUpdate } from './hooks' import Confirm from '@/app/components/base/confirm' import { useGetDataSourceOAuthUrl } from '@/service/use-datasource' import { openOAuthPopup } from '@/hooks/use-oauth' +import { CollectionType } from '@/app/components/tools/types' type CardProps = { item: DataSourceAuth @@ -42,6 +43,7 @@ const Card = ({ const pluginPayload = { category: AuthCategory.datasource, provider: `${item.plugin_id}/${item.name}`, + providerType: CollectionType.datasource, } const { handleAuthUpdate } = useDataSourceAuthUpdate({ pluginId: item.plugin_id, diff --git a/web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx b/web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx index 63c0b5b07e..b2b0aefb9b 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useState } from 'react' import { InstallStep } from '../../types' import Install from './steps/install' import Installed from './steps/installed' -import type { Dependency, InstallStatusResponse, Plugin } from '../../types' +import type { Dependency, InstallStatus, Plugin } from '../../types' type Props = { step: InstallStep @@ -26,8 +26,8 @@ const ReadyToInstall: FC = ({ isFromMarketPlace, }) => { const [installedPlugins, setInstalledPlugins] = useState([]) - const [installStatus, setInstallStatus] = useState([]) - const handleInstalled = useCallback((plugins: Plugin[], installStatus: InstallStatusResponse[]) => { + const [installStatus, setInstallStatus] = useState([]) + const handleInstalled = useCallback((plugins: Plugin[], installStatus: InstallStatus[]) => { setInstallStatus(installStatus) setInstalledPlugins(plugins) onStepChange(InstallStep.installed) diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx index 758daafca0..a717e0a24a 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx @@ -2,23 +2,31 @@ import type { FC } from 'react' import { useRef } from 'react' import React, { useCallback, useState } from 'react' -import type { Dependency, InstallStatusResponse, Plugin, VersionInfo } from '../../../types' +import { + type Dependency, + type InstallStatus, + type InstallStatusResponse, + type Plugin, + TaskStatus, + type VersionInfo, +} from '../../../types' import Button from '@/app/components/base/button' import { RiLoader2Line } from '@remixicon/react' import { useTranslation } from 'react-i18next' import type { ExposeRefs } from './install-multi' import InstallMulti from './install-multi' -import { useInstallOrUpdate } from '@/service/use-plugins' +import { useInstallOrUpdate, usePluginTaskList } from '@/service/use-plugins' import useRefreshPluginList from '../../hooks/use-refresh-plugin-list' import { useCanInstallPluginFromMarketplace } from '@/app/components/plugins/plugin-page/use-reference-setting' import { useMittContextSelector } from '@/context/mitt-context' import Checkbox from '@/app/components/base/checkbox' +import checkTaskStatus from '../../base/check-task-status' const i18nPrefix = 'plugin.installModal' type Props = { allPlugins: Dependency[] onStartToInstall?: () => void - onInstalled: (plugins: Plugin[], installStatus: InstallStatusResponse[]) => void + onInstalled: (plugins: Plugin[], installStatus: InstallStatus[]) => void onCancel: () => void isFromMarketPlace?: boolean isHideButton?: boolean @@ -55,18 +63,60 @@ const Install: FC = ({ setCanInstall(true) }, []) + const { + check, + stop, + } = checkTaskStatus() + + const handleCancel = useCallback(() => { + stop() + onCancel() + }, [onCancel, stop]) + + const { handleRefetch } = usePluginTaskList() + // Install from marketplace and github const { mutate: installOrUpdate, isPending: isInstalling } = useInstallOrUpdate({ - onSuccess: (res: InstallStatusResponse[]) => { - onInstalled(selectedPlugins, res.map((r, i) => { - return ({ - ...r, - isFromMarketPlace: allPlugins[selectedIndexes[i]].type === 'marketplace', + onSuccess: async (res: InstallStatusResponse[]) => { + const isAllSettled = res.every(r => r.status === TaskStatus.success || r.status === TaskStatus.failed) + // if all settled, return the install status + if (isAllSettled) { + onInstalled(selectedPlugins, res.map((r, i) => { + return ({ + success: r.status === TaskStatus.success, + isFromMarketPlace: allPlugins[selectedIndexes[i]].type === 'marketplace', + }) + })) + const hasInstallSuccess = res.some(r => r.status === TaskStatus.success) + if (hasInstallSuccess) { + refreshPluginList(undefined, true) + emit('plugin:install:success', selectedPlugins.map((p) => { + return `${p.plugin_id}/${p.name}` + })) + } + return + } + // if not all settled, keep checking the status of the plugins + handleRefetch() + const installStatus = await Promise.all(res.map(async (item, index) => { + if (item.status !== TaskStatus.running) { + return { + success: item.status === TaskStatus.success, + isFromMarketPlace: allPlugins[selectedIndexes[index]].type === 'marketplace', + } + } + const { status } = await check({ + taskId: item.taskId, + pluginUniqueIdentifier: item.uniqueIdentifier, }) + return { + success: status === TaskStatus.success, + isFromMarketPlace: allPlugins[selectedIndexes[index]].type === 'marketplace', + } })) - const hasInstallSuccess = res.some(r => r.success) + onInstalled(selectedPlugins, installStatus) + const hasInstallSuccess = installStatus.some(r => r.success) if (hasInstallSuccess) { - refreshPluginList(undefined, true) emit('plugin:install:success', selectedPlugins.map((p) => { return `${p.plugin_id}/${p.name}` })) @@ -150,7 +200,7 @@ const Install: FC = ({
{!canInstall && ( - )} diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx index 4e16d200e7..f787882211 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import type { InstallStatusResponse, Plugin } from '../../../types' +import type { InstallStatus, Plugin } from '../../../types' import Card from '@/app/components/plugins/card' import Button from '@/app/components/base/button' import { useTranslation } from 'react-i18next' @@ -11,7 +11,7 @@ import { MARKETPLACE_API_PREFIX } from '@/config' type Props = { list: Plugin[] - installStatus: InstallStatusResponse[] + installStatus: InstallStatus[] onCancel: () => void isHideButton?: boolean } diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx index 318d1112bb..ccd0d8be1b 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx @@ -331,6 +331,7 @@ const DetailHeader = ({ pluginPayload={{ provider: provider?.name || '', category: AuthCategory.tool, + providerType: provider?.type || '', }} /> ) 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 d2797b99f4..d56d48d6d5 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 @@ -314,6 +314,7 @@ const ToolSelector: FC = ({ pluginPayload={{ provider: currentProvider.name, category: AuthCategory.tool, + providerType: currentProvider.type, }} credentialId={value?.credential_id} onAuthorizationItemClick={handleAuthorizationItemClick} diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 0f312476d5..2e061d7d69 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -280,6 +280,12 @@ export type InstallPackageResponse = { } export type InstallStatusResponse = { + status: TaskStatus, + taskId: string, + uniqueIdentifier: string, +} + +export type InstallStatus = { success: boolean, isFromMarketPlace?: boolean } diff --git a/web/global.d.ts b/web/global.d.ts index c5488a6cae..0ccadf7887 100644 --- a/web/global.d.ts +++ b/web/global.d.ts @@ -1,6 +1,7 @@ import './types/i18n' import './types/jsx' import './types/mdx' +import './types/assets' declare module 'lamejs'; declare module 'lamejs/src/js/MPEGMode'; @@ -8,4 +9,4 @@ declare module 'lamejs/src/js/Lame'; declare module 'lamejs/src/js/BitStream'; declare module 'react-18-input-autosize'; -export {} +export { } diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index 1dec97cdfa..5d2bc080d3 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -10,6 +10,7 @@ import type { Dependency, GitHubItemAndMarketPlaceDependency, InstallPackageResponse, + InstallStatusResponse, InstalledLatestVersionResponse, InstalledPluginListWithTotalResponse, PackageDependency, @@ -233,7 +234,7 @@ export const useUploadGitHub = (payload: { export const useInstallOrUpdate = ({ onSuccess, }: { - onSuccess?: (res: { success: boolean }[]) => void + onSuccess?: (res: InstallStatusResponse[]) => void }) => { const { mutateAsync: updatePackageFromMarketPlace } = useUpdatePackageFromMarketPlace() @@ -251,6 +252,8 @@ export const useInstallOrUpdate = ({ const installedPayload = installedInfo[orgAndName] const isInstalled = !!installedPayload let uniqueIdentifier = '' + let taskId = '' + let isFinishedInstallation = false if (item.type === 'github') { const data = item as GitHubItemAndMarketPlaceDependency @@ -268,12 +271,14 @@ export const useInstallOrUpdate = ({ // has the same version, but not installed if (uniqueIdentifier === installedPayload?.uniqueIdentifier) { return { - success: true, + status: TaskStatus.success, + taskId: '', + uniqueIdentifier: '', } } } if (!isInstalled) { - await post('/workspaces/current/plugin/install/github', { + const { task_id, all_installed } = await post('/workspaces/current/plugin/install/github', { body: { repo: data.value.repo!, version: data.value.release! || data.value.version!, @@ -281,6 +286,8 @@ export const useInstallOrUpdate = ({ plugin_unique_identifier: uniqueIdentifier, }, }) + taskId = task_id + isFinishedInstallation = all_installed } } if (item.type === 'marketplace') { @@ -288,15 +295,19 @@ export const useInstallOrUpdate = ({ uniqueIdentifier = data.value.marketplace_plugin_unique_identifier! || plugin[i]?.plugin_id if (uniqueIdentifier === installedPayload?.uniqueIdentifier) { return { - success: true, + status: TaskStatus.success, + taskId: '', + uniqueIdentifier: '', } } if (!isInstalled) { - await post('/workspaces/current/plugin/install/marketplace', { + const { task_id, all_installed } = await post('/workspaces/current/plugin/install/marketplace', { body: { plugin_unique_identifiers: [uniqueIdentifier], }, }) + taskId = task_id + isFinishedInstallation = all_installed } } if (item.type === 'package') { @@ -304,38 +315,59 @@ export const useInstallOrUpdate = ({ uniqueIdentifier = data.value.unique_identifier if (uniqueIdentifier === installedPayload?.uniqueIdentifier) { return { - success: true, + status: TaskStatus.success, + taskId: '', + uniqueIdentifier: '', } } if (!isInstalled) { - await post('/workspaces/current/plugin/install/pkg', { + const { task_id, all_installed } = await post('/workspaces/current/plugin/install/pkg', { body: { plugin_unique_identifiers: [uniqueIdentifier], }, }) + taskId = task_id + isFinishedInstallation = all_installed } } if (isInstalled) { if (item.type === 'package') { await uninstallPlugin(installedPayload.installedId) - await post('/workspaces/current/plugin/install/pkg', { + const { task_id, all_installed } = await post('/workspaces/current/plugin/install/pkg', { body: { plugin_unique_identifiers: [uniqueIdentifier], }, }) + taskId = task_id + isFinishedInstallation = all_installed } else { - await updatePackageFromMarketPlace({ + const { task_id, all_installed } = await updatePackageFromMarketPlace({ original_plugin_unique_identifier: installedPayload?.uniqueIdentifier, new_plugin_unique_identifier: uniqueIdentifier, }) + taskId = task_id + isFinishedInstallation = all_installed + } + } + if (isFinishedInstallation) { + return { + status: TaskStatus.success, + taskId: '', + uniqueIdentifier: '', + } + } + else { + return { + status: TaskStatus.running, + taskId, + uniqueIdentifier, } } - return ({ success: true }) } // eslint-disable-next-line unused-imports/no-unused-vars catch (e) { - return Promise.resolve({ success: false }) + return Promise.resolve({ status: TaskStatus.failed, taskId: '', uniqueIdentifier: '' }) } })) }, diff --git a/web/types/assets.d.ts b/web/types/assets.d.ts new file mode 100644 index 0000000000..d7711f7eb4 --- /dev/null +++ b/web/types/assets.d.ts @@ -0,0 +1,24 @@ +declare module '*.svg' { + const value: any + export default value +} + +declare module '*.png' { + const value: any + export default value +} + +declare module '*.jpg' { + const value: any + export default value +} + +declare module '*.jpeg' { + const value: any + export default value +} + +declare module '*.gif' { + const value: any + export default value +}