From 12d7a9fd12c0ca320e9d6eaaa34cd393dd01d26b Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Thu, 25 Sep 2025 15:02:47 +0100 Subject: [PATCH] feat(editor): T2W canvas and assistant prompt design improvements (no-changelog) (#19897) --- .../frontend/@n8n/design-system/package.json | 3 +- .../AskAssistantChat/AskAssistantChat.test.ts | 81 ++- .../AskAssistantChat/AskAssistantChat.vue | 210 ++++--- .../AskAssistantChat.test.ts.snap | 518 +++++++++++------- .../N8nIcon/custom/filled-square.svg | 3 + .../src/components/N8nIcon/icons.ts | 2 + .../N8nPromptInput/N8nPromptInput.stories.ts | 282 ++++++++++ .../N8nPromptInput/N8nPromptInput.test.ts | 514 +++++++++++++++++ .../N8nPromptInput/N8nPromptInput.vue | 435 +++++++++++++++ .../__snapshots__/N8nPromptInput.test.ts.snap | 120 ++++ .../src/components/N8nPromptInput/index.ts | 3 + .../N8nSendStopButton.stories.ts | 299 ++++++++++ .../N8nSendStopButton.test.ts | 262 +++++++++ .../N8nSendStopButton/N8nSendStopButton.vue | 62 +++ .../src/components/N8nSendStopButton/index.ts | 5 + .../design-system/src/components/index.ts | 2 + .../src/composables/useCharacterLimit.ts | 33 ++ .../design-system/src/composables/useI18n.ts | 3 +- .../@n8n/design-system/src/locale/lang/en.ts | 1 + .../frontend/@n8n/i18n/src/locales/en.json | 3 +- .../Agent/AskAssistantBuild.test.ts | 65 ++- .../AskAssistant/Agent/AskAssistantBuild.vue | 5 +- .../render-types/CanvasNodeAIPrompt.test.ts | 57 +- .../nodes/render-types/CanvasNodeAIPrompt.vue | 103 +--- .../CanvasNodeAIPrompt.test.ts.snap | 36 +- .../playwright/pages/AIAssistantPage.ts | 2 +- pnpm-lock.yaml | 147 +---- pnpm-workspace.yaml | 1 + 28 files changed, 2698 insertions(+), 559 deletions(-) create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nIcon/custom/filled-square.svg create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nPromptInput/N8nPromptInput.stories.ts create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nPromptInput/N8nPromptInput.test.ts create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nPromptInput/N8nPromptInput.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nPromptInput/__snapshots__/N8nPromptInput.test.ts.snap create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nPromptInput/index.ts create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nSendStopButton/N8nSendStopButton.stories.ts create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nSendStopButton/N8nSendStopButton.test.ts create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nSendStopButton/N8nSendStopButton.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/N8nSendStopButton/index.ts create mode 100644 packages/frontend/@n8n/design-system/src/composables/useCharacterLimit.ts diff --git a/packages/frontend/@n8n/design-system/package.json b/packages/frontend/@n8n/design-system/package.json index d648f0e4791..dcf8a052c62 100644 --- a/packages/frontend/@n8n/design-system/package.json +++ b/packages/frontend/@n8n/design-system/package.json @@ -47,7 +47,8 @@ "vite": "catalog:", "vitest": "catalog:", "vitest-mock-extended": "catalog:", - "vue-tsc": "catalog:frontend" + "vue-tsc": "catalog:frontend", + "@vue/test-utils": "catalog:frontend" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.36", diff --git a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts index 5eacff3d361..3d9eb38e5d8 100644 --- a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts +++ b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts @@ -1,4 +1,5 @@ import { render } from '@testing-library/vue'; +import { mount } from '@vue/test-utils'; import { vi } from 'vitest'; import AskAssistantChat from './AskAssistantChat.vue'; @@ -239,7 +240,7 @@ describe('AskAssistantChat', () => { expect(wrapper.queryByTestId('error-retry-button')).not.toBeInTheDocument(); }); - it('limits maximum input length when maxLength prop is specified', async () => { + it('limits maximum input length when maxCharacterLength prop is specified', async () => { const wrapper = render(AskAssistantChat, { global: { directives: { @@ -249,13 +250,14 @@ describe('AskAssistantChat', () => { }, props: { user: { firstName: 'Kobi', lastName: 'Dog' }, - maxLength: 100, + maxCharacterLength: 100, }, }); expect(wrapper.container).toMatchSnapshot(); - const textarea = wrapper.queryByTestId('chat-input'); - expect(textarea).toHaveAttribute('maxLength', '100'); + // The maxCharacterLength prop is passed to the N8nPromptInput component + // but the textarea element itself doesn't have this attribute + // We can verify the component receives the prop via snapshot }); describe('collapseToolMessages', () => { @@ -981,4 +983,75 @@ describe('AskAssistantChat', () => { expect(wrapper.container.textContent).not.toContain('Quick reply'); }); }); + + describe('onSendMessage', () => { + it('should emit message when N8nPromptInput submits', async () => { + const wrapper = mount(AskAssistantChat, { + global: { + directives: { n8nHtml }, + stubs: { + ...Object.fromEntries(stubs.map((stub) => [stub, true])), + N8nPromptInput: { + name: 'N8nPromptInput', + props: [ + 'modelValue', + 'inputPlaceholder', + 'disabled', + 'streaming', + 'maxCharacterLength', + ], + emits: ['update:modelValue', 'submit', 'stop'], + setup( + _: unknown, + { + emit, + expose, + }: { + emit: (event: string, ...args: unknown[]) => void; + expose: (exposed: Record) => void; + }, + ) { + const focusInput = vi.fn(); + + expose({ focusInput }); + + return { + handleSubmit: () => emit('submit'), + updateValue: (e: Event) => { + const target = e.target as HTMLTextAreaElement; + emit('update:modelValue', target.value); + }, + }; + }, + template: ` +
+ + +
+ `, + }, + }, + }, + props: { + user: { firstName: 'Test', lastName: 'User' }, + }, + }); + + const textarea = wrapper.find('[data-test-id="chat-input"] textarea'); + await textarea.setValue('Test message'); + + await textarea.trigger('input'); + + const sendButton = wrapper.find('[data-test-id="chat-input"] button'); + await sendButton.trigger('click'); + + // Verify message was emitted with the correct value + expect(wrapper.emitted('message')).toBeTruthy(); + const messageEvents = wrapper.emitted('message'); + expect(messageEvents?.[0]).toEqual(['Test message']); + + await wrapper.vm.$nextTick(); + wrapper.unmount(); + }); + }); }); diff --git a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.vue b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.vue index f88c9f525f7..df571d39f5e 100644 --- a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.vue +++ b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.vue @@ -1,5 +1,5 @@ @@ -339,51 +408,26 @@ defineExpose({
-