mirror of https://github.com/langgenius/dify.git
block selector
This commit is contained in:
parent
1adec7ab51
commit
d79b686992
|
|
@ -14,6 +14,7 @@ type TooltipProps = {
|
|||
position?: 'top' | 'right' | 'bottom' | 'left'
|
||||
clickable?: boolean
|
||||
children: React.ReactNode
|
||||
noArrow?: boolean
|
||||
}
|
||||
|
||||
const Tooltip: FC<TooltipProps> = ({
|
||||
|
|
@ -25,6 +26,7 @@ const Tooltip: FC<TooltipProps> = ({
|
|||
htmlContent,
|
||||
className,
|
||||
clickable,
|
||||
noArrow,
|
||||
}) => {
|
||||
return (
|
||||
<div className='tooltip-container'>
|
||||
|
|
@ -39,6 +41,7 @@ const Tooltip: FC<TooltipProps> = ({
|
|||
place={position}
|
||||
clickable={clickable}
|
||||
isOpen={disabled ? false : undefined}
|
||||
noArrow={noArrow}
|
||||
>
|
||||
{htmlContent && htmlContent}
|
||||
</ReactTooltip>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { groupBy } from 'lodash-es'
|
||||
import BlockIcon from '../block-icon'
|
||||
import { BlockEnum } from '../types'
|
||||
import {
|
||||
useIsChatMode,
|
||||
useNodesExtraData,
|
||||
} from '../hooks'
|
||||
import { BLOCK_CLASSIFICATIONS } from './constants'
|
||||
import { useBlocks } from './hooks'
|
||||
import type { ToolDefaultValue } from './types'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type BlocksProps = {
|
||||
searchText: string
|
||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||
}
|
||||
const Blocks = ({
|
||||
searchText,
|
||||
onSelect,
|
||||
}: BlocksProps) => {
|
||||
const { t } = useTranslation()
|
||||
const isChatMode = useIsChatMode()
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const blocks = useBlocks()
|
||||
|
||||
const groups = useMemo(() => {
|
||||
return BLOCK_CLASSIFICATIONS.reduce((acc, classification) => {
|
||||
const list = groupBy(blocks, 'classification')[classification].filter((block) => {
|
||||
if (block.type === BlockEnum.DirectAnswer && !isChatMode)
|
||||
return false
|
||||
|
||||
return block.title.toLowerCase().includes(searchText.toLowerCase())
|
||||
})
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[classification]: list,
|
||||
}
|
||||
}, {} as Record<string, typeof blocks>)
|
||||
}, [blocks, isChatMode, searchText])
|
||||
const isEmpty = Object.values(groups).every(list => !list.length)
|
||||
|
||||
const renderGroup = useCallback((classification: string) => {
|
||||
const list = groups[classification]
|
||||
|
||||
return (
|
||||
<div
|
||||
key={classification}
|
||||
className='mb-1 last-of-type:mb-0'
|
||||
>
|
||||
{
|
||||
classification !== '-' && !!list.length && (
|
||||
<div className='flex items-start px-3 h-[22px] text-xs font-medium text-gray-500'>
|
||||
{t(`workflow.tabs.${classification}`)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
list.map(block => (
|
||||
<Tooltip
|
||||
key={block.type}
|
||||
selector={`workflow-block-${block.type}`}
|
||||
position='right'
|
||||
className='!p-0 !px-3 !py-2.5 !w-[200px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !bg-transparent !rounded-xl !shadow-lg'
|
||||
content={nodesExtraData[block.type].about}
|
||||
noArrow
|
||||
>
|
||||
<div
|
||||
key={block.type}
|
||||
className='flex items-center px-3 w-full h-8 rounded-lg hover:bg-gray-50 cursor-pointer'
|
||||
onClick={() => onSelect(block.type)}
|
||||
>
|
||||
<BlockIcon
|
||||
className='mr-2'
|
||||
type={block.type}
|
||||
/>
|
||||
<div className='text-sm text-gray-900'>{block.title}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}, [groups, nodesExtraData, onSelect, t])
|
||||
|
||||
return (
|
||||
<div className='p-1'>
|
||||
{
|
||||
isEmpty && (
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>{t('workflow.tabs.noResult')}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!isEmpty && BLOCK_CLASSIFICATIONS.map(renderGroup)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Blocks)
|
||||
|
|
@ -23,6 +23,7 @@ import {
|
|||
SearchLg,
|
||||
} from '@/app/components/base/icons/src/vender/line/general'
|
||||
import type { OnSelectBlock } from '@/app/components/workflow/types'
|
||||
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
|
||||
type NodeSelectorProps = {
|
||||
open?: boolean
|
||||
|
|
@ -105,10 +106,20 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
|||
<SearchLg className='shrink-0 ml-[1px] mr-[5px] w-3.5 h-3.5 text-gray-400' />
|
||||
<input
|
||||
value={searchText}
|
||||
className='grow px-0.5 py-[7px] text-[13px] bg-transparent appearance-none outline-none'
|
||||
className='grow px-0.5 py-[7px] text-[13px] text-gray-700 bg-transparent appearance-none outline-none caret-primary-600 placeholder:text-gray-400'
|
||||
placeholder={t('workflow.tabs.searchBlock') || ''}
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
/>
|
||||
{
|
||||
searchText && (
|
||||
<div
|
||||
className='flex items-center justify-center ml-[5px] w-[18px] h-[18px] cursor-pointer'
|
||||
onClick={() => setSearchText('')}
|
||||
>
|
||||
<XCircle className='w-[14px] h-[14px] text-gray-400' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<Tabs
|
||||
|
|
|
|||
|
|
@ -3,19 +3,12 @@ import {
|
|||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { groupBy } from 'lodash-es'
|
||||
import BlockIcon from '../block-icon'
|
||||
import { BlockEnum } from '../types'
|
||||
import { useIsChatMode } from '../hooks'
|
||||
import { BLOCK_CLASSIFICATIONS } from './constants'
|
||||
import {
|
||||
useBlocks,
|
||||
useTabs,
|
||||
} from './hooks'
|
||||
import type { BlockEnum } from '../types'
|
||||
import { useTabs } from './hooks'
|
||||
import type { ToolDefaultValue } from './types'
|
||||
import { TabsEnum } from './types'
|
||||
import Tools from './tools'
|
||||
import Blocks from './blocks'
|
||||
|
||||
export type TabsProps = {
|
||||
searchText: string
|
||||
|
|
@ -25,9 +18,6 @@ const Tabs: FC<TabsProps> = ({
|
|||
searchText,
|
||||
onSelect,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const isChatMode = useIsChatMode()
|
||||
const blocks = useBlocks()
|
||||
const tabs = useTabs()
|
||||
const [activeTab, setActiveTab] = useState(tabs[0].key)
|
||||
|
||||
|
|
@ -53,55 +43,25 @@ const Tabs: FC<TabsProps> = ({
|
|||
</div>
|
||||
{
|
||||
activeTab === TabsEnum.Blocks && (
|
||||
<div className='p-1'>
|
||||
{
|
||||
BLOCK_CLASSIFICATIONS.map(classification => (
|
||||
<div
|
||||
key={classification}
|
||||
className='mb-1 last-of-type:mb-0'
|
||||
>
|
||||
{
|
||||
classification !== '-' && (
|
||||
<div className='flex items-start px-3 h-[22px] text-xs font-medium text-gray-500'>
|
||||
{t(`workflow.tabs.${classification}`)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
groupBy(blocks, 'classification')[classification].filter((block) => {
|
||||
if (block.type === BlockEnum.DirectAnswer && !isChatMode)
|
||||
return false
|
||||
|
||||
return true
|
||||
}).map(block => (
|
||||
<div
|
||||
key={block.type}
|
||||
className='flex items-center px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer'
|
||||
onClick={() => onSelect(block.type)}
|
||||
>
|
||||
<BlockIcon
|
||||
className='mr-2'
|
||||
type={block.type}
|
||||
/>
|
||||
<div className='text-sm text-gray-900'>{block.title}</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<Blocks
|
||||
searchText={searchText}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
activeTab === TabsEnum.BuiltInTool && (
|
||||
<Tools onSelect={onSelect} />
|
||||
<Tools
|
||||
onSelect={onSelect}
|
||||
searchText={searchText}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
activeTab === TabsEnum.CustomTool && (
|
||||
<Tools
|
||||
isCustom
|
||||
searchText={searchText}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import produce from 'immer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStore } from '../../store'
|
||||
import type { BlockEnum } from '../../types'
|
||||
import type {
|
||||
|
|
@ -14,13 +16,20 @@ import Item from './item'
|
|||
type ToolsProps = {
|
||||
isCustom?: boolean
|
||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||
searchText: string
|
||||
}
|
||||
const Tools = ({
|
||||
isCustom,
|
||||
onSelect,
|
||||
searchText,
|
||||
}: ToolsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const totalToolsets = useStore(state => state.toolsets)
|
||||
const toolsets = totalToolsets.filter(toolset => toolset.type === (isCustom ? 'api' : 'builtin'))
|
||||
const toolsets = useMemo(() => {
|
||||
return totalToolsets.filter((toolset) => {
|
||||
return toolset.type === (isCustom ? 'api' : 'builtin') && toolset.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
})
|
||||
}, [totalToolsets, isCustom, searchText])
|
||||
const setToolsets = useStore(state => state.setToolsets)
|
||||
const toolsMap = useStore(state => state.toolsMap)
|
||||
const setToolsMap = useStore(state => state.setToolsMap)
|
||||
|
|
@ -63,6 +72,11 @@ const Tools = ({
|
|||
|
||||
return (
|
||||
<div className='p-1 max-h-[464px] overflow-y-auto'>
|
||||
{
|
||||
!toolsets.length && (
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>{t('workflow.tabs.noResult')}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
toolsets.map(toolset => (
|
||||
<Item
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const translation = {
|
|||
'logic': 'Logic',
|
||||
'transform': 'Transform',
|
||||
'utilities': 'Utilities',
|
||||
'noResult': 'No match found',
|
||||
},
|
||||
blocks: {
|
||||
'start': 'Start',
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const translation = {
|
|||
'logic': '逻辑',
|
||||
'transform': '转换',
|
||||
'utilities': '工具',
|
||||
'noResult': '未找到匹配项',
|
||||
},
|
||||
blocks: {
|
||||
'start': '开始',
|
||||
|
|
|
|||
Loading…
Reference in New Issue