feat(editor): Restrict when a ChatTrigger Node is added automatically (#11523)

This commit is contained in:
Charlie Kolb 2024-11-13 11:00:02 +01:00 committed by GitHub
parent 3e0c6cb3d2
commit 93a6f858fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 125 additions and 8 deletions

View file

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

View file

@ -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,25 @@ 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
// 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);
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

View file

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