diff --git a/cypress/constants.ts b/cypress/constants.ts
index 551c244102..39c755738c 100644
--- a/cypress/constants.ts
+++ b/cypress/constants.ts
@@ -35,7 +35,7 @@ export const INSTANCE_MEMBERS = [
];
export const MANUAL_TRIGGER_NODE_NAME = 'Manual Trigger';
-export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking "Test workflow"';
+export const MANUAL_TRIGGER_NODE_DISPLAY_NAME = 'When clicking ‘Test workflow’';
export const MANUAL_CHAT_TRIGGER_NODE_NAME = 'Chat Trigger';
export const SCHEDULE_TRIGGER_NODE_NAME = 'Schedule Trigger';
export const CODE_NODE_NAME = 'Code';
diff --git a/cypress/e2e/19-execution.cy.ts b/cypress/e2e/19-execution.cy.ts
index 98c0909b4d..84b71e0885 100644
--- a/cypress/e2e/19-execution.cy.ts
+++ b/cypress/e2e/19-execution.cy.ts
@@ -510,7 +510,7 @@ describe('Execution', () => {
cy.wait('@workflowRun').then((interception) => {
expect(interception.request.body).to.have.property('runData').that.is.an('object');
- const expectedKeys = ['When clicking "Test workflow"', 'fetch 5 random users'];
+ const expectedKeys = ['When clicking ‘Test workflow’', 'fetch 5 random users'];
expect(Object.keys(interception.request.body.runData)).to.have.lengthOf(expectedKeys.length);
expect(interception.request.body.runData).to.include.all.keys(expectedKeys);
diff --git a/cypress/e2e/4-node-creator.cy.ts b/cypress/e2e/4-node-creator.cy.ts
index 6955c95463..bb47ef4765 100644
--- a/cypress/e2e/4-node-creator.cy.ts
+++ b/cypress/e2e/4-node-creator.cy.ts
@@ -35,7 +35,7 @@ describe('Node Creator', () => {
nodeCreatorFeature.actions.openNodeCreator();
nodeCreatorFeature.getters.searchBar().find('input').type('manual');
- nodeCreatorFeature.getters.creatorItem().should('have.length', 2);
+ nodeCreatorFeature.getters.creatorItem().should('have.length', 1);
nodeCreatorFeature.getters.searchBar().find('input').clear().type('manual123');
nodeCreatorFeature.getters.creatorItem().should('have.length', 0);
nodeCreatorFeature.getters
@@ -159,7 +159,7 @@ describe('Node Creator', () => {
it('should have "Triggers" section collapsed when opening actions view from Regular root view', () => {
nodeCreatorFeature.actions.openNodeCreator();
- nodeCreatorFeature.getters.getCreatorItem('Manually').click();
+ nodeCreatorFeature.getters.getCreatorItem('Trigger manually').click();
nodeCreatorFeature.actions.openNodeCreator();
nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n');
@@ -308,7 +308,7 @@ describe('Node Creator', () => {
nodeCreatorFeature.getters.getCategoryItem('Actions').click();
nodeCreatorFeature.getters.getCreatorItem('Create a credential').click();
NDVModal.actions.close();
- WorkflowPage.actions.deleteNode('When clicking "Test workflow"');
+ WorkflowPage.actions.deleteNode('When clicking ‘Test workflow’');
WorkflowPage.getters.canvasNodePlusEndpointByName('n8n').click();
nodeCreatorFeature.getters.searchBar().find('input').clear().type('n8n');
nodeCreatorFeature.getters.getCreatorItem('n8n').click();
diff --git a/cypress/e2e/41-editors.cy.ts b/cypress/e2e/41-editors.cy.ts
index b50aa66872..0c44c51185 100644
--- a/cypress/e2e/41-editors.cy.ts
+++ b/cypress/e2e/41-editors.cy.ts
@@ -38,7 +38,7 @@ describe('Editors', () => {
});
ndv.actions.close();
- workflowPage.actions.openNode('When clicking "Test workflow"');
+ workflowPage.actions.openNode('When clicking ‘Test workflow’');
ndv.actions.setPinnedData([{ table: 'test_table' }]);
ndv.actions.close();
diff --git a/cypress/e2e/5-ndv.cy.ts b/cypress/e2e/5-ndv.cy.ts
index c322def1b8..0ebd859174 100644
--- a/cypress/e2e/5-ndv.cy.ts
+++ b/cypress/e2e/5-ndv.cy.ts
@@ -652,7 +652,7 @@ describe('NDV', () => {
ndv.getters.backToCanvas().click();
workflowPage.actions.executeWorkflow();
// Manual tigger node should show success indicator
- workflowPage.actions.openNode('When clicking "Test workflow"');
+ workflowPage.actions.openNode('When clicking ‘Test workflow’');
ndv.getters.nodeRunSuccessIndicator().should('exist');
// Code node should show error
ndv.getters.backToCanvas().click();
diff --git a/cypress/fixtures/Floating_Nodes.json b/cypress/fixtures/Floating_Nodes.json
index 2ffc1b3fde..6624c53ac6 100644
--- a/cypress/fixtures/Floating_Nodes.json
+++ b/cypress/fixtures/Floating_Nodes.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "d0eda550-2526-42a1-aa19-dee411c8acf9",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -91,7 +91,7 @@
],
"pinData": {},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Lots_of_nodes.json b/cypress/fixtures/Lots_of_nodes.json
index 7b3ad507c8..11152fb496 100644
--- a/cypress/fixtures/Lots_of_nodes.json
+++ b/cypress/fixtures/Lots_of_nodes.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "369fe424-dd3b-4399-9de3-50bd4ce1f75b",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -570,7 +570,7 @@
],
"pinData": {},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Multiple_trigger_node_rerun.json b/cypress/fixtures/Multiple_trigger_node_rerun.json
index f956be3742..c5b34aaa26 100644
--- a/cypress/fixtures/Multiple_trigger_node_rerun.json
+++ b/cypress/fixtures/Multiple_trigger_node_rerun.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "5ae8991f-08a2-4b27-b61c-85e3b8a83693",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -78,14 +78,14 @@
}
}
],
- "When clicking \"Test workflow\"": [
+ "When clicking ‘Test workflow’": [
{
"json": {}
}
]
},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/NDV-test-switch_reorder.json b/cypress/fixtures/NDV-test-switch_reorder.json
index cf970434f3..8e06d9dc02 100644
--- a/cypress/fixtures/NDV-test-switch_reorder.json
+++ b/cypress/fixtures/NDV-test-switch_reorder.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "b3f0815d-b733-413f-ab3f-74e48277bd3a",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -160,7 +160,7 @@
]
},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Node_IO_filter.json b/cypress/fixtures/Node_IO_filter.json
index 61be5d58d8..885c76a2b9 100644
--- a/cypress/fixtures/Node_IO_filter.json
+++ b/cypress/fixtures/Node_IO_filter.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "46770685-44d1-4aad-9107-1d790cf26b50",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -74,7 +74,7 @@
}
],
"pinData": {
- "When clicking \"Test workflow\"": [
+ "When clicking ‘Test workflow’": [
{
"json": {
"id": "654cfa05fa51480dcb543b1a",
@@ -599,7 +599,7 @@
]
},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Suggested_Templates.json b/cypress/fixtures/Suggested_Templates.json
index 3f69c4b1a9..308806c6c6 100644
--- a/cypress/fixtures/Suggested_Templates.json
+++ b/cypress/fixtures/Suggested_Templates.json
@@ -42,7 +42,7 @@
{
"parameters": {},
"id": "551313bb-1e01-4133-9956-e6f09968f2ce",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -92,7 +92,7 @@
}
],
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
@@ -191,7 +191,7 @@
{
"parameters": {},
"id": "551313bb-1e01-4133-9956-e6f09968f2ce",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -241,7 +241,7 @@
}
],
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
@@ -374,7 +374,7 @@
{
"parameters": {},
"id": "551313bb-1e01-4133-9956-e6f09968f2ce",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -424,7 +424,7 @@
}
],
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
@@ -524,7 +524,7 @@
{
"parameters": {},
"id": "551313bb-1e01-4133-9956-e6f09968f2ce",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -574,7 +574,7 @@
}
],
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Test_Workflow_pairedItem_incomplete_manual_bug.json b/cypress/fixtures/Test_Workflow_pairedItem_incomplete_manual_bug.json
index f740bc1df6..60875681ab 100644
--- a/cypress/fixtures/Test_Workflow_pairedItem_incomplete_manual_bug.json
+++ b/cypress/fixtures/Test_Workflow_pairedItem_incomplete_manual_bug.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "f26332f3-c61a-4843-94bd-64a73ad161ff",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -105,7 +105,7 @@
],
"pinData": {},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Test_workflow-actions_import_nodes_empty_name.json b/cypress/fixtures/Test_workflow-actions_import_nodes_empty_name.json
index e07e32119f..cb7b4dcf20 100644
--- a/cypress/fixtures/Test_workflow-actions_import_nodes_empty_name.json
+++ b/cypress/fixtures/Test_workflow-actions_import_nodes_empty_name.json
@@ -19,7 +19,7 @@
{
"parameters": {},
"id": "449ab540-d9d7-480d-b131-05e9989a69cd",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -42,7 +42,7 @@
}
],
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Test_workflow_5.json b/cypress/fixtures/Test_workflow_5.json
index 5771e197d9..f3bf74634b 100644
--- a/cypress/fixtures/Test_workflow_5.json
+++ b/cypress/fixtures/Test_workflow_5.json
@@ -40,7 +40,7 @@
{
"parameters": {},
"id": "ef63cdc5-50bc-4525-9873-7e7f7589a60e",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -199,7 +199,7 @@
]
]
},
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Test_workflow_filter.json b/cypress/fixtures/Test_workflow_filter.json
index e5aad93388..cf3ab886f9 100644
--- a/cypress/fixtures/Test_workflow_filter.json
+++ b/cypress/fixtures/Test_workflow_filter.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "f332a7d1-31b4-4e78-b31e-9e8db945bf3f",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -99,7 +99,7 @@
],
"pinData": {},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Test_workflow_ndv_run_error.json b/cypress/fixtures/Test_workflow_ndv_run_error.json
index 45a045851d..a42347dccf 100644
--- a/cypress/fixtures/Test_workflow_ndv_run_error.json
+++ b/cypress/fixtures/Test_workflow_ndv_run_error.json
@@ -30,7 +30,7 @@
{
"parameters": {},
"id": "4f4c6527-d565-448a-96bd-8f5414caf8cc",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -136,7 +136,7 @@
]
]
},
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Test_workflow_ndv_version.json b/cypress/fixtures/Test_workflow_ndv_version.json
index 7f3ba16f43..d682708eb8 100644
--- a/cypress/fixtures/Test_workflow_ndv_version.json
+++ b/cypress/fixtures/Test_workflow_ndv_version.json
@@ -3,7 +3,7 @@
"nodes": [
{
"id": "2acca986-10a6-451e-b20a-86e95b50e627",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [460, 460]
diff --git a/cypress/fixtures/Test_workflow_partial_execution_with_missing_credentials.json b/cypress/fixtures/Test_workflow_partial_execution_with_missing_credentials.json
index 2a9e75e11b..c02f01e59c 100644
--- a/cypress/fixtures/Test_workflow_partial_execution_with_missing_credentials.json
+++ b/cypress/fixtures/Test_workflow_partial_execution_with_missing_credentials.json
@@ -7,7 +7,7 @@
{
"parameters": {},
"id": "09e4325e-ede1-40cf-a1ba-58612bbc7f1b",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -77,7 +77,7 @@
}
],
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Test_workflow_schema_test.json b/cypress/fixtures/Test_workflow_schema_test.json
index 8c83c4f20e..0252fb893e 100644
--- a/cypress/fixtures/Test_workflow_schema_test.json
+++ b/cypress/fixtures/Test_workflow_schema_test.json
@@ -47,7 +47,7 @@
{
"parameters": {},
"id": "58512a93-dabf-4584-817f-27c608c1bdd5",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -69,7 +69,7 @@
]
]
},
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Test_workflow_schema_test_pinned_data.json b/cypress/fixtures/Test_workflow_schema_test_pinned_data.json
index 8bd5ef783d..73f6b62cff 100644
--- a/cypress/fixtures/Test_workflow_schema_test_pinned_data.json
+++ b/cypress/fixtures/Test_workflow_schema_test_pinned_data.json
@@ -47,7 +47,7 @@
{
"parameters": {},
"id": "3dc7cf26-ff25-4437-b9fd-0e8b127ebec9",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -552,7 +552,7 @@
]
]
},
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Test_workflow_webhook_with_pin_data.json b/cypress/fixtures/Test_workflow_webhook_with_pin_data.json
index fb632bcf36..503b723e5b 100644
--- a/cypress/fixtures/Test_workflow_webhook_with_pin_data.json
+++ b/cypress/fixtures/Test_workflow_webhook_with_pin_data.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "0a60e507-7f34-41c0-a0f9-697d852033b6",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -93,7 +93,7 @@
]
},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/Test_workflow_xml_output.json b/cypress/fixtures/Test_workflow_xml_output.json
index 17449bc56d..871156fab2 100644
--- a/cypress/fixtures/Test_workflow_xml_output.json
+++ b/cypress/fixtures/Test_workflow_xml_output.json
@@ -6,7 +6,7 @@
{
"parameters": {},
"id": "8108d313-8b03-4aa4-963d-cd1c0fe8f85c",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -37,7 +37,7 @@
}
],
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/expression_with_paired_item_in_multi_input_node.json b/cypress/fixtures/expression_with_paired_item_in_multi_input_node.json
index ffb7005f4f..d2b16c5656 100644
--- a/cypress/fixtures/expression_with_paired_item_in_multi_input_node.json
+++ b/cypress/fixtures/expression_with_paired_item_in_multi_input_node.json
@@ -6,7 +6,7 @@
{
"parameters": {},
"id": "bcb6abdf-d34b-4ea7-a8ed-58155b708c43",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -90,7 +90,7 @@
}
],
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/open_node_creator_for_connection.json b/cypress/fixtures/open_node_creator_for_connection.json
index 78827d4083..3f693e538c 100644
--- a/cypress/fixtures/open_node_creator_for_connection.json
+++ b/cypress/fixtures/open_node_creator_for_connection.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "25ff0c17-7064-4e14-aec6-45c71d63201b",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -107,4 +107,4 @@
},
"id": "L3tgfoW660UOSuX6",
"tags": []
-}
\ No newline at end of file
+}
diff --git a/cypress/fixtures/workflow-with-unknown-credentials.json b/cypress/fixtures/workflow-with-unknown-credentials.json
index 142422227c..9a04cd87e5 100644
--- a/cypress/fixtures/workflow-with-unknown-credentials.json
+++ b/cypress/fixtures/workflow-with-unknown-credentials.json
@@ -27,7 +27,7 @@
{
"parameters": {},
"id": "acdd1bdc-c642-4ea6-ad67-f4201b640cfa",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -37,7 +37,7 @@
}
],
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/cypress/fixtures/workflow-with-unknown-nodes.json b/cypress/fixtures/workflow-with-unknown-nodes.json
index 5ea0189e50..3406e512d7 100644
--- a/cypress/fixtures/workflow-with-unknown-nodes.json
+++ b/cypress/fixtures/workflow-with-unknown-nodes.json
@@ -6,7 +6,7 @@
{
"parameters": {},
"id": "40720511-19b6-4421-bdb0-3fb6efef4bc5",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -64,7 +64,7 @@
}
],
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts
index 1d15d71840..acbdf28cf4 100644
--- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts
@@ -247,7 +247,7 @@ export class Agent implements INodeType {
alias: ['LangChain'],
categories: ['AI'],
subcategories: {
- AI: ['Agents'],
+ AI: ['Agents', 'Root Nodes'],
},
resources: {
primaryDocumentation: [
diff --git a/packages/@n8n/nodes-langchain/nodes/agents/OpenAiAssistant/OpenAiAssistant.node.ts b/packages/@n8n/nodes-langchain/nodes/agents/OpenAiAssistant/OpenAiAssistant.node.ts
index 449fcd41c4..5db0025932 100644
--- a/packages/@n8n/nodes-langchain/nodes/agents/OpenAiAssistant/OpenAiAssistant.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/agents/OpenAiAssistant/OpenAiAssistant.node.ts
@@ -31,7 +31,7 @@ export class OpenAiAssistant implements INodeType {
alias: ['LangChain'],
categories: ['AI'],
subcategories: {
- AI: ['Agents'],
+ AI: ['Agents', 'Root Nodes'],
},
resources: {
primaryDocumentation: [
diff --git a/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts
index b177755591..a03b2d9a49 100644
--- a/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts
@@ -257,7 +257,7 @@ export class ChainLlm implements INodeType {
alias: ['LangChain'],
categories: ['AI'],
subcategories: {
- AI: ['Chains'],
+ AI: ['Chains', 'Root Nodes'],
},
resources: {
primaryDocumentation: [
diff --git a/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts
index 8647db9b95..da3f4ccc0d 100644
--- a/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts
@@ -30,7 +30,7 @@ export class ChainRetrievalQa implements INodeType {
alias: ['LangChain'],
categories: ['AI'],
subcategories: {
- AI: ['Chains'],
+ AI: ['Chains', 'Root Nodes'],
},
resources: {
primaryDocumentation: [
diff --git a/packages/@n8n/nodes-langchain/nodes/chains/ChainSummarization/ChainSummarization.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/ChainSummarization/ChainSummarization.node.ts
index 8cc64c6e4f..cd47eb6a15 100644
--- a/packages/@n8n/nodes-langchain/nodes/chains/ChainSummarization/ChainSummarization.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/chains/ChainSummarization/ChainSummarization.node.ts
@@ -16,7 +16,7 @@ export class ChainSummarization extends VersionedNodeType {
alias: ['LangChain'],
categories: ['AI'],
subcategories: {
- AI: ['Chains'],
+ AI: ['Chains', 'Root Nodes'],
},
resources: {
primaryDocumentation: [
diff --git a/packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/MemoryManager.node.ts b/packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/MemoryManager.node.ts
index ef20fb041f..83918616c1 100644
--- a/packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/MemoryManager.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/MemoryManager.node.ts
@@ -77,7 +77,7 @@ export class MemoryManager implements INodeType {
codex: {
categories: ['AI'],
subcategories: {
- AI: ['Miscellaneous'],
+ AI: ['Miscellaneous', 'Root Nodes'],
},
resources: {
primaryDocumentation: [
diff --git a/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserStructured/OutputParserStructured.node.ts b/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserStructured/OutputParserStructured.node.ts
index 354ba8fbb0..dc40a47c4a 100644
--- a/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserStructured/OutputParserStructured.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/output_parser/OutputParserStructured/OutputParserStructured.node.ts
@@ -146,6 +146,79 @@ export class OutputParserStructured implements INodeType {
}
}`,
},
+ {
+ displayName: 'Schema Type',
+ name: 'schemaType',
+ type: 'options',
+ noDataExpression: true,
+ options: [
+ {
+ name: 'Generate From JSON Example',
+ value: 'fromJson',
+ description: 'Generate a schema from an example JSON object',
+ },
+ {
+ name: 'Define Below',
+ value: 'manual',
+ description: 'Define the JSON schema manually',
+ },
+ ],
+ default: 'fromJson',
+ description: 'How to specify the schema for the function',
+ displayOptions: {
+ show: {
+ '@version': [{ _cnd: { gte: 1.2 } }],
+ },
+ },
+ },
+ {
+ displayName: 'JSON Example',
+ name: 'jsonSchemaExample',
+ type: 'json',
+ default: `{
+ "state": "California",
+ "cities": ["Los Angeles", "San Francisco", "San Diego"]
+}`,
+ noDataExpression: true,
+ typeOptions: {
+ rows: 10,
+ },
+ displayOptions: {
+ show: {
+ schemaType: ['fromJson'],
+ },
+ },
+ description: 'Example JSON object to use to generate the schema',
+ },
+ {
+ displayName: 'Input Schema',
+ name: 'inputSchema',
+ type: 'json',
+ default: `{
+ "type": "object",
+ "properties": {
+ "state": {
+ "type": "string"
+ },
+ "cities": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ }
+}`,
+ noDataExpression: true,
+ typeOptions: {
+ rows: 10,
+ },
+ displayOptions: {
+ show: {
+ schemaType: ['manual'],
+ },
+ },
+ description: 'Schema to use for the function',
+ },
{
displayName: 'JSON Schema',
name: 'jsonSchema',
diff --git a/packages/@n8n/nodes-langchain/nodes/trigger/ChatTrigger/ChatTrigger.node.ts b/packages/@n8n/nodes-langchain/nodes/trigger/ChatTrigger/ChatTrigger.node.ts
index 386479e569..200fe9c5a0 100644
--- a/packages/@n8n/nodes-langchain/nodes/trigger/ChatTrigger/ChatTrigger.node.ts
+++ b/packages/@n8n/nodes-langchain/nodes/trigger/ChatTrigger/ChatTrigger.node.ts
@@ -34,9 +34,6 @@ export class ChatTrigger implements INodeType {
},
],
},
- subcategories: {
- 'Core Nodes': ['Other Trigger Nodes'],
- },
},
supportsCORS: true,
maxNodes: 1,
diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts
index 38aa1698a5..1143c6c097 100644
--- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts
+++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts
@@ -79,7 +79,7 @@ export const versionDescription: INodeTypeDescription = {
alias: ['LangChain', 'ChatGPT', 'DallE'],
categories: ['AI'],
subcategories: {
- AI: ['Agents', 'Miscellaneous'],
+ AI: ['Agents', 'Miscellaneous', 'Root Nodes'],
},
resources: {
primaryDocumentation: [
diff --git a/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue b/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue
index a6cb79897b..877b79277f 100644
--- a/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue
+++ b/packages/design-system/src/components/N8nNodeCreatorNode/NodeCreatorNode.vue
@@ -1,5 +1,6 @@
+
+
diff --git a/packages/editor-ui/src/components/Node/NodeCreator/ItemTypes/NodeItem.vue b/packages/editor-ui/src/components/Node/NodeCreator/ItemTypes/NodeItem.vue
index 7db599d107..1a20ed2b9a 100644
--- a/packages/editor-ui/src/components/Node/NodeCreator/ItemTypes/NodeItem.vue
+++ b/packages/editor-ui/src/components/Node/NodeCreator/ItemTypes/NodeItem.vue
@@ -8,6 +8,7 @@
:show-action-arrow="showActionArrow"
:is-trigger="isTrigger"
:data-test-id="dataTestId"
+ :tag="nodeType.tag"
@dragstart="onDragStart"
@dragend="onDragEnd"
>
diff --git a/packages/editor-ui/src/components/Node/NodeCreator/Modes/NodesMode.vue b/packages/editor-ui/src/components/Node/NodeCreator/Modes/NodesMode.vue
index 34f5b703bc..3adfd023d6 100644
--- a/packages/editor-ui/src/components/Node/NodeCreator/Modes/NodesMode.vue
+++ b/packages/editor-ui/src/components/Node/NodeCreator/Modes/NodesMode.vue
@@ -139,6 +139,13 @@ function onSelected(item: INodeCreateElement) {
searchItems: mergedNodes,
});
}
+
+ if (item.type === 'link') {
+ window.open(item.properties.url, '_blank');
+ telemetry.trackNodesPanel('nodeCreateList.onLinkSelected', {
+ link: item.properties.url,
+ });
+ }
}
function subcategoriesMapper(item: INodeCreateElement) {
@@ -195,13 +202,13 @@ function onKeySelect(activeItemId: string) {
registerKeyHook('MainViewArrowRight', {
keyboardKeys: ['ArrowRight', 'Enter'],
- condition: (type) => ['subcategory', 'node', 'view'].includes(type),
+ condition: (type) => ['subcategory', 'node', 'link', 'view'].includes(type),
handler: onKeySelect,
});
registerKeyHook('MainViewArrowLeft', {
keyboardKeys: ['ArrowLeft'],
- condition: (type) => ['subcategory', 'node', 'view'].includes(type),
+ condition: (type) => ['subcategory', 'node', 'link', 'view'].includes(type),
handler: arrowLeft,
});
diff --git a/packages/editor-ui/src/components/Node/NodeCreator/Renderers/ItemsRenderer.vue b/packages/editor-ui/src/components/Node/NodeCreator/Renderers/ItemsRenderer.vue
index d4b4bce771..c2327dcfbc 100644
--- a/packages/editor-ui/src/components/Node/NodeCreator/Renderers/ItemsRenderer.vue
+++ b/packages/editor-ui/src/components/Node/NodeCreator/Renderers/ItemsRenderer.vue
@@ -8,6 +8,7 @@ import SubcategoryItem from '../ItemTypes/SubcategoryItem.vue';
import LabelItem from '../ItemTypes/LabelItem.vue';
import ActionItem from '../ItemTypes/ActionItem.vue';
import ViewItem from '../ItemTypes/ViewItem.vue';
+import LinkItem from '../ItemTypes/LinkItem.vue';
import CategorizedItemsRenderer from './CategorizedItemsRenderer.vue';
export interface Props {
@@ -147,6 +148,8 @@ watch(
[$style.active]: activeItemId === item.uuid,
[$style.iteratorItem]: true,
[$style[item.type]]: true,
+ // Borderless is only applied to views
+ [$style.borderless]: item.type === 'view' && item.properties.borderless === true,
}"
data-test-id="item-iterator-item"
:data-keyboard-nav-type="item.type !== 'label' ? item.type : undefined"
@@ -175,6 +178,12 @@ watch(
:view="item.properties"
:class="$style.viewItem"
/>
+
+
@@ -223,12 +232,14 @@ watch(
display: none;
}
}
+
.view {
position: relative;
&:last-child {
margin-top: var(--spacing-s);
padding-top: var(--spacing-xs);
+
&:after {
content: '';
position: absolute;
@@ -241,4 +252,34 @@ watch(
}
}
}
+.link {
+ position: relative;
+
+ &:last-child {
+ margin-bottom: var(--spacing-s);
+ padding-bottom: var(--spacing-xs);
+
+ &:after {
+ content: '';
+ position: absolute;
+ left: var(--spacing-s);
+ right: var(--spacing-s);
+ top: 0;
+ margin: auto;
+ bottom: 0;
+ border-bottom: 1px solid var(--color-foreground-base);
+ }
+ }
+}
+
+.borderless {
+ &:last-child {
+ margin-top: 0;
+ padding-top: 0;
+
+ &:after {
+ content: none;
+ }
+ }
+}
diff --git a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/NodesListPanel.test.ts b/packages/editor-ui/src/components/Node/NodeCreator/__tests__/NodesListPanel.test.ts
index 3c36faa611..ac4b2757f4 100644
--- a/packages/editor-ui/src/components/Node/NodeCreator/__tests__/NodesListPanel.test.ts
+++ b/packages/editor-ui/src/components/Node/NodeCreator/__tests__/NodesListPanel.test.ts
@@ -76,7 +76,7 @@ describe('NodesListPanel', () => {
await fireEvent.click(container.querySelector('.backButton')!);
await nextTick();
- expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(7);
+ expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(8);
});
it('should render regular nodes', async () => {
@@ -136,7 +136,7 @@ describe('NodesListPanel', () => {
await nextTick();
expect(screen.getByText('What happens next?')).toBeInTheDocument();
- expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(6);
+ expect(screen.queryAllByTestId('item-iterator-item')).toHaveLength(5);
screen.getByText('Action in an app').click();
await nextTick();
diff --git a/packages/editor-ui/src/components/Node/NodeCreator/composables/useViewStacks.ts b/packages/editor-ui/src/components/Node/NodeCreator/composables/useViewStacks.ts
index 3078cf3884..057d0dfe5c 100644
--- a/packages/editor-ui/src/components/Node/NodeCreator/composables/useViewStacks.ts
+++ b/packages/editor-ui/src/components/Node/NodeCreator/composables/useViewStacks.ts
@@ -1,19 +1,29 @@
-import type { INodeCreateElement, NodeFilterType, SimplifiedNodeType } from '@/Interface';
+import type {
+ INodeCreateElement,
+ NodeCreateElement,
+ NodeFilterType,
+ SimplifiedNodeType,
+} from '@/Interface';
import {
+ AI_CATEGORY_ROOT_NODES,
AI_CODE_NODE_TYPE,
+ AI_NODE_CREATOR_VIEW,
AI_OTHERS_NODE_CREATOR_VIEW,
+ AI_SUBCATEGORY,
DEFAULT_SUBCATEGORY,
TRIGGER_NODE_CREATOR_VIEW,
} from '@/constants';
import { defineStore } from 'pinia';
import { v4 as uuid } from 'uuid';
import { computed, nextTick, ref } from 'vue';
+import difference from 'lodash-es/difference';
import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
import {
flattenCreateElements,
groupItemsInSections,
+ isAINode,
searchNodes,
sortNodeCreateElements,
subcategorizeItems,
@@ -27,6 +37,7 @@ import { useKeyboardNavigation } from './useKeyboardNavigation';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import type { INodeInputFilter, NodeConnectionType } from 'n8n-workflow';
+import { useCanvasStore } from '@/stores/canvas.store';
interface ViewStack {
uuid?: string;
@@ -60,11 +71,12 @@ interface ViewStack {
export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
const nodeCreatorStore = useNodeCreatorStore();
const { getActiveItemIndex } = useKeyboardNavigation();
+ const i18n = useI18n();
const viewStacks = ref([]);
const activeStackItems = computed(() => {
- const stack = viewStacks.value[viewStacks.value.length - 1];
+ const stack = getLastActiveStack();
if (!stack?.baselineItems) {
return stack.items ? extendItemsWithUUID(stack.items) : [];
@@ -76,13 +88,24 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
? searchBaseItems.value
: flattenCreateElements(stack.baselineItems ?? []);
- return extendItemsWithUUID(searchNodes(stack.search || '', searchBase));
+ const canvasHasAINodes = useCanvasStore().aiNodes.length > 0;
+ const filteredNodes =
+ isAiRootView(stack) || canvasHasAINodes ? searchBase : filterOutAiNodes(searchBase);
+
+ const searchResults = extendItemsWithUUID(searchNodes(stack.search || '', filteredNodes));
+
+ const groupedNodes = groupIfAiNodes(searchResults, false) ?? searchResults;
+ // Set the active index to the second item if there's a section
+ // as the first item is collapsable
+ stack.activeIndex = groupedNodes.some((node) => node.type === 'section') ? 1 : 0;
+
+ return groupedNodes;
}
- return extendItemsWithUUID(stack.baselineItems);
+ return extendItemsWithUUID(groupIfAiNodes(stack.baselineItems, true));
});
const activeViewStack = computed(() => {
- const stack = viewStacks.value[viewStacks.value.length - 1];
+ const stack = getLastActiveStack();
if (!stack) return {};
const flatBaselineItems = flattenCreateElements(stack.baselineItems ?? []);
@@ -99,34 +122,148 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
);
const searchBaseItems = computed(() => {
- const stack = viewStacks.value[viewStacks.value.length - 1];
+ const stack = getLastActiveStack();
if (!stack?.searchItems) return [];
return stack.searchItems.map((item) => transformNodeType(item, stack.subcategory));
});
+ function getLastActiveStack() {
+ return viewStacks.value[viewStacks.value.length - 1];
+ }
+
// Generate a delta between the global search results(all nodes) and the stack search results
const globalSearchItemsDiff = computed(() => {
- const stack = viewStacks.value[viewStacks.value.length - 1];
+ const stack = getLastActiveStack();
if (!stack?.search) return [];
const allNodes = nodeCreatorStore.mergedNodes.map((item) => transformNodeType(item));
- const globalSearchResult = extendItemsWithUUID(searchNodes(stack.search || '', allNodes));
+ // Apply filtering for AI nodes if the current view is not the AI root view
+ const filteredNodes = isAiRootView(stack) ? allNodes : filterOutAiNodes(allNodes);
- return globalSearchResult.filter((item) => {
- return !activeStackItems.value.find((activeItem) => activeItem.key === item.key);
+ let globalSearchResult: INodeCreateElement[] = extendItemsWithUUID(
+ searchNodes(stack.search || '', filteredNodes),
+ );
+ if (isAiRootView(stack)) {
+ globalSearchResult = groupIfAiNodes(globalSearchResult);
+ }
+
+ const filteredItems = globalSearchResult.filter((item) => {
+ return !activeStackItems.value.find((activeItem) => {
+ if (activeItem.type === 'section') {
+ const matchingSectionItem = activeItem.children.some(
+ (sectionItem) => sectionItem.key === item.key,
+ );
+ return matchingSectionItem;
+ }
+
+ return activeItem.key === item.key;
+ });
});
+
+ // Filter out empty sections if all of their children are filtered out
+ const filteredSections = filteredItems.filter((item) => {
+ if (item.type === 'section') {
+ const hasVisibleChildren = item.children.some((child) =>
+ activeStackItems.value.some((filteredItem) => filteredItem.key === child.key),
+ );
+
+ return hasVisibleChildren;
+ }
+
+ return true;
+ });
+
+ return filteredSections;
});
const itemsBySubcategory = computed(() => subcategorizeItems(nodeCreatorStore.mergedNodes));
+ function isAiRootView(stack: ViewStack) {
+ return stack.rootView === AI_NODE_CREATOR_VIEW;
+ }
+
+ function groupIfAiNodes(items: INodeCreateElement[], sortAlphabetically = true) {
+ const aiNodes = items.filter((node): node is NodeCreateElement => isAINode(node));
+
+ if (aiNodes.length > 0) {
+ const sectionsMap = new Map();
+ aiNodes.forEach((node) => {
+ const section = node.properties.codex?.subcategories?.[AI_SUBCATEGORY]?.[0];
+
+ if (section) {
+ const currentItems = sectionsMap.get(section)?.items ?? [];
+ const isSubnodesSection =
+ !node.properties.codex?.subcategories?.[AI_SUBCATEGORY].includes(
+ AI_CATEGORY_ROOT_NODES,
+ );
+
+ sectionsMap.set(section, {
+ key: section,
+ title: isSubnodesSection
+ ? `${section} (${i18n.baseText('nodeCreator.subnodes')})`
+ : section,
+ items: [...currentItems, node.key],
+ });
+ }
+ });
+
+ const nonAiNodes = difference(items, aiNodes);
+ const nonAiTriggerNodes = nonAiNodes.filter(
+ (item) => item.type === 'node' && useNodeTypesStore().isTriggerNode(item.properties.name),
+ );
+
+ const nonAiRegularNodes = difference(nonAiNodes, nonAiTriggerNodes);
+
+ if (nonAiNodes.length > 0) {
+ let sectionKey = '';
+ if (nonAiRegularNodes.length && nonAiTriggerNodes.length) {
+ sectionKey = i18n.baseText('nodeCreator.actionsCategory.regularAndTriggers');
+ } else {
+ sectionKey = nonAiRegularNodes.length
+ ? i18n.baseText('nodeCreator.actionsCategory.regularNodes')
+ : i18n.baseText('nodeCreator.actionsCategory.triggerNodes');
+ }
+
+ const nodesKeys = nonAiNodes.map((node) => node.key);
+
+ sectionsMap.set(sectionKey, {
+ key: sectionKey,
+ title: sectionKey,
+ items: [...nodesKeys],
+ });
+ }
+ // Convert sectionsMap to array of sections
+ const sections = Array.from(sectionsMap.values());
+
+ return groupItemsInSections(items, sections, sortAlphabetically);
+ }
+
+ return items;
+ }
+
+ function filterOutAiNodes(items: INodeCreateElement[]) {
+ const filteredSearchBase = items.filter((item) => {
+ if (item.type === 'node') {
+ const isAICategory = item.properties.codex?.categories?.includes(AI_SUBCATEGORY) === true;
+
+ if (!isAICategory) return true;
+
+ const isRootNodeSubcategory =
+ item.properties.codex?.subcategories?.[AI_SUBCATEGORY]?.includes(AI_CATEGORY_ROOT_NODES);
+
+ return isRootNodeSubcategory;
+ }
+ return true;
+ });
+ return filteredSearchBase;
+ }
+
async function gotoCompatibleConnectionView(
connectionType: NodeConnectionType,
isOutput?: boolean,
filter?: INodeInputFilter,
) {
- const i18n = useI18n();
-
let nodesByConnectionType: { [key: string]: string[] };
let relatedAIView: { properties: NodeViewItem['properties'] } | undefined;
@@ -185,7 +322,7 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
}
function setStackBaselineItems() {
- const stack = viewStacks.value[viewStacks.value.length - 1];
+ const stack = getLastActiveStack();
if (!stack || !activeViewStack.value.uuid) return;
let stackItems = stack?.items ?? [];
@@ -258,7 +395,7 @@ export const useViewStacks = defineStore('nodeCreatorViewStacks', () => {
}
function updateCurrentViewStack(stack: Partial) {
- const currentStack = viewStacks.value[viewStacks.value.length - 1];
+ const currentStack = getLastActiveStack();
const matchedIndex = viewStacks.value.findIndex((s) => s.uuid === currentStack.uuid);
if (!currentStack) return;
diff --git a/packages/editor-ui/src/components/Node/NodeCreator/utils.ts b/packages/editor-ui/src/components/Node/NodeCreator/utils.ts
index dbde77d1f2..ac16811929 100644
--- a/packages/editor-ui/src/components/Node/NodeCreator/utils.ts
+++ b/packages/editor-ui/src/components/Node/NodeCreator/utils.ts
@@ -6,12 +6,18 @@ import type {
INodeCreateElement,
SectionCreateElement,
} from '@/Interface';
-import { AI_SUBCATEGORY, CORE_NODES_CATEGORY, DEFAULT_SUBCATEGORY } from '@/constants';
+import {
+ AI_CATEGORY_AGENTS,
+ AI_SUBCATEGORY,
+ CORE_NODES_CATEGORY,
+ DEFAULT_SUBCATEGORY,
+} from '@/constants';
import { v4 as uuidv4 } from 'uuid';
import { sublimeSearch } from '@/utils/sortUtils';
-import { i18n } from '@/plugins/i18n';
import type { NodeViewItemSection } from './viewsData';
+import { i18n } from '@/plugins/i18n';
+import { sortBy } from 'lodash-es';
export function transformNodeType(
node: SimplifiedNodeType,
@@ -70,6 +76,7 @@ export function sortNodeCreateElements(nodes: INodeCreateElement[]) {
export function searchNodes(searchFilter: string, items: INodeCreateElement[]) {
// In order to support the old search we need to remove the 'trigger' part
const trimmedFilter = searchFilter.toLowerCase().replace('trigger', '').trimEnd();
+
const result = (
sublimeSearch(trimmedFilter, items, [
{ key: 'properties.displayName', weight: 1.3 },
@@ -83,38 +90,72 @@ export function searchNodes(searchFilter: string, items: INodeCreateElement[]) {
export function flattenCreateElements(items: INodeCreateElement[]): INodeCreateElement[] {
return items.map((item) => (item.type === 'section' ? item.children : item)).flat();
}
+export function isAINode(node: INodeCreateElement) {
+ const isNode = node.type === 'node';
+ if (!isNode) return false;
+ if (node.properties.codex?.categories?.includes(AI_SUBCATEGORY)) {
+ const isAgentSubcategory =
+ node.properties.codex?.subcategories?.[AI_SUBCATEGORY]?.includes(AI_CATEGORY_AGENTS);
+
+ return !isAgentSubcategory;
+ }
+
+ return false;
+}
export function groupItemsInSections(
items: INodeCreateElement[],
sections: string[] | NodeViewItemSection[],
+ sortAlphabetically = true,
): INodeCreateElement[] {
const filteredSections = sections.filter(
(section): section is NodeViewItemSection => typeof section === 'object',
);
- const itemsBySection = items.reduce((acc: Record, item) => {
- const section = filteredSections.find((s) => s.items.includes(item.key));
- const key = section?.key ?? 'other';
- acc[key] = [...(acc[key] ?? []), item];
- return acc;
- }, {});
+ const itemsBySection = (items2: INodeCreateElement[]) =>
+ items2.reduce((acc: Record, item) => {
+ const section = filteredSections.find((s) => s.items.includes(item.key));
- const result: SectionCreateElement[] = filteredSections
- .map(
+ const key = section?.key ?? 'other';
+ if (key) {
+ acc[key] = [...(acc[key] ?? []), item];
+ }
+ return acc;
+ }, {});
+
+ const mapNewSections = (
+ newSections: NodeViewItemSection[],
+ children: Record,
+ ) =>
+ newSections.map(
(section): SectionCreateElement => ({
type: 'section',
key: section.key,
title: section.title,
- children: sortNodeCreateElements(itemsBySection[section.key] ?? []),
+ children: sortAlphabetically
+ ? sortNodeCreateElements(children[section.key] ?? [])
+ : children[section.key] ?? [],
}),
- )
+ );
+
+ const nonAINodes = items.filter((item) => !isAINode(item));
+ const AINodes = items.filter((item) => isAINode(item));
+
+ const nonAINodesBySection = itemsBySection(nonAINodes);
+ const nonAINodesSections = mapNewSections(filteredSections, nonAINodesBySection);
+
+ const AINodesBySection = itemsBySection(AINodes);
+
+ const AINodesSections = mapNewSections(sortBy(filteredSections, ['title']), AINodesBySection);
+
+ const result = [...nonAINodesSections, ...AINodesSections]
.concat({
type: 'section',
key: 'other',
title: i18n.baseText('nodeCreator.sectionNames.other'),
- children: sortNodeCreateElements(itemsBySection.other ?? []),
+ children: sortNodeCreateElements(nonAINodesBySection.other ?? []),
})
- .filter((section) => section.children.length > 0);
+ .filter((section) => section.type !== 'section' || section.children.length > 0);
if (result.length <= 1) {
return items;
diff --git a/packages/editor-ui/src/components/Node/NodeCreator/viewsData.ts b/packages/editor-ui/src/components/Node/NodeCreator/viewsData.ts
index da309ec5dd..d97a7329b8 100644
--- a/packages/editor-ui/src/components/Node/NodeCreator/viewsData.ts
+++ b/packages/editor-ui/src/components/Node/NodeCreator/viewsData.ts
@@ -5,10 +5,10 @@ import {
EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE,
FORM_TRIGGER_NODE_TYPE,
MANUAL_TRIGGER_NODE_TYPE,
+ MANUAL_CHAT_TRIGGER_NODE_TYPE,
SCHEDULE_TRIGGER_NODE_TYPE,
REGULAR_NODE_CREATOR_VIEW,
TRANSFORM_DATA_SUBCATEGORY,
- FILES_SUBCATEGORY,
FLOWS_CONTROL_SUBCATEGORY,
TRIGGER_NODE_CREATOR_VIEW,
EMAIL_IMAP_NODE_TYPE,
@@ -52,6 +52,8 @@ import {
EMAIL_SEND_NODE_TYPE,
EDIT_IMAGE_NODE_TYPE,
COMPRESSION_NODE_TYPE,
+ AI_CODE_TOOL_LANGCHAIN_NODE_TYPE,
+ AI_WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE,
} from '@/constants';
import { useI18n } from '@/composables/useI18n';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
@@ -76,13 +78,17 @@ export interface NodeViewItem {
iconProps?: {
color?: string;
};
+ url?: string;
connectionType?: NodeConnectionType;
panelClass?: string;
group?: string[];
sections?: NodeViewItemSection[];
description?: string;
displayName?: string;
- tag?: string;
+ tag?: {
+ type: string;
+ text: string;
+ };
forceIncludeNodes?: string[];
iconData?: {
type: string;
@@ -141,12 +147,24 @@ export function AIView(_nodes: SimplifiedNodeType[]): NodeView {
value: AI_NODE_CREATOR_VIEW,
title: i18n.baseText('nodeCreator.aiPanel.aiNodes'),
subtitle: i18n.baseText('nodeCreator.aiPanel.selectAiNode'),
- info: i18n.baseText('nodeCreator.aiPanel.infoBox', {
- interpolate: { link: templatesStore.getWebsiteCategoryURL('ai') },
- }),
items: [
- ...chainNodes,
+ {
+ key: 'ai_templates_root',
+ type: 'link',
+ properties: {
+ title: i18n.baseText('nodeCreator.aiPanel.linkItem.title'),
+ icon: 'box-open',
+ description: i18n.baseText('nodeCreator.aiPanel.linkItem.description'),
+ name: 'ai_templates_root',
+ url: templatesStore.getWebsiteCategoryURL(undefined, 'AdvancedAI'),
+ tag: {
+ type: 'info',
+ text: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerTag'),
+ },
+ },
+ },
...agentNodes,
+ ...chainNodes,
{
key: AI_OTHERS_NODE_CREATOR_VIEW,
type: 'view',
@@ -159,6 +177,7 @@ export function AIView(_nodes: SimplifiedNodeType[]): NodeView {
],
};
}
+
export function AINodesView(_nodes: SimplifiedNodeType[]): NodeView {
const i18n = useI18n();
@@ -232,12 +251,20 @@ export function AINodesView(_nodes: SimplifiedNodeType[]): NodeView {
},
},
{
- key: AI_CATEGORY_TOOLS,
type: 'subcategory',
+ key: AI_CATEGORY_TOOLS,
+ category: CORE_NODES_CATEGORY,
properties: {
title: AI_CATEGORY_TOOLS,
icon: 'tools',
...getAISubcategoryProperties(NodeConnectionType.AiTool),
+ sections: [
+ {
+ key: 'popular',
+ title: i18n.baseText('nodeCreator.sectionNames.popular'),
+ items: [AI_WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE, AI_CODE_TOOL_LANGCHAIN_NODE_TYPE],
+ },
+ ],
},
},
{
@@ -278,6 +305,18 @@ export function TriggerView() {
title: i18n.baseText('nodeCreator.triggerHelperPanel.selectATrigger'),
subtitle: i18n.baseText('nodeCreator.triggerHelperPanel.selectATriggerDescription'),
items: [
+ {
+ key: MANUAL_TRIGGER_NODE_TYPE,
+ type: 'node',
+ category: [CORE_NODES_CATEGORY],
+ properties: {
+ group: [],
+ name: MANUAL_TRIGGER_NODE_TYPE,
+ displayName: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDisplayName'),
+ description: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDescription'),
+ icon: 'fa:mouse-pointer',
+ },
+ },
{
key: DEFAULT_SUBCATEGORY,
type: 'subcategory',
@@ -331,18 +370,6 @@ export function TriggerView() {
},
},
},
- {
- key: MANUAL_TRIGGER_NODE_TYPE,
- type: 'node',
- category: [CORE_NODES_CATEGORY],
- properties: {
- group: [],
- name: MANUAL_TRIGGER_NODE_TYPE,
- displayName: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDisplayName'),
- description: i18n.baseText('nodeCreator.triggerHelperPanel.manualTriggerDescription'),
- icon: 'fa:mouse-pointer',
- },
- },
{
key: EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE,
type: 'node',
@@ -355,6 +382,18 @@ export function TriggerView() {
icon: 'fa:sign-out-alt',
},
},
+ {
+ key: MANUAL_CHAT_TRIGGER_NODE_TYPE,
+ type: 'node',
+ category: [CORE_NODES_CATEGORY],
+ properties: {
+ group: [],
+ name: MANUAL_CHAT_TRIGGER_NODE_TYPE,
+ displayName: i18n.baseText('nodeCreator.triggerHelperPanel.manualChatTriggerDisplayName'),
+ description: i18n.baseText('nodeCreator.triggerHelperPanel.manualChatTriggerDescription'),
+ icon: 'fa:comments',
+ },
+ },
{
type: 'subcategory',
key: OTHER_TRIGGER_NODES_SUBCATEGORY,
@@ -447,22 +486,6 @@ export function RegularView(nodes: SimplifiedNodeType[]) {
],
},
},
- {
- type: 'subcategory',
- key: FILES_SUBCATEGORY,
- category: CORE_NODES_CATEGORY,
- properties: {
- title: FILES_SUBCATEGORY,
- icon: 'file-alt',
- sections: [
- {
- key: 'popular',
- title: i18n.baseText('nodeCreator.sectionNames.popular'),
- items: [CONVERT_TO_FILE_NODE_TYPE, EXTRACT_FROM_FILE_NODE_TYPE],
- },
- ],
- },
- },
{
type: 'subcategory',
key: HELPERS_SUBCATEGORY,
@@ -491,9 +514,13 @@ export function RegularView(nodes: SimplifiedNodeType[]) {
title: i18n.baseText('nodeCreator.aiPanel.langchainAiNodes'),
icon: 'robot',
description: i18n.baseText('nodeCreator.aiPanel.nodesForAi'),
- tag: i18n.baseText('nodeCreator.aiPanel.newTag'),
+ tag: {
+ type: 'success',
+ text: i18n.baseText('nodeCreator.aiPanel.newTag'),
+ },
+ borderless: true,
},
- });
+ } as NodeViewItem);
view.items.push({
key: TRIGGER_NODE_CREATOR_VIEW,
diff --git a/packages/editor-ui/src/constants.ts b/packages/editor-ui/src/constants.ts
index b433c3edac..f4e1c8bb28 100644
--- a/packages/editor-ui/src/constants.ts
+++ b/packages/editor-ui/src/constants.ts
@@ -264,8 +264,10 @@ export const AI_CATEGORY_RETRIEVERS = 'Retrievers';
export const AI_CATEGORY_EMBEDDING = 'Embeddings';
export const AI_CATEGORY_DOCUMENT_LOADERS = 'Document Loaders';
export const AI_CATEGORY_TEXT_SPLITTERS = 'Text Splitters';
+export const AI_CATEGORY_ROOT_NODES = 'Root Nodes';
export const AI_UNCATEGORIZED_CATEGORY = 'Miscellaneous';
-
+export const AI_CODE_TOOL_LANGCHAIN_NODE_TYPE = '@n8n/n8n-nodes-langchain.toolCode';
+export const AI_WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE = '@n8n/n8n-nodes-langchain.toolWorkflow';
export const REQUEST_NODE_FORM_URL = 'https://n8n-community.typeform.com/to/K1fBVTZ3';
// Node Connection Types
@@ -674,10 +676,17 @@ export const AI_ASSISTANT_EXPERIMENT = {
variant: 'variant',
};
+export const CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT = {
+ name: '20_canvas_auto_add_manual_trigger',
+ control: 'control',
+ variant: 'variant',
+};
+
export const EXPERIMENTS_TO_TRACK = [
ASK_AI_EXPERIMENT.name,
TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT,
AI_ASSISTANT_EXPERIMENT.name,
+ CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.name,
];
export const MFA_AUTHENTICATION_REQUIRED_ERROR_CODE = 998;
diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json
index 04042d988c..951a80c0a4 100644
--- a/packages/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/editor-ui/src/plugins/i18n/locales/en.json
@@ -928,9 +928,9 @@
"ndv.output.of": " of ",
"ndv.output.pageSize": "Page Size",
"ndv.output.run": "Run",
- "ndv.output.runNodeHint": "Test this node to output data",
+ "ndv.output.runNodeHint": "Execute this node to view data",
"ndv.output.runNodeHintSubNode": "Output will appear here once the parent node is run",
- "ndv.output.insertTestData": "insert test data",
+ "ndv.output.insertTestData": "set mock data",
"ndv.output.staleDataWarning.regular": "Node parameters have changed.
Test node again to refresh output.",
"ndv.output.staleDataWarning.pinData": "Node parameter changes will not affect pinned output data.",
"ndv.output.tooMuchData.message": "The node contains {size} MB of data. Displaying it may cause problems.
If you do decide to display it, avoid the JSON view.",
@@ -986,6 +986,9 @@
"nodeCreator.actionsCategory.onNewEvent": "On new {event} event",
"nodeCreator.actionsCategory.onEvent": "On {event}",
"nodeCreator.actionsCategory.triggers": "Triggers",
+ "nodeCreator.actionsCategory.triggerNodes": "Trigger Nodes",
+ "nodeCreator.actionsCategory.regularNodes": "Regular Nodes",
+ "nodeCreator.actionsCategory.regularAndTriggers": "Regular & Trigger Nodes",
"nodeCreator.actionsCategory.searchActions": "Search {node} Actions...",
"nodeCreator.actionsCategory.noMatchingActions": "No matching Actions. Reset search",
"nodeCreator.actionsCategory.noMatchingTriggers": "No matching Triggers. Reset search",
@@ -996,6 +999,7 @@
"nodeCreator.actionsTooltip.actionsPerformStep": "Actions perform a step once your workflow has already started. Learn more",
"nodeCreator.actionsCallout.noTriggerItems": "No {nodeName} Triggers available. Users often combine the following Triggers with {nodeName} Actions.",
"nodeCreator.categoryNames.otherCategories": "Results in other categories",
+ "nodeCreator.subnodes": "sub-nodes",
"nodeCreator.noResults.dontWorryYouCanProbablyDoItWithThe": "Don’t worry, you can probably do it with the",
"nodeCreator.noResults.httpRequest": "HTTP Request",
"nodeCreator.noResults.node": "node",
@@ -1007,10 +1011,10 @@
"nodeCreator.searchBar.searchNodes": "Search nodes...",
"nodeCreator.subcategoryDescriptions.appTriggerNodes": "Runs the flow when something happens in an app like Telegram, Notion or Airtable",
"nodeCreator.subcategoryDescriptions.appRegularNodes": "Do something in an app or service like Google Sheets, Telegram or Notion",
- "nodeCreator.subcategoryDescriptions.dataTransformation": "Manipulate data, run JavaScript code, etc.",
+ "nodeCreator.subcategoryDescriptions.dataTransformation": "Manipulate, filter or convert data",
"nodeCreator.subcategoryDescriptions.files": "CSV, XLS, XML, text, images, etc.",
- "nodeCreator.subcategoryDescriptions.flow": "IF, Switch, Wait, Compare and Merge data, etc.",
- "nodeCreator.subcategoryDescriptions.helpers": "Code, HTTP Requests (API Calls), Webhook, and other helpers",
+ "nodeCreator.subcategoryDescriptions.flow": "Branch, merge or loop the flow, etc.",
+ "nodeCreator.subcategoryDescriptions.helpers": "Run code, make HTTP requests, set webhooks, etc.",
"nodeCreator.subcategoryDescriptions.otherTriggerNodes": "Runs the flow on workflow errors, file changes, etc.",
"nodeCreator.subcategoryDescriptions.agents": "Autonomous entities that interact and make decisions.",
"nodeCreator.subcategoryDescriptions.chains": "Structured assemblies for specific tasks.",
@@ -1054,11 +1058,14 @@
"nodeCreator.triggerHelperPanel.scheduleTriggerDisplayName": "On a schedule",
"nodeCreator.triggerHelperPanel.scheduleTriggerDescription": "Runs the flow every day, hour, or custom interval",
"nodeCreator.triggerHelperPanel.webhookTriggerDisplayName": "On webhook call",
- "nodeCreator.triggerHelperPanel.webhookTriggerDescription": "Runs the flow when another app sends a webhook",
+ "nodeCreator.triggerHelperPanel.webhookTriggerDescription": "Runs the flow on receiving an HTTP request",
"nodeCreator.triggerHelperPanel.formTriggerDisplayName": "On form submission",
"nodeCreator.triggerHelperPanel.formTriggerDescription": "Runs the flow when an n8n generated webform is submitted",
- "nodeCreator.triggerHelperPanel.manualTriggerDisplayName": "Manually",
- "nodeCreator.triggerHelperPanel.manualTriggerDescription": "Runs the flow on clicking a button in n8n",
+ "nodeCreator.triggerHelperPanel.manualTriggerDisplayName": "Trigger manually",
+ "nodeCreator.triggerHelperPanel.manualTriggerDescription": "Runs the flow on clicking a button in n8n. Good for getting started quickly",
+ "nodeCreator.triggerHelperPanel.manualChatTriggerDisplayName": "On chat message",
+ "nodeCreator.triggerHelperPanel.manualChatTriggerDescription": "Runs the flow when a user sends a chat message. For use with AI nodes",
+ "nodeCreator.triggerHelperPanel.manualTriggerTag": "Recommended",
"nodeCreator.triggerHelperPanel.whatHappensNext": "What happens next?",
"nodeCreator.triggerHelperPanel.selectATrigger": "What triggers this workflow?",
"nodeCreator.triggerHelperPanel.selectATriggerDescription": "A trigger is a step that starts your workflow",
@@ -1072,7 +1079,8 @@
"nodeCreator.aiPanel.newTag": "New",
"nodeCreator.aiPanel.langchainAiNodes": "Advanced AI",
"nodeCreator.aiPanel.title": "When should this workflow run?",
- "nodeCreator.aiPanel.infoBox": "Check out our templates for workflow examples and inspiration.",
+ "nodeCreator.aiPanel.linkItem.description": "See what's possible and get started 5x faster",
+ "nodeCreator.aiPanel.linkItem.title": "AI Templates",
"nodeCreator.aiPanel.scheduleTriggerDisplayName": "On a schedule",
"nodeCreator.aiPanel.scheduleTriggerDescription": "Runs the flow every day, hour, or custom interval",
"nodeCreator.aiPanel.webhookTriggerDisplayName": "On webhook call",
diff --git a/packages/editor-ui/src/stores/canvas.store.ts b/packages/editor-ui/src/stores/canvas.store.ts
index b5f57fc42c..93e0975e57 100644
--- a/packages/editor-ui/src/stores/canvas.store.ts
+++ b/packages/editor-ui/src/stores/canvas.store.ts
@@ -15,7 +15,7 @@ import {
scaleReset,
scaleSmaller,
} from '@/utils/canvasUtils';
-import { START_NODE_TYPE } from '@/constants';
+import { MANUAL_TRIGGER_NODE_TYPE, START_NODE_TYPE } from '@/constants';
import type {
BeforeStartEventParams,
BrowserJsPlumbInstance,
@@ -61,6 +61,9 @@ export const useCanvasStore = defineStore('canvas', () => {
(node) => node.type === START_NODE_TYPE || nodeTypesStore.isTriggerNode(node.type),
),
);
+ const aiNodes = computed(() =>
+ nodes.value.filter((node) => node.type.includes('langchain')),
+ );
const isDemo = ref(false);
const nodeViewScale = ref(1);
const canvasAddButtonPosition = ref([1, 1]);
@@ -91,6 +94,23 @@ export const useCanvasStore = defineStore('canvas', () => {
};
};
+ const getAutoAddManualTriggerNode = (): INodeUi | null => {
+ const manualTriggerNode = nodeTypesStore.getNodeType(MANUAL_TRIGGER_NODE_TYPE);
+
+ if (!manualTriggerNode) {
+ console.error('Could not find the manual trigger node');
+ return null;
+ }
+ return {
+ id: uuid(),
+ name: manualTriggerNode.defaults.name?.toString() ?? manualTriggerNode.displayName,
+ type: MANUAL_TRIGGER_NODE_TYPE,
+ parameters: {},
+ position: canvasAddButtonPosition.value,
+ typeVersion: 1,
+ };
+ };
+
const getNodesWithPlaceholderNode = (): INodeUi[] =>
triggerNodes.value.length > 0 ? nodes.value : [getPlaceholderTriggerNodeUI(), ...nodes.value];
@@ -298,6 +318,7 @@ export const useCanvasStore = defineStore('canvas', () => {
newNodeInsertPosition,
jsPlumbInstance,
isLoading: loadingService.isLoading,
+ aiNodes,
startLoading: loadingService.startLoading,
setLoadingText: loadingService.setLoadingText,
stopLoading: loadingService.stopLoading,
@@ -311,5 +332,6 @@ export const useCanvasStore = defineStore('canvas', () => {
zoomToFit,
wheelScroll,
initInstance,
+ getAutoAddManualTriggerNode,
};
});
diff --git a/packages/editor-ui/src/stores/templates.store.ts b/packages/editor-ui/src/stores/templates.store.ts
index 43b219d261..c67a603aea 100644
--- a/packages/editor-ui/src/stores/templates.store.ts
+++ b/packages/editor-ui/src/stores/templates.store.ts
@@ -121,7 +121,7 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
* Constructs URLSearchParams object based on the default parameters for the template repository
* and provided additional parameters
*/
- websiteTemplateRepositoryParameters() {
+ websiteTemplateRepositoryParameters(roleOverride?: string) {
const rootStore = useRootStore();
const userStore = useUsersStore();
const workflowsStore = useWorkflowsStore();
@@ -133,6 +133,7 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
};
const userRole: string | undefined =
userStore.currentUserCloudInfo?.role ?? userStore.currentUser?.personalizationAnswers?.role;
+
if (userRole) {
defaultParameters.utm_user_role = userRole;
}
@@ -156,10 +157,15 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
* Construct the URL for the template category page on the website for a given category id
*/
getWebsiteCategoryURL() {
- return (id: string) => {
- return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/?${this.websiteTemplateRepositoryParameters({
- categories: id,
- }).toString()}`;
+ return (id?: string, roleOverride?: string) => {
+ const payload: Record = {};
+ if (id) {
+ payload.categories = id;
+ }
+ if (roleOverride) {
+ payload.utm_user_role = roleOverride;
+ }
+ return `${TEMPLATES_URLS.BASE_WEBSITE_URL}/?${this.websiteTemplateRepositoryParameters(payload).toString()}`;
};
},
},
diff --git a/packages/editor-ui/src/utils/__tests__/pairedItemUtils.test.ts b/packages/editor-ui/src/utils/__tests__/pairedItemUtils.test.ts
index 12074d941f..6d41511c70 100644
--- a/packages/editor-ui/src/utils/__tests__/pairedItemUtils.test.ts
+++ b/packages/editor-ui/src/utils/__tests__/pairedItemUtils.test.ts
@@ -11,7 +11,7 @@ const MOCK_EXECUTION: Partial = {
startData: {},
resultData: {
runData: {
- 'When clicking "Test workflow"': [
+ 'When clicking ‘Test workflow’': [
{
startTime: 1706027170005,
executionTime: 0,
@@ -24,7 +24,7 @@ const MOCK_EXECUTION: Partial = {
{
startTime: 1706027170005,
executionTime: 1,
- source: [{ previousNode: 'When clicking "Test workflow"' }],
+ source: [{ previousNode: 'When clicking ‘Test workflow’' }],
executionStatus: 'success',
data: {
main: [
@@ -258,54 +258,54 @@ describe('pairedItemUtils', () => {
const actual = getPairedItemsMapping(MOCK_EXECUTION);
const expected = {
DebugHelper_r0_o0_i0: new Set([
- 'When clicking "Test workflow"_r0_o0_i0',
+ 'When clicking ‘Test workflow’_r0_o0_i0',
'If_r0_o0_i0',
'Edit Fields_r1_o0_i0',
'Edit Fields1_r1_o0_i0',
]),
DebugHelper_r0_o0_i1: new Set([
- 'When clicking "Test workflow"_r0_o0_i0',
+ 'When clicking ‘Test workflow’_r0_o0_i0',
'If_r0_o1_i0',
'Edit Fields_r0_o0_i0',
'Edit Fields1_r0_o0_i0',
]),
'Edit Fields1_r0_o0_i0': new Set([
- 'When clicking "Test workflow"_r0_o0_i0',
+ 'When clicking ‘Test workflow’_r0_o0_i0',
'DebugHelper_r0_o0_i1',
'If_r0_o1_i0',
'Edit Fields_r0_o0_i0',
]),
'Edit Fields1_r1_o0_i0': new Set([
- 'When clicking "Test workflow"_r0_o0_i0',
+ 'When clicking ‘Test workflow’_r0_o0_i0',
'DebugHelper_r0_o0_i0',
'If_r0_o0_i0',
'Edit Fields_r1_o0_i0',
]),
'Edit Fields_r0_o0_i0': new Set([
- 'When clicking "Test workflow"_r0_o0_i0',
+ 'When clicking ‘Test workflow’_r0_o0_i0',
'DebugHelper_r0_o0_i1',
'If_r0_o1_i0',
'Edit Fields1_r0_o0_i0',
]),
'Edit Fields_r1_o0_i0': new Set([
- 'When clicking "Test workflow"_r0_o0_i0',
+ 'When clicking ‘Test workflow’_r0_o0_i0',
'DebugHelper_r0_o0_i0',
'If_r0_o0_i0',
'Edit Fields1_r1_o0_i0',
]),
If_r0_o0_i0: new Set([
- 'When clicking "Test workflow"_r0_o0_i0',
+ 'When clicking ‘Test workflow’_r0_o0_i0',
'DebugHelper_r0_o0_i0',
'Edit Fields_r1_o0_i0',
'Edit Fields1_r1_o0_i0',
]),
If_r0_o1_i0: new Set([
- 'When clicking "Test workflow"_r0_o0_i0',
+ 'When clicking ‘Test workflow’_r0_o0_i0',
'DebugHelper_r0_o0_i1',
'Edit Fields_r0_o0_i0',
'Edit Fields1_r0_o0_i0',
]),
- 'When clicking "Test workflow"_r0_o0_i0': new Set([
+ 'When clicking ‘Test workflow’_r0_o0_i0': new Set([
'DebugHelper_r0_o0_i0',
'DebugHelper_r0_o0_i1',
'If_r0_o0_i0',
diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue
index 0b8d4a8e91..518610f734 100644
--- a/packages/editor-ui/src/views/NodeView.vue
+++ b/packages/editor-ui/src/views/NodeView.vue
@@ -250,6 +250,7 @@ import {
UPDATE_WEBHOOK_ID_NODE_TYPES,
TIME,
AI_ASSISTANT_LOCAL_STORAGE_KEY,
+ CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT,
} from '@/constants';
import useGlobalLinkActions from '@/composables/useGlobalLinkActions';
@@ -395,6 +396,7 @@ import type { ProjectSharingData } from '@/features/projects/projects.types';
import { useAIStore } from '@/stores/ai.store';
import { useStorage } from '@/composables/useStorage';
import { isJSPlumbEndpointElement } from '@/utils/typeGuards';
+import { usePostHog } from '@/stores/posthog.store';
import { ProjectTypes } from '@/features/projects/projects.utils';
interface AddNodeOptions {
@@ -944,6 +946,17 @@ export default defineComponent({
action: this.openSelectiveNodeCreator,
});
+ this.registerCustomAction({
+ key: 'showNodeCreator',
+ action: () => {
+ this.ndvStore.activeNodeName = null;
+
+ void this.$nextTick(() => {
+ this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.TAB);
+ });
+ },
+ });
+
this.readOnlyEnvRouteCheck();
this.canvasStore.isDemo = this.isDemo;
},
@@ -1177,12 +1190,6 @@ export default defineComponent({
? this.$locale.baseText('nodeView.addOrEnableTriggerNode')
: this.$locale.baseText('nodeView.addATriggerNodeFirst');
- this.registerCustomAction({
- key: 'showNodeCreator',
- action: () =>
- this.showTriggerCreator(NODE_CREATOR_OPEN_SOURCES.NO_TRIGGER_EXECUTION_TOOLTIP),
- });
-
const notice = this.showMessage({
type: 'info',
title: this.$locale.baseText('nodeView.cantExecuteNoTrigger'),
@@ -1257,9 +1264,15 @@ export default defineComponent({
},
showTriggerCreator(source: NodeCreatorOpenSource) {
if (this.createNodeActive) return;
+
+ this.ndvStore.activeNodeName = null;
this.nodeCreatorStore.setSelectedView(TRIGGER_NODE_CREATOR_VIEW);
this.nodeCreatorStore.setShowScrim(true);
- this.onToggleNodeCreator({ source, createNodeActive: true });
+ this.onToggleNodeCreator({
+ source,
+ createNodeActive: true,
+ nodeCreatorView: TRIGGER_NODE_CREATOR_VIEW,
+ });
},
async openExecution(executionId: string) {
this.canvasStore.startLoading();
@@ -3659,6 +3672,7 @@ export default defineComponent({
this.workflowsStore.workflow.scopes = scopes;
},
async newWorkflow(): Promise {
+ const { getVariant } = usePostHog();
this.canvasStore.startLoading();
this.resetWorkspace();
this.workflowData = await this.workflowsStore.getNewWorkflowData(
@@ -3670,15 +3684,24 @@ export default defineComponent({
this.uiStore.stateIsDirty = false;
this.canvasStore.setZoomLevel(1, [0, 0]);
- await this.tryToAddWelcomeSticky();
+ this.canvasStore.zoomToFit();
this.uiStore.nodeViewInitialized = true;
this.historyStore.reset();
this.executionsStore.activeExecution = null;
this.makeNewWorkflowShareable();
this.canvasStore.stopLoading();
- },
- async tryToAddWelcomeSticky(): Promise {
- this.canvasStore.zoomToFit();
+
+ // Pre-populate the canvas with the manual trigger node if the experiment is enabled and the user is in the variant group
+ if (
+ getVariant(CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.name) ===
+ CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.variant
+ ) {
+ const manualTriggerNode = this.canvasStore.getAutoAddManualTriggerNode();
+ if (manualTriggerNode) {
+ await this.addNodes([manualTriggerNode]);
+ this.uiStore.lastSelectedNode = manualTriggerNode.name;
+ }
+ }
},
async initView(): Promise {
if (this.$route.params.action === 'workflowSave') {
@@ -5375,4 +5398,3 @@ export default defineComponent({
);
}
-, IRun, IPushDataExecutionFinished
diff --git a/packages/nodes-base/nodes/Files/ConvertToFile/test/toText.workflow.json b/packages/nodes-base/nodes/Files/ConvertToFile/test/toText.workflow.json
index 3a855ccf9a..ea9b0fc6a9 100644
--- a/packages/nodes-base/nodes/Files/ConvertToFile/test/toText.workflow.json
+++ b/packages/nodes-base/nodes/Files/ConvertToFile/test/toText.workflow.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "59f5ae0f-52f7-4bc8-b325-29d2b0d810f8",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -217,7 +217,7 @@
]
},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/packages/nodes-base/nodes/GraphQL/test/workflow.json b/packages/nodes-base/nodes/GraphQL/test/workflow.json
index f78c655da4..690171afc5 100644
--- a/packages/nodes-base/nodes/GraphQL/test/workflow.json
+++ b/packages/nodes-base/nodes/GraphQL/test/workflow.json
@@ -7,7 +7,7 @@
{
"parameters": {},
"id": "fb826323-2e48-4f11-bb0e-e12de32e22ee",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [180, 160]
@@ -26,7 +26,7 @@
}
],
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/packages/nodes-base/nodes/Jwt/test/jwt.workflow.json b/packages/nodes-base/nodes/Jwt/test/jwt.workflow.json
index 12cba9fefc..b87ef4e7e1 100644
--- a/packages/nodes-base/nodes/Jwt/test/jwt.workflow.json
+++ b/packages/nodes-base/nodes/Jwt/test/jwt.workflow.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "fcc3e9dc-90c9-4b26-9b44-e661e0ebf658",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -305,7 +305,7 @@
]
},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
@@ -409,4 +409,4 @@
},
"id": "H0sZEXDuE7VIP5vz",
"tags": []
-}
\ No newline at end of file
+}
diff --git a/packages/nodes-base/nodes/ManualTrigger/ManualTrigger.node.ts b/packages/nodes-base/nodes/ManualTrigger/ManualTrigger.node.ts
index 4072f6f69f..29f1dfd74f 100644
--- a/packages/nodes-base/nodes/ManualTrigger/ManualTrigger.node.ts
+++ b/packages/nodes-base/nodes/ManualTrigger/ManualTrigger.node.ts
@@ -16,7 +16,7 @@ export class ManualTrigger implements INodeType {
eventTriggerDescription: '',
maxNodes: 1,
defaults: {
- name: 'When clicking "Test workflow"',
+ name: 'When clicking ‘Test workflow’',
color: '#909298',
},
@@ -25,7 +25,7 @@ export class ManualTrigger implements INodeType {
properties: [
{
displayName:
- 'This node is where a manual workflow execution starts. To make one, go back to the canvas and click test workflow’',
+ 'This node is where the workflow execution starts (when you click the ‘test’ button on the canvas).
Explore other ways to trigger your workflow (e.g on a schedule, or a webhook)',
name: 'notice',
type: 'notice',
default: '',
diff --git a/packages/nodes-base/nodes/Merge/test/node/workflow.keep_non_matches.json b/packages/nodes-base/nodes/Merge/test/node/workflow.keep_non_matches.json
index 8c2f85552a..617e26b7b2 100644
--- a/packages/nodes-base/nodes/Merge/test/node/workflow.keep_non_matches.json
+++ b/packages/nodes-base/nodes/Merge/test/node/workflow.keep_non_matches.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "94003e55-6c4e-492f-802a-49f4fb5b5f4b",
- "name": "When clicking \"Test Workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -485,7 +485,7 @@
]
},
"connections": {
- "When clicking \"Test Workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/packages/nodes-base/nodes/Switch/V3/test/switch.regex.workflow.json b/packages/nodes-base/nodes/Switch/V3/test/switch.regex.workflow.json
index 1876c5e233..45269dcfc7 100644
--- a/packages/nodes-base/nodes/Switch/V3/test/switch.regex.workflow.json
+++ b/packages/nodes-base/nodes/Switch/V3/test/switch.regex.workflow.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "1301e15e-7a64-44bf-bc4b-d60e7b8c629a",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
@@ -273,7 +273,7 @@
]
},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{
diff --git a/packages/workflow/test/TelemetryHelpers.test.ts b/packages/workflow/test/TelemetryHelpers.test.ts
index 11f741e79a..01f6c252ec 100644
--- a/packages/workflow/test/TelemetryHelpers.test.ts
+++ b/packages/workflow/test/TelemetryHelpers.test.ts
@@ -667,7 +667,7 @@ describe('generateNodesGraph', () => {
{
parameters: {},
id: 'fe69383c-e418-4f98-9c0e-924deafa7f93',
- name: 'When clicking "Test workflow"',
+ name: 'When clicking ‘Test workflow’',
type: 'n8n-nodes-base.manualTrigger',
typeVersion: 1,
position: [540, 220],
@@ -692,7 +692,7 @@ describe('generateNodesGraph', () => {
},
],
connections: {
- 'When clicking "Test workflow"': {
+ 'When clicking ‘Test workflow’': {
main: [
[
{
@@ -758,7 +758,7 @@ describe('generateNodesGraph', () => {
is_pinned: false,
},
nameIndices: {
- 'When clicking "Test workflow"': '0',
+ 'When clicking ‘Test workflow’': '0',
Chain: '1',
Model: '2',
},
diff --git a/packages/workflow/test/fixtures/WorkflowDataProxy/errors_run.json b/packages/workflow/test/fixtures/WorkflowDataProxy/errors_run.json
index 6ee89f092b..28864f7c4a 100644
--- a/packages/workflow/test/fixtures/WorkflowDataProxy/errors_run.json
+++ b/packages/workflow/test/fixtures/WorkflowDataProxy/errors_run.json
@@ -3,7 +3,7 @@
"startData": {},
"resultData": {
"runData": {
- "When clicking \"Test workflow\"": [
+ "When clicking ‘Test workflow’": [
{
"startTime": 1707471743600,
"executionTime": 1,
@@ -29,7 +29,7 @@
"executionTime": 1,
"source": [
{
- "previousNode": "When clicking \"Test workflow\""
+ "previousNode": "When clicking ‘Test workflow’"
}
],
"executionStatus": "success",
@@ -956,7 +956,7 @@
"source": {
"main": [
{
- "previousNode": "When clicking \"Test workflow\""
+ "previousNode": "When clicking ‘Test workflow’"
}
]
}
@@ -999,7 +999,7 @@
"source": {
"main": [
{
- "previousNode": "When clicking \"Test workflow\""
+ "previousNode": "When clicking ‘Test workflow’"
}
]
}
diff --git a/packages/workflow/test/fixtures/WorkflowDataProxy/errors_workflow.json b/packages/workflow/test/fixtures/WorkflowDataProxy/errors_workflow.json
index fd93f3374c..088946a843 100644
--- a/packages/workflow/test/fixtures/WorkflowDataProxy/errors_workflow.json
+++ b/packages/workflow/test/fixtures/WorkflowDataProxy/errors_workflow.json
@@ -4,7 +4,7 @@
{
"parameters": {},
"id": "b5122d27-4bb5-4100-a69b-03b1dcac76c7",
- "name": "When clicking \"Test workflow\"",
+ "name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [740, 1680]
@@ -561,7 +561,7 @@
]
},
"connections": {
- "When clicking \"Test workflow\"": {
+ "When clicking ‘Test workflow’": {
"main": [
[
{