diff --git a/packages/frontend/@n8n/stores/src/constants.ts b/packages/frontend/@n8n/stores/src/constants.ts index 7cea01c1aa1..a080721495f 100644 --- a/packages/frontend/@n8n/stores/src/constants.ts +++ b/packages/frontend/@n8n/stores/src/constants.ts @@ -24,6 +24,8 @@ export const STORES = { COLLABORATION: 'collaboration', ASSISTANT: 'assistant', BUILDER: 'builder', + CHAT_PANEL: 'chatPanel', + CHAT_PANEL_STATE: 'chatPanelState', BECOME_TEMPLATE_CREATOR: 'becomeTemplateCreator', PROJECTS: 'projects', API_KEYS: 'apiKeys', diff --git a/packages/frontend/editor-ui/src/App.vue b/packages/frontend/editor-ui/src/App.vue index 81a825e6e83..d71ac305e87 100644 --- a/packages/frontend/editor-ui/src/App.vue +++ b/packages/frontend/editor-ui/src/App.vue @@ -15,8 +15,8 @@ import { HIRING_BANNER, VIEWS, } from '@/constants'; +import { useChatPanelStore } from '@/features/assistant/chatPanel.store'; import { useAssistantStore } from '@/features/assistant/assistant.store'; -import { useBuilderStore } from '@/stores/builder.store'; import { useNDVStore } from '@/stores/ndv.store'; import { useSettingsStore } from '@/stores/settings.store'; import { useUIStore } from '@/stores/ui.store'; @@ -38,7 +38,7 @@ import { hasPermission } from './utils/rbac/permissions'; const route = useRoute(); const rootStore = useRootStore(); const assistantStore = useAssistantStore(); -const builderStore = useBuilderStore(); +const chatPanelStore = useChatPanelStore(); const uiStore = useUIStore(); const usersStore = useUsersStore(); const settingsStore = useSettingsStore(); @@ -71,8 +71,7 @@ const isDemoMode = computed(() => route.name === VIEWS.DEMO); const hasContentFooter = ref(false); const appGrid = ref(null); -const assistantSidebarWidth = computed(() => assistantStore.chatWidth); -const builderSidebarWidth = computed(() => builderStore.chatWidth); +const chatPanelWidth = computed(() => chatPanelStore.width); useTelemetryContext({ ndv_source: computed(() => ndvStore.lastSetActiveNodeSource) }); @@ -107,8 +106,8 @@ const updateGridWidth = async () => { uiStore.appGridDimensions = { width, height }; } }; -// As assistant sidebar width changes, recalculate the total width regularly -watch([assistantSidebarWidth, builderSidebarWidth], async () => { +// As chat panel width changes, recalculate the total width regularly +watch(chatPanelWidth, async () => { await updateGridWidth(); }); @@ -211,6 +210,7 @@ useExposeCssVar('--ask-assistant-floating-button-bottom-offset', askAiFloatingBu grid-area: banners; z-index: var(--z-index-top-banners); } + .content { display: flex; flex-direction: column; @@ -230,6 +230,7 @@ useExposeCssVar('--ask-assistant-floating-button-bottom-offset', askAiFloatingBu display: block; } } + .contentWrapper { display: flex; grid-area: content; diff --git a/packages/frontend/editor-ui/src/components/CredentialEdit/CredentialConfig.vue b/packages/frontend/editor-ui/src/components/CredentialEdit/CredentialConfig.vue index 609e25610c7..902a5e32983 100644 --- a/packages/frontend/editor-ui/src/components/CredentialEdit/CredentialConfig.vue +++ b/packages/frontend/editor-ui/src/components/CredentialEdit/CredentialConfig.vue @@ -30,6 +30,7 @@ import CopyInput from '../CopyInput.vue'; import CredentialInputs from './CredentialInputs.vue'; import GoogleAuthButton from './GoogleAuthButton.vue'; import OauthButton from './OauthButton.vue'; +import { useChatPanelStore } from '@/features/assistant/chatPanel.store'; import { useAssistantStore } from '@/features/assistant/assistant.store'; import FreeAiCreditsCallout from '@/components/FreeAiCreditsCallout.vue'; @@ -41,6 +42,7 @@ import { N8nNotice, N8nText, } from '@n8n/design-system'; + type Props = { mode: string; credentialType: ICredentialType; @@ -82,6 +84,7 @@ const rootStore = useRootStore(); const uiStore = useUIStore(); const workflowsStore = useWorkflowsStore(); const assistantStore = useAssistantStore(); +const chatPanelStore = useChatPanelStore(); const i18n = useI18n(); const telemetry = useTelemetry(); @@ -223,7 +226,7 @@ async function onAskAssistantClick() { }); return; } - await assistantStore.initCredHelp(props.credentialType); + await chatPanelStore.openWithCredHelp(props.credentialType); } watch(showOAuthSuccessBanner, (newValue, oldValue) => { @@ -414,6 +417,7 @@ watch(showOAuthSuccessBanner, (newValue, oldValue) => { .askAssistantButton { display: flex; align-items: center; + > span { margin-left: var(--spacing-3xs); font-size: var(--font-size-s); diff --git a/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue b/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue index f41433c4c7d..8ce4e15e241 100644 --- a/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue +++ b/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue @@ -20,6 +20,7 @@ import type { import { sanitizeHtml } from '@/utils/htmlUtils'; import { MAX_DISPLAY_DATA_SIZE, NEW_ASSISTANT_SESSION_MODAL, VIEWS } from '@/constants'; import type { BaseTextKey } from '@n8n/i18n'; +import { useChatPanelStore } from '@/features/assistant/chatPanel.store'; import { useAssistantStore } from '@/features/assistant/assistant.store'; import type { ChatRequest } from '@/features/assistant/assistant.types'; import { useUIStore } from '@/stores/ui.store'; @@ -32,6 +33,7 @@ import { N8nIconButton, N8nTooltip, } from '@n8n/design-system'; + type Props = { // TODO: .node can be undefined error: NodeError | NodeApiError | NodeOperationError; @@ -52,6 +54,7 @@ const ndvStore = useNDVStore(); const workflowsStore = useWorkflowsStore(); const rootStore = useRootStore(); const assistantStore = useAssistantStore(); +const chatPanelStore = useChatPanelStore(); const uiStore = useUIStore(); const workflowId = computed(() => workflowsStore.workflowId); @@ -444,7 +447,7 @@ async function onAskAssistantClick() { }); return; } - await assistantStore.initErrorHelper(errorHelp); + await chatPanelStore.openWithErrorHelper(errorHelp); assistantStore.trackUserOpenedAssistant({ source: 'error', task: 'error', @@ -754,6 +757,7 @@ async function onAskAssistantClick() { margin-bottom: var(--spacing-xs); margin-top: var(--spacing-xs); flex-direction: row-reverse; + span { margin-right: var(--spacing-5xs); margin-left: var(--spacing-5xs); @@ -776,6 +780,7 @@ async function onAskAssistantClick() { width: 100%; overflow: auto; background: var(--color-background-light); + code { font-size: var(--font-size-s); } @@ -797,6 +802,7 @@ async function onAskAssistantClick() { align-items: center; justify-content: center; cursor: pointer; + &:hover { color: var(--color-primary); } diff --git a/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue b/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue index 41ac2032148..55390305184 100644 --- a/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue +++ b/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue @@ -20,7 +20,8 @@ import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue'; import { useI18n } from '@n8n/i18n'; import { useTelemetry } from '@/composables/useTelemetry'; import { useAssistantStore } from '@/features/assistant/assistant.store'; -import { useBuilderStore } from '@/stores/builder.store'; +import { useBuilderStore } from '@/features/assistant/builder.store'; +import { useChatPanelStore } from '@/features/assistant/chatPanel.store'; import { N8nAssistantIcon, N8nButton, N8nIconButton, N8nTooltip } from '@n8n/design-system'; @@ -51,6 +52,7 @@ const i18n = useI18n(); const telemetry = useTelemetry(); const assistantStore = useAssistantStore(); const builderStore = useBuilderStore(); +const chatPanelStore = useChatPanelStore(); const { getAddedNodesAndConnections } = useActions(); @@ -101,11 +103,11 @@ function toggleFocusPanel() { async function onAskAssistantButtonClick() { if (builderStore.isAIBuilderEnabled) { - await builderStore.toggleChat(); + await chatPanelStore.toggle({ mode: 'builder' }); } else { - assistantStore.toggleChat(); + await chatPanelStore.toggle({ mode: 'assistant' }); } - if (builderStore.isAssistantOpen || assistantStore.isAssistantOpen) { + if (chatPanelStore.isOpen) { assistantStore.trackUserOpenedAssistant({ source: 'canvas', task: 'placeholder', diff --git a/packages/frontend/editor-ui/src/components/Node/NodeCreator/NodeCreator.vue b/packages/frontend/editor-ui/src/components/Node/NodeCreator/NodeCreator.vue index 8d8d5573633..97a05481c96 100644 --- a/packages/frontend/editor-ui/src/components/Node/NodeCreator/NodeCreator.vue +++ b/packages/frontend/editor-ui/src/components/Node/NodeCreator/NodeCreator.vue @@ -12,8 +12,7 @@ import NodesListPanel from './Panel/NodesListPanel.vue'; import { useCredentialsStore } from '@/stores/credentials.store'; import { useUIStore } from '@/stores/ui.store'; import { DRAG_EVENT_DATA_KEY } from '@/constants'; -import { useAssistantStore } from '@/features/assistant/assistant.store'; -import { useBuilderStore } from '@/stores/builder.store'; +import { useChatPanelStore } from '@/features/assistant/chatPanel.store'; import type { NodeTypeSelectedPayload } from '@/Interface'; import { onClickOutside } from '@vueuse/core'; @@ -37,8 +36,7 @@ const emit = defineEmits<{ nodeTypeSelected: [value: NodeTypeSelectedPayload[]]; }>(); const uiStore = useUIStore(); -const assistantStore = useAssistantStore(); -const builderStore = useBuilderStore(); +const chatPanelStore = useChatPanelStore(); const { setShowScrim, setActions, setMergeNodes } = useNodeCreatorStore(); const { generateMergedNodesAndActions } = useActionsGenerator(); @@ -58,11 +56,8 @@ const nodeCreatorInlineStyle = computed(() => { }); function getRightOffset() { - if (assistantStore.isAssistantOpen) { - return assistantStore.chatWidth; - } - if (builderStore.isAssistantOpen) { - return builderStore.chatWidth; + if (chatPanelStore.isOpen) { + return chatPanelStore.width; } return 0; diff --git a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeChoicePrompt.vue b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeChoicePrompt.vue index 200748cc938..b3e5308cf89 100644 --- a/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeChoicePrompt.vue +++ b/packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeChoicePrompt.vue @@ -1,15 +1,20 @@ @@ -55,12 +60,7 @@ async function onBuildWithAIClick() {
-
+