Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins
commit
20357beda4
@ -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
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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 = {
|
||||
Loading…
Reference in New Issue