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

View File

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