From b5cbf7566d351d8a8e9972f13ff5867ff1c8d7d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Tue, 12 Nov 2024 12:30:35 +0100 Subject: [PATCH 1/2] fix(Redis Chat Memory Node): Respect the SSL flag from the credential (#11689) --- .../nodes/memory/MemoryRedisChat/MemoryRedisChat.node.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/@n8n/nodes-langchain/nodes/memory/MemoryRedisChat/MemoryRedisChat.node.ts b/packages/@n8n/nodes-langchain/nodes/memory/MemoryRedisChat/MemoryRedisChat.node.ts index 23c7f64e36..09208d96f7 100644 --- a/packages/@n8n/nodes-langchain/nodes/memory/MemoryRedisChat/MemoryRedisChat.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/memory/MemoryRedisChat/MemoryRedisChat.node.ts @@ -127,6 +127,7 @@ export class MemoryRedisChat implements INodeType { socket: { host: credentials.host as string, port: credentials.port as number, + tls: credentials.ssl === true, }, database: credentials.database as number, }; From 7b20c1e93d9817fee8d9fa8c6dd991607368cd5d Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 12 Nov 2024 06:58:46 -0500 Subject: [PATCH 2/2] fix(editor): Ignore all node run messages after a success message is sent (no-changelog) (#11668) --- cypress/e2e/45-ai-assistant.cy.ts | 48 +++++++++++++++++++ .../node_execution_succeeded_response.json | 22 +++++++++ .../editor-ui/src/stores/assistant.store.ts | 12 ++++- 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 cypress/fixtures/aiAssistant/responses/node_execution_succeeded_response.json diff --git a/cypress/e2e/45-ai-assistant.cy.ts b/cypress/e2e/45-ai-assistant.cy.ts index 9c69269b33..9b50fef44a 100644 --- a/cypress/e2e/45-ai-assistant.cy.ts +++ b/cypress/e2e/45-ai-assistant.cy.ts @@ -224,6 +224,54 @@ describe('AI Assistant::enabled', () => { .should('contain.text', 'item.json.myNewField = 1'); }); + it('Should ignore node execution success and error messages after the node run successfully once', () => { + const getParameter = () => ndv.getters.parameterInput('jsCode').should('be.visible'); + + const getEditor = () => getParameter().find('.cm-content').should('exist'); + + cy.intercept('POST', '/rest/ai/chat', { + statusCode: 200, + fixture: 'aiAssistant/responses/code_diff_suggestion_response.json', + }).as('chatRequest'); + + cy.createFixtureWorkflow('aiAssistant/workflows/test_workflow.json'); + wf.actions.openNode('Code'); + ndv.getters.nodeExecuteButton().click(); + aiAssistant.getters.nodeErrorViewAssistantButton().click({ force: true }); + cy.wait('@chatRequest'); + + cy.intercept('POST', '/rest/ai/chat', { + statusCode: 200, + fixture: 'aiAssistant/responses/node_execution_succeeded_response.json', + }).as('chatRequest2'); + + getEditor() + .type('{selectall}') + .paste( + 'for (const item of $input.all()) {\n item.json.myNewField = 1;\n}\n\nreturn $input.all();', + ); + + ndv.getters.nodeExecuteButton().click(); + + getEditor() + .type('{selectall}') + .paste( + 'for (const item of $input.all()) {\n item.json.myNewField = 1aaaa!;\n}\n\nreturn $input.all();', + ); + + ndv.getters.nodeExecuteButton().click(); + + aiAssistant.getters.chatMessagesAssistant().should('have.length', 3); + + aiAssistant.getters + .chatMessagesAssistant() + .eq(2) + .should( + 'contain.text', + 'Code node ran successfully, did my solution help resolve your issue?\nQuick reply 👇Yes, thanksNo, I am still stuck', + ); + }); + it('should end chat session when `end_session` event is received', () => { cy.intercept('POST', '/rest/ai/chat', { statusCode: 200, diff --git a/cypress/fixtures/aiAssistant/responses/node_execution_succeeded_response.json b/cypress/fixtures/aiAssistant/responses/node_execution_succeeded_response.json new file mode 100644 index 0000000000..62caf2a6b5 --- /dev/null +++ b/cypress/fixtures/aiAssistant/responses/node_execution_succeeded_response.json @@ -0,0 +1,22 @@ +{ + "sessionId": "1", + "messages": [ + { + "role": "assistant", + "type": "message", + "text": "**Code** node ran successfully, did my solution help resolve your issue?", + "quickReplies": [ + { + "text": "Yes, thanks", + "type": "all-good", + "isFeedback": true + }, + { + "text": "No, I am still stuck", + "type": "still-stuck", + "isFeedback": true + } + ] + } + ] +} diff --git a/packages/editor-ui/src/stores/assistant.store.ts b/packages/editor-ui/src/stores/assistant.store.ts index 02178c0784..079db2b407 100644 --- a/packages/editor-ui/src/stores/assistant.store.ts +++ b/packages/editor-ui/src/stores/assistant.store.ts @@ -72,13 +72,15 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { }; }>({}); + type NodeExecutionStatus = 'error' | 'not_executed' | 'success'; + const chatSessionCredType = ref(); const chatSessionError = ref(); const currentSessionId = ref(); const currentSessionActiveExecutionId = ref(); const currentSessionWorkflowId = ref(); const lastUnread = ref(); - const nodeExecutionStatus = ref<'not_executed' | 'success' | 'error'>('not_executed'); + const nodeExecutionStatus = ref('not_executed'); // This is used to show a message when the assistant is performing intermediate steps // We use streaming for assistants that support it, and this for agents const assistantThinkingMessage = ref(); @@ -536,10 +538,16 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { (e) => handleServiceError(e, id, async () => await sendEvent(eventName, error)), ); } + async function onNodeExecution(pushEvent: PushPayload<'nodeExecuteAfter'>) { if (!chatSessionError.value || pushEvent.nodeName !== chatSessionError.value.node.name) { return; } + + if (nodeExecutionStatus.value === 'success') { + return; + } + if (pushEvent.data.error && nodeExecutionStatus.value !== 'error') { await sendEvent('node-execution-errored', pushEvent.data.error); nodeExecutionStatus.value = 'error'; @@ -550,7 +558,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => { }); } else if ( pushEvent.data.executionStatus === 'success' && - nodeExecutionStatus.value !== 'success' + ['error', 'not_executed'].includes(nodeExecutionStatus.value) ) { await sendEvent('node-execution-succeeded'); nodeExecutionStatus.value = 'success';