diff --git a/packages/@n8n/api-types/src/frontend-settings.ts b/packages/@n8n/api-types/src/frontend-settings.ts index 8e4a440655b..33a56242878 100644 --- a/packages/@n8n/api-types/src/frontend-settings.ts +++ b/packages/@n8n/api-types/src/frontend-settings.ts @@ -136,6 +136,7 @@ export interface FrontendSettings { }; aiBuilder: { enabled: boolean; + setup: boolean; }; deployment: { type: string; diff --git a/packages/@n8n/config/src/configs/ai-builder.config.ts b/packages/@n8n/config/src/configs/ai-builder.config.ts new file mode 100644 index 00000000000..96a88585bda --- /dev/null +++ b/packages/@n8n/config/src/configs/ai-builder.config.ts @@ -0,0 +1,8 @@ +import { Config, Env } from '../decorators'; + +@Config +export class AiBuilderConfig { + /** Keys for local service */ + @Env('N8N_AI_ANTHROPIC_KEY') + apiKey: string = ''; +} diff --git a/packages/@n8n/config/src/index.ts b/packages/@n8n/config/src/index.ts index 49829baaac5..ce1d1d288c6 100644 --- a/packages/@n8n/config/src/index.ts +++ b/packages/@n8n/config/src/index.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { AiAssistantConfig } from './configs/ai-assistant.config'; +import { AiBuilderConfig } from './configs/ai-builder.config'; import { AiConfig } from './configs/ai.config'; import { AuthConfig } from './configs/auth.config'; import { CacheConfig } from './configs/cache.config'; @@ -152,6 +153,9 @@ export class GlobalConfig { @Nested aiAssistant: AiAssistantConfig; + @Nested + aiBuilder: AiBuilderConfig; + @Nested tags: TagsConfig; diff --git a/packages/@n8n/config/test/config.test.ts b/packages/@n8n/config/test/config.test.ts index f820a72b32e..0c27c3b9789 100644 --- a/packages/@n8n/config/test/config.test.ts +++ b/packages/@n8n/config/test/config.test.ts @@ -347,6 +347,9 @@ describe('GlobalConfig', () => { aiAssistant: { baseUrl: '', }, + aiBuilder: { + apiKey: '', + }, tags: { disabled: false, }, diff --git a/packages/cli/src/services/__tests__/frontend.service.test.ts b/packages/cli/src/services/__tests__/frontend.service.test.ts index ec880fd5a80..42eeaa85952 100644 --- a/packages/cli/src/services/__tests__/frontend.service.test.ts +++ b/packages/cli/src/services/__tests__/frontend.service.test.ts @@ -284,6 +284,7 @@ describe('FrontendService', () => { expect(service.settings.aiBuilder).toEqual({ enabled: false, + setup: false, }); }); diff --git a/packages/cli/src/services/frontend.service.ts b/packages/cli/src/services/frontend.service.ts index 3aa1929fce7..7544a27f057 100644 --- a/packages/cli/src/services/frontend.service.ts +++ b/packages/cli/src/services/frontend.service.ts @@ -268,6 +268,7 @@ export class FrontendService { }, aiBuilder: { enabled: false, + setup: false, }, aiCredits: { enabled: false, @@ -425,7 +426,11 @@ export class FrontendService { this.settings.aiCredits.credits = this.license.getAiCredits(); } - this.settings.aiBuilder.enabled = isAiBuilderEnabled; + if (isAiBuilderEnabled) { + this.settings.aiBuilder.enabled = isAiBuilderEnabled; + this.settings.aiBuilder.setup = + !!this.globalConfig.aiAssistant.baseUrl || !!this.globalConfig.aiBuilder.apiKey; + } this.settings.mfa.enabled = this.globalConfig.mfa.enabled; diff --git a/packages/frontend/editor-ui/src/__tests__/defaults.ts b/packages/frontend/editor-ui/src/__tests__/defaults.ts index fc05ce64d41..55433ff99ea 100644 --- a/packages/frontend/editor-ui/src/__tests__/defaults.ts +++ b/packages/frontend/editor-ui/src/__tests__/defaults.ts @@ -137,6 +137,7 @@ export const defaultSettings: FrontendSettings = { }, aiBuilder: { enabled: false, + setup: false, }, workflowHistory: { pruneTime: 0, diff --git a/packages/frontend/editor-ui/src/components/Error/NodeErrorView.test.ts b/packages/frontend/editor-ui/src/components/Error/NodeErrorView.test.ts index 3cb1f3d0268..c80278d8a5d 100644 --- a/packages/frontend/editor-ui/src/components/Error/NodeErrorView.test.ts +++ b/packages/frontend/editor-ui/src/components/Error/NodeErrorView.test.ts @@ -6,7 +6,7 @@ import { mockedStore } from '@/__tests__/utils'; import { createComponentRenderer } from '@/__tests__/render'; import type { IExecutionResponse } from '@/Interface'; import NodeErrorView from '@/components/Error/NodeErrorView.vue'; -import { useAssistantStore } from '@/features/assistant/assistant.store'; +import { useChatPanelStore } from '@/features/assistant/chatPanel.store'; import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useNDVStore } from '@/stores/ndv.store'; import { useWorkflowsStore } from '@/stores/workflows.store'; @@ -29,7 +29,7 @@ Object.defineProperty(window, 'open', { writable: true, }); -let mockAiAssistantStore: ReturnType>; +let mockChatPanelStore: ReturnType>; let mockNodeTypeStore: ReturnType>; let mockNDVStore: ReturnType>; let mockWorkflowsStore: ReturnType>; @@ -41,7 +41,7 @@ describe('NodeErrorView.vue', () => { beforeEach(() => { createTestingPinia(); - mockAiAssistantStore = mockedStore(useAssistantStore); + mockChatPanelStore = mockedStore(useChatPanelStore); mockNodeTypeStore = mockedStore(useNodeTypesStore); mockNDVStore = mockedStore(useNDVStore); mockWorkflowsStore = mockedStore(useWorkflowsStore); @@ -118,7 +118,7 @@ describe('NodeErrorView.vue', () => { hidden: true, })); - mockAiAssistantStore.canShowAssistantButtonsOnCanvas = true; + mockChatPanelStore.canShowAiButtonOnCanvas = true; const { queryByTestId } = renderComponent({ props: { @@ -177,7 +177,7 @@ describe('NodeErrorView.vue', () => { }); it('does not renders open node button when the error is in sub node', () => { - mockAiAssistantStore.canShowAssistantButtonsOnCanvas = true; + mockChatPanelStore.canShowAiButtonOnCanvas = true; const { getByTestId, queryByTestId } = renderComponent({ props: { error, diff --git a/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue b/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue index 48102ea693d..bd967d921d0 100644 --- a/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue +++ b/packages/frontend/editor-ui/src/components/Error/NodeErrorView.vue @@ -139,7 +139,7 @@ const isAskAssistantAvailable = computed(() => { } const isCustomNode = node.value.type === undefined || isCommunityPackageName(node.value.type); - return assistantStore.canShowAssistantButtonsOnCanvas && !isCustomNode && !nodeIsHidden(); + return chatPanelStore.canShowAiButtonOnCanvas && !isCustomNode && !nodeIsHidden(); }); const assistantAlreadyAsked = computed(() => { @@ -780,6 +780,7 @@ async function onAskAssistantClick() { width: 100%; overflow: auto; background: var(--color--background--light-2); + code { font-size: var(--font-size--sm); } diff --git a/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue b/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue index 8e24c87be27..cea169fb1d9 100644 --- a/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue +++ b/packages/frontend/editor-ui/src/components/Node/NodeCreation.vue @@ -191,7 +191,7 @@ function openCommandBar(event: MouseEvent) { @click="toggleFocusPanel" /> - + { setAssistantEnabled(false); expect(assistantStore.isAssistantEnabled).toBe(false); - expect(assistantStore.canShowAssistant).toBe(false); - expect(assistantStore.canShowAssistantButtonsOnCanvas).toBe(false); }); it('should show assistant if all conditions are met', () => { @@ -296,8 +294,6 @@ describe('AI Assistant store', () => { setAssistantEnabled(true); expect(assistantStore.isAssistantEnabled).toBe(true); - expect(assistantStore.canShowAssistant).toBe(true); - expect(assistantStore.canShowAssistantButtonsOnCanvas).toBe(true); }); it('should not show assistant if on a settings page', () => { @@ -306,8 +302,6 @@ describe('AI Assistant store', () => { setAssistantEnabled(true); expect(assistantStore.isAssistantEnabled).toBe(true); - expect(assistantStore.canShowAssistant).toBe(false); - expect(assistantStore.canShowAssistantButtonsOnCanvas).toBe(false); }); [VIEWS.PROJECTS_CREDENTIALS, VIEWS.TEMPLATE_SETUP, VIEWS.CREDENTIALS].forEach((view) => { @@ -317,8 +311,6 @@ describe('AI Assistant store', () => { setAssistantEnabled(true); expect(assistantStore.isAssistantEnabled).toBe(true); - expect(assistantStore.canShowAssistant).toBe(true); - expect(assistantStore.canShowAssistantButtonsOnCanvas).toBe(false); }); }); @@ -344,7 +336,6 @@ describe('AI Assistant store', () => { const assistantStore = useAssistantStore(); setAssistantEnabled(true); - expect(assistantStore.canShowAssistantButtonsOnCanvas).toBe(true); expect(assistantStore.hideAssistantFloatingButton).toBe(false); }); }); @@ -359,7 +350,6 @@ describe('AI Assistant store', () => { const assistantStore = useAssistantStore(); setAssistantEnabled(true); - expect(assistantStore.canShowAssistantButtonsOnCanvas).toBe(true); expect(assistantStore.hideAssistantFloatingButton).toBe(true); }); }); diff --git a/packages/frontend/editor-ui/src/features/assistant/assistant.store.ts b/packages/frontend/editor-ui/src/features/assistant/assistant.store.ts index 3e517bf6a23..6715747350d 100644 --- a/packages/frontend/editor-ui/src/features/assistant/assistant.store.ts +++ b/packages/frontend/editor-ui/src/features/assistant/assistant.store.ts @@ -86,22 +86,8 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { return !sessionStarted || sessionExplicitlyEnded; }); - const isAssistantOpen = computed( - () => - canShowAssistant.value && - chatPanelStateStore.isOpen && - chatPanelStateStore.activeMode === 'assistant', - ); - const isAssistantEnabled = computed(() => settings.isAiAssistantEnabled); - const canShowAssistant = computed( - () => isAssistantEnabled.value && ENABLED_VIEWS.includes(route.name as VIEWS), - ); - - const canShowAssistantButtonsOnCanvas = computed( - () => isAssistantEnabled.value && EDITABLE_CANVAS_VIEWS.includes(route.name as VIEWS), - ); const hideAssistantFloatingButton = computed( () => EDITABLE_CANVAS_VIEWS.includes(route.name as VIEWS) && !workflowsStore.activeNode(), ); @@ -115,9 +101,10 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { const isFloatingButtonShown = computed( () => - canShowAssistantButtonsOnCanvas.value && - !isAssistantOpen.value && - !hideAssistantFloatingButton.value, + !chatPanelStateStore.isOpen && + !hideAssistantFloatingButton.value && + isAssistantEnabled.value && + EDITABLE_CANVAS_VIEWS.includes(route.name as VIEWS), ); function resetAssistantChat() { @@ -782,13 +769,10 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { return { isAssistantEnabled, - canShowAssistantButtonsOnCanvas, hideAssistantFloatingButton, chatMessages, unreadCount, streaming, - isAssistantOpen, - canShowAssistant, currentSessionId, lastUnread, isSessionEnded, diff --git a/packages/frontend/editor-ui/src/features/assistant/builder.store.test.ts b/packages/frontend/editor-ui/src/features/assistant/builder.store.test.ts index 242ad5ff213..03d085af179 100644 --- a/packages/frontend/editor-ui/src/features/assistant/builder.store.test.ts +++ b/packages/frontend/editor-ui/src/features/assistant/builder.store.test.ts @@ -88,14 +88,6 @@ spy.mockImplementation( }) as unknown as Telemetry, ); -const setAssistantEnabled = (enabled: boolean) => { - settingsStore.setSettings( - merge({}, defaultSettings, { - aiAssistant: { enabled, setup: true }, - }), - ); -}; - const currentRouteName = ENABLED_VIEWS[0]; vi.mock('vue-router', () => ({ useRoute: vi.fn(() => @@ -287,7 +279,7 @@ describe('AI Builder store', () => { builderStore.sendChatMessage({ text: 'Add nodes and connect them' }); // Initially shows "aiAssistant.thinkingSteps.thinking" from prepareForStreaming - expect(builderStore.assistantThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); + expect(builderStore.builderThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); // First tool starts onMessageCallback({ @@ -304,7 +296,7 @@ describe('AI Builder store', () => { }); // Should show "aiAssistant.thinkingSteps.thinking" - expect(builderStore.assistantThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); + expect(builderStore.builderThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); // Second tool starts (different toolCallId) onMessageCallback({ @@ -321,7 +313,7 @@ describe('AI Builder store', () => { }); // Still showing "aiAssistant.thinkingSteps.thinking" with multiple tools - expect(builderStore.assistantThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); + expect(builderStore.builderThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); // First tool completes onMessageCallback({ @@ -338,7 +330,7 @@ describe('AI Builder store', () => { }); // Still "aiAssistant.thinkingSteps.thinking" because second tool is still running - expect(builderStore.assistantThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); + expect(builderStore.builderThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); // Second tool completes onMessageCallback({ @@ -355,14 +347,14 @@ describe('AI Builder store', () => { }); // Now should show "aiAssistant.thinkingSteps.thinking" because all tools completed - expect(builderStore.assistantThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); + expect(builderStore.builderThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); // Call onDone to stop streaming onDoneCallback(); // Message should persist after streaming ends expect(builderStore.streaming).toBe(false); - expect(builderStore.assistantThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); + expect(builderStore.builderThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); vi.useRealTimers(); }); @@ -404,12 +396,12 @@ describe('AI Builder store', () => { // Should show "aiAssistant.thinkingSteps.thinking" when tool completes await vi.waitFor(() => - expect(builderStore.assistantThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'), + expect(builderStore.builderThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'), ); // Should still show "aiAssistant.thinkingSteps.thinking" after workflow-updated await vi.waitFor(() => expect(builderStore.chatMessages).toHaveLength(3)); // user + tool + workflow - expect(builderStore.assistantThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); + expect(builderStore.builderThinkingMessage).toBe('aiAssistant.thinkingSteps.thinking'); // Verify streaming has ended expect(builderStore.streaming).toBe(false); @@ -441,25 +433,7 @@ describe('AI Builder store', () => { builderStore.resetBuilderChat(); expect(builderStore.chatMessages).toEqual([]); - expect(builderStore.assistantThinkingMessage).toBeUndefined(); - }); - - it('should not show builder if disabled in settings', () => { - const builderStore = useBuilderStore(); - - setAssistantEnabled(false); - expect(builderStore.isAssistantEnabled).toBe(false); - expect(builderStore.canShowAssistant).toBe(false); - expect(builderStore.canShowAssistantButtonsOnCanvas).toBe(false); - }); - - it('should show builder if all conditions are met', () => { - const builderStore = useBuilderStore(); - - setAssistantEnabled(true); - expect(builderStore.isAssistantEnabled).toBe(true); - expect(builderStore.canShowAssistant).toBe(true); - expect(builderStore.canShowAssistantButtonsOnCanvas).toBe(true); + expect(builderStore.builderThinkingMessage).toBeUndefined(); }); describe('isAIBuilderEnabled computed property', () => { @@ -737,7 +711,7 @@ describe('AI Builder store', () => { // Verify streaming state was reset expect(builderStore.streaming).toBe(false); - expect(builderStore.assistantThinkingMessage).toBeUndefined(); + expect(builderStore.builderThinkingMessage).toBeUndefined(); }); it('should abort previous request when sending new message', () => { diff --git a/packages/frontend/editor-ui/src/features/assistant/builder.store.ts b/packages/frontend/editor-ui/src/features/assistant/builder.store.ts index 3e5cf184396..5d1089c6eaa 100644 --- a/packages/frontend/editor-ui/src/features/assistant/builder.store.ts +++ b/packages/frontend/editor-ui/src/features/assistant/builder.store.ts @@ -3,7 +3,6 @@ import { DEFAULT_NEW_WORKFLOW_NAME, WORKFLOW_BUILDER_DEPRECATED_EXPERIMENT, WORKFLOW_BUILDER_RELEASE_EXPERIMENT, - EDITABLE_CANVAS_VIEWS, PLACEHOLDER_EMPTY_WORKFLOW_ID, } from '@/constants'; import { BUILDER_ENABLED_VIEWS } from './constants'; @@ -32,7 +31,6 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store'; import { useCredentialsStore } from '@/features/credentials/credentials.store'; import { getAuthTypeForNodeCredential, getMainAuthField } from '@/utils/nodeTypesUtils'; import { stringSizeInBytes } from '@/utils/typesUtils'; -import { useChatPanelStateStore } from './chatPanelState.store'; const INFINITE_CREDITS = -1; export const ENABLED_VIEWS = BUILDER_ENABLED_VIEWS; @@ -41,7 +39,7 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { // Core state const chatMessages = ref([]); const streaming = ref(false); - const assistantThinkingMessage = ref(); + const builderThinkingMessage = ref(); const streamingAbortController = ref(null); const initialGeneration = ref(false); const creditsQuota = ref(); @@ -49,7 +47,6 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { const hasMessages = ref(false); // Store dependencies - const chatPanelStateStore = useChatPanelStateStore(); const settings = useSettingsStore(); const rootStore = useRootStore(); const workflowsStore = useWorkflowsStore(); @@ -74,9 +71,6 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { getRunningTools, } = useBuilderMessages(); - // Computed properties - const isAssistantEnabled = computed(() => settings.isAiAssistantEnabled); - const trackingSessionId = computed(() => rootStore.pushRef); const workflowPrompt = computed(() => { @@ -86,20 +80,6 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { return firstUserMessage?.content; }); - const canShowAssistant = computed( - () => isAssistantEnabled.value && ENABLED_VIEWS.includes(route.name as VIEWS), - ); - - const canShowAssistantButtonsOnCanvas = computed( - () => isAssistantEnabled.value && EDITABLE_CANVAS_VIEWS.includes(route.name as VIEWS), - ); - - const isAssistantOpen = computed( - () => - canShowAssistant.value && - chatPanelStateStore.isOpen && - chatPanelStateStore.activeMode === 'builder', - ); const isAIBuilderEnabled = computed(() => { // Check license first @@ -156,13 +136,13 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { */ function resetBuilderChat() { chatMessages.value = clearMessages(); - assistantThinkingMessage.value = undefined; + builderThinkingMessage.value = undefined; initialGeneration.value = false; } // Message handling functions function addLoadingAssistantMessage(message: string) { - assistantThinkingMessage.value = message; + builderThinkingMessage.value = message; } function stopStreaming() { @@ -184,7 +164,7 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { assert(e instanceof Error); stopStreaming(); - assistantThinkingMessage.value = undefined; + builderThinkingMessage.value = undefined; if (e.name === 'AbortError') { // Handle abort errors as they are expected when stopping streaming @@ -346,10 +326,10 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { chatMessages.value = result.messages; if (result.shouldClearThinking) { - assistantThinkingMessage.value = undefined; + builderThinkingMessage.value = undefined; } else { // Always update thinking message, even when undefined (to clear it) - assistantThinkingMessage.value = result.thinkingMessage; + builderThinkingMessage.value = result.thinkingMessage; } }, () => stopStreaming(), @@ -579,13 +559,9 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => { // Public API return { // State - isAssistantEnabled, - canShowAssistantButtonsOnCanvas, chatMessages, streaming, - isAssistantOpen, - canShowAssistant, - assistantThinkingMessage, + builderThinkingMessage, isAIBuilderEnabled, workflowPrompt, toolMessages, diff --git a/packages/frontend/editor-ui/src/features/assistant/chatPanel.store.ts b/packages/frontend/editor-ui/src/features/assistant/chatPanel.store.ts index fa44d0a2ad5..45d9ce290b3 100644 --- a/packages/frontend/editor-ui/src/features/assistant/chatPanel.store.ts +++ b/packages/frontend/editor-ui/src/features/assistant/chatPanel.store.ts @@ -3,12 +3,13 @@ import { defineStore } from 'pinia'; import { STORES } from '@n8n/stores'; import { useUIStore } from '@/stores/ui.store'; import { useRoute } from 'vue-router'; -import { ASK_AI_SLIDE_OUT_DURATION_MS } from '@/constants'; +import { ASK_AI_SLIDE_OUT_DURATION_MS, EDITABLE_CANVAS_VIEWS } from '@/constants'; import type { VIEWS } from '@/constants'; import { ASSISTANT_ENABLED_VIEWS, BUILDER_ENABLED_VIEWS } from './constants'; import { useChatPanelStateStore, type ChatPanelMode } from './chatPanelState.store'; import { useAssistantStore } from './assistant.store'; import { useBuilderStore } from './builder.store'; +import { useSettingsStore } from '@/stores/settings.store'; import type { ICredentialType } from 'n8n-workflow'; import type { ChatRequest } from './assistant.types'; @@ -31,11 +32,18 @@ export const useChatPanelStore = defineStore(STORES.CHAT_PANEL, () => { const uiStore = useUIStore(); const route = useRoute(); const chatPanelStateStore = useChatPanelStateStore(); + const settingsStore = useSettingsStore(); // Computed const isAssistantModeActive = computed(() => chatPanelStateStore.activeMode === 'assistant'); const isBuilderModeActive = computed(() => chatPanelStateStore.activeMode === 'builder'); + const canShowAiButtonOnCanvas = computed( + () => + settingsStore.isAiAssistantOrBuilderEnabled && + EDITABLE_CANVAS_VIEWS.includes(route.name as VIEWS), + ); + // Actions async function open(options?: { mode?: ChatPanelMode }) { const mode = options?.mode; @@ -183,6 +191,7 @@ export const useChatPanelStore = defineStore(STORES.CHAT_PANEL, () => { // Computed isAssistantModeActive, isBuilderModeActive, + canShowAiButtonOnCanvas, // Actions open, close, diff --git a/packages/frontend/editor-ui/src/features/assistant/components/Agent/AskAssistantBuild.vue b/packages/frontend/editor-ui/src/features/assistant/components/Agent/AskAssistantBuild.vue index 0b551553ca9..0575c6f7074 100644 --- a/packages/frontend/editor-ui/src/features/assistant/components/Agent/AskAssistantBuild.vue +++ b/packages/frontend/editor-ui/src/features/assistant/components/Agent/AskAssistantBuild.vue @@ -51,7 +51,7 @@ const loadingMessage = computed(() => { return undefined; } - return builderStore.assistantThinkingMessage; + return builderStore.builderThinkingMessage; }); const currentRoute = computed(() => route.name); const showExecuteMessage = computed(() => { diff --git a/packages/frontend/editor-ui/src/features/assistant/components/AssistantsHub.vue b/packages/frontend/editor-ui/src/features/assistant/components/AssistantsHub.vue index eda84270c4b..fd558bec8d9 100644 --- a/packages/frontend/editor-ui/src/features/assistant/components/AssistantsHub.vue +++ b/packages/frontend/editor-ui/src/features/assistant/components/AssistantsHub.vue @@ -2,8 +2,9 @@ import { useBuilderStore } from '@/features/assistant/builder.store'; import { useChatPanelStore } from '@/features/assistant/chatPanel.store'; import { useAssistantStore } from '@/features/assistant/assistant.store'; +import { useSettingsStore } from '@/stores/settings.store'; import { useDebounce } from '@/composables/useDebounce'; -import { computed, onBeforeUnmount, ref } from 'vue'; +import { computed, onBeforeUnmount, onMounted, ref } from 'vue'; import SlideTransition from '@/components/transitions/SlideTransition.vue'; import AskAssistantBuild from './Agent/AskAssistantBuild.vue'; import AskAssistantChat from './Chat/AskAssistantChat.vue'; @@ -17,6 +18,7 @@ import HubSwitcher from '@/features/assistant/components/HubSwitcher.vue'; const builderStore = useBuilderStore(); const assistantStore = useAssistantStore(); const chatPanelStore = useChatPanelStore(); +const settingsStore = useSettingsStore(); const route = useRoute(); const askAssistantBuildRef = ref>(); @@ -29,6 +31,7 @@ const chatWidth = computed(() => chatPanelStore.width); const canToggleModes = computed(() => { const currentRoute = route?.name; return ( + settingsStore.isAiAssistantEnabled && builderStore.isAIBuilderEnabled && currentRoute && BUILDER_ENABLED_VIEWS.includes(currentRoute as VIEWS) @@ -95,6 +98,20 @@ const unsubscribeBuilderStore = builderStore.$onAction(({ name }) => { } }); +// Set default mode based on which flags are enabled +onMounted(() => { + // If toggle is not available (only one mode enabled), lock to the appropriate mode + if (!canToggleModes.value) { + if (builderStore.isAIBuilderEnabled) { + // Only builder is enabled, lock to builder mode + chatPanelStore.switchMode('builder'); + } else if (settingsStore.isAiAssistantEnabled) { + // Only assistant is enabled, lock to assistant mode + chatPanelStore.switchMode('assistant'); + } + } +}); + onBeforeUnmount(() => { unsubscribeAssistantStore(); unsubscribeBuilderStore(); diff --git a/packages/frontend/editor-ui/src/stores/settings.store.ts b/packages/frontend/editor-ui/src/stores/settings.store.ts index b389e71871b..34b19f48c88 100644 --- a/packages/frontend/editor-ui/src/stores/settings.store.ts +++ b/packages/frontend/editor-ui/src/stores/settings.store.ts @@ -91,7 +91,13 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => { const isAskAiEnabled = computed(() => settings.value.askAi?.enabled); - const isAiBuilderEnabled = computed(() => settings.value.aiBuilder?.enabled); + const isAiBuilderEnabled = computed( + () => settings.value.aiBuilder?.enabled && settings.value.aiBuilder?.setup, + ); + + const isAiAssistantOrBuilderEnabled = computed( + () => isAiAssistantEnabled.value || isAiBuilderEnabled.value, + ); const showSetupPage = computed(() => userManagement.value.showSetupOnFirstLoad); @@ -362,6 +368,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => { isCommunityPlan, isAskAiEnabled, isAiBuilderEnabled, + isAiAssistantOrBuilderEnabled, isAiCreditsEnabled, aiCreditsQuota, reset, diff --git a/packages/frontend/editor-ui/src/views/NodeView.vue b/packages/frontend/editor-ui/src/views/NodeView.vue index 877136e7417..d21df4daf79 100644 --- a/packages/frontend/editor-ui/src/views/NodeView.vue +++ b/packages/frontend/editor-ui/src/views/NodeView.vue @@ -1817,9 +1817,7 @@ watch( }; fallbackNodes.value = - builderStore.isAIBuilderEnabled && - builderStore.isAssistantEnabled && - builderStore.assistantMessages.length === 0 + builderStore.isAIBuilderEnabled && builderStore.assistantMessages.length === 0 ? [choicePromptItem] : [addNodesItem]; },