fix: localize workflow block search filters

This commit is contained in:
lyzno1 2025-10-29 11:55:30 +08:00
parent f9df61e648
commit f06dc3ef90
No known key found for this signature in database
3 changed files with 91 additions and 35 deletions

View File

@ -34,6 +34,7 @@ import Link from 'next/link'
import Divider from '@/app/components/base/divider'
import { RiArrowRightUpLine } from '@remixicon/react'
import { getMarketplaceUrl } from '@/utils/var'
import { useGetLanguage } from '@/context/i18n'
const marketplaceFooterClassName = 'system-sm-medium z-10 flex h-8 flex-none cursor-pointer items-center rounded-b-lg border-[0.5px] border-t border-components-panel-border bg-components-panel-bg-blur px-4 py-1 text-text-accent-light-mode-only shadow-lg'
@ -83,6 +84,7 @@ const AllTools = ({
onFeaturedInstallSuccess,
}: AllToolsProps) => {
const { t } = useTranslation()
const language = useGetLanguage()
const tabs = useToolTabs()
const [activeTab, setActiveTab] = useState(ToolTypeEnum.All)
const [activeView, setActiveView] = useState<ViewType>(ViewType.flat)
@ -117,12 +119,29 @@ const AllTools = ({
mergedTools = mcpTools
const normalizedSearch = trimmedSearchText.toLowerCase()
const getLocalizedText = (text?: Record<string, string> | null) => {
if (!text)
return ''
if (text[language])
return text[language]
if (text['en-US'])
return text['en-US']
const firstValue = Object.values(text).find(Boolean)
return firstValue || ''
}
if (!hasFilter || !normalizedSearch)
return mergedTools.filter(toolWithProvider => toolWithProvider.tools.length > 0)
return mergedTools.reduce<ToolWithProvider[]>((acc, toolWithProvider) => {
const providerMatches = isMatchingKeywords(toolWithProvider.name, normalizedSearch)
const providerLabel = getLocalizedText(toolWithProvider.label)
const providerMatches = [
toolWithProvider.name,
providerLabel,
].some(text => isMatchingKeywords(text || '', normalizedSearch))
if (providerMatches) {
if (toolWithProvider.tools.length > 0)
@ -131,7 +150,11 @@ const AllTools = ({
}
const matchedTools = toolWithProvider.tools.filter((tool) => {
return tool.name.toLowerCase().includes(normalizedSearch)
const toolLabel = getLocalizedText(tool.label)
return [
tool.name,
toolLabel,
].some(text => isMatchingKeywords(text || '', normalizedSearch))
})
if (matchedTools.length > 0) {
@ -143,7 +166,7 @@ const AllTools = ({
return acc
}, [])
}, [activeTab, buildInTools, customTools, workflowTools, mcpTools, trimmedSearchText, hasFilter])
}, [activeTab, buildInTools, customTools, workflowTools, mcpTools, trimmedSearchText, hasFilter, language])
const {
queryPluginsWithDebounced: fetchPlugins,
@ -235,39 +258,37 @@ const AllTools = ({
</div>
</>
)}
{(hasToolsListContent || enable_marketplace) && (
{hasToolsListContent && (
<>
<div className='px-3 pb-1 pt-2'>
<span className='system-xs-medium text-text-primary'>{t('tools.allTools')}</span>
</div>
{hasToolsListContent && (
<Tools
className={toolContentClassName}
tools={tools}
onSelect={onSelect}
canNotSelectMultiple={canNotSelectMultiple}
onSelectMultiple={onSelectMultiple}
toolType={activeTab}
viewType={isSupportGroupView ? activeView : ViewType.flat}
hasSearchText={hasSearchText}
selectedTools={selectedTools}
canChooseMCPTool={canChooseMCPTool}
isShowRAGRecommendations={isShowRAGRecommendations}
/>
)}
{enable_marketplace && (
<PluginList
ref={pluginRef}
wrapElemRef={wrapElemRef as RefObject<HTMLElement>}
list={notInstalledPlugins}
searchText={searchText}
toolContentClassName={toolContentClassName}
tags={tags}
hideFindMoreFooter
/>
)}
<Tools
className={toolContentClassName}
tools={tools}
onSelect={onSelect}
canNotSelectMultiple={canNotSelectMultiple}
onSelectMultiple={onSelectMultiple}
toolType={activeTab}
viewType={isSupportGroupView ? activeView : ViewType.flat}
hasSearchText={hasSearchText}
selectedTools={selectedTools}
canChooseMCPTool={canChooseMCPTool}
isShowRAGRecommendations={isShowRAGRecommendations}
/>
</>
)}
{enable_marketplace && (
<PluginList
ref={pluginRef}
wrapElemRef={wrapElemRef as RefObject<HTMLElement>}
list={notInstalledPlugins}
searchText={searchText}
toolContentClassName={toolContentClassName}
tags={tags}
hideFindMoreFooter
/>
)}
</div>
{shouldShowEmptyState && (

View File

@ -36,6 +36,13 @@ const StartBlocks = ({
const filteredBlocks = useMemo(() => {
// Check if Start node already exists in workflow
const hasStartNode = nodes.some(node => (node.data as CommonNodeType)?.type === BlockEnumValues.Start)
const normalizedSearch = searchText.toLowerCase()
const getDisplayName = (blockType: BlockEnum) => {
if (blockType === BlockEnumValues.TriggerWebhook)
return t('workflow.customWebhook')
return t(`workflow.blocks.${blockType}`)
}
return START_BLOCKS.filter((block) => {
// Hide User Input (Start) if it already exists in workflow
@ -43,13 +50,14 @@ const StartBlocks = ({
return false
// Filter by search text
if (!block.title.toLowerCase().includes(searchText.toLowerCase()))
const displayName = getDisplayName(block.type).toLowerCase()
if (!displayName.includes(normalizedSearch) && !block.title.toLowerCase().includes(normalizedSearch))
return false
// availableBlocksTypes now contains properly filtered entry node types from parent
return availableBlocksTypes.includes(block.type)
})
}, [searchText, availableBlocksTypes, nodes])
}, [searchText, availableBlocksTypes, nodes, t])
const isEmpty = filteredBlocks.length === 0

View File

@ -4,6 +4,7 @@ import { useAllTriggerPlugins } from '@/service/use-triggers'
import TriggerPluginItem from './item'
import type { BlockEnum } from '../../types'
import type { TriggerDefaultValue, TriggerWithProvider } from '../types'
import { useGetLanguage } from '@/context/i18n'
type TriggerPluginListProps = {
onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void
@ -18,10 +19,30 @@ const TriggerPluginList = ({
onContentStateChange,
}: TriggerPluginListProps) => {
const { data: triggerPluginsData } = useAllTriggerPlugins()
const language = useGetLanguage()
const normalizedSearch = searchText.trim().toLowerCase()
const triggerPlugins = useMemo(() => {
const plugins = triggerPluginsData || []
const getLocalizedText = (text?: Record<string, string> | null) => {
if (!text)
return ''
if (text[language])
return text[language]
if (text['en-US'])
return text['en-US']
const firstValue = Object.values(text).find(Boolean)
return (typeof firstValue === 'string') ? firstValue : ''
}
const getSearchableTexts = (name: string, label?: Record<string, string> | null) => {
const localized = getLocalizedText(label)
const values = [localized, name].filter(Boolean)
return values.length > 0 ? values : ['']
}
const isMatchingKeywords = (value: string) => value.toLowerCase().includes(normalizedSearch)
if (!normalizedSearch)
return plugins.filter(triggerWithProvider => triggerWithProvider.events.length > 0)
@ -30,7 +51,10 @@ const TriggerPluginList = ({
if (triggerWithProvider.events.length === 0)
return acc
const providerMatches = triggerWithProvider.name.toLowerCase().includes(normalizedSearch)
const providerMatches = getSearchableTexts(
triggerWithProvider.name,
triggerWithProvider.label,
).some(text => isMatchingKeywords(text))
if (providerMatches) {
acc.push(triggerWithProvider)
@ -38,7 +62,10 @@ const TriggerPluginList = ({
}
const matchedEvents = triggerWithProvider.events.filter((event) => {
return event.name.toLowerCase().includes(normalizedSearch)
return getSearchableTexts(
event.name,
event.label,
).some(text => isMatchingKeywords(text))
})
if (matchedEvents.length > 0) {
@ -50,7 +77,7 @@ const TriggerPluginList = ({
return acc
}, [])
}, [triggerPluginsData, normalizedSearch])
}, [triggerPluginsData, normalizedSearch, language])
const hasContent = triggerPlugins.length > 0