From 641a5daeead355b62d6165745fcdd5b351aa4523 Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Thu, 31 Oct 2024 16:56:08 +0100 Subject: [PATCH 1/5] Modify ChatTrigger additions --- .../NodeCreator/__tests__/useActions.test.ts | 100 ++++++++++++++++++ .../NodeCreator/composables/useActions.ts | 23 ++-- 2 files changed, 115 insertions(+), 8 deletions(-) diff --git a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/useActions.test.ts b/packages/editor-ui/src/components/Node/NodeCreator/__tests__/useActions.test.ts index 14a03254e1..80878f0e96 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/useActions.test.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/__tests__/useActions.test.ts @@ -5,6 +5,8 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useActions } from '../composables/useActions'; import { + AGENT_NODE_TYPE, + GITHUB_TRIGGER_NODE_TYPE, HTTP_REQUEST_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE, NODE_CREATOR_OPEN_SOURCES, @@ -15,6 +17,7 @@ import { TRIGGER_NODE_CREATOR_VIEW, WEBHOOK_NODE_TYPE, } from '@/constants'; +import { CHAT_TRIGGER_NODE_TYPE } from 'n8n-workflow'; describe('useActions', () => { beforeAll(() => { @@ -54,6 +57,9 @@ describe('useActions', () => { vi.spyOn(workflowsStore, 'workflowTriggerNodes', 'get').mockReturnValue([ { type: SCHEDULE_TRIGGER_NODE_TYPE } as never, ]); + vi.spyOn(workflowsStore, 'getNodeTypes').mockReturnValue({ + getByNameAndVersion: () => ({ description: { group: ['trigger'] } }), + } as never); vi.spyOn(nodeCreatorStore, 'openSource', 'get').mockReturnValue( NODE_CREATOR_OPEN_SOURCES.ADD_NODE_BUTTON, ); @@ -67,6 +73,100 @@ describe('useActions', () => { }); }); + test('should insert a ChatTrigger node when an AI Agent is added without trigger', () => { + const workflowsStore = useWorkflowsStore(); + + vi.spyOn(workflowsStore, 'workflowTriggerNodes', 'get').mockReturnValue([]); + + const { getAddedNodesAndConnections } = useActions(); + + expect(getAddedNodesAndConnections([{ type: AGENT_NODE_TYPE }])).toEqual({ + connections: [ + { + from: { + nodeIndex: 0, + }, + to: { + nodeIndex: 1, + }, + }, + ], + nodes: [ + { type: CHAT_TRIGGER_NODE_TYPE, isAutoAdd: true }, + { type: AGENT_NODE_TYPE, openDetail: true }, + ], + }); + }); + + test('should insert a ChatTrigger node when an AI Agent is added with only a Manual Trigger', () => { + const workflowsStore = useWorkflowsStore(); + + vi.spyOn(workflowsStore, 'workflowTriggerNodes', 'get').mockReturnValue([ + { type: MANUAL_TRIGGER_NODE_TYPE } as never, + ]); + vi.spyOn(workflowsStore, 'getNodeTypes').mockReturnValue({ + getByNameAndVersion: () => ({ description: { group: ['trigger'] } }), + } as never); + + const { getAddedNodesAndConnections } = useActions(); + + expect(getAddedNodesAndConnections([{ type: AGENT_NODE_TYPE }])).toEqual({ + connections: [ + { + from: { + nodeIndex: 0, + }, + to: { + nodeIndex: 1, + }, + }, + ], + nodes: [ + { type: CHAT_TRIGGER_NODE_TYPE, isAutoAdd: true }, + { type: AGENT_NODE_TYPE, openDetail: true }, + ], + }); + }); + + test('should not insert a ChatTrigger node when an AI Agent is added with a trigger already present', () => { + const workflowsStore = useWorkflowsStore(); + + vi.spyOn(workflowsStore, 'allNodes', 'get').mockReturnValue([ + { type: GITHUB_TRIGGER_NODE_TYPE } as never, + ]); + vi.spyOn(workflowsStore, 'getNodeTypes').mockReturnValue({ + getByNameAndVersion: () => ({ description: { group: ['trigger'] } }), + } as never); + + const { getAddedNodesAndConnections } = useActions(); + + expect(getAddedNodesAndConnections([{ type: AGENT_NODE_TYPE }])).toEqual({ + connections: [], + nodes: [{ type: AGENT_NODE_TYPE, openDetail: true }], + }); + }); + + test('should not insert a ChatTrigger node when an AI Agent is added with a Chat Trigger already present', () => { + const workflowsStore = useWorkflowsStore(); + + vi.spyOn(workflowsStore, 'workflowTriggerNodes', 'get').mockReturnValue([ + { type: CHAT_TRIGGER_NODE_TYPE } as never, + ]); + vi.spyOn(workflowsStore, 'allNodes', 'get').mockReturnValue([ + { type: CHAT_TRIGGER_NODE_TYPE } as never, + ]); + vi.spyOn(workflowsStore, 'getNodeTypes').mockReturnValue({ + getByNameAndVersion: () => ({ description: { group: ['trigger'] } }), + } as never); + + const { getAddedNodesAndConnections } = useActions(); + + expect(getAddedNodesAndConnections([{ type: AGENT_NODE_TYPE }])).toEqual({ + connections: [], + nodes: [{ type: AGENT_NODE_TYPE, openDetail: true }], + }); + }); + test('should insert a No Op node when a Loop Over Items Node is added', () => { const workflowsStore = useWorkflowsStore(); const nodeCreatorStore = useNodeCreatorStore(); diff --git a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts index e9f7917385..b83dccd705 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts @@ -19,7 +19,6 @@ import { AI_CATEGORY_LANGUAGE_MODELS, BASIC_CHAIN_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE, - MANUAL_CHAT_TRIGGER_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE, NODE_CREATOR_OPEN_SOURCES, NO_OP_NODE_TYPE, @@ -204,8 +203,6 @@ export const useActions = () => { ); } function shouldPrependChatTrigger(addedNodes: AddedNode[]): boolean { - const { allNodes } = useWorkflowsStore(); - const COMPATIBLE_CHAT_NODES = [ QA_CHAIN_NODE_TYPE, AGENT_NODE_TYPE, @@ -214,13 +211,23 @@ export const useActions = () => { OPEN_AI_NODE_MESSAGE_ASSISTANT_TYPE, ]; - const isChatTriggerMissing = - allNodes.find((node) => - [MANUAL_CHAT_TRIGGER_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE].includes(node.type), - ) === undefined; const isCompatibleNode = addedNodes.some((node) => COMPATIBLE_CHAT_NODES.includes(node.type)); - return isCompatibleNode && isChatTriggerMissing; + if (!isCompatibleNode) return false; + + const { allNodes, getNodeTypes } = useWorkflowsStore(); + const { getByNameAndVersion } = getNodeTypes(); + + // We want to add a trigger if there are no triggers other than Manual Triggers + const shouldAddChatTrigger = allNodes.every((node) => { + const nodeType = getByNameAndVersion(node.type, node.typeVersion); + + return ( + !nodeType.description.group.includes('trigger') || node.type === MANUAL_TRIGGER_NODE_TYPE + ); + }); + + return shouldAddChatTrigger; } // AI-226: Prepend LLM Chain node when adding a language model From e2d4a6136cc90a0fac966dc11a4ba27ce50418db Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Mon, 4 Nov 2024 10:25:21 +0100 Subject: [PATCH 2/5] wip commit --- .../Node/NodeCreator/composables/useActions.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts index 1058ed2950..8e7e778f9d 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts @@ -211,6 +211,15 @@ export const useActions = () => { OPEN_AI_NODE_MESSAGE_ASSISTANT_TYPE, ]; + const node1 = addedNodes[0]; + if (node1 !== undefined && node1.type === AGENT_NODE_TYPE) { + console.log(node1); + setAddedNodeActionParameters({ + name: 'x', + key: 'x', + value: 'x', + }); + } const isCompatibleNode = addedNodes.some((node) => COMPATIBLE_CHAT_NODES.includes(node.type)); if (!isCompatibleNode) return false; From 9de3255465ff4fd31b99c593e62b3608e5d81c6c Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Thu, 7 Nov 2024 16:36:46 +0100 Subject: [PATCH 3/5] cleanup --- .../Node/NodeCreator/composables/useActions.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts index 8e7e778f9d..1058ed2950 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts @@ -211,15 +211,6 @@ export const useActions = () => { OPEN_AI_NODE_MESSAGE_ASSISTANT_TYPE, ]; - const node1 = addedNodes[0]; - if (node1 !== undefined && node1.type === AGENT_NODE_TYPE) { - console.log(node1); - setAddedNodeActionParameters({ - name: 'x', - key: 'x', - value: 'x', - }); - } const isCompatibleNode = addedNodes.some((node) => COMPATIBLE_CHAT_NODES.includes(node.type)); if (!isCompatibleNode) return false; From 261bd3237896570b4fbb39b2f223f834212575e9 Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Tue, 12 Nov 2024 14:39:00 +0100 Subject: [PATCH 4/5] Add e2e test --- cypress/e2e/30-langchain.cy.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cypress/e2e/30-langchain.cy.ts b/cypress/e2e/30-langchain.cy.ts index fb453816f6..43f6e0453c 100644 --- a/cypress/e2e/30-langchain.cy.ts +++ b/cypress/e2e/30-langchain.cy.ts @@ -359,6 +359,14 @@ describe('Langchain Integration', () => { getConnectionBySourceAndTarget(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME).should('exist'); getNodes().should('have.length', 3); }); + it('should not auto-add nodes if ChatTrigger is already present', () => { + addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true); + addNodeToCanvas(AGENT_NODE_NAME, true); + + addNodeToCanvas(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, true); + getConnectionBySourceAndTarget(CHAT_TRIGGER_NODE_DISPLAY_NAME, AGENT_NODE_NAME).should('exist'); + getNodes().should('have.length', 3); + }); it('should render runItems for sub-nodes and allow switching between them', () => { const workflowPage = new WorkflowPage(); const ndv = new NDV(); From ffcd8aa1334438ef5bbc23e8f87cb9ce3c562ecd Mon Sep 17 00:00:00 2001 From: Charlie Kolb Date: Tue, 12 Nov 2024 15:55:42 +0100 Subject: [PATCH 5/5] Add comment --- .../src/components/Node/NodeCreator/composables/useActions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts index 1058ed2950..92a5563d80 100644 --- a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts +++ b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActions.ts @@ -219,6 +219,8 @@ export const useActions = () => { const { getByNameAndVersion } = getNodeTypes(); // We want to add a trigger if there are no triggers other than Manual Triggers + // Performance here should be fine as `getByNameAndVersion` fetches nodeTypes once in bulk + // and `every` aborts on first `false` const shouldAddChatTrigger = allNodes.every((node) => { const nodeType = getByNameAndVersion(node.type, node.typeVersion);