diff --git a/packages/design-system/src/locale/lang/en.ts b/packages/design-system/src/locale/lang/en.ts index e6163b2d38..9e2e141b17 100644 --- a/packages/design-system/src/locale/lang/en.ts +++ b/packages/design-system/src/locale/lang/en.ts @@ -2,6 +2,7 @@ import type { N8nLocale } from 'n8n-design-system/types'; export default { + 'generic.retry': 'Retry', 'nds.auth.roles.owner': 'Owner', 'nds.userInfo.you': '(you)', 'nds.userSelect.selectUser': 'Select User', diff --git a/packages/editor-ui/src/composables/useAIAssistantHelpers.test.ts b/packages/editor-ui/src/composables/useAIAssistantHelpers.test.ts index d4b01a1374..7d0ac11398 100644 --- a/packages/editor-ui/src/composables/useAIAssistantHelpers.test.ts +++ b/packages/editor-ui/src/composables/useAIAssistantHelpers.test.ts @@ -1,8 +1,9 @@ import { describe, it, expect } from 'vitest'; -import type { INode } from 'n8n-workflow'; +import type { INode, IRunExecutionData, NodeConnectionType } from 'n8n-workflow'; import { useAIAssistantHelpers } from './useAIAssistantHelpers'; import { createTestingPinia } from '@pinia/testing'; import { setActivePinia } from 'pinia'; +import type { IWorkflowDb } from '@/Interface'; const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: string[] }> = [ { @@ -76,6 +77,15 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: name: 'document', value: "={{ $('Edit Fields 2').item.json.document}}", type: 'string', + typeVersion: 1, + }, + { + parameters: {}, + id: 'b5942df6-0160-4ef7-965d-57583acdc8aa', + name: 'Replace me with your logic', + type: 'n8n-nodes-base.noOp', + position: [520, 340], + typeVersion: 1, }, ], }, @@ -377,6 +387,121 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: }, ]; +const testWorkflow: IWorkflowDb = { + id: 'MokOcBHON6KkPq6Y', + name: 'My Sub-Workflow 3', + active: false, + createdAt: -1, + updatedAt: -1, + connections: { + 'Execute Workflow Trigger': { + main: [ + [ + { + node: 'Replace me with your logic', + type: 'main' as NodeConnectionType, + index: 0, + }, + ], + ], + }, + }, + nodes: [ + { + parameters: { + notice: '', + events: 'worklfow_call', + }, + id: 'c055762a-8fe7-4141-a639-df2372f30060', + name: 'Execute Workflow Trigger', + type: 'n8n-nodes-base.executeWorkflowTrigger', + position: [260, 340], + typeVersion: 0, + }, + { + parameters: {}, + id: 'b5942df6-0160-4ef7-965d-57583acdc8aa', + name: 'Replace me with your logic', + type: 'n8n-nodes-base.noOp', + position: [520, 340], + typeVersion: 1, + }, + ], + settings: { + executionOrder: 'v1', + }, + tags: [], + pinData: {}, + versionId: '9f3263e3-d23d-4cc8-bff0-0fdecfbd82bf', + usedCredentials: [], + scopes: [ + 'workflow:create', + 'workflow:delete', + 'workflow:execute', + 'workflow:list', + 'workflow:move', + 'workflow:read', + 'workflow:share', + 'workflow:update', + ], + sharedWithProjects: [], +}; + +const testExecutionData: IRunExecutionData['resultData'] = { + runData: { + 'When clicking ‘Test workflow’': [ + { + hints: [], + startTime: 1732882780588, + executionTime: 4, + source: [], + executionStatus: 'success', + data: { + main: [ + [ + { + json: {}, + pairedItem: { + item: 0, + }, + }, + ], + ], + }, + }, + ], + 'Edit Fields': [ + { + hints: [], + startTime: 1732882780593, + executionTime: 0, + source: [ + { + previousNode: 'When clicking ‘Test workflow’', + }, + ], + executionStatus: 'success', + data: { + main: [ + [ + { + json: { + something: 'here', + }, + pairedItem: { + item: 0, + }, + }, + ], + ], + }, + }, + ], + }, + pinData: {}, + lastNodeExecuted: 'Edit Fields', +}; + describe.each(referencedNodesTestCases)('getReferencedNodes', (testCase) => { let aiAssistantHelpers: ReturnType; @@ -390,3 +515,37 @@ describe.each(referencedNodesTestCases)('getReferencedNodes', (testCase) => { expect(aiAssistantHelpers.getReferencedNodes(testCase.node)).toEqual(testCase.expected); }); }); + +describe('Simplify assistant payloads', () => { + let aiAssistantHelpers: ReturnType; + + beforeEach(() => { + setActivePinia(createTestingPinia()); + aiAssistantHelpers = useAIAssistantHelpers(); + }); + + it('simplifyWorkflowForAssistant: Should remove unnecessary properties from workflow object', () => { + const simplifiedWorkflow = aiAssistantHelpers.simplifyWorkflowForAssistant(testWorkflow); + const removedProperties = [ + 'createdAt', + 'updatedAt', + 'settings', + 'versionId', + 'usedCredentials', + 'sharedWithProjects', + 'pinData', + 'scopes', + 'tags', + ]; + removedProperties.forEach((property) => { + expect(simplifiedWorkflow).not.toHaveProperty(property); + }); + }); + + it('simplifyResultData: Should remove data from nodes', () => { + const simplifiedResultData = aiAssistantHelpers.simplifyResultData(testExecutionData); + for (const nodeName of Object.keys(simplifiedResultData.runData)) { + expect(simplifiedResultData.runData[nodeName][0]).not.toHaveProperty('data'); + } + }); +}); diff --git a/packages/editor-ui/src/composables/useAIAssistantHelpers.ts b/packages/editor-ui/src/composables/useAIAssistantHelpers.ts index 5adc3952fe..b19f2817cd 100644 --- a/packages/editor-ui/src/composables/useAIAssistantHelpers.ts +++ b/packages/editor-ui/src/composables/useAIAssistantHelpers.ts @@ -16,6 +16,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store'; import { useDataSchema } from './useDataSchema'; import { VIEWS } from '@/constants'; import { useI18n } from './useI18n'; +import type { IWorkflowDb } from '@/Interface'; const CANVAS_VIEWS = [VIEWS.NEW_WORKFLOW, VIEWS.WORKFLOW, VIEWS.EXECUTION_DEBUG]; const EXECUTION_VIEWS = [VIEWS.EXECUTION_PREVIEW]; @@ -225,13 +226,13 @@ export const useAIAssistantHelpers = () => { simplifiedResultData.error = data.error; } // Map runData, excluding the `data` field from ITaskData - Object.keys(data.runData).forEach((key) => { + for (const key of Object.keys(data.runData)) { const taskDataArray = data.runData[key]; simplifiedResultData.runData[key] = taskDataArray.map((taskData) => { const { data: taskDataContent, ...taskDataWithoutData } = taskData; return taskDataWithoutData; }); - }); + } // Handle lastNodeExecuted if it exists if (data.lastNodeExecuted) { simplifiedResultData.lastNodeExecuted = data.lastNodeExecuted; @@ -243,6 +244,13 @@ export const useAIAssistantHelpers = () => { return simplifiedResultData; } + const simplifyWorkflowForAssistant = (workflow: IWorkflowDb): Partial => ({ + name: workflow.name, + active: workflow.active, + connections: workflow.connections, + nodes: workflow.nodes, + }); + return { processNodeForAssistant, getNodeInfoForAssistant, @@ -252,5 +260,6 @@ export const useAIAssistantHelpers = () => { getCurrentViewDescription, getReferencedNodes, simplifyResultData, + simplifyWorkflowForAssistant, }; }; diff --git a/packages/editor-ui/src/stores/assistant.store.ts b/packages/editor-ui/src/stores/assistant.store.ts index 079db2b407..446501e9c4 100644 --- a/packages/editor-ui/src/stores/assistant.store.ts +++ b/packages/editor-ui/src/stores/assistant.store.ts @@ -399,7 +399,9 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { authType: nodeInfo?.authType?.name, } : undefined, - currentWorkflow: workflowDataStale.value ? workflowsStore.workflow : undefined, + currentWorkflow: workflowDataStale.value + ? assistantHelpers.simplifyWorkflowForAssistant(workflowsStore.workflow) + : undefined, executionData: workflowExecutionDataStale.value && executionResult ? assistantHelpers.simplifyResultData(executionResult) diff --git a/packages/editor-ui/src/types/assistant.types.ts b/packages/editor-ui/src/types/assistant.types.ts index c911f5087b..3f1c4c7aae 100644 --- a/packages/editor-ui/src/types/assistant.types.ts +++ b/packages/editor-ui/src/types/assistant.types.ts @@ -19,7 +19,7 @@ export namespace ChatRequest { export interface WorkflowContext { executionSchema?: NodeExecutionSchema[]; - currentWorkflow?: IWorkflowDb; + currentWorkflow?: Partial; executionData?: IRunExecutionData['resultData']; }