Commit d79b6869 authored by StyleZhang's avatar StyleZhang

block selector

parent 1adec7ab
......@@ -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>
......
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}
/>
)
......
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': '开始',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment