Merge branch 'feat/plugins' into dev/plugin-deploy

pull/12372/head
StyleZhang 1 year ago
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…
Cancel
Save