UNPKG

n8n-editor-ui

Version:

Workflow Editor UI for n8n

472 lines (471 loc) 18.1 kB
import { C as computed, Cn as toDisplayString, D as createElementBlock, E as createCommentVNode, Gt as unref, It as ref, M as createVNode, P as defineComponent, T as createBlock, Ut as toValue, Z as onMounted, _ as Fragment, _t as watch, bn as normalizeStyle, bt as withCtx, c as useCssModule, et as openBlock, it as renderList, j as createTextVNode, mt as useTemplateRef, vn as normalizeClass, w as createBaseVNode } from "./vue.runtime.esm-bundler-tP5dCd7J.js"; import { _t as useI18n, g as onClickOutside } from "./_MapCache-ficiegRb.js"; import { M as N8nNavigationDropdown_default, Sn as N8nButton_default, ht as N8nTooltip_default, wn as N8nIcon_default, xn as N8nText_default } from "./src-DgvumQTi.js"; import { t as __plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BwBpWJRZ.js"; import { Nt as useNodeTypesStore, O as useTelemetry, Xn as useProjectsStore, gn as useCredentialsStore, gs as useSettingsStore, oa as useUIStore } from "./builder.store-sBTWwxRU.js"; import { Oo as getResourcePermissions, Qr as chatHubLLMProviderSchema, Yr as PROVIDER_CREDENTIAL_TYPE_MAP } from "./constants-BbpucWL4.js"; import { t as NodeIcon_default } from "./NodeIcon-BVhrZNYI.js"; import { t as CredentialIcon_default } from "./CredentialIcon-FEnE25SA.js"; import { i as CHAT_MODEL_BY_ID_SELECTOR_MODAL_KEY, m as providerDisplayNames, r as CHAT_CREDENTIAL_SELECTOR_MODAL_KEY } from "./constants-DLTaaipl.js"; import { a as flattenModel, d as personalAgentDefaultIcon, f as stringifyModel, m as workflowAgentDefaultIcon, o as fromStringToModel, t as useChatStore, u as isLlmProviderModel } from "./chat.store-BGFxJU_3.js"; import { t as ChatAgentAvatar_default } from "./ChatAgentAvatar-DMLTyAJz.js"; function truncateBeforeLast(text, maxLength, lastCharsLength = 5) { const chars = []; const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" }); for (const { segment } of segmenter.segment(text)) chars.push(segment); if (chars.length <= maxLength) return text; const lastWordIndex = chars.findLastIndex((ch) => ch.match(/^\s+$/)) + 1; const lastWord = chars.slice(lastWordIndex); const ellipsis = "…"; const ellipsisLength = 1; if (lastWord.length < 15) { const charsToRemove = chars.length - maxLength + ellipsisLength; const indexBeforeLastWord = lastWordIndex; const keepLength = indexBeforeLastWord - charsToRemove; if (keepLength > 0) return chars.slice(0, keepLength).join("") + ellipsis + chars.slice(indexBeforeLastWord).join(""); } if (lastCharsLength < 1) return chars.slice(0, maxLength).join("") + ellipsis; return chars.slice(0, maxLength - lastCharsLength - ellipsisLength).join("") + ellipsis + chars.slice(-lastCharsLength).join(""); } var NEW_AGENT_MENU_ID = "agent::new"; var MAX_AGENT_NAME_CHARS = 30; var MAX_AGENT_NAME_CHARS_MENU = 45; var ModelSelector_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({ __name: "ModelSelector", props: { selectedAgent: {}, includeCustomAgents: { type: Boolean, default: true }, credentials: {}, text: { type: Boolean }, warnMissingCredentials: { type: Boolean, default: false }, agents: {}, isLoading: { type: Boolean } }, emits: [ "change", "createCustomAgent", "selectCredential" ], setup(__props, { expose: __expose, emit: __emit }) { const emit = __emit; function handleSelectCredentials(provider, id) { emit("selectCredential", provider, id); } function handleSelectModelById(provider, modelId) { emit("change", { provider, model: modelId }); } const i18n = useI18n(); const dropdownRef = useTemplateRef("dropdownRef"); const uiStore = useUIStore(); const settingStore = useSettingsStore(); const credentialsStore = useCredentialsStore(); const projectStore = useProjectsStore(); const telemetry = useTelemetry(); const styles = useCssModule(); const credentialsName = computed(() => __props.selectedAgent ? credentialsStore.getCredentialById(__props.credentials?.[__props.selectedAgent.model.provider] ?? "")?.name : void 0); const isCredentialsRequired = computed(() => isLlmProviderModel(__props.selectedAgent?.model)); const isCredentialsMissing = computed(() => __props.warnMissingCredentials && isCredentialsRequired.value && __props.selectedAgent?.model.provider && !__props.credentials?.[__props.selectedAgent?.model.provider]); const menu = computed(() => { const menuItems = []; const fullNamesMap = {}; if (__props.includeCustomAgents) { const n8nAgentsSubmenu = []; if (__props.isLoading) n8nAgentsSubmenu.push({ id: "loading", title: i18n.baseText("generic.loadingEllipsis"), disabled: true }); else if (__props.agents.n8n.models.length === 0) n8nAgentsSubmenu.push({ id: "no-agents", title: i18n.baseText("chatHub.workflowAgents.empty.noAgents"), disabled: true }); else n8nAgentsSubmenu.push(...__props.agents.n8n.models.map((agent) => { const id = stringifyModel(agent.model); fullNamesMap[id] = agent.name; return { id, icon: agent.icon ?? workflowAgentDefaultIcon, iconSize: "large", title: truncateBeforeLast(agent.name, MAX_AGENT_NAME_CHARS_MENU), disabled: false, description: agent.description ? truncateBeforeLast(agent.description, 200, 0) : void 0 }; })); const customAgents = __props.isLoading ? [] : __props.agents["custom-agent"].models.map((agent) => { const id = stringifyModel(agent.model); fullNamesMap[id] = agent.name; return { id, icon: agent.icon ?? personalAgentDefaultIcon, iconSize: "large", title: truncateBeforeLast(agent.name, MAX_AGENT_NAME_CHARS_MENU), disabled: false, description: agent.description ? truncateBeforeLast(agent.description, 200, 0) : void 0 }; }); menuItems.push({ id: "custom-agents", title: i18n.baseText("chatHub.agent.personalAgents"), icon: "message-square", iconSize: "large", iconMargin: false, submenu: [...__props.isLoading ? [{ id: "loading", title: i18n.baseText("generic.loadingEllipsis"), disabled: true }, { isDivider: true, id: "divider" }] : customAgents.length > 0 ? [...customAgents, { isDivider: true, id: "divider" }] : [], { id: NEW_AGENT_MENU_ID, icon: "plus", iconSize: "large", title: i18n.baseText("chatHub.agent.newAgent"), disabled: false }] }); menuItems.push({ id: "n8n-agents", title: i18n.baseText("chatHub.agent.workflowAgents"), icon: "robot", iconSize: "large", iconMargin: false, submenu: n8nAgentsSubmenu }); menuItems.push({ isDivider: true, id: "agents-divider" }); } for (const provider of chatHubLLMProviderSchema.options) { const settings = settingStore.moduleSettings?.["chat-hub"]?.providers[provider]; if (settings && !settings.enabled) continue; const configureMenu = { id: `${provider}::configure`, icon: "settings", iconSize: "large", title: i18n.baseText("chatHub.agent.configureCredentials"), disabled: false }; if (__props.isLoading) { menuItems.push({ id: provider, title: providerDisplayNames[provider], submenu: [ configureMenu, { isDivider: true, id: "divider" }, { id: `${provider}::loading`, title: i18n.baseText("generic.loadingEllipsis"), disabled: true } ] }); continue; } const theAgents = [...__props.agents[provider].models]; for (const model of settings?.allowedModels ?? []) if (model.isManual) theAgents.push({ name: model.displayName, description: "", icon: null, model: { provider, model: model.model }, createdAt: "", updatedAt: null, metadata: { inputModalities: [ "text", "image", "audio", "video", "file" ], capabilities: { functionCalling: true }, available: true } }); const error = __props.agents[provider].error; const agentOptions = theAgents.length > 0 ? theAgents.filter((agent) => agent.model.provider === "n8n" || !settings || settings.allowedModels.length === 0 || settings.allowedModels.some((m) => "model" in agent.model && m.model === agent.model.model)).map((agent) => { const id = stringifyModel(agent.model); fullNamesMap[id] = agent.name; return { id, title: truncateBeforeLast(agent.name, MAX_AGENT_NAME_CHARS_MENU), disabled: false }; }).filter((item, index, self) => self.findIndex((i) => i.id === item.id) === index) : error ? [{ id: `${provider}::error`, value: null, disabled: true, title: error }] : []; const submenu = agentOptions.concat([...agentOptions.length > 0 ? [{ isDivider: true, id: "divider" }] : [], ...settings?.allowedModels.length === 0 ? [{ id: `${provider}::add-model`, icon: "plus", iconSize: "large", title: i18n.baseText("chatHub.agent.addModel"), disabled: false }] : []]); submenu.unshift(configureMenu, ...submenu.length > 1 ? [{ isDivider: true, id: "divider" }] : []); menuItems.push({ id: provider, title: providerDisplayNames[provider], submenu }); } return { items: menuItems, fullNames: fullNamesMap }; }); const selectedLabel = computed(() => __props.selectedAgent?.name ?? i18n.baseText("chatHub.models.selector.defaultLabel")); const canCreateCredentials = computed(() => { return getResourcePermissions(projectStore.personalProject?.scopes).credential.create; }); function openCredentialsSelectorOrCreate(provider) { const credentialType = PROVIDER_CREDENTIAL_TYPE_MAP[provider]; if (credentialsStore.getCredentialsByType(credentialType).length === 0 && canCreateCredentials.value) { uiStore.openNewCredential(credentialType); return; } uiStore.openModalWithData({ name: CHAT_CREDENTIAL_SELECTOR_MODAL_KEY, data: { provider, initialValue: __props.credentials?.[provider] ?? null, onSelect: handleSelectCredentials } }); } function openModelByIdSelector(provider) { uiStore.openModalWithData({ name: CHAT_MODEL_BY_ID_SELECTOR_MODAL_KEY, data: { provider, initialValue: null, onSelect: handleSelectModelById } }); } function onSelect(id) { if (id === NEW_AGENT_MENU_ID) { emit("createCustomAgent"); return; } const [, identifier] = id.split("::"); const parsedModel = fromStringToModel(id); if (!parsedModel) return; if (identifier === "configure" && isLlmProviderModel(parsedModel)) { openCredentialsSelectorOrCreate(parsedModel.provider); return; } if (identifier === "add-model" && isLlmProviderModel(parsedModel)) { openModelByIdSelector(parsedModel.provider); return; } telemetry.track("User selected model or agent", { ...flattenModel(parsedModel), is_custom: parsedModel.provider === "custom-agent" }); emit("change", parsedModel); } onClickOutside(computed(() => dropdownRef.value?.$el), () => dropdownRef.value?.close(), { ignore: [`.${styles.component} [role=menuitem]`] }); __expose({ open: () => dropdownRef.value?.open(), openCredentialSelector: (provider) => openCredentialsSelectorOrCreate(provider) }); return (_ctx, _cache) => { return openBlock(), createBlock(unref(N8nNavigationDropdown_default), { ref_key: "dropdownRef", ref: dropdownRef, "submenu-class": _ctx.$style.component, menu: menu.value.items, teleport: "", onSelect }, { "item-icon": withCtx(({ item }) => [item.id in unref(PROVIDER_CREDENTIAL_TYPE_MAP) ? (openBlock(), createBlock(CredentialIcon_default, { key: 0, "credential-type-name": unref(PROVIDER_CREDENTIAL_TYPE_MAP)[item.id], size: 16, class: normalizeClass(_ctx.$style.menuIcon) }, null, 8, ["credential-type-name", "class"])) : createCommentVNode("", true)]), default: withCtx(() => [createVNode(unref(N8nButton_default), { class: normalizeClass(_ctx.$style.dropdownButton), type: "secondary", text: _ctx.text, "data-test-id": "chat-model-selector" }, { default: withCtx(() => [ createVNode(ChatAgentAvatar_default, { agent: _ctx.selectedAgent, size: credentialsName.value || !isCredentialsRequired.value ? "md" : "sm", class: normalizeClass(_ctx.$style.icon) }, null, 8, [ "agent", "size", "class" ]), createBaseVNode("div", { class: normalizeClass(_ctx.$style.selected) }, [createBaseVNode("div", null, toDisplayString(unref(truncateBeforeLast)(selectedLabel.value, MAX_AGENT_NAME_CHARS)), 1), credentialsName.value ? (openBlock(), createBlock(unref(N8nText_default), { key: 0, size: "xsmall", color: "text-light" }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(truncateBeforeLast)(credentialsName.value, MAX_AGENT_NAME_CHARS)), 1)]), _: 1 })) : isCredentialsMissing.value ? (openBlock(), createBlock(unref(N8nText_default), { key: 1, size: "xsmall", color: "danger" }, { default: withCtx(() => [createVNode(unref(N8nIcon_default), { icon: "node-validation-error", size: "xsmall", class: normalizeClass(_ctx.$style.credentialsMissingIcon) }, null, 8, ["class"]), createTextVNode(" " + toDisplayString(unref(i18n).baseText("chatHub.agent.credentialsMissing")), 1)]), _: 1 })) : createCommentVNode("", true)], 2), createVNode(unref(N8nIcon_default), { icon: "chevron-down", size: "medium" }) ]), _: 1 }, 8, ["class", "text"])]), _: 1 }, 8, ["submenu-class", "menu"]); }; } }); var ModelSelector_vue_vue_type_style_index_0_lang_module_default = { component: "_component_1rkhd_123", dropdownButton: "_dropdownButton_1rkhd_128", credentialsMissingIcon: "_credentialsMissingIcon_1rkhd_136", selected: "_selected_1rkhd_141", icon: "_icon_1rkhd_148", menuIcon: "_menuIcon_1rkhd_153", avatarIcon: "_avatarIcon_1rkhd_157" }; var ModelSelector_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ModelSelector_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ModelSelector_vue_vue_type_style_index_0_lang_module_default }]]); var ToolsSelector_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({ __name: "ToolsSelector", props: { disabled: { type: Boolean }, selected: {}, transparentBg: { type: Boolean, default: false }, disabledTooltip: {} }, emits: ["click"], setup(__props, { emit: __emit }) { const emit = __emit; const nodeTypesStore = useNodeTypesStore(); const i18n = useI18n(); const toolCount = computed(() => __props.selected.length ?? 0); const displayToolNodeTypes = computed(() => { return __props.selected.slice(0, 3).map((t) => nodeTypesStore.getNodeType(t.type)).filter(Boolean); }); const toolsLabel = computed(() => { if (toolCount.value > 0) return i18n.baseText("chatHub.tools.selector.label.count", { adjustToNumber: toolCount.value }); return i18n.baseText("chatHub.tools.selector.label.default"); }); onMounted(async () => { await nodeTypesStore.loadNodeTypesIfNotLoaded(); }); return (_ctx, _cache) => { return openBlock(), createBlock(unref(N8nTooltip_default), { content: _ctx.disabledTooltip, disabled: !_ctx.disabledTooltip, placement: "top" }, { default: withCtx(() => [createVNode(unref(N8nButton_default), { type: "secondary", "native-type": "button", class: normalizeClass([_ctx.$style.toolsButton, { [_ctx.$style.transparentBg]: _ctx.transparentBg }]), disabled: _ctx.disabled, icon: toolCount.value > 0 ? void 0 : "plus", onClick: _cache[0] || (_cache[0] = ($event) => emit("click", $event)) }, { default: withCtx(() => [toolCount.value ? (openBlock(), createElementBlock("span", { key: 0, class: normalizeClass(_ctx.$style.iconStack) }, [(openBlock(true), createElementBlock(Fragment, null, renderList(displayToolNodeTypes.value, (nodeType, i) => { return openBlock(), createBlock(NodeIcon_default, { key: `${nodeType?.name}-${i}`, style: normalizeStyle({ zIndex: displayToolNodeTypes.value.length - i }), "node-type": nodeType, class: normalizeClass([_ctx.$style.icon, { [_ctx.$style.iconOverlap]: i !== 0 }]), circle: true, size: 12 }, null, 8, [ "style", "node-type", "class" ]); }), 128))], 2)) : createCommentVNode("", true), createTextVNode(" " + toDisplayString(toolsLabel.value), 1)]), _: 1 }, 8, [ "class", "disabled", "icon" ])]), _: 1 }, 8, ["content", "disabled"]); }; } }); var ToolsSelector_vue_vue_type_style_index_0_lang_module_default = { toolsButton: "_toolsButton_1u99p_123", transparentBg: "_transparentBg_1u99p_128", iconStack: "_iconStack_1u99p_132", icon: "_icon_1u99p_132", iconOverlap: "_iconOverlap_1u99p_145" }; var ToolsSelector_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ToolsSelector_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ToolsSelector_vue_vue_type_style_index_0_lang_module_default }]]); function useCustomAgent(agentId) { const store = useChatStore(); const isLoading = ref(false); const id = computed(() => toValue(agentId)); const customAgent = computed(() => { if (!id.value) return; return store.customAgents[id.value]; }); watch(id, async (theId) => { if (theId) try { isLoading.value = true; await store.fetchCustomAgent(theId); } finally { isLoading.value = false; } }, { immediate: true }); return { isLoading, customAgent }; } export { ToolsSelector_default as n, ModelSelector_default as r, useCustomAgent as t };