feat(core): Update LLM applications building support (no-changelog) (#7418)

extracted out of #7336

---------

Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
Co-authored-by: OlegIvaniv <me@olegivaniv.com>
Co-authored-by: Jan Oberhauser <janober@users.noreply.github.com>
Co-authored-by: Val <68596159+valya@users.noreply.github.com>
Co-authored-by: Alex Grozav <alex@grozav.com>
Co-authored-by: Deborah <deborah@starfallprojects.co.uk>
Co-authored-by: Jesper Bylund <mail@jesperbylund.com>
Co-authored-by: Jon <jonathan.bennetts@gmail.com>
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-10-20 10:52:56 +02:00 committed by GitHub
parent 34f3f8001e
commit 91dfc4d513
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 93 additions and 74 deletions

View file

@ -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) {
@ -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, {

View file

@ -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'}>`,

View file

@ -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 {
@ -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

View file

@ -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 {

View file

@ -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 {

View file

@ -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">
@ -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;
},

View file

@ -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 &&

View file

@ -161,7 +161,7 @@ export default defineComponent({
});
},
onDragStart(el: HTMLElement) {
if (el && el.dataset.path) {
if (el?.dataset.path) {
this.draggingPath = el.dataset.path;
}
@ -169,24 +169,22 @@ export default defineComponent({
},
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
},

View file

@ -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) {

View file

@ -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'));
@ -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(() => {
@ -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({

View file

@ -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];

View file

@ -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;
@ -158,7 +158,7 @@ export function resolveParameter(
let _connectionInputData = connectionInputData(
parentNode,
activeNode!.name,
contextNode!.name,
inputName,
runIndexParent,
nodeConnection,
@ -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',
@ -220,6 +220,8 @@ export function resolveParameter(
additionalKeys,
_executeData,
false,
{},
contextNode!.name,
) as IDataObject;
}

View file

@ -2211,6 +2211,7 @@ export default defineComponent({
if (lastSelectedNodeEndpointUuid && !isAutoAdd) {
const lastSelectedEndpoint = this.instance.getEndpoint(lastSelectedNodeEndpointUuid);
if (
lastSelectedEndpoint &&
this.checkNodeConnectionAllowed(
lastSelectedNode!,
newNodeData,
@ -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;
@ -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(

View file

@ -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[];
@ -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>;
@ -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',