mirror of https://github.com/langgenius/dify.git
Improve workflow block selector search functionality (#24707)
This commit is contained in:
parent
89ad6ad902
commit
64c7be59b7
|
|
@ -1,5 +1,5 @@
|
|||
'use client'
|
||||
import { RiCloseLine, RiSearchLine } from '@remixicon/react'
|
||||
import { RiCloseCircleFill, RiSearchLine } from '@remixicon/react'
|
||||
import TagsFilter from './tags-filter'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import cn from '@/utils/classnames'
|
||||
|
|
@ -48,7 +48,7 @@ const SearchBox = ({
|
|||
<RiSearchLine className='mr-1.5 size-4 text-text-placeholder' />
|
||||
<input
|
||||
className={cn(
|
||||
'body-md-medium block grow appearance-none bg-transparent text-text-secondary outline-none',
|
||||
'system-sm-regular block grow appearance-none bg-transparent text-text-secondary outline-none',
|
||||
)}
|
||||
value={search}
|
||||
onChange={(e) => {
|
||||
|
|
@ -58,10 +58,8 @@ const SearchBox = ({
|
|||
/>
|
||||
{
|
||||
search && (
|
||||
<div className='absolute right-2 top-1/2 -translate-y-1/2'>
|
||||
<ActionButton onClick={() => onSearchChange('')}>
|
||||
<RiCloseLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
<div className={cn('group absolute right-2 top-1/2 -translate-y-1/2 cursor-pointer p-[1px]')} onClick={() => onSearchChange('')}>
|
||||
<RiCloseCircleFill className='h-3.5 w-3.5 cursor-pointer text-text-quaternary group-hover:text-text-tertiary' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
'use client'
|
||||
import { useRef } from 'react'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { BlockEnum } from '../types'
|
||||
import type { ToolDefaultValue } from './types'
|
||||
|
|
@ -10,12 +10,15 @@ import cn from '@/utils/classnames'
|
|||
import Link from 'next/link'
|
||||
import { RiArrowRightUpLine } from '@remixicon/react'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
type AllStartBlocksProps = {
|
||||
className?: string
|
||||
searchText: string
|
||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||
availableBlocksTypes?: BlockEnum[]
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
const AllStartBlocks = ({
|
||||
|
|
@ -23,26 +26,68 @@ const AllStartBlocks = ({
|
|||
searchText,
|
||||
onSelect,
|
||||
availableBlocksTypes,
|
||||
tags = [],
|
||||
}: AllStartBlocksProps) => {
|
||||
const { t } = useTranslation()
|
||||
const wrapElemRef = useRef<HTMLDivElement>(null)
|
||||
const [hasStartBlocksContent, setHasStartBlocksContent] = useState(false)
|
||||
const [hasPluginContent, setHasPluginContent] = useState(false)
|
||||
|
||||
const handleStartBlocksContentChange = useCallback((hasContent: boolean) => {
|
||||
setHasStartBlocksContent(hasContent)
|
||||
}, [])
|
||||
|
||||
const handlePluginContentChange = useCallback((hasContent: boolean) => {
|
||||
setHasPluginContent(hasContent)
|
||||
}, [])
|
||||
|
||||
const hasAnyContent = hasStartBlocksContent || hasPluginContent
|
||||
const shouldShowEmptyState = searchText && !hasAnyContent
|
||||
|
||||
return (
|
||||
<div className={cn('min-w-[400px] max-w-[500px]', className)}>
|
||||
<div
|
||||
ref={wrapElemRef}
|
||||
className='max-h-[464px] overflow-y-auto'
|
||||
className='h-[640px] max-h-[640px] overflow-y-auto'
|
||||
>
|
||||
<StartBlocks
|
||||
searchText={searchText}
|
||||
onSelect={onSelect}
|
||||
availableBlocksTypes={ENTRY_NODE_TYPES as unknown as BlockEnum[]}
|
||||
/>
|
||||
{shouldShowEmptyState && (
|
||||
<div className='flex flex-col items-center gap-1 pt-48'>
|
||||
<SearchMenu className='h-8 w-8 text-text-quaternary' />
|
||||
<div className='text-sm font-medium text-text-secondary'>
|
||||
{t('workflow.tabs.noPluginsFound')}
|
||||
</div>
|
||||
<Link
|
||||
href='https://github.com/langgenius/dify-plugins/issues'
|
||||
target='_blank'
|
||||
>
|
||||
<Button
|
||||
size='small'
|
||||
variant='secondary-accent'
|
||||
className='h-6 px-3 text-xs'
|
||||
>
|
||||
{t('workflow.tabs.requestToCommunity')}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TriggerPluginSelector
|
||||
onSelect={onSelect}
|
||||
searchText={searchText}
|
||||
/>
|
||||
{!shouldShowEmptyState && (
|
||||
<>
|
||||
<StartBlocks
|
||||
searchText={searchText}
|
||||
onSelect={onSelect}
|
||||
availableBlocksTypes={ENTRY_NODE_TYPES as unknown as BlockEnum[]}
|
||||
onContentStateChange={handleStartBlocksContentChange}
|
||||
/>
|
||||
|
||||
<TriggerPluginSelector
|
||||
onSelect={onSelect}
|
||||
searchText={searchText}
|
||||
onContentStateChange={handlePluginContentChange}
|
||||
tags={tags}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer - Same as Tools tab marketplace footer */}
|
||||
|
|
|
|||
|
|
@ -143,7 +143,18 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
|||
onActiveTabChange={handleActiveTabChange}
|
||||
filterElem={
|
||||
<div className='relative m-2' onClick={e => e.stopPropagation()}>
|
||||
{(activeTab === TabsEnum.Start || activeTab === TabsEnum.Blocks) && (
|
||||
{activeTab === TabsEnum.Start && (
|
||||
<SearchBox
|
||||
search={searchText}
|
||||
onSearchChange={setSearchText}
|
||||
tags={tags}
|
||||
onTagsChange={setTags}
|
||||
size='small'
|
||||
placeholder={searchPlaceholder}
|
||||
inputClassName='grow'
|
||||
/>
|
||||
)}
|
||||
{activeTab === TabsEnum.Blocks && (
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
|
|
@ -161,7 +172,7 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
|||
tags={tags}
|
||||
onTagsChange={setTags}
|
||||
size='small'
|
||||
placeholder={t('plugin.searchTools')!}
|
||||
placeholder={searchPlaceholder}
|
||||
inputClassName='grow'
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useNodes } from 'reactflow'
|
||||
|
|
@ -17,12 +18,14 @@ type StartBlocksProps = {
|
|||
searchText: string
|
||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||
availableBlocksTypes?: BlockEnum[]
|
||||
onContentStateChange?: (hasContent: boolean) => void
|
||||
}
|
||||
|
||||
const StartBlocks = ({
|
||||
searchText,
|
||||
onSelect,
|
||||
availableBlocksTypes = [],
|
||||
onContentStateChange,
|
||||
}: StartBlocksProps) => {
|
||||
const { t } = useTranslation()
|
||||
const nodes = useNodes()
|
||||
|
|
@ -48,6 +51,10 @@ const StartBlocks = ({
|
|||
|
||||
const isEmpty = filteredBlocks.length === 0
|
||||
|
||||
useEffect(() => {
|
||||
onContentStateChange?.(!isEmpty)
|
||||
}, [isEmpty, onContentStateChange])
|
||||
|
||||
const renderBlock = useCallback((block: typeof START_BLOCKS[0]) => (
|
||||
<Tooltip
|
||||
key={block.type}
|
||||
|
|
@ -84,27 +91,23 @@ const StartBlocks = ({
|
|||
</Tooltip>
|
||||
), [nodesExtraData, onSelect, t])
|
||||
|
||||
if (isEmpty)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='min-w-[400px] max-w-[500px] p-1'>
|
||||
{isEmpty && (
|
||||
<div className='flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary'>
|
||||
{t('workflow.tabs.noResult')}
|
||||
</div>
|
||||
)}
|
||||
{!isEmpty && (
|
||||
<div className='mb-1'>
|
||||
{filteredBlocks.map((block, index) => (
|
||||
<div key={block.type}>
|
||||
{renderBlock(block)}
|
||||
{block.type === BlockEnumValues.Start && index < filteredBlocks.length - 1 && (
|
||||
<div className='my-1 px-3'>
|
||||
<div className='border-t border-divider-subtle' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className='p-1'>
|
||||
<div className='mb-1'>
|
||||
{filteredBlocks.map((block, index) => (
|
||||
<div key={block.type}>
|
||||
{renderBlock(block)}
|
||||
{block.type === BlockEnumValues.Start && index < filteredBlocks.length - 1 && (
|
||||
<div className='my-1 px-3'>
|
||||
<div className='border-t border-divider-subtle' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ const Tabs: FC<TabsProps> = ({
|
|||
searchText={searchText}
|
||||
onSelect={onSelect}
|
||||
availableBlocksTypes={availableBlocksTypes}
|
||||
tags={tags}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,16 +7,22 @@ import type { ToolDefaultValue } from './types'
|
|||
type TriggerPluginSelectorProps = {
|
||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||
searchText: string
|
||||
onContentStateChange?: (hasContent: boolean) => void
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
const TriggerPluginSelector = ({
|
||||
onSelect,
|
||||
searchText,
|
||||
onContentStateChange,
|
||||
tags = [],
|
||||
}: TriggerPluginSelectorProps) => {
|
||||
return (
|
||||
<TriggerPluginList
|
||||
onSelect={onSelect}
|
||||
searchText={searchText}
|
||||
onContentStateChange={onContentStateChange}
|
||||
tags={tags}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
'use client'
|
||||
import { memo, useMemo } from 'react'
|
||||
import { memo, useEffect, useMemo } from 'react'
|
||||
import { useAllBuiltInTools } from '@/service/use-tools'
|
||||
import TriggerPluginItem from './item'
|
||||
import type { BlockEnum } from '../../types'
|
||||
|
|
@ -9,11 +9,15 @@ import { useGetLanguage } from '@/context/i18n'
|
|||
type TriggerPluginListProps = {
|
||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||
searchText: string
|
||||
onContentStateChange?: (hasContent: boolean) => void
|
||||
tags?: string[]
|
||||
}
|
||||
|
||||
const TriggerPluginList = ({
|
||||
onSelect,
|
||||
searchText,
|
||||
onContentStateChange,
|
||||
tags = [],
|
||||
}: TriggerPluginListProps) => {
|
||||
const { data: buildInTools = [] } = useAllBuiltInTools()
|
||||
const language = useGetLanguage()
|
||||
|
|
@ -22,16 +26,26 @@ const TriggerPluginList = ({
|
|||
return buildInTools.filter((toolWithProvider) => {
|
||||
if (toolWithProvider.tools.length === 0) return false
|
||||
|
||||
if (!searchText) return true
|
||||
// Filter by search text
|
||||
if (searchText) {
|
||||
const matchesSearch = toolWithProvider.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
|| toolWithProvider.tools.some(tool =>
|
||||
tool.label[language].toLowerCase().includes(searchText.toLowerCase()),
|
||||
)
|
||||
if (!matchesSearch) return false
|
||||
}
|
||||
|
||||
return toolWithProvider.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
|| toolWithProvider.tools.some(tool =>
|
||||
tool.label[language].toLowerCase().includes(searchText.toLowerCase()),
|
||||
)
|
||||
return true
|
||||
})
|
||||
}, [buildInTools, searchText, language])
|
||||
|
||||
if (!triggerPlugins.length)
|
||||
const hasContent = triggerPlugins.length > 0
|
||||
|
||||
useEffect(() => {
|
||||
onContentStateChange?.(hasContent)
|
||||
}, [hasContent, onContentStateChange])
|
||||
|
||||
if (!hasContent)
|
||||
return null
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -246,6 +246,8 @@ const translation = {
|
|||
'transform': 'Transform',
|
||||
'utilities': 'Utilities',
|
||||
'noResult': 'No match found',
|
||||
'noPluginsFound': 'No plugins were found',
|
||||
'requestToCommunity': 'Requests to the community',
|
||||
'agent': 'Agent Strategy',
|
||||
'allAdded': 'All added',
|
||||
'addAll': 'Add all',
|
||||
|
|
|
|||
|
|
@ -244,6 +244,8 @@ const translation = {
|
|||
'transform': '変換',
|
||||
'utilities': 'ツール',
|
||||
'noResult': '該当なし',
|
||||
'noPluginsFound': 'プラグインが見つかりません',
|
||||
'requestToCommunity': 'コミュニティにリクエスト',
|
||||
'plugin': 'プラグイン',
|
||||
'agent': 'エージェント戦略',
|
||||
'addAll': 'すべてを追加する',
|
||||
|
|
|
|||
|
|
@ -245,6 +245,8 @@ const translation = {
|
|||
'transform': '转换',
|
||||
'utilities': '工具',
|
||||
'noResult': '未找到匹配项',
|
||||
'noPluginsFound': '未找到插件',
|
||||
'requestToCommunity': '向社区反馈',
|
||||
'agent': 'Agent 策略',
|
||||
'allAdded': '已添加全部',
|
||||
'addAll': '添加全部',
|
||||
|
|
|
|||
Loading…
Reference in New Issue