From d76e37b0188265089f0a028ebd5eb6d9a7e5a0de Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Tue, 22 Jul 2025 16:48:24 +0800 Subject: [PATCH] add datasource empty node --- .../{ => components}/conversion.tsx | 6 +- .../{ => components}/screenshot.tsx | 0 .../hooks/use-available-nodes-meta-data.ts | 8 ++ .../hooks/use-nodes-sync-draft.ts | 4 +- web/app/components/rag-pipeline/index.tsx | 9 ++- .../components/rag-pipeline/utils/index.ts | 1 + .../components/rag-pipeline/utils/nodes.ts | 77 +++++++++++++++++++ .../workflow/block-selector/data-sources.tsx | 19 +++++ .../workflow/block-selector/hooks.ts | 25 ++++-- .../workflow/block-selector/index.tsx | 3 + .../workflow/block-selector/main.tsx | 16 +++- .../workflow/block-selector/tabs.tsx | 4 +- .../workflow/hooks/use-nodes-interactions.ts | 6 +- web/app/components/workflow/index.tsx | 5 +- .../nodes/data-source-empty/constants.ts | 1 + .../nodes/data-source-empty/default.ts | 20 +++++ .../workflow/nodes/data-source-empty/hooks.ts | 47 +++++++++++ .../nodes/data-source-empty/index.tsx | 70 +++++++++++++++++ .../workflow/nodes/data-source-empty/types.ts | 3 + web/app/components/workflow/types.ts | 2 + web/i18n/en-US/workflow.ts | 1 + web/i18n/zh-Hans/workflow.ts | 1 + 22 files changed, 307 insertions(+), 21 deletions(-) rename web/app/components/rag-pipeline/{ => components}/conversion.tsx (96%) rename web/app/components/rag-pipeline/{ => components}/screenshot.tsx (100%) create mode 100644 web/app/components/rag-pipeline/utils/index.ts create mode 100644 web/app/components/rag-pipeline/utils/nodes.ts create mode 100644 web/app/components/workflow/nodes/data-source-empty/constants.ts create mode 100644 web/app/components/workflow/nodes/data-source-empty/default.ts create mode 100644 web/app/components/workflow/nodes/data-source-empty/hooks.ts create mode 100644 web/app/components/workflow/nodes/data-source-empty/index.tsx create mode 100644 web/app/components/workflow/nodes/data-source-empty/types.ts diff --git a/web/app/components/rag-pipeline/conversion.tsx b/web/app/components/rag-pipeline/components/conversion.tsx similarity index 96% rename from web/app/components/rag-pipeline/conversion.tsx rename to web/app/components/rag-pipeline/components/conversion.tsx index cf9f4807d5..e50ecc56fd 100644 --- a/web/app/components/rag-pipeline/conversion.tsx +++ b/web/app/components/rag-pipeline/components/conversion.tsx @@ -1,13 +1,13 @@ import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import Button from '../base/button' +import Button from '@/app/components/base/button' import PipelineScreenShot from './screenshot' -import Confirm from '../base/confirm' +import Confirm from '@/app/components/base/confirm' import { useConvertDatasetToPipeline } from '@/service/use-pipeline' import { useParams } from 'next/navigation' import { useInvalid } from '@/service/use-base' import { datasetDetailQueryKeyPrefix } from '@/service/knowledge/use-dataset' -import Toast from '../base/toast' +import Toast from '@/app/components/base/toast' const Conversion = () => { const { t } = useTranslation() diff --git a/web/app/components/rag-pipeline/screenshot.tsx b/web/app/components/rag-pipeline/components/screenshot.tsx similarity index 100% rename from web/app/components/rag-pipeline/screenshot.tsx rename to web/app/components/rag-pipeline/components/screenshot.tsx diff --git a/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts b/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts index 368224782f..017af60109 100644 --- a/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts +++ b/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts @@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next' import { useGetLanguage } from '@/context/i18n' import knowledgeBaseDefault from '@/app/components/workflow/nodes/knowledge-base/default' import dataSourceDefault from '@/app/components/workflow/nodes/data-source/default' +import dataSourceEmptyDefault from '@/app/components/workflow/nodes/data-source-empty/default' import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node' import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store' @@ -32,6 +33,13 @@ export const useAvailableNodesMetaData = () => { isUndeletable: true, }, }, + { + ...dataSourceEmptyDefault, + metaData: { + ...dataSourceEmptyDefault.metaData, + isUndeletable: true, + }, + }, ], []) const prefixLink = useMemo(() => { diff --git a/web/app/components/rag-pipeline/hooks/use-nodes-sync-draft.ts b/web/app/components/rag-pipeline/hooks/use-nodes-sync-draft.ts index b373bca4f5..5c326ccc34 100644 --- a/web/app/components/rag-pipeline/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/rag-pipeline/hooks/use-nodes-sync-draft.ts @@ -10,6 +10,7 @@ import { import { API_PREFIX } from '@/config' import { syncWorkflowDraft } from '@/service/workflow' import { usePipelineRefreshDraft } from '.' +import { CUSTOM_DATA_SOURCE_EMPTY_NODE } from '@/app/components/workflow/nodes/data-source-empty/constants' export const useNodesSyncDraft = () => { const store = useStoreApi() @@ -23,7 +24,8 @@ export const useNodesSyncDraft = () => { edges, transform, } = store.getState() - const nodes = getNodes() + const nodesOriginal = getNodes() + const nodes = nodesOriginal.filter(node => node.type !== CUSTOM_DATA_SOURCE_EMPTY_NODE) const [x, y, zoom] = transform const { pipelineId, diff --git a/web/app/components/rag-pipeline/index.tsx b/web/app/components/rag-pipeline/index.tsx index cfc324a281..6e1e4c7e5f 100644 --- a/web/app/components/rag-pipeline/index.tsx +++ b/web/app/components/rag-pipeline/index.tsx @@ -13,7 +13,9 @@ import { createRagPipelineSliceSlice } from './store' import RagPipelineMain from './components/rag-pipeline-main' import { usePipelineInit } from './hooks' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' -import Conversion from './conversion' +import Conversion from './components/conversion' +import type { Node } from '@/app/components/workflow/types' +import { processNodesWithoutDataSource } from './utils' const RagPipeline = () => { const { @@ -21,10 +23,11 @@ const RagPipeline = () => { isLoading, } = usePipelineInit() const nodesData = useMemo(() => { + let result: Node[] = [] if (data) - return initialNodes(data.graph.nodes, data.graph.edges) + result = initialNodes(data.graph.nodes, data.graph.edges) - return [] + return processNodesWithoutDataSource(result) }, [data]) const edgesData = useMemo(() => { if (data) diff --git a/web/app/components/rag-pipeline/utils/index.ts b/web/app/components/rag-pipeline/utils/index.ts new file mode 100644 index 0000000000..090c8d4d14 --- /dev/null +++ b/web/app/components/rag-pipeline/utils/index.ts @@ -0,0 +1 @@ +export * from './nodes' diff --git a/web/app/components/rag-pipeline/utils/nodes.ts b/web/app/components/rag-pipeline/utils/nodes.ts new file mode 100644 index 0000000000..f283267d00 --- /dev/null +++ b/web/app/components/rag-pipeline/utils/nodes.ts @@ -0,0 +1,77 @@ +import type { Node } from '@/app/components/workflow/types' +import { BlockEnum } from '@/app/components/workflow/types' +import { generateNewNode } from '@/app/components/workflow/utils' +import { CUSTOM_DATA_SOURCE_EMPTY_NODE } from '@/app/components/workflow/nodes/data-source-empty/constants' +import { CUSTOM_NOTE_NODE } from '@/app/components/workflow/note-node/constants' +import { NoteTheme } from '@/app/components/workflow/note-node/types' +import type { NoteNodeType } from '@/app/components/workflow/note-node/types' +import { CUSTOM_NODE } from '@/app/components/workflow/constants' + +export const processNodesWithoutDataSource = (nodes: Node[]) => { + if (!nodes || nodes.length === 0) return [] + + let leftNode + let hasNoteBySystem + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i] + if (node.type === CUSTOM_NOTE_NODE && node.data.noteBySystem) + hasNoteBySystem = true + + if (node.type !== CUSTOM_NODE) + continue + + if (node.data.type === BlockEnum.DataSource) + return nodes + + if (!leftNode) + leftNode = node + + if (node.position.x < leftNode.position.x) + leftNode = node + } + + if (leftNode) { + const { newNode } = generateNewNode({ + type: CUSTOM_DATA_SOURCE_EMPTY_NODE, + data: { + title: '', + desc: '', + type: BlockEnum.DataSourceEmpty, + width: 240, + }, + position: { + x: leftNode.position.x - 500, + y: leftNode.position.y, + }, + }) + let newNoteNode + if (!hasNoteBySystem) { + newNoteNode = generateNewNode({ + type: CUSTOM_NOTE_NODE, + data: { + title: '', + desc: '', + type: '' as any, + text: '{"root":{"children":[{"children":[{"detail":0,"format":1,"mode":"normal","style":"font-size: 14px;","text":"Get started with a blank pipeline","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":1,"textStyle":"font-size: 14px;"},{"children":[],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":1,"textStyle":""},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"A Knowledge Pipeline starts with Data Source as the starting node and ends with the knowledge base node. The general steps are: import documents from the data source → use extractor to extract document content → split and clean content into structured chunks → store in the knowledge base.","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""},{"children":[{"children":[{"detail":0,"format":2,"mode":"normal","style":"","text":"Link to documentation","type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"link","version":1,"textFormat":2,"rel":"noreferrer","target":null,"title":null,"url":"https://dify.ai"}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":2,"textStyle":""},{"children":[],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1,"textFormat":0,"textStyle":""}],"direction":"ltr","format":"","indent":0,"type":"root","version":1,"textFormat":1,"textStyle":"font-size: 14px;"}}', + theme: NoteTheme.blue, + author: '', + showAuthor: true, + width: 240, + height: 300, + noteBySystem: true, + } as NoteNodeType, + position: { + x: leftNode.position.x - 500, + y: leftNode.position.y + 100, + }, + }).newNode + } + return [ + newNode, + ...(newNoteNode ? [newNoteNode] : []), + ...nodes, + ] + } + + return nodes +} diff --git a/web/app/components/workflow/block-selector/data-sources.tsx b/web/app/components/workflow/block-selector/data-sources.tsx index ac3a9da041..6b2293afa5 100644 --- a/web/app/components/workflow/block-selector/data-sources.tsx +++ b/web/app/components/workflow/block-selector/data-sources.tsx @@ -2,6 +2,9 @@ import { useCallback, useRef, } from 'react' +import Link from 'next/link' +import { useTranslation } from 'react-i18next' +import { RiArrowRightUpLine } from '@remixicon/react' import { BlockEnum } from '../types' import type { OnSelectBlock, @@ -12,6 +15,8 @@ import Tools from './tools' import { ViewType } from './view-type-select' import cn from '@/utils/classnames' import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list' +import { getMarketplaceUrl } from '@/utils/var' +import { useGlobalPublicStore } from '@/context/global-public-context' type AllToolsProps = { className?: string @@ -28,6 +33,7 @@ const DataSources = ({ onSelect, dataSources, }: AllToolsProps) => { + const { t } = useTranslation() const pluginRef = useRef(null) const wrapElemRef = useRef(null) const handleSelect = useCallback((_: any, toolDefaultValue: ToolDefaultValue) => { @@ -40,6 +46,7 @@ const DataSources = ({ title: toolDefaultValue?.title, }) }, [onSelect]) + const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures) return (
@@ -56,6 +63,18 @@ const DataSources = ({ hasSearchText={!!searchText} canNotSelectMultiple /> + { + enable_marketplace && ( + + {t('plugin.findMoreInMarketplace')} + + + ) + }
) diff --git a/web/app/components/workflow/block-selector/hooks.ts b/web/app/components/workflow/block-selector/hooks.ts index 7645a24f50..b974922e6b 100644 --- a/web/app/components/workflow/block-selector/hooks.ts +++ b/web/app/components/workflow/block-selector/hooks.ts @@ -8,7 +8,7 @@ import { ToolTypeEnum, } from './types' -export const useTabs = (noBlocks?: boolean, noSources?: boolean) => { +export const useTabs = (noBlocks?: boolean, noSources?: boolean, noTools?: boolean) => { const { t } = useTranslation() const tabs = useMemo(() => { return [ @@ -32,18 +32,27 @@ export const useTabs = (noBlocks?: boolean, noSources?: boolean) => { }, ] ), - { - key: TabsEnum.Tools, - name: t('workflow.tabs.tools'), - }, + ...( + noTools + ? [] + : [ + { + key: TabsEnum.Tools, + name: t('workflow.tabs.tools'), + }, + ] + ), ] - }, [t, noBlocks, noSources]) + }, [t, noBlocks, noSources, noTools]) const initialTab = useMemo(() => { if (noBlocks) - return noSources ? TabsEnum.Tools : TabsEnum.Sources + return noTools ? TabsEnum.Sources : TabsEnum.Tools + + if (noTools) + return noBlocks ? TabsEnum.Sources : TabsEnum.Blocks return TabsEnum.Blocks - }, [noBlocks, noSources]) + }, [noBlocks, noSources, noTools]) const [activeTab, setActiveTab] = useState(initialTab) return { diff --git a/web/app/components/workflow/block-selector/index.tsx b/web/app/components/workflow/block-selector/index.tsx index 7329b63b82..54e8078e7b 100644 --- a/web/app/components/workflow/block-selector/index.tsx +++ b/web/app/components/workflow/block-selector/index.tsx @@ -30,6 +30,9 @@ const NodeSelectorWrapper = (props: NodeSelectorProps) => { if (block.metaData.type === BlockEnum.LoopStart) return false + if (block.metaData.type === BlockEnum.DataSourceEmpty) + return false + return true }) }, [availableNodesMetaData?.nodes]) diff --git a/web/app/components/workflow/block-selector/main.tsx b/web/app/components/workflow/block-selector/main.tsx index ef7b531084..4cef6c559b 100644 --- a/web/app/components/workflow/block-selector/main.tsx +++ b/web/app/components/workflow/block-selector/main.tsx @@ -50,6 +50,7 @@ export type NodeSelectorProps = { blocks?: NodeDefault[] dataSources?: ToolWithProvider[] noBlocks?: boolean + noTools?: boolean } const NodeSelector: FC = ({ open: openFromProps, @@ -68,6 +69,7 @@ const NodeSelector: FC = ({ blocks = [], dataSources = [], noBlocks = false, + noTools = false, }) => { const { t } = useTranslation() const [searchText, setSearchText] = useState('') @@ -98,7 +100,7 @@ const NodeSelector: FC = ({ activeTab, setActiveTab, tabs, - } = useTabs(!blocks.length, !dataSources.length) + } = useTabs(noBlocks, !dataSources.length, noTools) const handleActiveTabChange = useCallback((newActiveTab: TabsEnum) => { setActiveTab(newActiveTab) @@ -165,6 +167,17 @@ const NodeSelector: FC = ({ onClear={() => setSearchText('')} /> )} + {activeTab === TabsEnum.Sources && ( + setSearchText(e.target.value)} + onClear={() => setSearchText('')} + /> + )} {activeTab === TabsEnum.Tools && ( = ({ availableBlocksTypes={availableBlocksTypes} noBlocks={noBlocks} dataSources={dataSources} + noTools={noTools} /> diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index 048f550429..9be52ef3b0 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -28,6 +28,7 @@ export type TabsProps = { }> filterElem: React.ReactNode noBlocks?: boolean + noTools?: boolean } const Tabs: FC = ({ activeTab, @@ -41,6 +42,7 @@ const Tabs: FC = ({ tabs = [], filterElem, noBlocks, + noTools, }) => { const { data: buildInTools } = useAllBuiltInTools() const { data: customTools } = useAllCustomTools() @@ -96,7 +98,7 @@ const Tabs: FC = ({ ) } { - activeTab === TabsEnum.Tools && ( + activeTab === TabsEnum.Tools && !noTools && ( { return if (node.type === CUSTOM_LOOP_START_NODE) return + if (node.data.type === BlockEnum.DataSourceEmpty) + return handleNodeSelect(node.id) }, [handleNodeSelect]) @@ -1265,13 +1267,13 @@ export const useNodesInteractions = () => { if (nodeId) { // If nodeId is provided, copy that specific node const nodeToCopy = nodes.find(node => node.id === nodeId && node.data.type !== BlockEnum.Start - && node.type !== CUSTOM_ITERATION_START_NODE && node.type !== CUSTOM_LOOP_START_NODE && node.data.type !== BlockEnum.LoopEnd && node.data.type !== BlockEnum.KnowledgeBase) + && node.type !== CUSTOM_ITERATION_START_NODE && node.type !== CUSTOM_LOOP_START_NODE && node.data.type !== BlockEnum.LoopEnd && node.data.type !== BlockEnum.KnowledgeBase && node.data.type !== BlockEnum.DataSourceEmpty) if (nodeToCopy) setClipboardElements([nodeToCopy]) } else { // If no nodeId is provided, fall back to the current behavior - const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.DataSource && node.data.type !== BlockEnum.KnowledgeBase + const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.DataSource && node.data.type !== BlockEnum.KnowledgeBase && node.data.type !== BlockEnum.DataSourceEmpty && !node.data.isInIteration && !node.data.isInLoop) if (bundledNodes.length) { diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 3356188618..27c9919c40 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -57,6 +57,8 @@ import CustomLoopStartNode from './nodes/loop-start' import { CUSTOM_LOOP_START_NODE } from './nodes/loop-start/constants' import CustomSimpleNode from './simple-node' import { CUSTOM_SIMPLE_NODE } from './simple-node/constants' +import CustomDataSourceEmptyNode from './nodes/data-source-empty' +import { CUSTOM_DATA_SOURCE_EMPTY_NODE } from './nodes/data-source-empty/constants' import Operator from './operator' import Control from './operator/control' import CustomEdge from './custom-edge' @@ -94,6 +96,7 @@ const nodeTypes = { [CUSTOM_SIMPLE_NODE]: CustomSimpleNode, [CUSTOM_ITERATION_START_NODE]: CustomIterationStartNode, [CUSTOM_LOOP_START_NODE]: CustomLoopStartNode, + [CUSTOM_DATA_SOURCE_EMPTY_NODE]: CustomDataSourceEmptyNode, } const edgeTypes = { [CUSTOM_EDGE]: CustomEdge, @@ -190,7 +193,6 @@ export const Workflow: FC = memo(({ return () => { handleSyncWorkflowDraft(true, true) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft() @@ -282,7 +284,6 @@ export const Workflow: FC = memo(({ const { fetchInspectVars } = useSetWorkflowVarsWithValue() useEffect(() => { fetchInspectVars() - // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const store = useStoreApi() diff --git a/web/app/components/workflow/nodes/data-source-empty/constants.ts b/web/app/components/workflow/nodes/data-source-empty/constants.ts new file mode 100644 index 0000000000..93bac44856 --- /dev/null +++ b/web/app/components/workflow/nodes/data-source-empty/constants.ts @@ -0,0 +1 @@ +export const CUSTOM_DATA_SOURCE_EMPTY_NODE = 'custom-data-source-empty' diff --git a/web/app/components/workflow/nodes/data-source-empty/default.ts b/web/app/components/workflow/nodes/data-source-empty/default.ts new file mode 100644 index 0000000000..df7c5e853e --- /dev/null +++ b/web/app/components/workflow/nodes/data-source-empty/default.ts @@ -0,0 +1,20 @@ +import type { NodeDefault } from '../../types' +import type { DataSourceEmptyNodeType } from './types' +import { genNodeMetaData } from '@/app/components/workflow/utils' +import { BlockEnum } from '@/app/components/workflow/types' + +const metaData = genNodeMetaData({ + sort: -1, + type: BlockEnum.DataSourceEmpty, +}) +const nodeDefault: NodeDefault = { + metaData, + defaultValue: {}, + checkValid() { + return { + isValid: true, + } + }, +} + +export default nodeDefault diff --git a/web/app/components/workflow/nodes/data-source-empty/hooks.ts b/web/app/components/workflow/nodes/data-source-empty/hooks.ts new file mode 100644 index 0000000000..71ad4e857d --- /dev/null +++ b/web/app/components/workflow/nodes/data-source-empty/hooks.ts @@ -0,0 +1,47 @@ +import { useCallback } from 'react' +import { useStoreApi } from 'reactflow' +import { produce } from 'immer' +import type { OnSelectBlock } from '@/app/components/workflow/types' +import { generateNewNode } from '@/app/components/workflow/utils' +import { useNodesMetaData } from '@/app/components/workflow/hooks' + +export const useReplaceDataSourceNode = (id: string) => { + const store = useStoreApi() + const { nodesMap: nodesMetaDataMap } = useNodesMetaData() + + const handleReplaceNode = useCallback(( + type, + toolDefaultValue, + ) => { + const { + getNodes, + setNodes, + } = store.getState() + const nodes = getNodes() + const emptyNodeIndex = nodes.findIndex(node => node.id === id) + + if (emptyNodeIndex < 0) return + const { + defaultValue, + } = nodesMetaDataMap![type] + const emptyNode = nodes[emptyNodeIndex] + const { newNode } = generateNewNode({ + data: { + ...(defaultValue as any), + ...(toolDefaultValue || {}), + }, + position: { + x: emptyNode.position.x, + y: emptyNode.position.y, + }, + }) + const newNodes = produce(nodes, (draft) => { + draft[emptyNodeIndex] = newNode + }) + setNodes(newNodes) + }, []) + + return { + handleReplaceNode, + } +} diff --git a/web/app/components/workflow/nodes/data-source-empty/index.tsx b/web/app/components/workflow/nodes/data-source-empty/index.tsx new file mode 100644 index 0000000000..7c556160d7 --- /dev/null +++ b/web/app/components/workflow/nodes/data-source-empty/index.tsx @@ -0,0 +1,70 @@ +import { + memo, + useCallback, +} from 'react' +import { useTranslation } from 'react-i18next' +import type { NodeProps } from 'reactflow' +import { RiAddLine } from '@remixicon/react' +import cn from '@/utils/classnames' +import Button from '@/app/components/base/button' +import BlockSelector from '@/app/components/workflow/block-selector' +import { useReplaceDataSourceNode } from './hooks' + +const DataSourceEmptyNode = ({ id }: NodeProps) => { + const { t } = useTranslation() + const { handleReplaceNode } = useReplaceDataSourceNode(id) + + const renderTrigger = useCallback(() => { + return ( + + ) + }, []) + + return ( +
+
+
+ {t('workflow.blocks.datasource')} +
+
+
+
+ +
+
+
+ ) +} + +export default memo(DataSourceEmptyNode) diff --git a/web/app/components/workflow/nodes/data-source-empty/types.ts b/web/app/components/workflow/nodes/data-source-empty/types.ts new file mode 100644 index 0000000000..f9668f931e --- /dev/null +++ b/web/app/components/workflow/nodes/data-source-empty/types.ts @@ -0,0 +1,3 @@ +import type { CommonNodeType } from '@/app/components/workflow/types' + +export type DataSourceEmptyNodeType = CommonNodeType diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index a6c3ec9fdc..d8fd7f3981 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -47,6 +47,7 @@ export enum BlockEnum { LoopStart = 'loop-start', LoopEnd = 'loop-end', DataSource = 'datasource', + DataSourceEmpty = 'datasource-empty', KnowledgeBase = 'knowledge-index', } @@ -84,6 +85,7 @@ export type CommonNodeType = { _waitingRun?: boolean _retryIndex?: number _dataSourceStartToAdd?: boolean + noteBySystem?: boolean isInIteration?: boolean iteration_id?: string selected?: boolean diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index 435978086c..1a963396bf 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -913,6 +913,7 @@ const translation = { dataSource: { supportedFileFormats: 'Supported file formats', supportedFileFormatsPlaceholder: 'File extension, e.g. doc', + add: 'Add data source', }, knowledgeBase: { chunkStructure: 'Chunk Structure', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 1b6955066b..19c8890fd8 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -927,6 +927,7 @@ const translation = { dataSource: { supportedFileFormats: '支持的文件格式', supportedFileFormatsPlaceholder: '文件格式,例如:doc', + add: '添加数据源', }, knowledgeBase: { chunkStructure: '分段结构',