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

@@ -3,14 +3,14 @@
:nodeUi="currentNode"
:runIndex="runIndex"
:linkedRuns="linkedRuns"
:canLinkRuns="canLinkRuns"
:canLinkRuns="!mappedNode && canLinkRuns"
:tooMuchDataTitle="$locale.baseText('ndv.input.tooMuchData.title')"
:noDataInBranchMessage="$locale.baseText('ndv.input.noOutputDataInBranch')"
:isExecuting="isExecutingPrevious"
:executingMessage="$locale.baseText('ndv.input.executingPrevious')"
:sessionId="sessionId"
:overrideOutputs="connectedCurrentNodeOutputs"
:mappingEnabled="!readOnly"
:mappingEnabled="isMappingEnabled"
:distanceFromActive="currentNodeDepth"
:isProductionExecutionPreview="isProductionExecutionPreview"
paneType="input"
@@ -55,11 +55,43 @@
</n8n-option>
</n8n-select>
<span v-else :class="$style.title">{{ $locale.baseText('ndv.input') }}</span>
<n8n-radio-buttons
v-if="isActiveNodeConfig && !readOnly"
:options="inputModes"
:modelValue="inputMode"
@update:modelValue="onInputModeChange"
/>
</div>
</template>
<template #before-data v-if="isMappingMode">
<!--
Hide the run linking buttons for both input and ouput panels when in 'Mapping Mode' because the run indices wouldn't match.
Although this is not the most elegant solution, it's straightforward and simpler than introducing a new props and logic to handle this.
-->
<component :is="'style'">button.linkRun { display: none }</component>
<div :class="$style.mappedNode">
<n8n-select
:modelValue="mappedNode"
@update:modelValue="onMappedNodeSelected"
size="small"
@click.stop
teleported
>
<template #prepend>{{ $locale.baseText('ndv.input.previousNode') }}</template>
<n8n-option
v-for="nodeName in rootNodesParents"
:key="nodeName"
:label="nodeName"
:value="nodeName"
/>
</n8n-select>
</div>
</template>
<template #node-not-run>
<div :class="$style.noOutputData" v-if="parentNodes.length">
<div
:class="$style.noOutputData"
v-if="(isActiveNodeConfig && rootNode) || parentNodes.length"
>
<n8n-text tag="div" :bold="true" color="text-dark" size="large">{{
$locale.baseText('ndv.input.noOutputData.title')
}}</n8n-text>
@@ -76,7 +108,7 @@
<NodeExecuteButton
type="secondary"
:transparent="true"
:nodeName="currentNodeName"
:nodeName="isActiveNodeConfig ? rootNode : currentNodeName"
:label="$locale.baseText('ndv.input.noOutputData.executePrevious')"
@execute="onNodeExecute"
telemetrySource="inputs"
@@ -130,7 +162,8 @@
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import type { INodeUi } from '@/Interface';
import type { IConnectedNode, INodeTypeDescription, Workflow } from 'n8n-workflow';
import { NodeHelpers, NodeConnectionType } from 'n8n-workflow';
import type { ConnectionTypes, IConnectedNode, INodeTypeDescription, Workflow } from 'n8n-workflow';
import RunData from './RunData.vue';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import NodeExecuteButton from './NodeExecuteButton.vue';
@@ -145,6 +178,8 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNDVStore } from '@/stores/ndv.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
type MappingMode = 'debugging' | 'mapping';
export default defineComponent({
name: 'InputPanel',
mixins: [workflowHelpers],
@@ -178,6 +213,12 @@ export default defineComponent({
return {
showDraggableHintWithDelay: false,
draggableHintShown: false,
inputMode: 'debugging' as MappingMode,
mappedNode: null as string | null,
inputModes: [
{ value: 'mapping', label: this.$locale.baseText('ndv.input.mapping') },
{ value: 'debugging', label: this.$locale.baseText('ndv.input.debugging') },
],
};
},
computed: {
@@ -188,6 +229,9 @@ export default defineComponent({
isUserOnboarded(): boolean {
return this.ndvStore.isMappingOnboarded;
},
isMappingMode(): boolean {
return this.isActiveNodeConfig && this.inputMode === 'mapping';
},
showDraggableHint(): boolean {
const toIgnore = [
START_NODE_TYPE,
@@ -201,23 +245,59 @@ export default defineComponent({
return !!this.focusedMappableInput && !this.isUserOnboarded;
},
isActiveNodeConfig(): boolean {
let inputs = this.activeNodeType?.inputs ?? [];
let outputs = this.activeNodeType?.outputs ?? [];
if (this.activeNode !== null && this.currentWorkflow !== null) {
const node = this.currentWorkflow.getNode(this.activeNode.name);
inputs = NodeHelpers.getNodeInputs(this.currentWorkflow, node!, this.activeNodeType!);
outputs = NodeHelpers.getNodeOutputs(this.currentWorkflow, node!, this.activeNodeType!);
} else {
// If we can not figure out the node type we set no outputs
if (!Array.isArray(inputs)) {
inputs = [] as ConnectionTypes[];
}
if (!Array.isArray(outputs)) {
outputs = [] as ConnectionTypes[];
}
}
if (
(inputs.length === 0 ||
inputs.find((inputName) => inputName !== NodeConnectionType.Main)) &&
outputs.find((outputName) => outputName !== NodeConnectionType.Main)
) {
return true;
}
return false;
},
isMappingEnabled(): boolean {
if (this.readOnly) return false;
// Mapping is only enabled in mapping mode for config nodes and if node to map is selected
if (this.isActiveNodeConfig) return this.isMappingMode && this.mappedNode !== null;
return true;
},
isExecutingPrevious(): boolean {
if (!this.workflowRunning) {
return false;
}
const triggeredNode = this.workflowsStore.executedNode;
const executingNode = this.workflowsStore.executingNode;
if (
this.activeNode &&
triggeredNode === this.activeNode.name &&
this.activeNode.name !== executingNode
!this.workflowsStore.isNodeExecuting(this.activeNode.name)
) {
return true;
}
if (executingNode || triggeredNode) {
if (executingNode.length || triggeredNode) {
return !!this.parentNodes.find(
(node) => node.name === executingNode || node.name === triggeredNode,
(node) => this.workflowsStore.isNodeExecuting(node.name) || node.name === triggeredNode,
);
}
return false;
@@ -231,7 +311,31 @@ export default defineComponent({
activeNode(): INodeUi | null {
return this.ndvStore.activeNode;
},
rootNode(): string {
const workflow = this.currentWorkflow;
const rootNodes = workflow.getChildNodes(this.activeNode.name, 'ALL_NON_MAIN');
return rootNodes[0];
},
rootNodesParents(): string[] {
const workflow = this.currentWorkflow;
const parentNodes = [...workflow.getParentNodes(this.rootNode, 'main')].reverse();
return parentNodes;
},
currentNode(): INodeUi | null {
if (this.isActiveNodeConfig) {
// if we're mapping node we want to show the output of the mapped node
if (this.mappedNode) {
return this.workflowsStore.getNodeByName(this.mappedNode);
}
// in debugging mode data does get set manually and is only for debugging
// so we want to force the node to be the active node to make sure we show the correct data
return this.activeNode;
}
return this.workflowsStore.getNodeByName(this.currentNodeName);
},
connectedCurrentNodeOutputs(): number[] | undefined {
@@ -272,13 +376,21 @@ export default defineComponent({
},
},
methods: {
onInputModeChange(val: MappingMode) {
this.inputMode = val;
},
onMappedNodeSelected(val: string) {
this.mappedNode = val;
this.onRunIndexChange(0);
this.onUnlinkRun();
},
getMultipleNodesText(nodeName?: string): string {
if (
!nodeName ||
!this.isMultiInputNode ||
!this.activeNode ||
this.activeNodeType === null ||
this.activeNodeType.inputNames === undefined
this.activeNodeType?.inputNames === undefined
)
return '';
@@ -292,10 +404,7 @@ export default defineComponent({
// Match connected input indexes to their names specified by active node
const connectedInputs = connectedInputIndexes.map(
(inputIndex) =>
this.activeNodeType &&
this.activeNodeType.inputNames &&
this.activeNodeType.inputNames[inputIndex],
(inputIndex) => this.activeNodeType?.inputNames?.[inputIndex],
);
if (connectedInputs.length === 0) return '';
@@ -347,6 +456,16 @@ export default defineComponent({
},
},
watch: {
inputMode: {
handler(val) {
this.onRunIndexChange(-1);
if (val === 'mapping') {
this.onUnlinkRun();
this.mappedNode = this.rootNodesParents[0];
}
},
immediate: true,
},
showDraggableHint(curr: boolean, prev: boolean) {
if (curr && !prev) {
setTimeout(() => {
@@ -371,15 +490,22 @@ export default defineComponent({
</script>
<style lang="scss" module>
.mappedNode {
width: max-content;
padding: 0 var(--spacing-s) var(--spacing-s);
}
.titleSection {
display: flex;
max-width: 300px;
align-items: center;
> * {
margin-right: var(--spacing-2xs);
}
}
.inputModeTab {
margin-left: auto;
}
.noOutputData {
max-width: 180px;