feat: install plugin button

This commit is contained in:
AkaraChen 2025-01-03 10:35:06 +08:00
parent 5fb356fd33
commit 5ba0b85738
5 changed files with 37 additions and 16 deletions

View File

@ -83,7 +83,7 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) =>
if (!list) return [] if (!list) return []
return list.filter(tool => tool.name.toLowerCase().includes(query.toLowerCase())) return list.filter(tool => tool.name.toLowerCase().includes(query.toLowerCase()))
}, [query, list]) }, [query, list])
const isShowError = (['plugin-not-found', 'strategy-not-found'] as Array<undefined | StrategyStatus>).includes(strategyStatus) const showError = (['plugin-not-found', 'strategy-not-found'] as Array<undefined | StrategyStatus>).includes(strategyStatus)
const icon = list?.find( const icon = list?.find(
coll => coll.tools?.find(tool => tool.name === value?.agent_strategy_name), coll => coll.tools?.find(tool => tool.name === value?.agent_strategy_name),
)?.icon as string | undefined )?.icon as string | undefined
@ -105,8 +105,8 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) =>
{value?.agent_strategy_label || t('workflow.nodes.agent.strategy.selectTip')} {value?.agent_strategy_label || t('workflow.nodes.agent.strategy.selectTip')}
</p> </p>
{value && <div className='ml-auto flex items-center gap-1'> {value && <div className='ml-auto flex items-center gap-1'>
{strategyStatus === 'plugin-not-found' && <InstallPluginButton onClick={e => e.stopPropagation()} size={'small'} />} {strategyStatus === 'plugin-not-found' && <InstallPluginButton onClick={e => e.stopPropagation()} size={'small'} uniqueIdentifier={value.plugin_unique_identifier} />}
{isShowError ? <ExternalNotInstallWarn /> : <RiArrowDownSLine className='size-4 text-text-tertiary' />} {showError ? <ExternalNotInstallWarn /> : <RiArrowDownSLine className='size-4 text-text-tertiary' />}
</div>} </div>}
</div> </div>
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>

View File

@ -1,6 +1,6 @@
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { RiInstallLine, RiLoader2Line } from '@remixicon/react' import { RiInstallLine, RiLoader2Line } from '@remixicon/react'
import type { ComponentProps } from 'react' import type { ComponentProps, MouseEventHandler } from 'react'
import classNames from '@/utils/classnames' import classNames from '@/utils/classnames'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useCheckInstalled, useInstallPackageFromMarketPlace } from '@/service/use-plugins' import { useCheckInstalled, useInstallPackageFromMarketPlace } from '@/service/use-plugins'
@ -21,13 +21,21 @@ export const InstallPluginButton = (props: InstallPluginButtonProps) => {
manifest.refetch() manifest.refetch()
}, },
}) })
const handleInstall = () => { const handleInstall: MouseEventHandler = (e) => {
e.stopPropagation()
install.mutate(uniqueIdentifier) install.mutate(uniqueIdentifier)
} }
const isLoading = manifest.isLoading || install.isPending
if (!manifest.data) return null if (!manifest.data) return null
if (manifest.data.plugins.some(plugin => plugin.id === uniqueIdentifier)) return null if (manifest.data.plugins.some(plugin => plugin.id === uniqueIdentifier)) return null
return <Button variant={'secondary'} disabled={install.isPending} {...rest} onClick={handleInstall} className={classNames('flex items-center', className)} > return <Button
{install.isPending ? t('workflow.nodes.agent.pluginInstaller.install') : t('workflow.nodes.agent.pluginInstaller.installing')} variant={'secondary'}
{!install.isPending ? <RiInstallLine className='size-4 ml-1' /> : <RiLoader2Line className='size-4 ml-1 animate-spin' />} disabled={isLoading}
{...rest}
onClick={handleInstall}
className={classNames('flex items-center', className)}
>
{!isLoading ? t('workflow.nodes.agent.pluginInstaller.install') : t('workflow.nodes.agent.pluginInstaller.installing')}
{!isLoading ? <RiInstallLine className='size-4 ml-1' /> : <RiLoader2Line className='size-4 ml-1 animate-spin' />}
</Button> </Button>
} }

View File

