Skip to content

Commit

Permalink
feat(core): Update LLM applications building support (no-changelog) (n…
Browse files Browse the repository at this point in the history
…8n-io#7418)

extracted out of n8n-io#7336

---------

Co-authored-by: Jan Oberhauser <[email protected]>
Co-authored-by: OlegIvaniv <[email protected]>
Co-authored-by: Jan Oberhauser <[email protected]>
Co-authored-by: Val <[email protected]>
Co-authored-by: Alex Grozav <[email protected]>
Co-authored-by: Deborah <[email protected]>
Co-authored-by: Jesper Bylund <[email protected]>
Co-authored-by: Jon <[email protected]>
  • Loading branch information
9 people authored Oct 20, 2023
1 parent 34f3f80 commit 91dfc4d
Show file tree
Hide file tree
Showing 14 changed files with 93 additions and 74 deletions.
6 changes: 5 additions & 1 deletion packages/core/src/NodeExecuteFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2469,6 +2469,10 @@ const addExecutionDataFunctions = async (
});
}

if (get(runExecutionData, 'executionData.metadata', undefined) === undefined) {
runExecutionData.executionData!.metadata = {};
}

let sourceTaskData = get(runExecutionData, `executionData.metadata[${sourceNodeName}]`);

if (!sourceTaskData) {
Expand Down Expand Up @@ -3033,7 +3037,7 @@ export function getExecuteFunctions(
};

try {
return await nodeType.supplyData.call(context);
return await nodeType.supplyData.call(context, itemIndex);
} catch (error) {
if (!(error instanceof ExecutionBaseError)) {
error = new NodeOperationError(connectedNode, error, {
Expand Down
4 changes: 2 additions & 2 deletions packages/editor-ui/src/components/ChatEmbedModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ import { createChat } from '@n8n/chat';`,
}));
const cdnCode = computed(
() => `<link href="https://cdn.jsdelivr.net/npm/@n8n/chat/style.css" type="text/css" />
() => `<link href="https://cdn.jsdelivr.net/npm/@n8n/chat/style.css" rel="stylesheet" />
<script type="module">
import { createChat } from 'https://cdn.jsdelivr.net/npm/@n8n/chat/chat.js';
import { createChat } from 'https://cdn.jsdelivr.net/npm/@n8n/chat/chat.bundle.es.js';
${commonCode.value.createChat}
</${'script'}>`,
Expand Down
19 changes: 17 additions & 2 deletions packages/editor-ui/src/components/NodeDetailsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,21 @@ export default defineComponent({
return [];
},
parentNode(): string | undefined {
const pinData = this.workflowsStore.getPinData;
// Return the first parent node that contains data
for (const parentNodeName of this.parentNodes) {
// Check first for pinned data
if (pinData[parentNodeName]) {
return parentNodeName;
}
// Check then the data of the current execution
if (this.workflowRunData?.[parentNodeName]) {
return parentNodeName;
}
}
return this.parentNodes[0];
},
isExecutableTriggerNode(): boolean {
Expand Down Expand Up @@ -617,8 +632,8 @@ export default defineComponent({
}
if (
typeof this.activeNodeType.outputs === 'string' ||
typeof this.activeNodeType.inputs === 'string'
typeof this.activeNodeType?.outputs === 'string' ||
typeof this.activeNodeType?.inputs === 'string'
) {
// TODO: We should keep track of if it actually changed and only do if required
// Whenever a node with custom inputs and outputs gets closed redraw it in case
Expand Down
3 changes: 3 additions & 0 deletions packages/editor-ui/src/components/NodeExecuteButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ export default defineComponent({
return this.uiStore.isActionActive('workflowRunning');
},
isTriggerNode(): boolean {
if (!this.node) {
return false;
}
return this.nodeTypesStore.isTriggerNode(this.node.type);
},
isManualTriggerNode(): boolean {
Expand Down
16 changes: 10 additions & 6 deletions packages/editor-ui/src/components/NodeSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,17 @@ export default defineComponent({
return this.readOnly || this.hasForeignCredential;
},
isExecutable(): boolean {
if (
this.nodeType &&
!this.isTriggerNode &&
!this.nodeType.inputs.includes(NodeConnectionType.Main)
) {
return false;
if (this.nodeType && this.node) {
const workflow = this.workflowsStore.getCurrentWorkflow();
const workflowNode = workflow.getNode(this.node.name);
const inputs = NodeHelpers.getNodeInputs(workflow, workflowNode!, this.nodeType!);
const inputNames = NodeHelpers.getConnectionTypes(inputs);
if (!inputNames.includes(NodeConnectionType.Main) && !this.isTriggerNode) {
return false;
}
}
return this.executable || this.hasForeignCredential;
},
nodeTypeName(): string {
Expand Down
6 changes: 3 additions & 3 deletions packages/editor-ui/src/components/OutputPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
</template>

<template #content v-if="outputMode === 'logs'">
<run-data-ai :node="node" />
<run-data-ai :node="node" :run-index="runIndex" />
</template>
<template #recovered-artificial-output-data>
<div :class="$style.recoveredOutputData">
Expand Down Expand Up @@ -184,11 +184,11 @@ export default defineComponent({
if (this.node) {
const resultData = this.workflowsStore.getWorkflowResultDataByNodeName(this.node.name);
if (!resultData || !Array.isArray(resultData)) {
if (!resultData || !Array.isArray(resultData) || resultData.length === 0) {
return false;
}
return !!resultData[resultData.length - 1!].metadata;
return !!resultData[resultData.length - 1].metadata;
}
return false;
},
Expand Down
9 changes: 2 additions & 7 deletions packages/editor-ui/src/components/RunData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -702,14 +702,9 @@ export default defineComponent({
const workflow = this.workflowsStore.getCurrentWorkflow();
const workflowNode = workflow.getNode(this.node.name);
const inputs = NodeHelpers.getNodeInputs(workflow, workflowNode!, this.nodeType!);
const inputNames = NodeHelpers.getConnectionTypes(inputs);
const nonMainInputs = !!inputs.find((input) => {
if (typeof input === 'string') {
return input !== NodeConnectionType.Main;
}
return input.type !== NodeConnectionType.Main;
});
const nonMainInputs = !!inputNames.find((inputName) => inputName !== NodeConnectionType.Main);
return (
!nonMainInputs &&
Expand Down
30 changes: 14 additions & 16 deletions packages/editor-ui/src/components/RunDataJson.vue
Original file line number Diff line number Diff line change
Expand Up @@ -161,32 +161,30 @@ export default defineComponent({
});
},
onDragStart(el: HTMLElement) {
if (el && el.dataset.path) {
if (el?.dataset.path) {
this.draggingPath = el.dataset.path;
}
this.ndvStore.resetMappingTelemetry();
},
onDragEnd(el: HTMLElement) {
this.draggingPath = null;
const mappingTelemetry = this.ndvStore.mappingTelemetry;
const telemetryPayload = {
src_node_type: this.node.type,
src_field_name: el.dataset.name || '',
src_nodes_back: this.distanceFromActive,
src_run_index: this.runIndex,
src_runs_total: this.totalRuns,
src_field_nest_level: el.dataset.depth || 0,
src_view: 'json',
src_element: el,
success: false,
...mappingTelemetry,
};
setTimeout(() => {
const mappingTelemetry = this.ndvStore.mappingTelemetry;
const telemetryPayload = {
src_node_type: this.node.type,
src_field_name: el.dataset.name || '',
src_nodes_back: this.distanceFromActive,
src_run_index: this.runIndex,
src_runs_total: this.totalRuns,
src_field_nest_level: el.dataset.depth || 0,
src_view: 'json',
src_element: el,
success: false,
...mappingTelemetry,
};
void this.$externalHooks().run('runDataJson.onDragEnd', telemetryPayload);
this.$telemetry.track('User dragged data for mapping', telemetryPayload);
}, 1000); // ensure dest data gets set if drop
},
Expand Down
1 change: 0 additions & 1 deletion packages/editor-ui/src/components/VariableSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,6 @@ export default defineComponent({
const executionData = this.workflowsStore.getWorkflowExecution;
let parentNode = this.workflow.getParentNodes(this.activeNode.name, inputName, 1);
console.log('parentNode', parentNode);
let runData = this.workflowsStore.getWorkflowRunData;
if (runData === null) {
Expand Down
33 changes: 14 additions & 19 deletions packages/editor-ui/src/components/WorkflowLMChat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,8 @@ import { get, last } from 'lodash-es';
import { useUIStore, useWorkflowsStore } from '@/stores';
import { createEventBus } from 'n8n-design-system/utils';
import {
type INode,
type INodeType,
type ITaskData,
NodeHelpers,
NodeConnectionType,
} from 'n8n-workflow';
import type { IDataObject, INodeType, INode, ITaskData } from 'n8n-workflow';
import { NodeHelpers, NodeConnectionType } from 'n8n-workflow';
import type { INodeUi } from '@/Interface';
const RunDataAi = defineAsyncComponent(async () => import('@/components/RunDataAi/RunDataAi.vue'));
Expand Down Expand Up @@ -433,6 +428,17 @@ export default defineComponent({
this.waitForExecution(response.executionId);
},
extractResponseMessage(responseData?: IDataObject) {
if (!responseData) return '<NO RESPONSE FOUND>';
// Paths where the response message might be located
const paths = ['output', 'text', 'response.text'];
const matchedPath = paths.find((path) => get(responseData, path));
if (!matchedPath) return JSON.stringify(responseData, null, 2);
return get(responseData, matchedPath) as string;
},
waitForExecution(executionId?: string) {
const that = this;
const waitInterval = setInterval(() => {
Expand All @@ -455,18 +461,7 @@ export default defineComponent({
responseMessage = '[ERROR: ' + get(nodeResponseData, ['error', 'message']) + ']';
} else {
const responseData = get(nodeResponseData, 'data.main[0][0].json');
if (responseData) {
const responseObj = responseData as object & { output?: string };
if (responseObj.output !== undefined) {
responseMessage = responseObj.output;
} else if (Object.keys(responseObj).length === 0) {
responseMessage = '<NO RESPONSE FOUND>';
} else {
responseMessage = JSON.stringify(responseObj, null, 2);
}
} else {
responseMessage = '<NO RESPONSE FOUND>';
}
responseMessage = this.extractResponseMessage(responseData);
}
this.messages.push({
Expand Down
2 changes: 1 addition & 1 deletion packages/editor-ui/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export const NON_ACTIVATABLE_TRIGGER_NODE_TYPES = [
MANUAL_CHAT_TRIGGER_NODE_TYPE,
];

export const NODES_USING_CODE_NODE_EDITOR = [CODE_NODE_TYPE];
export const NODES_USING_CODE_NODE_EDITOR = [CODE_NODE_TYPE, AI_CODE_NODE_TYPE];

export const PIN_DATA_NODE_TYPES_DENYLIST = [SPLIT_IN_BATCHES_NODE_TYPE];

Expand Down
22 changes: 12 additions & 10 deletions packages/editor-ui/src/mixins/workflowHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,22 +114,22 @@ export function resolveParameter(
let itemIndex = opts?.targetItem?.itemIndex || 0;

const inputName = NodeConnectionType.Main;
let activeNode = useNDVStore().activeNode;
const activeNode = useNDVStore().activeNode;
let contextNode = activeNode;

const workflow = getCurrentWorkflow();

// Should actually just do that for incoming data and not things like parameters
if (activeNode) {
activeNode = getParentMainInputNode(workflow, activeNode);
contextNode = getParentMainInputNode(workflow, activeNode);
}

const workflowRunData = useWorkflowsStore().getWorkflowRunData;
let parentNode = workflow.getParentNodes(activeNode!.name, inputName, 1);
let parentNode = workflow.getParentNodes(contextNode!.name, inputName, 1);
const executionData = useWorkflowsStore().getWorkflowExecution;

let runIndexParent = opts?.inputRunIndex ?? 0;
const nodeConnection = workflow.getNodeConnectionIndexes(activeNode!.name, parentNode[0]);
if (opts.targetItem && opts?.targetItem?.nodeName === activeNode!.name && executionData) {
const nodeConnection = workflow.getNodeConnectionIndexes(contextNode!.name, parentNode[0]);
if (opts.targetItem && opts?.targetItem?.nodeName === contextNode!.name && executionData) {
const sourceItems = getSourceItems(executionData, opts.targetItem);
if (!sourceItems.length) {
return null;
Expand Down Expand Up @@ -158,7 +158,7 @@ export function resolveParameter(

let _connectionInputData = connectionInputData(
parentNode,
activeNode!.name,
contextNode!.name,
inputName,
runIndexParent,
nodeConnection,
Expand Down Expand Up @@ -198,11 +198,11 @@ export function resolveParameter(
if (
opts?.targetItem === undefined &&
workflowRunData !== null &&
workflowRunData[activeNode!.name]
workflowRunData[contextNode!.name]
) {
runIndexCurrent = workflowRunData[activeNode!.name].length - 1;
runIndexCurrent = workflowRunData[contextNode!.name].length - 1;
}
const _executeData = executeData(parentNode, activeNode!.name, inputName, runIndexCurrent);
const _executeData = executeData(parentNode, contextNode!.name, inputName, runIndexCurrent);

ExpressionEvaluatorProxy.setEvaluator(
useSettingsStore().settings.expressions?.evaluator ?? 'tmpl',
Expand All @@ -220,6 +220,8 @@ export function resolveParameter(
additionalKeys,
_executeData,
false,
{},
contextNode!.name,
) as IDataObject;
}

Expand Down
11 changes: 9 additions & 2 deletions packages/editor-ui/src/views/NodeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2211,6 +2211,7 @@ export default defineComponent({
if (lastSelectedNodeEndpointUuid && !isAutoAdd) {
const lastSelectedEndpoint = this.instance.getEndpoint(lastSelectedNodeEndpointUuid);
if (
lastSelectedEndpoint &&
this.checkNodeConnectionAllowed(
lastSelectedNode!,
newNodeData,
Expand Down Expand Up @@ -2382,7 +2383,14 @@ export default defineComponent({
);

if (targetNodeType?.inputs?.length) {
for (const input of targetNodeType?.inputs || []) {
const workflow = this.getCurrentWorkflow();
const workflowNode = workflow.getNode(targetNode.name);
let inputs: Array<ConnectionTypes | INodeInputConfiguration> = [];
if (targetNodeType) {
inputs = NodeHelpers.getNodeInputs(workflow, workflowNode!, targetNodeType);
}

for (const input of inputs || []) {
if (typeof input === 'string' || input.type !== targetInfoType || !input.filter) {
// No filters defined or wrong connection type
continue;
Expand Down Expand Up @@ -3356,7 +3364,6 @@ export default defineComponent({
nodeConnections || [],
(connectionType as ConnectionTypes) ?? NodeConnectionType.Main,
);

Object.keys(outputMap).forEach((sourceOutputIndex: string) => {
Object.keys(outputMap[sourceOutputIndex]).forEach((targetNodeName: string) => {
Object.keys(outputMap[sourceOutputIndex][targetNodeName]).forEach(
Expand Down
5 changes: 1 addition & 4 deletions packages/workflow/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,6 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn &
inputName: ConnectionTypes,
itemIndex: number,
inputIndex?: number,
nodeNameOverride?: string,
): Promise<unknown>;
getInputData(inputIndex?: number, inputName?: string): INodeExecutionData[];
getNodeOutputs(): INodeOutputConfiguration[];
Expand Down Expand Up @@ -1290,7 +1289,7 @@ export interface SupplyData {

export interface INodeType {
description: INodeTypeDescription;
supplyData?(this: IExecuteFunctions): Promise<SupplyData>;
supplyData?(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData>;
execute?(
this: IExecuteFunctions,
): Promise<INodeExecutionData[][] | NodeExecutionWithMetadata[][] | null>;
Expand Down Expand Up @@ -1534,8 +1533,6 @@ export const enum NodeConnectionType {
// eslint-disable-next-line @typescript-eslint/naming-convention
AiTool = 'ai_tool',
// eslint-disable-next-line @typescript-eslint/naming-convention
AiVectorRetriever = 'ai_vectorRetriever',
// eslint-disable-next-line @typescript-eslint/naming-convention
AiVectorStore = 'ai_vectorStore',
// eslint-disable-next-line @typescript-eslint/naming-convention
Main = 'main',
Expand Down

0 comments on commit 91dfc4d

Please sign in to comment.