From 27f794e1972db529b8b3a7ab6a889986ed4edd11 Mon Sep 17 00:00:00 2001 From: StyleZhang Date: Mon, 11 Nov 2024 18:17:44 +0800 Subject: [PATCH 1/2] feat: plugin task --- .../install-from-github/steps/loaded.tsx | 2 +- .../steps/install.tsx | 2 +- .../components/plugins/plugin-page/index.tsx | 8 +- .../plugins/plugin-page/install-info.tsx | 86 ----------- .../plugins/plugin-page/plugin-tasks/hooks.ts | 27 ++++ .../plugin-page/plugin-tasks/index.tsx | 137 ++++++++++++++++++ .../{store.tsx => plugin-tasks/store.ts} | 2 +- .../update-plugin/from-market-place.tsx | 2 +- web/i18n/en-US/plugin.ts | 6 + web/i18n/zh-Hans/plugin.ts | 6 + 10 files changed, 183 insertions(+), 95 deletions(-) delete mode 100644 web/app/components/plugins/plugin-page/install-info.tsx create mode 100644 web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts create mode 100644 web/app/components/plugins/plugin-page/plugin-tasks/index.tsx rename web/app/components/plugins/plugin-page/{store.tsx => plugin-tasks/store.ts} (94%) diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx index dce2e735c8..c046957263 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx @@ -9,7 +9,7 @@ import { pluginManifestToCardPluginProps } from '../../utils' import { useTranslation } from 'react-i18next' import { installPackageFromGitHub, uninstallPlugin } from '@/service/plugins' import { RiLoader2Line } from '@remixicon/react' -import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store' +import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/plugin-tasks/store' import checkTaskStatus from '../../base/check-task-status' import { parseGitHubUrl } from '../../utils' diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx index b48922bdb9..4d776f4430 100644 --- a/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx @@ -10,7 +10,7 @@ import { RiLoader2Line } from '@remixicon/react' import Badge, { BadgeState } from '@/app/components/base/badge/index' import { useInstallPackageFromLocal } from '@/service/use-plugins' import checkTaskStatus from '../../base/check-task-status' -import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store' +import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/plugin-tasks/store' const i18nPrefix = 'plugin.installModal' diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index f889bbc363..013c9bc9e2 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -16,8 +16,8 @@ import InstallPluginDropdown from './install-plugin-dropdown' import { useUploader } from './use-uploader' import usePermission from './use-permission' import DebugInfo from './debug-info' -import { usePluginTasksStore } from './store' -import InstallInfo from './install-info' +import { usePluginTasksStore } from './plugin-tasks/store' +import PluginTasks from './plugin-tasks' import Button from '@/app/components/base/button' import TabSlider from '@/app/components/base/tab-slider' import Tooltip from '@/app/components/base/tooltip' @@ -102,8 +102,6 @@ const PluginPage = ({ const options = usePluginPageContext(v => v.options) const [activeTab, setActiveTab] = usePluginPageContext(v => [v.activeTab, v.setActiveTab]) const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) - const [installed, total] = [2, 3] // Replace this with the actual progress - const progressPercentage = (installed / total) * 100 const uploaderProps = useUploader({ onFileChange: setCurrentFile, @@ -142,7 +140,7 @@ const PluginPage = ({ />
- + {canManagement && ( setActiveTab('discover')} diff --git a/web/app/components/plugins/plugin-page/install-info.tsx b/web/app/components/plugins/plugin-page/install-info.tsx deleted file mode 100644 index bb0a31f4be..0000000000 --- a/web/app/components/plugins/plugin-page/install-info.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { - useState, -} from 'react' -import { - RiCheckboxCircleFill, - RiErrorWarningFill, - RiInstallLine, -} from '@remixicon/react' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import Tooltip from '@/app/components/base/tooltip' -import Button from '@/app/components/base/button' -// import ProgressCircle from '@/app/components/base/progress-bar/progress-circle' -import { useMemo } from 'react' -import cn from '@/utils/classnames' - -const InstallInfo = () => { - const [open, setOpen] = useState(false) - const status = 'error' - const statusError = useMemo(() => status === 'error', [status]) - - return ( -
- - setOpen(v => !v)}> - -
- -
- {/* */} - -
-
-
-
- -
-
3 plugins failed to install
-
-
- -
-
- DuckDuckGo Search -
- -
-
-
-
-
- ) -} - -export default InstallInfo diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts b/web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts new file mode 100644 index 0000000000..3a198f5068 --- /dev/null +++ b/web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts @@ -0,0 +1,27 @@ +import { usePluginTasksStore } from './store' +import { TaskStatus } from '@/app/components/plugins/types' +import type { PluginStatus } from '@/app/components/plugins/types' + +export const usePluginTaskStatus = () => { + const pluginTasks = usePluginTasksStore(s => s.pluginTasks) + const allPlugins = pluginTasks.map(task => task.plugins).flat() + const errorPlugins: PluginStatus[] = [] + const successPlugins: PluginStatus[] = [] + const runningPlugins: PluginStatus[] = [] + + allPlugins.forEach((plugin) => { + if (plugin.status === TaskStatus.running) + runningPlugins.push(plugin) + if (plugin.status === TaskStatus.failed) + errorPlugins.push(plugin) + if (plugin.status === TaskStatus.success) + successPlugins.push(plugin) + }) + + return { + errorPlugins, + successPlugins, + runningPlugins, + totalPluginsLength: allPlugins.length, + } +} diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/index.tsx b/web/app/components/plugins/plugin-page/plugin-tasks/index.tsx new file mode 100644 index 0000000000..bde4371839 --- /dev/null +++ b/web/app/components/plugins/plugin-page/plugin-tasks/index.tsx @@ -0,0 +1,137 @@ +import { + useMemo, + useState, +} from 'react' +import { + RiCheckboxCircleFill, + RiErrorWarningFill, + RiInstallLine, +} from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { usePluginTaskStatus } from './hooks' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import Tooltip from '@/app/components/base/tooltip' +import Button from '@/app/components/base/button' +import ProgressCircle from '@/app/components/base/progress-bar/progress-circle' +import cn from '@/utils/classnames' + +const PluginTasks = () => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + const { + errorPlugins, + runningPlugins, + successPlugins, + totalPluginsLength, + } = usePluginTaskStatus() + + const isInstalling = runningPlugins.length > 0 && errorPlugins.length === 0 && successPlugins.length === 0 + const isInstallingWithError = errorPlugins.length > 0 && errorPlugins.length < totalPluginsLength + const isSuccess = successPlugins.length === totalPluginsLength && totalPluginsLength > 0 + const isFailed = errorPlugins.length === totalPluginsLength && totalPluginsLength > 0 + + const tip = useMemo(() => { + if (isInstalling) + return t('plugin.task.installing', { installingLength: runningPlugins.length, totalLength: totalPluginsLength }) + + if (isInstallingWithError) + return t('plugin.task.installingWithError', { installingLength: runningPlugins.length, totalLength: totalPluginsLength, errorLength: errorPlugins.length }) + + if (isFailed) + return t('plugin.task.installError', { errorLength: errorPlugins.length }) + }, [isInstalling, isInstallingWithError, isFailed, errorPlugins, runningPlugins, totalPluginsLength, t]) + + return ( +
+ + { + if (isFailed || isInstallingWithError) + setOpen(v => !v) + }} + > + +
+ +
+ { + isInstalling && ( + + ) + } + { + isInstallingWithError && ( + + ) + } + { + isSuccess && ( + + ) + } + { + isFailed && ( + + ) + } +
+
+
+
+ +
+
{t('plugin.task.installedError')}
+
+
+ +
+
+ DuckDuckGo Search +
+ +
+
+
+
+
+ ) +} + +export default PluginTasks diff --git a/web/app/components/plugins/plugin-page/store.tsx b/web/app/components/plugins/plugin-page/plugin-tasks/store.ts similarity index 94% rename from web/app/components/plugins/plugin-page/store.tsx rename to web/app/components/plugins/plugin-page/plugin-tasks/store.ts index 25074b973f..403d529a39 100644 --- a/web/app/components/plugins/plugin-page/store.tsx +++ b/web/app/components/plugins/plugin-page/plugin-tasks/store.ts @@ -1,5 +1,5 @@ import { create } from 'zustand' -import type { PluginTask } from '../types' +import type { PluginTask } from '@/app/components/plugins/types' import { fetchPluginTasks } from '@/service/plugins' type PluginTasksStore = { diff --git a/web/app/components/plugins/update-plugin/from-market-place.tsx b/web/app/components/plugins/update-plugin/from-market-place.tsx index e0b54a1acf..0454617a2e 100644 --- a/web/app/components/plugins/update-plugin/from-market-place.tsx +++ b/web/app/components/plugins/update-plugin/from-market-place.tsx @@ -12,7 +12,7 @@ import { pluginManifestToCardPluginProps } from '@/app/components/plugins/instal import useGetIcon from '../install-plugin/base/use-get-icon' import { updateFromMarketPlace } from '@/service/plugins' import checkTaskStatus from '@/app/components/plugins/install-plugin/base/check-task-status' -import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store' +import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/plugin-tasks/store' const i18nPrefix = 'plugin.upgrade' diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts index 5c3bdc6f29..abcf3480bb 100644 --- a/web/i18n/en-US/plugin.ts +++ b/web/i18n/en-US/plugin.ts @@ -124,6 +124,12 @@ const translation = { inDifyMarketplace: 'in Dify Marketplace', moreFrom: 'More from Marketplace', }, + task: { + installing: 'Installing {{installingLength}}/{{totalLength}} plugins...', + installingWithError: 'Installing {{installingLength}} of {{totalLength}} plugins, {{errorLength}} failed, click to view', + installError: '{{errorLength}} plugins failed to install, click to view', + installedError: '{{errorLength}} plugins failed to install', + }, } export default translation diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts index c1f7bb7600..3355cb742c 100644 --- a/web/i18n/zh-Hans/plugin.ts +++ b/web/i18n/zh-Hans/plugin.ts @@ -124,6 +124,12 @@ const translation = { inDifyMarketplace: '在 Dify 市场中', moreFrom: '更多来自市场', }, + task: { + installing: '{{installingLength}}/{{totalLength}} 插件安装中...', + installingWithError: '{{installingLength}}/{{totalLength}} 插件安装中,{{errorLength}} 安装失败。点击查看', + installError: '{{errorLength}} 个插件安装失败,点击查看', + installedError: '{{errorLength}} 个插件安装失败', + }, } export default translation From 7e39565fd25d80ff94530519c3370632eca24f4a Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 12 Nov 2024 11:48:27 +0800 Subject: [PATCH 2/2] feat: handle on update show update modal --- .../plugin-detail-panel/detail-header.tsx | 47 +++++++++++++++++-- web/app/components/plugins/types.ts | 1 + .../update-plugin/from-market-place.tsx | 37 +++++++++------ 3 files changed, 67 insertions(+), 18 deletions(-) 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 b40f264967..97e61b66d8 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx @@ -28,7 +28,7 @@ import { Github } from '@/app/components/base/icons/src/public/common' import { uninstallPlugin } from '@/service/plugins' import { useGetLanguage } from '@/context/i18n' import { useModalContext } from '@/context/modal-context' - +import UpdateFromMarketplace from '@/app/components/plugins/update-plugin/from-market-place' import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config' import cn from '@/utils/classnames' @@ -55,17 +55,35 @@ const DetailHeader = ({ source, tenant_id, version, + latest_unique_identifier, latest_version, meta, } = detail const { author, name, label, description, icon, verified } = detail.declaration const isFromGitHub = source === PluginSource.github + const isFromMarketplace = source === PluginSource.marketplace const hasNewVersion = useMemo(() => { - return source === PluginSource.github && latest_version !== version - }, [source, latest_version, version]) + if (isFromGitHub) + return latest_version !== version + + if (isFromMarketplace) + return !!latest_version && latest_version !== version + + return false + }, [isFromGitHub, isFromMarketplace, latest_version, version]) + + const [isShowUpdateModal, { + setTrue: showUpdateModal, + setFalse: hideUpdateModal, + }] = useBoolean(false) const handleUpdate = async () => { + if (isFromMarketplace) { + showUpdateModal() + return + } + try { const fetchedReleases = await fetchReleases(author, name) if (fetchedReleases.length === 0) @@ -106,6 +124,11 @@ const DetailHeader = ({ } } + const handleUpdatedFromMarketplace = () => { + onUpdate() + hideUpdateModal() + } + const [isShowPluginInfo, { setTrue: showPluginInfo, setFalse: hidePluginInfo, @@ -222,6 +245,24 @@ const DetailHeader = ({ isDisabled={deleting} /> )} + { + isShowUpdateModal && ( + + ) + }
) } diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 40627f67a3..a869b2c556 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -100,6 +100,7 @@ export type PluginDetail = { endpoints_active: number version: string latest_version: string + latest_unique_identifier: string source: PluginSource meta?: MetaData } diff --git a/web/app/components/plugins/update-plugin/from-market-place.tsx b/web/app/components/plugins/update-plugin/from-market-place.tsx index 0454617a2e..c76b154c40 100644 --- a/web/app/components/plugins/update-plugin/from-market-place.tsx +++ b/web/app/components/plugins/update-plugin/from-market-place.tsx @@ -69,23 +69,30 @@ const UpdatePluginModal: FC = ({ const handleConfirm = useCallback(async () => { if (uploadStep === UploadStep.notStarted) { setUploadStep(UploadStep.upgrading) - const { - all_installed: isInstalled, - task_id: taskId, - } = await updateFromMarketPlace({ - original_plugin_unique_identifier: originalPackageInfo.id, - new_plugin_unique_identifier: targetPackageInfo.id, - }) - if (isInstalled) { + try { + const { + all_installed: isInstalled, + task_id: taskId, + } = await updateFromMarketPlace({ + original_plugin_unique_identifier: originalPackageInfo.id, + new_plugin_unique_identifier: targetPackageInfo.id, + }) + + if (isInstalled) { + onSave() + return + } + setPluginTasksWithPolling() + await check({ + taskId, + pluginUniqueIdentifier: targetPackageInfo.id, + }) onSave() - return } - setPluginTasksWithPolling() - await check({ - taskId, - pluginUniqueIdentifier: targetPackageInfo.id, - }) - onSave() + catch (e) { + setUploadStep(UploadStep.notStarted) + } + return } if (uploadStep === UploadStep.installed) { onSave()