@ -8,12 +8,12 @@ import {
} from '@/app/components/workflow/hooks' } from '@/app/components/workflow/hooks'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { type ToolVarInputs, VarType } from '../tool/types' import { type ToolVarInputs, VarType } from '../tool/types'
import { useCheckInstalled } from '@/service/use-plugins' import { useCheckInstalled, useFetchPluginsInMarketPlaceByIds } from '@/service/use-plugins'
import type { Var } from '../../types' import type { Var } from '../../types'
import { VarType as VarKindType } from '../../types' import { VarType as VarKindType } from '../../types'
import useAvailableVarList from '../_base/hooks/use-available-var-list' import useAvailableVarList from '../_base/hooks/use-available-var-list'
export type StrategyStatus = 'loading' | 'plugin-not-found' | 'strategy-not-found' | 'success' export type StrategyStatus = 'loading' | 'plugin-not-found' | 'plugin-not-found-and-not-in-marketplace' | 'strategy-not-found' | 'success'
const useConfig = (id: string, payload: AgentNodeType) => { const useConfig = (id: string, payload: AgentNodeType) => {
const { nodesReadOnly: readOnly } = useNodesReadOnly() const { nodesReadOnly: readOnly } = useNodesReadOnly()
@ -25,16 +25,26 @@ const useConfig = (id: string, payload: AgentNodeType) => {
}) })
const strategyProvider = useStrategyProviderDetail( const strategyProvider = useStrategyProviderDetail(
inputs.agent_strategy_provider_name || '', inputs.agent_strategy_provider_name || '',
{ retry: false },
) )
const currentStrategy = strategyProvider.data?.declaration.strategies.find( const currentStrategy = strategyProvider.data?.declaration.strategies.find(
str => str.identity.name === inputs.agent_strategy_name, str => str.identity.name === inputs.agent_strategy_name,
) )
const marketplace = useFetchPluginsInMarketPlaceByIds([inputs.agent_strategy_provider_name!], {
retry: false,
})
const currentStrategyStatus: StrategyStatus = useMemo(() => { const currentStrategyStatus: StrategyStatus = useMemo(() => {
if (strategyProvider.isLoading) return 'loading' if (strategyProvider.isLoading || marketplace.isLoading) return 'loading'
if (strategyProvider.isError) return 'plugin-not-found' if (strategyProvider.isError) {
if (marketplace.data && marketplace.data.data.plugins.length === 0)
return 'plugin-not-found-and-not-in-marketplace'
return 'plugin-not-found'
}
if (!currentStrategy) return 'strategy-not-found' if (!currentStrategy) return 'strategy-not-found'
return 'success' return 'success'
}, [currentStrategy, strategyProvider]) }, [currentStrategy, marketplace, strategyProvider.isError, strategyProvider.isLoading])
console.log('currentStrategyStatus', currentStrategyStatus)
const pluginId = inputs.agent_strategy_provider_name?.split('/').splice(0, 2).join('/') const pluginId = inputs.agent_strategy_provider_name?.split('/').splice(0, 2).join('/')
const pluginDetail = useCheckInstalled({ const pluginDetail = useCheckInstalled({
pluginIds: [pluginId || ''], pluginIds: [pluginId || ''],

View File

@ -22,7 +22,7 @@ import type {
PluginsSearchParams, PluginsSearchParams,
} from '@/app/components/plugins/marketplace/types' } from '@/app/components/plugins/marketplace/types'
import { get, getMarketplace, post, postMarketplace } from './base' import { get, getMarketplace, post, postMarketplace } from './base'
import type { MutateOptions } from '@tanstack/react-query' import type { MutateOptions, QueryOptions } from '@tanstack/react-query'
import { import {
useMutation, useMutation,
useQuery, useQuery,
@ -321,8 +321,9 @@ export const useMutationPluginsFromMarketplace = () => {
}) })
} }
export const useFetchPluginsInMarketPlaceByIds = (unique_identifiers: string[]) => { export const useFetchPluginsInMarketPlaceByIds = (unique_identifiers: string[], options?: QueryOptions<{ data: PluginsFromMarketplaceResponse }>) => {
return useQuery({ return useQuery({
...options,
queryKey: [NAME_SPACE, 'fetchPluginsInMarketPlaceByIds', unique_identifiers], queryKey: [NAME_SPACE, 'fetchPluginsInMarketPlaceByIds', unique_identifiers],
queryFn: () => postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/identifier/batch', { queryFn: () => postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/identifier/batch', {
body: { body: {

View File

@ -2,6 +2,7 @@ import type {
StrategyPluginDetail, StrategyPluginDetail,
} from '@/app/components/plugins/types' } from '@/app/components/plugins/types'
import { useInvalid } from './use-base' import { useInvalid } from './use-base'
import type { QueryOptions } from '@tanstack/react-query'
import { import {
useQuery, useQuery,
} from '@tanstack/react-query' } from '@tanstack/react-query'
@ -21,8 +22,9 @@ export const useInvalidateStrategyProviders = () => {
return useInvalid(useStrategyListKey) return useInvalid(useStrategyListKey)
} }
export const useStrategyProviderDetail = (agentProvider: string) => { export const useStrategyProviderDetail = (agentProvider: string, options?: QueryOptions<StrategyPluginDetail>) => {
return useQuery<StrategyPluginDetail>({ return useQuery<StrategyPluginDetail>({
...options,
queryKey: [NAME_SPACE, 'detail', agentProvider], queryKey: [NAME_SPACE, 'detail', agentProvider],
queryFn: () => fetchStrategyDetail(agentProvider), queryFn: () => fetchStrategyDetail(agentProvider),
enabled: !!agentProvider, enabled: !!agentProvider,