feat(core): Add support for building LLM applications (#7235)

This extracts all core and editor changes from #7246 and #7137, so that
we can get these changes merged first.

ADO-1120

[DB Tests](https://github.com/n8n-io/n8n/actions/runs/6379749011)
[E2E Tests](https://github.com/n8n-io/n8n/actions/runs/6379751480)
[Workflow Tests](https://github.com/n8n-io/n8n/actions/runs/6379752828)

---------

Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
Co-authored-by: Oleg Ivaniv <me@olegivaniv.com>
Co-authored-by: Alex Grozav <alex@grozav.com>
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2023-10-02 17:33:43 +02:00
committed by GitHub
parent 04dfcd73be
commit 00a4b8b0c6
93 changed files with 6209 additions and 728 deletions

View File

@@ -18,11 +18,7 @@ import type {
DragStopEventParams,
} from '@jsplumb/browser-ui';
import { newInstance } from '@jsplumb/browser-ui';
import { N8nPlusEndpointHandler } from '@/plugins/endpoints/N8nPlusEndpointType';
import * as N8nPlusEndpointRenderer from '@/plugins/endpoints/N8nPlusEndpointRenderer';
import { N8nConnector } from '@/plugins/connectors/N8nCustomConnector';
import type { Connection } from '@jsplumb/core';
import { EndpointFactory, Connectors } from '@jsplumb/core';
import { MoveNodeCommand } from '@/models/history';
import {
DEFAULT_PLACEHOLDER_TRIGGER_BUTTON,
@@ -67,10 +63,6 @@ export const useCanvasStore = defineStore('canvas', () => {
}
});
Connectors.register(N8nConnector.type, N8nConnector);
N8nPlusEndpointRenderer.register();
EndpointFactory.registerHandler(N8nPlusEndpointHandler);
const setRecenteredCanvasAddButtonPosition = (offset?: XYPosition) => {
const position = getMidCanvasPosition(nodeViewScale.value, offset || [0, 0]);

View File

@@ -8,6 +8,7 @@ import type {
XYPosition,
} from '@/Interface';
import type { INodeIssues, IRunData } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import { defineStore } from 'pinia';
import { v4 as uuid } from 'uuid';
import { useWorkflowsStore } from './workflows.store';
@@ -124,7 +125,7 @@ export const useNDVStore = defineStore(STORES.NDV, {
return false;
}
const workflow = useWorkflowsStore().getCurrentWorkflow();
const parentNodes = workflow.getParentNodes(this.activeNode.name, 'main', 1);
const parentNodes = workflow.getParentNodes(this.activeNode.name, NodeConnectionType.Main, 1);
return parentNodes.includes(inputNodeName);
},
hoveringItemNumber(): number {
@@ -139,6 +140,9 @@ export const useNDVStore = defineStore(STORES.NDV, {
},
},
actions: {
setActiveNodeName(nodeName: string | null): void {
this.activeNodeName = nodeName;
},
setInputNodeName(nodeName: string | undefined): void {
this.input = {
...this.input,

View File

@@ -7,7 +7,8 @@ import type {
ActionsRecord,
} from '@/Interface';
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { transformNodeType } from '@/components/Node/NodeCreator/utils';
export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
const selectedView = ref<NodeFilterType>(TRIGGER_NODE_CREATOR_VIEW);
@@ -17,6 +18,10 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
const showScrim = ref(false);
const openSource = ref<NodeCreatorOpenSource>('');
const allNodeCreatorNodes = computed(() =>
Object.values(mergedNodes.value).map((i) => transformNodeType(i)),
);
function setMergeNodes(nodes: SimplifiedNodeType[]) {
mergedNodes.value = nodes;
}
@@ -48,5 +53,6 @@ export const useNodeCreatorStore = defineStore(STORES.NODE_CREATOR, () => {
setOpenSource,
setActions,
setMergeNodes,
allNodeCreatorNodes,
};
});

View File

@@ -16,17 +16,22 @@ import { addHeaders, addNodeTranslation } from '@/plugins/i18n';
import { omit } from '@/utils';
import type {
ILoadOptions,
INode,
INodeCredentials,
INodeListSearchResult,
INodeOutputConfiguration,
INodeParameters,
INodePropertyOptions,
INodeTypeDescription,
INodeTypeNameVersion,
ResourceMapperFields,
Workflow,
ConnectionTypes,
} from 'n8n-workflow';
import { defineStore } from 'pinia';
import { useCredentialsStore } from './credentials.store';
import { useRootStore } from './n8nRoot.store';
import { NodeHelpers, NodeConnectionType } from 'n8n-workflow';
function getNodeVersions(nodeType: INodeTypeDescription) {
return Array.isArray(nodeType.version) ? nodeType.version : [nodeType.version];
@@ -73,6 +78,34 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
return nodeType || null;
};
},
isConfigNode() {
return (workflow: Workflow, node: INode, nodeTypeName: string): boolean => {
const nodeType = this.getNodeType(nodeTypeName);
if (!nodeType) {
return false;
}
const outputs = NodeHelpers.getNodeOutputs(workflow, node, nodeType);
const outputTypes = NodeHelpers.getConnectionTypes(outputs);
return outputTypes
? outputTypes.filter((output) => output !== NodeConnectionType.Main).length > 0
: false;
};
},
isConfigurableNode() {
return (workflow: Workflow, node: INode, nodeTypeName: string): boolean => {
const nodeType = this.getNodeType(nodeTypeName);
if (nodeType === null) {
return false;
}
const inputs = NodeHelpers.getNodeInputs(workflow, node, nodeType);
const inputTypes = NodeHelpers.getConnectionTypes(inputs);
return inputTypes
? inputTypes.filter((input) => input !== NodeConnectionType.Main).length > 0
: false;
};
},
isTriggerNode() {
return (nodeTypeName: string) => {
const nodeType = this.getNodeType(nodeTypeName);
@@ -96,6 +129,48 @@ export const useNodeTypesStore = defineStore(STORES.NODE_TYPES, {
return acc;
}, []);
},
visibleNodeTypesByOutputConnectionTypeNames(): { [key: string]: string[] } {
const nodesByOutputType = this.visibleNodeTypes.reduce(
(acc, node) => {
const outputTypes = node.outputs;
if (Array.isArray(outputTypes)) {
outputTypes.forEach((value: ConnectionTypes | INodeOutputConfiguration) => {
const outputType = typeof value === 'string' ? value : value.type;
if (!acc[outputType]) {
acc[outputType] = [];
}
acc[outputType].push(node.name);
});
}
return acc;
},
{} as { [key: string]: string[] },
);
return nodesByOutputType;
},
visibleNodeTypesByInputConnectionTypeNames(): { [key: string]: string[] } {
const nodesByOutputType = this.visibleNodeTypes.reduce(
(acc, node) => {
const inputTypes = node.inputs;
if (Array.isArray(inputTypes)) {
inputTypes.forEach((value: ConnectionTypes | INodeOutputConfiguration) => {
const outputType = typeof value === 'string' ? value : value.type;
if (!acc[outputType]) {
acc[outputType] = [];
}
acc[outputType].push(node.name);
});
}
return acc;
},
{} as { [key: string]: string[] },
);
return nodesByOutputType;
},
},
actions: {
setNodeTypes(newNodeTypes: INodeTypeDescription[] = []): void {

View File

@@ -57,7 +57,11 @@ export const useSegment = defineStore('segment', () => {
const nodeRunData = runData.data.resultData.runData[nodeName];
const node = workflowsStore.getNodeByName(nodeName);
const nodeTypeName = node ? node.type : 'unknown';
if (nodeRunData[0].data && nodeRunData[0].data.main.some((out) => out && out?.length > 1)) {
if (
nodeRunData[0].data &&
nodeRunData[0].data.main &&
nodeRunData[0].data.main.some((out) => out && out?.length > 1)
) {
multipleOutputNodes.add(nodeTypeName);
}
if (node && !node.disabled) {

View File

@@ -5,6 +5,7 @@ import {
} from '@/api/workflow-webhooks';
import {
ABOUT_MODAL_KEY,
CHAT_EMBED_MODAL_KEY,
CHANGE_PASSWORD_MODAL_KEY,
COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY,
COMMUNITY_PACKAGE_INSTALL_MODAL_KEY,
@@ -27,6 +28,7 @@ import {
VERSIONS_MODAL_KEY,
VIEWS,
WORKFLOW_ACTIVE_MODAL_KEY,
WORKFLOW_LM_CHAT_MODAL_KEY,
WORKFLOW_SETTINGS_MODAL_KEY,
WORKFLOW_SHARE_MODAL_KEY,
EXTERNAL_SECRETS_PROVIDER_MODAL_KEY,
@@ -69,6 +71,9 @@ export const useUIStore = defineStore(STORES.UI, {
[ABOUT_MODAL_KEY]: {
open: false,
},
[CHAT_EMBED_MODAL_KEY]: {
open: false,
},
[CHANGE_PASSWORD_MODAL_KEY]: {
open: false,
},
@@ -103,6 +108,9 @@ export const useUIStore = defineStore(STORES.UI, {
[VERSIONS_MODAL_KEY]: {
open: false,
},
[WORKFLOW_LM_CHAT_MODAL_KEY]: {
open: false,
},
[WORKFLOW_SETTINGS_MODAL_KEY]: {
open: false,
},

View File

@@ -125,7 +125,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
workflowsById: {},
subWorkflowExecutionError: null,
activeExecutionId: null,
executingNode: null,
executingNode: [],
executionWaitingForWebhook: false,
nodeMetadata: {},
isInDebugMode: false,
@@ -262,6 +262,9 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
return (nodeName: string) =>
this.nodeMetadata[nodeName] === undefined || this.nodeMetadata[nodeName].pristine;
},
isNodeExecuting(): (nodeName: string) => boolean {
return (nodeName: string) => this.executingNode.includes(nodeName);
},
// Executions getters
getExecutionDataById(): (id: string) => IExecutionsSummary | undefined {
return (id: string): IExecutionsSummary | undefined =>
@@ -434,10 +437,18 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
this.setWorkflowTagIds([]);
this.activeExecutionId = null;
this.executingNode = null;
this.executingNode.length = 0;
this.executionWaitingForWebhook = false;
},
addExecutingNode(nodeName: string): void {
this.executingNode.push(nodeName);
},
removeExecutingNode(nodeName: string): void {
this.executingNode = this.executingNode.filter((name) => name !== nodeName);
},
setWorkflowId(id: string): void {
this.workflow.id = id === 'new' ? PLACEHOLDER_EMPTY_WORKFLOW_ID : id;
},