n8n-editor-ui
Version:
Workflow Editor UI for n8n
862 lines (861 loc) • 38.7 kB
JavaScript
import { o as __toESM, t as __commonJSMin } from "./chunk-6z4oVpB-.js";
import { C as computed, Cn as toDisplayString, D as createElementBlock, E as createCommentVNode, G as nextTick, Gt as unref, It as ref, M as createVNode, P as defineComponent, T as createBlock, Vt as toRef, W as mergeProps, _ as Fragment, _t as watch, bn as normalizeStyle, bt as withCtx, et as openBlock, h as withModifiers, it as renderList, j as createTextVNode, mt as useTemplateRef, vn as normalizeClass, w as createBaseVNode, zt as shallowRef } from "./vue.runtime.esm-bundler-tP5dCd7J.js";
import { B as useTimestamp, V as useVirtualList, _t as useI18n, at as useThrottleFn, m as require_isArray, vt as I18nT } from "./_MapCache-ficiegRb.js";
import { Sn as N8nButton_default, ht as N8nTooltip_default, wn as N8nIcon_default, xn as N8nText_default, xt as N8nIconButton_default } from "./src-DgvumQTi.js";
import { t as __plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BwBpWJRZ.js";
import { r as require_get, y as useRouter } from "./truncate-B1HVeveJ.js";
import { Fi as parse, Gn as useNodeHelpers, Kn as injectWorkflowState, N as useWorkflowsStore, Nt as useNodeTypesStore, bt as formatTokenUsageCount, fn as require_upperFirst, fs as v4_default, v as useToast, vt as addTokenUsageData, xt as isChatNode, yt as emptyTokenUsageData } from "./builder.store-sBTWwxRU.js";
import { Bi as createEmptyRunExecutionData, Ka as AGENT_LANGCHAIN_NODE_TYPE, Ua as NodeConnectionTypes, Vi as createRunExecutionData, go as require__getTag, hi as parseErrorMetadata, mi as Workflow, vo as require__baseKeys, wc as LOGS_EXECUTION_DATA_THROTTLE_DURATION, wn as MANUAL_CHAT_TRIGGER_NODE_TYPE, zt as CHAT_TRIGGER_NODE_TYPE } from "./constants-BbpucWL4.js";
import { C as require_isTypedArray, O as require_isBuffer, k as require_isArguments, x as require__isPrototype, y as require_isArrayLike } from "./merge-k8vSyyXH.js";
import { x as TOOL_EXECUTOR_NODE_NAME } from "./_baseOrderBy-B_6CV8x6.js";
import { t as useRunWorkflow } from "./useRunWorkflow-BQJbxiOd.js";
import { t as NodeIcon_default } from "./NodeIcon-BVhrZNYI.js";
import { a as toTime, i as toDayMonth } from "./dateFormatter-DDfPHI9I.js";
import { t as AnimatedSpinner_default } from "./AnimatedSpinner-wiGFgffA.js";
var require_isEmpty = /* @__PURE__ */ __commonJSMin(((exports, module) => {
var baseKeys = require__baseKeys(), getTag = require__getTag(), isArguments = require_isArguments(), isArray = require_isArray(), isArrayLike = require_isArrayLike(), isBuffer = require_isBuffer(), isPrototype = require__isPrototype(), isTypedArray = require_isTypedArray();
var mapTag = "[object Map]", setTag = "[object Set]";
var hasOwnProperty = Object.prototype.hasOwnProperty;
function isEmpty$1(value) {
if (value == null) return true;
if (isArrayLike(value) && (isArray(value) || typeof value == "string" || typeof value.splice == "function" || isBuffer(value) || isTypedArray(value) || isArguments(value))) return !value.length;
var tag = getTag(value);
if (tag == mapTag || tag == setTag) return !value.size;
if (isPrototype(value)) return !baseKeys(value).length;
for (var key in value) if (hasOwnProperty.call(value, key)) return false;
return true;
}
module.exports = isEmpty$1;
}));
var import_get = /* @__PURE__ */ __toESM(require_get());
var import_isEmpty = /* @__PURE__ */ __toESM(require_isEmpty());
function getConsumedTokens(task) {
return task.reduce((acc, curr) => {
const tokenUsageData = curr?.json?.tokenUsage ?? curr?.json?.tokenUsageEstimate;
if (!tokenUsageData) return acc;
return addTokenUsageData(acc, {
...tokenUsageData,
isEstimate: !!curr?.json.tokenUsageEstimate
});
}, emptyTokenUsageData);
}
function getConsumedTokensFromTaskData(runData) {
return getConsumedTokens(Object.values(runData.data ?? {}).flat().flat());
}
function createNode(node, context, runIndex, runData, children = []) {
return {
parent: context.parent,
node,
id: `${context.workflow.id}:${node.id}:${[...context.ancestorRunIndexes, runIndex].join(":")}`,
runIndex,
runData,
children,
consumedTokens: runData ? getConsumedTokensFromTaskData(runData) : emptyTokenUsageData,
workflow: context.workflow,
executionId: context.executionId,
execution: context.data,
isSubExecution: context.isSubExecution
};
}
function getChildNodes(treeNode, node, runIndex, context) {
const subExecutionLocator = findSubExecutionLocator(treeNode);
if (subExecutionLocator !== void 0) {
const workflow = context.workflows[subExecutionLocator.workflowId];
const subWorkflowRunData = context.subWorkflowData[subExecutionLocator.executionId];
if (!workflow || !subWorkflowRunData) return [];
return createLogTreeRec(void 0, {
...context,
parent: treeNode,
ancestorRunIndexes: [...context.ancestorRunIndexes, runIndex ?? 0],
workflow,
executionId: subExecutionLocator.executionId,
data: subWorkflowRunData,
isSubExecution: true
});
}
const connectedSubNodes = context.workflow.getParentNodes(node.name, "ALL_NON_MAIN", 1);
function isMatchedSource(source) {
return (source?.previousNode === node.name || isPlaceholderLog(treeNode) && source?.previousNode === "PartialExecutionToolExecutor") && (runIndex === void 0 || (source.previousNodeRun ?? 0) === runIndex);
}
return connectedSubNodes.flatMap((subNodeName) => (context.data.resultData.runData[subNodeName] ?? []).flatMap((t, index) => {
if (!(t.source.some((source) => source !== null) ? t.source.some(isMatchedSource) : runIndex === void 0 || index === runIndex)) return [];
const subNode = context.workflow.getNode(subNodeName);
return subNode ? getTreeNodeData(subNode, t, index, {
...context,
ancestorRunIndexes: [...context.ancestorRunIndexes, runIndex ?? 0],
parent: treeNode
}) : [];
}));
}
function getTreeNodeData(node, runData, runIndex, context) {
const treeNode = createNode(node, context, runIndex ?? 0, runData);
const children = getChildNodes(treeNode, node, runIndex, context).sort(sortLogEntries);
if ((runData === void 0 || node.disabled) && children.length === 0) return [];
treeNode.children = children;
return [treeNode];
}
function getTotalConsumedTokens(...usage) {
return usage.reduce(addTokenUsageData, emptyTokenUsageData);
}
function getSubtreeTotalConsumedTokens(treeNode, includeSubWorkflow) {
const executionId = treeNode.executionId;
function calculate(currentNode) {
if (!includeSubWorkflow && currentNode.executionId !== executionId) return emptyTokenUsageData;
return getTotalConsumedTokens(currentNode.consumedTokens, ...currentNode.children.map(calculate));
}
return calculate(treeNode);
}
function findLogEntryToAutoSelect(subTree) {
const entryWithError = findLogEntryRec((e) => !!e.runData?.error, subTree);
if (entryWithError) return entryWithError;
const entryForAiAgent = findLogEntryRec((entry) => entry.node.type === "@n8n/n8n-nodes-langchain.agent" || entry.parent?.node.type === "@n8n/n8n-nodes-langchain.agent" && isPlaceholderLog(entry.parent), subTree);
if (entryForAiAgent) return entryForAiAgent;
return subTree[subTree.length - 1];
}
function createLogTreeRec(filter, context) {
const runData = context.data.resultData.runData;
const intermediateResult = Object.entries(runData).flatMap(([nodeName, taskData]) => {
const node = context.workflow.getNode(nodeName);
if (node === null || filter && filter.rootNodeId !== node.id) return [];
const childNodes = context.workflow.getChildNodes(nodeName, "ALL_NON_MAIN");
if (childNodes.length === 0) {
const taskDataList = taskData.map((task, runIndex) => ({
node,
task,
runIndex,
nodeHasMultipleRuns: taskData.length > 1
}));
return filter ? taskDataList.filter((item) => item.runIndex === filter.rootNodeRunIndex) : taskDataList;
}
if (childNodes.some((child) => (runData[child] ?? []).length > 0)) return [];
const firstChild = context.workflow.getNode(childNodes[0]);
if (firstChild === null) return [];
return [{
node: firstChild,
nodeHasMultipleRuns: false
}];
});
const seenPlaceholders = /* @__PURE__ */ new Set();
return intermediateResult.filter((entry) => {
if (entry.task !== void 0) return true;
if (seenPlaceholders.has(entry.node.id)) return false;
seenPlaceholders.add(entry.node.id);
return true;
}).flatMap(({ node, runIndex, task, nodeHasMultipleRuns }) => getTreeNodeData(node, task, nodeHasMultipleRuns ? runIndex : void 0, context)).sort(sortLogEntries);
}
function createLogTree(workflow, response, workflows = {}, subWorkflowData = {}, filter) {
return createLogTreeRec(filter, {
parent: void 0,
ancestorRunIndexes: [],
executionId: response.id,
workflow,
workflows,
data: response.data ?? createEmptyRunExecutionData(),
subWorkflowData,
isSubExecution: false
});
}
function findLogEntryById(id, entries) {
return findLogEntryRec((entry) => entry.id === id, entries);
}
function findLogEntryRec(isMatched, entries) {
for (const entry of entries) {
if (isMatched(entry)) return entry;
const child = findLogEntryRec(isMatched, entry.children);
if (child) return child;
}
}
function findSelectedLogEntry(selection, entries, isExecuting) {
switch (selection.type) {
case "initial": return isExecuting ? void 0 : findLogEntryToAutoSelect(entries);
case "none": return;
case "selected": {
const found = findLogEntryRec((e) => e.id === selection.entry.id, entries);
if (found === void 0 && !isExecuting) for (let runIndex = selection.entry.runIndex - 1; runIndex >= 0; runIndex--) {
const fallback = findLogEntryRec((e) => e.workflow.id === selection.entry.workflow.id && e.node.id === selection.entry.node.id && e.runIndex === runIndex, entries);
if (fallback !== void 0) return fallback;
}
return found;
}
}
}
function flattenLogEntries(entries, collapsedEntryIds, ret = []) {
for (const entry of entries) {
ret.push(entry);
if (!collapsedEntryIds[entry.id]) flattenLogEntries(entry.children, collapsedEntryIds, ret);
}
return ret;
}
function getEntryAtRelativeIndex(entries, id, relativeIndex) {
const offset = entries.findIndex((e) => e.id === id);
return offset === -1 ? void 0 : entries[offset + relativeIndex];
}
function sortLogEntries(a, b) {
if (a.runData === void 0) return a.children.length > 0 ? sortLogEntries(a.children[0], b) : 0;
if (b.runData === void 0) return b.children.length > 0 ? sortLogEntries(a, b.children[0]) : 0;
if (a.runData.startTime === b.runData.startTime) return a.runData.executionIndex - b.runData.executionIndex;
return a.runData.startTime - b.runData.startTime;
}
function mergeStartData(startData, response) {
if (!response.data) return response;
const nodeNames = [...new Set(Object.keys(startData).concat(Object.keys(response.data.resultData.runData))).values()];
const runData = Object.fromEntries(nodeNames.map((nodeName) => {
const tasks = response.data?.resultData.runData[nodeName] ?? [];
return [nodeName, tasks.concat((startData[nodeName] ?? []).filter((task) => {
if (tasks.some((t) => t.executionIndex === task.executionIndex && t.startTime === task.startTime)) return false;
return tasks.every((t) => t.startTime < task.startTime);
}).map((task) => ({
...task,
executionTime: 0,
executionStatus: "running"
})))];
}));
return {
...response,
data: {
...response.data,
resultData: {
...response.data.resultData,
runData
}
}
};
}
function hasSubExecution(entry) {
return findSubExecutionLocator(entry) !== void 0;
}
function findSubExecutionLocator(entry) {
const metadata = entry.runData?.metadata?.subExecution;
if (metadata) return {
workflowId: metadata.workflowId,
executionId: metadata.executionId
};
return parseErrorMetadata(entry.runData?.error)?.subExecution;
}
function getDepth(entry) {
let depth = 0;
let currentEntry = entry;
while (currentEntry.parent !== void 0) {
currentEntry = currentEntry.parent;
depth++;
}
return depth;
}
function getInputKey(node) {
if (node.type === "@n8n/n8n-nodes-langchain.manualChatTrigger" && node.typeVersion < 1.1) return "input";
if (node.type === "@n8n/n8n-nodes-langchain.chatTrigger") return "chatInput";
return "chatInput";
}
function extractChatInput(workflow, resultData) {
const chatTrigger = workflow.nodes.find(isChatNode);
if (chatTrigger === void 0) return;
const inputKey = getInputKey(chatTrigger);
const runData = (resultData.runData[chatTrigger.name] ?? [])[0];
const message = runData?.data?.[NodeConnectionTypes.Main]?.[0]?.[0]?.json?.[inputKey];
if (runData === void 0 || typeof message !== "string") return;
return {
text: message,
sender: "user",
id: v4_default()
};
}
function extractBotResponse(resultData, executionId, emptyText) {
const lastNodeExecuted = resultData.lastNodeExecuted;
if (!lastNodeExecuted) return void 0;
const nodeResponseDataArray = (0, import_get.default)(resultData.runData, lastNodeExecuted) ?? [];
const nodeResponseData = nodeResponseDataArray[nodeResponseDataArray.length - 1];
let responseMessage;
if ((0, import_get.default)(nodeResponseData, "error")) responseMessage = "[ERROR: " + (0, import_get.default)(nodeResponseData, "error.message") + "]";
else {
const mainOutputs = (0, import_get.default)(nodeResponseData, "data.main");
let text;
if (mainOutputs && Array.isArray(mainOutputs)) {
for (const branch of mainOutputs) if (branch?.[0]?.json) {
const responseData = branch[0].json;
text = extractResponseText(responseData);
if (text) break;
}
}
text = text ?? emptyText;
if (!text) return;
responseMessage = text;
}
return {
text: responseMessage,
sender: "bot",
id: executionId ?? v4_default()
};
}
function extractResponseText(responseData) {
if (!responseData || (0, import_isEmpty.default)(responseData)) return;
const matchedPath = [
"output",
"text",
"response.text",
"message"
].find((path) => (0, import_get.default)(responseData, path));
if (!matchedPath) return JSON.stringify(responseData, null, 2);
const matchedOutput = (0, import_get.default)(responseData, matchedPath);
if (typeof matchedOutput === "object") return "```json\n" + JSON.stringify(matchedOutput, null, 2) + "\n```";
return matchedOutput?.toString() ?? "";
}
function restoreChatHistory(workflowExecutionData, emptyText) {
if (!workflowExecutionData?.data) return [];
const userMessage = extractChatInput(workflowExecutionData.workflowData, workflowExecutionData.data.resultData);
const botMessage = extractBotResponse(workflowExecutionData.data.resultData, workflowExecutionData.id, emptyText);
return [...userMessage ? [userMessage] : [], ...botMessage ? [botMessage] : []];
}
async function processFiles(data) {
if (!data || data.length === 0) return [];
const filePromises = data.map(async (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve({
name: file.name,
type: file.type,
data: reader.result
});
reader.onerror = () => reject(/* @__PURE__ */ new Error(`Error reading file: ${reader.error?.message ?? "Unknown error"}`));
reader.readAsDataURL(file);
});
});
return await Promise.all(filePromises);
}
function isSubNodeLog(logEntry) {
return logEntry.parent !== void 0 && logEntry.parent.executionId === logEntry.executionId;
}
function isPlaceholderLog(treeNode) {
return treeNode.runData === void 0;
}
function copyExecutionData(executionData) {
return {
...executionData,
data: createRunExecutionData({
...executionData.data,
resultData: {
...executionData.data?.resultData,
runData: Object.fromEntries(Object.entries(executionData.data?.resultData.runData ?? {}).map(([k, v]) => [k, [...v]]))
}
})
};
}
var ConsumedTokensDetails_default = /* @__PURE__ */ defineComponent({
__name: "ConsumedTokensDetails",
props: { consumedTokens: {} },
setup(__props) {
const i18n = useI18n();
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", null, [
createVNode(unref(N8nText_default), {
bold: true,
size: "small"
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("runData.aiContentBlock.tokens.prompt")) + " " + toDisplayString(unref(i18n).baseText("runData.aiContentBlock.tokens", { interpolate: { count: unref(formatTokenUsageCount)(_ctx.consumedTokens, "prompt") } })), 1)]),
_: 1
}),
_cache[0] || (_cache[0] = createBaseVNode("br", null, null, -1)),
createVNode(unref(N8nText_default), {
bold: true,
size: "small"
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("runData.aiContentBlock.tokens.completion")) + " " + toDisplayString(unref(i18n).baseText("runData.aiContentBlock.tokens", { interpolate: { count: unref(formatTokenUsageCount)(_ctx.consumedTokens, "completion") } })), 1)]),
_: 1
})
]);
};
}
});
var LogsViewConsumedTokenCountText_default = /* @__PURE__ */ defineComponent({
__name: "LogsViewConsumedTokenCountText",
props: { consumedTokens: {} },
setup(__props) {
const locale = useI18n();
return (_ctx, _cache) => {
return _ctx.consumedTokens !== void 0 ? (openBlock(), createBlock(unref(N8nTooltip_default), {
key: 0,
enterable: false
}, {
content: withCtx(() => [createVNode(ConsumedTokensDetails_default, { "consumed-tokens": _ctx.consumedTokens }, null, 8, ["consumed-tokens"])]),
default: withCtx(() => [createBaseVNode("span", null, toDisplayString(unref(locale).baseText("runData.aiContentBlock.tokens", { interpolate: { count: unref(formatTokenUsageCount)(_ctx.consumedTokens, "total") } })), 1)]),
_: 1
})) : createCommentVNode("", true);
};
}
});
var _hoisted_1$1 = { key: 0 };
var LogsViewNodeName_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "LogsViewNodeName",
props: {
name: {},
isError: { type: Boolean },
isDeleted: { type: Boolean }
},
setup(__props) {
return (_ctx, _cache) => {
return openBlock(), createBlock(unref(N8nText_default), {
tag: "div",
bold: true,
size: "small",
class: normalizeClass(_ctx.$style.name),
color: _ctx.isError ? "danger" : void 0
}, {
default: withCtx(() => [_ctx.isDeleted ? (openBlock(), createElementBlock("del", _hoisted_1$1, toDisplayString(_ctx.name), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(_ctx.name), 1)], 64))]),
_: 1
}, 8, ["class", "color"]);
};
}
});
var LogsViewNodeName_vue_vue_type_style_index_0_lang_module_default = { name: "_name_1qhq5_123" };
var LogsViewNodeName_default = /* @__PURE__ */ __plugin_vue_export_helper_default(LogsViewNodeName_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": LogsViewNodeName_vue_vue_type_style_index_0_lang_module_default }]]);
var import_upperFirst = /* @__PURE__ */ __toESM(require_upperFirst());
var _hoisted_1 = ["aria-expanded", "aria-selected"];
var LogsOverviewRow_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "LogsOverviewRow",
props: {
data: {},
isSelected: { type: Boolean },
isReadOnly: { type: Boolean },
shouldShowTokenCountColumn: { type: Boolean },
isCompact: { type: Boolean },
latestInfo: {},
expanded: { type: Boolean },
canOpenNdv: { type: Boolean }
},
emits: [
"toggleExpanded",
"toggleSelected",
"triggerPartialExecution",
"openNdv"
],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const container$2 = useTemplateRef("containerRef");
const locale = useI18n();
const now = useTimestamp({ interval: 1e3 });
const nodeTypeStore = useNodeTypesStore();
const type = computed(() => nodeTypeStore.getNodeType(props.data.node.type));
const isRunning = computed(() => props.data.runData?.executionStatus === "running");
const isWaiting = computed(() => props.data.runData?.executionStatus === "waiting");
const isSettled = computed(() => !isRunning.value && !isWaiting.value);
const isError = computed(() => !!props.data.runData?.error);
const statusTextKeyPath = computed(() => isSettled.value ? "logs.overview.body.summaryText.in" : "logs.overview.body.summaryText.for");
const startedAtText = computed(() => {
if (props.data.runData === void 0) return "—";
const time = new Date(props.data.runData.startTime);
return locale.baseText("logs.overview.body.started", { interpolate: { time: `${toTime(time, true)}, ${toDayMonth(time)}` } });
});
const statusText = computed(() => (0, import_upperFirst.default)(props.data.runData?.executionStatus ?? ""));
const timeText = computed(() => props.data.runData ? locale.displayTimer(isSettled.value ? props.data.runData.executionTime : Math.floor((now.value - props.data.runData.startTime) / 1e3) * 1e3, true) : void 0);
const subtreeConsumedTokens = computed(() => props.shouldShowTokenCountColumn ? getSubtreeTotalConsumedTokens(props.data, false) : void 0);
const hasChildren = computed(() => props.data.children.length > 0 || hasSubExecution(props.data));
const indents = computed(() => {
const ret = [];
let data = props.data;
while (data.parent !== void 0) {
const siblings = data.parent?.children ?? [];
const lastSibling = siblings[siblings.length - 1];
ret.unshift({
straight: lastSibling?.id !== data.id,
curved: data === props.data
});
data = data.parent;
}
return ret;
});
watch(() => props.isSelected, (isSelected) => {
nextTick(() => {
if (isSelected) {
const activeElement = document.activeElement;
if (!(activeElement?.getAttribute("data-test-id") === "chat-input" || activeElement?.closest("[data-test-id=\"canvas-chat\"]") !== null)) container$2.value?.focus();
}
});
}, { immediate: true });
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
ref: "containerRef",
role: "treeitem",
tabindex: "-1",
"aria-expanded": props.data.children.length > 0 && props.expanded,
"aria-selected": props.isSelected,
class: normalizeClass({
[_ctx.$style.container]: true,
[_ctx.$style.compact]: props.isCompact,
[_ctx.$style.error]: isError.value,
[_ctx.$style.selected]: props.isSelected
}),
onClick: _cache[3] || (_cache[3] = withModifiers(($event) => emit("toggleSelected"), ["stop"]))
}, [
(openBlock(true), createElementBlock(Fragment, null, renderList(indents.value, (indent$1, level) => {
return openBlock(), createElementBlock("div", {
key: level,
class: normalizeClass({
[_ctx.$style.indent]: true,
[_ctx.$style.connectorCurved]: indent$1.curved,
[_ctx.$style.connectorStraight]: indent$1.straight
})
}, null, 2);
}), 128)),
createBaseVNode("div", {
class: normalizeClass(_ctx.$style.background),
style: normalizeStyle({ "--indent-depth": indents.value.length })
}, null, 6),
createVNode(NodeIcon_default, {
"node-type": type.value,
size: 16,
class: normalizeClass(_ctx.$style.icon)
}, null, 8, ["node-type", "class"]),
createVNode(LogsViewNodeName_default, {
class: normalizeClass(_ctx.$style.name),
name: _ctx.latestInfo?.name ?? props.data.node.name,
"is-error": isError.value,
"is-deleted": _ctx.latestInfo?.deleted ?? false
}, null, 8, [
"class",
"name",
"is-error",
"is-deleted"
]),
!_ctx.isCompact ? (openBlock(), createBlock(unref(N8nText_default), {
key: 0,
tag: "div",
color: "text-light",
size: "small",
class: normalizeClass(_ctx.$style.timeTook)
}, {
default: withCtx(() => [timeText.value !== void 0 ? (openBlock(), createBlock(unref(I18nT), {
key: 0,
keypath: statusTextKeyPath.value,
scope: "global"
}, {
status: withCtx(() => [createVNode(unref(N8nText_default), {
color: isError.value ? "danger" : void 0,
bold: isError.value,
size: "small"
}, {
default: withCtx(() => [isRunning.value ? (openBlock(), createBlock(AnimatedSpinner_default, {
key: 0,
class: normalizeClass(_ctx.$style.statusTextIcon)
}, null, 8, ["class"])) : isWaiting.value ? (openBlock(), createBlock(unref(N8nIcon_default), {
key: 1,
icon: "status-waiting",
class: normalizeClass(_ctx.$style.statusTextIcon)
}, null, 8, ["class"])) : isError.value ? (openBlock(), createBlock(unref(N8nIcon_default), {
key: 2,
icon: "triangle-alert",
class: normalizeClass(_ctx.$style.statusTextIcon)
}, null, 8, ["class"])) : createCommentVNode("", true), createTextVNode(" " + toDisplayString(statusText.value), 1)]),
_: 1
}, 8, ["color", "bold"])]),
time: withCtx(() => [createTextVNode(toDisplayString(timeText.value), 1)]),
_: 1
}, 8, ["keypath"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode("—")], 64))]),
_: 1
}, 8, ["class"])) : createCommentVNode("", true),
!_ctx.isCompact ? (openBlock(), createBlock(unref(N8nText_default), {
key: 1,
tag: "div",
color: "text-light",
size: "small",
class: normalizeClass(_ctx.$style.startedAt)
}, {
default: withCtx(() => [createTextVNode(toDisplayString(startedAtText.value), 1)]),
_: 1
}, 8, ["class"])) : createCommentVNode("", true),
!_ctx.isCompact && subtreeConsumedTokens.value !== void 0 ? (openBlock(), createBlock(unref(N8nText_default), {
key: 2,
tag: "div",
color: "text-light",
size: "small",
class: normalizeClass(_ctx.$style.consumedTokens)
}, {
default: withCtx(() => [subtreeConsumedTokens.value.totalTokens > 0 && (props.data.children.length === 0 || !props.expanded) ? (openBlock(), createBlock(LogsViewConsumedTokenCountText_default, {
key: 0,
"consumed-tokens": subtreeConsumedTokens.value
}, null, 8, ["consumed-tokens"])) : createCommentVNode("", true)]),
_: 1
}, 8, ["class"])) : createCommentVNode("", true),
isError.value && _ctx.isCompact ? (openBlock(), createBlock(unref(N8nIcon_default), {
key: 3,
size: "medium",
color: "danger",
icon: "triangle-alert",
class: normalizeClass(_ctx.$style.compactErrorIcon)
}, null, 8, ["class"])) : createCommentVNode("", true),
_ctx.canOpenNdv && (!_ctx.isCompact || !props.latestInfo?.deleted) ? (openBlock(), createBlock(unref(N8nIconButton_default), {
key: 4,
type: "secondary",
size: "small",
icon: "square-pen",
"icon-size": "medium",
style: normalizeStyle({ visibility: props.data.isSubExecution ? "hidden" : "" }),
disabled: props.latestInfo?.deleted,
class: normalizeClass(_ctx.$style.openNdvButton),
"aria-label": unref(locale).baseText("logs.overview.body.open"),
onClick: _cache[0] || (_cache[0] = withModifiers(($event) => emit("openNdv"), ["stop"]))
}, null, 8, [
"style",
"disabled",
"class",
"aria-label"
])) : createCommentVNode("", true),
!_ctx.isCompact || !props.isReadOnly && !props.latestInfo?.deleted && !props.latestInfo?.disabled ? (openBlock(), createBlock(unref(N8nIconButton_default), {
key: 5,
type: "secondary",
size: "small",
icon: "play",
"aria-label": unref(locale).baseText("logs.overview.body.run"),
class: normalizeClass([_ctx.$style.partialExecutionButton, indents.value.length > 0 ? _ctx.$style.unavailable : ""]),
disabled: props.latestInfo?.deleted || props.latestInfo?.disabled,
onClick: _cache[1] || (_cache[1] = withModifiers(($event) => emit("triggerPartialExecution"), ["stop"]))
}, null, 8, [
"aria-label",
"class",
"disabled"
])) : createCommentVNode("", true),
_ctx.isCompact && !hasChildren.value ? (openBlock(), createElementBlock(Fragment, { key: 6 }, [isRunning.value ? (openBlock(), createBlock(AnimatedSpinner_default, {
key: 0,
class: normalizeClass(_ctx.$style.statusIcon)
}, null, 8, ["class"])) : isWaiting.value ? (openBlock(), createBlock(unref(N8nIcon_default), {
key: 1,
icon: "status-waiting",
class: normalizeClass(_ctx.$style.statusIcon)
}, null, 8, ["class"])) : createCommentVNode("", true)], 64)) : createCommentVNode("", true),
!_ctx.isCompact || hasChildren.value ? (openBlock(), createBlock(unref(N8nButton_default), {
key: 7,
type: "secondary",
size: "small",
icon: props.expanded ? "chevron-down" : "chevron-up",
"icon-size": "medium",
square: true,
style: normalizeStyle({ visibility: hasChildren.value ? "" : "hidden" }),
class: normalizeClass(_ctx.$style.toggleButton),
"aria-label": unref(locale).baseText("logs.overview.body.toggleRow"),
onClick: _cache[2] || (_cache[2] = withModifiers(($event) => emit("toggleExpanded"), ["stop"]))
}, null, 8, [
"icon",
"style",
"class",
"aria-label"
])) : createCommentVNode("", true)
], 10, _hoisted_1);
};
}
});
var LogsOverviewRow_vue_vue_type_style_index_0_lang_module_default = {
container: "_container_1742e_123",
background: "_background_1742e_140",
selected: "_selected_1742e_151",
error: "_error_1742e_157",
indent: "_indent_1742e_161",
connectorCurved: "_connectorCurved_1742e_170",
connectorStraight: "_connectorStraight_1742e_180",
icon: "_icon_1742e_189",
name: "_name_1742e_196",
timeTook: "_timeTook_1742e_202",
statusTextIcon: "_statusTextIcon_1742e_207",
startedAt: "_startedAt_1742e_212",
consumedTokens: "_consumedTokens_1742e_218",
compactErrorIcon: "_compactErrorIcon_1742e_225",
partialExecutionButton: "_partialExecutionButton_1742e_237",
openNdvButton: "_openNdvButton_1742e_238",
compact: "_compact_1742e_225",
unavailable: "_unavailable_1742e_248",
toggleButton: "_toggleButton_1742e_256",
statusIcon: "_statusIcon_1742e_280",
placeholder: "_placeholder_1742e_288"
};
var LogsOverviewRow_default = /* @__PURE__ */ __plugin_vue_export_helper_default(LogsOverviewRow_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": LogsOverviewRow_vue_vue_type_style_index_0_lang_module_default }]]);
var LogsOverviewRows_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
__name: "LogsOverviewRows",
props: {
selected: {},
isReadOnly: { type: Boolean },
isCompact: { type: Boolean },
shouldShowTokenCountColumn: { type: Boolean },
canOpenNdv: { type: Boolean },
flatLogEntries: {},
latestNodeInfo: {},
execution: {}
},
emits: [
"select",
"openNdv",
"toggleExpanded"
],
setup(__props, { emit: __emit }) {
const emit = __emit;
const runWorkflow = useRunWorkflow({ router: useRouter() });
const isExpanded = computed(() => __props.flatLogEntries.reduce((acc, entry, index, arr) => {
acc[entry.id] = arr[index + 1]?.parent?.id === entry.id;
return acc;
}, {}));
const virtualList = useVirtualList(toRef(() => __props.flatLogEntries), { itemHeight: 32 });
async function handleTriggerPartialExecution(treeNode) {
const latestName = __props.latestNodeInfo[treeNode.node.id]?.name ?? treeNode.node.name;
if (latestName) await runWorkflow.runWorkflow({ destinationNode: {
nodeName: latestName,
mode: "inclusive"
} });
}
watch([() => __props.execution?.status === "running", () => __props.flatLogEntries.length], async ([isRunning, flatEntryCount], [wasRunning]) => {
await nextTick(() => {
if (__props.selected === void 0 && (isRunning || wasRunning)) virtualList.scrollTo(flatEntryCount - 1);
});
}, { immediate: true });
watch(() => __props.selected?.id, async (selectedId) => {
await nextTick(() => {
if (selectedId === void 0) return;
const index = virtualList.list.value.some((e) => e.data.id === selectedId) ? -1 : __props.flatLogEntries.findIndex((e) => e.id === selectedId);
if (index >= 0) virtualList.scrollTo(index);
});
}, { immediate: true });
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", mergeProps({ class: _ctx.$style.tree }, unref(virtualList).containerProps), [createBaseVNode("div", mergeProps({ role: "tree" }, unref(virtualList).wrapperProps.value), [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(virtualList).list.value, ({ data, index }) => {
return openBlock(), createBlock(LogsOverviewRow_default, {
key: index,
data,
"is-read-only": _ctx.isReadOnly,
"is-selected": data.id === _ctx.selected?.id,
"is-compact": _ctx.isCompact,
"should-show-token-count-column": _ctx.shouldShowTokenCountColumn,
"latest-info": _ctx.latestNodeInfo[data.node.id],
expanded: isExpanded.value[data.id],
"can-open-ndv": _ctx.canOpenNdv,
onToggleExpanded: ($event) => emit("toggleExpanded", data),
onOpenNdv: ($event) => emit("openNdv", data),
onTriggerPartialExecution: ($event) => handleTriggerPartialExecution(data),
onToggleSelected: ($event) => emit("select", _ctx.selected?.id === data.id ? void 0 : data)
}, null, 8, [
"data",
"is-read-only",
"is-selected",
"is-compact",
"should-show-token-count-column",
"latest-info",
"expanded",
"can-open-ndv",
"onToggleExpanded",
"onOpenNdv",
"onTriggerPartialExecution",
"onToggleSelected"
]);
}), 128))], 16)], 16);
};
}
});
var LogsOverviewRows_vue_vue_type_style_index_0_lang_module_default = {
tree: "_tree_92rld_123",
container: "_container_92rld_128",
staticScrollBar: "_staticScrollBar_92rld_128"
};
var LogsOverviewRows_default = /* @__PURE__ */ __plugin_vue_export_helper_default(LogsOverviewRows_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": LogsOverviewRows_vue_vue_type_style_index_0_lang_module_default }]]);
function useThrottleWithReactiveDelay(state, delay) {
const throttled = shallowRef(state.value);
watch(state, useThrottleFn((latest) => {
throttled.value = latest;
}, delay, true, true), { immediate: true });
return throttled;
}
function useLogsExecutionData({ isEnabled, filter } = {}) {
const nodeHelpers = useNodeHelpers();
const workflowsStore = useWorkflowsStore();
const workflowState = injectWorkflowState();
const toast = useToast();
const state = ref();
const updateInterval = computed(() => workflowsStore.workflowExecutionData?.status === "running" && Object.keys(workflowsStore.workflowExecutionData.data?.resultData.runData ?? {}).length > 1 ? LOGS_EXECUTION_DATA_THROTTLE_DURATION : 0);
const throttledState = useThrottleWithReactiveDelay(state, updateInterval);
const throttledWorkflowData = computed(() => throttledState.value?.response.workflowData);
const subWorkflowExecData = ref({});
const subWorkflows = ref({});
const workflow = ref();
const latestNodeNameById = computed(() => Object.values(workflow.value?.nodes ?? {}).reduce((acc, node) => {
const nodeInStore = workflowsStore.getNodeById(node.id);
acc[node.id] = {
deleted: !nodeInStore,
disabled: nodeInStore?.disabled ?? false,
name: nodeInStore?.name ?? node.name
};
return acc;
}, {}));
const hasChat = computed(() => [Object.values(workflow.value?.nodes ?? {}), workflowsStore.workflow.nodes].some((nodes) => nodes.some(isChatNode)));
const entries = computed(() => {
if (isEnabled !== void 0 && !isEnabled.value || !throttledState.value || !workflow.value) return [];
const mergedExecutionData = mergeStartData(throttledState.value.startData, throttledState.value.response);
return createLogTree(workflow.value, mergedExecutionData, subWorkflows.value, subWorkflowExecData.value, filter?.value);
});
function resetExecutionData() {
state.value = void 0;
workflowState.setWorkflowExecutionData(null);
nodeHelpers.updateNodesExecutionIssues();
workflowsStore.fetchLastSuccessfulExecution();
}
async function loadSubExecution(logEntry) {
const locator = findSubExecutionLocator(logEntry);
if (!state.value || locator === void 0) return;
try {
const subExecution = await workflowsStore.fetchExecutionDataById(locator.executionId);
const data = subExecution?.data ? parse(subExecution.data) : void 0;
if (!data || !subExecution) throw Error("Data is missing");
subWorkflowExecData.value[locator.executionId] = data;
subWorkflows.value[locator.workflowId] = new Workflow({
...subExecution.workflowData,
nodeTypes: workflowsStore.getNodeTypes()
});
} catch (e) {
toast.showError(e, "Unable to load sub execution");
}
}
watch([
() => workflowsStore.workflowExecutionData?.id,
() => workflowsStore.workflowExecutionData?.workflowData.id,
() => workflowsStore.workflowExecutionData?.status,
() => workflowsStore.workflowExecutionResultDataLastUpdate,
() => workflowsStore.workflowExecutionStartedData
], useThrottleFn(([executionId], [previousExecutionId]) => {
state.value = workflowsStore.workflowExecutionData === null ? void 0 : {
response: copyExecutionData(workflowsStore.workflowExecutionData),
startData: workflowsStore.workflowExecutionStartedData?.[1] ?? {}
};
if (executionId !== previousExecutionId) {
subWorkflowExecData.value = {};
subWorkflows.value = {};
}
}, updateInterval, true, true), { immediate: true });
watch(() => workflowsStore.workflowId, () => {
resetExecutionData();
});
watch(throttledWorkflowData, (data) => {
workflow.value = data ? new Workflow({
...data,
nodeTypes: workflowsStore.getNodeTypes()
}) : void 0;
}, { immediate: true });
return {
execution: computed(() => throttledState.value?.response),
entries,
hasChat,
latestNodeNameById,
resetExecutionData,
loadSubExecution
};
}
function useLogsTreeExpand(entries, loadSubExecution) {
const collapsedEntries = shallowRef({});
const flatLogEntries = computed(() => flattenLogEntries(entries.value, collapsedEntries.value));
function toggleExpanded(treeNode, expand) {
if (hasSubExecution(treeNode) && treeNode.children.length === 0) {
loadSubExecution(treeNode);
return;
}
collapsedEntries.value = {
...collapsedEntries.value,
[treeNode.id]: expand === void 0 ? !collapsedEntries.value[treeNode.id] : !expand
};
}
return {
flatLogEntries,
toggleExpanded
};
}
export { isPlaceholderLog as _, LogsViewConsumedTokenCountText_default as a, restoreChatHistory as b, findLogEntryById as c, getConsumedTokens as d, getDepth as f, getTotalConsumedTokens as g, getSubtreeTotalConsumedTokens as h, LogsViewNodeName_default as i, findLogEntryRec as l, getInputKey as m, useLogsExecutionData as n, ConsumedTokensDetails_default as o, getEntryAtRelativeIndex as p, LogsOverviewRows_default as r, extractBotResponse as s, useLogsTreeExpand as t, findSelectedLogEntry as u, isSubNodeLog as v, processFiles as y };