mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-09 19:06:51 +08:00
feat: workflow new nodes (#4683)
Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: Patryk Garstecki <patryk20120@yahoo.pl> Co-authored-by: Sebastian.W <thiner@gmail.com> Co-authored-by: 呆萌闷油瓶 <253605712@qq.com> Co-authored-by: takatost <takatost@users.noreply.github.com> Co-authored-by: rechardwang <wh_goodjob@163.com> Co-authored-by: Nite Knite <nkCoding@gmail.com> Co-authored-by: Chenhe Gu <guchenhe@gmail.com> Co-authored-by: Joshua <138381132+joshua20231026@users.noreply.github.com> Co-authored-by: Weaxs <459312872@qq.com> Co-authored-by: Ikko Eltociear Ashimine <eltociear@gmail.com> Co-authored-by: leejoo0 <81673835+leejoo0@users.noreply.github.com> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: sino <sino2322@gmail.com> Co-authored-by: Vikey Chen <vikeytk@gmail.com> Co-authored-by: wanghl <Wang-HL@users.noreply.github.com> Co-authored-by: Haolin Wang-汪皓临 <haolin.wang@atlaslovestravel.com> Co-authored-by: Zixuan Cheng <61724187+Theysua@users.noreply.github.com> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: Bowen Liang <bowenliang@apache.org> Co-authored-by: Bowen Liang <liangbowen@gf.com.cn> Co-authored-by: fanghongtai <42790567+fanghongtai@users.noreply.github.com> Co-authored-by: wxfanghongtai <wxfanghongtai@gf.com.cn> Co-authored-by: Matri <qjp@bithuman.io> Co-authored-by: Benjamin <benjaminx@gmail.com>
This commit is contained in:
BIN
web/app/components/tools/add-tool-modal/D.png
Normal file
BIN
web/app/components/tools/add-tool-modal/D.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
70
web/app/components/tools/add-tool-modal/category.tsx
Normal file
70
web/app/components/tools/add-tool-modal/category.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
'use client'
|
||||
import { useRef } from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useMount } from 'ahooks'
|
||||
import { Apps02 } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import I18n from '@/context/i18n'
|
||||
import { getLanguage } from '@/i18n/language'
|
||||
import { useStore as useLabelStore } from '@/app/components/tools/labels/store'
|
||||
import { fetchLabelList } from '@/service/tools'
|
||||
|
||||
type Props = {
|
||||
value: string
|
||||
onSelect: (type: string) => void
|
||||
}
|
||||
|
||||
const Icon = ({ svgString, active }: { svgString: string; active: boolean }) => {
|
||||
const svgRef = useRef<SVGSVGElement | null>(null)
|
||||
const SVGParsor = (svg: string) => {
|
||||
if (!svg)
|
||||
return null
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(svg, 'image/svg+xml')
|
||||
console.log(doc.documentElement)
|
||||
return doc.documentElement
|
||||
}
|
||||
useMount(() => {
|
||||
const svgElement = SVGParsor(svgString)
|
||||
if (svgRef.current && svgElement)
|
||||
svgRef.current.appendChild(svgElement)
|
||||
})
|
||||
return <svg className={cn('w-4 h-4 text-gray-700', active && '!text-primary-600')} ref={svgRef} />
|
||||
}
|
||||
|
||||
const Category = ({
|
||||
value,
|
||||
onSelect,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
const language = getLanguage(locale)
|
||||
const labelList = useLabelStore(s => s.labelList)
|
||||
const setLabelList = useLabelStore(s => s.setLabelList)
|
||||
|
||||
useMount(() => {
|
||||
fetchLabelList().then((res) => {
|
||||
setLabelList(res)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='mb-3'>
|
||||
<div className='px-3 py-0.5 text-gray-500 text-xs leading-[18px] font-medium'>{t('tools.addToolModal.category').toLocaleUpperCase()}</div>
|
||||
<div className={cn('mb-0.5 p-1 pl-3 flex items-center cursor-pointer text-gray-700 text-sm leading-5 rounded-lg hover:bg-white', value === '' && '!bg-white !text-primary-600 font-medium')} onClick={() => onSelect('')}>
|
||||
<Apps02 className='shrink-0 w-4 h-4 mr-2' />
|
||||
{t('tools.type.all')}
|
||||
</div>
|
||||
{labelList.map(label => (
|
||||
<div key={label.name} title={label.label[language]} className={cn('mb-0.5 p-1 pl-3 flex items-center cursor-pointer text-gray-700 text-sm leading-5 rounded-lg hover:bg-white truncate overflow-hidden', value === label.name && '!bg-white !text-primary-600 font-medium')} onClick={() => onSelect(label.name)}>
|
||||
<div className='shrink-0 w-4 h-4 mr-2'>
|
||||
<Icon active={value === label.name} svgString={label.icon} />
|
||||
</div>
|
||||
{label.label[language]}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Category
|
||||
BIN
web/app/components/tools/add-tool-modal/empty.png
Normal file
BIN
web/app/components/tools/add-tool-modal/empty.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
15
web/app/components/tools/add-tool-modal/empty.tsx
Normal file
15
web/app/components/tools/add-tool-modal/empty.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const Empty = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-center'>
|
||||
<div className="shrink-0 w-[163px] h-[149px] bg-cover bg-no-repeat bg-[url('~@/app/components/tools/add-tool-modal/empty.png')]"></div>
|
||||
<div className='mb-1 text-[13px] font-medium text-gray-700 leading-[18px]'>{t('tools.addToolModal.emptyTitle')}</div>
|
||||
<div className='text-[13px] text-gray-500 leading-[18px]'>{t('tools.addToolModal.emptyTip')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Empty
|
||||
235
web/app/components/tools/add-tool-modal/index.tsx
Normal file
235
web/app/components/tools/add-tool-modal/index.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import produce from 'immer'
|
||||
import cn from 'classnames'
|
||||
import { useMount } from 'ahooks'
|
||||
import type { Collection, CustomCollectionBackend, Tool } from '../types'
|
||||
import Type from './type'
|
||||
import Category from './category'
|
||||
import Tools from './tools'
|
||||
import I18n from '@/context/i18n'
|
||||
import { getLanguage } from '@/i18n/language'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import SearchInput from '@/app/components/base/search-input'
|
||||
import { Plus, XClose } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal'
|
||||
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
||||
import {
|
||||
createCustomCollection,
|
||||
fetchAllBuiltInTools,
|
||||
fetchAllCustomTools,
|
||||
fetchAllWorkflowTools,
|
||||
removeBuiltInToolCredential,
|
||||
updateBuiltInToolCredential,
|
||||
} from '@/service/tools'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
import type { ModelConfig } from '@/models/debug'
|
||||
|
||||
type Props = {
|
||||
onHide: () => void
|
||||
}
|
||||
// Add and Edit
|
||||
const AddToolModal: FC<Props> = ({
|
||||
onHide,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18n)
|
||||
const language = getLanguage(locale)
|
||||
const [currentType, setCurrentType] = useState('builtin')
|
||||
const [currentCategory, setCurrentCategory] = useState('')
|
||||
const [keywords, setKeywords] = useState<string>('')
|
||||
const handleKeywordsChange = (value: string) => {
|
||||
setKeywords(value)
|
||||
}
|
||||
const [toolList, setToolList] = useState<ToolWithProvider[]>([])
|
||||
const [listLoading, setListLoading] = useState(true)
|
||||
const getAllTools = async () => {
|
||||
setListLoading(true)
|
||||
const buildInTools = await fetchAllBuiltInTools()
|
||||
const customTools = await fetchAllCustomTools()
|
||||
const workflowTools = await fetchAllWorkflowTools()
|
||||
const mergedToolList = [
|
||||
...buildInTools,
|
||||
...customTools,
|
||||
...workflowTools.filter((toolWithProvider) => {
|
||||
return !toolWithProvider.tools.some((tool) => {
|
||||
return !!tool.parameters.find(item => item.name === '__image')
|
||||
})
|
||||
}),
|
||||
]
|
||||
setToolList(mergedToolList)
|
||||
setListLoading(false)
|
||||
}
|
||||
const filteredList = useMemo(() => {
|
||||
return toolList.filter((toolWithProvider) => {
|
||||
if (currentType === 'all')
|
||||
return true
|
||||
else
|
||||
return toolWithProvider.type === currentType
|
||||
}).filter((toolWithProvider) => {
|
||||
if (!currentCategory)
|
||||
return true
|
||||
else
|
||||
return toolWithProvider.labels.includes(currentCategory)
|
||||
}).filter((toolWithProvider) => {
|
||||
return toolWithProvider.tools.some((tool) => {
|
||||
return tool.label[language].toLowerCase().includes(keywords.toLowerCase())
|
||||
})
|
||||
})
|
||||
}, [currentType, currentCategory, toolList, keywords, language])
|
||||
|
||||
const {
|
||||
modelConfig,
|
||||
setModelConfig,
|
||||
} = useContext(ConfigContext)
|
||||
|
||||
const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false)
|
||||
const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => {
|
||||
await createCustomCollection(data)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
setIsShowEditCustomCollectionModal(false)
|
||||
getAllTools()
|
||||
}
|
||||
const [showSettingAuth, setShowSettingAuth] = useState(false)
|
||||
const [collection, setCollection] = useState<Collection>()
|
||||
const toolSelectHandle = (collection: Collection, tool: Tool) => {
|
||||
const parameters: Record<string, string> = {}
|
||||
if (tool.parameters) {
|
||||
tool.parameters.forEach((item) => {
|
||||
parameters[item.name] = ''
|
||||
})
|
||||
}
|
||||
|
||||
const nexModelConfig = produce(modelConfig, (draft: ModelConfig) => {
|
||||
draft.agentConfig.tools.push({
|
||||
provider_id: collection.id || collection.name,
|
||||
provider_type: collection.type,
|
||||
provider_name: collection.name,
|
||||
tool_name: tool.name,
|
||||
tool_label: tool.label[locale] || tool.label[locale.replaceAll('-', '_')],
|
||||
tool_parameters: parameters,
|
||||
enabled: true,
|
||||
})
|
||||
})
|
||||
setModelConfig(nexModelConfig)
|
||||
}
|
||||
const authSelectHandle = (provider: Collection) => {
|
||||
setCollection(provider)
|
||||
setShowSettingAuth(true)
|
||||
}
|
||||
const updateBuiltinAuth = async (value: Record<string, any>) => {
|
||||
if (!collection)
|
||||
return
|
||||
await updateBuiltInToolCredential(collection.name, value)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
await getAllTools()
|
||||
setShowSettingAuth(false)
|
||||
}
|
||||
const removeBuiltinAuth = async () => {
|
||||
if (!collection)
|
||||
return
|
||||
await removeBuiltInToolCredential(collection.name)
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.api.actionSuccess'),
|
||||
})
|
||||
await getAllTools()
|
||||
setShowSettingAuth(false)
|
||||
}
|
||||
|
||||
useMount(() => {
|
||||
getAllTools()
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
isOpen
|
||||
mask
|
||||
clickOutsideNotOpen
|
||||
onClose={onHide}
|
||||
footer={null}
|
||||
panelClassname={cn('mt-16 mx-2 sm:mr-2 mb-3 !p-0 rounded-xl', 'mt-2 !w-[640px]', '!max-w-[640px]')}
|
||||
>
|
||||
<div
|
||||
className='w-full flex bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl'
|
||||
style={{
|
||||
height: 'calc(100vh - 16px)',
|
||||
}}
|
||||
>
|
||||
<div className='relative shrink-0 w-[200px] pb-3 bg-gray-100 rounded-l-xl border-r-[0.5px] border-black/2 overflow-y-auto'>
|
||||
<div className='sticky top-0 left-0 right-0'>
|
||||
<div className='sticky top-0 left-0 right-0 px-5 py-3 text-md font-semibold text-gray-900'>{t('tools.addTool')}</div>
|
||||
<div className='px-3 pt-2 pb-4'>
|
||||
<Button type='primary' className='w-[176px] text-[13px] leading-[18px] font-medium' onClick={() => setIsShowEditCustomCollectionModal(true)}>
|
||||
<Plus className='w-4 h-4 mr-1'/>
|
||||
{t('tools.createCustomTool')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-2 py-1'>
|
||||
<Type value={currentType} onSelect={setCurrentType}/>
|
||||
<Category value={currentCategory} onSelect={setCurrentCategory}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='relative grow bg-white rounded-r-xl overflow-y-auto'>
|
||||
<div className='z-10 sticky top-0 left-0 right-0 p-2 flex items-center gap-1 bg-white'>
|
||||
<div className='grow'>
|
||||
<SearchInput className='w-full' value={keywords} onChange={handleKeywordsChange} />
|
||||
</div>
|
||||
<div className='ml-2 mr-1 w-[1px] h-4 bg-gray-200'></div>
|
||||
<div className='p-2 cursor-pointer' onClick={onHide}>
|
||||
<XClose className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
</div>
|
||||
{listLoading && (
|
||||
<div className='flex h-[200px] items-center justify-center bg-white'>
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
{!listLoading && (
|
||||
<Tools
|
||||
showWorkflowEmpty={currentType === 'workflow'}
|
||||
tools={filteredList}
|
||||
addedTools={(modelConfig?.agentConfig?.tools as any) || []}
|
||||
onSelect={toolSelectHandle}
|
||||
onAuthSetup={authSelectHandle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
{isShowEditCollectionToolModal && (
|
||||
<EditCustomToolModal
|
||||
positionLeft
|
||||
payload={null}
|
||||
onHide={() => setIsShowEditCustomCollectionModal(false)}
|
||||
onAdd={doCreateCustomToolCollection}
|
||||
/>
|
||||
)}
|
||||
{showSettingAuth && collection && (
|
||||
<ConfigCredential
|
||||
collection={collection}
|
||||
onCancel={() => setShowSettingAuth(false)}
|
||||
onSaved={updateBuiltinAuth}
|
||||
onRemove={removeBuiltinAuth}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
export default React.memo(AddToolModal)
|
||||
146
web/app/components/tools/add-tool-modal/tools.tsx
Normal file
146
web/app/components/tools/add-tool-modal/tools.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { Check, Plus } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { Tag01 } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { useStore as useLabelStore } from '@/app/components/tools/labels/store'
|
||||
import Empty from '@/app/components/tools/add-tool-modal/empty'
|
||||
import type { Tool } from '@/app/components/tools/types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import type { AgentTool } from '@/types/app'
|
||||
import { MAX_TOOLS_NUM } from '@/config'
|
||||
|
||||
type ToolsProps = {
|
||||
showWorkflowEmpty: boolean
|
||||
tools: ToolWithProvider[]
|
||||
addedTools: AgentTool[]
|
||||
onSelect: (provider: ToolWithProvider, tool: Tool) => void
|
||||
onAuthSetup: (provider: ToolWithProvider) => void
|
||||
}
|
||||
const Blocks = ({
|
||||
showWorkflowEmpty,
|
||||
tools,
|
||||
addedTools,
|
||||
onSelect,
|
||||
onAuthSetup,
|
||||
}: ToolsProps) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useGetLanguage()
|
||||
const labelList = useLabelStore(s => s.labelList)
|
||||
const addable = addedTools.length < MAX_TOOLS_NUM
|
||||
|
||||
const renderGroup = useCallback((toolWithProvider: ToolWithProvider) => {
|
||||
const list = toolWithProvider.tools
|
||||
const needAuth = toolWithProvider.allow_delete && !toolWithProvider.is_team_authorization && toolWithProvider.type === CollectionType.builtIn
|
||||
|
||||
return (
|
||||
<div
|
||||
key={toolWithProvider.id}
|
||||
className='group mb-1 last-of-type:mb-0'
|
||||
>
|
||||
<div className='flex items-center justify-between w-full pl-3 pr-1 h-[22px] text-xs font-medium text-gray-500'>
|
||||
{toolWithProvider.label[language]}
|
||||
<a className='hidden cursor-pointer items-center group-hover:flex' href={`/tools?category=${toolWithProvider.type}`} target='_blank'>{t('tools.addToolModal.manageInTools')}<ArrowUpRight className='ml-0.5 w-3 h-3' /></a>
|
||||
</div>
|
||||
{list.map((tool) => {
|
||||
const labelContent = (() => {
|
||||
if (!tool.labels)
|
||||
return ''
|
||||
return tool.labels.map((name) => {
|
||||
const label = labelList.find(item => item.name === name)
|
||||
return label?.label[language]
|
||||
}).filter(Boolean).join(', ')
|
||||
})()
|
||||
const added = !!addedTools?.find(v => v.provider_id === toolWithProvider.id && v.provider_type === toolWithProvider.type && v.tool_name === tool.name)
|
||||
return (
|
||||
<Tooltip
|
||||
key={tool.name}
|
||||
selector={`workflow-block-tool-${tool.name}`}
|
||||
position='bottom'
|
||||
className='!p-0 !px-3 !py-2.5 !w-[210px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !bg-transparent !rounded-xl !shadow-lg translate-x-[108px]'
|
||||
htmlContent={(
|
||||
<div>
|
||||
<BlockIcon
|
||||
size='md'
|
||||
className='mb-2'
|
||||
type={BlockEnum.Tool}
|
||||
toolIcon={toolWithProvider.icon}
|
||||
/>
|
||||
<div className='mb-1 text-sm leading-5 text-gray-900'>{tool.label[language]}</div>
|
||||
<div className='text-xs text-gray-700 leading-[18px]'>{tool.description[language]}</div>
|
||||
{tool.labels?.length > 0 && (
|
||||
<div className='flex items-center shrink-0 mt-1'>
|
||||
<div className='relative w-full flex items-center gap-1 py-1 rounded-md text-gray-500' title={labelContent}>
|
||||
<Tag01 className='shrink-0 w-3 h-3 text-gray-500' />
|
||||
<div className='grow text-xs text-start leading-[18px] font-normal truncate'>{labelContent}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
noArrow
|
||||
>
|
||||
<div className='group/item flex items-center w-full pl-3 pr-1 h-8 rounded-lg hover:bg-gray-50 cursor-pointer'>
|
||||
<BlockIcon
|
||||
className={cn('mr-2 shrink-0', needAuth && 'opacity-30')}
|
||||
type={BlockEnum.Tool}
|
||||
toolIcon={toolWithProvider.icon}
|
||||
/>
|
||||
<div className={cn('grow text-sm text-gray-900 truncate', needAuth && 'opacity-30')}>{tool.label[language]}</div>
|
||||
{!needAuth && added && (
|
||||
<div className='flex items-center gap-1 rounded-[6px] border border-gray-100 px-2 py-[3px] bg-white text-gray-300 text-xs font-medium leading-[18px]'>
|
||||
<Check className='w-3 h-3'/>
|
||||
{t('tools.addToolModal.added').toLocaleUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
{!needAuth && !added && addable && (
|
||||
<Button
|
||||
type='default'
|
||||
className={cn('hidden shrink-0 items-center !h-6 px-2 py-1 bg-white text-xs font-medium leading-[18px] text-primary-600 group-hover/item:flex')}
|
||||
onClick={() => onSelect(toolWithProvider, tool)}
|
||||
>
|
||||
<Plus className='w-3 h-3'/>
|
||||
{t('tools.addToolModal.add').toLocaleUpperCase()}
|
||||
</Button>
|
||||
)}
|
||||
{needAuth && (
|
||||
<Button
|
||||
type='default'
|
||||
className={cn('hidden shrink-0 items-center !h-6 px-2 py-1 bg-white text-xs font-medium leading-[18px] text-primary-600 group-hover/item:flex')}
|
||||
onClick={() => onAuthSetup(toolWithProvider)}
|
||||
>{t('tools.auth.setup')}</Button>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}, [addable, language, t, labelList, addedTools, onAuthSetup, onSelect])
|
||||
|
||||
return (
|
||||
<div className='p-1 pb-6 max-w-[440px]'>
|
||||
{!tools.length && !showWorkflowEmpty && (
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>{t('workflow.tabs.noResult')}</div>
|
||||
)}
|
||||
{!tools.length && showWorkflowEmpty && (
|
||||
<div className='pt-[280px]'>
|
||||
<Empty/>
|
||||
</div>
|
||||
)}
|
||||
{!!tools.length && tools.map(renderGroup)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Blocks)
|
||||
34
web/app/components/tools/add-tool-modal/type.tsx
Normal file
34
web/app/components/tools/add-tool-modal/type.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
'use client'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Exchange02, FileCode } from '@/app/components/base/icons/src/vender/line/others'
|
||||
|
||||
type Props = {
|
||||
value: string
|
||||
onSelect: (type: string) => void
|
||||
}
|
||||
|
||||
const Types = ({
|
||||
value,
|
||||
onSelect,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='mb-3'>
|
||||
<div className={cn('mb-0.5 p-1 pl-3 flex items-center cursor-pointer text-sm leading-5 rounded-lg hover:bg-white', value === 'builtin' && '!bg-white font-medium')} onClick={() => onSelect('builtin')}>
|
||||
<div className="shrink-0 w-4 h-4 mr-2 bg-cover bg-no-repeat bg-[url('~@/app/components/tools/add-tool-modal/D.png')]" />
|
||||
<span className={cn('text-gray-700', value === 'builtin' && '!text-primary-600')}>{t('tools.type.builtIn')}</span>
|
||||
</div>
|
||||
<div className={cn('mb-0.5 p-1 pl-3 flex items-center cursor-pointer text-gray-700 text-sm leading-5 rounded-lg hover:bg-white', value === 'api' && '!bg-white !text-primary-600 font-medium')} onClick={() => onSelect('api')}>
|
||||
<FileCode className='shrink-0 w-4 h-4 mr-2' />
|
||||
{t('tools.type.custom')}
|
||||
</div>
|
||||
<div className={cn('mb-0.5 p-1 pl-3 flex items-center cursor-pointer text-gray-700 text-sm leading-5 rounded-lg hover:bg-white', value === 'workflow' && '!bg-white !text-primary-600 font-medium')} onClick={() => onSelect('workflow')}>
|
||||
<Exchange02 className='shrink-0 w-4 h-4 mr-2' />
|
||||
{t('tools.type.workflow')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Types
|
||||
Reference in New Issue
Block a user