mirror of
https://github.com/langgenius/dify.git
synced 2025-12-27 10:02:25 +00:00
block selector & data source node
This commit is contained in:
parent
efb27eb443
commit
8d9c252811
@ -109,7 +109,7 @@ const Blocks = ({
|
||||
}, [groups, nodesExtraData, onSelect, t])
|
||||
|
||||
return (
|
||||
<div className='p-1'>
|
||||
<div className='max-h-[480px] overflow-y-auto p-1'>
|
||||
{
|
||||
isEmpty && (
|
||||
<div className='flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary'>{t('workflow.tabs.noResult')}</div>
|
||||
|
||||
@ -3,6 +3,12 @@ import { BlockEnum } from '../types'
|
||||
import { BlockClassificationEnum } from './types'
|
||||
|
||||
export const BLOCKS: Block[] = [
|
||||
{
|
||||
classification: BlockClassificationEnum.Default,
|
||||
type: BlockEnum.DataSource,
|
||||
title: 'File upload',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
classification: BlockClassificationEnum.Default,
|
||||
type: BlockEnum.Start,
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BLOCKS } from './constants'
|
||||
import {
|
||||
@ -16,19 +20,43 @@ export const useBlocks = () => {
|
||||
})
|
||||
}
|
||||
|
||||
export const useTabs = () => {
|
||||
export const useTabs = (noBlocks?: boolean) => {
|
||||
const { t } = useTranslation()
|
||||
const tabs = useMemo(() => {
|
||||
return [
|
||||
...(
|
||||
noBlocks
|
||||
? []
|
||||
: [
|
||||
{
|
||||
key: TabsEnum.Blocks,
|
||||
name: t('workflow.tabs.blocks'),
|
||||
},
|
||||
]
|
||||
),
|
||||
{
|
||||
key: TabsEnum.Sources,
|
||||
name: t('workflow.tabs.sources'),
|
||||
},
|
||||
{
|
||||
key: TabsEnum.Tools,
|
||||
name: t('workflow.tabs.tools'),
|
||||
},
|
||||
]
|
||||
}, [t, noBlocks])
|
||||
const initialTab = useMemo(() => {
|
||||
if (noBlocks)
|
||||
return TabsEnum.Sources
|
||||
|
||||
return [
|
||||
{
|
||||
key: TabsEnum.Blocks,
|
||||
name: t('workflow.tabs.blocks'),
|
||||
},
|
||||
{
|
||||
key: TabsEnum.Tools,
|
||||
name: t('workflow.tabs.tools'),
|
||||
},
|
||||
]
|
||||
return TabsEnum.Blocks
|
||||
}, [noBlocks])
|
||||
const [activeTab, setActiveTab] = useState(initialTab)
|
||||
|
||||
return {
|
||||
tabs,
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
}
|
||||
}
|
||||
|
||||
export const useToolTabs = () => {
|
||||
|
||||
@ -16,13 +16,15 @@ import type {
|
||||
import type { BlockEnum, OnSelectBlock } from '../types'
|
||||
import Tabs from './tabs'
|
||||
import { TabsEnum } from './types'
|
||||
import { useTabs } from './hooks'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Input from '@/app/components/base/input'
|
||||
import SearchBox from '@/app/components/plugins/marketplace/search-box'
|
||||
// import SearchBox from '@/app/components/plugins/marketplace/search-box'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
import {
|
||||
Plus02,
|
||||
@ -85,10 +87,12 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
||||
onSelect(type, toolDefaultValue)
|
||||
}, [handleOpenChange, onSelect])
|
||||
|
||||
const [activeTab, setActiveTab] = useState(noBlocks ? TabsEnum.Tools : TabsEnum.Blocks)
|
||||
const handleActiveTabChange = useCallback((newActiveTab: TabsEnum) => {
|
||||
setActiveTab(newActiveTab)
|
||||
}, [])
|
||||
const {
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
tabs,
|
||||
} = useTabs()
|
||||
|
||||
const searchPlaceholder = useMemo(() => {
|
||||
if (activeTab === TabsEnum.Blocks)
|
||||
return t('workflow.tabs.searchBlock')
|
||||
@ -128,9 +132,31 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
||||
}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<div className={`rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg ${popupClassName}`}>
|
||||
<div className='px-2 pt-2' onClick={e => e.stopPropagation()}>
|
||||
{activeTab === TabsEnum.Blocks && (
|
||||
<div className={cn(
|
||||
'overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg backdrop-blur-[5px]',
|
||||
popupClassName,
|
||||
)}>
|
||||
<div className='border-b border-divider-subtle bg-background-section-burn'>
|
||||
<div className='flex h-9 items-center px-1 pt-1'>
|
||||
{
|
||||
tabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={cn(
|
||||
'system-sm-medium mr-0.5 cursor-pointer rounded-t-lg px-3 py-2 text-text-tertiary hover:bg-state-base-hover',
|
||||
activeTab === tab.key && 'bg-components-panel-bg text-text-accent shadow-sm',
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setActiveTab(tab.key)
|
||||
}}
|
||||
>
|
||||
{tab.name}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div className='relative z-[1] bg-components-panel-bg p-2'>
|
||||
<Input
|
||||
showLeftIcon
|
||||
showClearIcon
|
||||
@ -140,6 +166,10 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
onClear={() => setSearchText('')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className='p-2' onClick={e => e.stopPropagation()}>
|
||||
{activeTab === TabsEnum.Blocks && (
|
||||
)}
|
||||
{activeTab === TabsEnum.Tools && (
|
||||
<SearchBox
|
||||
@ -151,11 +181,9 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
||||
placeholder={t('plugin.searchTools')!}
|
||||
/>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div> */}
|
||||
<Tabs
|
||||
activeTab={activeTab}
|
||||
onActiveTabChange={handleActiveTabChange}
|
||||
onSelect={handleSelect}
|
||||
searchText={searchText}
|
||||
tags={tags}
|
||||
|
||||
@ -2,16 +2,13 @@ import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/service/use-tools'
|
||||
import type { BlockEnum } from '../types'
|
||||
import { useTabs } from './hooks'
|
||||
import type { ToolDefaultValue } from './types'
|
||||
import { TabsEnum } from './types'
|
||||
import Blocks from './blocks'
|
||||
import AllTools from './all-tools'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type TabsProps = {
|
||||
activeTab: TabsEnum
|
||||
onActiveTabChange: (activeTab: TabsEnum) => void
|
||||
searchText: string
|
||||
tags: string[]
|
||||
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
|
||||
@ -20,42 +17,18 @@ export type TabsProps = {
|
||||
}
|
||||
const Tabs: FC<TabsProps> = ({
|
||||
activeTab,
|
||||
onActiveTabChange,
|
||||
tags,
|
||||
searchText,
|
||||
onSelect,
|
||||
availableBlocksTypes,
|
||||
noBlocks,
|
||||
}) => {
|
||||
const tabs = useTabs()
|
||||
const { data: buildInTools } = useAllBuiltInTools()
|
||||
const { data: customTools } = useAllCustomTools()
|
||||
const { data: workflowTools } = useAllWorkflowTools()
|
||||
|
||||
return (
|
||||
<div onClick={e => e.stopPropagation()}>
|
||||
{
|
||||
!noBlocks && (
|
||||
<div className='flex items-center border-b-[0.5px] border-divider-subtle px-3'>
|
||||
{
|
||||
tabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={cn(
|
||||
'system-sm-medium relative mr-4 cursor-pointer pb-2 pt-1',
|
||||
activeTab === tab.key
|
||||
? 'text-text-primary after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-util-colors-blue-brand-blue-brand-600'
|
||||
: 'text-text-tertiary',
|
||||
)}
|
||||
onClick={() => onActiveTabChange(tab.key)}
|
||||
>
|
||||
{tab.name}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
activeTab === TabsEnum.Blocks && !noBlocks && (
|
||||
<Blocks
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
export enum TabsEnum {
|
||||
Blocks = 'blocks',
|
||||
Tools = 'tools',
|
||||
Sources = 'sources',
|
||||
}
|
||||
|
||||
export enum ToolTypeEnum {
|
||||
|
||||
@ -22,6 +22,7 @@ import IterationStartDefault from './nodes/iteration-start/default'
|
||||
import AgentDefault from './nodes/agent/default'
|
||||
import LoopStartDefault from './nodes/loop-start/default'
|
||||
import LoopEndDefault from './nodes/loop-end/default'
|
||||
import DataSourceDefault from './nodes/data-source/default'
|
||||
|
||||
type NodesExtraData = {
|
||||
author: string
|
||||
@ -33,6 +34,15 @@ type NodesExtraData = {
|
||||
checkValid: any
|
||||
}
|
||||
export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
|
||||
[BlockEnum.DataSource]: {
|
||||
author: 'Dify',
|
||||
about: '',
|
||||
availablePrevNodes: [],
|
||||
availableNextNodes: [],
|
||||
getAvailablePrevNodes: DataSourceDefault.getAvailablePrevNodes,
|
||||
getAvailableNextNodes: DataSourceDefault.getAvailableNextNodes,
|
||||
checkValid: DataSourceDefault.checkValid,
|
||||
},
|
||||
[BlockEnum.Start]: {
|
||||
author: 'Dify',
|
||||
about: '',
|
||||
@ -243,6 +253,12 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
|
||||
}
|
||||
|
||||
export const NODES_INITIAL_DATA = {
|
||||
[BlockEnum.DataSource]: {
|
||||
type: BlockEnum.DataSource,
|
||||
title: '',
|
||||
desc: '',
|
||||
...DataSourceDefault.defaultValue,
|
||||
},
|
||||
[BlockEnum.Start]: {
|
||||
type: BlockEnum.Start,
|
||||
title: '',
|
||||
|
||||
@ -25,11 +25,8 @@ import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '../store'
|
||||
|
||||
import {
|
||||
getParallelInfo,
|
||||
} from '../utils'
|
||||
import {
|
||||
PARALLEL_DEPTH_LIMIT,
|
||||
PARALLEL_LIMIT,
|
||||
SUPPORT_OUTPUT_VARS_NODE,
|
||||
} from '../constants'
|
||||
@ -323,24 +320,24 @@ export const useWorkflow = () => {
|
||||
}, [store, workflowStore, t])
|
||||
|
||||
const checkNestedParallelLimit = useCallback((nodes: Node[], edges: Edge[], parentNodeId?: string) => {
|
||||
const {
|
||||
parallelList,
|
||||
hasAbnormalEdges,
|
||||
} = getParallelInfo(nodes, edges, parentNodeId)
|
||||
const { workflowConfig } = workflowStore.getState()
|
||||
// const {
|
||||
// parallelList,
|
||||
// hasAbnormalEdges,
|
||||
// } = getParallelInfo(nodes, edges, parentNodeId)
|
||||
// const { workflowConfig } = workflowStore.getState()
|
||||
|
||||
if (hasAbnormalEdges)
|
||||
return false
|
||||
// if (hasAbnormalEdges)
|
||||
// return false
|
||||
|
||||
for (let i = 0; i < parallelList.length; i++) {
|
||||
const parallel = parallelList[i]
|
||||
// for (let i = 0; i < parallelList.length; i++) {
|
||||
// const parallel = parallelList[i]
|
||||
|
||||
if (parallel.depth > (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT)) {
|
||||
const { setShowTips } = workflowStore.getState()
|
||||
setShowTips(t('workflow.common.parallelTip.depthLimit', { num: (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT) }))
|
||||
return false
|
||||
}
|
||||
}
|
||||
// if (parallel.depth > (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT)) {
|
||||
// const { setShowTips } = workflowStore.getState()
|
||||
// setShowTips(t('workflow.common.parallelTip.depthLimit', { num: (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT) }))
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
|
||||
return true
|
||||
}, [t, workflowStore])
|
||||
|
||||
@ -131,7 +131,7 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex rounded-2xl border-[2px]',
|
||||
'relative flex rounded-2xl border',
|
||||
showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent',
|
||||
!showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight',
|
||||
data._waitingRun && 'opacity-70',
|
||||
@ -142,6 +142,15 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
height: (data.type === BlockEnum.Iteration || data.type === BlockEnum.Loop) ? data.height : 'auto',
|
||||
}}
|
||||
>
|
||||
{
|
||||
data.type === BlockEnum.DataSource && (
|
||||
<div className='absolute inset-[-2px] top-[-22px] z-[-1] rounded-[18px] bg-node-data-source-bg p-0.5 backdrop-blur-[6px]'>
|
||||
<div className='system-2xs-semibold-uppercase flex h-5 items-center px-2.5 text-text-tertiary'>
|
||||
data source
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div
|
||||
className={cn(
|
||||
'group relative pb-1 shadow-xs',
|
||||
|
||||
@ -38,6 +38,8 @@ import ListFilterNode from './list-operator/node'
|
||||
import ListFilterPanel from './list-operator/panel'
|
||||
import AgentNode from './agent/node'
|
||||
import AgentPanel from './agent/panel'
|
||||
import DataSourceNode from './data-source/node'
|
||||
import DataSourcePanel from './data-source/panel'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
|
||||
export const NodeComponentMap: Record<string, ComponentType<any>> = {
|
||||
@ -61,6 +63,7 @@ export const NodeComponentMap: Record<string, ComponentType<any>> = {
|
||||
[BlockEnum.DocExtractor]: DocExtractorNode,
|
||||
[BlockEnum.ListFilter]: ListFilterNode,
|
||||
[BlockEnum.Agent]: AgentNode,
|
||||
[BlockEnum.DataSource]: DataSourceNode,
|
||||
}
|
||||
|
||||
export const PanelComponentMap: Record<string, ComponentType<any>> = {
|
||||
@ -84,6 +87,7 @@ export const PanelComponentMap: Record<string, ComponentType<any>> = {
|
||||
[BlockEnum.DocExtractor]: DocExtractorPanel,
|
||||
[BlockEnum.ListFilter]: ListFilterPanel,
|
||||
[BlockEnum.Agent]: AgentPanel,
|
||||
[BlockEnum.DataSource]: DataSourcePanel,
|
||||
}
|
||||
|
||||
export const CUSTOM_NODE_TYPE = 'custom'
|
||||
|
||||
27
web/app/components/workflow/nodes/data-source/default.ts
Normal file
27
web/app/components/workflow/nodes/data-source/default.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { BlockEnum } from '../../types'
|
||||
import type { NodeDefault } from '../../types'
|
||||
import type { DataSourceNodeType } from './types'
|
||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks'
|
||||
|
||||
const nodeDefault: NodeDefault<DataSourceNodeType> = {
|
||||
defaultValue: {
|
||||
},
|
||||
getAvailablePrevNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode
|
||||
? ALL_CHAT_AVAILABLE_BLOCKS
|
||||
: ALL_COMPLETION_AVAILABLE_BLOCKS.filter(type => type !== BlockEnum.End)
|
||||
return nodes
|
||||
},
|
||||
getAvailableNextNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode ? ALL_CHAT_AVAILABLE_BLOCKS : ALL_COMPLETION_AVAILABLE_BLOCKS
|
||||
return nodes
|
||||
},
|
||||
checkValid() {
|
||||
return {
|
||||
isValid: true,
|
||||
errorMessage: '',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default nodeDefault
|
||||
13
web/app/components/workflow/nodes/data-source/node.tsx
Normal file
13
web/app/components/workflow/nodes/data-source/node.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import type { DataSourceNodeType } from './types'
|
||||
import type { NodeProps } from '@/app/components/workflow/types'
|
||||
const Node: FC<NodeProps<DataSourceNodeType>> = () => {
|
||||
return (
|
||||
<div className='mb-1 px-3 py-1'>
|
||||
DataSource
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Node)
|
||||
14
web/app/components/workflow/nodes/data-source/panel.tsx
Normal file
14
web/app/components/workflow/nodes/data-source/panel.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import type { DataSourceNodeType } from './types'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
|
||||
const Panel: FC<NodePanelProps<DataSourceNodeType>> = () => {
|
||||
return (
|
||||
<div className='mb-2 mt-2 space-y-4 px-4'>
|
||||
datasource
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Panel)
|
||||
3
web/app/components/workflow/nodes/data-source/types.ts
Normal file
3
web/app/components/workflow/nodes/data-source/types.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
|
||||
export type DataSourceNodeType = CommonNodeType
|
||||
@ -40,6 +40,7 @@ export enum BlockEnum {
|
||||
Loop = 'loop',
|
||||
LoopStart = 'loop-start',
|
||||
LoopEnd = 'loop-end',
|
||||
DataSource = 'data-source',
|
||||
}
|
||||
|
||||
export enum ControlMode {
|
||||
|
||||
@ -229,6 +229,7 @@ const translation = {
|
||||
'utilities': 'Utilities',
|
||||
'noResult': 'No match found',
|
||||
'agent': 'Agent Strategy',
|
||||
'sources': 'Sources',
|
||||
},
|
||||
blocks: {
|
||||
'start': 'Start',
|
||||
|
||||
@ -230,6 +230,7 @@ const translation = {
|
||||
'utilities': '工具',
|
||||
'noResult': '未找到匹配项',
|
||||
'agent': 'Agent 策略',
|
||||
'sources': '数据源',
|
||||
},
|
||||
blocks: {
|
||||
'start': '开始',
|
||||
|
||||
@ -120,6 +120,7 @@ const config = {
|
||||
'price-premium-text-background': 'var(--color-premium-text-background)',
|
||||
'price-enterprise-background': 'var(--color-price-enterprise-background)',
|
||||
'grid-mask-background': 'var(--color-grid-mask-background)',
|
||||
'node-data-source-bg': 'var(--color-node-data-source-bg)',
|
||||
},
|
||||
animation: {
|
||||
'spin-slow': 'spin 2s linear infinite',
|
||||
|
||||
@ -61,4 +61,9 @@ html[data-theme="dark"] {
|
||||
rgba(24, 24, 27, 0.08) 0%,
|
||||
rgba(0, 0, 0, 0) 100%);
|
||||
--color-line-divider-bg: linear-gradient(90deg, rgba(200, 206, 218, 0.14) 0%, rgba(0, 0, 0, 0) 100%, );
|
||||
--workflow-block-wrapper-bg-1: rgba(39, 39, 43, 1);
|
||||
--workflow-block-wrapper-bg-2: rgba(39, 39, 43, 0.2);
|
||||
--color-node-data-source-bg: linear-gradient(100deg, var(--workflow-block-wrapper-bg-1, #E9EBF0) 0%, var(--workflow-block-wrapper-bg-2, rgba(233, 235, 240, 0.20)) 100%);
|
||||
|
||||
|
||||
}
|
||||
@ -61,4 +61,7 @@ html[data-theme="light"] {
|
||||
rgba(200, 206, 218, 0.2) 0%,
|
||||
rgba(255, 255, 255, 0) 100%);
|
||||
--color-line-divider-bg: linear-gradient(90deg, rgba(16, 24, 40, 0.08) 0%, rgba(255, 255, 255, 0) 100%);
|
||||
--workflow-block-wrapper-bg-1: rgba(233, 235, 240, 1);
|
||||
--workflow-block-wrapper-bg-2: rgba(233, 235, 240, 0.2);
|
||||
--color-node-data-source-bg: linear-gradient(100deg, var(--workflow-block-wrapper-bg-1, #E9EBF0) 0%, var(--workflow-block-wrapper-bg-2, rgba(233, 235, 240, 0.20)) 100%);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user