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-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/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/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 e0b54a1acf..c76b154c40 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'
@@ -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()
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