diff --git a/cypress/e2e/45-ai-assistant.cy.ts b/cypress/e2e/45-ai-assistant.cy.ts index 6be8a8db60..f80b4637d0 100644 --- a/cypress/e2e/45-ai-assistant.cy.ts +++ b/cypress/e2e/45-ai-assistant.cy.ts @@ -31,7 +31,8 @@ describe('AI Assistant::enabled', () => { aiAssistant.getters.askAssistantFloatingButton().click(); aiAssistant.getters.askAssistantChat().should('be.visible'); aiAssistant.getters.placeholderMessage().should('be.visible'); - aiAssistant.getters.chatInputWrapper().should('not.exist'); + aiAssistant.getters.chatInput().should('be.visible'); + aiAssistant.getters.sendMessageButton().should('be.disabled'); aiAssistant.getters.closeChatButton().should('be.visible'); aiAssistant.getters.closeChatButton().click(); aiAssistant.getters.askAssistantChat().should('not.be.visible'); @@ -137,29 +138,6 @@ describe('AI Assistant::enabled', () => { aiAssistant.getters.chatMessagesUser().eq(0).should('contain.text', "Sure, let's do it"); }); - it('should send message to assistant when node is executed only once', () => { - const TOTAL_REQUEST_COUNT = 1; - cy.intercept('POST', '/rest/ai-assistant/chat', { - statusCode: 200, - fixture: 'aiAssistant/simple_message_response.json', - }).as('chatRequest'); - cy.createFixtureWorkflow('aiAssistant/test_workflow.json'); - wf.actions.openNode('Edit Fields'); - ndv.getters.nodeExecuteButton().click(); - aiAssistant.getters.nodeErrorViewAssistantButton().click(); - cy.wait('@chatRequest'); - aiAssistant.getters.chatMessagesAssistant().should('have.length', 1); - cy.get('@chatRequest.all').then((interceptions) => { - expect(interceptions).to.have.length(TOTAL_REQUEST_COUNT); - }); - // Executing the same node should not send a new message if users haven't responded to quick replies - ndv.getters.nodeExecuteButton().click(); - cy.get('@chatRequest.all').then((interceptions) => { - expect(interceptions).to.have.length(TOTAL_REQUEST_COUNT); - }); - aiAssistant.getters.chatMessagesAssistant().should('have.length', 2); - }); - it('should show quick replies when node is executed after new suggestion', () => { cy.intercept('POST', '/rest/ai-assistant/chat', (req) => { req.reply((res) => { @@ -281,4 +259,32 @@ describe('AI Assistant::enabled', () => { aiAssistant.getters.chatMessagesSystem().should('have.length', 1); aiAssistant.getters.chatMessagesSystem().first().should('contain.text', 'session has ended'); }); + + it('should reset session after it ended and sidebar is closed', () => { + cy.intercept('POST', '/rest/ai-assistant/chat', (req) => { + req.reply((res) => { + if (['init-support-chat'].includes(req.body.payload.type)) { + res.send({ statusCode: 200, fixture: 'aiAssistant/simple_message_response.json' }); + } else { + res.send({ statusCode: 200, fixture: 'aiAssistant/end_session_response.json' }); + } + }); + }).as('chatRequest'); + aiAssistant.actions.openChat(); + aiAssistant.actions.sendMessage('Hello'); + cy.wait('@chatRequest'); + aiAssistant.actions.closeChat(); + aiAssistant.actions.openChat(); + // After closing and reopening the chat, all messages should be still there + aiAssistant.getters.chatMessagesAll().should('have.length', 2); + // End the session + aiAssistant.actions.sendMessage('Thanks, bye'); + cy.wait('@chatRequest'); + aiAssistant.getters.chatMessagesSystem().should('have.length', 1); + aiAssistant.getters.chatMessagesSystem().first().should('contain.text', 'session has ended'); + aiAssistant.actions.closeChat(); + aiAssistant.actions.openChat(); + // Now, session should be reset + aiAssistant.getters.placeholderMessage().should('be.visible'); + }); }); diff --git a/cypress/fixtures/aiAssistant/end_session_response.json b/cypress/fixtures/aiAssistant/end_session_response.json index c53574d93a..9478c3adb3 100644 --- a/cypress/fixtures/aiAssistant/end_session_response.json +++ b/cypress/fixtures/aiAssistant/end_session_response.json @@ -1,9 +1,9 @@ { - "sessionId": "f9130bd7-c078-4862-a38a-369b27b0ff20-e96eb9f7-d581-4684-b6a9-fd3dfe9fe1fb-XCldJLlusGrEVku5I9cYT", + "sessionId": "1", "messages": [ { "role": "assistant", - "type": "agent-suggestion", + "type": "message", "title": "Glad to Help", "text": "I'm glad I could help. If you have any more questions or need further assistance with your n8n workflows, feel free to ask!" }, diff --git a/cypress/pages/features/ai-assistant.ts b/cypress/pages/features/ai-assistant.ts index dbd8491923..fe4e4d6435 100644 --- a/cypress/pages/features/ai-assistant.ts +++ b/cypress/pages/features/ai-assistant.ts @@ -38,13 +38,24 @@ export class AIAssistant extends BasePage { }; actions = { - enableAssistant(): void { + enableAssistant: () => { overrideFeatureFlag(AI_ASSISTANT_FEATURE.experimentName, AI_ASSISTANT_FEATURE.enabledFor); cy.enableFeature(AI_ASSISTANT_FEATURE.name); }, - disableAssistant(): void { + disableAssistant: () => { overrideFeatureFlag(AI_ASSISTANT_FEATURE.experimentName, AI_ASSISTANT_FEATURE.disabledFor); cy.disableFeature(AI_ASSISTANT_FEATURE.name); }, + sendMessage: (message: string) => { + this.getters.chatInput().type(message).type('{enter}'); + }, + closeChat: () => { + this.getters.closeChatButton().click(); + this.getters.askAssistantChat().should('not.be.visible'); + }, + openChat: () => { + this.getters.askAssistantFloatingButton().click(); + this.getters.askAssistantChat().should('be.visible'); + }, }; } diff --git a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts index 93c90b5089..45da527485 100644 --- a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts +++ b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts @@ -247,3 +247,23 @@ AssistantThinkingChat.args = { }, loadingMessage: 'Thinking...', }; + +export const WithCodeSnippet = Template.bind({}); +WithCodeSnippet.args = { + user: { + firstName: 'Max', + lastName: 'Test', + }, + messages: getMessages([ + { + id: '58575953', + type: 'text', + role: 'assistant', + content: + 'To filter every other item in the Code node, you can use the following JavaScript code snippet. This code will iterate through the incoming items and only pass through every other item.', + codeSnippet: + "node.on('input', function(msg) {\n if (msg.seed) { dummyjson.seed = msg.seed; }\n try {\n var value = dummyjson.parse(node.template, {mockdata: msg});\n if (node.syntax === 'json') {\n try { value = JSON.parse(value); }\n catch(e) { node.error(RED._('datagen.errors.json-error')); }\n }\n if (node.fieldType === 'msg') {\n RED.util.setMessageProperty(msg,node.field,value);\n }\n else if (node.fieldType === 'flow') {\n node.context().flow.set(node.field,value);\n }\n else if (node.fieldType === 'global') {\n node.context().global.set(node.field,value);\n }\n node.send(msg);\n }\n catch(e) {", + read: true, + }, + ]), +}; diff --git a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue index 1a1c50891f..1fb5d42c63 100644 --- a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue +++ b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue @@ -163,11 +163,16 @@ function growInput() { - + > +
@@ -243,20 +248,16 @@ function growInput() {

{{ t('assistantChat.placeholder.2') }} -

-

- {{ t('assistantChat.placeholder.3') }} - {{ t('assistantChat.placeholder.4') }} + {{ t('assistantChat.placeholder.3') }}

- {{ t('assistantChat.placeholder.5') }} + {{ t('assistantChat.placeholder.4') }}

@@ -407,8 +408,29 @@ p { .textMessage { display: flex; - align-items: center; + flex-direction: column; + gap: var(--spacing-xs); font-size: var(--font-size-2xs); + word-break: break-word; +} + +.code-snippet { + border: var(--border-base); + background-color: var(--color-foreground-xlight); + border-radius: var(--border-radius-base); + padding: var(--spacing-2xs); + font-family: var(--font-family-monospace); + max-height: 218px; // 12 lines + overflow: auto; + + pre { + white-space-collapse: collapse; + } + + code { + background-color: transparent; + font-size: var(--font-size-3xs); + } } .block { diff --git a/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap b/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap index fec00dc9a5..c7aa8da5cb 100644 --- a/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap +++ b/packages/design-system/src/components/AskAssistantChat/__tests__/__snapshots__/AskAssistantChat.spec.ts.snap @@ -132,7 +132,7 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = ` -

@@ -144,9 +144,10 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `

-
+
+ @@ -449,6 +450,7 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = ` + @@ -842,13 +844,10 @@ exports[`AskAssistantChat > renders default placeholder chat correctly 1`] = ` class="info" >

- I'm your Assistant, here to guide you through your journey with n8n. + I can answer most questions about building workflows in n8n.

- While I'm still learning, I'm already equipped to help you debug any errors you might encounter. -

-

- If you run into an issue with a node, you'll see the + For specific tasks, you’ll see the - button + button in the UI.

- Clicking it will start a chat with me, and I'll do my best to assist you! + How can I help?

- +
+