feat: plugin task

This commit is contained in:
StyleZhang 2024-11-11 18:17:44 +08:00
parent dbc10425c8
commit 27f794e197
10 changed files with 183 additions and 95 deletions

View File

@ -9,7 +9,7 @@ import { pluginManifestToCardPluginProps } from '../../utils'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { installPackageFromGitHub, uninstallPlugin } from '@/service/plugins' import { installPackageFromGitHub, uninstallPlugin } from '@/service/plugins'
import { RiLoader2Line } from '@remixicon/react' 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 checkTaskStatus from '../../base/check-task-status'
import { parseGitHubUrl } from '../../utils' import { parseGitHubUrl } from '../../utils'

View File

@ -10,7 +10,7 @@ import { RiLoader2Line } from '@remixicon/react'
import Badge, { BadgeState } from '@/app/components/base/badge/index' import Badge, { BadgeState } from '@/app/components/base/badge/index'
import { useInstallPackageFromLocal } from '@/service/use-plugins' import { useInstallPackageFromLocal } from '@/service/use-plugins'
import checkTaskStatus from '../../base/check-task-status' 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' const i18nPrefix = 'plugin.installModal'

View File

@ -16,8 +16,8 @@ import InstallPluginDropdown from './install-plugin-dropdown'
import { useUploader } from './use-uploader' import { useUploader } from './use-uploader'
import usePermission from './use-permission' import usePermission from './use-permission'
import DebugInfo from './debug-info' import DebugInfo from './debug-info'
import { usePluginTasksStore } from './store' import { usePluginTasksStore } from './plugin-tasks/store'
import InstallInfo from './install-info' import PluginTasks from './plugin-tasks'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import TabSlider from '@/app/components/base/tab-slider' import TabSlider from '@/app/components/base/tab-slider'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
@ -102,8 +102,6 @@ const PluginPage = ({
const options = usePluginPageContext(v => v.options) const options = usePluginPageContext(v => v.options)
const [activeTab, setActiveTab] = usePluginPageContext(v => [v.activeTab, v.setActiveTab]) const [activeTab, setActiveTab] = usePluginPageContext(v => [v.activeTab, v.setActiveTab])
const { enable_marketplace } = useAppContextSelector(s => s.systemFeatures) 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({ const uploaderProps = useUploader({
onFileChange: setCurrentFile, onFileChange: setCurrentFile,
@ -142,7 +140,7 @@ const PluginPage = ({
/> />
</div> </div>
<div className='flex flex-shrink-0 items-center gap-1'> <div className='flex flex-shrink-0 items-center gap-1'>
<InstallInfo /> <PluginTasks />
{canManagement && ( {canManagement && (
<InstallPluginDropdown <InstallPluginDropdown
onSwitchToMarketplaceTab={() => setActiveTab('discover')} onSwitchToMarketplaceTab={() => setActiveTab('discover')}

View File

@ -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 (
<div className='flex items-center'>
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-start'
offset={{
mainAxis: 4,
crossAxis: 79,
}}
>
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
<Tooltip popupContent='Installing 1/3 plugins...'>
<div
className={cn(
'relative flex items-center justify-center w-8 h-8 rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs hover:bg-components-button-secondary-bg-hover',
statusError && 'border-components-button-destructive-secondary-border-hover bg-state-destructive-hover hover:bg-state-destructive-hover-alt',
)}
>
<RiInstallLine
className={cn(
'w-4 h-4 text-components-button-secondary-text',
statusError && 'text-components-button-destructive-secondary-text',
)}
/>
<div className='absolute -right-1 -top-1'>
{/* <ProgressCircle
percentage={33}
circleFillColor='fill-components-progress-brand-bg'
sectorFillColor='fill-components-progress-error-bg'
circleStrokeColor='stroke-components-progress-error-bg'
/> */}
<RiCheckboxCircleFill className='w-3.5 h-3.5 text-text-success' />
</div>
</div>
</Tooltip>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-10'>
<div className='p-1 pb-2 w-[320px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
<div className='flex items-center px-2 pt-1 h-7 system-sm-semibold-uppercase'>3 plugins failed to install</div>
<div className='flex items-center p-1 pl-2 h-8 rounded-lg hover:bg-state-base-hover'>
<div className='relative flex items-center justify-center mr-2 w-6 h-6 rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge'>
<RiErrorWarningFill className='absolute -right-0.5 -bottom-0.5 w-3 h-3 text-text-destructive' />
</div>
<div className='grow system-md-regular text-text-secondary truncate'>
DuckDuckGo Search
</div>
<Button
size='small'
variant='ghost-accent'
>
Clear
</Button>
</div>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
</div>
)
}
export default InstallInfo

View File

@ -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,
}
}

View File

@ -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 (
<div className='flex items-center'>
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-start'
offset={{
mainAxis: 4,
crossAxis: 79,
}}
>
<PortalToFollowElemTrigger
onClick={() => {
if (isFailed || isInstallingWithError)
setOpen(v => !v)
}}
>
<Tooltip popupContent={tip}>
<div
className={cn(
'relative flex items-center justify-center w-8 h-8 rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs hover:bg-components-button-secondary-bg-hover',
(isInstallingWithError || isFailed) && 'border-components-button-destructive-secondary-border-hover bg-state-destructive-hover hover:bg-state-destructive-hover-alt',
)}
>
<RiInstallLine
className={cn(
'w-4 h-4 text-components-button-secondary-text',
(isInstallingWithError || isFailed) && 'text-components-button-destructive-secondary-text',
)}
/>
<div className='absolute -right-1 -top-1'>
{
isInstalling && (
<ProgressCircle
percentage={runningPlugins.length / totalPluginsLength * 100}
circleFillColor='fill-components-progress-brand-bg'
sectorFillColor='fill-components-progress-error-bg'
circleStrokeColor='stroke-components-progress-error-bg'
/>
)
}
{
isInstallingWithError && (
<ProgressCircle
percentage={runningPlugins.length / totalPluginsLength * 100}
circleFillColor='fill-components-progress-brand-bg'
sectorFillColor='fill-components-progress-error-bg'
circleStrokeColor='stroke-components-progress-error-bg'
/>
)
}
{
isSuccess && (
<RiCheckboxCircleFill className='w-3.5 h-3.5 text-text-success' />
)
}
{
isFailed && (
<RiErrorWarningFill className='w-3.5 h-3.5 text-text-destructive' />
)
}
</div>
</div>
</Tooltip>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-10'>
<div className='p-1 pb-2 w-[320px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
<div className='flex items-center px-2 pt-1 h-7 system-sm-semibold-uppercase'>{t('plugin.task.installedError')}</div>
<div className='flex items-center p-1 pl-2 h-8 rounded-lg hover:bg-state-base-hover'>
<div className='relative flex items-center justify-center mr-2 w-6 h-6 rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge'>
<RiErrorWarningFill className='absolute -right-0.5 -bottom-0.5 w-3 h-3 text-text-destructive' />
</div>
<div className='grow system-md-regular text-text-secondary truncate'>
DuckDuckGo Search
</div>
<Button
size='small'
variant='ghost-accent'
>
{t('common.operation.clear')}
</Button>
</div>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
</div>
)
}
export default PluginTasks

View File

@ -1,5 +1,5 @@
import { create } from 'zustand' import { create } from 'zustand'
import type { PluginTask } from '../types' import type { PluginTask } from '@/app/components/plugins/types'
import { fetchPluginTasks } from '@/service/plugins' import { fetchPluginTasks } from '@/service/plugins'
type PluginTasksStore = { type PluginTasksStore = {

View File

@ -12,7 +12,7 @@ import { pluginManifestToCardPluginProps } from '@/app/components/plugins/instal
import useGetIcon from '../install-plugin/base/use-get-icon' import useGetIcon from '../install-plugin/base/use-get-icon'
import { updateFromMarketPlace } from '@/service/plugins' import { updateFromMarketPlace } from '@/service/plugins'
import checkTaskStatus from '@/app/components/plugins/install-plugin/base/check-task-status' 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' const i18nPrefix = 'plugin.upgrade'

View File

@ -124,6 +124,12 @@ const translation = {
inDifyMarketplace: 'in Dify Marketplace', inDifyMarketplace: 'in Dify Marketplace',
moreFrom: 'More from 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 export default translation

View File

@ -124,6 +124,12 @@ const translation = {
inDifyMarketplace: '在 Dify 市场中', inDifyMarketplace: '在 Dify 市场中',
moreFrom: '更多来自市场', moreFrom: '更多来自市场',
}, },
task: {
installing: '{{installingLength}}/{{totalLength}} 插件安装中...',
installingWithError: '{{installingLength}}/{{totalLength}} 插件安装中,{{errorLength}} 安装失败。点击查看',
installError: '{{errorLength}} 个插件安装失败,点击查看',
installedError: '{{errorLength}} 个插件安装失败',
},
} }
export default translation export default translation