fix(editor): Update easy AI workflow setup instructions when user is in free AI credits experiment (no-changelog) (#12479)

This commit is contained in:
Ricardo Espinoza 2025-01-08 01:59:08 -05:00 committed by GitHub
parent 10148c9ad3
commit 6f00c74c1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 354 additions and 177 deletions

View file

@ -5,11 +5,15 @@ import {
WorkflowPage, WorkflowPage,
visitPublicApiPage, visitPublicApiPage,
getPublicApiUpgradeCTA, getPublicApiUpgradeCTA,
WorkflowsPage,
} from '../pages'; } from '../pages';
const NUMBER_OF_AI_CREDITS = 100;
const mainSidebar = new MainSidebar(); const mainSidebar = new MainSidebar();
const bannerStack = new BannerStack(); const bannerStack = new BannerStack();
const workflowPage = new WorkflowPage(); const workflowPage = new WorkflowPage();
const workflowsPage = new WorkflowsPage();
describe('Cloud', () => { describe('Cloud', () => {
before(() => { before(() => {
@ -22,6 +26,10 @@ describe('Cloud', () => {
cy.overrideSettings({ cy.overrideSettings({
deployment: { type: 'cloud' }, deployment: { type: 'cloud' },
n8nMetadata: { userId: '1' }, n8nMetadata: { userId: '1' },
aiCredits: {
enabled: true,
credits: NUMBER_OF_AI_CREDITS,
},
}); });
cy.intercept('GET', '/rest/admin/cloud-plan', planData).as('getPlanData'); cy.intercept('GET', '/rest/admin/cloud-plan', planData).as('getPlanData');
cy.intercept('GET', '/rest/cloud/proxy/user/me', {}).as('getCloudUserInfo'); cy.intercept('GET', '/rest/cloud/proxy/user/me', {}).as('getCloudUserInfo');
@ -64,4 +72,66 @@ describe('Cloud', () => {
getPublicApiUpgradeCTA().should('be.visible'); getPublicApiUpgradeCTA().should('be.visible');
}); });
}); });
describe('Easy AI workflow experiment', () => {
it('should not show option to take you to the easy AI workflow if experiment is control', () => {
window.localStorage.setItem(
'N8N_EXPERIMENT_OVERRIDES',
JSON.stringify({ '026_easy_ai_workflow': 'control' }),
);
cy.visit(workflowsPage.url);
cy.getByTestId('easy-ai-workflow-card').should('not.exist');
});
it('should show option to take you to the easy AI workflow if experiment is variant', () => {
window.localStorage.setItem(
'N8N_EXPERIMENT_OVERRIDES',
JSON.stringify({ '026_easy_ai_workflow': 'variant' }),
);
cy.visit(workflowsPage.url);
cy.getByTestId('easy-ai-workflow-card').should('to.exist');
});
it('should show default instructions if free AI credits experiment is control', () => {
window.localStorage.setItem(
'N8N_EXPERIMENT_OVERRIDES',
JSON.stringify({ '027_free_openai_calls': 'control', '026_easy_ai_workflow': 'variant' }),
);
cy.visit(workflowsPage.url);
cy.getByTestId('easy-ai-workflow-card').click();
workflowPage.getters
.stickies()
.eq(0)
.should(($el) => {
expect($el).contains.text('Set up your OpenAI credentials in the OpenAI Model node');
});
});
it('should show updated instructions if free AI credits experiment is variant', () => {
window.localStorage.setItem(
'N8N_EXPERIMENT_OVERRIDES',
JSON.stringify({ '027_free_openai_calls': 'variant', '026_easy_ai_workflow': 'variant' }),
);
cy.visit(workflowsPage.url);
cy.getByTestId('easy-ai-workflow-card').click();
workflowPage.getters
.stickies()
.eq(0)
.should(($el) => {
expect($el).contains.text(
`Claim your free ${NUMBER_OF_AI_CREDITS} OpenAI calls in the OpenAI model node`,
);
});
});
});
}); });

View file

