import type { Ref } from 'vue';
import { computed, ref, watch } from 'vue';
-import type { ITaskSubRunMetadata, ITaskDataConnections } from 'n8n-workflow';
-import { NodeConnectionType } from 'n8n-workflow';
+import type { ITaskDataConnections, NodeConnectionType, Workflow, ITaskData } from 'n8n-workflow';
import type { IAiData, IAiDataContent, INodeUi } from '@/Interface';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
@@ -28,29 +27,21 @@ export interface Props {
runIndex?: number;
hideTitle?: boolean;
slim?: boolean;
+ workflow: Workflow;
}
const props = withDefaults(defineProps
(), { runIndex: 0 });
const workflowsStore = useWorkflowsStore();
const nodeTypesStore = useNodeTypesStore();
const selectedRun: Ref = ref([]);
-
function isTreeNodeSelected(node: TreeNode) {
return selectedRun.value.some((run) => run.node === node.node && run.runIndex === node.runIndex);
}
function getReferencedData(
- reference: ITaskSubRunMetadata,
+ taskData: ITaskData,
withInput: boolean,
withOutput: boolean,
): IAiDataContent[] {
- const resultData = workflowsStore.getWorkflowResultDataByNodeName(reference.node);
-
- if (!resultData?.[reference.runIndex]) {
- return [];
- }
-
- const taskData = resultData[reference.runIndex];
-
if (!taskData) {
return [];
}
@@ -98,18 +89,18 @@ function onItemClick(data: TreeNode) {
return;
}
+
+ const selectedNodeRun = workflowsStore.getWorkflowResultDataByNodeName(data.node)?.[
+ data.runIndex
+ ];
+ if (!selectedNodeRun) {
+ return;
+ }
selectedRun.value = [
{
node: data.node,
runIndex: data.runIndex,
- data: getReferencedData(
- {
- node: data.node,
- runIndex: data.runIndex,
- },
- true,
- true,
- ),
+ data: getReferencedData(selectedNodeRun, true, true),
},
];
}
@@ -145,21 +136,20 @@ const createNode = (
});
function getTreeNodeData(nodeName: string, currentDepth: number): TreeNode[] {
- const { connectionsByDestinationNode } = workflowsStore.getCurrentWorkflow();
- const connections = connectionsByDestinationNode[nodeName];
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ const connections = props.workflow.connectionsByDestinationNode[nodeName];
const resultData = aiData.value?.filter((data) => data.node === nodeName) ?? [];
if (!connections) {
return resultData.map((d) => createNode(nodeName, currentDepth, d));
}
- const nonMainConnectionsKeys = Object.keys(connections).filter(
- (key) => key !== NodeConnectionType.Main,
- );
- const children = nonMainConnectionsKeys.flatMap((key) =>
- connections[key][0].flatMap((node) => getTreeNodeData(node.node, currentDepth + 1)),
- );
+ // Get the first level of children
+ const connectedSubNodes = props.workflow.getParentNodes(nodeName, 'ALL_NON_MAIN', 1);
+
+ const children = connectedSubNodes
+ // Only include sub-nodes which have data
+ .filter((name) => aiData.value?.find((data) => data.node === name))
+ .flatMap((name) => getTreeNodeData(name, currentDepth + 1));
children.sort((a, b) => a.startTime - b.startTime);
@@ -170,35 +160,49 @@ function getTreeNodeData(nodeName: string, currentDepth: number): TreeNode[] {
return [createNode(nodeName, currentDepth, undefined, children)];
}
-const aiData = computed(() => {
- const resultData = workflowsStore.getWorkflowResultDataByNodeName(props.node.name);
+const aiData = computed(() => {
+ const result: AIResult[] = [];
+ const connectedSubNodes = props.workflow.getParentNodes(props.node.name, 'ALL_NON_MAIN');
+ const rootNodeResult = workflowsStore.getWorkflowResultDataByNodeName(props.node.name);
+ const rootNodeStartTime = rootNodeResult?.[0]?.startTime ?? 0;
+ const rootNodeEndTime = rootNodeStartTime + (rootNodeResult?.[0]?.executionTime ?? 0);
- if (!resultData || !Array.isArray(resultData)) {
- return;
- }
+ connectedSubNodes.forEach((nodeName) => {
+ const nodeRunData = workflowsStore.getWorkflowResultDataByNodeName(nodeName) ?? [];
- const subRun = resultData[props.runIndex].metadata?.subRun;
- if (!Array.isArray(subRun)) {
- return;
- }
- // Extend the subRun with the data and sort by adding execution time + startTime and comparing them
- const subRunWithData = subRun.flatMap((run) =>
- getReferencedData(run, false, true).map((data) => ({ ...run, data })),
- );
+ nodeRunData.forEach((run, runIndex) => {
+ const referenceData = {
+ data: getReferencedData(run, false, true)[0],
+ node: nodeName,
+ runIndex,
+ };
- subRunWithData.sort((a, b) => {
- const aTime = a.data?.metadata?.startTime || 0;
- const bTime = b.data?.metadata?.startTime || 0;
+ result.push(referenceData);
+ });
+ });
+
+ // Sort the data by start time
+ result.sort((a, b) => {
+ const aTime = a.data?.metadata?.startTime ?? 0;
+ const bTime = b.data?.metadata?.startTime ?? 0;
return aTime - bTime;
});
- return subRunWithData;
+ // Only show data that is within the root node's execution time
+ // This is because sub-node could be connected to multiple root nodes
+ const currentNodeResult = result.filter((r) => {
+ const startTime = r.data?.metadata?.startTime ?? 0;
+
+ return startTime >= rootNodeStartTime && startTime <= rootNodeEndTime;
+ });
+
+ return currentNodeResult;
});
const executionTree = computed(() => {
const rootNode = props.node;
- const tree = getTreeNodeData(rootNode.name, 0);
+ const tree = getTreeNodeData(rootNode.name, 1);
return tree || [];
});
@@ -206,7 +210,7 @@ watch(() => props.runIndex, selectFirst, { immediate: true });
-
+
workflowsStore.getCurrentWorkflow());
function getTriggerNode() {
- const workflow = workflowHelpers.getCurrentWorkflow();
- const triggerNode = workflow.queryNodes((nodeType: INodeType) =>
+ const triggerNode = workflow.value.queryNodes((nodeType: INodeType) =>
[CHAT_TRIGGER_NODE_TYPE, MANUAL_CHAT_TRIGGER_NODE_TYPE].includes(nodeType.description.name),
);
@@ -164,19 +162,19 @@ function setNode() {
return;
}
- const workflow = workflowHelpers.getCurrentWorkflow();
- const childNodes = workflow.getChildNodes(triggerNode.name);
+ const childNodes = workflow.value.getChildNodes(triggerNode.name);
for (const childNode of childNodes) {
// Look for the first connected node with metadata
// TODO: Allow later users to change that in the UI
- const resultData = workflowsStore.getWorkflowResultDataByNodeName(childNode);
+ const connectedSubNodes = workflow.value.getParentNodes(childNode, 'ALL_NON_MAIN');
+ const resultData = connectedSubNodes.map(workflowsStore.getWorkflowResultDataByNodeName);
if (!resultData && !Array.isArray(resultData)) {
continue;
}
- if (resultData[resultData.length - 1].metadata) {
+ if (resultData.some((data) => data?.[0].metadata)) {
node.value = workflowsStore.getNodeByName(childNode);
break;
}
@@ -190,7 +188,6 @@ function setConnectedNode() {
showError(new Error('Chat Trigger Node could not be found!'), 'Trigger Node not found');
return;
}
- const workflow = workflowHelpers.getCurrentWorkflow();
const chatNode = workflowsStore.getNodes().find((storeNode: INodeUi): boolean => {
if (storeNode.type === CHAIN_SUMMARIZATION_LANGCHAIN_NODE_TYPE) return false;
@@ -202,10 +199,10 @@ function setConnectedNode() {
let isCustomChainOrAgent = false;
if (nodeType.name === AI_CODE_NODE_TYPE) {
- const inputs = NodeHelpers.getNodeInputs(workflow, storeNode, nodeType);
+ const inputs = NodeHelpers.getNodeInputs(workflow.value, storeNode, nodeType);
const inputTypes = NodeHelpers.getConnectionTypes(inputs);
- const outputs = NodeHelpers.getNodeOutputs(workflow, storeNode, nodeType);
+ const outputs = NodeHelpers.getNodeOutputs(workflow.value, storeNode, nodeType);
const outputTypes = NodeHelpers.getConnectionTypes(outputs);
if (
@@ -219,7 +216,7 @@ function setConnectedNode() {
if (!isAgent && !isChain && !isCustomChainOrAgent) return false;
- const parentNodes = workflow.getParentNodes(storeNode.name);
+ const parentNodes = workflow.value.getParentNodes(storeNode.name);
const isChatChild = parentNodes.some((parentNodeName) => parentNodeName === triggerNode.name);
return Boolean(isChatChild && (isAgent || isChain || isCustomChainOrAgent));
@@ -431,10 +428,9 @@ async function sendMessage(message: string, files?: File[]) {
}
function displayExecution(executionId: string) {
- const workflow = workflowHelpers.getCurrentWorkflow();
const route = router.resolve({
name: VIEWS.EXECUTION_PREVIEW,
- params: { name: workflow.id, executionId },
+ params: { name: workflow.value.id, executionId },
});
window.open(route.href, '_blank');
}
@@ -452,9 +448,10 @@ function reuseMessage(message: ChatMessageText) {
function getChatMessages(): ChatMessageText[] {
if (!connectedNode.value) return [];
- const workflow = workflowHelpers.getCurrentWorkflow();
const connectedMemoryInputs =
- workflow.connectionsByDestinationNode[connectedNode.value.name][NodeConnectionType.AiMemory];
+ workflow.value.connectionsByDestinationNode[connectedNode.value.name][
+ NodeConnectionType.AiMemory
+ ];
if (!connectedMemoryInputs) return [];
const memoryConnection = (connectedMemoryInputs ?? []).find((i) => i.length > 0)?.[0];
@@ -573,7 +570,13 @@ onMounted(() => {
locale.baseText('chat.window.logs')
}}
-
+