block selector & data source node

pull/21398/head
zxhlyh 1 year ago
parent efb27eb443
commit 8d9c252811

@ -109,7 +109,7 @@ const Blocks = ({
}, [groups, nodesExtraData, onSelect, t]) }, [groups, nodesExtraData, onSelect, t])
return ( return (
<div className='p-1'> <div className='max-h-[480px] overflow-y-auto p-1'>
{ {
isEmpty && ( isEmpty && (
<div className='flex h-[22px] items-center px-3 text-xs font-medium text-text-tertiary'>{t('workflow.tabs.noResult')}</div> <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' import { BlockClassificationEnum } from './types'
export const BLOCKS: Block[] = [ export const BLOCKS: Block[] = [
{
classification: BlockClassificationEnum.Default,
type: BlockEnum.DataSource,
title: 'File upload',
description: '',
},
{ {
classification: BlockClassificationEnum.Default, classification: BlockClassificationEnum.Default,
type: BlockEnum.Start, type: BlockEnum.Start,

@ -1,3 +1,7 @@
import {
useMemo,
useState,
} from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { BLOCKS } from './constants' import { BLOCKS } from './constants'
import { import {
@ -16,19 +20,43 @@ export const useBlocks = () => {
}) })
} }
export const useTabs = () => { export const useTabs = (noBlocks?: boolean) => {
const { t } = useTranslation() 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 [ return TabsEnum.Blocks
{ }, [noBlocks])
key: TabsEnum.Blocks, const [activeTab, setActiveTab] = useState(initialTab)
name: t('workflow.tabs.blocks'),
}, return {
{ tabs,
key: TabsEnum.Tools, activeTab,
name: t('workflow.tabs.tools'), setActiveTab,
}, }
]
} }
export const useToolTabs = () => { export const useToolTabs = () => {

@ -16,13 +16,15 @@ import type {
import type { BlockEnum, OnSelectBlock } from '../types' import type { BlockEnum, OnSelectBlock } from '../types'
import Tabs from './tabs' import Tabs from './tabs'
import { TabsEnum } from './types' import { TabsEnum } from './types'
import { useTabs } from './hooks'
import { import {
PortalToFollowElem, PortalToFollowElem,
PortalToFollowElemContent, PortalToFollowElemContent,
PortalToFollowElemTrigger, PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem' } from '@/app/components/base/portal-to-follow-elem'
import Input from '@/app/components/base/input' 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 { import {
Plus02, Plus02,
@ -85,10 +87,12 @@ const NodeSelector: FC<NodeSelectorProps> = ({
onSelect(type, toolDefaultValue) onSelect(type, toolDefaultValue)
}, [handleOpenChange, onSelect]) }, [handleOpenChange, onSelect])
const [activeTab, setActiveTab] = useState(noBlocks ? TabsEnum.Tools : TabsEnum.Blocks) const {
const handleActiveTabChange = useCallback((newActiveTab: TabsEnum) => { activeTab,
setActiveTab(newActiveTab) setActiveTab,
}, []) tabs,
} = useTabs()
const searchPlaceholder = useMemo(() => { const searchPlaceholder = useMemo(() => {
if (activeTab === TabsEnum.Blocks) if (activeTab === TabsEnum.Blocks)
return t('workflow.tabs.searchBlock') return t('workflow.tabs.searchBlock')
@ -128,9 +132,31 @@ const NodeSelector: FC<NodeSelectorProps> = ({
} }
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[1000]'> <PortalToFollowElemContent className='z-[1000]'>
<div className={`rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg ${popupClassName}`}> <div className={cn(
<div className='px-2 pt-2' onClick={e => e.stopPropagation()}> 'overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-lg backdrop-blur-[5px]',
{activeTab === TabsEnum.Blocks && ( 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 <Input
showLeftIcon showLeftIcon
showClearIcon showClearIcon
@ -140,6 +166,10 @@ const NodeSelector: FC<NodeSelectorProps> = ({
onChange={e => setSearchText(e.target.value)} onChange={e => setSearchText(e.target.value)}
onClear={() => setSearchText('')} onClear={() => setSearchText('')}
/> />
</div>
</div>
{/* <div className='p-2' onClick={e => e.stopPropagation()}>
{activeTab === TabsEnum.Blocks && (
)} )}
{activeTab === TabsEnum.Tools && ( {activeTab === TabsEnum.Tools && (
<SearchBox <SearchBox
@ -151,11 +181,9 @@ const NodeSelector: FC<NodeSelectorProps> = ({
placeholder={t('plugin.searchTools')!} placeholder={t('plugin.searchTools')!}
/> />
)} )}
</div> */}
</div>
<Tabs <Tabs
activeTab={activeTab} activeTab={activeTab}
onActiveTabChange={handleActiveTabChange}
onSelect={handleSelect} onSelect={handleSelect}
searchText={searchText} searchText={searchText}
tags={tags} tags={tags}

@ -2,16 +2,13 @@ import type { FC } from 'react'
import { memo } from 'react' import { memo } from 'react'
import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/service/use-tools' import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/service/use-tools'
import type { BlockEnum } from '../types' import type { BlockEnum } from '../types'
import { useTabs } from './hooks'
import type { ToolDefaultValue } from './types' import type { ToolDefaultValue } from './types'
import { TabsEnum } from './types' import { TabsEnum } from './types'
import Blocks from './blocks' import Blocks from './blocks'
import AllTools from './all-tools' import AllTools from './all-tools'
import cn from '@/utils/classnames'
export type TabsProps = { export type TabsProps = {
activeTab: TabsEnum activeTab: TabsEnum
onActiveTabChange: (activeTab: TabsEnum) => void
searchText: string searchText: string
tags: string[] tags: string[]
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
@ -20,42 +17,18 @@ export type TabsProps = {
} }
const Tabs: FC<TabsProps> = ({ const Tabs: FC<TabsProps> = ({
activeTab, activeTab,
onActiveTabChange,
tags, tags,
searchText, searchText,
onSelect, onSelect,
availableBlocksTypes, availableBlocksTypes,
noBlocks, noBlocks,
}) => { }) => {
const tabs = useTabs()
const { data: buildInTools } = useAllBuiltInTools() const { data: buildInTools } = useAllBuiltInTools()
const { data: customTools } = useAllCustomTools() const { data: customTools } = useAllCustomTools()
const { data: workflowTools } = useAllWorkflowTools() const { data: workflowTools } = useAllWorkflowTools()
return ( return (
<div onClick={e => e.stopPropagation()}> <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 && ( activeTab === TabsEnum.Blocks && !noBlocks && (
<Blocks <Blocks

@ -1,6 +1,7 @@
export enum TabsEnum { export enum TabsEnum {
Blocks = 'blocks', Blocks = 'blocks',
Tools = 'tools', Tools = 'tools',
Sources = 'sources',
} }
export enum ToolTypeEnum { export enum ToolTypeEnum {

@ -22,6 +22,7 @@ import IterationStartDefault from './nodes/iteration-start/default'
import AgentDefault from './nodes/agent/default' import AgentDefault from './nodes/agent/default'
import LoopStartDefault from './nodes/loop-start/default' import LoopStartDefault from './nodes/loop-start/default'
import LoopEndDefault from './nodes/loop-end/default' import LoopEndDefault from './nodes/loop-end/default'
import DataSourceDefault from './nodes/data-source/default'
type NodesExtraData = { type NodesExtraData = {
author: string author: string
@ -33,6 +34,15 @@ type NodesExtraData = {
checkValid: any checkValid: any
} }
export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = { 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]: { [BlockEnum.Start]: {
author: 'Dify', author: 'Dify',
about: '', about: '',
@ -243,6 +253,12 @@ export const NODES_EXTRA_DATA: Record<BlockEnum, NodesExtraData> = {
} }
export const NODES_INITIAL_DATA = { export const NODES_INITIAL_DATA = {
[BlockEnum.DataSource]: {
type: BlockEnum.DataSource,
title: '',
desc: '',
...DataSourceDefault.defaultValue,
},
[BlockEnum.Start]: { [BlockEnum.Start]: {
type: BlockEnum.Start, type: BlockEnum.Start,
title: '', title: '',

@ -25,11 +25,8 @@ import {
useStore, useStore,
useWorkflowStore, useWorkflowStore,
} from '../store' } from '../store'
import { import {
getParallelInfo,
} from '../utils'
import {
PARALLEL_DEPTH_LIMIT,
PARALLEL_LIMIT, PARALLEL_LIMIT,
SUPPORT_OUTPUT_VARS_NODE, SUPPORT_OUTPUT_VARS_NODE,
} from '../constants' } from '../constants'
@ -323,24 +320,24 @@ export const useWorkflow = () => {
}, [store, workflowStore, t]) }, [store, workflowStore, t])
const checkNestedParallelLimit = useCallback((nodes: Node[], edges: Edge[], parentNodeId?: string) => { const checkNestedParallelLimit = useCallback((nodes: Node[], edges: Edge[], parentNodeId?: string) => {
const { // const {
parallelList, // parallelList,
hasAbnormalEdges, // hasAbnormalEdges,
} = getParallelInfo(nodes, edges, parentNodeId) // } = getParallelInfo(nodes, edges, parentNodeId)
const { workflowConfig } = workflowStore.getState() // const { workflowConfig } = workflowStore.getState()
if (hasAbnormalEdges) // if (hasAbnormalEdges)
return false // return false
for (let i = 0; i < parallelList.length; i++) { // for (let i = 0; i < parallelList.length; i++) {
const parallel = parallelList[i] // const parallel = parallelList[i]
if (parallel.depth > (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT)) { // if (parallel.depth > (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT)) {
const { setShowTips } = workflowStore.getState() // const { setShowTips } = workflowStore.getState()
setShowTips(t('workflow.common.parallelTip.depthLimit', { num: (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT) })) // setShowTips(t('workflow.common.parallelTip.depthLimit', { num: (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT) }))
return false // return false
} // }
} // }
return true return true
}, [t, workflowStore]) }, [t, workflowStore])

@ -131,7 +131,7 @@ const BaseNode: FC<BaseNodeProps> = ({
return ( return (
<div <div
className={cn( className={cn(
'flex rounded-2xl border-[2px]', 'relative flex rounded-2xl border',
showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent', showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent',
!showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight', !showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight',
data._waitingRun && 'opacity-70', data._waitingRun && 'opacity-70',
@ -142,6 +142,15 @@ const BaseNode: FC<BaseNodeProps> = ({
height: (data.type === BlockEnum.Iteration || data.type === BlockEnum.Loop) ? data.height : 'auto', 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 <div
className={cn( className={cn(
'group relative pb-1 shadow-xs', 'group relative pb-1 shadow-xs',

@ -38,6 +38,8 @@ import ListFilterNode from './list-operator/node'
import ListFilterPanel from './list-operator/panel' import ListFilterPanel from './list-operator/panel'
import AgentNode from './agent/node' import AgentNode from './agent/node'
import AgentPanel from './agent/panel' import AgentPanel from './agent/panel'
import DataSourceNode from './data-source/node'
import DataSourcePanel from './data-source/panel'
import { TransferMethod } from '@/types/app' import { TransferMethod } from '@/types/app'
export const NodeComponentMap: Record<string, ComponentType<any>> = { export const NodeComponentMap: Record<string, ComponentType<any>> = {
@ -61,6 +63,7 @@ export const NodeComponentMap: Record<string, ComponentType<any>> = {
[BlockEnum.DocExtractor]: DocExtractorNode, [BlockEnum.DocExtractor]: DocExtractorNode,
[BlockEnum.ListFilter]: ListFilterNode, [BlockEnum.ListFilter]: ListFilterNode,
[BlockEnum.Agent]: AgentNode, [BlockEnum.Agent]: AgentNode,
[BlockEnum.DataSource]: DataSourceNode,
} }
export const PanelComponentMap: Record<string, ComponentType<any>> = { export const PanelComponentMap: Record<string, ComponentType<any>> = {
@ -84,6 +87,7 @@ export const PanelComponentMap: Record<string, ComponentType<any>> = {
[BlockEnum.DocExtractor]: DocExtractorPanel, [BlockEnum.DocExtractor]: DocExtractorPanel,
[BlockEnum.ListFilter]: ListFilterPanel, [BlockEnum.ListFilter]: ListFilterPanel,
[BlockEnum.Agent]: AgentPanel, [BlockEnum.Agent]: AgentPanel,
[BlockEnum.DataSource]: DataSourcePanel,
} }
export const CUSTOM_NODE_TYPE = 'custom' export const CUSTOM_NODE_TYPE = 'custom'

@ -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

@ -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)

@ -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)

@ -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', Loop = 'loop',
LoopStart = 'loop-start', LoopStart = 'loop-start',
LoopEnd = 'loop-end', LoopEnd = 'loop-end',
DataSource = 'data-source',
} }
export enum ControlMode { export enum ControlMode {

@ -229,6 +229,7 @@ const translation = {
'utilities': 'Utilities', 'utilities': 'Utilities',
'noResult': 'No match found', 'noResult': 'No match found',
'agent': 'Agent Strategy', 'agent': 'Agent Strategy',
'sources': 'Sources',
}, },
blocks: { blocks: {
'start': 'Start', 'start': 'Start',

@ -230,6 +230,7 @@ const translation = {
'utilities': '工具', 'utilities': '工具',
'noResult': '未找到匹配项', 'noResult': '未找到匹配项',
'agent': 'Agent 策略', 'agent': 'Agent 策略',
'sources': '数据源',
}, },
blocks: { blocks: {
'start': '开始', 'start': '开始',

@ -120,6 +120,7 @@ const config = {
'price-premium-text-background': 'var(--color-premium-text-background)', 'price-premium-text-background': 'var(--color-premium-text-background)',
'price-enterprise-background': 'var(--color-price-enterprise-background)', 'price-enterprise-background': 'var(--color-price-enterprise-background)',
'grid-mask-background': 'var(--color-grid-mask-background)', 'grid-mask-background': 'var(--color-grid-mask-background)',
'node-data-source-bg': 'var(--color-node-data-source-bg)',
}, },
animation: { animation: {
'spin-slow': 'spin 2s linear infinite', 'spin-slow': 'spin 2s linear infinite',

@ -61,4 +61,9 @@ html[data-theme="dark"] {
rgba(24, 24, 27, 0.08) 0%, rgba(24, 24, 27, 0.08) 0%,
rgba(0, 0, 0, 0) 100%); 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%, ); --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(200, 206, 218, 0.2) 0%,
rgba(255, 255, 255, 0) 100%); 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%); --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…
Cancel
Save