@ -18,7 +18,7 @@ import type { PushMessage } from '@n8n/api-types';
import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useToast } from '@/composables/useToast'; import { useToast } from '@/composables/useToast';
import { WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants'; import { AI_CREDITS_EXPERIMENT, WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants';
import { getTriggerNodeServiceName } from '@/utils/nodeTypesUtils'; import { getTriggerNodeServiceName } from '@/utils/nodeTypesUtils';
import { codeNodeEditorEventBus, globalLinkActionsEventBus } from '@/event-bus'; import { codeNodeEditorEventBus, globalLinkActionsEventBus } from '@/event-bus';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
@ -36,8 +36,9 @@ import type { PushMessageQueueItem } from '@/types';
import { useAssistantStore } from '@/stores/assistant.store'; import { useAssistantStore } from '@/stores/assistant.store';
import NodeExecutionErrorMessage from '@/components/NodeExecutionErrorMessage.vue'; import NodeExecutionErrorMessage from '@/components/NodeExecutionErrorMessage.vue';
import type { IExecutionResponse } from '@/Interface'; import type { IExecutionResponse } from '@/Interface';
import { EASY_AI_WORKFLOW_JSON } from '@/constants.workflows';
import { clearPopupWindowState } from '../utils/executionUtils'; import { clearPopupWindowState } from '../utils/executionUtils';
import { usePostHog } from '@/stores/posthog.store';
import { getEasyAiWorkflowJson } from '@/utils/easyAiWorkflowUtils';
export function usePushConnection({ router }: { router: ReturnType<typeof useRouter> }) { export function usePushConnection({ router }: { router: ReturnType<typeof useRouter> }) {
const workflowHelpers = useWorkflowHelpers({ router }); const workflowHelpers = useWorkflowHelpers({ router });
@ -54,6 +55,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
const uiStore = useUIStore(); const uiStore = useUIStore();
const workflowsStore = useWorkflowsStore(); const workflowsStore = useWorkflowsStore();
const assistantStore = useAssistantStore(); const assistantStore = useAssistantStore();
const posthogStore = usePostHog();
const retryTimeout = ref<NodeJS.Timeout | null>(null); const retryTimeout = ref<NodeJS.Timeout | null>(null);
const pushMessageQueue = ref<PushMessageQueueItem[]>([]); const pushMessageQueue = ref<PushMessageQueueItem[]>([]);
@ -205,8 +207,13 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
clearPopupWindowState(); clearPopupWindowState();
const workflow = workflowsStore.getWorkflowById(receivedData.data.workflowId); const workflow = workflowsStore.getWorkflowById(receivedData.data.workflowId);
if (workflow?.meta?.templateId) { if (workflow?.meta?.templateId) {
const isEasyAIWorkflow = const isAiCreditsExperimentEnabled =
workflow.meta.templateId === EASY_AI_WORKFLOW_JSON.meta.templateId; posthogStore.getVariant(AI_CREDITS_EXPERIMENT.name) === AI_CREDITS_EXPERIMENT.variant;
const easyAiWorkflowJson = getEasyAiWorkflowJson({
isInstanceInAiFreeCreditsExperiment: isAiCreditsExperimentEnabled,
withOpenAiFreeCredits: settingsStore.aiCreditsQuota,
});
const isEasyAIWorkflow = workflow.meta.templateId === easyAiWorkflowJson.meta.templateId;
if (isEasyAIWorkflow) { if (isEasyAIWorkflow) {
telemetry.track( telemetry.track(
'User executed test AI workflow', 'User executed test AI workflow',

View file

@ -1,172 +1,6 @@
import { NodeConnectionType } from 'n8n-workflow'; import { NodeConnectionType } from 'n8n-workflow';
import type { INodeUi, WorkflowDataWithTemplateId } from './Interface'; import type { INodeUi, WorkflowDataWithTemplateId } from './Interface';
export const EASY_AI_WORKFLOW_JSON: WorkflowDataWithTemplateId = {
name: 'Demo: My first AI Agent in n8n',
meta: {
templateId: 'PT1i+zU92Ii5O2XCObkhfHJR5h9rNJTpiCIkYJk9jHU=',
},
nodes: [
{
id: '0d7e4666-bc0e-489a-9e8f-a5ef191f4954',
name: 'Google Calendar',
type: 'n8n-nodes-base.googleCalendarTool',
typeVersion: 1.2,
position: [880, 220],
parameters: {
operation: 'getAll',
calendar: {
__rl: true,
mode: 'list',
},
returnAll: true,
options: {
timeMin:
"={{ $fromAI('after', 'The earliest datetime we want to look for events for') }}",
timeMax: "={{ $fromAI('before', 'The latest datetime we want to look for events for') }}",
query:
"={{ $fromAI('query', 'The search query to look for in the calendar. Leave empty if no search query is needed') }}",
singleEvents: true,
},
},
},
{
id: '5b410409-5b0b-47bd-b413-5b9b1000a063',
name: 'When chat message received',
type: '@n8n/n8n-nodes-langchain.chatTrigger',
typeVersion: 1.1,
position: [360, 20],
webhookId: 'a889d2ae-2159-402f-b326-5f61e90f602e',
parameters: {
options: {},
},
},
{
id: '29963449-1dc1-487d-96f2-7ff0a5c3cd97',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [560, 20],
parameters: {
options: {
systemMessage:
"=You're a helpful assistant that the user to answer questions about their calendar.\n\nToday is {{ $now.format('cccc') }} the {{ $now.format('yyyy-MM-dd HH:mm') }}.",
},
},
},
{
id: 'eae35513-07c2-4de2-a795-a153b6934c1b',
name: 'Sticky Note',
type: 'n8n-nodes-base.stickyNote',
typeVersion: 1,
position: [0, 0],
parameters: {
content:
'## 👋 Welcome to n8n!\nThis example shows how to build an AI Agent that interacts with your \ncalendar.\n\n### 1. Connect your accounts\n- Set up your [OpenAI credentials](https://docs.n8n.io/integrations/builtin/credentials/openai/?utm_source=n8n_app&utm_medium=credential_settings&utm_campaign=create_new_credentials_modal) in the `OpenAI Model` node\n- Connect your Google account in the `Google Calendar` node credentials section\n\n### 2. Ready to test it?\nClick Chat below and start asking questions! For example you can try `What meetings do I have today?`',
height: 389,
width: 319,
color: 6,
},
},
{
id: '68b59889-7aca-49fd-a49b-d86fa6239b96',
name: 'Sticky Note1',
type: 'n8n-nodes-base.stickyNote',
typeVersion: 1,
position: [820, 200],
parameters: {
content:
"\n\n\n\n\n\n\n\n\n\n\n\nDon't have **Google Calendar**? Simply exchange this with the **Microsoft Outlook** or other tools",
height: 253,
width: 226,
color: 7,
},
},
{
id: 'cbaedf86-9153-4778-b893-a7e50d3e04ba',
name: 'OpenAI Model',
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
typeVersion: 1,
position: [520, 220],
parameters: {
options: {},
},
},
{
id: '75481370-bade-4d90-a878-3a3b0201edcc',
name: 'Memory',
type: '@n8n/n8n-nodes-langchain.memoryBufferWindow',
typeVersion: 1.3,
position: [680, 220],
parameters: {},
},
{
id: '907552eb-6e0f-472e-9d90-4513a67a31db',
name: 'Sticky Note3',
type: 'n8n-nodes-base.stickyNote',
typeVersion: 1,
position: [0, 400],
parameters: {
content:
'### Want to learn more?\nWant to learn more about AI and how to apply it best in n8n? Have a look at our [new tutorial series on YouTube](https://www.youtube.com/watch?v=yzvLfHb0nqE&lc).',
height: 100,
width: 317,
color: 6,
},
},
] as INodeUi[],
connections: {
'Google Calendar': {
ai_tool: [
[
{
node: 'AI Agent',
type: NodeConnectionType.AiTool,
index: 0,
},
],
],
},
'When chat message received': {
main: [
[
{
node: 'AI Agent',
type: NodeConnectionType.Main,
index: 0,
},
],
],
},
'OpenAI Model': {
ai_languageModel: [
[
{
node: 'AI Agent',
type: NodeConnectionType.AiLanguageModel,
index: 0,
},
],
],
},
Memory: {
ai_memory: [
[
{
node: 'AI Agent',
type: NodeConnectionType.AiMemory,
index: 0,
},
],
],
},
},
settings: {
executionOrder: 'v1',
},
pinData: {},
};
export const SAMPLE_SUBWORKFLOW_WORKFLOW: WorkflowDataWithTemplateId = { export const SAMPLE_SUBWORKFLOW_WORKFLOW: WorkflowDataWithTemplateId = {
name: 'My Sub-Workflow', name: 'My Sub-Workflow',
meta: { meta: {

View file

@ -0,0 +1,38 @@
import { describe, it, expect } from 'vitest';
import { getEasyAiWorkflowJson } from './easyAiWorkflowUtils';
describe('getEasyAiWorkflowJson', () => {
it('should update sticky note content for AI free credits experiment', () => {
const workflow = getEasyAiWorkflowJson({
isInstanceInAiFreeCreditsExperiment: true,
withOpenAiFreeCredits: 25,
});
if (!workflow?.nodes) fail();
const stickyNote = workflow.nodes.find(
(node) => node.type === 'n8n-nodes-base.stickyNote' && node.name === 'Sticky Note',
);
expect(stickyNote?.parameters.content).toContain(
'Claim your `free` 25 OpenAI calls in the `OpenAI model` node',
);
});
it('should show default content when not in AI free credits experiment', () => {
const workflow = getEasyAiWorkflowJson({
isInstanceInAiFreeCreditsExperiment: false,
withOpenAiFreeCredits: 0,
});
if (!workflow?.nodes) fail();
const stickyNote = workflow.nodes.find(
(node) => node.type === 'n8n-nodes-base.stickyNote' && node.name === 'Sticky Note',
);
expect(stickyNote?.parameters.content).toContain(
'Set up your [OpenAI credentials](https://docs.n8n.io/integrations/builtin/credentials/openai/?utm_source=n8n_app&utm_medium=credential_settings&utm_campaign=create_new_credentials_modal) in the `OpenAI Model` node',
);
});
});

View file

@ -0,0 +1,193 @@
import type { INodeUi, WorkflowDataWithTemplateId } from '@/Interface';
import { NodeConnectionType } from 'n8n-workflow';
/**
* Generates a workflow JSON object for an AI Agent in n8n.
*
* @param {Object} params - The parameters for generating the workflow JSON.
* @param {boolean} params.isInstanceInAiFreeCreditsExperiment - Indicates if the instance is part of the AI free credits experiment.
* @param {number} params.withOpenAiFreeCredits - The number of free OpenAI calls available.
*
* @remarks
* This function can be deleted once the free AI credits experiment is removed.
*/
export const getEasyAiWorkflowJson = ({
isInstanceInAiFreeCreditsExperiment,
withOpenAiFreeCredits,
}: {
withOpenAiFreeCredits: number;
isInstanceInAiFreeCreditsExperiment: boolean;
}): WorkflowDataWithTemplateId => {
let instructionsFirstStep =
'Set up your [OpenAI credentials](https://docs.n8n.io/integrations/builtin/credentials/openai/?utm_source=n8n_app&utm_medium=credential_settings&utm_campaign=create_new_credentials_modal) in the `OpenAI Model` node';
if (isInstanceInAiFreeCreditsExperiment) {
instructionsFirstStep = `Claim your \`free\` ${withOpenAiFreeCredits} OpenAI calls in the \`OpenAI model\` node`;
}
return {
name: 'Demo: My first AI Agent in n8n',
meta: {
templateId: 'PT1i+zU92Ii5O2XCObkhfHJR5h9rNJTpiCIkYJk9jHU=',
},
nodes: [
{
id: '0d7e4666-bc0e-489a-9e8f-a5ef191f4954',
name: 'Google Calendar',
type: 'n8n-nodes-base.googleCalendarTool',
typeVersion: 1.2,
position: [880, 220],
parameters: {
operation: 'getAll',
calendar: {
__rl: true,
mode: 'list',
},
returnAll: true,
options: {
timeMin:
"={{ $fromAI('after', 'The earliest datetime we want to look for events for') }}",
timeMax:
"={{ $fromAI('before', 'The latest datetime we want to look for events for') }}",
query:
"={{ $fromAI('query', 'The search query to look for in the calendar. Leave empty if no search query is needed') }}",
singleEvents: true,
},
},
},
{
id: '5b410409-5b0b-47bd-b413-5b9b1000a063',
name: 'When chat message received',
type: '@n8n/n8n-nodes-langchain.chatTrigger',
typeVersion: 1.1,
position: [360, 20],
webhookId: 'a889d2ae-2159-402f-b326-5f61e90f602e',
parameters: {
options: {},
},
},
{
id: '29963449-1dc1-487d-96f2-7ff0a5c3cd97',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [560, 20],
parameters: {
options: {
systemMessage:
"=You're a helpful assistant that the user to answer questions about their calendar.\n\nToday is {{ $now.format('cccc') }} the {{ $now.format('yyyy-MM-dd HH:mm') }}.",
},
},
},
{
id: 'eae35513-07c2-4de2-a795-a153b6934c1b',
name: 'Sticky Note',
type: 'n8n-nodes-base.stickyNote',
typeVersion: 1,
position: [0, 0],
parameters: {
content: `## 👋 Welcome to n8n!\nThis example shows how to build an AI Agent that interacts with your \ncalendar.\n\n### 1. Connect your accounts\n- ${instructionsFirstStep} \n- Connect your Google account in the \`Google Calendar\` node credentials section\n\n### 2. Ready to test it?\nClick Chat below and start asking questions! For example you can try \`What meetings do I have today?\``,
height: 389,
width: 319,
color: 6,
},
},
{
id: '68b59889-7aca-49fd-a49b-d86fa6239b96',
name: 'Sticky Note1',
type: 'n8n-nodes-base.stickyNote',
typeVersion: 1,
position: [820, 200],
parameters: {
content:
"\n\n\n\n\n\n\n\n\n\n\n\nDon't have **Google Calendar**? Simply exchange this with the **Microsoft Outlook** or other tools",
height: 253,
width: 226,
color: 7,
},
},
{
id: 'cbaedf86-9153-4778-b893-a7e50d3e04ba',
name: 'OpenAI Model',
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
typeVersion: 1,
position: [520, 220],
parameters: {
options: {},
},
},
{
id: '75481370-bade-4d90-a878-3a3b0201edcc',
name: 'Memory',
type: '@n8n/n8n-nodes-langchain.memoryBufferWindow',
typeVersion: 1.3,
position: [680, 220],
parameters: {},
},
{
id: '907552eb-6e0f-472e-9d90-4513a67a31db',
name: 'Sticky Note3',
type: 'n8n-nodes-base.stickyNote',
typeVersion: 1,
position: [0, 400],
parameters: {
content:
'### Want to learn more?\nWant to learn more about AI and how to apply it best in n8n? Have a look at our [new tutorial series on YouTube](https://www.youtube.com/watch?v=yzvLfHb0nqE&lc).',
height: 100,
width: 317,
color: 6,
},
},
] as INodeUi[],
connections: {
'Google Calendar': {
ai_tool: [
[
{
node: 'AI Agent',
type: NodeConnectionType.AiTool,
index: 0,
},
],
],
},
'When chat message received': {
main: [
[
{
node: 'AI Agent',
type: NodeConnectionType.Main,
index: 0,
},
],
],
},
'OpenAI Model': {
ai_languageModel: [
[
{
node: 'AI Agent',
type: NodeConnectionType.AiLanguageModel,
index: 0,
},
],
],
},
Memory: {
ai_memory: [
[
{
node: 'AI Agent',
type: NodeConnectionType.AiMemory,
index: 0,
},
],
],
},
},
settings: {
executionOrder: 'v1',
},
pinData: {},
};
};

View file

@ -62,6 +62,7 @@ import {
STICKY_NODE_TYPE, STICKY_NODE_TYPE,
VALID_WORKFLOW_IMPORT_URL_REGEX, VALID_WORKFLOW_IMPORT_URL_REGEX,
VIEWS, VIEWS,
AI_CREDITS_EXPERIMENT,
} from '@/constants'; } from '@/constants';
import { useSourceControlStore } from '@/stores/sourceControl.store'; import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useNodeCreatorStore } from '@/stores/nodeCreator.store'; import { useNodeCreatorStore } from '@/stores/nodeCreator.store';
@ -85,6 +86,7 @@ import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { useTelemetry } from '@/composables/useTelemetry'; import { useTelemetry } from '@/composables/useTelemetry';
import { useHistoryStore } from '@/stores/history.store'; import { useHistoryStore } from '@/stores/history.store';
import { useProjectsStore } from '@/stores/projects.store'; import { useProjectsStore } from '@/stores/projects.store';
import { usePostHog } from '@/stores/posthog.store';
import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useExecutionDebugging } from '@/composables/useExecutionDebugging'; import { useExecutionDebugging } from '@/composables/useExecutionDebugging';
import { useUsersStore } from '@/stores/users.store'; import { useUsersStore } from '@/stores/users.store';
@ -108,7 +110,7 @@ import { getResourcePermissions } from '@/permissions';
import NodeViewUnfinishedWorkflowMessage from '@/components/NodeViewUnfinishedWorkflowMessage.vue'; import NodeViewUnfinishedWorkflowMessage from '@/components/NodeViewUnfinishedWorkflowMessage.vue';
import { createCanvasConnectionHandleString } from '@/utils/canvasUtilsV2'; import { createCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
import { isValidNodeConnectionType } from '@/utils/typeGuards'; import { isValidNodeConnectionType } from '@/utils/typeGuards';
import { EASY_AI_WORKFLOW_JSON } from '@/constants.workflows'; import { getEasyAiWorkflowJson } from '@/utils/easyAiWorkflowUtils';
const LazyNodeCreation = defineAsyncComponent( const LazyNodeCreation = defineAsyncComponent(
async () => await import('@/components/Node/NodeCreation.vue'), async () => await import('@/components/Node/NodeCreation.vue'),
@ -155,6 +157,7 @@ const tagsStore = useTagsStore();
const pushConnectionStore = usePushConnectionStore(); const pushConnectionStore = usePushConnectionStore();
const ndvStore = useNDVStore(); const ndvStore = useNDVStore();
const templatesStore = useTemplatesStore(); const templatesStore = useTemplatesStore();
const posthogStore = usePostHog();
const canvasEventBus = createEventBus<CanvasEventBusEvents>(); const canvasEventBus = createEventBus<CanvasEventBusEvents>();
@ -325,7 +328,14 @@ async function initializeRoute(force = false) {
const loadWorkflowFromJSON = route.query.fromJson === 'true'; const loadWorkflowFromJSON = route.query.fromJson === 'true';
if (loadWorkflowFromJSON) { if (loadWorkflowFromJSON) {
await openTemplateFromWorkflowJSON(EASY_AI_WORKFLOW_JSON); const isAiCreditsExperimentEnabled =
posthogStore.getVariant(AI_CREDITS_EXPERIMENT.name) === AI_CREDITS_EXPERIMENT.variant;
const easyAiWorkflowJson = getEasyAiWorkflowJson({
isInstanceInAiFreeCreditsExperiment: isAiCreditsExperimentEnabled,
withOpenAiFreeCredits: settingsStore.aiCreditsQuota,
});
await openTemplateFromWorkflowJSON(easyAiWorkflowJson);
} else { } else {
await openWorkflowTemplate(templateId.toString()); await openWorkflowTemplate(templateId.toString());
} }

View file

@ -181,7 +181,8 @@ import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
import { getResourcePermissions } from '@/permissions'; import { getResourcePermissions } from '@/permissions';
import { useBeforeUnload } from '@/composables/useBeforeUnload'; import { useBeforeUnload } from '@/composables/useBeforeUnload';
import NodeViewUnfinishedWorkflowMessage from '@/components/NodeViewUnfinishedWorkflowMessage.vue'; import NodeViewUnfinishedWorkflowMessage from '@/components/NodeViewUnfinishedWorkflowMessage.vue';
import { EASY_AI_WORKFLOW_JSON } from '@/constants.workflows'; import { AI_CREDITS_EXPERIMENT } from '@/constants';
import { getEasyAiWorkflowJson } from '@/utils/easyAiWorkflowUtils';
interface AddNodeOptions { interface AddNodeOptions {
position?: XYPosition; position?: XYPosition;
@ -220,6 +221,7 @@ export default defineComponent({
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const posthogStore = usePostHog();
const ndvStore = useNDVStore(); const ndvStore = useNDVStore();
const externalHooks = useExternalHooks(); const externalHooks = useExternalHooks();
const i18n = useI18n(); const i18n = useI18n();
@ -254,6 +256,7 @@ export default defineComponent({
nodeViewRef, nodeViewRef,
onMouseMoveEnd, onMouseMoveEnd,
workflowHelpers, workflowHelpers,
posthogStore,
runWorkflow, runWorkflow,
stopCurrentExecution, stopCurrentExecution,
callDebounced, callDebounced,
@ -3401,7 +3404,15 @@ export default defineComponent({
const templateId = this.$route.params.id; const templateId = this.$route.params.id;
const loadWorkflowFromJSON = this.$route.query.fromJson === 'true'; const loadWorkflowFromJSON = this.$route.query.fromJson === 'true';
if (loadWorkflowFromJSON) { if (loadWorkflowFromJSON) {
await this.openWorkflowTemplateFromJson({ workflow: EASY_AI_WORKFLOW_JSON }); const isAiCreditsExperimentEnabled =
this.posthogStore.getVariant(AI_CREDITS_EXPERIMENT.name) ===
AI_CREDITS_EXPERIMENT.variant;
const easyAiWorkflowJson = getEasyAiWorkflowJson({
isInstanceInAiFreeCreditsExperiment: isAiCreditsExperimentEnabled,
withOpenAiFreeCredits: useSettingsStore().aiCreditsQuota,
});
await this.openWorkflowTemplateFromJson({ workflow: easyAiWorkflowJson });
} else { } else {
await this.openWorkflowTemplate(templateId.toString()); await this.openWorkflowTemplate(templateId.toString());
} }

View file

@ -6,7 +6,12 @@ import ResourcesListLayout, {
} from '@/components/layouts/ResourcesListLayout.vue'; } from '@/components/layouts/ResourcesListLayout.vue';
import WorkflowCard from '@/components/WorkflowCard.vue'; import WorkflowCard from '@/components/WorkflowCard.vue';
import WorkflowTagsDropdown from '@/components/WorkflowTagsDropdown.vue'; import WorkflowTagsDropdown from '@/components/WorkflowTagsDropdown.vue';
import { EASY_AI_WORKFLOW_EXPERIMENT, EnterpriseEditionFeature, VIEWS } from '@/constants'; import {
EASY_AI_WORKFLOW_EXPERIMENT,
AI_CREDITS_EXPERIMENT,
EnterpriseEditionFeature,
VIEWS,
} from '@/constants';
import type { IUser, IWorkflowDb } from '@/Interface'; import type { IUser, IWorkflowDb } from '@/Interface';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
@ -32,7 +37,7 @@ import {
} from 'n8n-design-system'; } from 'n8n-design-system';
import { pickBy } from 'lodash-es'; import { pickBy } from 'lodash-es';
import ProjectHeader from '@/components/Projects/ProjectHeader.vue'; import ProjectHeader from '@/components/Projects/ProjectHeader.vue';
import { EASY_AI_WORKFLOW_JSON } from '@/constants.workflows'; import { getEasyAiWorkflowJson } from '@/utils/easyAiWorkflowUtils';
const i18n = useI18n(); const i18n = useI18n();
const route = useRoute(); const route = useRoute();
@ -269,9 +274,18 @@ const openAIWorkflow = async (source: string) => {
}, },
{ withPostHog: true }, { withPostHog: true },
); );
const isAiCreditsExperimentEnabled =
posthogStore.getVariant(AI_CREDITS_EXPERIMENT.name) === AI_CREDITS_EXPERIMENT.variant;
const easyAiWorkflowJson = getEasyAiWorkflowJson({
isInstanceInAiFreeCreditsExperiment: isAiCreditsExperimentEnabled,
withOpenAiFreeCredits: settingsStore.aiCreditsQuota,
});
await router.push({ await router.push({
name: VIEWS.TEMPLATE_IMPORT, name: VIEWS.TEMPLATE_IMPORT,
params: { id: EASY_AI_WORKFLOW_JSON.meta.templateId }, params: { id: easyAiWorkflowJson.meta.templateId },
query: { fromJson: 'true' }, query: { fromJson: 'true' },
}); });
}; };