Files
lcbp3.np-dms.work/n8n-cache/n8n/public/assets/LogsPanel-CvmnM4bL.js
2025-09-21 20:29:15 +07:00

6290 lines
193 KiB
JavaScript
Executable File

import { by as addTokenUsageData, bz as emptyTokenUsageData, bA as parseErrorMetadata, bB as isChatNode, bC as NodeConnectionTypes, bD as v4, bE as get, bF as AGENT_LANGCHAIN_NODE_TYPE, bG as MANUAL_CHAT_TRIGGER_NODE_TYPE, bH as CHAT_TRIGGER_NODE_TYPE, bI as isEmpty, a as useToast, r as ref, x as computed, c as useI18n$1, bJ as usePinnedData, am as useMessage, an as MODAL_CONFIRM, a1 as useWorkflowsStore, at as useRootStore, bK as useLogsStore, b as useRouter, bL as useNodeHelpers, bM as useRunWorkflow, V as VIEWS, bN as chatEventBus, a7 as watch, bO as provide, d as defineComponent, h as createElementBlock, g as openBlock, n as normalizeClass, i as createVNode, j as createBaseVNode, l as unref, p as N8nText, w as withCtx, X as renderSlot, k as createTextVNode, t as toDisplayString, _ as _export_sfc, a2 as useRoute, af as useSourceControlStore, bP as useCanvasOperations, ad as useNodeTypesStore, bQ as START_NODE_TYPE, e as createBlock, f as createCommentVNode, a9 as Tooltip, bR as formatTokenUsageCount, bS as getDefaultExportFromCjs, bT as requireUpperFirst, F as Fragment, aG as useTemplateRef, bU as useTimestamp, bV as toTime, bW as toDayMonth, B as withModifiers, A as renderList, ap as normalizeStyle, bX as NodeIcon, ab as I18nT, N as N8nIcon, aa as _sfc_main$k, q as N8nButton, Y as nextTick, bY as getScrollbarWidth, bZ as useVirtualList, b_ as toRef, K as mergeProps, O as N8nRadioButtons, b$ as inject, c0 as isRef, c1 as toRefs, o as onMounted, c2 as normalizeProps, c3 as guardReactiveProps, c4 as resolveDynamicComponent, c5 as markdownLink, c6 as useFileDialog, c7 as onUnmounted, b2 as withDirectives, c8 as vModelText, b8 as withKeys, c9 as useAttrs, ca as useClipboard, br as createSlots, aY as useNDVStore, cb as PopOutWindowKey, cc as resolveDirective, cd as RunData, C as N8nLink, ce as waitingNodeTooltip, a_ as useLocalStorage, cf as LOG_DETAILS_PANEL_STATE, cg as KeyboardShortcutTooltip, ch as N8nResizeWrapper, ci as useStyles, aM as N8nActionDropdown, cj as Workflow, ck as LOGS_EXECUTION_DATA_THROTTLE_DURATION, cl as useThrottleFn, cm as parse, a3 as PLACEHOLDER_EMPTY_WORKFLOW_ID, cn as shallowRef, Q as useUIStore, co as useCanvasStore, al as useTelemetry, ax as useDocumentTitle, cp as onScopeDispose, W as onBeforeUnmount, cq as useProvideTooltipAppendTo, cr as LOGS_PANEL_STATE, cs as LOCAL_STORAGE_PANEL_HEIGHT, ct as LOCAL_STORAGE_OVERVIEW_PANEL_WIDTH, cu as LOCAL_STORAGE_PANEL_WIDTH, cv as useActiveElement, bn as useKeybindings, cw as ndvEventBus } from "./index--OJ5nhDf.js";
import { _ as __unplugin_components_1 } from "./AnimatedSpinner-CxbOZIWM.js";
import { _ as _sfc_main$j } from "./ConsumedTokensDetails.vue_vue_type_script_setup_true_lang-CSmXlf80.js";
import { H as HighlightJS, V as VueMarkdown } from "./core-Br-UFy15.js";
import { c as canvasEventBus } from "./canvas-DbK7UyVG.js";
const TOOL_EXECUTOR_NODE_NAME = "PartialExecutionToolExecutor";
function constructChatWebsocketUrl(url, executionId, sessionId2, isPublic) {
const baseUrl = new URL(url).origin;
const wsProtocol = baseUrl.startsWith("https") ? "wss" : "ws";
const wsUrl = baseUrl.replace(/^https?/, wsProtocol);
return `${wsUrl}/chat?sessionId=${sessionId2}&executionId=${executionId}${isPublic ? "&isPublic=true" : ""}`;
}
function getConsumedTokens(task) {
if (!task.data) {
return emptyTokenUsageData;
}
const tokenUsage = Object.values(task.data).flat().flat().reduce((acc, curr) => {
const tokenUsageData = curr?.json?.tokenUsage ?? curr?.json?.tokenUsageEstimate;
if (!tokenUsageData) return acc;
return addTokenUsageData(acc, {
...tokenUsageData,
isEstimate: !!curr?.json.tokenUsageEstimate
});
}, emptyTokenUsageData);
return tokenUsage;
}
function createNode(node, context, runIndex, runData, children = []) {
return {
parent: context.parent,
node,
// The ID consists of workflow ID, node ID and run index (including ancestor's), which
// makes it possible to identify the same log across different executions
id: `${context.workflow.id}:${node.id}:${[...context.ancestorRunIndexes, runIndex].join(":")}`,
runIndex,
runData,
children,
consumedTokens: runData ? getConsumedTokens(runData) : emptyTokenUsageData,
workflow: context.workflow,
executionId: context.executionId,
execution: context.data
};
}
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({
...context,
parent: treeNode,
ancestorRunIndexes: [...context.ancestorRunIndexes, runIndex ?? 0],
workflow,
executionId: subExecutionLocator.executionId,
data: subWorkflowRunData
});
}
const connectedSubNodes = context.workflow.getParentNodes(node.name, "ALL_NON_MAIN", 1);
function isMatchedSource(source) {
return (source?.previousNode === node.name || isPlaceholderLog(treeNode) && source?.previousNode === TOOL_EXECUTOR_NODE_NAME) && (runIndex === void 0 || source.previousNodeRun === runIndex);
}
return connectedSubNodes.flatMap(
(subNodeName) => (context.data.resultData.runData[subNodeName] ?? []).flatMap((t, index) => {
const isMatched = t.source.some((source) => source !== null) ? t.source.some(isMatchedSource) : runIndex === void 0 || index === runIndex;
if (!isMatched) {
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 === AGENT_LANGCHAIN_NODE_TYPE || entry.parent?.node.type === AGENT_LANGCHAIN_NODE_TYPE && isPlaceholderLog(entry.parent),
subTree
);
if (entryForAiAgent) {
return entryForAiAgent;
}
return subTree[subTree.length - 1];
}
function createLogTreeRec(context) {
const runData = context.data.resultData.runData;
return Object.entries(runData).flatMap(([nodeName, taskData]) => {
const node = context.workflow.getNode(nodeName);
if (node === null) {
return [];
}
const childNodes = context.workflow.getChildNodes(nodeName, "ALL_NON_MAIN");
if (childNodes.length === 0) {
return taskData.map((task, runIndex) => ({
node,
task,
runIndex,
nodeHasMultipleRuns: taskData.length > 1
}));
}
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 }];
}).flatMap(
({ node, runIndex, task, nodeHasMultipleRuns }) => getTreeNodeData(node, task, nodeHasMultipleRuns ? runIndex : void 0, context)
).sort(sortLogEntries);
}
function createLogTree(workflow, response, workflows = {}, subWorkflowData = {}) {
return createLogTreeRec({
parent: void 0,
ancestorRunIndexes: [],
executionId: response.id,
workflow,
workflows,
data: response.data ?? { resultData: { runData: {} } },
subWorkflowData
});
}
function findLogEntryRec(isMatched, entries) {
for (const entry of entries) {
if (isMatched(entry)) {
return entry;
}
const child = findLogEntryRec(isMatched, entry.children);
if (child) {
return child;
}
}
return void 0;
}
function findSelectedLogEntry(selection, entries, isExecuting) {
switch (selection.type) {
case "initial":
return isExecuting ? void 0 : findLogEntryToAutoSelect(entries);
case "none":
return void 0;
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] ?? [];
const mergedTasks = tasks.concat(
(startData[nodeName] ?? []).filter(
(task) => (
// To remove duplicate runs, we check start time in addition to execution index
// because nodes such as Wait and Form emits multiple websocket events with
// different execution index for a single run
tasks.every(
(t) => t.startTime < task.startTime && t.executionIndex !== task.executionIndex
)
)
).map((task) => ({
...task,
executionTime: 0,
executionStatus: "running"
}))
);
return [nodeName, mergedTasks];
})
);
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 === MANUAL_CHAT_TRIGGER_NODE_TYPE && node.typeVersion < 1.1) {
return "input";
}
if (node.type === CHAT_TRIGGER_NODE_TYPE) {
return "chatInput";
}
return "chatInput";
}
function extractChatInput(workflow, resultData) {
const chatTrigger = workflow.nodes.find(isChatNode);
if (chatTrigger === void 0) {
return void 0;
}
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 void 0;
}
return {
text: message,
sender: "user",
id: v4()
};
}
function extractBotResponse(resultData, executionId, emptyText2) {
const lastNodeExecuted = resultData.lastNodeExecuted;
if (!lastNodeExecuted) return void 0;
const nodeResponseDataArray = get(resultData.runData, lastNodeExecuted) ?? [];
const nodeResponseData = nodeResponseDataArray[nodeResponseDataArray.length - 1];
let responseMessage;
if (get(nodeResponseData, "error")) {
responseMessage = "[ERROR: " + get(nodeResponseData, "error.message") + "]";
} else {
const responseData = get(nodeResponseData, "data.main[0][0].json");
const text = extractResponseText(responseData) ?? emptyText2;
if (!text) {
return void 0;
}
responseMessage = text;
}
return {
text: responseMessage,
sender: "bot",
id: executionId ?? v4()
};
}
function extractResponseText(responseData) {
if (!responseData || isEmpty(responseData)) {
return void 0;
}
const paths = ["output", "text", "response.text", "message"];
const matchedPath = paths.find((path) => get(responseData, path));
if (!matchedPath) return JSON.stringify(responseData, null, 2);
const matchedOutput = get(responseData, matchedPath);
if (typeof matchedOutput === "object") {
return "```json\n" + JSON.stringify(matchedOutput, null, 2) + "\n```";
}
return matchedOutput?.toString() ?? "";
}
function restoreChatHistory(workflowExecutionData, emptyText2) {
if (!workflowExecutionData?.data) {
return [];
}
const userMessage = extractChatInput(
workflowExecutionData.workflowData,
workflowExecutionData.data.resultData
);
const botMessage = extractBotResponse(
workflowExecutionData.data.resultData,
workflowExecutionData.id,
emptyText2
);
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(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 useChatMessaging({
chatTrigger,
messages: messages2,
sessionId: sessionId2,
executionResultData,
onRunChatWorkflow,
ws
}) {
const locale = useI18n$1();
const { showError } = useToast();
const previousMessageIndex = ref(0);
const isLoading = ref(false);
const setLoadingState = (loading) => {
isLoading.value = loading;
};
async function convertFileToBinaryData(file) {
const reader = new FileReader();
return await new Promise((resolve, reject) => {
reader.onload = () => {
const binaryData = {
data: reader.result.split("base64,")?.[1] ?? "",
mimeType: file.type,
fileName: file.name,
fileSize: `${file.size} bytes`,
fileExtension: file.name.split(".").pop() ?? "",
fileType: file.type.split("/")[0]
};
resolve(binaryData);
};
reader.onerror = () => {
reject(new Error("Failed to convert file to binary data"));
};
reader.readAsDataURL(file);
});
}
async function getKeyedFiles(files) {
const binaryData = {};
await Promise.all(
files.map(async (file, index) => {
const data = await convertFileToBinaryData(file);
const key = `data${index}`;
binaryData[key] = data;
})
);
return binaryData;
}
function extractFileMeta(file) {
return {
fileName: file.name,
fileSize: `${file.size} bytes`,
fileExtension: file.name.split(".").pop() ?? "",
fileType: file.type.split("/")[0],
mimeType: file.type
};
}
async function startWorkflowWithMessage(message, files) {
const triggerNode = chatTrigger.value;
if (!triggerNode) {
showError(new Error("Chat Trigger Node could not be found!"), "Trigger Node not found");
return;
}
const inputKey = getInputKey(triggerNode);
const inputPayload = {
json: {
sessionId: sessionId2.value,
action: "sendMessage",
[inputKey]: message
}
};
if (files && files.length > 0) {
const filesMeta = files.map((file) => extractFileMeta(file));
const binaryData = await getKeyedFiles(files);
inputPayload.json.files = filesMeta;
inputPayload.binary = binaryData;
}
const nodeData = {
startTime: Date.now(),
executionTime: 0,
executionIndex: 0,
executionStatus: "success",
data: {
main: [[inputPayload]]
},
source: [null]
};
isLoading.value = true;
const response = await onRunChatWorkflow({
triggerNode: triggerNode.name,
nodeData,
source: "RunData.ManualChatMessage",
message
});
isLoading.value = false;
ws.value = null;
if (!response?.executionId) {
return;
}
const responseMode = triggerNode.parameters.options?.responseMode;
if (responseMode === "responseNodes") return;
const chatMessage = executionResultData.value ? extractBotResponse(
executionResultData.value,
response.executionId,
locale.baseText("chat.window.chat.response.empty")
) : void 0;
if (chatMessage !== void 0) {
messages2.value.push(chatMessage);
}
}
async function sendMessage(message, files) {
previousMessageIndex.value = 0;
if (message.trim() === "" && (!files || files.length === 0)) {
showError(
new Error(locale.baseText("chat.window.chat.provideMessage")),
locale.baseText("chat.window.chat.emptyChatMessage")
);
return;
}
const pinnedChatData = usePinnedData(chatTrigger.value);
if (pinnedChatData.hasData.value) {
const confirmResult = await useMessage().confirm(
locale.baseText("chat.window.chat.unpinAndExecute.description"),
locale.baseText("chat.window.chat.unpinAndExecute.title"),
{
confirmButtonText: locale.baseText("chat.window.chat.unpinAndExecute.confirm"),
cancelButtonText: locale.baseText("chat.window.chat.unpinAndExecute.cancel")
}
);
if (!(confirmResult === MODAL_CONFIRM)) return;
pinnedChatData.unsetData("unpin-and-send-chat-message-modal");
}
const newMessage = {
text: message,
sender: "user",
sessionId: sessionId2.value,
id: v4(),
files
};
messages2.value.push(newMessage);
if (ws.value?.readyState === WebSocket.OPEN && !isLoading.value) {
ws.value.send(
JSON.stringify({
sessionId: sessionId2.value,
action: "sendMessage",
chatInput: message,
files: await processFiles(files)
})
);
isLoading.value = true;
} else {
await startWorkflowWithMessage(newMessage.text, files);
}
}
return {
previousMessageIndex,
isLoading: computed(() => isLoading.value),
setLoadingState,
sendMessage
};
}
const ChatSymbol$1 = "Chat";
const ChatOptionsSymbol = "ChatOptions";
const ChatSymbol = "Chat";
function useChatState(isReadOnly) {
const locale = useI18n$1();
const workflowsStore = useWorkflowsStore();
const rootStore = useRootStore();
const logsStore = useLogsStore();
const router = useRouter();
const nodeHelpers = useNodeHelpers();
const { runWorkflow } = useRunWorkflow({ router });
const ws = ref(null);
const messages2 = ref([]);
const currentSessionId = ref(v4().replace(/-/g, ""));
const previousChatMessages = computed(() => workflowsStore.getPastChatMessages);
const chatTriggerNode = computed(() => workflowsStore.allNodes.find(isChatNode) ?? null);
const allowFileUploads = computed(
() => chatTriggerNode.value?.parameters?.options?.allowFileUploads === true
);
const allowedFilesMimeTypes = computed(
() => chatTriggerNode.value?.parameters?.options?.allowedFilesMimeTypes?.toString() ?? ""
);
const respondNodesResponseMode = computed(
() => chatTriggerNode.value?.parameters?.options?.responseMode === "responseNodes"
);
const { sendMessage, isLoading, setLoadingState } = useChatMessaging({
chatTrigger: chatTriggerNode,
messages: messages2,
sessionId: currentSessionId,
executionResultData: computed(() => workflowsStore.getWorkflowExecution?.data?.resultData),
onRunChatWorkflow,
ws
});
function createChatConfig(params) {
const chatConfig2 = {
messages: params.messages,
sendMessage: params.sendMessage,
initialMessages: ref([]),
currentSessionId: params.currentSessionId,
waitingForResponse: params.isLoading
};
const chatOptions2 = {
i18n: {
en: {
title: "",
footer: "",
subtitle: "",
inputPlaceholder: params.locale.baseText("chat.window.chat.placeholder"),
getStarted: "",
closeButtonTooltip: ""
}
},
webhookUrl: "",
mode: "window",
showWindowCloseButton: true,
disabled: params.isDisabled,
allowFileUploads: params.allowFileUploads,
allowedFilesMimeTypes
};
return { chatConfig: chatConfig2, chatOptions: chatOptions2 };
}
const { chatConfig, chatOptions } = createChatConfig({
messages: messages2,
sendMessage,
currentSessionId,
isLoading,
isDisabled: computed(() => isReadOnly),
allowFileUploads,
locale
});
const restoredChatMessages = computed(
() => restoreChatHistory(
workflowsStore.workflowExecutionData,
locale.baseText("chat.window.chat.response.empty")
)
);
provide(ChatSymbol, chatConfig);
provide(ChatOptionsSymbol, chatOptions);
async function createExecutionPromise() {
return await new Promise((resolve) => {
const resolveIfFinished = (isRunning) => {
if (!isRunning) {
unwatch();
resolve();
}
};
const unwatch = watch(() => workflowsStore.isWorkflowRunning, resolveIfFinished);
resolveIfFinished(workflowsStore.isWorkflowRunning);
});
}
async function onRunChatWorkflow(payload) {
const runWorkflowOptions = {
triggerNode: payload.triggerNode,
nodeData: payload.nodeData,
source: payload.source
};
if (workflowsStore.chatPartialExecutionDestinationNode) {
runWorkflowOptions.destinationNode = workflowsStore.chatPartialExecutionDestinationNode;
workflowsStore.chatPartialExecutionDestinationNode = null;
}
const response = await runWorkflow(runWorkflowOptions);
if (response) {
if (respondNodesResponseMode.value) {
const wsUrl = constructChatWebsocketUrl(
rootStore.urlBaseEditor,
response.executionId,
currentSessionId.value,
false
);
ws.value = new WebSocket(wsUrl);
ws.value.onmessage = (event) => {
if (event.data === "n8n|heartbeat") {
ws.value?.send("n8n|heartbeat-ack");
return;
}
if (event.data === "n8n|continue") {
setLoadingState(true);
return;
}
setLoadingState(false);
const newMessage = {
text: event.data,
sender: "bot",
sessionId: currentSessionId.value,
id: v4()
};
messages2.value.push(newMessage);
if (logsStore.isOpen) {
chatEventBus.emit("focusInput");
}
};
ws.value.onclose = () => {
setLoadingState(false);
ws.value = null;
};
}
await createExecutionPromise();
workflowsStore.appendChatMessage(payload.message);
return response;
}
return;
}
function refreshSession() {
workflowsStore.setWorkflowExecutionData(null);
nodeHelpers.updateNodesExecutionIssues();
messages2.value = [];
currentSessionId.value = v4().replace(/-/g, "");
if (logsStore.isOpen) {
chatEventBus.emit("focusInput");
}
}
function displayExecution(executionId) {
const route = router.resolve({
name: VIEWS.EXECUTION_PREVIEW,
params: { name: workflowsStore.workflowId, executionId }
});
window.open(route.href, "_blank");
}
return {
currentSessionId,
messages: computed(() => isReadOnly ? restoredChatMessages.value : messages2.value),
previousChatMessages,
sendMessage,
refreshSession,
displayExecution
};
}
const _sfc_main$i = /* @__PURE__ */ defineComponent({
__name: "LogsPanelHeader",
props: {
title: {}
},
emits: ["click"],
setup(__props, { emit: __emit }) {
const emit = __emit;
return (_ctx, _cache) => {
return openBlock(), createElementBlock("header", {
class: normalizeClass(_ctx.$style.container),
onClick: _cache[0] || (_cache[0] = ($event) => emit("click"))
}, [
createVNode(unref(N8nText), {
class: normalizeClass(_ctx.$style.title),
bold: true,
size: "small"
}, {
default: withCtx(() => [
renderSlot(_ctx.$slots, "title", {}, () => [
createTextVNode(toDisplayString(_ctx.title), 1)
])
]),
_: 3
}, 8, ["class"]),
createBaseVNode("div", {
class: normalizeClass(_ctx.$style.actions)
}, [
renderSlot(_ctx.$slots, "actions")
], 2)
], 2);
};
}
});
const container$8 = "_container_1uwbw_123";
const title$2 = "_title_1uwbw_144";
const actions$1 = "_actions_1uwbw_152";
const style0$b = {
container: container$8,
title: title$2,
actions: actions$1
};
const cssModules$b = {
"$style": style0$b
};
const LogsPanelHeader = /* @__PURE__ */ _export_sfc(_sfc_main$i, [["__cssModules", cssModules$b]]);
function useClearExecutionButtonVisible() {
const route = useRoute();
const sourceControlStore = useSourceControlStore();
const workflowsStore = useWorkflowsStore();
const workflowExecutionData = computed(() => workflowsStore.workflowExecutionData);
const isWorkflowRunning = computed(() => workflowsStore.isWorkflowRunning);
const isReadOnlyRoute = computed(() => !!route?.meta?.readOnlyCanvas);
const { editableWorkflow } = useCanvasOperations();
const nodeTypesStore = useNodeTypesStore();
const isReadOnlyEnvironment = computed(() => sourceControlStore.preferences.branchReadOnly);
const allTriggerNodesDisabled = computed(
() => editableWorkflow.value.nodes.filter((node) => node.type === START_NODE_TYPE || nodeTypesStore.isTriggerNode(node.type)).every((node) => node.disabled)
);
return computed(
() => !isReadOnlyRoute.value && !isReadOnlyEnvironment.value && !isWorkflowRunning.value && !allTriggerNodesDisabled.value && !!workflowExecutionData.value
);
}
const _sfc_main$h = /* @__PURE__ */ defineComponent({
__name: "LogsViewConsumedTokenCountText",
props: {
consumedTokens: {}
},
setup(__props) {
const locale = useI18n$1();
return (_ctx, _cache) => {
const _component_ConsumedTokensDetails = _sfc_main$j;
return _ctx.consumedTokens !== void 0 ? (openBlock(), createBlock(unref(Tooltip), {
key: 0,
enterable: false
}, {
content: withCtx(() => [
createVNode(_component_ConsumedTokensDetails, { "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 upperFirstExports = requireUpperFirst();
const upperFirst = /* @__PURE__ */ getDefaultExportFromCjs(upperFirstExports);
const _hoisted_1$e = { key: 0 };
const _sfc_main$g = /* @__PURE__ */ defineComponent({
__name: "LogsViewNodeName",
props: {
name: {},
isError: { type: Boolean },
isDeleted: { type: Boolean }
},
setup(__props) {
return (_ctx, _cache) => {
return openBlock(), createBlock(unref(N8nText), {
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$e, toDisplayString(_ctx.name), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
createTextVNode(toDisplayString(_ctx.name), 1)
], 64))
]),
_: 1
}, 8, ["class", "color"]);
};
}
});
const name$1 = "_name_1t0q3_123";
const style0$a = {
name: name$1
};
const cssModules$a = {
"$style": style0$a
};
const LogsViewNodeName = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__cssModules", cssModules$a]]);
const _hoisted_1$d = ["aria-expanded", "aria-selected"];
const _sfc_main$f = /* @__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 container2 = useTemplateRef("containerRef");
const locale = useI18n$1();
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(() => upperFirst(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) => {
void nextTick(() => {
if (isSelected) {
container2.value?.focus();
}
});
},
{ immediate: true }
);
return (_ctx, _cache) => {
const _component_NodeIcon = NodeIcon;
const _component_AnimatedSpinner = __unplugin_components_1;
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, (indent2, level) => {
return openBlock(), createElementBlock("div", {
key: level,
class: normalizeClass({
[_ctx.$style.indent]: true,
[_ctx.$style.connectorCurved]: indent2.curved,
[_ctx.$style.connectorStraight]: indent2.straight
})
}, null, 2);
}), 128)),
createBaseVNode("div", {
class: normalizeClass(_ctx.$style.background),
style: normalizeStyle({ "--indent-depth": indents.value.length })
}, null, 6),
createVNode(_component_NodeIcon, {
"node-type": type.value,
size: 16,
class: normalizeClass(_ctx.$style.icon)
}, null, 8, ["node-type", "class"]),
createVNode(LogsViewNodeName, {
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), {
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), {
color: isError.value ? "danger" : void 0,
bold: isError.value,
size: "small"
}, {
default: withCtx(() => [
isRunning.value ? (openBlock(), createBlock(_component_AnimatedSpinner, {
key: 0,
class: normalizeClass(_ctx.$style.statusTextIcon)
}, null, 8, ["class"])) : isWaiting.value ? (openBlock(), createBlock(unref(N8nIcon), {
key: 1,
icon: "status-waiting",
class: normalizeClass(_ctx.$style.statusTextIcon)
}, null, 8, ["class"])) : isError.value ? (openBlock(), createBlock(unref(N8nIcon), {
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), {
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), {
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(_sfc_main$h, {
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), {
key: 3,
size: "medium",
color: "danger",
icon: "triangle-alert",
class: normalizeClass(_ctx.$style.compactErrorIcon)
}, null, 8, ["class"])) : createCommentVNode("", true),
!_ctx.isCompact || !props.latestInfo?.deleted ? (openBlock(), createBlock(unref(_sfc_main$k), {
key: 4,
type: "secondary",
size: "small",
icon: "square-pen",
"icon-size": "medium",
style: normalizeStyle({
visibility: props.canOpenNdv ? "" : "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(_sfc_main$k), {
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(_component_AnimatedSpinner, {
key: 0,
class: normalizeClass(_ctx.$style.statusIcon)
}, null, 8, ["class"])) : isWaiting.value ? (openBlock(), createBlock(unref(N8nIcon), {
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), {
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$d);
};
}
});
const container$7 = "_container_6ygvb_123";
const background = "_background_6ygvb_140";
const selected = "_selected_6ygvb_149";
const error = "_error_6ygvb_155";
const indent = "_indent_6ygvb_159";
const connectorCurved = "_connectorCurved_6ygvb_168";
const connectorStraight = "_connectorStraight_6ygvb_178";
const icon$3 = "_icon_6ygvb_187";
const name = "_name_6ygvb_193";
const timeTook = "_timeTook_6ygvb_199";
const statusTextIcon = "_statusTextIcon_6ygvb_204";
const startedAt = "_startedAt_6ygvb_209";
const consumedTokens = "_consumedTokens_6ygvb_215";
const compactErrorIcon = "_compactErrorIcon_6ygvb_222";
const partialExecutionButton = "_partialExecutionButton_6ygvb_234";
const openNdvButton = "_openNdvButton_6ygvb_235";
const compact = "_compact_6ygvb_222";
const unavailable = "_unavailable_6ygvb_245";
const toggleButton = "_toggleButton_6ygvb_253";
const statusIcon = "_statusIcon_6ygvb_277";
const placeholder$1 = "_placeholder_6ygvb_285";
const style0$9 = {
container: container$7,
background,
selected,
error,
indent,
connectorCurved,
connectorStraight,
icon: icon$3,
name,
timeTook,
statusTextIcon,
startedAt,
consumedTokens,
compactErrorIcon,
partialExecutionButton,
openNdvButton,
compact,
unavailable,
toggleButton,
statusIcon,
placeholder: placeholder$1
};
const cssModules$9 = {
"$style": style0$9
};
const LogsOverviewRow = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["__cssModules", cssModules$9]]);
const _sfc_main$e = /* @__PURE__ */ defineComponent({
__name: "LogsViewExecutionSummary",
props: {
status: {},
consumedTokens: {},
startTime: {},
timeTook: {}
},
setup(__props) {
const locale = useI18n$1();
const now = useTimestamp({ interval: 1e3 });
const executionStatusText = computed(
() => __props.status === "running" || __props.status === "waiting" ? locale.baseText("logs.overview.body.summaryText.for", {
interpolate: {
status: upperFirst(__props.status),
time: locale.displayTimer(Math.floor((now.value - __props.startTime) / 1e3) * 1e3, true)
}
}) : __props.timeTook === void 0 ? upperFirst(__props.status) : locale.baseText("logs.overview.body.summaryText.in", {
interpolate: {
status: upperFirst(__props.status),
time: locale.displayTimer(__props.timeTook, true)
}
})
);
return (_ctx, _cache) => {
return openBlock(), createBlock(unref(N8nText), {
tag: "div",
color: "text-light",
size: "small",
class: normalizeClass(_ctx.$style.container)
}, {
default: withCtx(() => [
createBaseVNode("span", null, toDisplayString(executionStatusText.value), 1),
_ctx.consumedTokens.totalTokens > 0 ? (openBlock(), createBlock(_sfc_main$h, {
key: 0,
"consumed-tokens": _ctx.consumedTokens
}, null, 8, ["consumed-tokens"])) : createCommentVNode("", true)
]),
_: 1
}, 8, ["class"]);
};
}
});
const container$6 = "_container_pt5hk_123";
const style0$8 = {
container: container$6
};
const cssModules$8 = {
"$style": style0$8
};
const LogsViewExecutionSummary = /* @__PURE__ */ _export_sfc(_sfc_main$e, [["__cssModules", cssModules$8]]);
const _sfc_main$d = /* @__PURE__ */ defineComponent({
__name: "LogsOverviewPanel",
props: {
isOpen: { type: Boolean },
selected: {},
isReadOnly: { type: Boolean },
isCompact: { type: Boolean },
execution: {},
entries: {},
flatLogEntries: {},
latestNodeInfo: {}
},
emits: ["clickHeader", "select", "clearExecutionData", "openNdv", "toggleExpanded"],
setup(__props, { emit: __emit }) {
const emit = __emit;
const locale = useI18n$1();
const router = useRouter();
const runWorkflow = useRunWorkflow({ router });
const isClearExecutionButtonVisible = useClearExecutionButtonVisible();
const isEmpty2 = computed(() => __props.flatLogEntries.length === 0 || __props.execution === void 0);
const switchViewOptions = computed(() => [
{ label: locale.baseText("logs.overview.header.switch.overview"), value: "overview" },
{ label: locale.baseText("logs.overview.header.switch.details"), value: "details" }
]);
const hasStaticScrollbar = getScrollbarWidth() > 0;
const consumedTokens2 = computed(
() => getTotalConsumedTokens(
...__props.entries.map(
(entry) => getSubtreeTotalConsumedTokens(
entry,
false
// Exclude token usages from sub workflow which is loaded only after expanding the row
)
)
)
);
const shouldShowTokenCountColumn = computed(
() => consumedTokens2.value.totalTokens > 0 || __props.entries.some((entry) => getSubtreeTotalConsumedTokens(entry, true).totalTokens > 0)
);
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 }
);
function handleSwitchView(value) {
emit("select", value === "overview" ? void 0 : __props.flatLogEntries[0]);
}
async function handleTriggerPartialExecution(treeNode) {
const latestName = __props.latestNodeInfo[treeNode.node.id]?.name ?? treeNode.node.name;
if (latestName) {
await runWorkflow.runWorkflow({ destinationNode: latestName });
}
}
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", {
class: normalizeClass([_ctx.$style.container, hasStaticScrollbar ? _ctx.$style.staticScrollBar : ""]),
"data-test-id": "logs-overview"
}, [
createVNode(LogsPanelHeader, {
title: unref(locale).baseText("logs.overview.header.title"),
"data-test-id": "logs-overview-header",
onClick: _cache[1] || (_cache[1] = ($event) => emit("clickHeader"))
}, {
actions: withCtx(() => [
unref(isClearExecutionButtonVisible) ? (openBlock(), createBlock(unref(Tooltip), {
key: 0,
content: unref(locale).baseText("logs.overview.header.actions.clearExecution.tooltip")
}, {
default: withCtx(() => [
createVNode(unref(N8nButton), {
size: "mini",
type: "secondary",
icon: "trash-2",
"icon-size": "medium",
"data-test-id": "clear-execution-data-button",
class: normalizeClass(_ctx.$style.clearButton),
onClick: _cache[0] || (_cache[0] = withModifiers(($event) => emit("clearExecutionData"), ["stop"]))
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(unref(locale).baseText("logs.overview.header.actions.clearExecution")), 1)
]),
_: 1
}, 8, ["class"])
]),
_: 1
}, 8, ["content"])) : createCommentVNode("", true),
renderSlot(_ctx.$slots, "actions")
]),
_: 3
}, 8, ["title"]),
_ctx.isOpen ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass([_ctx.$style.content, isEmpty2.value ? _ctx.$style.empty : ""]),
"data-test-id": "logs-overview-body"
}, [
isEmpty2.value || _ctx.execution === void 0 ? (openBlock(), createBlock(unref(N8nText), {
key: 0,
tag: "p",
size: "medium",
color: "text-base",
class: normalizeClass(_ctx.$style.emptyText),
"data-test-id": "logs-overview-empty"
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(unref(locale).baseText("logs.overview.body.empty.message")), 1)
]),
_: 1
}, 8, ["class"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
createVNode(LogsViewExecutionSummary, {
"data-test-id": "logs-overview-status",
class: normalizeClass(_ctx.$style.summary),
status: _ctx.execution.status,
"consumed-tokens": consumedTokens2.value,
"start-time": +new Date(_ctx.execution.startedAt),
"time-took": _ctx.execution.startedAt && _ctx.execution.stoppedAt ? +new Date(_ctx.execution.stoppedAt) - +new Date(_ctx.execution.startedAt) : void 0
}, null, 8, ["class", "status", "consumed-tokens", "start-time", "time-took"]),
createBaseVNode("div", mergeProps({
class: _ctx.$style.tree
}, unref(virtualList).containerProps), [
createBaseVNode("div", mergeProps(unref(virtualList).wrapperProps.value, { role: "tree" }), [
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(virtualList).list.value, ({ data, index }) => {
return openBlock(), createBlock(LogsOverviewRow, {
key: index,
data,
"is-read-only": _ctx.isReadOnly,
"is-selected": data.id === _ctx.selected?.id,
"is-compact": _ctx.isCompact,
"should-show-token-count-column": shouldShowTokenCountColumn.value,
"latest-info": _ctx.latestNodeInfo[data.node.id],
expanded: isExpanded.value[data.id],
"can-open-ndv": data.executionId === _ctx.execution?.id,
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),
createVNode(unref(N8nRadioButtons), {
size: "small-medium",
class: normalizeClass(_ctx.$style.switchViewButtons),
"model-value": _ctx.selected ? "details" : "overview",
options: switchViewOptions.value,
"onUpdate:modelValue": handleSwitchView
}, null, 8, ["class", "model-value", "options"])
], 64))
], 2)) : createCommentVNode("", true)
], 2);
};
}
});
const container$5 = "_container_pydb1_123";
const clearButton = "_clearButton_pydb1_133";
const content$1 = "_content_pydb1_139";
const empty = "_empty_pydb1_149";
const emptyText = "_emptyText_pydb1_154";
const summary = "_summary_pydb1_159";
const tree = "_tree_pydb1_163";
const staticScrollBar = "_staticScrollBar_pydb1_168";
const switchViewButtons = "_switchViewButtons_pydb1_193";
const style0$7 = {
container: container$5,
clearButton,
content: content$1,
empty,
emptyText,
summary,
tree,
staticScrollBar,
switchViewButtons
};
const cssModules$7 = {
"$style": style0$7
};
const LogsOverviewPanel = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["__cssModules", cssModules$7]]);
function bash(hljs) {
const regex = hljs.regex;
const VAR = {};
const BRACED_VAR = {
begin: /\$\{/,
end: /\}/,
contains: [
"self",
{
begin: /:-/,
contains: [VAR]
}
// default values
]
};
Object.assign(VAR, {
className: "variable",
variants: [
{ begin: regex.concat(
/\$[\w\d#@][\w\d_]*/,
// negative look-ahead tries to avoid matching patterns that are not
// Perl at all like $ident$, @ident@, etc.
`(?![\\w\\d])(?![$])`
) },
BRACED_VAR
]
});
const SUBST = {
className: "subst",
begin: /\$\(/,
end: /\)/,
contains: [hljs.BACKSLASH_ESCAPE]
};
const COMMENT = hljs.inherit(
hljs.COMMENT(),
{
match: [
/(^|\s)/,
/#.*$/
],
scope: {
2: "comment"
}
}
);
const HERE_DOC = {
begin: /<<-?\s*(?=\w+)/,
starts: { contains: [
hljs.END_SAME_AS_BEGIN({
begin: /(\w+)/,
end: /(\w+)/,
className: "string"
})
] }
};
const QUOTE_STRING = {
className: "string",
begin: /"/,
end: /"/,
contains: [
hljs.BACKSLASH_ESCAPE,
VAR,
SUBST
]
};
SUBST.contains.push(QUOTE_STRING);
const ESCAPED_QUOTE = {
match: /\\"/
};
const APOS_STRING = {
className: "string",
begin: /'/,
end: /'/
};
const ESCAPED_APOS = {
match: /\\'/
};
const ARITHMETIC = {
begin: /\$?\(\(/,
end: /\)\)/,
contains: [
{
begin: /\d+#[0-9a-f]+/,
className: "number"
},
hljs.NUMBER_MODE,
VAR
]
};
const SH_LIKE_SHELLS = [
"fish",
"bash",
"zsh",
"sh",
"csh",
"ksh",
"tcsh",
"dash",
"scsh"
];
const KNOWN_SHEBANG = hljs.SHEBANG({
binary: `(${SH_LIKE_SHELLS.join("|")})`,
relevance: 10
});
const FUNCTION = {
className: "function",
begin: /\w[\w\d_]*\s*\(\s*\)\s*\{/,
returnBegin: true,
contains: [hljs.inherit(hljs.TITLE_MODE, { begin: /\w[\w\d_]*/ })],
relevance: 0
};
const KEYWORDS2 = [
"if",
"then",
"else",
"elif",
"fi",
"time",
"for",
"while",
"until",
"in",
"do",
"done",
"case",
"esac",
"coproc",
"function",
"select"
];
const LITERALS2 = [
"true",
"false"
];
const PATH_MODE = { match: /(\/[a-z._-]+)+/ };
const SHELL_BUILT_INS = [
"break",
"cd",
"continue",
"eval",
"exec",
"exit",
"export",
"getopts",
"hash",
"pwd",
"readonly",
"return",
"shift",
"test",
"times",
"trap",
"umask",
"unset"
];
const BASH_BUILT_INS = [
"alias",
"bind",
"builtin",
"caller",
"command",
"declare",
"echo",
"enable",
"help",
"let",
"local",
"logout",
"mapfile",
"printf",
"read",
"readarray",
"source",
"sudo",
"type",
"typeset",
"ulimit",
"unalias"
];
const ZSH_BUILT_INS = [
"autoload",
"bg",
"bindkey",
"bye",
"cap",
"chdir",
"clone",
"comparguments",
"compcall",
"compctl",
"compdescribe",
"compfiles",
"compgroups",
"compquote",
"comptags",
"comptry",
"compvalues",
"dirs",
"disable",
"disown",
"echotc",
"echoti",
"emulate",
"fc",
"fg",
"float",
"functions",
"getcap",
"getln",
"history",
"integer",
"jobs",
"kill",
"limit",
"log",
"noglob",
"popd",
"print",
"pushd",
"pushln",
"rehash",
"sched",
"setcap",
"setopt",
"stat",
"suspend",
"ttyctl",
"unfunction",
"unhash",
"unlimit",
"unsetopt",
"vared",
"wait",
"whence",
"where",
"which",
"zcompile",
"zformat",
"zftp",
"zle",
"zmodload",
"zparseopts",
"zprof",
"zpty",
"zregexparse",
"zsocket",
"zstyle",
"ztcp"
];
const GNU_CORE_UTILS = [
"chcon",
"chgrp",
"chown",
"chmod",
"cp",
"dd",
"df",
"dir",
"dircolors",
"ln",
"ls",
"mkdir",
"mkfifo",
"mknod",
"mktemp",
"mv",
"realpath",
"rm",
"rmdir",
"shred",
"sync",
"touch",
"truncate",
"vdir",
"b2sum",
"base32",
"base64",
"cat",
"cksum",
"comm",
"csplit",
"cut",
"expand",
"fmt",
"fold",
"head",
"join",
"md5sum",
"nl",
"numfmt",
"od",
"paste",
"ptx",
"pr",
"sha1sum",
"sha224sum",
"sha256sum",
"sha384sum",
"sha512sum",
"shuf",
"sort",
"split",
"sum",
"tac",
"tail",
"tr",
"tsort",
"unexpand",
"uniq",
"wc",
"arch",
"basename",
"chroot",
"date",
"dirname",
"du",
"echo",
"env",
"expr",
"factor",
// "false", // keyword literal already
"groups",
"hostid",
"id",
"link",
"logname",
"nice",
"nohup",
"nproc",
"pathchk",
"pinky",
"printenv",
"printf",
"pwd",
"readlink",
"runcon",
"seq",
"sleep",
"stat",
"stdbuf",
"stty",
"tee",
"test",
"timeout",
// "true", // keyword literal already
"tty",
"uname",
"unlink",
"uptime",
"users",
"who",
"whoami",
"yes"
];
return {
name: "Bash",
aliases: [
"sh",
"zsh"
],
keywords: {
$pattern: /\b[a-z][a-z0-9._-]+\b/,
keyword: KEYWORDS2,
literal: LITERALS2,
built_in: [
...SHELL_BUILT_INS,
...BASH_BUILT_INS,
// Shell modifiers
"set",
"shopt",
...ZSH_BUILT_INS,
...GNU_CORE_UTILS
]
},
contains: [
KNOWN_SHEBANG,
// to catch known shells and boost relevancy
hljs.SHEBANG(),
// to catch unknown shells but still highlight the shebang
FUNCTION,
ARITHMETIC,
COMMENT,
HERE_DOC,
PATH_MODE,
QUOTE_STRING,
ESCAPED_QUOTE,
APOS_STRING,
ESCAPED_APOS,
VAR
]
};
}
const IDENT_RE$1 = "[A-Za-z$_][0-9A-Za-z$_]*";
const KEYWORDS$1 = [
"as",
// for exports
"in",
"of",
"if",
"for",
"while",
"finally",
"var",
"new",
"function",
"do",
"return",
"void",
"else",
"break",
"catch",
"instanceof",
"with",
"throw",
"case",
"default",
"try",
"switch",
"continue",
"typeof",
"delete",
"let",
"yield",
"const",
"class",
// JS handles these with a special rule
// "get",
// "set",
"debugger",
"async",
"await",
"static",
"import",
"from",
"export",
"extends",
// It's reached stage 3, which is "recommended for implementation":
"using"
];
const LITERALS$1 = [
"true",
"false",
"null",
"undefined",
"NaN",
"Infinity"
];
const TYPES$1 = [
// Fundamental objects
"Object",
"Function",
"Boolean",
"Symbol",
// numbers and dates
"Math",
"Date",
"Number",
"BigInt",
// text
"String",
"RegExp",
// Indexed collections
"Array",
"Float32Array",
"Float64Array",
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Int32Array",
"Uint16Array",
"Uint32Array",
"BigInt64Array",
"BigUint64Array",
// Keyed collections
"Set",
"Map",
"WeakSet",
"WeakMap",
// Structured data
"ArrayBuffer",
"SharedArrayBuffer",
"Atomics",
"DataView",
"JSON",
// Control abstraction objects
"Promise",
"Generator",
"GeneratorFunction",
"AsyncFunction",
// Reflection
"Reflect",
"Proxy",
// Internationalization
"Intl",
// WebAssembly
"WebAssembly"
];
const ERROR_TYPES$1 = [
"Error",
"EvalError",
"InternalError",
"RangeError",
"ReferenceError",
"SyntaxError",
"TypeError",
"URIError"
];
const BUILT_IN_GLOBALS$1 = [
"setInterval",
"setTimeout",
"clearInterval",
"clearTimeout",
"require",
"exports",
"eval",
"isFinite",
"isNaN",
"parseFloat",
"parseInt",
"decodeURI",
"decodeURIComponent",
"encodeURI",
"encodeURIComponent",
"escape",
"unescape"
];
const BUILT_IN_VARIABLES$1 = [
"arguments",
"this",
"super",
"console",
"window",
"document",
"localStorage",
"sessionStorage",
"module",
"global"
// Node.js
];
const BUILT_INS$1 = [].concat(
BUILT_IN_GLOBALS$1,
TYPES$1,
ERROR_TYPES$1
);
function javascript$1(hljs) {
const regex = hljs.regex;
const hasClosingTag = (match, { after }) => {
const tag = "</" + match[0].slice(1);
const pos = match.input.indexOf(tag, after);
return pos !== -1;
};
const IDENT_RE$1$1 = IDENT_RE$1;
const FRAGMENT = {
begin: "<>",
end: "</>"
};
const XML_SELF_CLOSING = /<[A-Za-z0-9\\._:-]+\s*\/>/;
const XML_TAG = {
begin: /<[A-Za-z0-9\\._:-]+/,
end: /\/[A-Za-z0-9\\._:-]+>|\/>/,
/**
* @param {RegExpMatchArray} match
* @param {CallbackResponse} response
*/
isTrulyOpeningTag: (match, response) => {
const afterMatchIndex = match[0].length + match.index;
const nextChar = match.input[afterMatchIndex];
if (
// HTML should not include another raw `<` inside a tag
// nested type?
// `<Array<Array<number>>`, etc.
nextChar === "<" || // the , gives away that this is not HTML
// `<T, A extends keyof T, V>`
nextChar === ","
) {
response.ignoreMatch();
return;
}
if (nextChar === ">") {
if (!hasClosingTag(match, { after: afterMatchIndex })) {
response.ignoreMatch();
}
}
let m;
const afterMatch = match.input.substring(afterMatchIndex);
if (m = afterMatch.match(/^\s*=/)) {
response.ignoreMatch();
return;
}
if (m = afterMatch.match(/^\s+extends\s+/)) {
if (m.index === 0) {
response.ignoreMatch();
return;
}
}
}
};
const KEYWORDS$1$1 = {
$pattern: IDENT_RE$1,
keyword: KEYWORDS$1,
literal: LITERALS$1,
built_in: BUILT_INS$1,
"variable.language": BUILT_IN_VARIABLES$1
};
const decimalDigits = "[0-9](_?[0-9])*";
const frac = `\\.(${decimalDigits})`;
const decimalInteger = `0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`;
const NUMBER = {
className: "number",
variants: [
// DecimalLiteral
{ begin: `(\\b(${decimalInteger})((${frac})|\\.)?|(${frac}))[eE][+-]?(${decimalDigits})\\b` },
{ begin: `\\b(${decimalInteger})\\b((${frac})\\b|\\.)?|(${frac})\\b` },
// DecimalBigIntegerLiteral
{ begin: `\\b(0|[1-9](_?[0-9])*)n\\b` },
// NonDecimalIntegerLiteral
{ begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b" },
{ begin: "\\b0[bB][0-1](_?[0-1])*n?\\b" },
{ begin: "\\b0[oO][0-7](_?[0-7])*n?\\b" },
// LegacyOctalIntegerLiteral (does not include underscore separators)
// https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
{ begin: "\\b0[0-7]+n?\\b" }
],
relevance: 0
};
const SUBST = {
className: "subst",
begin: "\\$\\{",
end: "\\}",
keywords: KEYWORDS$1$1,
contains: []
// defined later
};
const HTML_TEMPLATE = {
begin: ".?html`",
end: "",
starts: {
end: "`",
returnEnd: false,
contains: [
hljs.BACKSLASH_ESCAPE,
SUBST
],
subLanguage: "xml"
}
};
const CSS_TEMPLATE = {
begin: ".?css`",
end: "",
starts: {
end: "`",
returnEnd: false,
contains: [
hljs.BACKSLASH_ESCAPE,
SUBST
],
subLanguage: "css"
}
};
const GRAPHQL_TEMPLATE = {
begin: ".?gql`",
end: "",
starts: {
end: "`",
returnEnd: false,
contains: [
hljs.BACKSLASH_ESCAPE,
SUBST
],
subLanguage: "graphql"
}
};
const TEMPLATE_STRING = {
className: "string",
begin: "`",
end: "`",
contains: [
hljs.BACKSLASH_ESCAPE,
SUBST
]
};
const JSDOC_COMMENT = hljs.COMMENT(
/\/\*\*(?!\/)/,
"\\*/",
{
relevance: 0,
contains: [
{
begin: "(?=@[A-Za-z]+)",
relevance: 0,
contains: [
{
className: "doctag",
begin: "@[A-Za-z]+"
},
{
className: "type",
begin: "\\{",
end: "\\}",
excludeEnd: true,
excludeBegin: true,
relevance: 0
},
{
className: "variable",
begin: IDENT_RE$1$1 + "(?=\\s*(-)|$)",
endsParent: true,
relevance: 0
},
// eat spaces (not newlines) so we can find
// types or variables
{
begin: /(?=[^\n])\s/,
relevance: 0
}
]
}
]
}
);
const COMMENT = {
className: "comment",
variants: [
JSDOC_COMMENT,
hljs.C_BLOCK_COMMENT_MODE,
hljs.C_LINE_COMMENT_MODE
]
};
const SUBST_INTERNALS = [
hljs.APOS_STRING_MODE,
hljs.QUOTE_STRING_MODE,
HTML_TEMPLATE,
CSS_TEMPLATE,
GRAPHQL_TEMPLATE,
TEMPLATE_STRING,
// Skip numbers when they are part of a variable name
{ match: /\$\d+/ },
NUMBER
// This is intentional:
// See https://github.com/highlightjs/highlight.js/issues/3288
// hljs.REGEXP_MODE
];
SUBST.contains = SUBST_INTERNALS.concat({
// we need to pair up {} inside our subst to prevent
// it from ending too early by matching another }
begin: /\{/,
end: /\}/,
keywords: KEYWORDS$1$1,
contains: [
"self"
].concat(SUBST_INTERNALS)
});
const SUBST_AND_COMMENTS = [].concat(COMMENT, SUBST.contains);
const PARAMS_CONTAINS = SUBST_AND_COMMENTS.concat([
// eat recursive parens in sub expressions
{
begin: /(\s*)\(/,
end: /\)/,
keywords: KEYWORDS$1$1,
contains: ["self"].concat(SUBST_AND_COMMENTS)
}
]);
const PARAMS = {
className: "params",
// convert this to negative lookbehind in v12
begin: /(\s*)\(/,
// to match the parms with
end: /\)/,
excludeBegin: true,
excludeEnd: true,
keywords: KEYWORDS$1$1,
contains: PARAMS_CONTAINS
};
const CLASS_OR_EXTENDS = {
variants: [
// class Car extends vehicle
{
match: [
/class/,
/\s+/,
IDENT_RE$1$1,
/\s+/,
/extends/,
/\s+/,
regex.concat(IDENT_RE$1$1, "(", regex.concat(/\./, IDENT_RE$1$1), ")*")
],
scope: {
1: "keyword",
3: "title.class",
5: "keyword",
7: "title.class.inherited"
}
},
// class Car
{
match: [
/class/,
/\s+/,
IDENT_RE$1$1
],
scope: {
1: "keyword",
3: "title.class"
}
}
]
};
const CLASS_REFERENCE = {
relevance: 0,
match: regex.either(
// Hard coded exceptions
/\bJSON/,
// Float32Array, OutT
/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,
// CSSFactory, CSSFactoryT
/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,
// FPs, FPsT
/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/
// P
// single letters are not highlighted
// BLAH
// this will be flagged as a UPPER_CASE_CONSTANT instead
),
className: "title.class",
keywords: {
_: [
// se we still get relevance credit for JS library classes
...TYPES$1,
...ERROR_TYPES$1
]
}
};
const USE_STRICT = {
label: "use_strict",
className: "meta",
relevance: 10,
begin: /^\s*['"]use (strict|asm)['"]/
};
const FUNCTION_DEFINITION = {
variants: [
{
match: [
/function/,
/\s+/,
IDENT_RE$1$1,
/(?=\s*\()/
]
},
// anonymous function
{
match: [
/function/,
/\s*(?=\()/
]
}
],
className: {
1: "keyword",
3: "title.function"
},
label: "func.def",
contains: [PARAMS],
illegal: /%/
};
const UPPER_CASE_CONSTANT = {
relevance: 0,
match: /\b[A-Z][A-Z_0-9]+\b/,
className: "variable.constant"
};
function noneOf(list) {
return regex.concat("(?!", list.join("|"), ")");
}
const FUNCTION_CALL = {
match: regex.concat(
/\b/,
noneOf([
...BUILT_IN_GLOBALS$1,
"super",
"import"
].map((x) => `${x}\\s*\\(`)),
IDENT_RE$1$1,
regex.lookahead(/\s*\(/)
),
className: "title.function",
relevance: 0
};
const PROPERTY_ACCESS = {
begin: regex.concat(/\./, regex.lookahead(
regex.concat(IDENT_RE$1$1, /(?![0-9A-Za-z$_(])/)
)),
end: IDENT_RE$1$1,
excludeBegin: true,
keywords: "prototype",
className: "property",
relevance: 0
};
const GETTER_OR_SETTER = {
match: [
/get|set/,
/\s+/,
IDENT_RE$1$1,
/(?=\()/
],
className: {
1: "keyword",
3: "title.function"
},
contains: [
{
// eat to avoid empty params
begin: /\(\)/
},
PARAMS
]
};
const FUNC_LEAD_IN_RE = "(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|" + hljs.UNDERSCORE_IDENT_RE + ")\\s*=>";
const FUNCTION_VARIABLE = {
match: [
/const|var|let/,
/\s+/,
IDENT_RE$1$1,
/\s*/,
/=\s*/,
/(async\s*)?/,
// async is optional
regex.lookahead(FUNC_LEAD_IN_RE)
],
keywords: "async",
className: {
1: "keyword",
3: "title.function"
},
contains: [
PARAMS
]
};
return {
name: "JavaScript",
aliases: ["js", "jsx", "mjs", "cjs"],
keywords: KEYWORDS$1$1,
// this will be extended by TypeScript
exports: { PARAMS_CONTAINS, CLASS_REFERENCE },
illegal: /#(?![$_A-z])/,
contains: [
hljs.SHEBANG({
label: "shebang",
binary: "node",
relevance: 5
}),
USE_STRICT,
hljs.APOS_STRING_MODE,
hljs.QUOTE_STRING_MODE,
HTML_TEMPLATE,
CSS_TEMPLATE,
GRAPHQL_TEMPLATE,
TEMPLATE_STRING,
COMMENT,
// Skip numbers when they are part of a variable name
{ match: /\$\d+/ },
NUMBER,
CLASS_REFERENCE,
{
scope: "attr",
match: IDENT_RE$1$1 + regex.lookahead(":"),
relevance: 0
},
FUNCTION_VARIABLE,
{
// "value" container
begin: "(" + hljs.RE_STARTERS_RE + "|\\b(case|return|throw)\\b)\\s*",
keywords: "return throw case",
relevance: 0,
contains: [
COMMENT,
hljs.REGEXP_MODE,
{
className: "function",
// we have to count the parens to make sure we actually have the
// correct bounding ( ) before the =>. There could be any number of
// sub-expressions inside also surrounded by parens.
begin: FUNC_LEAD_IN_RE,
returnBegin: true,
end: "\\s*=>",
contains: [
{
className: "params",
variants: [
{
begin: hljs.UNDERSCORE_IDENT_RE,
relevance: 0
},
{
className: null,
begin: /\(\s*\)/,
skip: true
},
{
begin: /(\s*)\(/,
end: /\)/,
excludeBegin: true,
excludeEnd: true,
keywords: KEYWORDS$1$1,
contains: PARAMS_CONTAINS
}
]
}
]
},
{
// could be a comma delimited list of params to a function call
begin: /,/,
relevance: 0
},
{
match: /\s+/,
relevance: 0
},
{
// JSX
variants: [
{ begin: FRAGMENT.begin, end: FRAGMENT.end },
{ match: XML_SELF_CLOSING },
{
begin: XML_TAG.begin,
// we carefully check the opening tag to see if it truly
// is a tag and not a false positive
"on:begin": XML_TAG.isTrulyOpeningTag,
end: XML_TAG.end
}
],
subLanguage: "xml",
contains: [
{
begin: XML_TAG.begin,
end: XML_TAG.end,
skip: true,
contains: ["self"]
}
]
}
]
},
FUNCTION_DEFINITION,
{
// prevent this from getting swallowed up by function
// since they appear "function like"
beginKeywords: "while if switch catch for"
},
{
// we have to count the parens to make sure we actually have the correct
// bounding ( ). There could be any number of sub-expressions inside
// also surrounded by parens.
begin: "\\b(?!function)" + hljs.UNDERSCORE_IDENT_RE + "\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",
// end parens
returnBegin: true,
label: "func.def",
contains: [
PARAMS,
hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1$1, className: "title.function" })
]
},
// catch ... so it won't trigger the property rule below
{
match: /\.\.\./,
relevance: 0
},
PROPERTY_ACCESS,
// hack: prevents detection of keywords in some circumstances
// .keyword()
// $keyword = x
{
match: "\\$" + IDENT_RE$1$1,
relevance: 0
},
{
match: [/\bconstructor(?=\s*\()/],
className: { 1: "title.function" },
contains: [PARAMS]
},
FUNCTION_CALL,
UPPER_CASE_CONSTANT,
CLASS_OR_EXTENDS,
GETTER_OR_SETTER,
{
match: /\$[(.]/
// relevance booster for a pattern common to JS libs: `$(something)` and `$.something`
}
]
};
}
function python(hljs) {
const regex = hljs.regex;
const IDENT_RE2 = new RegExp("[\\p{XID_Start}_]\\p{XID_Continue}*", "u");
const RESERVED_WORDS = [
"and",
"as",
"assert",
"async",
"await",
"break",
"case",
"class",
"continue",
"def",
"del",
"elif",
"else",
"except",
"finally",
"for",
"from",
"global",
"if",
"import",
"in",
"is",
"lambda",
"match",
"nonlocal|10",
"not",
"or",
"pass",
"raise",
"return",
"try",
"while",
"with",
"yield"
];
const BUILT_INS2 = [
"__import__",
"abs",
"all",
"any",
"ascii",
"bin",
"bool",
"breakpoint",
"bytearray",
"bytes",
"callable",
"chr",
"classmethod",
"compile",
"complex",
"delattr",
"dict",
"dir",
"divmod",
"enumerate",
"eval",
"exec",
"filter",
"float",
"format",
"frozenset",
"getattr",
"globals",
"hasattr",
"hash",
"help",
"hex",
"id",
"input",
"int",
"isinstance",
"issubclass",
"iter",
"len",
"list",
"locals",
"map",
"max",
"memoryview",
"min",
"next",
"object",
"oct",
"open",
"ord",
"pow",
"print",
"property",
"range",
"repr",
"reversed",
"round",
"set",
"setattr",
"slice",
"sorted",
"staticmethod",
"str",
"sum",
"super",
"tuple",
"type",
"vars",
"zip"
];
const LITERALS2 = [
"__debug__",
"Ellipsis",
"False",
"None",
"NotImplemented",
"True"
];
const TYPES2 = [
"Any",
"Callable",
"Coroutine",
"Dict",
"List",
"Literal",
"Generic",
"Optional",
"Sequence",
"Set",
"Tuple",
"Type",
"Union"
];
const KEYWORDS2 = {
$pattern: /[A-Za-z]\w+|__\w+__/,
keyword: RESERVED_WORDS,
built_in: BUILT_INS2,
literal: LITERALS2,
type: TYPES2
};
const PROMPT = {
className: "meta",
begin: /^(>>>|\.\.\.) /
};
const SUBST = {
className: "subst",
begin: /\{/,
end: /\}/,
keywords: KEYWORDS2,
illegal: /#/
};
const LITERAL_BRACKET = {
begin: /\{\{/,
relevance: 0
};
const STRING = {
className: "string",
contains: [hljs.BACKSLASH_ESCAPE],
variants: [
{
begin: /([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,
end: /'''/,
contains: [
hljs.BACKSLASH_ESCAPE,
PROMPT
],
relevance: 10
},
{
begin: /([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,
end: /"""/,
contains: [
hljs.BACKSLASH_ESCAPE,
PROMPT
],
relevance: 10
},
{
begin: /([fF][rR]|[rR][fF]|[fF])'''/,
end: /'''/,
contains: [
hljs.BACKSLASH_ESCAPE,
PROMPT,
LITERAL_BRACKET,
SUBST
]
},
{
begin: /([fF][rR]|[rR][fF]|[fF])"""/,
end: /"""/,
contains: [
hljs.BACKSLASH_ESCAPE,
PROMPT,
LITERAL_BRACKET,
SUBST
]
},
{
begin: /([uU]|[rR])'/,
end: /'/,
relevance: 10
},
{
begin: /([uU]|[rR])"/,
end: /"/,
relevance: 10
},
{
begin: /([bB]|[bB][rR]|[rR][bB])'/,
end: /'/
},
{
begin: /([bB]|[bB][rR]|[rR][bB])"/,
end: /"/
},
{
begin: /([fF][rR]|[rR][fF]|[fF])'/,
end: /'/,
contains: [
hljs.BACKSLASH_ESCAPE,
LITERAL_BRACKET,
SUBST
]
},
{
begin: /([fF][rR]|[rR][fF]|[fF])"/,
end: /"/,
contains: [
hljs.BACKSLASH_ESCAPE,
LITERAL_BRACKET,
SUBST
]
},
hljs.APOS_STRING_MODE,
hljs.QUOTE_STRING_MODE
]
};
const digitpart = "[0-9](_?[0-9])*";
const pointfloat = `(\\b(${digitpart}))?\\.(${digitpart})|\\b(${digitpart})\\.`;
const lookahead = `\\b|${RESERVED_WORDS.join("|")}`;
const NUMBER = {
className: "number",
relevance: 0,
variants: [
// exponentfloat, pointfloat
// https://docs.python.org/3.9/reference/lexical_analysis.html#floating-point-literals
// optionally imaginary
// https://docs.python.org/3.9/reference/lexical_analysis.html#imaginary-literals
// Note: no leading \b because floats can start with a decimal point
// and we don't want to mishandle e.g. `fn(.5)`,
// no trailing \b for pointfloat because it can end with a decimal point
// and we don't want to mishandle e.g. `0..hex()`; this should be safe
// because both MUST contain a decimal point and so cannot be confused with
// the interior part of an identifier
{
begin: `(\\b(${digitpart})|(${pointfloat}))[eE][+-]?(${digitpart})[jJ]?(?=${lookahead})`
},
{
begin: `(${pointfloat})[jJ]?`
},
// decinteger, bininteger, octinteger, hexinteger
// https://docs.python.org/3.9/reference/lexical_analysis.html#integer-literals
// optionally "long" in Python 2
// https://docs.python.org/2.7/reference/lexical_analysis.html#integer-and-long-integer-literals
// decinteger is optionally imaginary
// https://docs.python.org/3.9/reference/lexical_analysis.html#imaginary-literals
{
begin: `\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${lookahead})`
},
{
begin: `\\b0[bB](_?[01])+[lL]?(?=${lookahead})`
},
{
begin: `\\b0[oO](_?[0-7])+[lL]?(?=${lookahead})`
},
{
begin: `\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${lookahead})`
},
// imagnumber (digitpart-based)
// https://docs.python.org/3.9/reference/lexical_analysis.html#imaginary-literals
{
begin: `\\b(${digitpart})[jJ](?=${lookahead})`
}
]
};
const COMMENT_TYPE = {
className: "comment",
begin: regex.lookahead(/# type:/),
end: /$/,
keywords: KEYWORDS2,
contains: [
{
// prevent keywords from coloring `type`
begin: /# type:/
},
// comment within a datatype comment includes no keywords
{
begin: /#/,
end: /\b\B/,
endsWithParent: true
}
]
};
const PARAMS = {
className: "params",
variants: [
// Exclude params in functions without params
{
className: "",
begin: /\(\s*\)/,
skip: true
},
{
begin: /\(/,
end: /\)/,
excludeBegin: true,
excludeEnd: true,
keywords: KEYWORDS2,
contains: [
"self",
PROMPT,
NUMBER,
STRING,
hljs.HASH_COMMENT_MODE
]
}
]
};
SUBST.contains = [
STRING,
NUMBER,
PROMPT
];
return {
name: "Python",
aliases: [
"py",
"gyp",
"ipython"
],
unicodeRegex: true,
keywords: KEYWORDS2,
illegal: /(<\/|\?)|=>/,
contains: [
PROMPT,
NUMBER,
{
// very common convention
scope: "variable.language",
match: /\bself\b/
},
{
// eat "if" prior to string so that it won't accidentally be
// labeled as an f-string
beginKeywords: "if",
relevance: 0
},
{ match: /\bor\b/, scope: "keyword" },
STRING,
COMMENT_TYPE,
hljs.HASH_COMMENT_MODE,
{
match: [
/\bdef/,
/\s+/,
IDENT_RE2
],
scope: {
1: "keyword",
3: "title.function"
},
contains: [PARAMS]
},
{
variants: [
{
match: [
/\bclass/,
/\s+/,
IDENT_RE2,
/\s*/,
/\(\s*/,
IDENT_RE2,
/\s*\)/
]
},
{
match: [
/\bclass/,
/\s+/,
IDENT_RE2
]
}
],
scope: {
1: "keyword",
3: "title.class",
6: "title.class.inherited"
}
},
{
className: "meta",
begin: /^[\t ]*@/,
end: /(?=#)|$/,
contains: [
NUMBER,
PARAMS,
STRING
]
}
]
};
}
const IDENT_RE = "[A-Za-z$_][0-9A-Za-z$_]*";
const KEYWORDS = [
"as",
// for exports
"in",
"of",
"if",
"for",
"while",
"finally",
"var",
"new",
"function",
"do",
"return",
"void",
"else",
"break",
"catch",
"instanceof",
"with",
"throw",
"case",
"default",
"try",
"switch",
"continue",
"typeof",
"delete",
"let",
"yield",
"const",
"class",
// JS handles these with a special rule
// "get",
// "set",
"debugger",
"async",
"await",
"static",
"import",
"from",
"export",
"extends",
// It's reached stage 3, which is "recommended for implementation":
"using"
];
const LITERALS = [
"true",
"false",
"null",
"undefined",
"NaN",
"Infinity"
];
const TYPES = [
// Fundamental objects
"Object",
"Function",
"Boolean",
"Symbol",
// numbers and dates
"Math",
"Date",
"Number",
"BigInt",
// text
"String",
"RegExp",
// Indexed collections
"Array",
"Float32Array",
"Float64Array",
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Int32Array",
"Uint16Array",
"Uint32Array",
"BigInt64Array",
"BigUint64Array",
// Keyed collections
"Set",
"Map",
"WeakSet",
"WeakMap",
// Structured data
"ArrayBuffer",
"SharedArrayBuffer",
"Atomics",
"DataView",
"JSON",
// Control abstraction objects
"Promise",
"Generator",
"GeneratorFunction",
"AsyncFunction",
// Reflection
"Reflect",
"Proxy",
// Internationalization
"Intl",
// WebAssembly
"WebAssembly"
];
const ERROR_TYPES = [
"Error",
"EvalError",
"InternalError",
"RangeError",
"ReferenceError",
"SyntaxError",
"TypeError",
"URIError"
];
const BUILT_IN_GLOBALS = [
"setInterval",
"setTimeout",
"clearInterval",
"clearTimeout",
"require",
"exports",
"eval",
"isFinite",
"isNaN",
"parseFloat",
"parseInt",
"decodeURI",
"decodeURIComponent",
"encodeURI",
"encodeURIComponent",
"escape",
"unescape"
];
const BUILT_IN_VARIABLES = [
"arguments",
"this",
"super",
"console",
"window",
"document",
"localStorage",
"sessionStorage",
"module",
"global"
// Node.js
];
const BUILT_INS = [].concat(
BUILT_IN_GLOBALS,
TYPES,
ERROR_TYPES
);
function javascript(hljs) {
const regex = hljs.regex;
const hasClosingTag = (match, { after }) => {
const tag = "</" + match[0].slice(1);
const pos = match.input.indexOf(tag, after);
return pos !== -1;
};
const IDENT_RE$12 = IDENT_RE;
const FRAGMENT = {
begin: "<>",
end: "</>"
};
const XML_SELF_CLOSING = /<[A-Za-z0-9\\._:-]+\s*\/>/;
const XML_TAG = {
begin: /<[A-Za-z0-9\\._:-]+/,
end: /\/[A-Za-z0-9\\._:-]+>|\/>/,
/**
* @param {RegExpMatchArray} match
* @param {CallbackResponse} response
*/
isTrulyOpeningTag: (match, response) => {
const afterMatchIndex = match[0].length + match.index;
const nextChar = match.input[afterMatchIndex];
if (
// HTML should not include another raw `<` inside a tag
// nested type?
// `<Array<Array<number>>`, etc.
nextChar === "<" || // the , gives away that this is not HTML
// `<T, A extends keyof T, V>`
nextChar === ","
) {
response.ignoreMatch();
return;
}
if (nextChar === ">") {
if (!hasClosingTag(match, { after: afterMatchIndex })) {
response.ignoreMatch();
}
}
let m;
const afterMatch = match.input.substring(afterMatchIndex);
if (m = afterMatch.match(/^\s*=/)) {
response.ignoreMatch();
return;
}
if (m = afterMatch.match(/^\s+extends\s+/)) {
if (m.index === 0) {
response.ignoreMatch();
return;
}
}
}
};
const KEYWORDS$12 = {
$pattern: IDENT_RE,
keyword: KEYWORDS,
literal: LITERALS,
built_in: BUILT_INS,
"variable.language": BUILT_IN_VARIABLES
};
const decimalDigits = "[0-9](_?[0-9])*";
const frac = `\\.(${decimalDigits})`;
const decimalInteger = `0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`;
const NUMBER = {
className: "number",
variants: [
// DecimalLiteral
{ begin: `(\\b(${decimalInteger})((${frac})|\\.)?|(${frac}))[eE][+-]?(${decimalDigits})\\b` },
{ begin: `\\b(${decimalInteger})\\b((${frac})\\b|\\.)?|(${frac})\\b` },
// DecimalBigIntegerLiteral
{ begin: `\\b(0|[1-9](_?[0-9])*)n\\b` },
// NonDecimalIntegerLiteral
{ begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b" },
{ begin: "\\b0[bB][0-1](_?[0-1])*n?\\b" },
{ begin: "\\b0[oO][0-7](_?[0-7])*n?\\b" },
// LegacyOctalIntegerLiteral (does not include underscore separators)
// https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
{ begin: "\\b0[0-7]+n?\\b" }
],
relevance: 0
};
const SUBST = {
className: "subst",
begin: "\\$\\{",
end: "\\}",
keywords: KEYWORDS$12,
contains: []
// defined later
};
const HTML_TEMPLATE = {
begin: ".?html`",
end: "",
starts: {
end: "`",
returnEnd: false,
contains: [
hljs.BACKSLASH_ESCAPE,
SUBST
],
subLanguage: "xml"
}
};
const CSS_TEMPLATE = {
begin: ".?css`",
end: "",
starts: {
end: "`",
returnEnd: false,
contains: [
hljs.BACKSLASH_ESCAPE,
SUBST
],
subLanguage: "css"
}
};
const GRAPHQL_TEMPLATE = {
begin: ".?gql`",
end: "",
starts: {
end: "`",
returnEnd: false,
contains: [
hljs.BACKSLASH_ESCAPE,
SUBST
],
subLanguage: "graphql"
}
};
const TEMPLATE_STRING = {
className: "string",
begin: "`",
end: "`",
contains: [
hljs.BACKSLASH_ESCAPE,
SUBST
]
};
const JSDOC_COMMENT = hljs.COMMENT(
/\/\*\*(?!\/)/,
"\\*/",
{
relevance: 0,
contains: [
{
begin: "(?=@[A-Za-z]+)",
relevance: 0,
contains: [
{
className: "doctag",
begin: "@[A-Za-z]+"
},
{
className: "type",
begin: "\\{",
end: "\\}",
excludeEnd: true,
excludeBegin: true,
relevance: 0
},
{
className: "variable",
begin: IDENT_RE$12 + "(?=\\s*(-)|$)",
endsParent: true,
relevance: 0
},
// eat spaces (not newlines) so we can find
// types or variables
{
begin: /(?=[^\n])\s/,
relevance: 0
}
]
}
]
}
);
const COMMENT = {
className: "comment",
variants: [
JSDOC_COMMENT,
hljs.C_BLOCK_COMMENT_MODE,
hljs.C_LINE_COMMENT_MODE
]
};
const SUBST_INTERNALS = [
hljs.APOS_STRING_MODE,
hljs.QUOTE_STRING_MODE,
HTML_TEMPLATE,
CSS_TEMPLATE,
GRAPHQL_TEMPLATE,
TEMPLATE_STRING,
// Skip numbers when they are part of a variable name
{ match: /\$\d+/ },
NUMBER
// This is intentional:
// See https://github.com/highlightjs/highlight.js/issues/3288
// hljs.REGEXP_MODE
];
SUBST.contains = SUBST_INTERNALS.concat({
// we need to pair up {} inside our subst to prevent
// it from ending too early by matching another }
begin: /\{/,
end: /\}/,
keywords: KEYWORDS$12,
contains: [
"self"
].concat(SUBST_INTERNALS)
});
const SUBST_AND_COMMENTS = [].concat(COMMENT, SUBST.contains);
const PARAMS_CONTAINS = SUBST_AND_COMMENTS.concat([
// eat recursive parens in sub expressions
{
begin: /(\s*)\(/,
end: /\)/,
keywords: KEYWORDS$12,
contains: ["self"].concat(SUBST_AND_COMMENTS)
}
]);
const PARAMS = {
className: "params",
// convert this to negative lookbehind in v12
begin: /(\s*)\(/,
// to match the parms with
end: /\)/,
excludeBegin: true,
excludeEnd: true,
keywords: KEYWORDS$12,
contains: PARAMS_CONTAINS
};
const CLASS_OR_EXTENDS = {
variants: [
// class Car extends vehicle
{
match: [
/class/,
/\s+/,
IDENT_RE$12,
/\s+/,
/extends/,
/\s+/,
regex.concat(IDENT_RE$12, "(", regex.concat(/\./, IDENT_RE$12), ")*")
],
scope: {
1: "keyword",
3: "title.class",
5: "keyword",
7: "title.class.inherited"
}
},
// class Car
{
match: [
/class/,
/\s+/,
IDENT_RE$12
],
scope: {
1: "keyword",
3: "title.class"
}
}
]
};
const CLASS_REFERENCE = {
relevance: 0,
match: regex.either(
// Hard coded exceptions
/\bJSON/,
// Float32Array, OutT
/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,
// CSSFactory, CSSFactoryT
/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,
// FPs, FPsT
/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/
// P
// single letters are not highlighted
// BLAH
// this will be flagged as a UPPER_CASE_CONSTANT instead
),
className: "title.class",
keywords: {
_: [
// se we still get relevance credit for JS library classes
...TYPES,
...ERROR_TYPES
]
}
};
const USE_STRICT = {
label: "use_strict",
className: "meta",
relevance: 10,
begin: /^\s*['"]use (strict|asm)['"]/
};
const FUNCTION_DEFINITION = {
variants: [
{
match: [
/function/,
/\s+/,
IDENT_RE$12,
/(?=\s*\()/
]
},
// anonymous function
{
match: [
/function/,
/\s*(?=\()/
]
}
],
className: {
1: "keyword",
3: "title.function"
},
label: "func.def",
contains: [PARAMS],
illegal: /%/
};
const UPPER_CASE_CONSTANT = {
relevance: 0,
match: /\b[A-Z][A-Z_0-9]+\b/,
className: "variable.constant"
};
function noneOf(list) {
return regex.concat("(?!", list.join("|"), ")");
}
const FUNCTION_CALL = {
match: regex.concat(
/\b/,
noneOf([
...BUILT_IN_GLOBALS,
"super",
"import"
].map((x) => `${x}\\s*\\(`)),
IDENT_RE$12,
regex.lookahead(/\s*\(/)
),
className: "title.function",
relevance: 0
};
const PROPERTY_ACCESS = {
begin: regex.concat(/\./, regex.lookahead(
regex.concat(IDENT_RE$12, /(?![0-9A-Za-z$_(])/)
)),
end: IDENT_RE$12,
excludeBegin: true,
keywords: "prototype",
className: "property",
relevance: 0
};
const GETTER_OR_SETTER = {
match: [
/get|set/,
/\s+/,
IDENT_RE$12,
/(?=\()/
],
className: {
1: "keyword",
3: "title.function"
},
contains: [
{
// eat to avoid empty params
begin: /\(\)/
},
PARAMS
]
};
const FUNC_LEAD_IN_RE = "(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|" + hljs.UNDERSCORE_IDENT_RE + ")\\s*=>";
const FUNCTION_VARIABLE = {
match: [
/const|var|let/,
/\s+/,
IDENT_RE$12,
/\s*/,
/=\s*/,
/(async\s*)?/,
// async is optional
regex.lookahead(FUNC_LEAD_IN_RE)
],
keywords: "async",
className: {
1: "keyword",
3: "title.function"
},
contains: [
PARAMS
]
};
return {
name: "JavaScript",
aliases: ["js", "jsx", "mjs", "cjs"],
keywords: KEYWORDS$12,
// this will be extended by TypeScript
exports: { PARAMS_CONTAINS, CLASS_REFERENCE },
illegal: /#(?![$_A-z])/,
contains: [
hljs.SHEBANG({
label: "shebang",
binary: "node",
relevance: 5
}),
USE_STRICT,
hljs.APOS_STRING_MODE,
hljs.QUOTE_STRING_MODE,
HTML_TEMPLATE,
CSS_TEMPLATE,
GRAPHQL_TEMPLATE,
TEMPLATE_STRING,
COMMENT,
// Skip numbers when they are part of a variable name
{ match: /\$\d+/ },
NUMBER,
CLASS_REFERENCE,
{
scope: "attr",
match: IDENT_RE$12 + regex.lookahead(":"),
relevance: 0
},
FUNCTION_VARIABLE,
{
// "value" container
begin: "(" + hljs.RE_STARTERS_RE + "|\\b(case|return|throw)\\b)\\s*",
keywords: "return throw case",
relevance: 0,
contains: [
COMMENT,
hljs.REGEXP_MODE,
{
className: "function",
// we have to count the parens to make sure we actually have the
// correct bounding ( ) before the =>. There could be any number of
// sub-expressions inside also surrounded by parens.
begin: FUNC_LEAD_IN_RE,
returnBegin: true,
end: "\\s*=>",
contains: [
{
className: "params",
variants: [
{
begin: hljs.UNDERSCORE_IDENT_RE,
relevance: 0
},
{
className: null,
begin: /\(\s*\)/,
skip: true
},
{
begin: /(\s*)\(/,
end: /\)/,
excludeBegin: true,
excludeEnd: true,
keywords: KEYWORDS$12,
contains: PARAMS_CONTAINS
}
]
}
]
},
{
// could be a comma delimited list of params to a function call
begin: /,/,
relevance: 0
},
{
match: /\s+/,
relevance: 0
},
{
// JSX
variants: [
{ begin: FRAGMENT.begin, end: FRAGMENT.end },
{ match: XML_SELF_CLOSING },
{
begin: XML_TAG.begin,
// we carefully check the opening tag to see if it truly
// is a tag and not a false positive
"on:begin": XML_TAG.isTrulyOpeningTag,
end: XML_TAG.end
}
],
subLanguage: "xml",
contains: [
{
begin: XML_TAG.begin,
end: XML_TAG.end,
skip: true,
contains: ["self"]
}
]
}
]
},
FUNCTION_DEFINITION,
{
// prevent this from getting swallowed up by function
// since they appear "function like"
beginKeywords: "while if switch catch for"
},
{
// we have to count the parens to make sure we actually have the correct
// bounding ( ). There could be any number of sub-expressions inside
// also surrounded by parens.
begin: "\\b(?!function)" + hljs.UNDERSCORE_IDENT_RE + "\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",
// end parens
returnBegin: true,
label: "func.def",
contains: [
PARAMS,
hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$12, className: "title.function" })
]
},
// catch ... so it won't trigger the property rule below
{
match: /\.\.\./,
relevance: 0
},
PROPERTY_ACCESS,
// hack: prevents detection of keywords in some circumstances
// .keyword()
// $keyword = x
{
match: "\\$" + IDENT_RE$12,
relevance: 0
},
{
match: [/\bconstructor(?=\s*\()/],
className: { 1: "title.function" },
contains: [PARAMS]
},
FUNCTION_CALL,
UPPER_CASE_CONSTANT,
CLASS_OR_EXTENDS,
GETTER_OR_SETTER,
{
match: /\$[(.]/
// relevance booster for a pattern common to JS libs: `$(something)` and `$.something`
}
]
};
}
function typescript(hljs) {
const regex = hljs.regex;
const tsLanguage = javascript(hljs);
const IDENT_RE$12 = IDENT_RE;
const TYPES2 = [
"any",
"void",
"number",
"boolean",
"string",
"object",
"never",
"symbol",
"bigint",
"unknown"
];
const NAMESPACE = {
begin: [
/namespace/,
/\s+/,
hljs.IDENT_RE
],
beginScope: {
1: "keyword",
3: "title.class"
}
};
const INTERFACE = {
beginKeywords: "interface",
end: /\{/,
excludeEnd: true,
keywords: {
keyword: "interface extends",
built_in: TYPES2
},
contains: [tsLanguage.exports.CLASS_REFERENCE]
};
const USE_STRICT = {
className: "meta",
relevance: 10,
begin: /^\s*['"]use strict['"]/
};
const TS_SPECIFIC_KEYWORDS = [
"type",
// "namespace",
"interface",
"public",
"private",
"protected",
"implements",
"declare",
"abstract",
"readonly",
"enum",
"override",
"satisfies"
];
const KEYWORDS$12 = {
$pattern: IDENT_RE,
keyword: KEYWORDS.concat(TS_SPECIFIC_KEYWORDS),
literal: LITERALS,
built_in: BUILT_INS.concat(TYPES2),
"variable.language": BUILT_IN_VARIABLES
};
const DECORATOR = {
className: "meta",
begin: "@" + IDENT_RE$12
};
const swapMode = (mode, label, replacement) => {
const indx = mode.contains.findIndex((m) => m.label === label);
if (indx === -1) {
throw new Error("can not find mode to replace");
}
mode.contains.splice(indx, 1, replacement);
};
Object.assign(tsLanguage.keywords, KEYWORDS$12);
tsLanguage.exports.PARAMS_CONTAINS.push(DECORATOR);
const ATTRIBUTE_HIGHLIGHT = tsLanguage.contains.find((c) => c.scope === "attr");
const OPTIONAL_KEY_OR_ARGUMENT = Object.assign(
{},
ATTRIBUTE_HIGHLIGHT,
{ match: regex.concat(IDENT_RE$12, regex.lookahead(/\s*\?:/)) }
);
tsLanguage.exports.PARAMS_CONTAINS.push([
tsLanguage.exports.CLASS_REFERENCE,
// class reference for highlighting the params types
ATTRIBUTE_HIGHLIGHT,
// highlight the params key
OPTIONAL_KEY_OR_ARGUMENT
// Added for optional property assignment highlighting
]);
tsLanguage.contains = tsLanguage.contains.concat([
DECORATOR,
NAMESPACE,
INTERFACE,
OPTIONAL_KEY_OR_ARGUMENT
// Added for optional property assignment highlighting
]);
swapMode(tsLanguage, "shebang", hljs.SHEBANG());
swapMode(tsLanguage, "use_strict", USE_STRICT);
const functionDeclaration = tsLanguage.contains.find((m) => m.label === "func.def");
functionDeclaration.relevance = 0;
Object.assign(tsLanguage, {
name: "TypeScript",
aliases: [
"ts",
"tsx",
"mts",
"cts"
]
});
return tsLanguage;
}
function xml(hljs) {
const regex = hljs.regex;
const TAG_NAME_RE = regex.concat(/[\p{L}_]/u, regex.optional(/[\p{L}0-9_.-]*:/u), /[\p{L}0-9_.-]*/u);
const XML_IDENT_RE = /[\p{L}0-9._:-]+/u;
const XML_ENTITIES = {
className: "symbol",
begin: /&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/
};
const XML_META_KEYWORDS = {
begin: /\s/,
contains: [
{
className: "keyword",
begin: /#?[a-z_][a-z1-9_-]+/,
illegal: /\n/
}
]
};
const XML_META_PAR_KEYWORDS = hljs.inherit(XML_META_KEYWORDS, {
begin: /\(/,
end: /\)/
});
const APOS_META_STRING_MODE = hljs.inherit(hljs.APOS_STRING_MODE, { className: "string" });
const QUOTE_META_STRING_MODE = hljs.inherit(hljs.QUOTE_STRING_MODE, { className: "string" });
const TAG_INTERNALS = {
endsWithParent: true,
illegal: /</,
relevance: 0,
contains: [
{
className: "attr",
begin: XML_IDENT_RE,
relevance: 0
},
{
begin: /=\s*/,
relevance: 0,
contains: [
{
className: "string",
endsParent: true,
variants: [
{
begin: /"/,
end: /"/,
contains: [XML_ENTITIES]
},
{
begin: /'/,
end: /'/,
contains: [XML_ENTITIES]
},
{ begin: /[^\s"'=<>`]+/ }
]
}
]
}
]
};
return {
name: "HTML, XML",
aliases: [
"html",
"xhtml",
"rss",
"atom",
"xjb",
"xsd",
"xsl",
"plist",
"wsf",
"svg"
],
case_insensitive: true,
unicodeRegex: true,
contains: [
{
className: "meta",
begin: /<![a-z]/,
end: />/,
relevance: 10,
contains: [
XML_META_KEYWORDS,
QUOTE_META_STRING_MODE,
APOS_META_STRING_MODE,
XML_META_PAR_KEYWORDS,
{
begin: /\[/,
end: /\]/,
contains: [
{
className: "meta",
begin: /<![a-z]/,
end: />/,
contains: [
XML_META_KEYWORDS,
XML_META_PAR_KEYWORDS,
QUOTE_META_STRING_MODE,
APOS_META_STRING_MODE
]
}
]
}
]
},
hljs.COMMENT(
/<!--/,
/-->/,
{ relevance: 10 }
),
{
begin: /<!\[CDATA\[/,
end: /\]\]>/,
relevance: 10
},
XML_ENTITIES,
// xml processing instructions
{
className: "meta",
end: /\?>/,
variants: [
{
begin: /<\?xml/,
relevance: 10,
contains: [
QUOTE_META_STRING_MODE
]
},
{
begin: /<\?[a-z][a-z0-9]+/
}
]
},
{
className: "tag",
/*
The lookahead pattern (?=...) ensures that 'begin' only matches
'<style' as a single word, followed by a whitespace or an
ending bracket.
*/
begin: /<style(?=\s|>)/,
end: />/,
keywords: { name: "style" },
contains: [TAG_INTERNALS],
starts: {
end: /<\/style>/,
returnEnd: true,
subLanguage: [
"css",
"xml"
]
}
},
{
className: "tag",
// See the comment in the <style tag about the lookahead pattern
begin: /<script(?=\s|>)/,
end: />/,
keywords: { name: "script" },
contains: [TAG_INTERNALS],
starts: {
end: /<\/script>/,
returnEnd: true,
subLanguage: [
"javascript",
"handlebars",
"xml"
]
}
},
// we need this for now for jSX
{
className: "tag",
begin: /<>|<\/>/
},
// open tag
{
className: "tag",
begin: regex.concat(
/</,
regex.lookahead(regex.concat(
TAG_NAME_RE,
// <tag/>
// <tag>
// <tag ...
regex.either(/\/>/, />/, /\s/)
))
),
end: /\/?>/,
contains: [
{
className: "name",
begin: TAG_NAME_RE,
relevance: 0,
starts: TAG_INTERNALS
}
]
},
// close tag
{
className: "tag",
begin: regex.concat(
/<\//,
regex.lookahead(regex.concat(
TAG_NAME_RE,
/>/
))
),
contains: [
{
className: "name",
begin: TAG_NAME_RE,
relevance: 0
},
{
begin: />/,
relevance: 0,
endsParent: true
}
]
}
]
};
}
function useChat() {
return inject(ChatSymbol$1);
}
function useOptions() {
const options = inject(ChatOptionsSymbol);
return {
options
};
}
function useI18n() {
const { options } = useOptions();
const language = options?.defaultLanguage ?? "en";
function t(key) {
const val = options?.i18n?.[language]?.[key];
if (isRef(val)) {
return val.value;
}
return val ?? key;
}
function te(key) {
return !!options?.i18n?.[language]?.[key];
}
return { t, te };
}
const _hoisted_1$c = {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em"
};
function render$7(_ctx, _cache) {
return openBlock(), createElementBlock("svg", _hoisted_1$c, _cache[0] || (_cache[0] = [
createBaseVNode("path", {
fill: "currentColor",
d: "M20 6.91L17.09 4L12 9.09L6.91 4L4 6.91L9.09 12L4 17.09L6.91 20L12 14.91L17.09 20L20 17.09L14.91 12z"
}, null, -1)
]));
}
const IconDelete = { name: "mdi-closeThick", render: render$7 };
const _hoisted_1$b = {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em"
};
function render$6(_ctx, _cache) {
return openBlock(), createElementBlock("svg", _hoisted_1$b, _cache[0] || (_cache[0] = [
createBaseVNode("path", {
fill: "currentColor",
d: "M13 9h5.5L13 3.5zM6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m0 18h12v-8l-4 4l-2-2zM8 9a2 2 0 0 0-2 2a2 2 0 0 0 2 2a2 2 0 0 0 2-2a2 2 0 0 0-2-2"
}, null, -1)
]));
}
const IconFileImage = { name: "mdi-fileImage", render: render$6 };
const _hoisted_1$a = {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em"
};
function render$5(_ctx, _cache) {
return openBlock(), createElementBlock("svg", _hoisted_1$a, _cache[0] || (_cache[0] = [
createBaseVNode("path", {
fill: "currentColor",
d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zm-1 11h-2v5a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2c.4 0 .7.1 1 .3V11h3zm0-4V3.5L18.5 9z"
}, null, -1)
]));
}
const IconFileMusic = { name: "mdi-fileMusic", render: render$5 };
const _hoisted_1$9 = {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em"
};
function render$4(_ctx, _cache) {
return openBlock(), createElementBlock("svg", _hoisted_1$9, _cache[0] || (_cache[0] = [
createBaseVNode("path", {
fill: "currentColor",
d: "M13 9h5.5L13 3.5zM6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m9 16v-2H6v2zm3-4v-2H6v2z"
}, null, -1)
]));
}
const IconFileText = { name: "mdi-fileText", render: render$4 };
const _hoisted_1$8 = {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em"
};
function render$3(_ctx, _cache) {
return openBlock(), createElementBlock("svg", _hoisted_1$8, _cache[0] || (_cache[0] = [
createBaseVNode("path", {
fill: "currentColor",
d: "M13 9h5.5L13 3.5zM6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m11 17v-6l-3 2.2V13H7v6h7v-2.2z"
}, null, -1)
]));
}
const IconFileVideo = { name: "mdi-fileVideo", render: render$3 };
const _hoisted_1$7 = {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em"
};
function render$2(_ctx, _cache) {
return openBlock(), createElementBlock("svg", _hoisted_1$7, _cache[0] || (_cache[0] = [
createBaseVNode("path", {
fill: "currentColor",
d: "M14 3v2h3.59l-9.83 9.83l1.41 1.41L19 6.41V10h2V3m-2 16H5V5h7V3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7h-2z"
}, null, -1)
]));
}
const IconPreview = { name: "mdi-openInNew", render: render$2 };
const _hoisted_1$6 = { class: "chat-file-name" };
const _sfc_main$c = /* @__PURE__ */ defineComponent({
__name: "ChatFile",
props: {
file: {},
isRemovable: { type: Boolean },
isPreviewable: { type: Boolean }
},
emits: ["remove"],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const iconMapper = {
document: IconFileText,
audio: IconFileMusic,
image: IconFileImage,
video: IconFileVideo
};
const TypeIcon = computed(() => {
const type = props.file?.type.split("/")[0];
return iconMapper[type] || IconFileText;
});
function onClick() {
if (props.isPreviewable) {
window.open(URL.createObjectURL(props.file));
}
}
function onDelete() {
emit("remove", props.file);
}
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
class: "chat-file",
onClick
}, [
createVNode(unref(TypeIcon)),
createBaseVNode("p", _hoisted_1$6, toDisplayString(_ctx.file.name), 1),
_ctx.isRemovable ? (openBlock(), createElementBlock("span", {
key: 0,
class: "chat-file-delete",
onClick: withModifiers(onDelete, ["stop"])
}, [
createVNode(unref(IconDelete))
])) : _ctx.isPreviewable ? (openBlock(), createBlock(unref(IconPreview), {
key: 1,
class: "chat-file-preview"
})) : createCommentVNode("", true)
]);
};
}
});
const ChatFile = /* @__PURE__ */ _export_sfc(_sfc_main$c, [["__scopeId", "data-v-70b9370d"]]);
const _hoisted_1$5 = {
key: 0,
class: "chat-message-actions"
};
const _hoisted_2$2 = {
key: 2,
class: "chat-message-files"
};
const _sfc_main$b = /* @__PURE__ */ defineComponent({
__name: "Message",
props: {
message: {}
},
setup(__props, { expose: __expose }) {
const props = __props;
HighlightJS.registerLanguage("javascript", javascript$1);
HighlightJS.registerLanguage("typescript", typescript);
HighlightJS.registerLanguage("python", python);
HighlightJS.registerLanguage("xml", xml);
HighlightJS.registerLanguage("bash", bash);
const { message } = toRefs(props);
const { options } = useOptions();
const messageContainer = ref(null);
const fileSources = ref({});
const messageText = computed(() => {
return message.value.text || "&lt;Empty response&gt;";
});
const classes = computed(() => {
return {
"chat-message-from-user": message.value.sender === "user",
"chat-message-from-bot": message.value.sender === "bot",
"chat-message-transparent": message.value.transparent === true
};
});
const linksNewTabPlugin = (vueMarkdownItInstance) => {
vueMarkdownItInstance.use(markdownLink, {
attrs: {
target: "_blank",
rel: "noopener"
}
});
};
const scrollToView = () => {
if (messageContainer.value?.scrollIntoView) {
messageContainer.value.scrollIntoView({
block: "start"
});
}
};
const markdownOptions = {
highlight(str, lang) {
if (lang && HighlightJS.getLanguage(lang)) {
try {
return HighlightJS.highlight(str, { language: lang }).value;
} catch {
}
}
return "";
}
};
const messageComponents = { ...options?.messageComponents ?? {} };
__expose({ scrollToView });
const readFileAsDataURL = async (file) => await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
onMounted(async () => {
if (message.value.files) {
for (const file of message.value.files) {
try {
const dataURL = await readFileAsDataURL(file);
fileSources.value[file.name] = dataURL;
} catch (error2) {
console.error("Error reading file:", error2);
}
}
}
});
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
ref_key: "messageContainer",
ref: messageContainer,
class: normalizeClass(["chat-message", classes.value])
}, [
!!_ctx.$slots.beforeMessage ? (openBlock(), createElementBlock("div", _hoisted_1$5, [
renderSlot(_ctx.$slots, "beforeMessage", normalizeProps(guardReactiveProps({ message: unref(message) })))
])) : createCommentVNode("", true),
renderSlot(_ctx.$slots, "default", {}, () => [
unref(message).type === "component" && messageComponents[unref(message).key] ? (openBlock(), createBlock(resolveDynamicComponent(messageComponents[unref(message).key]), normalizeProps(mergeProps({ key: 0 }, unref(message).arguments)), null, 16)) : (openBlock(), createBlock(unref(VueMarkdown), {
key: 1,
class: "chat-message-markdown",
source: messageText.value,
options: markdownOptions,
plugins: [linksNewTabPlugin]
}, null, 8, ["source", "plugins"])),
(unref(message).files ?? []).length > 0 ? (openBlock(), createElementBlock("div", _hoisted_2$2, [
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(message).files ?? [], (file) => {
return openBlock(), createElementBlock("div", {
key: file.name,
class: "chat-message-file"
}, [
createVNode(ChatFile, {
file,
"is-removable": false,
"is-previewable": true
}, null, 8, ["file"])
]);
}), 128))
])) : createCommentVNode("", true)
])
], 2);
};
}
});
const _hoisted_1$4 = {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em"
};
function render$1(_ctx, _cache) {
return openBlock(), createElementBlock("svg", _hoisted_1$4, _cache[0] || (_cache[0] = [
createBaseVNode("path", {
fill: "currentColor",
d: "M16.5 6v11.5a4 4 0 0 1-4 4a4 4 0 0 1-4-4V5A2.5 2.5 0 0 1 11 2.5A2.5 2.5 0 0 1 13.5 5v10.5a1 1 0 0 1-1 1a1 1 0 0 1-1-1V6H10v9.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0 2.5-2.5V5a4 4 0 0 0-4-4a4 4 0 0 0-4 4v12.5a5.5 5.5 0 0 0 5.5 5.5a5.5 5.5 0 0 0 5.5-5.5V6z"
}, null, -1)
]));
}
const IconPaperclip = { name: "mdi-paperclip", render: render$1 };
const _hoisted_1$3 = {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em"
};
function render(_ctx, _cache) {
return openBlock(), createElementBlock("svg", _hoisted_1$3, _cache[0] || (_cache[0] = [
createBaseVNode("path", {
fill: "currentColor",
d: "m2 21l21-9L2 3v7l15 2l-15 2z"
}, null, -1)
]));
}
const IconSend = { name: "mdi-send", render };
const _hoisted_1$2 = { class: "chat-inputs" };
const _hoisted_2$1 = {
key: 0,
class: "chat-input-left-panel"
};
const _hoisted_3$1 = ["disabled", "placeholder"];
const _hoisted_4 = { class: "chat-inputs-controls" };
const _hoisted_5 = ["disabled"];
const _hoisted_6 = ["disabled"];
const _hoisted_7 = {
key: 0,
class: "chat-files"
};
const _sfc_main$a = /* @__PURE__ */ defineComponent({
__name: "Input",
props: {
placeholder: { default: "inputPlaceholder" }
},
emits: ["arrowKeyDown"],
setup(__props, { emit: __emit }) {
const props = __props;
const { t } = useI18n();
const emit = __emit;
const { options } = useOptions();
const chatStore = useChat();
const { waitingForResponse } = chatStore;
const files = ref(null);
const chatTextArea = ref(null);
const input = ref("");
const isSubmitting = ref(false);
const resizeObserver = ref(null);
const waitingForChatResponse = ref(false);
const isSubmitDisabled = computed(() => {
if (waitingForChatResponse.value) return false;
return input.value === "" || unref(waitingForResponse) || options.disabled?.value === true;
});
const isInputDisabled = computed(() => options.disabled?.value === true);
const isFileUploadDisabled = computed(
() => isFileUploadAllowed.value && unref(waitingForResponse) && !options.disabled?.value
);
const isFileUploadAllowed = computed(() => unref(options.allowFileUploads) === true);
const allowedFileTypes = computed(() => unref(options.allowedFilesMimeTypes));
const styleVars = computed(() => {
const controlsCount = isFileUploadAllowed.value ? 2 : 1;
return {
"--controls-count": controlsCount
};
});
const {
open: openFileDialog,
reset: resetFileDialog,
onChange
} = useFileDialog({
multiple: true,
reset: false
});
onChange((newFiles) => {
if (!newFiles) return;
const newFilesDT = new DataTransfer();
if (files.value) {
for (let i = 0; i < files.value.length; i++) {
newFilesDT.items.add(files.value[i]);
}
}
for (let i = 0; i < newFiles.length; i++) {
newFilesDT.items.add(newFiles[i]);
}
files.value = newFilesDT.files;
});
onMounted(() => {
chatEventBus.on("focusInput", focusChatInput);
chatEventBus.on("blurInput", blurChatInput);
chatEventBus.on("setInputValue", setInputValue);
if (chatTextArea.value) {
resizeObserver.value = new ResizeObserver((entries) => {
for (const entry of entries) {
if (entry.target === chatTextArea.value) {
adjustTextAreaHeight();
}
}
});
resizeObserver.value.observe(chatTextArea.value);
}
});
onUnmounted(() => {
chatEventBus.off("focusInput", focusChatInput);
chatEventBus.off("blurInput", blurChatInput);
chatEventBus.off("setInputValue", setInputValue);
if (resizeObserver.value) {
resizeObserver.value.disconnect();
resizeObserver.value = null;
}
});
function blurChatInput() {
if (chatTextArea.value) {
chatTextArea.value.blur();
}
}
function focusChatInput() {
if (chatTextArea.value) {
chatTextArea.value.focus();
}
}
function setInputValue(value) {
input.value = value;
focusChatInput();
}
function attachFiles() {
if (files.value) {
const filesToAttach = Array.from(files.value);
resetFileDialog();
files.value = null;
return filesToAttach;
}
return [];
}
function setupWebsocketConnection(executionId) {
if (options.webhookUrl && chatStore.currentSessionId.value) {
try {
const wsUrl = constructChatWebsocketUrl(
options.webhookUrl,
executionId,
chatStore.currentSessionId.value,
true
);
chatStore.ws = new WebSocket(wsUrl);
chatStore.ws.onmessage = (e) => {
if (e.data === "n8n|heartbeat") {
chatStore.ws?.send("n8n|heartbeat-ack");
return;
}
if (e.data === "n8n|continue") {
waitingForChatResponse.value = false;
chatStore.waitingForResponse.value = true;
return;
}
const newMessage = {
id: v4(),
text: e.data,
sender: "bot"
};
chatStore.messages.value.push(newMessage);
waitingForChatResponse.value = true;
chatStore.waitingForResponse.value = false;
};
chatStore.ws.onclose = () => {
chatStore.ws = null;
waitingForChatResponse.value = false;
chatStore.waitingForResponse.value = false;
};
} catch (error2) {
console.error("Error setting up websocket connection", error2);
}
}
}
async function processFiles2(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(new Error(`Error reading file: ${reader.error?.message ?? "Unknown error"}`));
reader.readAsDataURL(file);
});
});
return await Promise.all(filePromises);
}
async function respondToChatNode(ws, messageText) {
const sentMessage = {
id: v4(),
text: messageText,
sender: "user",
files: files.value ? attachFiles() : void 0
};
chatStore.messages.value.push(sentMessage);
ws.send(
JSON.stringify({
sessionId: chatStore.currentSessionId.value,
action: "sendMessage",
chatInput: messageText,
files: await processFiles2(sentMessage.files)
})
);
chatStore.waitingForResponse.value = true;
waitingForChatResponse.value = false;
}
async function onSubmit(event) {
event.preventDefault();
if (isSubmitDisabled.value) {
return;
}
const messageText = input.value;
input.value = "";
isSubmitting.value = true;
if (chatStore.ws && waitingForChatResponse.value) {
await respondToChatNode(chatStore.ws, messageText);
return;
}
const response = await chatStore.sendMessage(messageText, attachFiles());
if (response?.executionId) {
setupWebsocketConnection(response.executionId);
}
isSubmitting.value = false;
}
async function onSubmitKeydown(event) {
if (event.shiftKey || event.isComposing) {
return;
}
await onSubmit(event);
adjustTextAreaHeight();
}
function onFileRemove(file) {
if (!files.value) return;
const dt = new DataTransfer();
for (let i = 0; i < files.value.length; i++) {
const currentFile = files.value[i];
if (file.name !== currentFile.name) dt.items.add(currentFile);
}
resetFileDialog();
files.value = dt.files;
}
function onKeyDown(event) {
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
event.preventDefault();
emit("arrowKeyDown", {
key: event.key,
currentInputValue: input.value
});
}
}
function onOpenFileDialog() {
if (isFileUploadDisabled.value) return;
openFileDialog({ accept: unref(allowedFileTypes) });
}
function adjustTextAreaHeight() {
const textarea = chatTextArea.value;
if (!textarea) return;
textarea.style.height = "var(--chat--textarea--height)";
const newHeight = Math.min(textarea.scrollHeight, 480);
textarea.style.height = `${newHeight}px`;
}
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
class: "chat-input",
style: normalizeStyle(styleVars.value),
onKeydown: withModifiers(onKeyDown, ["stop"])
}, [
createBaseVNode("div", _hoisted_1$2, [
_ctx.$slots.leftPanel ? (openBlock(), createElementBlock("div", _hoisted_2$1, [
renderSlot(_ctx.$slots, "leftPanel", {}, void 0, true)
])) : createCommentVNode("", true),
withDirectives(createBaseVNode("textarea", {
ref_key: "chatTextArea",
ref: chatTextArea,
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => input.value = $event),
"data-test-id": "chat-input",
disabled: isInputDisabled.value,
placeholder: unref(t)(props.placeholder),
onKeydown: withKeys(onSubmitKeydown, ["enter"]),
onInput: adjustTextAreaHeight,
onMousedown: adjustTextAreaHeight,
onFocus: adjustTextAreaHeight
}, null, 40, _hoisted_3$1), [
[vModelText, input.value]
]),
createBaseVNode("div", _hoisted_4, [
isFileUploadAllowed.value ? (openBlock(), createElementBlock("button", {
key: 0,
disabled: isFileUploadDisabled.value,
class: "chat-input-file-button",
"data-test-id": "chat-attach-file-button",
onClick: onOpenFileDialog
}, [
createVNode(unref(IconPaperclip), {
height: "24",
width: "24"
})
], 8, _hoisted_5)) : createCommentVNode("", true),
createBaseVNode("button", {
disabled: isSubmitDisabled.value,
class: "chat-input-send-button",
onClick: onSubmit
}, [
createVNode(unref(IconSend), {
height: "24",
width: "24"
})
], 8, _hoisted_6)
])
]),
files.value?.length && (!isSubmitting.value || waitingForChatResponse.value) ? (openBlock(), createElementBlock("div", _hoisted_7, [
(openBlock(true), createElementBlock(Fragment, null, renderList(files.value, (file) => {
return openBlock(), createBlock(ChatFile, {
key: file.name,
file,
"is-removable": true,
"is-previewable": true,
onRemove: onFileRemove
}, null, 8, ["file"]);
}), 128))
])) : createCommentVNode("", true)
], 36);
};
}
});
const ChatInput = /* @__PURE__ */ _export_sfc(_sfc_main$a, [["__scopeId", "data-v-b2a5be81"]]);
const _sfc_main$9 = /* @__PURE__ */ defineComponent({
__name: "MessageTyping",
props: {
animation: { default: "bouncing" }
},
setup(__props) {
const props = __props;
const message = {
id: "typing",
text: "",
sender: "bot"
};
const messageContainer = ref();
const classes = computed(() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
"chat-message-typing": true,
[`chat-message-typing-animation-${props.animation}`]: true
};
});
onMounted(() => {
messageContainer.value?.scrollToView();
});
return (_ctx, _cache) => {
return openBlock(), createBlock(unref(_sfc_main$b), {
ref_key: "messageContainer",
ref: messageContainer,
class: normalizeClass(classes.value),
message,
"data-test-id": "chat-message-typing"
}, {
default: withCtx(() => _cache[0] || (_cache[0] = [
createBaseVNode("div", { class: "chat-message-typing-body" }, [
createBaseVNode("span", { class: "chat-message-typing-circle" }),
createBaseVNode("span", { class: "chat-message-typing-circle" }),
createBaseVNode("span", { class: "chat-message-typing-circle" })
], -1)
])),
_: 1
}, 8, ["class"]);
};
}
});
const _hoisted_1$1 = {
key: 0,
class: "empty-container"
};
const _hoisted_2 = {
class: "empty",
"data-test-id": "chat-messages-empty"
};
const _hoisted_3 = {
key: 1,
class: "chat-messages-list"
};
const _sfc_main$8 = /* @__PURE__ */ defineComponent({
__name: "MessagesList",
props: {
messages: {},
emptyText: {}
},
setup(__props) {
const chatStore = useChat();
const messageComponents = ref([]);
const { initialMessages, waitingForResponse } = chatStore;
watch(
() => messageComponents.value.length,
() => {
const lastMessageComponent = messageComponents.value[messageComponents.value.length - 1];
if (lastMessageComponent) {
lastMessageComponent.scrollToView();
}
}
);
return (_ctx, _cache) => {
return _ctx.emptyText && unref(initialMessages).length === 0 && _ctx.messages.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_1$1, [
createBaseVNode("div", _hoisted_2, [
createVNode(unref(N8nIcon), {
icon: "message-circle",
size: "large",
class: "emptyIcon"
}),
createVNode(unref(N8nText), {
tag: "p",
size: "medium",
color: "text-base"
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(_ctx.emptyText), 1)
]),
_: 1
})
])
])) : (openBlock(), createElementBlock("div", _hoisted_3, [
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(initialMessages), (initialMessage) => {
return openBlock(), createBlock(_sfc_main$b, {
key: initialMessage.id,
message: initialMessage
}, null, 8, ["message"]);
}), 128)),
(openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.messages, (message) => {
return openBlock(), createBlock(_sfc_main$b, {
key: message.id,
ref_for: true,
ref_key: "messageComponents",
ref: messageComponents,
message
}, {
beforeMessage: withCtx(({ message: message2 }) => [
renderSlot(_ctx.$slots, "beforeMessage", mergeProps({ ref_for: true }, { message: message2 }))
]),
_: 2
}, 1032, ["message"]);
}), 128)),
unref(waitingForResponse) ? (openBlock(), createBlock(_sfc_main$9, { key: 0 })) : createCommentVNode("", true)
]));
};
}
});
const _sfc_main$7 = /* @__PURE__ */ defineComponent({
__name: "MessageOptionTooltip",
props: {
placement: {
type: String,
default: "top"
}
},
setup(__props) {
return (_ctx, _cache) => {
const _component_n8n_icon = N8nIcon;
const _component_n8n_tooltip = Tooltip;
return openBlock(), createElementBlock("div", {
class: normalizeClass(_ctx.$style.container)
}, [
createVNode(_component_n8n_tooltip, { placement: __props.placement }, {
content: withCtx(() => [
renderSlot(_ctx.$slots, "default")
]),
default: withCtx(() => [
createBaseVNode("span", {
class: normalizeClass(_ctx.$style.icon)
}, [
createVNode(_component_n8n_icon, {
icon: "info",
size: "xsmall"
})
], 2)
]),
_: 3
}, 8, ["placement"])
], 2);
};
}
});
const container$4 = "_container_pqtqf_123";
const icon$2 = "_icon_pqtqf_129";
const style0$6 = {
container: container$4,
icon: icon$2
};
const cssModules$6 = {
"$style": style0$6
};
const MessageOptionTooltip = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["__cssModules", cssModules$6]]);
const _sfc_main$6 = /* @__PURE__ */ defineComponent({
__name: "MessageOptionAction",
props: {
label: {},
icon: {},
placement: {}
},
setup(__props) {
const attrs = useAttrs();
const onClick = () => {
attrs.onClick?.();
};
return (_ctx, _cache) => {
const _component_n8n_icon = N8nIcon;
const _component_n8n_tooltip = Tooltip;
return openBlock(), createElementBlock("div", {
class: normalizeClass(_ctx.$style.container)
}, [
createVNode(_component_n8n_tooltip, { placement: _ctx.placement }, {
content: withCtx(() => [
createTextVNode(toDisplayString(_ctx.label), 1)
]),
default: withCtx(() => [
createVNode(_component_n8n_icon, {
class: normalizeClass(_ctx.$style.icon),
icon: _ctx.icon,
size: "xsmall",
onClick
}, null, 8, ["class", "icon"])
]),
_: 1
}, 8, ["placement"])
], 2);
};
}
});
const container$3 = "_container_u1r1u_123";
const icon$1 = "_icon_u1r1u_129";
const style0$5 = {
container: container$3,
icon: icon$1
};
const cssModules$5 = {
"$style": style0$5
};
const MessageOptionAction = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__cssModules", cssModules$5]]);
const _hoisted_1 = ["onClick"];
const _sfc_main$5 = /* @__PURE__ */ defineComponent({
__name: "ChatMessagesPanel",
props: {
pastChatMessages: {},
messages: {},
sessionId: {},
showCloseButton: { type: Boolean },
isOpen: { type: Boolean, default: true },
isReadOnly: { type: Boolean, default: false }
},
emits: ["displayExecution", "sendMessage", "refreshSession", "close", "clickHeader"],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const clipboard = useClipboard();
const locale = useI18n$1();
const toast = useToast();
const previousMessageIndex = ref(0);
const sessionIdText = computed(
() => locale.baseText("chat.window.session.id", {
interpolate: { id: `${props.sessionId.slice(0, 5)}...` }
})
);
const inputPlaceholder = computed(() => {
if (props.messages.length > 0) {
return locale.baseText("chat.window.chat.placeholder");
}
return locale.baseText("chat.window.chat.placeholderPristine");
});
function isTextMessage(message) {
return message.type === "text" || !message.type;
}
function repostMessage(message) {
void sendMessage(message.text);
}
function reuseMessage(message) {
chatEventBus.emit("setInputValue", message.text);
}
function sendMessage(message) {
previousMessageIndex.value = 0;
emit("sendMessage", message);
}
function onRefreshSession() {
emit("refreshSession");
}
function onArrowKeyDown({ currentInputValue, key }) {
const pastMessages = props.pastChatMessages;
const isCurrentInputEmptyOrMatch = currentInputValue.length === 0 || pastMessages.includes(currentInputValue);
if (isCurrentInputEmptyOrMatch && (key === "ArrowUp" || key === "ArrowDown")) {
if (pastMessages.length === 0) return;
chatEventBus.emit("blurInput");
if (pastMessages.length === 1) {
previousMessageIndex.value = 0;
} else {
if (key === "ArrowUp") {
if (currentInputValue.length === 0 && previousMessageIndex.value === 0) {
previousMessageIndex.value = pastMessages.length - 1;
} else {
previousMessageIndex.value = previousMessageIndex.value === 0 ? pastMessages.length - 1 : previousMessageIndex.value - 1;
}
} else if (key === "ArrowDown") {
previousMessageIndex.value = previousMessageIndex.value === pastMessages.length - 1 ? 0 : previousMessageIndex.value + 1;
}
}
const selectedMessage = pastMessages[previousMessageIndex.value];
chatEventBus.emit("setInputValue", selectedMessage);
chatEventBus.emit("focusInput");
}
if (!isCurrentInputEmptyOrMatch) {
previousMessageIndex.value = 0;
}
}
async function copySessionId() {
await clipboard.copy(props.sessionId);
toast.showMessage({
title: locale.baseText("generic.copiedToClipboard"),
message: "",
type: "success"
});
}
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
class: normalizeClass([_ctx.$style.chat, "ignore-key-press-canvas"]),
"data-test-id": "workflow-lm-chat-dialog",
tabindex: "0"
}, [
createVNode(LogsPanelHeader, {
"data-test-id": "chat-header",
title: unref(locale).baseText("chat.window.title"),
onClick: _cache[0] || (_cache[0] = ($event) => emit("clickHeader"))
}, {
actions: withCtx(() => [
unref(clipboard).isSupported && !_ctx.isReadOnly ? (openBlock(), createBlock(unref(Tooltip), { key: 0 }, {
content: withCtx(() => [
createTextVNode(toDisplayString(_ctx.sessionId) + " ", 1),
_cache[3] || (_cache[3] = createBaseVNode("br", null, null, -1)),
createTextVNode(" " + toDisplayString(unref(locale).baseText("chat.window.session.id.copy")), 1)
]),
default: withCtx(() => [
createVNode(unref(N8nButton), {
"data-test-id": "chat-session-id",
type: "secondary",
size: "mini",
class: normalizeClass(_ctx.$style.newHeaderButton),
onClick: withModifiers(copySessionId, ["stop"])
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(sessionIdText.value), 1)
]),
_: 1
}, 8, ["class"])
]),
_: 1
})) : createCommentVNode("", true),
_ctx.messages.length > 0 && !_ctx.isReadOnly ? (openBlock(), createBlock(unref(Tooltip), {
key: 1,
content: unref(locale).baseText("chat.window.session.resetSession")
}, {
default: withCtx(() => [
createVNode(unref(_sfc_main$k), {
class: normalizeClass(_ctx.$style.newHeaderButton),
"data-test-id": "refresh-session-button",
outline: "",
type: "secondary",
size: "small",
"icon-size": "medium",
icon: "undo-2",
title: unref(locale).baseText("chat.window.session.reset"),
onClick: withModifiers(onRefreshSession, ["stop"])
}, null, 8, ["class", "title"])
]),
_: 1
}, 8, ["content"])) : createCommentVNode("", true)
]),
_: 1
}, 8, ["title"]),
_ctx.isOpen ? (openBlock(), createElementBlock("main", {
key: 0,
class: normalizeClass(_ctx.$style.chatBody),
"data-test-id": "canvas-chat-body"
}, [
createVNode(_sfc_main$8, {
messages: _ctx.messages,
class: normalizeClass(_ctx.$style.messages),
"empty-text": unref(locale).baseText("chat.window.chat.emptyChatMessage.v2")
}, {
beforeMessage: withCtx(({ message }) => [
!_ctx.isReadOnly && message.sender === "bot" && !message.id.includes("preload") ? (openBlock(), createBlock(MessageOptionTooltip, {
key: 0,
placement: "right",
"data-test-id": "execution-id-tooltip"
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(unref(locale).baseText("chat.window.chat.chatMessageOptions.executionId")) + ": ", 1),
createBaseVNode("a", {
href: "#",
onClick: ($event) => emit("displayExecution", message.id)
}, toDisplayString(message.id), 9, _hoisted_1)
]),
_: 2
}, 1024)) : createCommentVNode("", true),
!_ctx.isReadOnly && isTextMessage(message) && message.sender === "user" ? (openBlock(), createBlock(MessageOptionAction, {
key: 1,
"data-test-id": "repost-message-button",
icon: "redo-2",
label: unref(locale).baseText("chat.window.chat.chatMessageOptions.repostMessage"),
placement: "left",
onClickOnce: ($event) => repostMessage(message)
}, null, 8, ["label", "onClickOnce"])) : createCommentVNode("", true),
!_ctx.isReadOnly && isTextMessage(message) && message.sender === "user" ? (openBlock(), createBlock(MessageOptionAction, {
key: 2,
"data-test-id": "reuse-message-button",
icon: "files",
label: unref(locale).baseText("chat.window.chat.chatMessageOptions.reuseMessage"),
placement: "left",
onClick: ($event) => reuseMessage(message)
}, null, 8, ["label", "onClick"])) : createCommentVNode("", true)
]),
_: 1
}, 8, ["messages", "class", "empty-text"])
], 2)) : createCommentVNode("", true),
_ctx.isOpen ? (openBlock(), createElementBlock("div", {
key: 1,
class: normalizeClass(_ctx.$style.messagesInput)
}, [
createVNode(ChatInput, {
"data-test-id": "lm-chat-inputs",
placeholder: inputPlaceholder.value,
onArrowKeyDown
}, createSlots({ _: 2 }, [
_ctx.pastChatMessages.length > 0 ? {
name: "leftPanel",
fn: withCtx(() => [
createBaseVNode("div", {
class: normalizeClass(_ctx.$style.messagesHistory)
}, [
createVNode(unref(N8nButton), {
title: "Navigate to previous message",
icon: "chevron-up",
type: "tertiary",
text: "",
size: "mini",
onClick: _cache[1] || (_cache[1] = ($event) => onArrowKeyDown({ currentInputValue: "", key: "ArrowUp" }))
}),
createVNode(unref(N8nButton), {
title: "Navigate to next message",
icon: "chevron-down",
type: "tertiary",
text: "",
size: "mini",
onClick: _cache[2] || (_cache[2] = ($event) => onArrowKeyDown({ currentInputValue: "", key: "ArrowDown" }))
})
], 2)
]),
key: "0"
} : void 0
]), 1032, ["placeholder"])
], 2)) : createCommentVNode("", true)
], 2);
};
}
});
const chat$1 = "_chat_oezi2_123";
const chatHeader = "_chatHeader_oezi2_151";
const chatTitle = "_chatTitle_oezi2_164";
const session = "_session_oezi2_168";
const sessionId = "_sessionId_oezi2_176";
const copyable = "_copyable_oezi2_182";
const headerButton = "_headerButton_oezi2_186";
const newHeaderButton = "_newHeaderButton_oezi2_191";
const chatBody = "_chatBody_oezi2_196";
const messages = "_messages_oezi2_205";
const messagesInput = "_messagesInput_oezi2_216";
const messagesHistory = "_messagesHistory_oezi2_250";
const style0$4 = {
chat: chat$1,
chatHeader,
chatTitle,
session,
sessionId,
copyable,
headerButton,
newHeaderButton,
chatBody,
messages,
messagesInput,
messagesHistory
};
const cssModules$4 = {
"$style": style0$4
};
const ChatMessagesPanel = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__cssModules", cssModules$4]]);
const _sfc_main$4 = /* @__PURE__ */ defineComponent({
__name: "LogsViewRunData",
props: {
title: {},
paneType: {},
logEntry: {},
collapsingTableColumnName: {}
},
emits: ["collapsingTableColumnChanged"],
setup(__props, { emit: __emit }) {
const emit = __emit;
const locale = useI18n$1();
const ndvStore = useNDVStore();
const popOutWindow = inject(PopOutWindowKey, ref());
const displayMode = ref(__props.paneType === "input" ? "schema" : "table");
const isMultipleInput = computed(
() => __props.paneType === "input" && (__props.logEntry.runData?.source.length ?? 0) > 1
);
const runDataProps = computed(() => {
if (isSubNodeLog(__props.logEntry) || __props.paneType === "output") {
return { node: __props.logEntry.node, runIndex: __props.logEntry.runIndex };
}
const source = __props.logEntry.runData?.source[0];
const node = source && __props.logEntry.workflow.getNode(source.previousNode);
if (!source || !node) {
return void 0;
}
return {
node: {
...node,
disabled: false
// For RunData component to render data from disabled nodes as well
},
runIndex: source.previousNodeRun ?? 0,
overrideOutputs: [source.previousNodeOutput ?? 0]
};
});
const isExecuting = computed(
() => __props.paneType === "output" && (__props.logEntry.runData?.executionStatus === "running" || __props.logEntry.runData?.executionStatus === "waiting")
);
function handleClickOpenNdv() {
ndvStore.setActiveNodeName(__props.logEntry.node.name, "logs_view");
}
function handleChangeDisplayMode(value) {
displayMode.value = value;
}
return (_ctx, _cache) => {
const _directive_n8n_html = resolveDirective("n8n-html");
return runDataProps.value ? (openBlock(), createBlock(RunData, mergeProps({ key: 0 }, runDataProps.value, {
key: `run-data${unref(popOutWindow) ? "-pop-out" : ""}`,
class: _ctx.$style.component,
"workflow-object": _ctx.logEntry.workflow,
"workflow-execution": _ctx.logEntry.execution,
"too-much-data-title": unref(locale).baseText("ndv.output.tooMuchData.title"),
"no-data-in-branch-message": unref(locale).baseText("ndv.output.noOutputDataInBranch"),
"executing-message": unref(locale).baseText("ndv.output.executing"),
"pane-type": _ctx.paneType,
"disable-run-index-selection": true,
compact: true,
"disable-pin": true,
"disable-edit": true,
"disable-hover-highlight": true,
"disable-settings-hint": true,
"display-mode": displayMode.value,
"disable-ai-content": !unref(isSubNodeLog)(_ctx.logEntry),
"is-executing": isExecuting.value,
"table-header-bg-color": "light",
"collapsing-table-column-name": _ctx.collapsingTableColumnName,
onDisplayModeChange: handleChangeDisplayMode,
onCollapsingTableColumnChanged: _cache[0] || (_cache[0] = ($event) => emit("collapsingTableColumnChanged", $event))
}), createSlots({
header: withCtx(() => [
createVNode(unref(N8nText), {
class: normalizeClass(_ctx.$style.title),
bold: true,
color: "text-light",
size: "small"
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(_ctx.title), 1)
]),
_: 1
}, 8, ["class"])
]),
"no-output-data": withCtx(() => [
createVNode(unref(N8nText), {
bold: true,
color: "text-dark",
size: "large"
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(unref(locale).baseText("ndv.output.noOutputData.title")), 1)
]),
_: 1
})
]),
"node-waiting": withCtx(() => [
createVNode(unref(N8nText), {
bold: true,
color: "text-dark",
size: "large"
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(unref(locale).baseText("ndv.output.waitNodeWaiting.title")), 1)
]),
_: 1
}),
withDirectives(createVNode(unref(N8nText), null, null, 512), [
[_directive_n8n_html, unref(waitingNodeTooltip)(_ctx.logEntry.node)]
])
]),
_: 2
}, [
isMultipleInput.value ? {
name: "content",
fn: withCtx(() => []),
key: "0"
} : void 0,
isMultipleInput.value ? {
name: "callout-message",
fn: withCtx(() => [
createVNode(unref(I18nT), {
keypath: "logs.details.body.multipleInputs",
scope: "global"
}, {
button: withCtx(() => [
createVNode(unref(N8nLink), {
size: "small",
onClick: handleClickOpenNdv
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(unref(locale).baseText("logs.details.body.multipleInputs.openingTheNode")), 1)
]),
_: 1
})
]),
_: 1
})
]),
key: "1"
} : void 0
]), 1040, ["class", "workflow-object", "workflow-execution", "too-much-data-title", "no-data-in-branch-message", "executing-message", "pane-type", "display-mode", "disable-ai-content", "is-executing", "collapsing-table-column-name"])) : createCommentVNode("", true);
};
}
});
const component = "_component_qlocg_123";
const title$1 = "_title_qlocg_127";
const style0$3 = {
component,
title: title$1
};
const cssModules$3 = {
"$style": style0$3
};
const LogsViewRunData = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["__cssModules", cssModules$3]]);
function useResizablePanel(localStorageKey, {
container: container2,
defaultSize,
snap = true,
minSize = 0,
maxSize = (size) => size,
position = "left",
allowCollapse,
allowFullSize
}) {
const containerSize = ref(0);
const persistedSize = useLocalStorage(localStorageKey, -1, { writeDefaults: false });
const isResizing = ref(false);
const sizeOnResizeStart = ref();
const minSizeValue = computed(() => resolveSize(minSize, containerSize.value));
const maxSizeValue = computed(() => resolveSize(maxSize, containerSize.value));
const constrainedSize = computed(() => {
const sizeInPixels = persistedSize.value >= 0 && persistedSize.value <= 1 ? containerSize.value * persistedSize.value : -1;
if (isResizing.value && allowCollapse && sizeInPixels < 30) {
return 0;
}
if (isResizing.value && allowFullSize && sizeInPixels > containerSize.value - 30) {
return containerSize.value;
}
const defaultSizeValue = resolveSize(defaultSize, containerSize.value);
if (Number.isNaN(sizeInPixels) || !Number.isFinite(sizeInPixels) || sizeInPixels < 0) {
return defaultSizeValue;
}
return Math.max(
minSizeValue.value,
Math.min(
snap && Math.abs(defaultSizeValue - sizeInPixels) < 30 ? defaultSizeValue : sizeInPixels,
maxSizeValue.value
)
);
});
function getSize(el) {
return position === "bottom" ? el.height : el.width;
}
function getOffsetSize(el) {
return position === "bottom" ? el.offsetHeight : el.offsetWidth;
}
function getValue(data) {
return position === "bottom" ? data.y : data.x;
}
function resolveSize(getter, containerSizeValue) {
return typeof getter === "number" ? getter : getter(containerSizeValue);
}
function onResize(data) {
const containerRect = unref(container2)?.getBoundingClientRect();
const newSizeInPixels = Math.max(
0,
position === "bottom" ? (containerRect ? getSize(containerRect) : 0) - getValue(data) : getValue(data) - (containerRect ? getValue(containerRect) : 0)
);
isResizing.value = true;
persistedSize.value = newSizeInPixels / containerSize.value;
if (sizeOnResizeStart.value === void 0) {
sizeOnResizeStart.value = persistedSize.value;
}
}
function onResizeEnd() {
if (minSizeValue.value > 0 && constrainedSize.value <= 0 || maxSizeValue.value < containerSize.value && constrainedSize.value >= containerSize.value) {
persistedSize.value = sizeOnResizeStart.value;
}
sizeOnResizeStart.value = void 0;
isResizing.value = false;
}
watch(
() => unref(container2),
(el, _, onCleanUp) => {
if (!el) {
return;
}
const observer = new ResizeObserver(() => {
containerSize.value = getOffsetSize(el);
});
observer.observe(el);
containerSize.value = getOffsetSize(el);
onCleanUp(() => observer.disconnect());
},
{ immediate: true }
);
return {
isResizing: computed(() => isResizing.value),
isCollapsed: computed(() => isResizing.value && constrainedSize.value <= 0),
isFullSize: computed(() => isResizing.value && constrainedSize.value >= containerSize.value),
size: constrainedSize,
onResize,
onResizeEnd
};
}
const MIN_IO_PANEL_WIDTH = 200;
const _sfc_main$3 = /* @__PURE__ */ defineComponent({
__name: "LogDetailsPanel",
props: {
isOpen: { type: Boolean },
logEntry: {},
window: {},
latestInfo: {},
panels: {},
collapsingInputTableColumnName: {},
collapsingOutputTableColumnName: {}
},
emits: ["clickHeader", "toggleInputOpen", "toggleOutputOpen", "collapsingInputTableColumnChanged", "collapsingOutputTableColumnChanged"],
setup(__props, { emit: __emit }) {
const emit = __emit;
const locale = useI18n$1();
const nodeTypeStore = useNodeTypesStore();
const type = computed(() => nodeTypeStore.getNodeType(__props.logEntry.node.type));
const consumedTokens2 = computed(() => getSubtreeTotalConsumedTokens(__props.logEntry, false));
const isTriggerNode = computed(() => type.value?.group.includes("trigger"));
const container2 = useTemplateRef("container");
const resizer = useResizablePanel("N8N_LOGS_INPUT_PANEL_WIDTH", {
container: container2,
defaultSize: (size) => size / 2,
minSize: MIN_IO_PANEL_WIDTH,
maxSize: (size) => size - MIN_IO_PANEL_WIDTH,
allowCollapse: true,
allowFullSize: true
});
const shouldResize = computed(() => __props.panels === LOG_DETAILS_PANEL_STATE.BOTH);
function handleResizeEnd() {
if (resizer.isCollapsed.value) {
emit("toggleInputOpen", false);
}
if (resizer.isFullSize.value) {
emit("toggleOutputOpen", false);
}
resizer.onResizeEnd();
}
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
ref_key: "container",
ref: container2,
class: normalizeClass(_ctx.$style.container),
"data-test-id": "log-details"
}, [
createVNode(LogsPanelHeader, {
"data-test-id": "log-details-header",
class: normalizeClass(_ctx.$style.header),
onClick: _cache[2] || (_cache[2] = ($event) => emit("clickHeader"))
}, {
title: withCtx(() => [
createBaseVNode("div", {
class: normalizeClass(_ctx.$style.title)
}, [
createVNode(NodeIcon, {
"node-type": type.value,
size: 16,
class: normalizeClass(_ctx.$style.icon)
}, null, 8, ["node-type", "class"]),
createVNode(LogsViewNodeName, {
name: _ctx.latestInfo?.name ?? _ctx.logEntry.node.name,
"is-deleted": _ctx.latestInfo?.deleted ?? false
}, null, 8, ["name", "is-deleted"]),
_ctx.isOpen && _ctx.logEntry.runData !== void 0 ? (openBlock(), createBlock(LogsViewExecutionSummary, {
key: 0,
class: normalizeClass(_ctx.$style.executionSummary),
status: _ctx.logEntry.runData.executionStatus ?? "unknown",
"consumed-tokens": consumedTokens2.value,
"start-time": _ctx.logEntry.runData.startTime,
"time-took": _ctx.logEntry.runData.executionTime
}, null, 8, ["class", "status", "consumed-tokens", "start-time", "time-took"])) : createCommentVNode("", true)
], 2)
]),
actions: withCtx(() => [
_ctx.isOpen && !isTriggerNode.value && !unref(isPlaceholderLog)(_ctx.logEntry) ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass(_ctx.$style.actions)
}, [
createVNode(KeyboardShortcutTooltip, {
label: unref(locale).baseText("generic.shortcutHint"),
shortcut: { keys: ["i"] }
}, {
default: withCtx(() => [
createVNode(unref(N8nButton), {
size: "mini",
type: "secondary",
class: normalizeClass(_ctx.panels === unref(LOG_DETAILS_PANEL_STATE).OUTPUT ? "" : _ctx.$style.pressed),
onClick: _cache[0] || (_cache[0] = withModifiers(($event) => emit("toggleInputOpen"), ["stop"]))
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(unref(locale).baseText("logs.details.header.actions.input")), 1)
]),
_: 1
}, 8, ["class"])
]),
_: 1
}, 8, ["label"]),
createVNode(KeyboardShortcutTooltip, {
label: unref(locale).baseText("generic.shortcutHint"),
shortcut: { keys: ["o"] }
}, {
default: withCtx(() => [
createVNode(unref(N8nButton), {
size: "mini",
type: "secondary",
class: normalizeClass(_ctx.panels === unref(LOG_DETAILS_PANEL_STATE).INPUT ? "" : _ctx.$style.pressed),
onClick: _cache[1] || (_cache[1] = withModifiers(($event) => emit("toggleOutputOpen"), ["stop"]))
}, {
default: withCtx(() => [
createTextVNode(toDisplayString(unref(locale).baseText("logs.details.header.actions.output")), 1)
]),
_: 1
}, 8, ["class"])
]),
_: 1
}, 8, ["label"])
], 2)) : createCommentVNode("", true),
renderSlot(_ctx.$slots, "actions")
]),
_: 3
}, 8, ["class"]),
_ctx.isOpen ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass(_ctx.$style.content),
"data-test-id": "logs-details-body"
}, [
unref(isPlaceholderLog)(_ctx.logEntry) ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass(_ctx.$style.placeholder)
}, [
createVNode(unref(N8nText), { color: "text-base" }, {
default: withCtx(() => [
createTextVNode(toDisplayString(unref(locale).baseText("ndv.output.runNodeHint")), 1)
]),
_: 1
})
], 2)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
!isTriggerNode.value && _ctx.panels !== unref(LOG_DETAILS_PANEL_STATE).OUTPUT ? (openBlock(), createBlock(unref(N8nResizeWrapper), {
key: 0,
class: normalizeClass({
[_ctx.$style.inputResizer]: true,
[_ctx.$style.collapsed]: unref(resizer).isCollapsed.value,
[_ctx.$style.full]: unref(resizer).isFullSize.value
}),
width: unref(resizer).size.value,
style: normalizeStyle(shouldResize.value ? { width: `${unref(resizer).size.value ?? 0}px` } : void 0),
"supported-directions": ["right"],
"is-resizing-enabled": shouldResize.value,
window: _ctx.window,
onResize: unref(resizer).onResize,
onResizeend: handleResizeEnd
}, {
default: withCtx(() => [
createVNode(LogsViewRunData, {
"data-test-id": "log-details-input",
"pane-type": "input",
title: unref(locale).baseText("logs.details.header.actions.input"),
"log-entry": _ctx.logEntry,
"collapsing-table-column-name": _ctx.collapsingInputTableColumnName,
onCollapsingTableColumnChanged: _cache[3] || (_cache[3] = ($event) => emit("collapsingInputTableColumnChanged", $event))
}, null, 8, ["title", "log-entry", "collapsing-table-column-name"])
]),
_: 1
}, 8, ["class", "width", "style", "is-resizing-enabled", "window", "onResize"])) : createCommentVNode("", true),
isTriggerNode.value || _ctx.panels !== unref(LOG_DETAILS_PANEL_STATE).INPUT ? (openBlock(), createBlock(LogsViewRunData, {
key: 1,
"data-test-id": "log-details-output",
"pane-type": "output",
class: normalizeClass(_ctx.$style.outputPanel),
title: unref(locale).baseText("logs.details.header.actions.output"),
"log-entry": _ctx.logEntry,
"collapsing-table-column-name": _ctx.collapsingOutputTableColumnName,
onCollapsingTableColumnChanged: _cache[4] || (_cache[4] = ($event) => emit("collapsingOutputTableColumnChanged", $event))
}, null, 8, ["class", "title", "log-entry", "collapsing-table-column-name"])) : createCommentVNode("", true)
], 64))
], 2)) : createCommentVNode("", true)
], 2);
};
}
});
const container$2 = "_container_tdw6t_123";
const header = "_header_tdw6t_132";
const actions = "_actions_tdw6t_136";
const pressed = "_pressed_tdw6t_142";
const title = "_title_tdw6t_146";
const icon = "_icon_tdw6t_152";
const executionSummary = "_executionSummary_tdw6t_156";
const content = "_content_tdw6t_160";
const outputPanel = "_outputPanel_tdw6t_168";
const inputResizer = "_inputResizer_tdw6t_173";
const collapsed = "_collapsed_tdw6t_177";
const full = "_full_tdw6t_177";
const placeholder = "_placeholder_tdw6t_181";
const style0$2 = {
container: container$2,
header,
actions,
pressed,
title,
icon,
executionSummary,
content,
outputPanel,
inputResizer,
collapsed,
full,
placeholder
};
const cssModules$2 = {
"$style": style0$2
};
const LogsDetailsPanel = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__cssModules", cssModules$2]]);
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
__name: "LogsPanelActions",
props: {
isOpen: { type: Boolean },
isSyncSelectionEnabled: { type: Boolean },
showToggleButton: { type: Boolean },
showPopOutButton: { type: Boolean }
},
emits: ["popOut", "toggleOpen", "toggleSyncSelection"],
setup(__props, { emit: __emit }) {
const emit = __emit;
const appStyles = useStyles();
const locales = useI18n$1();
const tooltipZIndex = computed(() => appStyles.APP_Z_INDEXES.ASK_ASSISTANT_FLOATING_BUTTON + 100);
const popOutButtonText = computed(() => locales.baseText("runData.panel.actions.popOut"));
const toggleButtonText = computed(
() => locales.baseText(__props.isOpen ? "runData.panel.actions.collapse" : "runData.panel.actions.open")
);
const menuItems = computed(() => [
{
id: "toggleSyncSelection",
label: locales.baseText("runData.panel.actions.sync"),
checked: __props.isSyncSelectionEnabled
},
...__props.showPopOutButton ? [{ id: "popOut", label: popOutButtonText.value }] : []
]);
function handleSelectMenuItem(selected2) {
switch (selected2) {
case "popOut":
emit(selected2);
return;
case "toggleSyncSelection":
emit(selected2);
return;
}
}
return (_ctx, _cache) => {
const _component_N8nTooltip = Tooltip;
return openBlock(), createElementBlock("div", {
class: normalizeClass(_ctx.$style.container)
}, [
!_ctx.isOpen && _ctx.showPopOutButton ? (openBlock(), createBlock(_component_N8nTooltip, {
key: 0,
"z-index": tooltipZIndex.value,
content: popOutButtonText.value
}, {
default: withCtx(() => [
createVNode(unref(_sfc_main$k), {
icon: "pop-out",
type: "tertiary",
text: "",
size: "small",
"icon-size": "medium",
"aria-label": popOutButtonText.value,
onClick: _cache[0] || (_cache[0] = withModifiers(($event) => emit("popOut"), ["stop"]))
}, null, 8, ["aria-label"])
]),
_: 1
}, 8, ["z-index", "content"])) : createCommentVNode("", true),
_ctx.isOpen ? (openBlock(), createBlock(unref(N8nActionDropdown), {
key: 1,
"icon-size": "small",
"activator-icon": "ellipsis",
"activator-size": "small",
items: menuItems.value,
teleported: false,
onSelect: handleSelectMenuItem
}, null, 8, ["items"])) : createCommentVNode("", true),
_ctx.showToggleButton ? (openBlock(), createBlock(KeyboardShortcutTooltip, {
key: 2,
label: unref(locales).baseText("generic.shortcutHint"),
shortcut: { keys: ["l"] },
"z-index": tooltipZIndex.value
}, {
default: withCtx(() => [
createVNode(unref(_sfc_main$k), {
type: "tertiary",
text: "",
size: "small",
"icon-size": "medium",
icon: _ctx.isOpen ? "chevron-down" : "chevron-up",
"aria-label": toggleButtonText.value,
onClick: _cache[1] || (_cache[1] = withModifiers(($event) => emit("toggleOpen"), ["stop"]))
}, null, 8, ["icon", "aria-label"])
]),
_: 1
}, 8, ["label", "z-index"])) : createCommentVNode("", true)
], 2);
};
}
});
const container$1 = "_container_xdwf0_123";
const style0$1 = {
container: container$1
};
const cssModules$1 = {
"$style": style0$1
};
const LogsPanelActions = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__cssModules", cssModules$1]]);
function useLogsExecutionData() {
const nodeHelpers = useNodeHelpers();
const workflowsStore = useWorkflowsStore();
const toast = useToast();
const execData = ref();
const subWorkflowExecData = ref({});
const subWorkflows = ref({});
const workflow = computed(
() => execData.value ? new Workflow({
...execData.value?.workflowData,
nodeTypes: workflowsStore.getNodeTypes()
}) : void 0
);
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 (!execData.value?.data || !workflow.value) {
return [];
}
return createLogTree(
workflow.value,
execData.value,
subWorkflows.value,
subWorkflowExecData.value
);
});
const updateInterval = computed(
() => (entries.value?.length ?? 0) > 1 ? LOGS_EXECUTION_DATA_THROTTLE_DURATION : 0
);
function resetExecutionData() {
execData.value = void 0;
workflowsStore.setWorkflowExecutionData(null);
nodeHelpers.updateNodesExecutionIssues();
}
async function loadSubExecution(logEntry) {
const locator = findSubExecutionLocator(logEntry);
if (!execData.value?.data || 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(
// Fields that should trigger update
[
() => workflowsStore.workflowExecutionData?.id,
() => workflowsStore.workflowExecutionData?.workflowData.id,
() => workflowsStore.workflowExecutionData?.status,
() => workflowsStore.workflowExecutionResultDataLastUpdate,
() => workflowsStore.workflowExecutionStartedData
],
useThrottleFn(
([executionId], [previousExecutionId]) => {
execData.value = workflowsStore.workflowExecutionData === null ? void 0 : mergeStartData(
workflowsStore.workflowExecutionStartedData?.[1] ?? {},
workflowsStore.workflowExecutionData
);
if (executionId !== previousExecutionId) {
subWorkflowExecData.value = {};
subWorkflows.value = {};
}
},
updateInterval,
true,
true
),
{ immediate: true }
);
watch(
() => workflowsStore.workflowId,
(newId) => {
if (newId === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
resetExecutionData();
}
}
);
return {
execution: computed(() => execData.value),
entries,
hasChat,
latestNodeNameById,
resetExecutionData,
loadSubExecution
};
}
function useLogsSelection(execution, tree2, flatLogEntries, toggleExpand) {
const telemetry = useTelemetry();
const manualLogEntrySelection = shallowRef({ type: "initial" });
const nodeIdToSelect = shallowRef();
const isExecutionStopped = computed(() => execution.value?.stoppedAt !== void 0);
const selected2 = computed(
() => findSelectedLogEntry(manualLogEntrySelection.value, tree2.value, !isExecutionStopped.value)
);
const logsStore = useLogsStore();
const uiStore = useUIStore();
const canvasStore = useCanvasStore();
const workflowsStore = useWorkflowsStore();
function syncSelectionToCanvasIfEnabled(value) {
if (!logsStore.isLogSelectionSyncedWithCanvas) {
return;
}
canvasEventBus.emit("nodes:select", { ids: [value.node.id], panIntoView: true });
}
function select(value) {
manualLogEntrySelection.value = value === void 0 ? { type: "none" } : { type: "selected", entry: value };
if (value) {
syncSelectionToCanvasIfEnabled(value);
telemetry.track("User selected node in log view", {
node_type: value.node.type,
node_id: value.node.id,
execution_id: execution.value?.id,
workflow_id: execution.value?.workflowData.id,
subworkflow_depth: getDepth(value)
});
}
}
function selectPrev() {
const entries = flatLogEntries.value;
if (entries.length === 0) {
return;
}
const prevEntry = selected2.value ? getEntryAtRelativeIndex(entries, selected2.value.id, -1) ?? entries[0] : entries[entries.length - 1];
manualLogEntrySelection.value = { type: "selected", entry: prevEntry };
syncSelectionToCanvasIfEnabled(prevEntry);
}
function selectNext() {
const entries = flatLogEntries.value;
if (entries.length === 0) {
return;
}
const nextEntry = selected2.value ? getEntryAtRelativeIndex(entries, selected2.value.id, 1) ?? entries[entries.length - 1] : entries[0];
manualLogEntrySelection.value = { type: "selected", entry: nextEntry };
syncSelectionToCanvasIfEnabled(nextEntry);
}
watch(
selected2,
(sel) => {
if (sel) {
logsStore.setSubNodeSelected(isSubNodeLog(sel));
}
},
{ immediate: true }
);
watch(
[() => uiStore.lastSelectedNode, () => logsStore.isLogSelectionSyncedWithCanvas],
([selectedOnCanvas, shouldSync]) => {
const selectedNodeId = selectedOnCanvas ? workflowsStore.nodesByName[selectedOnCanvas]?.id : void 0;
nodeIdToSelect.value = shouldSync && !canvasStore.hasRangeSelection && selected2.value?.node.id !== selectedNodeId ? selectedNodeId : void 0;
},
{ immediate: true }
);
watch(
[tree2, nodeIdToSelect],
([latestTree, id]) => {
if (id === void 0) {
return;
}
const entry = findLogEntryRec((e) => e.node.id === id, latestTree);
if (!entry) {
return;
}
nodeIdToSelect.value = void 0;
manualLogEntrySelection.value = { type: "selected", entry };
let parent = entry.parent;
while (parent !== void 0) {
toggleExpand(parent, true);
parent = parent.parent;
}
},
{ immediate: true }
);
return { selected: selected2, select, selectPrev, selectNext };
}
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) {
void loadSubExecution(treeNode);
return;
}
collapsedEntries.value = {
...collapsedEntries.value,
[treeNode.id]: expand === void 0 ? !collapsedEntries.value[treeNode.id] : !expand
};
}
return {
flatLogEntries,
toggleExpanded
};
}
function isStyle(node) {
return node instanceof HTMLStyleElement || node instanceof HTMLLinkElement && node.rel === "stylesheet";
}
function syncStyleMutations(destination, mutations) {
const currentStyles = destination.document.head.querySelectorAll('style, link[rel="stylesheet"]');
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (isStyle(node)) {
destination.document.head.appendChild(node.cloneNode(true));
}
}
for (const node of mutation.removedNodes) {
if (isStyle(node)) {
for (const found of currentStyles) {
if (found.isEqualNode(node)) {
found.remove();
}
}
}
}
}
}
function copyFavicon(source, target) {
const iconUrl = source.document.querySelector("link[rel=icon]")?.getAttribute("href");
if (iconUrl) {
const link = target.document.createElement("link");
link.setAttribute("rel", "icon");
link.setAttribute("href", iconUrl);
target.document.head.appendChild(link);
}
}
function usePopOutWindow({
title: title2,
container: container2,
content: content2,
initialHeight,
initialWidth,
shouldPopOut,
onRequestClose
}) {
const popOutWindow = ref();
const isUnmounting = ref(false);
const canPopOut = computed(
() => window.parent === window
/* Not in iframe */
);
const isPoppedOut = computed(() => !!popOutWindow.value);
const tooltipContainer = computed(
() => isPoppedOut.value ? content2.value ?? void 0 : void 0
);
const observer = new MutationObserver((mutations) => {
if (popOutWindow.value) {
syncStyleMutations(popOutWindow.value, mutations);
}
});
const documentTitle = useDocumentTitle(popOutWindow);
observer.observe(document.head, { childList: true, subtree: true });
provide(PopOutWindowKey, popOutWindow);
useProvideTooltipAppendTo(tooltipContainer);
async function showPopOut() {
if (!content2.value) {
return;
}
if (!popOutWindow.value) {
const options = `popup=yes,width=${initialWidth},height=${initialHeight},left=100,top=100,toolbar=no,menubar=no,scrollbars=yes,resizable=yes`;
popOutWindow.value = window.open("", "_blank", options) ?? void 0;
}
if (!popOutWindow.value) {
return;
}
copyFavicon(window, popOutWindow.value);
for (const styleSheet of [...document.styleSheets]) {
try {
const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join("");
const style = document.createElement("style");
style.textContent = cssRules;
popOutWindow.value.document.head.appendChild(style);
} catch (e) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.type = styleSheet.type;
link.media = styleSheet.media;
link.href = styleSheet.href;
popOutWindow.value.document.head.appendChild(link);
}
}
popOutWindow.value.document.body.append(content2.value);
popOutWindow.value.addEventListener("pagehide", () => !isUnmounting.value && onRequestClose());
}
function hidePopOut() {
popOutWindow.value?.close();
popOutWindow.value = void 0;
if (content2.value) {
container2.value?.appendChild(content2.value);
}
}
watch(shouldPopOut, (value) => value ? requestAnimationFrame(showPopOut) : hidePopOut(), {
immediate: true
});
watch(
[title2, popOutWindow],
([newTitle, win]) => {
if (win) {
documentTitle.set(newTitle);
}
},
{ immediate: true }
);
onScopeDispose(() => {
observer.disconnect();
});
onBeforeUnmount(() => {
isUnmounting.value = true;
if (popOutWindow.value) {
popOutWindow.value.close();
onRequestClose();
}
});
return { canPopOut, isPoppedOut, popOutWindow };
}
function useLogsPanelLayout(workflowName, popOutContainer, popOutContent2, container2, logsContainer2) {
const logsStore = useLogsStore();
const telemetry = useTelemetry();
const resizer = useResizablePanel(LOCAL_STORAGE_PANEL_HEIGHT, {
container: document.body,
position: "bottom",
snap: false,
defaultSize: (size) => size * 0.3,
minSize: 160,
maxSize: (size) => size * 0.75,
allowCollapse: true
});
const chatPanelResizer = useResizablePanel(LOCAL_STORAGE_PANEL_WIDTH, {
container: container2,
defaultSize: (size) => Math.min(800, size * 0.3),
minSize: 240,
maxSize: (size) => size * 0.8
});
const overviewPanelResizer = useResizablePanel(LOCAL_STORAGE_OVERVIEW_PANEL_WIDTH, {
container: logsContainer2,
defaultSize: (size) => Math.min(240, size * 0.2),
minSize: 80,
maxSize: 500,
allowFullSize: true
});
const isOpen = computed(
() => logsStore.isOpen ? !resizer.isCollapsed.value : resizer.isResizing.value && resizer.size.value > 0
);
const isCollapsingDetailsPanel = computed(() => overviewPanelResizer.isFullSize.value);
const popOutWindowTitle = computed(() => `Logs - ${workflowName.value}`);
const shouldPopOut = computed(() => logsStore.state === LOGS_PANEL_STATE.FLOATING);
const {
canPopOut,
isPoppedOut,
popOutWindow
} = usePopOutWindow({
title: popOutWindowTitle,
initialHeight: 400,
initialWidth: window.document.body.offsetWidth * 0.8,
container: popOutContainer,
content: popOutContent2,
shouldPopOut,
onRequestClose: () => {
if (!isOpen.value) {
return;
}
telemetry.track("User toggled log view", { new_state: "attached" });
logsStore.setPreferPoppedOut(false);
}
});
function handleToggleOpen(open) {
const wasOpen = logsStore.isOpen;
if (open === wasOpen) {
return;
}
logsStore.toggleOpen(open);
telemetry.track("User toggled log view", {
new_state: wasOpen ? "collapsed" : "attached"
});
}
function handlePopOut() {
telemetry.track("User toggled log view", { new_state: "floating" });
logsStore.toggleOpen(true);
logsStore.setPreferPoppedOut(true);
}
function handleResizeEnd() {
if (!logsStore.isOpen && !resizer.isCollapsed.value) {
handleToggleOpen(true);
}
if (resizer.isCollapsed.value) {
handleToggleOpen(false);
}
resizer.onResizeEnd();
}
watch(
[() => logsStore.state, resizer.size],
([state, height]) => {
logsStore.setHeight(
state === LOGS_PANEL_STATE.FLOATING ? 0 : state === LOGS_PANEL_STATE.ATTACHED ? height : 32
);
},
{ immediate: true }
);
return {
height: resizer.size,
chatPanelWidth: chatPanelResizer.size,
overviewPanelWidth: overviewPanelResizer.size,
canPopOut,
isOpen,
isCollapsingDetailsPanel,
isPoppedOut,
isOverviewPanelFullWidth: overviewPanelResizer.isFullSize,
popOutWindow,
onToggleOpen: handleToggleOpen,
onPopOut: handlePopOut,
onResize: resizer.onResize,
onResizeEnd: handleResizeEnd,
onChatPanelResize: chatPanelResizer.onResize,
onChatPanelResizeEnd: chatPanelResizer.onResizeEnd,
onOverviewPanelResize: overviewPanelResizer.onResize,
onOverviewPanelResizeEnd: overviewPanelResizer.onResizeEnd
};
}
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
__name: "LogsViewKeyboardEventListener",
props: {
keyMap: {},
container: {}
},
setup(__props) {
const popOutWindow = inject(PopOutWindowKey, ref());
const activeElement = useActiveElement({ window: popOutWindow?.value });
const isBlurred = computed(() => {
if (popOutWindow?.value) {
return popOutWindow.value.document.activeElement === null;
}
return !activeElement.value || !__props.container || !__props.container.contains(activeElement.value) && __props.container !== activeElement.value;
});
useKeybindings(
toRef(() => __props.keyMap),
{ disabled: isBlurred }
);
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div");
};
}
});
const _sfc_main = /* @__PURE__ */ defineComponent({
__name: "LogsPanel",
props: {
isReadOnly: { type: Boolean, default: false }
},
setup(__props) {
const props = __props;
const container2 = useTemplateRef("container");
const logsContainer2 = useTemplateRef("logsContainer");
const popOutContainer = useTemplateRef("popOutContainer");
const popOutContent2 = useTemplateRef("popOutContent");
const logsStore = useLogsStore();
const ndvStore = useNDVStore();
const workflowsStore = useWorkflowsStore();
const workflowName = computed(() => workflowsStore.workflow.name);
const {
height,
chatPanelWidth,
overviewPanelWidth,
canPopOut,
isOpen,
isPoppedOut,
isCollapsingDetailsPanel,
isOverviewPanelFullWidth,
popOutWindow,
onResize,
onResizeEnd,
onToggleOpen,
onPopOut,
onChatPanelResize,
onChatPanelResizeEnd,
onOverviewPanelResize,
onOverviewPanelResizeEnd
} = useLogsPanelLayout(workflowName, popOutContainer, popOutContent2, container2, logsContainer2);
const {
currentSessionId,
messages: messages2,
previousChatMessages,
sendMessage,
refreshSession,
displayExecution
} = useChatState(props.isReadOnly);
const { entries, execution, hasChat, latestNodeNameById, resetExecutionData, loadSubExecution } = useLogsExecutionData();
const { flatLogEntries, toggleExpanded } = useLogsTreeExpand(entries, loadSubExecution);
const { selected: selected2, select, selectNext, selectPrev } = useLogsSelection(
execution,
entries,
flatLogEntries,
toggleExpanded
);
const inputTableColumnCollapsing = ref();
const outputTableColumnCollapsing = ref();
const isLogDetailsOpen = computed(() => isOpen.value && selected2.value !== void 0);
const isLogDetailsVisuallyOpen = computed(
() => isLogDetailsOpen.value && !isCollapsingDetailsPanel.value
);
const logsPanelActionsProps = computed(() => ({
isOpen: isOpen.value,
isSyncSelectionEnabled: logsStore.isLogSelectionSyncedWithCanvas,
showToggleButton: !isPoppedOut.value,
showPopOutButton: canPopOut.value && !isPoppedOut.value,
onPopOut,
onToggleOpen,
onToggleSyncSelection: logsStore.toggleLogSelectionSync
}));
const inputCollapsingColumnName = computed(
() => inputTableColumnCollapsing.value?.nodeName === selected2.value?.node.name ? inputTableColumnCollapsing.value?.columnName ?? null : null
);
const outputCollapsingColumnName = computed(
() => outputTableColumnCollapsing.value?.nodeName === selected2.value?.node.name ? outputTableColumnCollapsing.value?.columnName ?? null : null
);
const keyMap = computed(() => ({
j: selectNext,
k: selectPrev,
Escape: () => select(void 0),
ArrowDown: selectNext,
ArrowUp: selectPrev,
Space: () => selected2.value && toggleExpanded(selected2.value),
Enter: () => selected2.value && handleOpenNdv(selected2.value)
}));
function handleResizeOverviewPanelEnd() {
if (isOverviewPanelFullWidth.value) {
select(void 0);
}
onOverviewPanelResizeEnd();
}
function handleOpenNdv(treeNode) {
ndvStore.setActiveNodeName(treeNode.node.name, "logs_view");
void nextTick(() => {
const source = treeNode.runData?.source[0];
const inputBranch = source?.previousNodeOutput ?? 0;
ndvEventBus.emit("updateInputNodeName", source?.previousNode);
ndvEventBus.emit("setInputBranchIndex", inputBranch);
ndvStore.setOutputRunIndex(treeNode.runIndex);
});
}
function handleChangeInputTableColumnCollapsing(columnName) {
inputTableColumnCollapsing.value = columnName && selected2.value ? { nodeName: selected2.value.node.name, columnName } : void 0;
}
function handleChangeOutputTableColumnCollapsing(columnName) {
outputTableColumnCollapsing.value = columnName && selected2.value ? { nodeName: selected2.value.node.name, columnName } : void 0;
}
return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", {
ref_key: "popOutContainer",
ref: popOutContainer
}, [
(openBlock(), createBlock(_sfc_main$1, {
key: String(!!unref(popOutWindow)),
"key-map": keyMap.value,
container: unref(container2)
}, null, 8, ["key-map", "container"])),
createBaseVNode("div", {
ref_key: "popOutContent",
ref: popOutContent2,
class: normalizeClass([_ctx.$style.popOutContent, unref(isPoppedOut) ? _ctx.$style.poppedOut : ""])
}, [
createVNode(unref(N8nResizeWrapper), {
height: unref(isPoppedOut) ? void 0 : unref(height),
"supported-directions": ["top"],
"is-resizing-enabled": !unref(isPoppedOut),
class: normalizeClass(_ctx.$style.resizeWrapper),
style: normalizeStyle({ height: unref(isOpen) && !unref(isPoppedOut) ? `${unref(height)}px` : "auto" }),
onResize: unref(onResize),
onResizeend: unref(onResizeEnd)
}, {
default: withCtx(() => [
createBaseVNode("div", {
ref_key: "container",
ref: container2,
class: normalizeClass(_ctx.$style.container),
tabindex: "-1"
}, [
unref(hasChat) && (!props.isReadOnly || unref(messages2).length > 0) ? (openBlock(), createBlock(unref(N8nResizeWrapper), {
key: 0,
"supported-directions": ["right"],
"is-resizing-enabled": unref(isOpen),
width: unref(chatPanelWidth),
style: normalizeStyle({ width: `${unref(chatPanelWidth)}px` }),
class: normalizeClass(_ctx.$style.chat),
window: unref(popOutWindow),
onResize: unref(onChatPanelResize),
onResizeend: unref(onChatPanelResizeEnd)
}, {
default: withCtx(() => [
(openBlock(), createBlock(ChatMessagesPanel, {
key: `canvas-chat-${unref(currentSessionId)}${unref(isPoppedOut) ? "-pop-out" : ""}`,
"data-test-id": "canvas-chat",
"is-open": unref(isOpen),
"is-read-only": _ctx.isReadOnly,
messages: unref(messages2),
"session-id": unref(currentSessionId),
"past-chat-messages": unref(previousChatMessages),
"show-close-button": false,
"is-new-logs-enabled": true,
onClose: unref(onToggleOpen),
onRefreshSession: unref(refreshSession),
onDisplayExecution: unref(displayExecution),
onSendMessage: unref(sendMessage),
onClickHeader: _cache[0] || (_cache[0] = ($event) => unref(onToggleOpen)(true))
}, null, 8, ["is-open", "is-read-only", "messages", "session-id", "past-chat-messages", "onClose", "onRefreshSession", "onDisplayExecution", "onSendMessage"]))
]),
_: 1
}, 8, ["is-resizing-enabled", "width", "style", "class", "window", "onResize", "onResizeend"])) : createCommentVNode("", true),
createBaseVNode("div", {
ref_key: "logsContainer",
ref: logsContainer2,
class: normalizeClass(_ctx.$style.logsContainer)
}, [
createVNode(unref(N8nResizeWrapper), {
class: normalizeClass(_ctx.$style.overviewResizer),
width: unref(overviewPanelWidth),
style: normalizeStyle({ width: isLogDetailsVisuallyOpen.value ? `${unref(overviewPanelWidth)}px` : "" }),
"supported-directions": ["right"],
"is-resizing-enabled": isLogDetailsOpen.value,
window: unref(popOutWindow),
onResize: unref(onOverviewPanelResize),
onResizeend: handleResizeOverviewPanelEnd
}, {
default: withCtx(() => [
createVNode(LogsOverviewPanel, {
class: normalizeClass(_ctx.$style.logsOverview),
"is-open": unref(isOpen),
"is-read-only": _ctx.isReadOnly,
"is-compact": isLogDetailsVisuallyOpen.value,
selected: unref(selected2),
execution: unref(execution),
entries: unref(entries),
"latest-node-info": unref(latestNodeNameById),
"flat-log-entries": unref(flatLogEntries),
onClickHeader: _cache[1] || (_cache[1] = ($event) => unref(onToggleOpen)(true)),
onSelect: unref(select),
onClearExecutionData: unref(resetExecutionData),
onToggleExpanded: unref(toggleExpanded),
onOpenNdv: handleOpenNdv
}, {
actions: withCtx(() => [
!isLogDetailsVisuallyOpen.value ? (openBlock(), createBlock(LogsPanelActions, normalizeProps(mergeProps({ key: 0 }, logsPanelActionsProps.value)), null, 16)) : createCommentVNode("", true)
]),
_: 1
}, 8, ["class", "is-open", "is-read-only", "is-compact", "selected", "execution", "entries", "latest-node-info", "flat-log-entries", "onSelect", "onClearExecutionData", "onToggleExpanded"])
]),
_: 1
}, 8, ["class", "width", "style", "is-resizing-enabled", "window", "onResize"]),
isLogDetailsVisuallyOpen.value && unref(selected2) ? (openBlock(), createBlock(LogsDetailsPanel, {
key: 0,
class: normalizeClass(_ctx.$style.logDetails),
"is-open": unref(isOpen),
"log-entry": unref(selected2),
window: unref(popOutWindow),
"latest-info": unref(latestNodeNameById)[unref(selected2).node.id],
panels: unref(logsStore).detailsState,
"collapsing-input-table-column-name": inputCollapsingColumnName.value,
"collapsing-output-table-column-name": outputCollapsingColumnName.value,
onClickHeader: _cache[2] || (_cache[2] = ($event) => unref(onToggleOpen)(true)),
onToggleInputOpen: unref(logsStore).toggleInputOpen,
onToggleOutputOpen: unref(logsStore).toggleOutputOpen,
onCollapsingInputTableColumnChanged: handleChangeInputTableColumnCollapsing,
onCollapsingOutputTableColumnChanged: handleChangeOutputTableColumnCollapsing
}, {
actions: withCtx(() => [
isLogDetailsVisuallyOpen.value ? (openBlock(), createBlock(LogsPanelActions, normalizeProps(mergeProps({ key: 0 }, logsPanelActionsProps.value)), null, 16)) : createCommentVNode("", true)
]),
_: 1
}, 8, ["class", "is-open", "log-entry", "window", "latest-info", "panels", "collapsing-input-table-column-name", "collapsing-output-table-column-name", "onToggleInputOpen", "onToggleOutputOpen"])) : createCommentVNode("", true)
], 2)
], 2)
]),
_: 1
}, 8, ["height", "is-resizing-enabled", "class", "style", "onResize", "onResizeend"])
], 2)
], 512);
};
}
});
const popOutContent = "_popOutContent_1xguj_123";
const resizeWrapper = "_resizeWrapper_1xguj_129";
const poppedOut = "_poppedOut_1xguj_136";
const container = "_container_1xguj_140";
const chat = "_chat_1xguj_149";
const logsContainer = "_logsContainer_1xguj_153";
const overviewResizer = "_overviewResizer_1xguj_163";
const logsOverview = "_logsOverview_1xguj_171";
const logsDetails = "_logsDetails_1xguj_175";
const style0 = {
popOutContent,
resizeWrapper,
poppedOut,
container,
chat,
logsContainer,
overviewResizer,
logsOverview,
logsDetails
};
const cssModules = {
"$style": style0
};
const LogsPanel = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
export {
LogsPanel as default
};