diff --git a/web/app/components/workflow/block-selector/blocks.tsx b/web/app/components/workflow/block-selector/blocks.tsx index 4182530a91..7074de2e45 100644 --- a/web/app/components/workflow/block-selector/blocks.tsx +++ b/web/app/components/workflow/block-selector/blocks.tsx @@ -109,7 +109,7 @@ const Blocks = ({ }, [groups, nodesExtraData, onSelect, t]) return ( -
+
{ isEmpty && (
{t('workflow.tabs.noResult')}
diff --git a/web/app/components/workflow/block-selector/constants.tsx b/web/app/components/workflow/block-selector/constants.tsx index 680cbf45b9..12dd8b5bc5 100644 --- a/web/app/components/workflow/block-selector/constants.tsx +++ b/web/app/components/workflow/block-selector/constants.tsx @@ -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, diff --git a/web/app/components/workflow/block-selector/hooks.ts b/web/app/components/workflow/block-selector/hooks.ts index a8b1759506..395a4db00a 100644 --- a/web/app/components/workflow/block-selector/hooks.ts +++ b/web/app/components/workflow/block-selector/hooks.ts @@ -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 = () => { diff --git a/web/app/components/workflow/block-selector/index.tsx b/web/app/components/workflow/block-selector/index.tsx index f0f57adefe..506f6d0977 100644 --- a/web/app/components/workflow/block-selector/index.tsx +++ b/web/app/components/workflow/block-selector/index.tsx @@ -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 = ({ 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 = ({ } -
-
e.stopPropagation()}> - {activeTab === TabsEnum.Blocks && ( +
+
+
+ { + tabs.map(tab => ( +
{ + e.stopPropagation() + setActiveTab(tab.key) + }} + > + {tab.name} +
+ )) + } +
+
= ({ onChange={e => setSearchText(e.target.value)} onClear={() => setSearchText('')} /> +
+
+ {/*
e.stopPropagation()}> + {activeTab === TabsEnum.Blocks && ( )} {activeTab === TabsEnum.Tools && ( = ({ placeholder={t('plugin.searchTools')!} /> )} - -
+
*/} void searchText: string tags: string[] onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void @@ -20,42 +17,18 @@ export type TabsProps = { } const Tabs: FC = ({ activeTab, - onActiveTabChange, tags, searchText, onSelect, availableBlocksTypes, noBlocks, }) => { - const tabs = useTabs() const { data: buildInTools } = useAllBuiltInTools() const { data: customTools } = useAllCustomTools() const { data: workflowTools } = useAllWorkflowTools() return (
e.stopPropagation()}> - { - !noBlocks && ( -
- { - tabs.map(tab => ( -
onActiveTabChange(tab.key)} - > - {tab.name} -
- )) - } -
- ) - } { activeTab === TabsEnum.Blocks && !noBlocks && ( = { + [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 = { } export const NODES_INITIAL_DATA = { + [BlockEnum.DataSource]: { + type: BlockEnum.DataSource, + title: '', + desc: '', + ...DataSourceDefault.defaultValue, + }, [BlockEnum.Start]: { type: BlockEnum.Start, title: '', diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index 99dce4dc15..67419dc211 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -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]) diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index 527b2f094d..0a5f397c56 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -131,7 +131,7 @@ const BaseNode: FC = ({ return (
= ({ height: (data.type === BlockEnum.Iteration || data.type === BlockEnum.Loop) ? data.height : 'auto', }} > + { + data.type === BlockEnum.DataSource && ( +
+
+ data source +
+
+ ) + }
> = { @@ -61,6 +63,7 @@ export const NodeComponentMap: Record> = { [BlockEnum.DocExtractor]: DocExtractorNode, [BlockEnum.ListFilter]: ListFilterNode, [BlockEnum.Agent]: AgentNode, + [BlockEnum.DataSource]: DataSourceNode, } export const PanelComponentMap: Record> = { @@ -84,6 +87,7 @@ export const PanelComponentMap: Record> = { [BlockEnum.DocExtractor]: DocExtractorPanel, [BlockEnum.ListFilter]: ListFilterPanel, [BlockEnum.Agent]: AgentPanel, + [BlockEnum.DataSource]: DataSourcePanel, } export const CUSTOM_NODE_TYPE = 'custom' diff --git a/web/app/components/workflow/nodes/data-source/default.ts b/web/app/components/workflow/nodes/data-source/default.ts new file mode 100644 index 0000000000..74dc34bd23 --- /dev/null +++ b/web/app/components/workflow/nodes/data-source/default.ts @@ -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 = { + 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 diff --git a/web/app/components/workflow/nodes/data-source/node.tsx b/web/app/components/workflow/nodes/data-source/node.tsx new file mode 100644 index 0000000000..37489df34c --- /dev/null +++ b/web/app/components/workflow/nodes/data-source/node.tsx @@ -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> = () => { + return ( +
+ DataSource +
+ ) +} + +export default memo(Node) diff --git a/web/app/components/workflow/nodes/data-source/panel.tsx b/web/app/components/workflow/nodes/data-source/panel.tsx new file mode 100644 index 0000000000..feefaa8612 --- /dev/null +++ b/web/app/components/workflow/nodes/data-source/panel.tsx @@ -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> = () => { + return ( +
+ datasource +
+ ) +} + +export default memo(Panel) diff --git a/web/app/components/workflow/nodes/data-source/types.ts b/web/app/components/workflow/nodes/data-source/types.ts new file mode 100644 index 0000000000..3a8c06ab4c --- /dev/null +++ b/web/app/components/workflow/nodes/data-source/types.ts @@ -0,0 +1,3 @@ +import type { CommonNodeType } from '@/app/components/workflow/types' + +export type DataSourceNodeType = CommonNodeType diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 0cc859fcb5..16dfff2894 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -40,6 +40,7 @@ export enum BlockEnum { Loop = 'loop', LoopStart = 'loop-start', LoopEnd = 'loop-end', + DataSource = 'data-source', } export enum ControlMode { diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 543f689611..b5364cabbe 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -229,6 +229,7 @@ const translation = { 'utilities': 'Utilities', 'noResult': 'No match found', 'agent': 'Agent Strategy', + 'sources': 'Sources', }, blocks: { 'start': 'Start', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index ab56c468ce..0a4583b27e 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -230,6 +230,7 @@ const translation = { 'utilities': '工具', 'noResult': '未找到匹配项', 'agent': 'Agent 策略', + 'sources': '数据源', }, blocks: { 'start': '开始', diff --git a/web/tailwind-common-config.ts b/web/tailwind-common-config.ts index 3f64afcc29..4bdd3e5b51 100644 --- a/web/tailwind-common-config.ts +++ b/web/tailwind-common-config.ts @@ -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', diff --git a/web/themes/manual-dark.css b/web/themes/manual-dark.css index 3c2b143b3c..d3220bd1a5 100644 --- a/web/themes/manual-dark.css +++ b/web/themes/manual-dark.css @@ -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%); + + } \ No newline at end of file diff --git a/web/themes/manual-light.css b/web/themes/manual-light.css index 92473320bc..819a08c8bc 100644 --- a/web/themes/manual-light.css +++ b/web/themes/manual-light.css @@ -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%); } \ No newline at end of file