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

@@ -30,7 +30,7 @@ import type {
INodeProperties,
IWorkflowSettings,
} from 'n8n-workflow';
import { NodeHelpers } from 'n8n-workflow';
import { NodeConnectionType, ExpressionEvaluatorProxy, NodeHelpers } from 'n8n-workflow';
import type {
INodeTypesMaxCount,
@@ -62,9 +62,45 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
import { useEnvironmentsStore } from '@/stores/environments.ee.store';
import { useUsersStore } from '@/stores/users.store';
import { useSettingsStore } from '@/stores/settings.store';
import { getWorkflowPermissions } from '@/permissions';
import type { IPermissions } from '@/permissions';
export function getParentMainInputNode(workflow: Workflow, node: INode): INode {
const nodeType = useNodeTypesStore().getNodeType(node.type);
if (nodeType) {
const outputs = NodeHelpers.getNodeOutputs(workflow, node, nodeType);
if (!!outputs.find((output) => output !== NodeConnectionType.Main)) {
// Get the first node which is connected to a non-main output
const nonMainNodesConnected = outputs?.reduce((acc, outputName) => {
const parentNodes = workflow.getChildNodes(node.name, outputName);
if (parentNodes.length > 0) {
acc.push(...parentNodes);
}
return acc;
}, [] as string[]);
if (nonMainNodesConnected.length) {
const returnNode = workflow.getNode(nonMainNodesConnected[0]);
if (returnNode === null) {
// This should theoretically never happen as the node is connected
// but who knows and it makes TS happy
throw new Error(
`The node "${nonMainNodesConnected[0]}" which is a connection of "${node.name}" could not be found!`,
);
}
// The chain of non-main nodes is potentially not finished yet so
// keep on going
return getParentMainInputNode(workflow, returnNode);
}
}
}
return node;
}
export function resolveParameter(
parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
opts: {
@@ -77,10 +113,16 @@ export function resolveParameter(
): IDataObject | null {
let itemIndex = opts?.targetItem?.itemIndex || 0;
const inputName = 'main';
const activeNode = useNDVStore().activeNode;
const inputName = NodeConnectionType.Main;
let activeNode = useNDVStore().activeNode;
const workflow = getCurrentWorkflow();
// Should actually just do that for incoming data and not things like parameters
if (activeNode) {
activeNode = getParentMainInputNode(workflow, activeNode);
}
const workflowRunData = useWorkflowsStore().getWorkflowRunData;
let parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1);
const executionData = useWorkflowsStore().getWorkflowExecution;
@@ -162,6 +204,10 @@ export function resolveParameter(
}
const _executeData = executeData(parentNode, activeNode!.name, inputName, runIndexCurrent);
ExpressionEvaluatorProxy.setEvaluator(
useSettingsStore().settings.expressions?.evaluator ?? 'tmpl',
);
return workflow.expression.getParameterValue(
parameter,
runExecutionData,
@@ -222,6 +268,34 @@ function getCurrentWorkflow(copyData?: boolean): Workflow {
return useWorkflowsStore().getCurrentWorkflow(copyData);
}
function getConnectedNodes(
direction: 'upstream' | 'downstream',
workflow: Workflow,
nodeName: string,
): string[] {
let checkNodes: string[];
if (direction === 'downstream') {
checkNodes = workflow.getChildNodes(nodeName);
} else if (direction === 'upstream') {
checkNodes = workflow.getParentNodes(nodeName);
} else {
throw new Error(`The direction "${direction}" is not supported!`);
}
// Find also all nodes which are connected to the child nodes via a non-main input
let connectedNodes: string[] = [];
checkNodes.forEach((checkNode) => {
connectedNodes = [
...connectedNodes,
checkNode,
...workflow.getParentNodes(checkNode, 'ALL_NON_MAIN'),
];
});
// Remove duplicates
return [...new Set(connectedNodes)];
}
function getNodes(): INodeUi[] {
return useWorkflowsStore().getNodes();
}
@@ -356,11 +430,33 @@ export function executeData(
[inputName]: workflowRunData[currentNode][runIndex].source,
};
} else {
const workflow = getCurrentWorkflow();
let previousNodeOutput: number | undefined;
// As the node can be connected through either of the outputs find the correct one
// and set it to make pairedItem work on not executed nodes
if (workflow.connectionsByDestinationNode[currentNode]?.main) {
mainConnections: for (const mainConnections of workflow.connectionsByDestinationNode[
currentNode
].main) {
for (const connection of mainConnections) {
if (
connection.type === NodeConnectionType.Main &&
connection.node === parentNodeName
) {
previousNodeOutput = connection.index;
break mainConnections;
}
}
}
}
// The current node did not get executed in UI yet so build data manually
executeData.source = {
[inputName]: [
{
previousNode: parentNodeName,
previousNodeOutput,
},
],
};
@@ -399,7 +495,9 @@ export const workflowHelpers = defineComponent({
resolveParameter,
resolveRequiredParameters,
getCurrentWorkflow,
getConnectedNodes,
getNodes,
getParentMainInputNode,
getWorkflow,
getNodeTypes,
connectionInputData,