@@ -564,6 +620,46 @@ function getParameterValue
+
+
+
+
+
+ {{ ' ' }}
+
+ {{ parameter.typeOptions.calloutAction.label }}
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/editor-ui/src/composables/useCalloutHelpers.test.ts b/packages/frontend/editor-ui/src/composables/useCalloutHelpers.test.ts
new file mode 100644
index 0000000000..523c87fa2c
--- /dev/null
+++ b/packages/frontend/editor-ui/src/composables/useCalloutHelpers.test.ts
@@ -0,0 +1,161 @@
+import { useCalloutHelpers } from '@/composables/useCalloutHelpers';
+import { updateCurrentUserSettings } from '@/api/users';
+
+const mocks = vi.hoisted(() => ({
+ resolve: vi.fn(),
+ track: vi.fn(),
+ useRoute: vi.fn(() => ({ query: {}, params: {} })),
+ getVariant: vi.fn(() => 'default'),
+ isCalloutDismissed: vi.fn(() => false),
+ setCalloutDismissed: vi.fn(),
+ restApiContext: vi.fn(() => ({})),
+ getWorkflowById: vi.fn(),
+}));
+
+vi.mock('vue-router', () => ({
+ useRouter: () => ({
+ resolve: mocks.resolve,
+ }),
+ useRoute: mocks.useRoute,
+ RouterLink: vi.fn(),
+}));
+
+vi.mock('@/composables/useTelemetry', () => ({
+ useTelemetry: () => ({ track: mocks.track }),
+}));
+
+vi.mock('@/stores/posthog.store', () => ({
+ usePostHog: () => ({
+ getVariant: mocks.getVariant,
+ }),
+}));
+
+vi.mock('@/stores/users.store', () => ({
+ useUsersStore: () => ({
+ isCalloutDismissed: mocks.isCalloutDismissed,
+ setCalloutDismissed: mocks.setCalloutDismissed,
+ currentUser: { settings: { dismissedCallouts: {} } },
+ }),
+}));
+
+vi.mock('@/stores/workflows.store', () => ({
+ useWorkflowsStore: () => ({
+ getCurrentWorkflow: vi.fn(() => ({
+ id: '1',
+ })),
+ getWorkflowById: mocks.getWorkflowById,
+ }),
+}));
+
+vi.mock('@n8n/stores/useRootStore', () => ({
+ useRootStore: () => ({
+ restApiContext: mocks.restApiContext,
+ }),
+}));
+
+vi.mock('@/api/users', () => ({
+ updateCurrentUserSettings: vi.fn(),
+}));
+
+describe('useCalloutHelpers()', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ describe('openRagStarterTemplate()', () => {
+ it('opens the RAG starter template successfully', async () => {
+ vi.spyOn(window, 'open').mockImplementation(() => null);
+ mocks.resolve.mockReturnValue({ href: 'n8n.io' });
+
+ const { openRagStarterTemplate } = useCalloutHelpers();
+ const nodeType = 'testNode';
+
+ await openRagStarterTemplate('testNode');
+
+ expect(window.open).toHaveBeenCalledWith('n8n.io', '_blank');
+ expect(mocks.track).toHaveBeenCalledWith('User clicked on RAG callout', {
+ node_type: nodeType,
+ });
+ });
+ });
+
+ describe('isRagStarterWorkflowExperimentEnabled', () => {
+ it('should be false if the RAG starter workflow experiment is not enabled', () => {
+ const { isRagStarterWorkflowExperimentEnabled } = useCalloutHelpers();
+ expect(isRagStarterWorkflowExperimentEnabled.value).toBe(false);
+ });
+
+ it('should be true if the RAG starter workflow experiment is enabled', () => {
+ mocks.getVariant.mockReturnValueOnce('variant');
+
+ const { isRagStarterWorkflowExperimentEnabled } = useCalloutHelpers();
+ expect(isRagStarterWorkflowExperimentEnabled.value).toBe(true);
+ });
+ });
+
+ describe('isRagStarterCalloutVisible', () => {
+ it('should be false if the feature flag is disabled', () => {
+ const { isRagStarterCalloutVisible } = useCalloutHelpers();
+ expect(isRagStarterCalloutVisible.value).toBe(false);
+ });
+
+ it('should be true if the feature flag is enabled and not on the RAG starter template', () => {
+ mocks.getVariant.mockReturnValueOnce('variant');
+
+ const { isRagStarterCalloutVisible } = useCalloutHelpers();
+ expect(isRagStarterCalloutVisible.value).toBe(true);
+ });
+
+ it('should be false if the feature flag is enabled and currently on unsaved RAG starter template', () => {
+ mocks.getVariant.mockReturnValueOnce('variant');
+ mocks.useRoute.mockReturnValueOnce({
+ query: { templateId: 'rag-starter-template' },
+ params: {},
+ });
+
+ const { isRagStarterCalloutVisible } = useCalloutHelpers();
+ expect(isRagStarterCalloutVisible.value).toBe(false);
+ });
+
+ it('should be false if the feature flag is enabled and currently on saved RAG starter template', () => {
+ mocks.getVariant.mockReturnValueOnce('variant');
+ mocks.getWorkflowById.mockReturnValueOnce({
+ meta: { templateId: 'rag-starter-template' },
+ });
+
+ const { isRagStarterCalloutVisible } = useCalloutHelpers();
+ expect(isRagStarterCalloutVisible.value).toBe(false);
+ });
+ });
+
+ describe('isCalloutDismissed()', () => {
+ it('should return false if callout is not dismissed', async () => {
+ const { isCalloutDismissed } = useCalloutHelpers();
+ const result = isCalloutDismissed('testNode');
+ expect(result).toBe(false);
+ });
+
+ it('should return true if callout is dismissed', async () => {
+ mocks.isCalloutDismissed.mockReturnValueOnce(true);
+
+ const { isCalloutDismissed } = useCalloutHelpers();
+ const result = isCalloutDismissed('testNode');
+ expect(result).toBe(true);
+ });
+ });
+
+ describe('dismissCallout()', () => {
+ it('should dismiss the callout and update user settings', async () => {
+ const { dismissCallout } = useCalloutHelpers();
+
+ await dismissCallout('testCallout');
+ expect(mocks.setCalloutDismissed).toHaveBeenCalledWith('testCallout');
+
+ expect(updateCurrentUserSettings).toHaveBeenCalledWith(mocks.restApiContext, {
+ dismissedCallouts: {
+ testCallout: true,
+ },
+ });
+ });
+ });
+});
diff --git a/packages/frontend/editor-ui/src/composables/useCalloutHelpers.ts b/packages/frontend/editor-ui/src/composables/useCalloutHelpers.ts
new file mode 100644
index 0000000000..c76b32ece2
--- /dev/null
+++ b/packages/frontend/editor-ui/src/composables/useCalloutHelpers.ts
@@ -0,0 +1,81 @@
+import { computed } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { useTelemetry } from '@/composables/useTelemetry';
+import { useRootStore } from '@n8n/stores/useRootStore';
+import { usePostHog } from '@/stores/posthog.store';
+import { useUsersStore } from '@/stores/users.store';
+import { RAG_STARTER_WORKFLOW_EXPERIMENT, VIEWS } from '@/constants';
+import { getRagStarterWorkflowJson } from '@/utils/easyAiWorkflowUtils';
+import { updateCurrentUserSettings } from '@/api/users';
+import { useWorkflowsStore } from '@/stores/workflows.store';
+
+export function useCalloutHelpers() {
+ const route = useRoute();
+ const router = useRouter();
+ const telemetry = useTelemetry();
+ const posthogStore = usePostHog();
+ const rootStore = useRootStore();
+ const workflowsStore = useWorkflowsStore();
+ const usersStore = useUsersStore();
+
+ const openRagStarterTemplate = async (nodeType?: string) => {
+ telemetry.track('User clicked on RAG callout', {
+ node_type: nodeType ?? null,
+ });
+
+ const template = getRagStarterWorkflowJson();
+
+ const { href } = router.resolve({
+ name: VIEWS.TEMPLATE_IMPORT,
+ params: { id: template.meta.templateId },
+ query: { fromJson: 'true', parentFolderId: route.params.folderId },
+ });
+
+ window.open(href, '_blank');
+ };
+
+ const isRagStarterWorkflowExperimentEnabled = computed(() => {
+ return (
+ posthogStore.getVariant(RAG_STARTER_WORKFLOW_EXPERIMENT.name) ===
+ RAG_STARTER_WORKFLOW_EXPERIMENT.variant
+ );
+ });
+
+ const isRagStarterCalloutVisible = computed(() => {
+ const template = getRagStarterWorkflowJson();
+
+ const routeTemplateId = route.query.templateId;
+ const currentWorkflow = workflowsStore.getCurrentWorkflow();
+ const workflow = workflowsStore.getWorkflowById(currentWorkflow.id);
+
+ // Hide the RAG starter callout if we're currently on the RAG starter template
+ if ((routeTemplateId ?? workflow?.meta?.templateId) === template.meta.templateId) {
+ return false;
+ }
+
+ return isRagStarterWorkflowExperimentEnabled.value;
+ });
+
+ const isCalloutDismissed = (callout: string) => {
+ return usersStore.isCalloutDismissed(callout);
+ };
+
+ const dismissCallout = async (callout: string) => {
+ usersStore.setCalloutDismissed(callout);
+
+ await updateCurrentUserSettings(rootStore.restApiContext, {
+ dismissedCallouts: {
+ ...usersStore.currentUser?.settings?.dismissedCallouts,
+ [callout]: true,
+ },
+ });
+ };
+
+ return {
+ openRagStarterTemplate,
+ isRagStarterWorkflowExperimentEnabled,
+ isRagStarterCalloutVisible,
+ isCalloutDismissed,
+ dismissCallout,
+ };
+}
diff --git a/packages/frontend/editor-ui/src/constants.ts b/packages/frontend/editor-ui/src/constants.ts
index 7ac655d39d..b61764159b 100644
--- a/packages/frontend/editor-ui/src/constants.ts
+++ b/packages/frontend/editor-ui/src/constants.ts
@@ -738,10 +738,17 @@ export const WORKFLOW_BUILDER_EXPERIMENT = {
variant: 'variant',
};
+export const RAG_STARTER_WORKFLOW_EXPERIMENT = {
+ name: '033_rag_template',
+ control: 'control',
+ variant: 'variant',
+};
+
export const EXPERIMENTS_TO_TRACK = [
EASY_AI_WORKFLOW_EXPERIMENT.name,
AI_CREDITS_EXPERIMENT.name,
WORKFLOW_BUILDER_EXPERIMENT.name,
+ RAG_STARTER_WORKFLOW_EXPERIMENT.name,
];
export const WORKFLOW_EVALUATION_EXPERIMENT = '032_evaluation_mvp';
diff --git a/packages/frontend/editor-ui/src/stores/users.store.test.ts b/packages/frontend/editor-ui/src/stores/users.store.test.ts
index 979a1b6817..d675da86e7 100644
--- a/packages/frontend/editor-ui/src/stores/users.store.test.ts
+++ b/packages/frontend/editor-ui/src/stores/users.store.test.ts
@@ -90,4 +90,71 @@ describe('users.store', () => {
);
});
});
+
+ describe('isCalloutDismissed', () => {
+ it('should return true if callout is dismissed', () => {
+ const usersStore = useUsersStore();
+
+ usersStore.usersById['1'] = {
+ ...mockUser,
+ isDefaultUser: false,
+ isPendingUser: false,
+ mfaEnabled: false,
+ settings: {
+ dismissedCallouts: {
+ testCallout: true,
+ },
+ },
+ };
+ usersStore.currentUserId = '1';
+
+ const isDismissed = usersStore.isCalloutDismissed('testCallout');
+ expect(isDismissed).toBe(true);
+ });
+ });
+
+ describe('setCalloutDismissed', () => {
+ it('should set callout as dismissed in user settings', () => {
+ const usersStore = useUsersStore();
+
+ usersStore.usersById['1'] = {
+ ...mockUser,
+ isDefaultUser: false,
+ isPendingUser: false,
+ mfaEnabled: false,
+ settings: {},
+ };
+ usersStore.currentUserId = '1';
+
+ usersStore.setCalloutDismissed('testCallout');
+
+ expect(usersStore.usersById['1'].settings?.dismissedCallouts).toEqual({
+ testCallout: true,
+ });
+ });
+
+ it('should not lose existing dismissed callouts', () => {
+ const usersStore = useUsersStore();
+
+ usersStore.usersById['1'] = {
+ ...mockUser,
+ isDefaultUser: false,
+ isPendingUser: false,
+ mfaEnabled: false,
+ settings: {
+ dismissedCallouts: {
+ previousCallout: true,
+ },
+ },
+ };
+ usersStore.currentUserId = '1';
+
+ usersStore.setCalloutDismissed('testCallout');
+
+ expect(usersStore.usersById['1'].settings?.dismissedCallouts).toEqual({
+ previousCallout: true,
+ testCallout: true,
+ });
+ });
+ });
});
diff --git a/packages/frontend/editor-ui/src/stores/users.store.ts b/packages/frontend/editor-ui/src/stores/users.store.ts
index ec1fb3fb63..49d573d754 100644
--- a/packages/frontend/editor-ui/src/stores/users.store.ts
+++ b/packages/frontend/editor-ui/src/stores/users.store.ts
@@ -91,6 +91,19 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
}
};
+ const isCalloutDismissed = (callout: string) =>
+ Boolean(currentUser.value?.settings?.dismissedCallouts?.[callout]);
+
+ const setCalloutDismissed = (callout: string) => {
+ if (currentUser.value?.settings) {
+ if (!currentUser.value?.settings?.dismissedCallouts) {
+ currentUser.value.settings.dismissedCallouts = {};
+ }
+
+ currentUser.value.settings.dismissedCallouts[callout] = true;
+ }
+ };
+
const personalizedNodeTypes = computed(() => {
const user = currentUser.value;
if (!user) {
@@ -437,6 +450,8 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
updateGlobalRole,
reset,
setEasyAIWorkflowOnboardingDone,
+ isCalloutDismissed,
+ setCalloutDismissed,
submitContactEmail,
};
});
diff --git a/packages/frontend/editor-ui/src/utils/easyAiWorkflowUtils.ts b/packages/frontend/editor-ui/src/utils/easyAiWorkflowUtils.ts
index dc74b30349..2738b2582a 100644
--- a/packages/frontend/editor-ui/src/utils/easyAiWorkflowUtils.ts
+++ b/packages/frontend/editor-ui/src/utils/easyAiWorkflowUtils.ts
@@ -3,13 +3,6 @@ import { NodeConnectionTypes } 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 = (): WorkflowDataWithTemplateId => {
return {
@@ -94,3 +87,252 @@ export const getEasyAiWorkflowJson = (): WorkflowDataWithTemplateId => {
pinData: {},
};
};
+
+export const getRagStarterWorkflowJson = (): WorkflowDataWithTemplateId => {
+ return {
+ name: 'Demo: RAG in n8n',
+ meta: {
+ templateId: 'rag-starter-template',
+ },
+ nodes: [
+ {
+ parameters: {
+ formTitle: 'Upload your data to test RAG',
+ formFields: {
+ values: [
+ {
+ fieldLabel: 'Upload your file(s)',
+ fieldType: 'file',
+ acceptFileTypes: '.pdf, .csv',
+ requiredField: true,
+ },
+ ],
+ },
+ options: {},
+ },
+ type: 'n8n-nodes-base.formTrigger',
+ typeVersion: 2.2,
+ position: [-120, 0],
+ id: 'f7a656ec-83fc-4ed2-a089-57a9def662b7',
+ name: 'Upload your file here',
+ webhookId: '82848bc4-5ea2-4e5a-8bb6-3c09b94a8c5d',
+ },
+ {
+ parameters: {
+ options: {},
+ },
+ type: '@n8n/n8n-nodes-langchain.embeddingsOpenAi',
+ typeVersion: 1.2,
+ position: [520, 480],
+ id: '6ea78663-cf2f-4f2d-8e68-43047c2afd87',
+ name: 'Embeddings OpenAI',
+ credentials: {
+ openAiApi: {
+ id: '14',
+ name: 'OpenAi account',
+ },
+ },
+ },
+ {
+ parameters: {
+ dataType: 'binary',
+ options: {},
+ },
+ type: '@n8n/n8n-nodes-langchain.documentDefaultDataLoader',
+ typeVersion: 1.1,
+ position: [320, 160],
+ id: '94aecac0-03f9-4915-932b-d14a2576607b',
+ name: 'Default Data Loader',
+ },
+ {
+ parameters: {
+ content:
+ '### Readme\nLoad your data into a vector database with the 📚 **Load Data** flow, and then use your data as chat context with the 🐕 **Retriever** flow.\n\n**Quick start**\n1. Click on the `Execute Workflow` button to run the 📚 **Load Data** flow.\n2. Click on `Open Chat` button to run the 🐕 **Retriever** flow. Then ask a question about content from your document(s)\n\n\nFor more info, check our docs on RAG in n8n',
+ height: 300,
+ width: 440,
+ color: 4,
+ },
+ type: 'n8n-nodes-base.stickyNote',
+ position: [-660, -60],
+ typeVersion: 1,
+ id: '0d07742b-0b36-4c2e-990c-266cbe6e2d4d',
+ name: 'Sticky Note',
+ },
+ {
+ parameters: {
+ content: '### 📚 Load Data Flow',
+ height: 460,
+ width: 700,
+ color: 7,
+ },
+ type: 'n8n-nodes-base.stickyNote',
+ position: [-180, -60],
+ typeVersion: 1,
+ id: 'd19d04f3-5231-4e47-bed7-9f24a4a8f582',
+ name: 'Sticky Note1',
+ },
+ {
+ parameters: {
+ mode: 'insert',
+ memoryKey: {
+ __rl: true,
+ value: 'vector_store_key',
+ mode: 'list',
+ cachedResultName: 'vector_store_key',
+ },
+ },
+ type: '@n8n/n8n-nodes-langchain.vectorStoreInMemory',
+ typeVersion: 1.2,
+ position: [60, 0],
+ id: 'bf50a11f-ca6a-4e04-a6d2-42fee272b260',
+ name: 'Insert Data to Store',
+ },
+ {
+ parameters: {
+ mode: 'retrieve-as-tool',
+ toolName: 'knowledge_base',
+ toolDescription: 'Use this knowledge base to answer questions from the user',
+ memoryKey: {
+ __rl: true,
+ mode: 'list',
+ value: 'vector_store_key',
+ },
+ },
+ type: '@n8n/n8n-nodes-langchain.vectorStoreInMemory',
+ typeVersion: 1.2,
+ position: [940, 200],
+ id: '09c0db62-5413-440e-8c13-fb6bb66d9b6a',
+ name: 'Query Data Tool',
+ },
+ {
+ parameters: {
+ options: {},
+ },
+ type: '@n8n/n8n-nodes-langchain.agent',
+ typeVersion: 2,
+ position: [940, -20],
+ id: '579aed76-9644-42d1-ac13-7369059ff1c2',
+ name: 'AI Agent',
+ },
+ {
+ parameters: {
+ options: {},
+ },
+ type: '@n8n/n8n-nodes-langchain.chatTrigger',
+ typeVersion: 1.1,
+ position: [720, -20],
+ id: '9c30de61-935a-471f-ae88-ec5f67beeefc',
+ name: 'When chat message received',
+ webhookId: '4091fa09-fb9a-4039-9411-7104d213f601',
+ },
+ {
+ parameters: {
+ model: {
+ __rl: true,
+ mode: 'list',
+ value: 'gpt-4o-mini',
+ },
+ options: {},
+ },
+ type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
+ typeVersion: 1.2,
+ position: [720, 200],
+ id: 'b5aa8942-9cd5-4c2f-bd77-7a0ceb921bac',
+ name: 'OpenAI Chat Model',
+ credentials: {
+ openAiApi: {
+ id: '14',
+ name: 'OpenAi account',
+ },
+ },
+ },
+ {
+ parameters: {
+ content: '### 🐕 2. Retriever Flow',
+ height: 460,
+ width: 680,
+ color: 7,
+ },
+ type: 'n8n-nodes-base.stickyNote',
+ position: [600, -60],
+ typeVersion: 1,
+ id: '28bc73a1-e64a-47bf-ac1c-ffe644894ea5',
+ name: 'Sticky Note2',
+ },
+ ],
+ connections: {
+ 'Upload your file here': {
+ main: [
+ [
+ {
+ node: 'Insert Data to Store',
+ type: 'main',
+ index: 0,
+ },
+ ],
+ ],
+ },
+ 'Embeddings OpenAI': {
+ ai_embedding: [
+ [
+ {
+ node: 'Insert Data to Store',
+ type: 'ai_embedding',
+ index: 0,
+ },
+ {
+ node: 'Query Data Tool',
+ type: 'ai_embedding',
+ index: 0,
+ },
+ ],
+ ],
+ },
+ 'Default Data Loader': {
+ ai_document: [
+ [
+ {
+ node: 'Insert Data to Store',
+ type: 'ai_document',
+ index: 0,
+ },
+ ],
+ ],
+ },
+ 'Query Data Tool': {
+ ai_tool: [
+ [
+ {
+ node: 'AI Agent',
+ type: 'ai_tool',
+ index: 0,
+ },
+ ],
+ ],
+ },
+ 'When chat message received': {
+ main: [
+ [
+ {
+ node: 'AI Agent',
+ type: 'main',
+ index: 0,
+ },
+ ],
+ ],
+ },
+ 'OpenAI Chat Model': {
+ ai_languageModel: [
+ [
+ {
+ node: 'AI Agent',
+ type: 'ai_languageModel',
+ index: 0,
+ },
+ ],
+ ],
+ },
+ },
+ pinData: {},
+ };
+};
diff --git a/packages/frontend/editor-ui/src/views/NodeView.vue b/packages/frontend/editor-ui/src/views/NodeView.vue
index 9288bbeb46..67d1c9dd86 100644
--- a/packages/frontend/editor-ui/src/views/NodeView.vue
+++ b/packages/frontend/editor-ui/src/views/NodeView.vue
@@ -121,7 +121,7 @@ import { getResourcePermissions } from '@/permissions';
import NodeViewUnfinishedWorkflowMessage from '@/components/NodeViewUnfinishedWorkflowMessage.vue';
import { createCanvasConnectionHandleString } from '@/utils/canvasUtils';
import { isValidNodeConnectionType } from '@/utils/typeGuards';
-import { getEasyAiWorkflowJson } from '@/utils/easyAiWorkflowUtils';
+import { getEasyAiWorkflowJson, getRagStarterWorkflowJson } from '@/utils/easyAiWorkflowUtils';
import type { CanvasLayoutEvent } from '@/composables/useCanvasLayout';
import { useWorkflowSaving } from '@/composables/useWorkflowSaving';
import { useBuilderStore } from '@/stores/builder.store';
@@ -374,7 +374,22 @@ async function initializeRoute(force = false) {
if (loadWorkflowFromJSON) {
const easyAiWorkflowJson = getEasyAiWorkflowJson();
- await openTemplateFromWorkflowJSON(easyAiWorkflowJson);
+ const ragStarterWorkflowJson = getRagStarterWorkflowJson();
+
+ switch (templateId) {
+ case easyAiWorkflowJson.meta.templateId:
+ await openTemplateFromWorkflowJSON(easyAiWorkflowJson);
+ break;
+ case ragStarterWorkflowJson.meta.templateId:
+ await openTemplateFromWorkflowJSON(ragStarterWorkflowJson);
+ break;
+ default:
+ toast.showError(
+ new Error(i18n.baseText('nodeView.couldntLoadWorkflow.invalidWorkflowObject')),
+ i18n.baseText('nodeView.couldntImportWorkflow'),
+ );
+ await router.replace({ name: VIEWS.NEW_WORKFLOW });
+ }
} else {
await openWorkflowTemplate(templateId.toString());
}
diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts
index 2f921f705d..570d5691ae 100644
--- a/packages/workflow/src/interfaces.ts
+++ b/packages/workflow/src/interfaces.ts
@@ -1251,6 +1251,7 @@ export type NodePropertyTypes =
| 'fixedCollection'
| 'hidden'
| 'json'
+ | 'callout'
| 'notice'
| 'multiOptions'
| 'number'
@@ -1294,6 +1295,12 @@ export type NodePropertyAction = {
target?: string;
};
+export type CalloutActionType = 'openRagStarterTemplate';
+export interface CalloutAction {
+ type: CalloutActionType;
+ label: string;
+}
+
export interface INodePropertyTypeOptions {
// Supported by: button
buttonConfig?: {
@@ -1326,6 +1333,7 @@ export interface INodePropertyTypeOptions {
assignment?: AssignmentTypeOptions;
minRequiredFields?: number; // Supported by: fixedCollection
maxAllowedFields?: number; // Supported by: fixedCollection
+ calloutAction?: CalloutAction; // Supported by: callout
[key: string]: any;
}
@@ -2835,6 +2843,7 @@ export interface IUserSettings {
npsSurvey?: NpsSurveyState;
easyAIWorkflowOnboarded?: boolean;
userClaimedAiCredits?: boolean;
+ dismissedCallouts?: Record;
}
export interface IProcessedDataConfig {
diff --git a/packages/workflow/test/fixtures/WorkflowDataProxy/from_ai_multiple_items_workflow.json b/packages/workflow/test/fixtures/WorkflowDataProxy/from_ai_multiple_items_workflow.json
index 86f8a0c209..a8e82b3e7c 100644
--- a/packages/workflow/test/fixtures/WorkflowDataProxy/from_ai_multiple_items_workflow.json
+++ b/packages/workflow/test/fixtures/WorkflowDataProxy/from_ai_multiple_items_workflow.json
@@ -69,7 +69,7 @@
},
{
"parameters": {
- "notice_tip": "",
+ "aiAgentStarterCallout": "",
"agent": "toolsAgent",
"promptType": "define",
"text": "=Add this user to my Users sheet:\n{{ $json.toJsonString() }}",