From 274e7f4f094b499af08a22aa30ed8bbe4d0b67b2 Mon Sep 17 00:00:00 2001 From: twwu Date: Thu, 11 Sep 2025 16:02:06 +0800 Subject: [PATCH 1/3] refactor(workflow): streamline node metadata structure and enhance filtering logic --- .../hooks/use-available-nodes-meta-data.ts | 21 +------ .../hooks/use-available-nodes-meta-data.ts | 30 +--------- .../workflow/hooks/use-nodes-interactions.ts | 17 +++++- .../panel-operator/panel-operator-popup.tsx | 59 ++++++++++--------- .../workflow/nodes/answer/default.ts | 1 + .../nodes/data-source-empty/default.ts | 2 + .../workflow/nodes/data-source/default.ts | 2 + .../components/workflow/nodes/end/default.ts | 1 + .../workflow/nodes/iteration/default.ts | 1 + .../workflow/nodes/knowledge-base/default.ts | 4 ++ .../workflow/nodes/loop-end/default.ts | 1 + .../components/workflow/nodes/loop/default.ts | 1 + .../workflow/nodes/start/default.ts | 5 ++ web/app/components/workflow/types.ts | 2 + .../workflow/utils/gen-node-meta-data.ts | 15 +++++ 15 files changed, 86 insertions(+), 76 deletions(-) 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 3dad34f441..6ce4524c8e 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 @@ -19,26 +19,9 @@ export const useAvailableNodesMetaData = () => { ...dataSourceDefault.defaultValue, _dataSourceStartToAdd: true, }, - metaData: { - ...dataSourceDefault.metaData, - isStart: true, - isRequired: true, - }, - }, - { - ...knowledgeBaseDefault, - metaData: { - ...knowledgeBaseDefault.metaData, - isRequired: true, - }, - }, - { - ...dataSourceEmptyDefault, - metaData: { - ...dataSourceEmptyDefault.metaData, - isUndeletable: true, - }, }, + knowledgeBaseDefault, + dataSourceEmptyDefault, ], []) const prefixLink = useMemo(() => { diff --git a/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts index 983d598573..f590c1a8ce 100644 --- a/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts +++ b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts @@ -15,35 +15,11 @@ export const useAvailableNodesMetaData = () => { const mergedNodesMetaData = useMemo(() => [ ...WORKFLOW_COMMON_NODES, - { - ...StartDefault, - metaData: { - ...StartDefault.metaData, - isStart: true, - isRequired: true, - isUndeletable: true, - }, - }, + StartDefault, ...( isChatMode - ? [ - { - ...AnswerDefault, - metaData: { - ...AnswerDefault.metaData, - isRequired: true, - }, - }, - ] - : [ - { - ...EndDefault, - metaData: { - ...EndDefault.metaData, - isRequired: true, - }, - }, - ] + ? [AnswerDefault] + : [EndDefault] ), ], [isChatMode]) diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index 3f1fd56df1..023f3a7470 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -1256,15 +1256,26 @@ export const useNodesInteractions = () => { } 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 && node.data.type !== BlockEnum.DataSourceEmpty - && !node.data.isInIteration && !node.data.isInLoop) + const bundledNodes = nodes.filter((node) => { + if (!node.data._isBundled) + return false + const { metaData } = nodesMetaDataMap![node.data.type as BlockEnum] + if (metaData.isSingleton) + return false + return !node.data.isInIteration && !node.data.isInLoop + }) if (bundledNodes.length) { setClipboardElements(bundledNodes) return } - const selectedNode = nodes.find(node => node.data.selected && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.LoopEnd && node.data.type !== BlockEnum.DataSource) + const selectedNode = nodes.find((node) => { + if (!node.data.selected) + return false + const { metaData } = nodesMetaDataMap![node.data.type as BlockEnum] + return !metaData.isSingleton + }) if (selectedNode) setClipboardElements([selectedNode]) diff --git a/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx b/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx index 75abdd6d12..a871e60e3a 100644 --- a/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx +++ b/web/app/components/workflow/nodes/_base/components/panel-operator/panel-operator-popup.tsx @@ -16,7 +16,6 @@ import { } from '@/app/components/workflow/hooks' import ShortcutsName from '@/app/components/workflow/shortcuts-name' import type { Node } from '@/app/components/workflow/types' -import { BlockEnum } from '@/app/components/workflow/types' type PanelOperatorPopupProps = { id: string @@ -43,7 +42,7 @@ const PanelOperatorPopup = ({ const { nodesReadOnly } = useNodesReadOnly() const edge = edges.find(edge => edge.target === id) const nodeMetaData = useNodeMetaData({ id, data } as Node) - const showChangeBlock = data.type !== BlockEnum.Start && !nodesReadOnly && data.type !== BlockEnum.Iteration && data.type !== BlockEnum.Loop + const showChangeBlock = !nodeMetaData.isTypeFixed && !nodesReadOnly const isChildNode = !!(data.isInIteration || data.isInLoop) return ( @@ -85,31 +84,37 @@ const PanelOperatorPopup = ({ ) } { - data.type !== BlockEnum.Start && !nodesReadOnly && ( + !nodesReadOnly && ( <> -
-
{ - onClosePopup() - handleNodesCopy(id) - }} - > - {t('workflow.common.copy')} - -
-
{ - onClosePopup() - handleNodesDuplicate(id) - }} - > - {t('workflow.common.duplicate')} - -
-
-
+ { + !nodeMetaData.isSingleton && ( + <> +
+
{ + onClosePopup() + handleNodesCopy(id) + }} + > + {t('workflow.common.copy')} + +
+
{ + onClosePopup() + handleNodesDuplicate(id) + }} + > + {t('workflow.common.duplicate')} + +
+
+
+ + ) + } { !nodeMetaData.isUndeletable && ( <> @@ -117,7 +122,7 @@ const PanelOperatorPopup = ({
handleNodeDelete(id)} > diff --git a/web/app/components/workflow/nodes/answer/default.ts b/web/app/components/workflow/nodes/answer/default.ts index d3cb2c6741..f115274900 100644 --- a/web/app/components/workflow/nodes/answer/default.ts +++ b/web/app/components/workflow/nodes/answer/default.ts @@ -6,6 +6,7 @@ import { BlockEnum } from '@/app/components/workflow/types' const metaData = genNodeMetaData({ sort: 2.1, type: BlockEnum.Answer, + isRequired: true, }) const nodeDefault: NodeDefault = { metaData, diff --git a/web/app/components/workflow/nodes/data-source-empty/default.ts b/web/app/components/workflow/nodes/data-source-empty/default.ts index df7c5e853e..69d9904217 100644 --- a/web/app/components/workflow/nodes/data-source-empty/default.ts +++ b/web/app/components/workflow/nodes/data-source-empty/default.ts @@ -6,6 +6,8 @@ import { BlockEnum } from '@/app/components/workflow/types' const metaData = genNodeMetaData({ sort: -1, type: BlockEnum.DataSourceEmpty, + isUndeletable: true, + isSingleton: true, }) const nodeDefault: NodeDefault = { metaData, diff --git a/web/app/components/workflow/nodes/data-source/default.ts b/web/app/components/workflow/nodes/data-source/default.ts index f5ebb27655..82e69679a2 100644 --- a/web/app/components/workflow/nodes/data-source/default.ts +++ b/web/app/components/workflow/nodes/data-source/default.ts @@ -15,6 +15,8 @@ const i18nPrefix = 'workflow.errorMsg' const metaData = genNodeMetaData({ sort: -1, type: BlockEnum.DataSource, + isStart: true, + isRequired: true, }) const nodeDefault: NodeDefault = { metaData, diff --git a/web/app/components/workflow/nodes/end/default.ts b/web/app/components/workflow/nodes/end/default.ts index 32e4f1051d..cadb580c34 100644 --- a/web/app/components/workflow/nodes/end/default.ts +++ b/web/app/components/workflow/nodes/end/default.ts @@ -6,6 +6,7 @@ import { BlockEnum } from '@/app/components/workflow/types' const metaData = genNodeMetaData({ sort: 2.1, type: BlockEnum.End, + isRequired: true, }) const nodeDefault: NodeDefault = { metaData, diff --git a/web/app/components/workflow/nodes/iteration/default.ts b/web/app/components/workflow/nodes/iteration/default.ts index 3c348a4fe0..450379ec6b 100644 --- a/web/app/components/workflow/nodes/iteration/default.ts +++ b/web/app/components/workflow/nodes/iteration/default.ts @@ -9,6 +9,7 @@ const metaData = genNodeMetaData({ classification: BlockClassificationEnum.Logic, sort: 2, type: BlockEnum.Iteration, + isTypeFixed: true, }) const nodeDefault: NodeDefault = { metaData, diff --git a/web/app/components/workflow/nodes/knowledge-base/default.ts b/web/app/components/workflow/nodes/knowledge-base/default.ts index e43c08778f..8175e2ac9e 100644 --- a/web/app/components/workflow/nodes/knowledge-base/default.ts +++ b/web/app/components/workflow/nodes/knowledge-base/default.ts @@ -6,6 +6,10 @@ import { BlockEnum } from '@/app/components/workflow/types' const metaData = genNodeMetaData({ sort: 3.1, type: BlockEnum.KnowledgeBase, + isRequired: true, + isUndeletable: true, + isSingleton: true, + isTypeFixed: true, }) const nodeDefault: NodeDefault = { metaData, diff --git a/web/app/components/workflow/nodes/loop-end/default.ts b/web/app/components/workflow/nodes/loop-end/default.ts index c02d007cde..bb46ff1166 100644 --- a/web/app/components/workflow/nodes/loop-end/default.ts +++ b/web/app/components/workflow/nodes/loop-end/default.ts @@ -10,6 +10,7 @@ const metaData = genNodeMetaData({ classification: BlockClassificationEnum.Logic, sort: 2, type: BlockEnum.LoopEnd, + isSingleton: true, }) const nodeDefault: NodeDefault = { metaData, diff --git a/web/app/components/workflow/nodes/loop/default.ts b/web/app/components/workflow/nodes/loop/default.ts index 0a80763001..390545d0e2 100644 --- a/web/app/components/workflow/nodes/loop/default.ts +++ b/web/app/components/workflow/nodes/loop/default.ts @@ -14,6 +14,7 @@ const metaData = genNodeMetaData({ sort: 3, type: BlockEnum.Loop, author: 'AICT-Team', + isTypeFixed: true, }) const nodeDefault: NodeDefault = { metaData, diff --git a/web/app/components/workflow/nodes/start/default.ts b/web/app/components/workflow/nodes/start/default.ts index 8337c2275f..3b98b57a73 100644 --- a/web/app/components/workflow/nodes/start/default.ts +++ b/web/app/components/workflow/nodes/start/default.ts @@ -6,6 +6,11 @@ import { BlockEnum } from '@/app/components/workflow/types' const metaData = genNodeMetaData({ sort: 0.1, type: BlockEnum.Start, + isStart: true, + isRequired: true, + isUndeletable: true, + isSingleton: true, + isTypeFixed: true, }) const nodeDefault: NodeDefault = { metaData, diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index bfae195a0e..f5ff7d5ce8 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -329,6 +329,8 @@ export type NodeDefault = { isRequired?: boolean isUndeletable?: boolean isStart?: boolean + isSingleton?: boolean + isTypeFixed?: boolean } defaultValue: Partial defaultRunInputData?: Record diff --git a/web/app/components/workflow/utils/gen-node-meta-data.ts b/web/app/components/workflow/utils/gen-node-meta-data.ts index e7271cfcc4..9c4e9cabca 100644 --- a/web/app/components/workflow/utils/gen-node-meta-data.ts +++ b/web/app/components/workflow/utils/gen-node-meta-data.ts @@ -8,6 +8,11 @@ export type GenNodeMetaDataParams = { title?: string author?: string helpLinkUri?: string + isRequired?: boolean + isUndeletable?: boolean + isStart?: boolean + isSingleton?: boolean + isTypeFixed?: boolean } export const genNodeMetaData = ({ classification = BlockClassificationEnum.Default, @@ -16,6 +21,11 @@ export const genNodeMetaData = ({ title = '', author = 'Dify', helpLinkUri, + isRequired = false, + isUndeletable = false, + isStart = false, + isSingleton = false, + isTypeFixed = false, }: GenNodeMetaDataParams) => { return { classification, @@ -24,5 +34,10 @@ export const genNodeMetaData = ({ title, author, helpLinkUri: helpLinkUri || type, + isRequired, + isUndeletable, + isStart, + isSingleton, + isTypeFixed, } } From ac41151571ad1dfb29d67d0375c415f02b857896 Mon Sep 17 00:00:00 2001 From: QuantumGhost Date: Fri, 5 Sep 2025 16:17:47 +0800 Subject: [PATCH 2/3] chore(api): remove unused installed_plugins.jsonl --- api/installed_plugins.jsonl | 1 - 1 file changed, 1 deletion(-) delete mode 100644 api/installed_plugins.jsonl diff --git a/api/installed_plugins.jsonl b/api/installed_plugins.jsonl deleted file mode 100644 index 463e24ae64..0000000000 --- a/api/installed_plugins.jsonl +++ /dev/null @@ -1 +0,0 @@ -{"not_installed": [], "plugin_install_failed": []} \ No newline at end of file From 32a1a61d65a1eec65a6b4f6ebaf0659107dd3c7c Mon Sep 17 00:00:00 2001 From: QuantumGhost Date: Fri, 12 Sep 2025 17:07:26 +0800 Subject: [PATCH 3/3] security(api): enforce privilege validation for dataset-to-pipeline transformation The transformation from classic dataset to knowledge pipeline represents an irreversible write operation that permanently alters the dataset structure. To prevent unauthorized modifications, this change implements strict privilege validation in `RagPipelineTransformApi`. Only users with editor privileges or dataset operator roles are authorized to execute this transformation, ensuring proper access control for this critical operation. --- .../console/datasets/rag_pipeline/rag_pipeline_workflow.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py b/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py index 964de0a863..c70343ec95 100644 --- a/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py +++ b/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py @@ -950,6 +950,12 @@ class RagPipelineTransformApi(Resource): @login_required @account_initialization_required def post(self, dataset_id): + if not isinstance(current_user, Account): + raise Forbidden() + + if not (current_user.is_editor or current_user.is_dataset_operator): + raise Forbidden() + dataset_id = str(dataset_id) rag_pipeline_transform_service = RagPipelineTransformService() result = rag_pipeline_transform_service.transform_dataset(dataset_id)