feat: suggestions ui

This commit is contained in:
lyzno1 2025-10-22 12:14:05 +08:00
parent f179b03d6e
commit 5cb268e99b
No known key found for this signature in database
2 changed files with 86 additions and 35 deletions

View File

@ -153,7 +153,7 @@ const AllTools = ({
data: recommendedPlugins = [],
isLoading: isLoadingRecommended,
} = useRecommendedMarketplacePlugins({
enabled: enable_marketplace,
enabled: enable_marketplace && !isInRAGPipeline,
})
const recommendedPluginIds = useMemo(
() => recommendedPlugins.map(plugin => plugin.plugin_id),
@ -193,6 +193,7 @@ const AllTools = ({
&& activeTab === ToolTypeEnum.All
&& !hasFilter
&& !isLoadingRecommended
&& !isInRAGPipeline
&& recommendedPlugins.length > 0
return (
@ -241,6 +242,7 @@ const AllTools = ({
canChooseMCPTool={canChooseMCPTool}
installedPluginIds={installedPluginIds}
loadingInstalledStatus={loadingRecommendedInstallStatus}
isLoading={isLoadingRecommended}
onInstallSuccess={async () => {
invalidateBuiltInTools()
await installedCheck.refetch()

View File

@ -10,8 +10,11 @@ import ActionItem from './tool/action-item'
import type { Tool } from '@/app/components/tools/types'
import { CollectionType } from '@/app/components/tools/types'
import BlockIcon from '../block-icon'
import { RiArrowDownSLine, RiArrowUpSLine, RiLoader2Line } from '@remixicon/react'
import { RiArrowDownSLine, RiArrowRightSLine, RiArrowUpSLine, RiLoader2Line } from '@remixicon/react'
import { useInstallPackageFromMarketPlace } from '@/service/use-plugins'
import Loading from '@/app/components/base/loading'
import Link from 'next/link'
import { getMarketplaceUrl } from '@/utils/var'
const MAX_RECOMMENDED_COUNT = 15
const INITIAL_VISIBLE_COUNT = 5
@ -24,6 +27,7 @@ type FeaturedToolsProps = {
canChooseMCPTool?: boolean
installedPluginIds: Set<string>
loadingInstalledStatus: boolean
isLoading?: boolean
onInstallSuccess?: () => void
}
@ -33,6 +37,8 @@ function isToolSelected(tool: Tool, provider: ToolWithProvider, selectedTools?:
return selectedTools.some(item => (item.provider_name === provider.name || item.provider_name === provider.id) && item.tool_name === tool.name)
}
const STORAGE_KEY = 'workflow_tools_featured_collapsed'
const FeaturedTools = ({
plugins,
providerMap,
@ -41,12 +47,15 @@ const FeaturedTools = ({
canChooseMCPTool,
installedPluginIds,
loadingInstalledStatus,
isLoading = false,
onInstallSuccess,
}: FeaturedToolsProps) => {
const { t } = useTranslation()
const language = useGetLanguage()
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
const [installingIdentifier, setInstallingIdentifier] = useState<string | null>(null)
const [isCollapsed, setIsCollapsed] = useState<boolean>(false)
const installMutation = useInstallPackageFromMarketPlace({
onSuccess: () => {
onInstallSuccess?.()
@ -56,6 +65,20 @@ const FeaturedTools = ({
},
})
useEffect(() => {
if (typeof window === 'undefined')
return
const stored = window.localStorage.getItem(STORAGE_KEY)
if (stored !== null)
setIsCollapsed(stored === 'true')
}, [])
useEffect(() => {
if (typeof window === 'undefined')
return
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
}, [isCollapsed])
useEffect(() => {
setVisibleCount(INITIAL_VISIBLE_COUNT)
}, [plugins])
@ -65,43 +88,69 @@ const FeaturedTools = ({
[plugins, visibleCount],
)
if (!visiblePlugins.length)
return null
const showMore = visibleCount < Math.min(MAX_RECOMMENDED_COUNT, plugins.length)
const isMutating = installMutation.isPending
const showEmptyState = !isLoading && !visiblePlugins.length
return (
<div className='px-3 pb-3 pt-2'>
<div className='system-xs-medium mb-2 text-text-tertiary'>
{t('workflow.tabs.featuredTools')}
</div>
<div className='space-y-2'>
{visiblePlugins.map(plugin => renderFeaturedToolItem({
plugin,
providerMap,
installedPluginIds,
installMutationPending: installMutation.isPending,
installingIdentifier,
loadingInstalledStatus,
canChooseMCPTool,
onSelect,
selectedTools,
language,
installPlugin: installMutation.mutate,
setInstallingIdentifier,
}))}
</div>
{showMore && (
<Button
className='mt-2 w-full'
size='small'
variant='ghost'
onClick={() => {
setVisibleCount(count => Math.min(count + INITIAL_VISIBLE_COUNT, MAX_RECOMMENDED_COUNT, plugins.length))
}}
>
{t('workflow.tabs.showMoreFeatured')}
</Button>
<button
type='button'
className='flex w-full items-center justify-between rounded-md px-0 py-1 text-left text-text-tertiary'
onClick={() => setIsCollapsed(prev => !prev)}
>
<span className='system-xs-medium'>{t('workflow.tabs.featuredTools')}</span>
{isCollapsed ? <RiArrowRightSLine className='size-3.5' /> : <RiArrowDownSLine className='size-3.5' />}
</button>
{!isCollapsed && (
<>
{isLoading && (
<div className='py-3'>
<Loading type='app' />
</div>
)}
{showEmptyState && (
<p className='system-xs-regular py-2 text-text-tertiary'>
<Link className='text-text-accent' href={getMarketplaceUrl('', { category: 'tool' })} target='_blank' rel='noopener noreferrer'>
{t('workflow.tabs.noFeaturedPlugins')}
</Link>
</p>
)}
{!isLoading && visiblePlugins.length > 0 && (
<div className='space-y-2'>
{visiblePlugins.map(plugin => renderFeaturedToolItem({
plugin,
providerMap,
installedPluginIds,
installMutationPending: isMutating,
installingIdentifier,
loadingInstalledStatus,
canChooseMCPTool,
onSelect,
selectedTools,
language,
installPlugin: installMutation.mutate,
setInstallingIdentifier,
}))}
</div>
)}
{!isLoading && visiblePlugins.length > 0 && showMore && (
<Button
className='mt-2 w-full'
size='small'
variant='ghost'
onClick={() => {
setVisibleCount(count => Math.min(count + INITIAL_VISIBLE_COUNT, MAX_RECOMMENDED_COUNT, plugins.length))
}}
>
{t('workflow.tabs.showMoreFeatured')}
</Button>
)}
</>
)}
</div>
)