UNPKG

n8n-editor-ui

Version:

Workflow Editor UI for n8n

1,558 lines 64.4 kB
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/es-DlTgUi2w.js","assets/core-6W4wWNc1.js","assets/chunk-6z4oVpB-.js","assets/languageModules-D_uyjb6R.js","assets/xml-B6veOr9z.js"])))=>i.map(i=>d[i]);
import { o as __toESM } from "./chunk-6z4oVpB-.js";
import { C as computed, Cn as toDisplayString, D as createElementBlock, E as createCommentVNode, G as nextTick, Gt as unref, It as ref, M as createVNode, P as defineComponent, T as createBlock, _ as Fragment, _t as watch, b as Teleport, bn as normalizeStyle, bt as withCtx, c as useCssModule, et as openBlock, h as withModifiers, it as renderList, j as createTextVNode, mt as useTemplateRef, q as onBeforeMount, vn as normalizeClass, w as createBaseVNode } from "./vue.runtime.esm-bundler-tP5dCd7J.js";
import { D as useElementSize, F as useScroll, L as useSpeechRecognition, M as useLocalStorage, N as useMediaQuery, R as useSpeechSynthesis, _t as useI18n, vt as I18nT } from "./_MapCache-ficiegRb.js";
import { Sn as N8nButton_default, ct as useDeviceSupport, gt as require_markdown_it_link_attributes, ht as N8nTooltip_default, mt as N8nLink_default, s as N8nScrollArea_default, wn as N8nIcon_default, xn as N8nText_default, xt as N8nIconButton_default, yn as N8nHeading_default, yt as N8nInput_default } from "./src-DgvumQTi.js";
import "./en-CF30SCh2.js";
import { t as __vitePreload } from "./preload-helper-CR0ecmWK.js";
import { t as __plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BwBpWJRZ.js";
import { v as useRoute, y as useRouter } from "./truncate-B1HVeveJ.js";
import { Dr as useUsersStore, N as useWorkflowsStore, Yn as useDocumentTitle, fs as v4_default, gs as useSettingsStore, oa as useUIStore, v as useToast } from "./builder.store-sBTWwxRU.js";
import "./empty-BuGRxzl4.js";
import "./sanitize-html-Cc45ZKm8.js";
import "./CalendarDate-zWqgZMlk.js";
import "./path-browserify-BtCDmZ3_.js";
import { Ds as LOCAL_STORAGE_CHAT_HUB_SELECTED_TOOLS, Es as LOCAL_STORAGE_CHAT_HUB_SELECTED_MODEL, Ro as VIEWS, Yr as PROVIDER_CREDENTIAL_TYPE_MAP, Zr as chatHubConversationModelSchema, pi as INodesSchema } from "./constants-BbpucWL4.js";
import "./merge-k8vSyyXH.js";
import { r as useRootStore } from "./_baseOrderBy-B_6CV8x6.js";
import "./dateformat-CM9k0--B.js";
import "./useDebounce-Bnb8W2LR.js";
import "./retry-dP46utx2.js";
import "./nodeIcon-DI2Acud1.js";
import "./NodeIcon-BVhrZNYI.js";
import { t as useClipboard } from "./useClipboard-CyG-SBdG.js";
import "./CredentialIcon-FEnE25SA.js";
import { t as VueMarkdown_default } from "./VueMarkdown-D9lDzMKr.js";
import { f as MOBILE_MEDIA_QUERY, m as providerDisplayNames, n as CHAT_CONVERSATION_VIEW, p as TOOLS_SELECTOR_MODAL_KEY, t as AGENT_EDITOR_MODAL_KEY, u as CHAT_VIEW } from "./constants-DLTaaipl.js";
import { h as buildChatAttachmentUrl, i as findOneFromModelsResponse, l as isLlmProvider, n as createMimeTypes, p as unflattenModel, t as useChatStore, u as isLlmProviderModel } from "./chat.store-BGFxJU_3.js";
import "./fileUtils-D_SiS8no.js";
import { n as ToolsSelector_default, r as ModelSelector_default, t as useCustomAgent } from "./useCustomAgent-DBhdLpPp.js";
import { t as ChatAgentAvatar_default } from "./ChatAgentAvatar-DMLTyAJz.js";
import { n as useChatCredentials, r as chatHubConversationModelWithCachedDisplayNameSchema, t as ChatLayout_default } from "./ChatLayout-CIrtU9sd.js";
import { t as ChatFile_default } from "./ChatFile-Dtf_b24Y.js";
var ChatConversationHeader_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
	__name: "ChatConversationHeader",
	props: {
		selectedModel: {},
		credentials: {},
		readyToShowModelSelector: { type: Boolean }
	},
	emits: [
		"selectModel",
		"renameConversation",
		"editCustomAgent",
		"createCustomAgent",
		"selectCredential",
		"openWorkflow"
	],
	setup(__props, { expose: __expose, emit: __emit }) {
		const emit = __emit;
		const modelSelectorRef = useTemplateRef("modelSelectorRef");
		const i18n = useI18n();
		const chatStore = useChatStore();
		const isLoadingAgents = ref(false);
		const showOpenWorkflow = computed(() => {
			return __props.selectedModel?.model.provider === "n8n" && __props.selectedModel.metadata.scopes?.includes("workflow:read");
		});
		function onOpenWorkflow() {
			if (__props.selectedModel?.model.provider === "n8n") emit("openWorkflow", __props.selectedModel.model.workflowId);
		}
		function onModelChange(selection) {
			emit("selectModel", selection);
		}
		watch(() => __props.credentials, async (creds) => {
			if (creds) {
				isLoadingAgents.value = true;
				try {
					await chatStore.fetchAgents(creds);
				} finally {
					isLoadingAgents.value = false;
				}
			}
		}, { immediate: true });
		__expose({
			openModelSelector: () => modelSelectorRef.value?.open(),
			openCredentialSelector: (provider) => modelSelectorRef.value?.openCredentialSelector(provider)
		});
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.component) }, [
				createBaseVNode("div", { class: normalizeClass(_ctx.$style.grow) }, [_ctx.readyToShowModelSelector ? (openBlock(), createBlock(ModelSelector_default, {
					key: 0,
					ref_key: "modelSelectorRef",
					ref: modelSelectorRef,
					"selected-agent": _ctx.selectedModel,
					credentials: _ctx.credentials,
					text: "",
					agents: unref(chatStore).agents,
					"is-loading": isLoadingAgents.value,
					onChange: onModelChange,
					onCreateCustomAgent: _cache[0] || (_cache[0] = ($event) => emit("createCustomAgent")),
					onSelectCredential: _cache[1] || (_cache[1] = (provider, credentialId) => emit("selectCredential", provider, credentialId))
				}, null, 8, [
					"selected-agent",
					"credentials",
					"agents",
					"is-loading"
				])) : createCommentVNode("", true)], 2),
				_ctx.selectedModel?.model.provider === "custom-agent" ? (openBlock(), createBlock(unref(N8nButton_default), {
					key: 0,
					class: normalizeClass(_ctx.$style.editAgent),
					type: "secondary",
					size: "small",
					icon: "settings",
					label: unref(i18n).baseText("chatHub.chat.header.button.editAgent"),
					onClick: _cache[2] || (_cache[2] = ($event) => emit("editCustomAgent", _ctx.selectedModel.model.agentId))
				}, null, 8, ["class", "label"])) : createCommentVNode("", true),
				showOpenWorkflow.value ? (openBlock(), createBlock(unref(N8nButton_default), {
					key: 1,
					class: normalizeClass(_ctx.$style.editAgent),
					type: "secondary",
					size: "small",
					icon: "settings",
					label: unref(i18n).baseText("chatHub.chat.header.button.openWorkflow"),
					onClick: onOpenWorkflow
				}, null, 8, ["class", "label"])) : createCommentVNode("", true)
			], 2);
		};
	}
});
var ChatConversationHeader_vue_vue_type_style_index_0_lang_module_default = {
	component: "_component_1rkfw_123",
	menuButton: "_menuButton_1rkfw_133",
	grow: "_grow_1rkfw_137",
	title: "_title_1rkfw_144",
	editAgent: "_editAgent_1rkfw_148"
};
var ChatConversationHeader_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ChatConversationHeader_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ChatConversationHeader_vue_vue_type_style_index_0_lang_module_default }]]);
var ChatTypingIndicator_vue_vue_type_style_index_0_lang_module_default = {
	typing: "_typing_9qr9t_123",
	blink: "_blink_9qr9t_1"
};
var _sfc_main = {};
function _sfc_render(_ctx, _cache) {
	return openBlock(), createElementBlock("span", {
		class: normalizeClass(_ctx.$style.typing),
		"data-test-id": "chat-typing-indicator"
	}, _cache[0] || (_cache[0] = [
		createBaseVNode("i", null, null, -1),
		createBaseVNode("i", null, null, -1),
		createBaseVNode("i", null, null, -1)
	]), 2);
}
var ChatTypingIndicator_default = /* @__PURE__ */ __plugin_vue_export_helper_default(_sfc_main, [["render", _sfc_render], ["__cssModules", { "$style": ChatTypingIndicator_vue_vue_type_style_index_0_lang_module_default }]]);
var import_markdown_it_link_attributes = /* @__PURE__ */ __toESM(require_markdown_it_link_attributes());
var hljsInstance;
var asyncImport = { status: "uninitialized" };
function useChatHubMarkdownOptions(codeBlockActionsClassName, tableContainerClassName) {
	const forceReRenderKey = ref(0);
	const codeBlockContents = ref();
	const options = { highlight(str, lang) {
		if (!lang) return "";
		const normalizedLang = lang.toLowerCase();
		if (hljsInstance?.getLanguage(normalizedLang)) try {
			return hljsInstance.highlight(str, { language: normalizedLang }).value;
		} catch {}
		loadLanguageModules();
		return "";
	} };
	async function loadLanguageModules() {
		if (asyncImport.status === "done") return;
		if (asyncImport.status === "inProgress") {
			await asyncImport.promise;
			forceReRenderKey.value++;
			return;
		}
		try {
			asyncImport = {
				status: "inProgress",
				promise: Promise.all([__vitePreload(() => import("./es-DlTgUi2w.js"), __vite__mapDeps([0,1,2])), __vitePreload(() => import("./languageModules-D_uyjb6R.js"), __vite__mapDeps([3,4]))])
			};
			const [hljs, languages] = await asyncImport.promise;
			asyncImport = { status: "done" };
			hljsInstance = hljs.default.newInstance();
			for (const [lang, module] of Object.entries(languages)) hljsInstance.registerLanguage(lang, module);
			forceReRenderKey.value++;
		} catch (error) {
			console.warn("Failed to load syntax highlighting modules", error);
		}
	}
	return {
		options,
		forceReRenderKey,
		plugins: computed(() => {
			const linksNewTabPlugin = (vueMarkdownItInstance) => {
				vueMarkdownItInstance.use(import_markdown_it_link_attributes.default, { attrs: {
					target: "_blank",
					rel: "noopener"
				} });
			};
			const codeBlockPlugin = (vueMarkdownItInstance) => {
				const defaultFenceRenderer = vueMarkdownItInstance.renderer.rules.fence;
				codeBlockContents.value = /* @__PURE__ */ new Map();
				vueMarkdownItInstance.renderer.rules.fence = (tokens, idx, options$1, env, self) => {
					const defaultRendered = defaultFenceRenderer?.(tokens, idx, options$1, env, self) ?? self.renderToken(tokens, idx, options$1);
					const content$1 = tokens[idx]?.content.trim();
					if (content$1) codeBlockContents.value?.set(String(idx), content$1);
					return defaultRendered.replace("</pre>", `<div data-markdown-token-idx="${idx}" class="${codeBlockActionsClassName}"></div></pre>`);
				};
			};
			const tablePlugin = (vueMarkdownItInstance) => {
				const defaultTableOpenRenderer = vueMarkdownItInstance.renderer.rules.table_open;
				const defaultTableCloseRenderer = vueMarkdownItInstance.renderer.rules.table_close;
				vueMarkdownItInstance.renderer.rules.table_open = (tokens, idx, options$1, env, self) => {
					return (defaultTableOpenRenderer?.(tokens, idx, options$1, env, self) ?? self.renderToken(tokens, idx, options$1)).replace("<table", `<div class="${tableContainerClassName}"><table`);
				};
				vueMarkdownItInstance.renderer.rules.table_close = (tokens, idx, options$1, env, self) => {
					return (defaultTableCloseRenderer?.(tokens, idx, options$1, env, self) ?? self.renderToken(tokens, idx, options$1)).replace("</table>", "</table></div>");
				};
			};
			return [
				linksNewTabPlugin,
				codeBlockPlugin,
				tablePlugin
			];
		}),
		codeBlockContents
	};
}
var CopyButton_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
	__name: "CopyButton",
	props: { content: {} },
	setup(__props) {
		const i18n = useI18n();
		const clipboard = useClipboard();
		const justCopied = ref(false);
		const copyTooltip = computed(() => {
			return justCopied.value ? i18n.baseText("generic.copied") : i18n.baseText("generic.copy");
		});
		async function handleCopy() {
			await clipboard.copy(__props.content);
			justCopied.value = true;
			setTimeout(() => {
				justCopied.value = false;
			}, 1e3);
		}
		return (_ctx, _cache) => {
			return openBlock(), createBlock(unref(N8nTooltip_default), {
				placement: "bottom",
				"show-after": 300
			}, {
				content: withCtx(() => [createTextVNode(toDisplayString(copyTooltip.value), 1)]),
				default: withCtx(() => [createVNode(unref(N8nIconButton_default), {
					icon: justCopied.value ? "check" : "copy",
					type: "tertiary",
					size: "medium",
					text: "",
					class: normalizeClass(_ctx.$style.button),
					tabindex: "0",
					onClick: handleCopy
				}, null, 8, ["icon", "class"])]),
				_: 1
			});
		};
	}
});
var CopyButton_vue_vue_type_style_index_0_lang_module_default = { button: "_button_9m8dh_123" };
var CopyButton_default = /* @__PURE__ */ __plugin_vue_export_helper_default(CopyButton_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": CopyButton_vue_vue_type_style_index_0_lang_module_default }]]);
var ChatMessageActions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
	__name: "ChatMessageActions",
	props: {
		message: {},
		isSpeechSynthesisAvailable: { type: Boolean },
		isSpeaking: { type: Boolean },
		hasSessionStreaming: { type: Boolean }
	},
	emits: [
		"edit",
		"regenerate",
		"switchAlternative",
		"readAloud"
	],
	setup(__props, { emit: __emit }) {
		const i18n = useI18n();
		const router = useRouter();
		const workflowsStore = useWorkflowsStore();
		const emit = __emit;
		const currentAlternativeIndex = computed(() => {
			return __props.message.alternatives.findIndex((id) => id === __props.message.id);
		});
		const executionUrl = computed(() => {
			if (workflowsStore.canViewWorkflows && __props.message.type === "ai" && __props.message.provider === "n8n" && __props.message.executionId) return router.resolve({
				name: VIEWS.EXECUTION_PREVIEW,
				params: {
					name: __props.message.workflowId,
					executionId: __props.message.executionId
				}
			}).href;
		});
		function handleEdit() {
			emit("edit");
		}
		function handleRegenerate() {
			emit("regenerate");
		}
		function handleReadAloud() {
			emit("readAloud");
		}
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.actions) }, [
				createVNode(CopyButton_default, {
					content: _ctx.message.content,
					"data-test-id": "chat-message-copy"
				}, null, 8, ["content"]),
				_ctx.isSpeechSynthesisAvailable && _ctx.message.type === "ai" ? (openBlock(), createBlock(unref(N8nTooltip_default), {
					key: 0,
					placement: "bottom",
					"show-after": 300
				}, {
					content: withCtx(() => [createTextVNode(toDisplayString(_ctx.isSpeaking ? unref(i18n).baseText("chatHub.message.actions.stopReading") : unref(i18n).baseText("chatHub.message.actions.readAloud")), 1)]),
					default: withCtx(() => [createVNode(unref(N8nIconButton_default), {
						icon: _ctx.isSpeaking ? "volume-x" : "volume-2",
						type: "tertiary",
						size: "medium",
						text: "",
						onClick: handleReadAloud
					}, null, 8, ["icon"])]),
					_: 1
				})) : createCommentVNode("", true),
				_ctx.message.status === "success" ? (openBlock(), createBlock(unref(N8nTooltip_default), {
					key: 1,
					placement: "bottom",
					"show-after": 300
				}, {
					content: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("chatHub.message.actions.edit")), 1)]),
					default: withCtx(() => [createVNode(unref(N8nIconButton_default), {
						icon: "pen",
						type: "tertiary",
						size: "medium",
						text: "",
						"data-test-id": "chat-message-edit",
						disabled: _ctx.hasSessionStreaming,
						onClick: handleEdit
					}, null, 8, ["disabled"])]),
					_: 1
				})) : createCommentVNode("", true),
				_ctx.message.type === "ai" ? (openBlock(), createBlock(unref(N8nTooltip_default), {
					key: 2,
					placement: "bottom",
					"show-after": 300
				}, {
					content: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("chatHub.message.actions.regenerate")), 1)]),
					default: withCtx(() => [createVNode(unref(N8nIconButton_default), {
						icon: "refresh-cw",
						type: "tertiary",
						size: "medium",
						text: "",
						"data-test-id": "chat-message-regenerate",
						disabled: _ctx.hasSessionStreaming,
						onClick: handleRegenerate
					}, null, 8, ["disabled"])]),
					_: 1
				})) : createCommentVNode("", true),
				executionUrl.value ? (openBlock(), createBlock(unref(N8nTooltip_default), {
					key: 3,
					placement: "bottom",
					"show-after": 300
				}, {
					content: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("chatHub.message.actions.executionId")) + ": ", 1), createVNode(unref(N8nLink_default), {
						to: executionUrl.value,
						"new-window": true
					}, {
						default: withCtx(() => [createTextVNode(toDisplayString(_ctx.message.executionId), 1)]),
						_: 1
					}, 8, ["to"])]),
					default: withCtx(() => [createVNode(unref(N8nIconButton_default), {
						icon: "info",
						type: "tertiary",
						size: "medium",
						text: "",
						"data-test-id": "chat-message-info"
					})]),
					_: 1
				})) : createCommentVNode("", true),
				_ctx.message.alternatives.length > 1 ? (openBlock(), createElementBlock(Fragment, { key: 4 }, [
					createVNode(unref(N8nIconButton_default), {
						icon: "chevron-left",
						type: "tertiary",
						size: "medium",
						text: "",
						disabled: _ctx.hasSessionStreaming || currentAlternativeIndex.value === 0,
						"data-test-id": "chat-message-prev-alternative",
						onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("switchAlternative", _ctx.message.alternatives[currentAlternativeIndex.value - 1]))
					}, null, 8, ["disabled"]),
					createVNode(unref(N8nText_default), {
						size: "medium",
						color: "text-base"
					}, {
						default: withCtx(() => [createTextVNode(toDisplayString(`${currentAlternativeIndex.value + 1}/${_ctx.message.alternatives.length}`), 1)]),
						_: 1
					}),
					createVNode(unref(N8nIconButton_default), {
						icon: "chevron-right",
						type: "tertiary",
						size: "medium",
						text: "",
						disabled: _ctx.hasSessionStreaming || currentAlternativeIndex.value === _ctx.message.alternatives.length - 1,
						"data-test-id": "chat-message-next-alternative",
						onClick: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("switchAlternative", _ctx.message.alternatives[currentAlternativeIndex.value + 1]))
					}, null, 8, ["disabled"])
				], 64)) : createCommentVNode("", true)
			], 2);
		};
	}
});
var ChatMessageActions_vue_vue_type_style_index_0_lang_module_default = { actions: "_actions_1sizk_123" };
var ChatMessageActions_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ChatMessageActions_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ChatMessageActions_vue_vue_type_style_index_0_lang_module_default }], ["__scopeId", "data-v-28c70984"]]);
var _hoisted_1$1 = ["data-message-id", "data-test-id"];
var _hoisted_2 = { key: 2 };
var _hoisted_3 = { key: 1 };
var ChatMessage_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
	__name: "ChatMessage",
	props: {
		message: {},
		compact: { type: Boolean },
		isEditing: { type: Boolean },
		isEditSubmitting: { type: Boolean },
		hasSessionStreaming: { type: Boolean },
		cachedAgentDisplayName: {},
		cachedAgentIcon: {},
		minHeight: {},
		containerWidth: {}
	},
	emits: [
		"startEdit",
		"cancelEdit",
		"update",
		"regenerate",
		"switchAlternative"
	],
	setup(__props, { expose: __expose, emit: __emit }) {
		const emit = __emit;
		const chatStore = useChatStore();
		const rootStore = useRootStore();
		const { isCtrlKeyPressed } = useDeviceSupport();
		const i18n = useI18n();
		const styles = useCssModule();
		const editedText = ref("");
		const newFiles = ref([]);
		const removedExistingIndices = ref(/* @__PURE__ */ new Set());
		const fileInputRef = useTemplateRef("fileInputRef");
		const hoveredCodeBlockActions = ref(null);
		const textareaRef = useTemplateRef("textarea");
		const markdown = useChatHubMarkdownOptions(styles.codeBlockActions, styles.tableContainer);
		const speech = useSpeechSynthesis(computed(() => __props.message.content), {
			pitch: 1,
			rate: 1,
			volume: 1
		});
		const agent = computed(() => {
			const model = unflattenModel(__props.message);
			if (!model) return null;
			return chatStore.getAgent(model, {
				name: __props.cachedAgentDisplayName,
				icon: __props.cachedAgentIcon
			});
		});
		const attachments$2 = computed(() => __props.message.attachments.map(({ fileName, mimeType }, index) => ({
			file: new File([], fileName ?? "file", { type: mimeType }),
			downloadUrl: buildChatAttachmentUrl(rootStore.restApiContext, __props.message.sessionId, __props.message.id, index)
		})));
		const mergedAttachments = computed(() => [...attachments$2.value.flatMap(({ downloadUrl, file }, idx) => removedExistingIndices.value.has(idx) ? [] : [{
			isNew: false,
			file,
			index: idx,
			downloadUrl
		}]), ...newFiles.value.map((file, index) => ({
			isNew: true,
			file,
			index
		}))]);
		const hideMessage = computed(() => {
			return __props.message.status === "success" && __props.message.content === "";
		});
		const hoveredCodeBlockContent = computed(() => {
			const idx = hoveredCodeBlockActions.value?.getAttribute("data-markdown-token-idx");
			return idx ? markdown.codeBlockContents.value?.get(idx) : void 0;
		});
		function handleEdit() {
			emit("startEdit");
		}
		function handleCancelEdit() {
			emit("cancelEdit");
		}
		function handleConfirmEdit() {
			if (!editedText.value.trim()) return;
			const keptAttachmentIndices = __props.message.attachments.map((_, idx) => idx).filter((idx) => !removedExistingIndices.value.has(idx));
			emit("update", editedText.value, keptAttachmentIndices, newFiles.value);
		}
		function handleAttachClick() {
			fileInputRef.value?.click();
		}
		function handleFileSelect(e) {
			const target = e.target;
			const files = target.files;
			if (!files || files.length === 0) return;
			for (const file of Array.from(files)) newFiles.value.push(file);
			if (target) target.value = "";
		}
		function handleRemoveFile(file) {
			if (file.isNew) {
				newFiles.value = newFiles.value.filter((_, idx) => idx !== file.index);
				return;
			}
			removedExistingIndices.value.add(file.index);
		}
		function addFiles(files) {
			for (const file of files) newFiles.value.push(file);
		}
		__expose({ addFiles });
		function handleKeydownTextarea(e) {
			if (e.key === "Escape") {
				emit("cancelEdit");
				return;
			}
			const trimmed = editedText.value.trim();
			if (e.key === "Enter" && isCtrlKeyPressed(e) && !e.isComposing && trimmed) {
				e.preventDefault();
				handleConfirmEdit();
			}
		}
		function handleRegenerate() {
			emit("regenerate", __props.message);
		}
		function handleReadAloud() {
			if (speech.isSupported.value) if (speech.isPlaying.value) speech.stop();
			else speech.speak();
		}
		function handleSwitchAlternative(messageId) {
			emit("switchAlternative", messageId);
		}
		function handleMouseMove(e) {
			const container = e.target instanceof HTMLElement || e.target instanceof SVGElement ? e.target.closest("pre")?.querySelector(`.${styles.codeBlockActions}`) : null;
			hoveredCodeBlockActions.value = container instanceof HTMLElement ? container : null;
		}
		function handleMouseLeave() {
			hoveredCodeBlockActions.value = null;
		}
		watch(() => __props.isEditing, (editing) => {
			editedText.value = editing ? __props.message.content : "";
			newFiles.value = [];
			removedExistingIndices.value = /* @__PURE__ */ new Set();
		}, { immediate: true });
		watch(textareaRef, async (textarea$1) => {
			if (textarea$1) {
				await new Promise((r) => setTimeout(r, 0));
				textarea$1.focus();
				textarea$1.$el.scrollIntoView({ block: "nearest" });
			}
		}, {
			immediate: true,
			flush: "post"
		});
		onBeforeMount(() => {
			speech.stop();
		});
		return (_ctx, _cache) => {
			return !hideMessage.value ? (openBlock(), createElementBlock("div", {
				key: 0,
				class: normalizeClass([
					_ctx.$style.message,
					_ctx.message.type === "human" ? _ctx.$style.user : _ctx.$style.assistant,
					{ [_ctx.$style.compact]: _ctx.compact }
				]),
				style: normalizeStyle({
					minHeight: _ctx.minHeight ? `${_ctx.minHeight}px` : void 0,
					"--container--width": `${_ctx.containerWidth}px`
				}),
				"data-message-id": _ctx.message.id,
				"data-test-id": `chat-message-${_ctx.message.id}`
			}, [
				createBaseVNode("div", { class: normalizeClass(_ctx.$style.avatar) }, [_ctx.message.type === "human" ? (openBlock(), createBlock(unref(N8nIcon_default), {
					key: 0,
					icon: "user",
					width: "20",
					height: "20"
				})) : (openBlock(), createBlock(ChatAgentAvatar_default, {
					key: 1,
					agent: agent.value,
					size: "md",
					tooltip: ""
				}, null, 8, ["agent"]))], 2),
				createBaseVNode("div", { class: normalizeClass(_ctx.$style.content) }, [_ctx.message.type === "human" ? (openBlock(), createElementBlock("input", {
					key: 0,
					ref_key: "fileInputRef",
					ref: fileInputRef,
					type: "file",
					"data-test-id": "message-edit-file-input",
					class: normalizeClass(_ctx.$style.fileInput),
					multiple: "",
					onChange: handleFileSelect
				}, null, 34)) : createCommentVNode("", true), _ctx.isEditing ? (openBlock(), createElementBlock("div", {
					key: 1,
					class: normalizeClass(_ctx.$style.editContainer)
				}, [
					_ctx.message.type === "human" && mergedAttachments.value.length > 0 ? (openBlock(), createElementBlock("div", {
						key: 0,
						class: normalizeClass(_ctx.$style.attachments)
					}, [(openBlock(true), createElementBlock(Fragment, null, renderList(mergedAttachments.value, (attachment, index) => {
						return openBlock(), createBlock(ChatFile_default, {
							key: index,
							file: attachment.file,
							"is-removable": "",
							href: attachment.isNew ? void 0 : attachment.downloadUrl,
							onRemove: ($event) => handleRemoveFile(attachment)
						}, null, 8, [
							"file",
							"href",
							"onRemove"
						]);
					}), 128))], 2)) : createCommentVNode("", true),
					createVNode(unref(N8nInput_default), {
						ref: "textarea",
						modelValue: editedText.value,
						"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => editedText.value = $event),
						type: "textarea",
						autosize: {
							minRows: 1,
							maxRows: 20
						},
						class: normalizeClass(_ctx.$style.textarea),
						onKeydown: handleKeydownTextarea
					}, null, 8, ["modelValue", "class"]),
					createBaseVNode("div", { class: normalizeClass(_ctx.$style.editFooter) }, [_ctx.message.type === "human" ? (openBlock(), createBlock(unref(N8nIconButton_default), {
						key: 0,
						"native-type": "button",
						type: "secondary",
						icon: "paperclip",
						text: "",
						onClick: withModifiers(handleAttachClick, ["stop"])
					})) : createCommentVNode("", true), createBaseVNode("div", { class: normalizeClass(_ctx.$style.editActions) }, [createVNode(unref(N8nButton_default), {
						type: "secondary",
						size: "small",
						onClick: handleCancelEdit
					}, {
						default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("chatHub.message.edit.cancel")), 1)]),
						_: 1
					}), createVNode(unref(N8nButton_default), {
						type: "primary",
						size: "small",
						disabled: !editedText.value.trim() || _ctx.isEditSubmitting,
						loading: _ctx.isEditSubmitting,
						onClick: handleConfirmEdit
					}, {
						default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("chatHub.message.edit.send")), 1)]),
						_: 1
					}, 8, ["disabled", "loading"])], 2)], 2)
				], 2)) : (openBlock(), createElementBlock("div", _hoisted_2, [createBaseVNode("div", {
					class: normalizeClass([_ctx.$style.chatMessage, { [_ctx.$style.errorMessage]: _ctx.message.status === "error" }]),
					onMousemove: handleMouseMove,
					onMouseleave: handleMouseLeave
				}, [attachments$2.value.length > 0 ? (openBlock(), createElementBlock("div", {
					key: 0,
					class: normalizeClass(_ctx.$style.attachments)
				}, [(openBlock(true), createElementBlock(Fragment, null, renderList(attachments$2.value, (attachment, index) => {
					return openBlock(), createBlock(ChatFile_default, {
						key: index,
						file: attachment.file,
						"is-removable": false,
						href: attachment.downloadUrl
					}, null, 8, ["file", "href"]);
				}), 128))], 2)) : createCommentVNode("", true), _ctx.message.type === "human" ? (openBlock(), createElementBlock("div", _hoisted_3, toDisplayString(_ctx.message.content), 1)) : (openBlock(), createBlock(unref(VueMarkdown_default), {
					key: unref(markdown).forceReRenderKey.value,
					class: normalizeClass([_ctx.$style.chatMessageMarkdown, "chat-message-markdown"]),
					source: _ctx.message.status === "error" && !_ctx.message.content ? unref(i18n).baseText("chatHub.message.error.unknown") : _ctx.message.content,
					options: unref(markdown).options,
					plugins: unref(markdown).plugins.value
				}, null, 8, [
					"class",
					"source",
					"options",
					"plugins"
				]))], 34), _ctx.message.status === "running" ? (openBlock(), createBlock(ChatTypingIndicator_default, {
					key: 0,
					class: normalizeClass(_ctx.$style.typingIndicator)
				}, null, 8, ["class"])) : (openBlock(), createBlock(ChatMessageActions_default, {
					key: 1,
					"is-speech-synthesis-available": unref(speech).isSupported.value,
					"is-speaking": unref(speech).isPlaying.value,
					class: normalizeClass(_ctx.$style.actions),
					message: _ctx.message,
					"has-session-streaming": _ctx.hasSessionStreaming,
					onEdit: handleEdit,
					onRegenerate: handleRegenerate,
					onReadAloud: handleReadAloud,
					onSwitchAlternative: handleSwitchAlternative
				}, null, 8, [
					"is-speech-synthesis-available",
					"is-speaking",
					"class",
					"message",
					"has-session-streaming"
				]))]))], 2),
				hoveredCodeBlockActions.value && hoveredCodeBlockContent.value ? (openBlock(), createBlock(Teleport, {
					key: 0,
					to: hoveredCodeBlockActions.value
				}, [createVNode(CopyButton_default, { content: hoveredCodeBlockContent.value }, null, 8, ["content"])], 8, ["to"])) : createCommentVNode("", true)
			], 14, _hoisted_1$1)) : createCommentVNode("", true);
		};
	}
});
var ChatMessage_vue_vue_type_style_index_0_lang_module_default = {
	message: "_message_o9qnd_123",
	avatar: "_avatar_o9qnd_128",
	compact: "_compact_o9qnd_141",
	content: "_content_o9qnd_146",
	attachments: "_attachments_o9qnd_151",
	chatMessage: "_chatMessage_o9qnd_156",
	user: "_user_o9qnd_170",
	errorMessage: "_errorMessage_o9qnd_177",
	chatMessageMarkdown: "_chatMessageMarkdown_o9qnd_185",
	codeBlockActions: "_codeBlockActions_o9qnd_250",
	tableContainer: "_tableContainer_o9qnd_259",
	actions: "_actions_o9qnd_288",
	editContainer: "_editContainer_o9qnd_292",
	textarea: "_textarea_o9qnd_307",
	fileInput: "_fileInput_o9qnd_320",
	editFooter: "_editFooter_o9qnd_324",
	editActions: "_editActions_o9qnd_330",
	typingIndicator: "_typingIndicator_o9qnd_336"
};
var ChatMessage_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ChatMessage_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ChatMessage_vue_vue_type_style_index_0_lang_module_default }]]);
var _hoisted_1 = ["accept"];
var ChatPrompt_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
	__name: "ChatPrompt",
	props: {
		messagingState: {},
		isNewSession: { type: Boolean },
		isToolsSelectable: { type: Boolean },
		selectedModel: {},
		selectedTools: {}
	},
	emits: [
		"submit",
		"stop",
		"selectModel",
		"selectTools",
		"setCredentials",
		"editAgent"
	],
	setup(__props, { expose: __expose, emit: __emit }) {
		const emit = __emit;
		const inputRef = useTemplateRef("inputRef");
		const fileInputRef = useTemplateRef("fileInputRef");
		const message$1 = ref("");
		const committedSpokenMessage = ref("");
		const attachments$2 = ref([]);
		const toast = useToast();
		const i18n = useI18n();
		const uiStore = useUIStore();
		const speechInput = useSpeechRecognition({
			continuous: true,
			interimResults: true,
			lang: navigator.language
		});
		const placeholder = computed(() => {
			if (__props.selectedModel) return i18n.baseText("chatHub.chat.prompt.placeholder.withModel", { interpolate: { model: __props.selectedModel.name ?? "a model" } });
			return i18n.baseText("chatHub.chat.prompt.placeholder.selectModel");
		});
		const llmProvider = computed(() => isLlmProviderModel(__props.selectedModel?.model) ? __props.selectedModel?.model.provider : void 0);
		const acceptedMimeTypes = computed(() => createMimeTypes(__props.selectedModel?.metadata.inputModalities ?? []));
		const canUploadFiles = computed(() => !!acceptedMimeTypes.value);
		function onMic() {
			committedSpokenMessage.value = message$1.value;
			if (speechInput.isListening.value) speechInput.stop();
			else speechInput.start();
		}
		function onStop() {
			emit("stop");
		}
		function onAttach() {
			fileInputRef.value?.click();
		}
		function handleFileSelect(e) {
			const target = e.target;
			const files = target.files;
			if (!files || files.length === 0) return;
			for (const file of Array.from(files)) attachments$2.value.push(file);
			if (target) target.value = "";
			inputRef.value?.focus();
		}
		function removeAttachment(removed) {
			attachments$2.value = attachments$2.value.filter((attachment) => attachment !== removed);
		}
		function handleSubmitForm() {
			const trimmed = message$1.value.trim();
			if (trimmed) {
				speechInput.stop();
				emit("submit", trimmed, attachments$2.value);
			}
		}
		function handleKeydownTextarea(e) {
			const trimmed = message$1.value.trim();
			speechInput.stop();
			if (e.key === "Enter" && !e.shiftKey && !e.isComposing && trimmed) {
				e.preventDefault();
				speechInput.stop();
				emit("submit", trimmed, attachments$2.value);
			}
		}
		function handleClickInputWrapper() {
			inputRef.value?.focus();
		}
		watch(speechInput.result, (spoken) => {
			message$1.value = committedSpokenMessage.value + " " + spoken.trimStart();
		});
		watch(speechInput.isFinal, (final) => {
			if (final) committedSpokenMessage.value = message$1.value;
		}, { flush: "post" });
		watch(speechInput.error, (event) => {
			if (event?.error === "not-allowed") {
				toast.showError(new Error(i18n.baseText("chatHub.chat.prompt.microphone.accessDenied")), i18n.baseText("chatHub.chat.prompt.microphone.allowAccess"));
				return;
			}
			if (event?.error === "no-speech") toast.showMessage({
				title: i18n.baseText("chatHub.chat.prompt.microphone.noSpeech"),
				type: "warning"
			});
		});
		function onSelectTools() {
			if (__props.selectedModel?.model.provider === "custom-agent") {
				emit("editAgent", __props.selectedModel.model.agentId);
				return;
			}
			uiStore.openModalWithData({
				name: TOOLS_SELECTOR_MODAL_KEY,
				data: {
					selected: __props.selectedTools,
					onConfirm: (newTools) => emit("selectTools", newTools)
				}
			});
		}
		__expose({
			focus: () => inputRef.value?.focus(),
			reset: () => {
				message$1.value = "";
				committedSpokenMessage.value = "";
				attachments$2.value = [];
			},
			setText: (text) => {
				message$1.value = text;
			},
			addAttachments: (files) => {
				attachments$2.value.push(...files);
				inputRef.value?.focus();
			}
		});
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("form", {
				class: normalizeClass(_ctx.$style.prompt),
				onSubmit: withModifiers(handleSubmitForm, ["prevent"])
			}, [createBaseVNode("div", { class: normalizeClass(_ctx.$style.inputWrap) }, [
				_ctx.messagingState === "missingAgent" ? (openBlock(), createBlock(unref(N8nText_default), {
					key: 0,
					class: normalizeClass(_ctx.$style.callout)
				}, {
					default: withCtx(() => [createVNode(unref(I18nT), {
						keypath: _ctx.isNewSession ? "chatHub.chat.prompt.callout.selectModel.new" : "chatHub.chat.prompt.callout.selectModel.existing",
						tag: "span",
						scope: "global"
					}, {
						link: withCtx(() => [createBaseVNode("a", {
							href: "",
							onClick: _cache[0] || (_cache[0] = withModifiers(($event) => emit("selectModel"), ["prevent"]))
						}, toDisplayString(unref(i18n).baseText(_ctx.isNewSession ? "chatHub.chat.prompt.callout.selectModel.new.link" : "chatHub.chat.prompt.callout.selectModel.existing.link")), 1)]),
						_: 1
					}, 8, ["keypath"])]),
					_: 1
				}, 8, ["class"])) : _ctx.messagingState === "missingCredentials" && llmProvider.value ? (openBlock(), createBlock(unref(N8nText_default), {
					key: 1,
					class: normalizeClass(_ctx.$style.callout)
				}, {
					default: withCtx(() => [createVNode(unref(I18nT), {
						keypath: _ctx.isNewSession ? "chatHub.chat.prompt.callout.setCredentials.new" : "chatHub.chat.prompt.callout.setCredentials.existing",
						tag: "span",
						scope: "global"
					}, {
						link: withCtx(() => [createBaseVNode("a", {
							href: "",
							onClick: _cache[1] || (_cache[1] = withModifiers(($event) => emit("setCredentials", llmProvider.value), ["prevent"]))
						}, toDisplayString(unref(i18n).baseText(_ctx.isNewSession ? "chatHub.chat.prompt.callout.setCredentials.new.link" : "chatHub.chat.prompt.callout.setCredentials.existing.link")), 1)]),
						provider: withCtx(() => [createTextVNode(toDisplayString(unref(providerDisplayNames)[llmProvider.value]), 1)]),
						_: 1
					}, 8, ["keypath"])]),
					_: 1
				}, 8, ["class"])) : createCommentVNode("", true),
				createBaseVNode("input", {
					ref_key: "fileInputRef",
					ref: fileInputRef,
					type: "file",
					class: normalizeClass(_ctx.$style.fileInput),
					accept: acceptedMimeTypes.value,
					multiple: "",
					onChange: handleFileSelect
				}, null, 42, _hoisted_1),
				createBaseVNode("div", {
					class: normalizeClass(_ctx.$style.inputWrapper),
					onClick: handleClickInputWrapper
				}, [
					attachments$2.value.length > 0 ? (openBlock(), createElementBlock("div", {
						key: 0,
						class: normalizeClass(_ctx.$style.attachments)
					}, [(openBlock(true), createElementBlock(Fragment, null, renderList(attachments$2.value, (file, index) => {
						return openBlock(), createBlock(ChatFile_default, {
							key: index,
							file,
							"is-previewable": true,
							"is-removable": _ctx.messagingState === "idle",
							onRemove: removeAttachment
						}, null, 8, ["file", "is-removable"]);
					}), 128))], 2)) : createCommentVNode("", true),
					createVNode(unref(N8nInput_default), {
						ref_key: "inputRef",
						ref: inputRef,
						modelValue: message$1.value,
						"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => message$1.value = $event),
						type: "textarea",
						placeholder: placeholder.value,
						autocomplete: "off",
						autosize: {
							minRows: 1,
							maxRows: 6
						},
						autofocus: "",
						disabled: _ctx.messagingState !== "idle",
						onKeydown: handleKeydownTextarea
					}, null, 8, [
						"modelValue",
						"placeholder",
						"disabled"
					]),
					createBaseVNode("div", { class: normalizeClass(_ctx.$style.footer) }, [createBaseVNode("div", { class: normalizeClass(_ctx.$style.tools) }, [createVNode(ToolsSelector_default, {
						class: normalizeClass(_ctx.$style.toolsButton),
						selected: _ctx.selectedTools ?? [],
						disabled: _ctx.messagingState !== "idle" || !_ctx.isToolsSelectable,
						"disabled-tooltip": _ctx.isToolsSelectable ? void 0 : unref(i18n).baseText("chatHub.tools.selector.disabled.tooltip"),
						"transparent-bg": "",
						onClick: onSelectTools
					}, null, 8, [
						"class",
						"selected",
						"disabled",
						"disabled-tooltip"
					])], 2), createBaseVNode("div", { class: normalizeClass(_ctx.$style.actions) }, [
						createVNode(unref(N8nTooltip_default), {
							content: !canUploadFiles.value ? unref(i18n).baseText("chatHub.chat.prompt.button.attach.disabled") : unref(i18n).baseText("chatHub.chat.prompt.button.attach"),
							disabled: canUploadFiles.value && _ctx.messagingState === "idle",
							placement: "top"
						}, {
							default: withCtx(() => [createVNode(unref(N8nIconButton_default), {
								"native-type": "button",
								type: "secondary",
								disabled: _ctx.messagingState !== "idle" || !canUploadFiles.value,
								icon: "paperclip",
								"icon-size": "large",
								text: "",
								onClick: withModifiers(onAttach, ["stop"])
							}, null, 8, ["disabled"])]),
							_: 1
						}, 8, ["content", "disabled"]),
						unref(speechInput).isSupported ? (openBlock(), createBlock(unref(N8nIconButton_default), {
							key: 0,
							"native-type": "button",
							title: unref(speechInput).isListening.value ? unref(i18n).baseText("chatHub.chat.prompt.button.stopRecording") : unref(i18n).baseText("chatHub.chat.prompt.button.voiceInput"),
							type: "secondary",
							disabled: _ctx.messagingState !== "idle",
							icon: unref(speechInput).isListening.value ? "square" : "mic",
							class: normalizeClass({ [_ctx.$style.recording]: unref(speechInput).isListening.value }),
							"icon-size": "large",
							onClick: withModifiers(onMic, ["stop"])
						}, null, 8, [
							"title",
							"disabled",
							"icon",
							"class"
						])) : createCommentVNode("", true),
						_ctx.messagingState !== "receiving" ? (openBlock(), createBlock(unref(N8nIconButton_default), {
							key: 1,
							"native-type": "submit",
							disabled: _ctx.messagingState !== "idle" || !message$1.value.trim(),
							title: unref(i18n).baseText("chatHub.chat.prompt.button.send"),
							loading: _ctx.messagingState === "waitingFirstChunk",
							icon: "arrow-up",
							"icon-size": "large",
							onClick: _cache[3] || (_cache[3] = withModifiers(() => {}, ["stop"]))
						}, null, 8, [
							"disabled",
							"title",
							"loading"
						])) : (openBlock(), createBlock(unref(N8nIconButton_default), {
							key: 2,
							"native-type": "button",
							title: unref(i18n).baseText("chatHub.chat.prompt.button.stopGenerating"),
							icon: "square",
							"icon-size": "large",
							onClick: withModifiers(onStop, ["stop"])
						}, null, 8, ["title"]))
					], 2)], 2)
				], 2)
			], 2)], 34);
		};
	}
});
var ChatPrompt_vue_vue_type_style_index_0_lang_module_default = {
	prompt: "_prompt_b2qw2_123",
	inputWrap: "_inputWrap_b2qw2_128",
	callout: "_callout_b2qw2_136",
	fileInput: "_fileInput_b2qw2_153",
	inputWrapper: "_inputWrapper_b2qw2_157",
	footer: "_footer_b2qw2_181",
	tools: "_tools_b2qw2_188",
	toolsButton: "_toolsButton_b2qw2_192",
	iconStack: "_iconStack_b2qw2_197",
	icon: "_icon_b2qw2_197",
	iconOverlap: "_iconOverlap_b2qw2_210",
	iconFallback: "_iconFallback_b2qw2_214",
	actions: "_actions_b2qw2_220",
	attachments: "_attachments_b2qw2_229",
	recording: "_recording_b2qw2_235",
	chatHubPromptRecordingPulse: "_chatHubPromptRecordingPulse_b2qw2_1"
};
var ChatPrompt_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ChatPrompt_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ChatPrompt_vue_vue_type_style_index_0_lang_module_default }]]);
var ChatStarter_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
	__name: "ChatStarter",
	props: { isMobileDevice: { type: Boolean } },
	setup(__props) {
		const userStore = useUsersStore();
		const i18n = useI18n();
		const greetings = computed(() => {
			const name = userStore.currentUser?.firstName ?? userStore.currentUser?.fullName ?? i18n.baseText("chatHub.chat.greeting.fallback");
			return i18n.baseText("chatHub.chat.greeting", { interpolate: { name } });
		});
		return (_ctx, _cache) => {
			return openBlock(), createElementBlock("div", { class: normalizeClass([_ctx.$style.starter, { [_ctx.$style.isMobileDevice]: _ctx.isMobileDevice }]) }, [createVNode(unref(N8nHeading_default), {
				tag: "h2",
				bold: "",
				size: "xlarge"
			}, {
				default: withCtx(() => [createTextVNode(toDisplayString(greetings.value), 1)]),
				_: 1
			})], 2);
		};
	}
});
var ChatStarter_vue_vue_type_style_index_0_lang_module_default = {
	starter: "_starter_1julh_123",
	card: "_card_1julh_131",
	cardIcon: "_cardIcon_1julh_149",
	cardText: "_cardText_1julh_155"
};
var ChatStarter_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ChatStarter_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ChatStarter_vue_vue_type_style_index_0_lang_module_default }]]);
function useFileDrop(canAcceptFiles, onFilesDropped) {
	const isDragging = ref(false);
	function handleDragEnter(e) {
		if (!canAcceptFiles.value) return;
		if (e.dataTransfer?.types.includes("Files")) isDragging.value = true;
	}
	function handleDragLeave(e) {
		if (!canAcceptFiles.value) return;
		const target = e.currentTarget;
		const relatedTarget = e.relatedTarget;
		if (relatedTarget && target.contains(relatedTarget)) return;
		isDragging.value = false;
	}
	function handleDragOver(e) {
		if (!canAcceptFiles.value) return;
		e.preventDefault();
		e.stopPropagation();
	}
	function handleDrop(e) {
		e.preventDefault();
		e.stopPropagation();
		isDragging.value = false;
		if (!canAcceptFiles.value) return;
		const files = e.dataTransfer?.files;
		if (!files || files.length === 0) return;
		onFilesDropped(Array.from(files));
	}
	function handlePaste(e) {
		if (!canAcceptFiles.value) return;
		const items = e.clipboardData?.items;
		if (!items) return;
		let hasFiles = false;
		const files = [];
		for (const item of Array.from(items)) if (item.kind === "file") {
			const file = item.getAsFile();
			if (file) {
				files.push(file);
				hasFiles = true;
			}
		}
		if (hasFiles) {
			e.preventDefault();
			onFilesDropped(files);
		}
	}
	return {
		isDragging,
		handleDragEnter,
		handleDragLeave,
		handleDragOver,
		handleDrop,
		handlePaste
	};
}
var ChatView_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
	__name: "ChatView",
	setup(__props) {
		const router = useRouter();
		const route = useRoute();
		const usersStore = useUsersStore();
		const chatStore = useChatStore();
		const settingsStore = useSettingsStore();
		const toast = useToast();
		const isMobileDevice$1 = useMediaQuery(MOBILE_MEDIA_QUERY);
		const documentTitle = useDocumentTitle();
		const uiStore = useUIStore();
		const i18n = useI18n();
		const headerRef = useTemplateRef("headerRef");
		const inputRef = useTemplateRef("inputRef");
		const scrollableRef = useTemplateRef("scrollable");
		const scrollableSize = useElementSize(scrollableRef);
		const sessionId = computed(() => typeof route.params.id === "string" ? route.params.id : v4_default());
		const isResponding = computed(() => chatStore.isResponding(sessionId.value));
		const isNewSession$1 = computed(() => sessionId.value !== route.params.id);
		const scrollContainerRef = computed(() => scrollableRef.value?.parentElement ?? null);
		const currentConversation = computed(() => sessionId.value ? chatStore.sessions.byId[sessionId.value] : void 0);
		const currentConversationTitle = computed(() => currentConversation.value?.title);
		const canSelectTools = computed(() => selectedModel.value?.model.provider === "custom-agent" || !!selectedModel.value?.metadata.capabilities.functionCalling);
		const { arrivedState, measure } = useScroll(scrollContainerRef, {
			throttle: 100,
			offset: { bottom: 100 }
		});
		const defaultModel = useLocalStorage(LOCAL_STORAGE_CHAT_HUB_SELECTED_MODEL(usersStore.currentUserId ?? "anonymous"), null, {
			writeDefaults: false,
			shallow: true,
			serializer: {
				read: (value) => {
					try {
						return chatHubConversationModelWithCachedDisplayNameSchema.parse(JSON.parse(value));
					} catch (error) {
						return null;
					}
				},
				write: (value) => JSON.stringify(value)
			}
		});
		const defaultAgent = computed(() => defaultModel.value ? chatStore.getAgent(defaultModel.value) : void 0);
		const defaultTools = useLocalStorage(LOCAL_STORAGE_CHAT_HUB_SELECTED_TOOLS(usersStore.currentUserId ?? "anonymous"), null, {
			writeDefaults: false,
			shallow: true,
			serializer: {
				read: (value) => {
					try {
						return INodesSchema.parse(JSON.parse(value));
					} catch (error) {
						return null;
					}
				},
				write: (value) => JSON.stringify(value)
			}
		});
		const shouldSkipNextScrollTrigger = ref(false);
		const modelFromQuery = computed(() => {
			const agentId = route.query.agentId;
			const workflowId = route.query.workflowId;
			const provider = route.query.provider;
			const model = route.query.model;
			if (!isNewSession$1.value) return null;
			if (typeof agentId === "string") return chatStore.getAgent({
				provider: "custom-agent",
				agentId
			});
			if (typeof workflowId === "string") return chatStore.getAgent({
				provider: "n8n",
				workflowId
			});
			if (typeof provider === "string" && typeof model === "string") {
				const parsedModel = chatHubConversationModelSchema.safeParse({
					provider,
					model
				});
				if (parsedModel.success) return chatStore.getAgent(parsedModel.data);
			}
			return null;
		});
		const selectedModel = computed(() => {
			if (!isNewSession$1.value) {
				const model = currentConversation.value ? unflattenModel(currentConversation.value) : null;
				if (!model) return null;
				return chatStore.getAgent(model, {
					name: currentConversation.value?.agentName || currentConversation.value?.model,
					icon: currentConversation.value?.agentIcon
				});
			}
			if (modelFromQuery.value) return modelFromQuery.value;
			if (chatStore.streaming?.sessionId === sessionId.value) return chatStore.streaming.agent;
			if (!defaultModel.value) return null;
			return chatStore.getAgent(defaultModel.value, {
				name: defaultModel.value.cachedDisplayName,
				icon: defaultModel.value.cachedIcon
			});
		});
		const { customAgent } = useCustomAgent(computed(() => selectedModel.value?.model.provider === "custom-agent" ? selectedModel.value.model.agentId : void 0));
		const selectedTools = computed(() => {
			if (customAgent.value) return customAgent.value.tools;
			if (currentConversation.value?.tools) return currentConversation.value.tools;
			return modelFromQuery.value ? [] : defaultTools.value ?? [];
		});
		const { credentialsByProvider, selectCredential } = useChatCredentials(usersStore.currentUserId ?? "anonymous");
		const chatMessages = computed(() => chatStore.getActiveMessages(sessionId.value));
		const credentialsForSelectedProvider = computed(() => {
			const provider = selectedModel.value?.model.provider;
			if (!provider) return null;
			if (!isLlmProvider(provider)) return {};
			const credentialsId = credentialsByProvider.value?.[provider];
			if (!credentialsId) return null;
			return { [PROVIDER_CREDENTIAL_TYPE_MAP[provider]]: {
				id: credentialsId,
				name: ""
			} };
		});
		const isMissingSelectedCredential = computed(() => !credentialsForSelectedProvider.value);
		const messagingState = computed(() => {
			if (chatStore.streaming?.sessionId === sessionId.value) return chatStore.streaming.messageId ? "receiving" : "waitingFirstChunk";
			if (chatStore.agentsReady && !selectedModel.value) return "missingAgent";
			if (chatStore.agentsReady && isMissingSelectedCredential.value) return "missingCredentials";
			return "idle";
		});
		const editingMessageId = ref();
		const messageElementsRef = useTemplateRef("messages");
		const didSubmitInCurrentSession = ref(false);
		const fileDrop = useFileDrop(computed(() => {
			if (!(!!createMimeTypes(selectedModel.value?.metadata.inputModalities ?? []) && !isMissingSelectedCredential.value)) return false;
			if (editingMessageId.value) return chatMessages.value.find((msg) => msg.id === editingMessageId.value)?.type === "human";
			return true;
		}), onFilesDropped);
		function scrollToBottom(smooth) {
			scrollContainerRef.value?.scrollTo({
				top: scrollableRef.value?.scrollHeight,
				behavior: smooth ? "smooth" : "instant"
			});
		}
		function scrollToMessage(messageId) {
			scrollableRef.value?.querySelector(`[data-message-id="${messageId}"]`)?.scrollIntoView({ behavior: "smooth" });
		}
		watch(() => chatMessages.value[chatMessages.value.length - 1]?.id, (lastMessageId) => {
			if (!lastMessageId) return;
			if (shouldSkipNextScrollTrigger.value) {
				shouldSkipNextScrollTrigger.value = false;
				return;
			}
			nextTick(measure);
			if (chatStore.streaming?.sessionId === sessionId.value) {
				scrollToMessage(chatStore.streaming.promptId);
				return;
			}
			scrollToBottom(false);
		}, {
			immediate: true,
			flush: "post"
		});
		watch(() => chatStore.agents, (models) => {
			const settings = settingsStore.moduleSettings?.["chat-hub"];
			if (!models || !!selectedModel.value || !isNewSession$1.value || !settings) return;
			const model = findOneFromModelsResponse(models, settings.providers);
			if (model) handleSelectAgent(model);
		}, { immediate: true });
		watch([sessionId, isNewSession$1], async ([id, isNew]) => {
			didSubmitInCurrentSession.value = false;
			editingMessageId.value = void 0;
			if (!isNew && !chatStore.getConversation(id)) try {
				await chatStore.fetchMessages(id);
			} catch (error) {
				toast.showError(error, i18n.baseText("chatHub.error.fetchConversationFailed"));
				await router.push({ name: CHAT_VIEW });
			}
		}, { immediate: true });
		watch([inputRef, sessionId], ([input]) => {
			input?.focus();
		}, { immediate: true });
		watch(currentConversationTitle, (title$1) => {
			documentTitle.set(title$1 ?? "Chat");
		}, { immediate: true });
		watch(credentialsByProvider, (credentials) => {
			if (credentials) chatStore.fetchAgents(credentials);
		}, { immediate: true });
		watch(defaultAgent, (agent, prevAgent) => {
			if (defaultModel.value && agent?.name && agent.name !== prevAgent?.name) defaultModel.value = {
				...defaultModel.value,
				cachedDisplayName: agent.name
			};
			if (defaultModel.value && agent?.icon && (agent.icon.type !== prevAgent?.icon?.type || agent.icon.value !== prevAgent.icon.value)) defaultModel.value = {
				...defaultModel.value,
				cachedIcon: agent.icon
			};
			if (agent && !agent.metadata.capabilities.functionCalling && (defaultTools.value ?? []).length > 0) defaultTools.value = [];
		}, { immediate: true });
		async function onSubmit(message$1, attachments$2) {
			if (!message$1.trim() || isResponding.value || !selectedModel.value || !credentialsForSelectedProvider.value) return;
			didSubmitInCurrentSession.value = true;
			editingMessageId.value = void 0;
			await chatStore.sendMessage(sessionId.value, message$1, selectedModel.value, credentialsForSelectedProvider.value, canSelectTools.value ? selectedTools.value : [], attachments$2);
			inputRef.value?.reset();
			if (isNewSession$1.value) router.push({
				name: CHAT_CONVERSATION_VIEW,
				params: { id: sessionId.value }
			});
		}
		async function onStop() {
			await chatStore.stopStreamingMessage(sessionId.value);
		}
		function handleStartEditMessage(messageId) {
			editingMessageId.value = messageId;
		}
		function handleCancelEditMessage() {
			editingMessageId.value = void 0;
		}
		async function handleEditMessage(content$1, keptAttachmentIndices, newFiles) {
			if (!editingMessageId.value || isResponding.value || !selectedModel.value || !credentialsForSelectedProvider.value) return;
			await chatStore.editMessage(sessionId.value, editingMessageId.value, content$1, selectedModel.value, credentialsForSelectedProvider.value, keptAttachmentIndices, newFiles);
			editingMessageId.value = void 0;
		}
		async function handleRegenerateMessage(message$1) {
			if (isResponding.value || message$1.type !== "ai" || !selectedModel.value || !credentialsForSelectedProvider.value) return;
			const messageToRetry = message$1.id;
			editingMessageId.value = void 0;
			await chatStore.regenerateMessage(sessionId.value, messageToRetry, selectedModel.value, credentialsForSelectedProvider.value);
		}
		async function handleSelectModel(selection, selectedAgent) {
			const agent = selectedAgent ?? chatStore.getAgent(selection);
			if (currentConversation.value) try {
				await chatStore.updateSessionModel(sessionId.value, selection, agent.name);
			} catch (error) {
				toast.showError(error, i18n.baseText("chatHub.error.updateModelFailed"));
			}
			else {
				defaultModel.value = {
					...selection,
					cachedDisplayName: agent.name,
					cachedIcon: agent.icon ?? void 0
				};
				await router.push({
					name: CHAT_VIEW,
					force: true
				});
			}
		}
		async function handleSelectAgent(selection) {
			await handleSelectModel(selection.model, selection);
		}
		function handleSwitchAlternative(messageId) {
			shouldSkipNextScrollTrigger.value = true;
			chatStore.switchAlternative(sessionId.value, messageId);
		}
		function handleConfigureCredentials(provider) {
			headerRef.value?.openCredentialSelector(provider);
		}
		function handleConfigureModel() {
			headerRef.value?.openModelSelector();
		}
		async function handleUpdateTools(newTools) {
			defaultTools.value = newTools;
			if (currentConversation.value) try {
				await chatStore.updateToolsInSession(sessionId.value, newTools);
			} catch (error) {
				toast.showError(error, i18n.baseText("chatHub.error.updateToolsFailed"));
			}
		}
		function handleEditAgent(agentId) {
			uiStore.openModalWithData({
				name: AGENT_EDITOR_MODAL_KEY,
				data: {
					agentId,
					credentials: credentialsByProvider,
					onCreateCustomAgent: handleSelectAgent
				}
			});
		}
		function openNewAgentCreator() {
			uiStore.openModalWithData({
				name: AGENT_EDITOR_MODAL_KEY,
				data: {
					credentials: credentialsByProvider,
					onCreateCustomAgent: handleSelectAgent
				}
			});
		}
		function handleOpenWorkflow(workflowId) {
			const routeData = router.resolve({
				name: VIEWS.WORKFLOW,
				params: { name: workflowId }
			});
			window.open(routeData.href, "_blank");
		}
		function onFilesDropped(files) {
			if (!editingMessageId.value) {
				inputRef.value?.addAttachments(files);
				return;
			}
			const index = chatMessages.value.findIndex((message$1) => message$1.id === editingMessageId.value);
			messageElementsRef.value?.[index]?.addFiles(files);
		}
		return (_ctx, _cache) => {
			return openBlock(), createBlock(ChatLayout_default, {
				class: normalizeClass({
					[_ctx.$style.chatLayout]: true,
					[_ctx.$style.isNewSession]: isNewSession$1.value,
					[_ctx.$style.isExistingSession]: !isNewSession$1.value,
					[_ctx.$style.isMobileDevice]: unref(isMobileDevice$1),
					[_ctx.$style.isDraggingFile]: unref(fileDrop).isDragging.value
				}),
				onDragenter: unref(fileDrop).handleDragEnter,
				onDragleave: unref(fileDrop).handleDragLeave,
				onDragover: unref(fileDrop).handleDragOver,
				onDrop: unref(fileDrop).handleDrop,
				onPaste: unref(fileDrop).handlePaste
			}, {
				default: withCtx(() => [
					unref(fileDrop).isDragging.value ? (openBlock(), createElementBlock("div", {
						key: 0,
						class: normalizeClass(_ctx.$style.dropOverlay)
					}, [createVNode(unref(N8nText_default), {
						size: "large",
						color: "text-dark"
					}, {
						default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("chatHub.chat.dropOverlay")), 1)]),
						_: 1
					})], 2)) : createCommentVNode("", true),
					createVNode(ChatConversationHeader_default, {
						ref_key: "headerRef",
						ref: headerRef,
						"selected-model": selectedModel.value,
						credentials: unref(credentialsByProvider),
						"ready-to-show-model-selector": isNewSession$1.value || !!currentConversation.value,
						onSelectModel: handleSelectModel,
						onEditCustomAgent: handleEditAgent,
						onCreateCustomAgent: openNewAgentCreator,
						onSelectCredential: unref(selectCredential),
						onOpenWorkflow: handleOpenWorkflow
					}, null, 8, [
						"selected-model",
						"credentials",
						"ready-to-show-model-selector",
						"onSelectCredential"
					]),
					createVNode(unref(N8nScrollArea_default), {
						type: "scroll",
						"enable-vertical-scroll": true,
						"enable-horizontal-scroll": false,
						"as-child": "",
						class: normalizeClass(_ctx.$style.scrollArea)
					}, {
						default: withCtx(() => [createBaseVNode("div", {
							class: normalizeClass(_ctx.$style.scrollable),
							ref: "scrollable"
						}, [isNewSession$1.value ? (openBlock(), createBlock(ChatStarter_default, {
							key: 0,
							class: normalizeClass(_ctx.$style.starter),
							"is-mobile-device": unref(isMobileDevice$1)
						}, null, 8, ["class", "is-mobile-device"])) : (openBlock(), createElementBlock("div", {
							key: 1,
							role: "log",
							"aria-live": "polite",
							class: normalizeClass(_ctx.$style.messageList)
						}, [(openBlock(true), createElementBlock(Fragment, null, renderList(chatMessages.value, (message$1, index) => {
							return openBlock(), createBlock(ChatMessage_default, {
								key: message$1.id,
								ref_for: true,
								ref: "messages",
								message: message$1,
								compact: unref(isMobileDevice$1),
								"is-editing": editingMessageId.value === message$1.id,
								"is-edit-submitting": unref(chatStore).streaming?.revisionOfMessageId === message$1.id,
								"has-session-streaming": isResponding.value,
								"cached-agent-display-name": selectedModel.value?.name ?? null,
								"cached-agent-icon": selectedModel.value?.icon ?? null,
								"min-height": didSubmitInCurrentSession.value && message$1.type === "ai" && index === chatMessages.value.length - 1 && scrollContainerRef.value ? scrollContainerRef.value.offsetHeight - 30 - 200 : void 0,
								"container-width": unref(scrollableSize).width.value ?? 0,
								onStartEdit: ($event) => handleStartEditMessage(message$1.id),
								onCancelEdit: handleCancelEditMessage,
								onRegenerate: handleRegenerateMessage,
								onUpdate: handleEditMessage,
								onSwitchAlternative: handleSwitchAlternative
							}, null, 8, [
								"message",
								"compact",
								"is-editing",
								"is-edit-submitting",
								"has-session-streaming",
								"cached-agent-display-name",
								"cached-agent-icon",
								"min-height",
								"container-width",
								"onStartEdit"
							]);
						}), 128))], 2)), createBaseVNode("div", { class: normalizeClass(_ctx.$style.promptContainer) }, [!unref(arrivedState).bottom && !isNewSession$1.value ? (openBlock(), createBlock(unref(N8nIconButton_default), {
							key: 0,
							type: "secondary",
							icon: "arrow-down",
							class: normalizeClass(_ctx.$style.scrollToBottomButton),
							title: unref(i18n).baseText("chatHub.chat.scrollToBottom"),
							onClick: _cache[0] || (_cache[0] = ($event) => scrollToBottom(true))
						}, null, 8, ["class", "title"])) : createCommentVNode("", true), createVNode(ChatPrompt_default, {
							ref_key: "inputRef",
							ref: inputRef,
							class: normalizeClass(_ctx.$style.prompt),
							"selected-model": selectedModel.value,
							"selected-tools": selectedTools.value,
							"messaging-state": messagingState.value,
							"is-tools-selectable": canSelectTools.value,
							"is-new-session": isNewSession$1.value,
							onSubmit,
							onStop,
							onSelectModel: handleConfigureModel,
							onSelectTools: handleUpdateTools,
							onSetCredentials: handleConfigureCredentials,
							onEditAgent: handleEditAgent
						}, null, 8, [
							"class",
							"selected-model",
							"selected-tools",
							"messaging-state",
							"is-tools-selectable",
							"is-new-session"
						])], 2)], 2)]),
						_: 1
					}, 8, ["class"])
				]),
				_: 1
			}, 8, [
				"class",
				"onDragenter",
				"onDragleave",
				"onDragover",
				"onDrop",
				"onPaste"
			]);
		};
	}
});
var ChatView_vue_vue_type_style_index_0_lang_module_default = {
	chatLayout: "_chatLayout_1sy6c_123",
	scrollArea: "_scrollArea_1sy6c_127",
	scrollable: "_scrollable_1sy6c_132",
	isNewSession: "_isNewSession_1sy6c_141",
	header: "_header_1sy6c_145",
	isMobileDevice: "_isMobileDevice_1sy6c_151",
	starter: "_starter_1sy6c_151",
	messageList: "_messageList_1sy6c_156",
	promptContainer: "_promptContainer_1sy6c_172",
	isExistingSession: "_isExistingSession_1sy6c_176",
	prompt: "_prompt_1sy6c_172",
	scrollToBottomButton: "_scrollToBottomButton_1sy6c_194",
	isDraggingFile: "_isDraggingFile_1sy6c_202",
	dropOverlay: "_dropOverlay_1sy6c_206"
};
var ChatView_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ChatView_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ChatView_vue_vue_type_style_index_0_lang_module_default }]]);
export { ChatView_default as default };