mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-24 23:22:38 +08:00
fix(ai-builder): Allow Anthropic key to be used in local development (no-changelog) (#20772)
This commit is contained in:
@@ -136,6 +136,7 @@ export interface FrontendSettings {
|
||||
};
|
||||
aiBuilder: {
|
||||
enabled: boolean;
|
||||
setup: boolean;
|
||||
};
|
||||
deployment: {
|
||||
type: string;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Config, Env } from '../decorators';
|
||||
|
||||
@Config
|
||||
export class AiBuilderConfig {
|
||||
/** Keys for local service */
|
||||
@Env('N8N_AI_ANTHROPIC_KEY')
|
||||
apiKey: string = '';
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -347,6 +347,9 @@ describe('GlobalConfig', () => {
|
||||
aiAssistant: {
|
||||
baseUrl: '',
|
||||
},
|
||||
aiBuilder: {
|
||||
apiKey: '',
|
||||
},
|
||||
tags: {
|
||||
disabled: false,
|
||||
},
|
||||
|
||||
@@ -284,6 +284,7 @@ describe('FrontendService', () => {
|
||||
|
||||
expect(service.settings.aiBuilder).toEqual({
|
||||
enabled: false,
|
||||
setup: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ export const defaultSettings: FrontendSettings = {
|
||||
},
|
||||
aiBuilder: {
|
||||
enabled: false,
|
||||
setup: false,
|
||||
},
|
||||
workflowHistory: {
|
||||
pruneTime: 0,
|
||||
|
||||
@@ -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<typeof mockedStore<typeof useAssistantStore>>;
|
||||
let mockChatPanelStore: ReturnType<typeof mockedStore<typeof useChatPanelStore>>;
|
||||
let mockNodeTypeStore: ReturnType<typeof mockedStore<typeof useNodeTypesStore>>;
|
||||
let mockNDVStore: ReturnType<typeof mockedStore<typeof useNDVStore>>;
|
||||
let mockWorkflowsStore: ReturnType<typeof mockedStore<typeof useWorkflowsStore>>;
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ function openCommandBar(event: MouseEvent) {
|
||||
@click="toggleFocusPanel"
|
||||
/>
|
||||
</KeyboardShortcutTooltip>
|
||||
<N8nTooltip v-if="assistantStore.canShowAssistantButtonsOnCanvas" placement="left">
|
||||
<N8nTooltip v-if="chatPanelStore.canShowAiButtonOnCanvas" placement="left">
|
||||
<template #content> {{ i18n.baseText('aiAssistant.tooltip') }}</template>
|
||||
<N8nButton
|
||||
type="tertiary"
|
||||
|
||||
@@ -287,8 +287,6 @@ describe('AI Assistant store', () => {
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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<ChatUI.AssistantMessage[]>([]);
|
||||
const streaming = ref<boolean>(false);
|
||||
const assistantThinkingMessage = ref<string | undefined>();
|
||||
const builderThinkingMessage = ref<string | undefined>();
|
||||
const streamingAbortController = ref<AbortController | null>(null);
|
||||
const initialGeneration = ref<boolean>(false);
|
||||
const creditsQuota = ref<number | undefined>();
|
||||
@@ -49,7 +47,6 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => {
|
||||
const hasMessages = ref<boolean>(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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ const loadingMessage = computed(() => {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return builderStore.assistantThinkingMessage;
|
||||
return builderStore.builderThinkingMessage;
|
||||
});
|
||||
const currentRoute = computed(() => route.name);
|
||||
const showExecuteMessage = computed(() => {
|
||||
|
||||
@@ -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<InstanceType<typeof AskAssistantBuild>>();
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1817,9 +1817,7 @@ watch(
|
||||
};
|
||||
|
||||
fallbackNodes.value =
|
||||
builderStore.isAIBuilderEnabled &&
|
||||
builderStore.isAssistantEnabled &&
|
||||
builderStore.assistantMessages.length === 0
|
||||
builderStore.isAIBuilderEnabled && builderStore.assistantMessages.length === 0
|
||||
? [choicePromptItem]
|
||||
: [addNodesItem];
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user