mirror of
https://github.com/langgenius/dify.git
synced 2026-04-27 11:06:46 +08:00
Merge branch 'feat/plugins' into dev/plugin-deploy
This commit is contained in:
commit
2252821cae
@ -29,6 +29,7 @@ import {
|
|||||||
useMarketplaceCollectionsAndPlugins,
|
useMarketplaceCollectionsAndPlugins,
|
||||||
useMarketplacePlugins,
|
useMarketplacePlugins,
|
||||||
} from './hooks'
|
} from './hooks'
|
||||||
|
import { getMarketplaceListCondition } from './utils'
|
||||||
|
|
||||||
export type MarketplaceContextValue = {
|
export type MarketplaceContextValue = {
|
||||||
intersected: boolean
|
intersected: boolean
|
||||||
@ -134,6 +135,7 @@ export const MarketplaceContextProvider = ({
|
|||||||
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
|
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
|
||||||
queryMarketplaceCollectionsAndPlugins({
|
queryMarketplaceCollectionsAndPlugins({
|
||||||
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
|
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
|
||||||
|
condition: getMarketplaceListCondition(activePluginTypeRef.current),
|
||||||
})
|
})
|
||||||
resetPlugins()
|
resetPlugins()
|
||||||
|
|
||||||
@ -156,6 +158,7 @@ export const MarketplaceContextProvider = ({
|
|||||||
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
|
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
|
||||||
queryMarketplaceCollectionsAndPlugins({
|
queryMarketplaceCollectionsAndPlugins({
|
||||||
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
|
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
|
||||||
|
condition: getMarketplaceListCondition(activePluginTypeRef.current),
|
||||||
})
|
})
|
||||||
resetPlugins()
|
resetPlugins()
|
||||||
|
|
||||||
@ -178,6 +181,7 @@ export const MarketplaceContextProvider = ({
|
|||||||
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
|
if (!searchPluginTextRef.current && !filterPluginTagsRef.current.length) {
|
||||||
queryMarketplaceCollectionsAndPlugins({
|
queryMarketplaceCollectionsAndPlugins({
|
||||||
category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type,
|
category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type,
|
||||||
|
condition: getMarketplaceListCondition(type),
|
||||||
})
|
})
|
||||||
resetPlugins()
|
resetPlugins()
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@ export type PluginsSort = {
|
|||||||
|
|
||||||
export type CollectionsAndPluginsSearchParams = {
|
export type CollectionsAndPluginsSearchParams = {
|
||||||
category?: string
|
category?: string
|
||||||
|
condition?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SearchParams = {
|
export type SearchParams = {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { Plugin } from '@/app/components/plugins/types'
|
import type { Plugin } from '@/app/components/plugins/types'
|
||||||
|
import { PluginType } from '@/app/components/plugins/types'
|
||||||
import type {
|
import type {
|
||||||
CollectionsAndPluginsSearchParams,
|
CollectionsAndPluginsSearchParams,
|
||||||
MarketplaceCollection,
|
MarketplaceCollection,
|
||||||
@ -14,7 +15,10 @@ export const getMarketplaceCollectionsAndPlugins = async (query?: CollectionsAnd
|
|||||||
let marketplaceCollections = [] as MarketplaceCollection[]
|
let marketplaceCollections = [] as MarketplaceCollection[]
|
||||||
let marketplaceCollectionPluginsMap = {} as Record<string, Plugin[]>
|
let marketplaceCollectionPluginsMap = {} as Record<string, Plugin[]>
|
||||||
try {
|
try {
|
||||||
const marketplaceCollectionsData = await globalThis.fetch(`${MARKETPLACE_API_PREFIX}/collections?page=1&page_size=100`, { cache: 'no-store' })
|
let marketplaceUrl = `${MARKETPLACE_API_PREFIX}/collections?page=1&page_size=100`
|
||||||
|
if (query?.condition)
|
||||||
|
marketplaceUrl += `&condition=${query.condition}`
|
||||||
|
const marketplaceCollectionsData = await globalThis.fetch(marketplaceUrl, { cache: 'no-store' })
|
||||||
const marketplaceCollectionsDataJson = await marketplaceCollectionsData.json()
|
const marketplaceCollectionsDataJson = await marketplaceCollectionsData.json()
|
||||||
marketplaceCollections = marketplaceCollectionsDataJson.data.collections
|
marketplaceCollections = marketplaceCollectionsDataJson.data.collections
|
||||||
await Promise.all(marketplaceCollections.map(async (collection: MarketplaceCollection) => {
|
await Promise.all(marketplaceCollections.map(async (collection: MarketplaceCollection) => {
|
||||||
@ -83,3 +87,16 @@ export const getMarketplacePlugins = async (query: PluginsSearchParams) => {
|
|||||||
marketplacePlugins,
|
marketplacePlugins,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getMarketplaceListCondition = (pluginType: string) => {
|
||||||
|
if (pluginType === PluginType.tool)
|
||||||
|
return 'category=tool'
|
||||||
|
|
||||||
|
if (pluginType === PluginType.model)
|
||||||
|
return 'category=model'
|
||||||
|
|
||||||
|
if (pluginType === PluginType.extension)
|
||||||
|
return 'category=endpoint'
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import { useCallback } from 'react'
|
import {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
import { TaskStatus } from '@/app/components/plugins/types'
|
import { TaskStatus } from '@/app/components/plugins/types'
|
||||||
import type { PluginStatus } from '@/app/components/plugins/types'
|
import type { PluginStatus } from '@/app/components/plugins/types'
|
||||||
import {
|
import {
|
||||||
@ -36,12 +41,49 @@ export const usePluginTaskStatus = () => {
|
|||||||
pluginId,
|
pluginId,
|
||||||
})
|
})
|
||||||
}, [mutate])
|
}, [mutate])
|
||||||
|
const totalPluginsLength = allPlugins.length
|
||||||
|
const runningPluginsLength = runningPlugins.length
|
||||||
|
const errorPluginsLength = errorPlugins.length
|
||||||
|
const successPluginsLength = successPlugins.length
|
||||||
|
|
||||||
|
const isInstalling = runningPluginsLength > 0 && errorPluginsLength === 0 && successPluginsLength === 0
|
||||||
|
const isInstallingWithSuccess = runningPluginsLength > 0 && successPluginsLength > 0 && errorPluginsLength === 0
|
||||||
|
const isInstallingWithError = runningPluginsLength > 0 && errorPluginsLength > 0
|
||||||
|
const isSuccess = successPluginsLength === totalPluginsLength && totalPluginsLength > 0
|
||||||
|
const isFailed = runningPluginsLength === 0 && (errorPluginsLength + successPluginsLength) === totalPluginsLength && totalPluginsLength > 0 && errorPluginsLength > 0
|
||||||
|
|
||||||
|
const [opacity, setOpacity] = useState(1)
|
||||||
|
const timerRef = useRef<NodeJS.Timeout | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSuccess && opacity > 0) {
|
||||||
|
if (timerRef.current) {
|
||||||
|
clearTimeout(timerRef.current)
|
||||||
|
timerRef.current = null
|
||||||
|
}
|
||||||
|
timerRef.current = setTimeout(() => {
|
||||||
|
setOpacity(v => v - 0.1)
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSuccess)
|
||||||
|
setOpacity(1)
|
||||||
|
}, [isSuccess, opacity])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
errorPlugins,
|
errorPlugins,
|
||||||
successPlugins,
|
successPlugins,
|
||||||
runningPlugins,
|
runningPlugins,
|
||||||
totalPluginsLength: allPlugins.length,
|
runningPluginsLength,
|
||||||
|
errorPluginsLength,
|
||||||
|
successPluginsLength,
|
||||||
|
totalPluginsLength,
|
||||||
|
isInstalling,
|
||||||
|
isInstallingWithSuccess,
|
||||||
|
isInstallingWithError,
|
||||||
|
isSuccess,
|
||||||
|
isFailed,
|
||||||
handleClearErrorPlugin,
|
handleClearErrorPlugin,
|
||||||
|
opacity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,37 +28,42 @@ const PluginTasks = () => {
|
|||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const {
|
const {
|
||||||
errorPlugins,
|
errorPlugins,
|
||||||
runningPlugins,
|
runningPluginsLength,
|
||||||
successPlugins,
|
successPluginsLength,
|
||||||
|
errorPluginsLength,
|
||||||
totalPluginsLength,
|
totalPluginsLength,
|
||||||
|
isInstalling,
|
||||||
|
isInstallingWithSuccess,
|
||||||
|
isInstallingWithError,
|
||||||
|
isSuccess,
|
||||||
|
isFailed,
|
||||||
handleClearErrorPlugin,
|
handleClearErrorPlugin,
|
||||||
|
opacity,
|
||||||
} = usePluginTaskStatus()
|
} = usePluginTaskStatus()
|
||||||
const { getIconUrl } = useGetIcon()
|
const { getIconUrl } = useGetIcon()
|
||||||
const runningPluginsLength = runningPlugins.length
|
|
||||||
const errorPluginsLength = errorPlugins.length
|
|
||||||
const successPluginsLength = successPlugins.length
|
|
||||||
|
|
||||||
const isInstalling = runningPluginsLength > 0 && errorPluginsLength === 0
|
|
||||||
const isInstallingWithError = runningPluginsLength > 0 && errorPluginsLength > 0
|
|
||||||
const isSuccess = successPluginsLength === totalPluginsLength && totalPluginsLength > 0
|
|
||||||
const isFailed = runningPluginsLength === 0 && (errorPluginsLength + successPluginsLength) === totalPluginsLength && totalPluginsLength > 0
|
|
||||||
|
|
||||||
const tip = useMemo(() => {
|
const tip = useMemo(() => {
|
||||||
if (isInstalling)
|
if (isInstalling)
|
||||||
return t('plugin.task.installing', { installingLength: runningPlugins.length, totalLength: totalPluginsLength })
|
return t('plugin.task.installing', { installingLength: runningPluginsLength })
|
||||||
|
|
||||||
|
if (isInstallingWithSuccess)
|
||||||
|
return t('plugin.task.installingWithSuccess', { installingLength: runningPluginsLength, successLength: successPluginsLength })
|
||||||
|
|
||||||
if (isInstallingWithError)
|
if (isInstallingWithError)
|
||||||
return t('plugin.task.installingWithError', { installingLength: runningPlugins.length, totalLength: totalPluginsLength, errorLength: errorPlugins.length })
|
return t('plugin.task.installingWithError', { installingLength: runningPluginsLength, successLength: successPluginsLength, errorLength: errorPluginsLength })
|
||||||
|
|
||||||
if (isFailed)
|
if (isFailed)
|
||||||
return t('plugin.task.installError', { errorLength: errorPlugins.length })
|
return t('plugin.task.installError', { errorLength: errorPluginsLength })
|
||||||
}, [isInstalling, isInstallingWithError, isFailed, errorPlugins, runningPlugins, totalPluginsLength, t])
|
}, [isInstalling, isInstallingWithSuccess, isInstallingWithError, isFailed, errorPluginsLength, runningPluginsLength, successPluginsLength, t])
|
||||||
|
|
||||||
if (!totalPluginsLength)
|
if (!totalPluginsLength)
|
||||||
return null
|
return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center'>
|
<div
|
||||||
|
className='flex items-center'
|
||||||
|
style={{ opacity }}
|
||||||
|
>
|
||||||
<PortalToFollowElem
|
<PortalToFollowElem
|
||||||
open={open}
|
open={open}
|
||||||
onOpenChange={setOpen}
|
onOpenChange={setOpen}
|
||||||
@ -70,7 +75,7 @@ const PluginTasks = () => {
|
|||||||
>
|
>
|
||||||
<PortalToFollowElemTrigger
|
<PortalToFollowElemTrigger
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isFailed || isInstallingWithError)
|
if (isFailed)
|
||||||
setOpen(v => !v)
|
setOpen(v => !v)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -89,16 +94,17 @@ const PluginTasks = () => {
|
|||||||
/>
|
/>
|
||||||
<div className='absolute -right-1 -top-1'>
|
<div className='absolute -right-1 -top-1'>
|
||||||
{
|
{
|
||||||
isInstalling && (
|
(isInstalling || isInstallingWithSuccess) && (
|
||||||
<ProgressCircle
|
<ProgressCircle
|
||||||
percentage={runningPlugins.length / totalPluginsLength * 100}
|
percentage={successPluginsLength / totalPluginsLength * 100}
|
||||||
|
circleFillColor='fill-components-progress-brand-bg'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
isInstallingWithError && (
|
isInstallingWithError && (
|
||||||
<ProgressCircle
|
<ProgressCircle
|
||||||
percentage={runningPlugins.length / totalPluginsLength * 100}
|
percentage={runningPluginsLength / totalPluginsLength * 100}
|
||||||
circleFillColor='fill-components-progress-brand-bg'
|
circleFillColor='fill-components-progress-brand-bg'
|
||||||
sectorFillColor='fill-components-progress-error-border'
|
sectorFillColor='fill-components-progress-error-border'
|
||||||
circleStrokeColor='stroke-components-progress-error-border'
|
circleStrokeColor='stroke-components-progress-error-border'
|
||||||
@ -121,35 +127,50 @@ const PluginTasks = () => {
|
|||||||
</PortalToFollowElemTrigger>
|
</PortalToFollowElemTrigger>
|
||||||
<PortalToFollowElemContent className='z-[11]'>
|
<PortalToFollowElemContent className='z-[11]'>
|
||||||
<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='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'>
|
<div className='sticky top-0 flex items-center justify-between px-2 pt-1 h-7 system-sm-semibold-uppercase'>
|
||||||
{t('plugin.task.installedError', { errorLength: errorPlugins.length })}
|
{t('plugin.task.installedError', { errorLength: errorPluginsLength })}
|
||||||
|
<Button
|
||||||
|
className='shrink-0'
|
||||||
|
size='small'
|
||||||
|
variant='ghost'
|
||||||
|
>
|
||||||
|
{t('plugin.task.clearAll')}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{
|
<div className='max-h-[400px] overflow-y-auto'>
|
||||||
errorPlugins.map(errorPlugin => (
|
{
|
||||||
<div
|
errorPlugins.map(errorPlugin => (
|
||||||
key={errorPlugin.plugin_unique_identifier}
|
<div
|
||||||
className='flex items-center p-1 pl-2 h-8 rounded-lg hover:bg-state-base-hover'
|
key={errorPlugin.plugin_unique_identifier}
|
||||||
>
|
className='flex p-2 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 z-10 w-3 h-3 text-text-destructive' />
|
|
||||||
<CardIcon
|
|
||||||
size='tiny'
|
|
||||||
src={getIconUrl(errorPlugin.icon)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='grow system-md-regular text-text-secondary truncate'>
|
|
||||||
{errorPlugin.labels[language]}
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
size='small'
|
|
||||||
variant='ghost-accent'
|
|
||||||
onClick={() => handleClearErrorPlugin(errorPlugin.taskId, errorPlugin.plugin_unique_identifier)}
|
|
||||||
>
|
>
|
||||||
{t('common.operation.clear')}
|
<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'>
|
||||||
</Button>
|
<RiErrorWarningFill className='absolute -right-0.5 -bottom-0.5 z-10 w-3 h-3 text-text-destructive' />
|
||||||
</div>
|
<CardIcon
|
||||||
))
|
size='tiny'
|
||||||
}
|
src={getIconUrl(errorPlugin.icon)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='grow'>
|
||||||
|
<div className='system-md-regular text-text-secondary truncate'>
|
||||||
|
{errorPlugin.labels[language]}
|
||||||
|
</div>
|
||||||
|
<div className='system-xs-regular text-text-destructive break-all'>
|
||||||
|
{errorPlugin.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className='shrink-0'
|
||||||
|
size='small'
|
||||||
|
variant='ghost'
|
||||||
|
onClick={() => handleClearErrorPlugin(errorPlugin.taskId, errorPlugin.plugin_unique_identifier)}
|
||||||
|
>
|
||||||
|
{t('common.operation.clear')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PortalToFollowElemContent>
|
</PortalToFollowElemContent>
|
||||||
</PortalToFollowElem>
|
</PortalToFollowElem>
|
||||||
|
|||||||
@ -8,6 +8,5 @@ export const getValidTagKeys = (tags: string[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getValidCategoryKeys = (category?: string) => {
|
export const getValidCategoryKeys = (category?: string) => {
|
||||||
const currentCategory = categoryKeys.find(key => key === category)
|
return categoryKeys.find(key => key === category)
|
||||||
return currentCategory ? `${currentCategory}s` : ''
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import {
|
|||||||
useMarketplacePlugins,
|
useMarketplacePlugins,
|
||||||
} from '@/app/components/plugins/marketplace/hooks'
|
} from '@/app/components/plugins/marketplace/hooks'
|
||||||
import { PluginType } from '@/app/components/plugins/types'
|
import { PluginType } from '@/app/components/plugins/types'
|
||||||
|
import { getMarketplaceListCondition } from '@/app/components/plugins/marketplace/utils'
|
||||||
|
|
||||||
export const useMarketplace = (searchPluginText: string, filterPluginTags: string[]) => {
|
export const useMarketplace = (searchPluginText: string, filterPluginTags: string[]) => {
|
||||||
const {
|
const {
|
||||||
@ -39,7 +40,10 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
queryMarketplaceCollectionsAndPlugins({ category: PluginType.tool })
|
queryMarketplaceCollectionsAndPlugins({
|
||||||
|
category: PluginType.tool,
|
||||||
|
condition: getMarketplaceListCondition(PluginType.tool),
|
||||||
|
})
|
||||||
resetPlugins()
|
resetPlugins()
|
||||||
}
|
}
|
||||||
}, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins])
|
}, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins])
|
||||||
|
|||||||
@ -162,10 +162,12 @@ const translation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
task: {
|
task: {
|
||||||
installing: 'Installing {{installingLength}}/{{totalLength}} plugins...',
|
installing: 'Installing {{installingLength}} plugins, 0 done.',
|
||||||
installingWithError: 'Installing {{installingLength}} of {{totalLength}} plugins, {{errorLength}} failed, click to view',
|
installingWithSuccess: 'Installing {{installingLength}} plugins, {{successLength}} success.',
|
||||||
|
installingWithError: 'Installing {{installingLength}} plugins, {{successLength}} success, {{errorLength}} failed',
|
||||||
installError: '{{errorLength}} plugins failed to install, click to view',
|
installError: '{{errorLength}} plugins failed to install, click to view',
|
||||||
installedError: '{{errorLength}} plugins failed to install',
|
installedError: '{{errorLength}} plugins failed to install',
|
||||||
|
clearAll: 'Clear all',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -162,10 +162,12 @@ const translation = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
task: {
|
task: {
|
||||||
installing: '{{installingLength}}/{{totalLength}} 插件安装中...',
|
installing: '{{installingLength}} 个插件安装中,0 已完成',
|
||||||
installingWithError: '{{installingLength}}/{{totalLength}} 插件安装中,{{errorLength}} 安装失败。点击查看',
|
installingWithSuccess: '{{installingLength}} 个插件安装中,{{successLength}} 安装成功',
|
||||||
|
installingWithError: '{{installingLength}} 个插件安装中,{{successLength}} 安装成功,{{errorLength}} 安装失败',
|
||||||
installError: '{{errorLength}} 个插件安装失败,点击查看',
|
installError: '{{errorLength}} 个插件安装失败,点击查看',
|
||||||
installedError: '{{errorLength}} 个插件安装失败',
|
installedError: '{{errorLength}} 个插件安装失败',
|
||||||
|
clearAll: '清除所有',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user