From 97055d5714e89c4779e46c250e843b40e7c824c5 Mon Sep 17 00:00:00 2001 From: oleg Date: Mon, 28 Apr 2025 15:38:32 +0200 Subject: [PATCH] feat: AI workflow builder front-end (no-changelog) (#14820) Co-authored-by: Giulio Andreini --- .../@n8n/ai-workflow-builder/package.json | 2 +- .../src/ai-workflow-builder.service.ts | 56 +- .../ai-workflow-builder/src/chains/planner.ts | 15 +- .../src/chains/validator.ts | 75 + .../@n8n/ai-workflow-builder/src/types.ts | 8 + .../ai-workflow-builder/src/workflow-state.ts | 2 + packages/cli/package.json | 2 +- .../@n8n/design-system/.storybook/fonts.scss | 27 +- .../AskAssistantButton/AskAssistantButton.vue | 10 +- .../AskAssistantChat.stories.ts | 125 +- .../AskAssistantChat/AskAssistantChat.vue | 546 +++----- .../AskAssistantChat.test.ts.snap | 1235 +++++++++-------- .../AskAssistantChat/messages/BaseMessage.vue | 65 + .../messages/BlockMessage.vue | 115 ++ .../messages/CodeDiffMessage.vue | 50 + .../messages/ErrorMessage.vue | 66 + .../messages/EventMessage.vue | 72 + .../AskAssistantChat/messages/TextMessage.vue | 182 +++ .../AskAssistantChat/messages/useMarkdown.ts | 32 + .../messages/workflow/BaseWorkflowMessage.vue | 71 + .../workflow/ComposedNodesMessage.vue | 54 + .../messages/workflow/RateWorkflowMessage.vue | 122 ++ .../workflow/WorkflowGeneratedMessage.vue | 31 + .../workflow/WorkflowNodesMessage.vue | 48 + .../workflow/WorkflowStepsMessage.vue | 49 + .../AssistantLoadingMessage.vue | 6 +- .../@n8n/design-system/src/locale/lang/en.ts | 14 + .../@n8n/design-system/src/types/assistant.ts | 73 +- packages/frontend/editor-ui/src/App.vue | 12 +- packages/frontend/editor-ui/src/api/ai.ts | 17 + .../Agent/AskAssistantBuild.test.ts | 195 +++ .../AskAssistant/Agent/AskAssistantBuild.vue | 230 +++ .../components/AskAssistant/AssistantsHub.vue | 102 ++ .../{ => Chat}/AskAssistantChat.vue | 75 +- .../{ => Chat}/AskAssistantFloatingButton.vue | 9 +- .../{ => Chat}/NewAssistantSessionModal.vue | 2 +- .../components/AskAssistant/HubSwitcher.vue | 32 + .../editor-ui/src/components/Modals.vue | 2 +- .../Node/NodeCreator/NodeCreator.vue | 16 +- .../src/components/canvas/Canvas.vue | 16 +- .../canvas/elements/nodes/CanvasNode.vue | 5 +- .../elements/nodes/CanvasNodeRenderer.vue | 4 + .../nodes/render-types/CanvasNodeAIPrompt.vue | 131 ++ .../src/composables/useCanvasLayout.ts | 6 +- .../src/composables/useCanvasMapping.ts | 10 + packages/frontend/editor-ui/src/constants.ts | 8 + .../editor-ui/src/event-bus/node-view.ts | 3 + .../src/plugins/i18n/locales/en.json | 9 + .../editor-ui/src/stores/assistant.store.ts | 2 +- .../src/stores/builder.store.test.ts | 370 +++++ .../editor-ui/src/stores/builder.store.ts | 365 +++++ .../editor-ui/src/types/assistant.types.ts | 57 +- .../frontend/editor-ui/src/types/canvas.ts | 14 +- .../frontend/editor-ui/src/views/NodeView.vue | 61 +- pnpm-lock.yaml | 17 +- pnpm-workspace.yaml | 1 + 56 files changed, 3857 insertions(+), 1067 deletions(-) create mode 100644 packages/@n8n/ai-workflow-builder/src/chains/validator.ts create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/BaseMessage.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/BlockMessage.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/CodeDiffMessage.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/ErrorMessage.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/EventMessage.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/TextMessage.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/useMarkdown.ts create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/workflow/BaseWorkflowMessage.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/workflow/ComposedNodesMessage.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/workflow/RateWorkflowMessage.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/workflow/WorkflowGeneratedMessage.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/workflow/WorkflowNodesMessage.vue create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/workflow/WorkflowStepsMessage.vue create mode 100644 packages/frontend/editor-ui/src/components/AskAssistant/Agent/AskAssistantBuild.test.ts create mode 100644 packages/frontend/editor-ui/src/components/AskAssistant/Agent/AskAssistantBuild.vue create mode 100644 packages/frontend/editor-ui/src/components/AskAssistant/AssistantsHub.vue rename packages/frontend/editor-ui/src/components/AskAssistant/{ => Chat}/AskAssistantChat.vue (58%) rename packages/frontend/editor-ui/src/components/AskAssistant/{ => Chat}/AskAssistantFloatingButton.vue (90%) rename packages/frontend/editor-ui/src/components/AskAssistant/{ => Chat}/NewAssistantSessionModal.vue (98%) create mode 100644 packages/frontend/editor-ui/src/components/AskAssistant/HubSwitcher.vue create mode 100644 packages/frontend/editor-ui/src/components/canvas/elements/nodes/render-types/CanvasNodeAIPrompt.vue create mode 100644 packages/frontend/editor-ui/src/stores/builder.store.test.ts create mode 100644 packages/frontend/editor-ui/src/stores/builder.store.ts diff --git a/packages/@n8n/ai-workflow-builder/package.json b/packages/@n8n/ai-workflow-builder/package.json index dff6973bad..9cd77f590b 100644 --- a/packages/@n8n/ai-workflow-builder/package.json +++ b/packages/@n8n/ai-workflow-builder/package.json @@ -33,7 +33,7 @@ "@langchain/openai": "catalog:", "@n8n/config": "workspace:*", "@n8n/di": "workspace:*", - "@n8n_io/ai-assistant-sdk": "1.13.0", + "@n8n_io/ai-assistant-sdk": "catalog:", "n8n-workflow": "workspace:*", "zod": "catalog:" }, diff --git a/packages/@n8n/ai-workflow-builder/src/ai-workflow-builder.service.ts b/packages/@n8n/ai-workflow-builder/src/ai-workflow-builder.service.ts index c9bc7c7ffd..d72c0d6b8a 100644 --- a/packages/@n8n/ai-workflow-builder/src/ai-workflow-builder.service.ts +++ b/packages/@n8n/ai-workflow-builder/src/ai-workflow-builder.service.ts @@ -12,6 +12,7 @@ import { connectionComposerChain } from './chains/connection-composer'; import { nodesSelectionChain } from './chains/node-selector'; import { nodesComposerChain } from './chains/nodes-composer'; import { plannerChain } from './chains/planner'; +import { validatorChain } from './chains/validator'; import { ILicenseService } from './interfaces'; import { anthropicClaude37Sonnet, gpt41mini } from './llm-config'; import type { MessageResponse } from './types'; @@ -58,19 +59,21 @@ export class AiWorkflowBuilderService { assert(this.client, 'Client not setup'); - // @ts-expect-error getProxyHeaders will only be available after `@n8n_io/ai-assistant-sdk` v1.14.0 is released - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - const authHeaders = (await this.client?.getProxyHeaders(user)) as Record; + const authHeaders = await this.client.generateApiProxyCredentials(user); this.llmSimpleTask = gpt41mini({ baseUrl: baseUrl + '/v1/api-proxy/openai', // When using api-proxy the key will be populated automatically, we just need to pass a placeholder - apiKey: '_', - headers: authHeaders, + apiKey: '-', + headers: { + Authorization: authHeaders.apiKey, + }, }); this.llmComplexTask = anthropicClaude37Sonnet({ baseUrl: baseUrl + '/v1/api-proxy/anthropic', - apiKey: '_', - headers: authHeaders, + apiKey: '-', + headers: { + Authorization: authHeaders.apiKey, + }, }); return; } @@ -97,6 +100,7 @@ export class AiWorkflowBuilderService { private isWorkflowEvent(eventName: string): boolean { return [ + 'prompt_validation', 'generated_steps', 'generated_nodes', 'composed_nodes', @@ -106,6 +110,33 @@ export class AiWorkflowBuilderService { } private getAgent() { + const validatorChainNode = async ( + state: typeof WorkflowState.State, + config: RunnableConfig, + ): Promise> => { + assert(this.llmSimpleTask, 'LLM not setup'); + + const isWorkflowPrompt = await validatorChain(this.llmSimpleTask).invoke( + { + prompt: state.prompt, + }, + config, + ); + + if (!isWorkflowPrompt) { + await dispatchCustomEvent('prompt_validation', { + role: 'assistant', + type: 'prompt-validation', + isWorkflowPrompt, + id: Date.now().toString(), + }); + } + + return { + isWorkflowPrompt, + }; + }; + const plannerChainNode = async ( state: typeof WorkflowState.State, config: RunnableConfig, @@ -290,7 +321,7 @@ export class AiWorkflowBuilderService { ///////////////////// Workflow Graph Definition ///////////////////// const workflowGraph = new StateGraph(WorkflowState) - // .addNode('supervisor', supervisorChainNode) + .addNode('validator', validatorChainNode) .addNode('planner', plannerChainNode) .addNode('node_selector', nodeSelectionChainNode) .addNode('nodes_composer', nodesComposerChainNode) @@ -298,8 +329,12 @@ export class AiWorkflowBuilderService { .addNode('finalize', generateWorkflowJSON); // Define the graph edges to set the processing order: - // Start with the planner. - workflowGraph.addEdge(START, 'planner'); + // Start with the validator + workflowGraph.addEdge(START, 'validator'); + // If validated, continue to planner + workflowGraph.addConditionalEdges('validator', (state) => { + return state.isWorkflowPrompt ? 'planner' : END; + }); // Planner node flows into node selector: workflowGraph.addEdge('planner', 'node_selector'); // Node selector is followed by nodes composer: @@ -327,6 +362,7 @@ export class AiWorkflowBuilderService { steps: [], nodes: [], workflowJSON: { nodes: [], connections: {} }, + isWorkflowPrompt: false, next: 'PLAN', }; diff --git a/packages/@n8n/ai-workflow-builder/src/chains/planner.ts b/packages/@n8n/ai-workflow-builder/src/chains/planner.ts index 89dd947aa7..509c0e9673 100644 --- a/packages/@n8n/ai-workflow-builder/src/chains/planner.ts +++ b/packages/@n8n/ai-workflow-builder/src/chains/planner.ts @@ -10,18 +10,25 @@ export const plannerPrompt = new SystemMessage( `You are a Workflow Planner for n8n, a platform that helps users automate processes across different services and APIs. ## Your Task -Convert user requests into clear, sequential workflow steps that can be implemented with n8n nodes. +Convert user requests into clear, sequential workflow steps that can be implemented with n8n nodes. ONLY include steps that are explicitly stated or directly implied in the user request. ## Guidelines 1. Analyze the user request to understand their end goal and required process 2. Break down the automation into logical steps based on complexity - simpler workflows need fewer steps, complex ones may need more -3. Focus on actions (fetch data, transform, filter, send notification, etc.) +3. Focus ONLY on actions mentioned directly in the user prompt 4. Create steps that can be mapped to n8n nodes later 5. Order steps sequentially from trigger to final action -6. Be specific about data transformations needed -7. Include error handling steps when appropriate +6. Be specific about data transformations needed ONLY if mentioned in the request +7. NEVER add extra steps like storing data or sending notifications unless explicitly requested 8. Only recommend raw HTTP requests if you think there isn't a suitable n8n node +## CRITICAL REQUIREMENTS +- DO NOT add any steps not directly mentioned or implied in the user request +- DO NOT assume the user wants to store data in a database unless explicitly stated +- DO NOT assume the user wants to send notifications or emails unless explicitly stated +- DO NOT add any "nice to have" steps that aren't clearly part of the user's request +- Keep the workflow EXACTLY focused on what was requested, nothing more + ## Output Format Return ONLY a JSON object with this structure: \`\`\`json diff --git a/packages/@n8n/ai-workflow-builder/src/chains/validator.ts b/packages/@n8n/ai-workflow-builder/src/chains/validator.ts new file mode 100644 index 0000000000..e4cb19d78b --- /dev/null +++ b/packages/@n8n/ai-workflow-builder/src/chains/validator.ts @@ -0,0 +1,75 @@ +import type { BaseChatModel } from '@langchain/core/language_models/chat_models'; +import type { AIMessageChunk } from '@langchain/core/messages'; +import { SystemMessage } from '@langchain/core/messages'; +import { ChatPromptTemplate, HumanMessagePromptTemplate } from '@langchain/core/prompts'; +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { OperationalError } from 'n8n-workflow'; +import { z } from 'zod'; + +const validatorPrompt = new SystemMessage( + `You are a workflow prompt validator for n8n. You need to analyze the user's prompt and determine +if they're actually trying to build a workflow that connects different online services or automates a task. + +A workflow prompt should: +- Describe an automation or integration task +- Potentially mention connecting services (like Google Sheets, Slack, etc.) +- Describe a process that could be broken down into steps +- Mention something that could be automated + +Examples of VALID workflow prompts: +- "Create a workflow that sends a Slack message when a new row is added to Google Sheets" +- "I want to automatically save Gmail attachments to Dropbox" +- "Build a workflow that posts new Twitter mentions to a Discord channel" +- "When I get a new lead in my CRM, add them to my email marketing list" + +Examples of INVALID workflow prompts: +- "What's the weather like today?" +- "Tell me a joke" +- "What is n8n?" +- "Help me fix my computer" +- "What time is it?" + + +Analyze the prompt and determine if it's a valid workflow prompt. Respond with just true or false.`, +); + +const validatorSchema = z.object({ + isWorkflowPrompt: z.boolean(), +}); + +const validatorTool = new DynamicStructuredTool({ + name: 'validate_prompt', + description: 'Validate if the user prompt is a workflow prompt', + schema: validatorSchema, + func: async ({ isWorkflowPrompt }) => { + return { isWorkflowPrompt }; + }, +}); + +const humanTemplate = ` + + {prompt} + +`; + +const chatPrompt = ChatPromptTemplate.fromMessages([ + validatorPrompt, + HumanMessagePromptTemplate.fromTemplate(humanTemplate), +]); + +export const validatorChain = (llm: BaseChatModel) => { + if (!llm.bindTools) { + throw new OperationalError("LLM doesn't support binding tools"); + } + + return chatPrompt + .pipe( + llm.bindTools([validatorTool], { + tool_choice: validatorTool.name, + }), + ) + .pipe((x: AIMessageChunk) => { + const toolCall = x.tool_calls?.[0]; + return (toolCall?.args as z.infer).isWorkflowPrompt; + }); +}; diff --git a/packages/@n8n/ai-workflow-builder/src/types.ts b/packages/@n8n/ai-workflow-builder/src/types.ts index 7d6a9fcd72..27a28dd6c4 100644 --- a/packages/@n8n/ai-workflow-builder/src/types.ts +++ b/packages/@n8n/ai-workflow-builder/src/types.ts @@ -88,6 +88,13 @@ export interface WorkflowConnectionsMessage { read: boolean; } +export interface PromptValidationMessage { + role: 'assistant'; + type: 'prompt-validation'; + isWorkflowPrompt: boolean; + id: string; +} + export type MessageResponse = | (( | AssistantChatMessage @@ -99,6 +106,7 @@ export type MessageResponse = | WorkflowNodeMessage | WorkflowComposedMessage | WorkflowConnectionsMessage + | PromptValidationMessage ) & { quickReplies?: QuickReplyOption[]; }) diff --git a/packages/@n8n/ai-workflow-builder/src/workflow-state.ts b/packages/@n8n/ai-workflow-builder/src/workflow-state.ts index 3e87729f30..a9318dbb97 100644 --- a/packages/@n8n/ai-workflow-builder/src/workflow-state.ts +++ b/packages/@n8n/ai-workflow-builder/src/workflow-state.ts @@ -17,6 +17,8 @@ export const WorkflowState = Annotation.Root({ workflowJSON: Annotation({ reducer: (x, y) => y ?? x ?? { nodes: [], connections: {} }, }), + // Whether the user prompt is a workflow prompt. + isWorkflowPrompt: Annotation({ reducer: (x, y) => y ?? x ?? false }), // The next phase to be executed in the workflow graph. next: Annotation({ reducer: (x, y) => y ?? x ?? END, default: () => END }), }); diff --git a/packages/cli/package.json b/packages/cli/package.json index c686463bb3..a9c24c3975 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -103,7 +103,7 @@ "@n8n/task-runner": "workspace:*", "@n8n/ai-workflow-builder": "workspace:*", "@n8n/typeorm": "0.3.20-12", - "@n8n_io/ai-assistant-sdk": "1.13.0", + "@n8n_io/ai-assistant-sdk": "catalog:", "@n8n_io/license-sdk": "2.20.0", "@oclif/core": "4.0.7", "@rudderstack/rudder-sdk-node": "2.0.9", diff --git a/packages/frontend/@n8n/design-system/.storybook/fonts.scss b/packages/frontend/@n8n/design-system/.storybook/fonts.scss index e6e5ab65d6..00f332c4f8 100644 --- a/packages/frontend/@n8n/design-system/.storybook/fonts.scss +++ b/packages/frontend/@n8n/design-system/.storybook/fonts.scss @@ -1 +1,26 @@ -@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap'); +/* Storybook-specific font paths */ +@font-face { + font-family: InterVariable; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url('../src/css/fonts/InterVariable.woff2') format('woff2'); +} + +@font-face { + font-family: InterVariable; + font-style: italic; + font-weight: 100 900; + font-display: swap; + src: url('../src/css/fonts/InterVariable-Italic.woff2') format('woff2'); +} + +@font-face { + font-family: CommitMono; + font-style: italic; + font-weight: 100 900; + font-display: swap; + src: url('../src/css/fonts/CommitMonoVariable.woff2') format('woff2'); +} + +@import '../src/css/_tokens.scss'; diff --git a/packages/frontend/@n8n/design-system/src/components/AskAssistantButton/AskAssistantButton.vue b/packages/frontend/@n8n/design-system/src/components/AskAssistantButton/AskAssistantButton.vue index 74d0a36316..cfb7822242 100644 --- a/packages/frontend/@n8n/design-system/src/components/AskAssistantButton/AskAssistantButton.vue +++ b/packages/frontend/@n8n/design-system/src/components/AskAssistantButton/AskAssistantButton.vue @@ -10,7 +10,7 @@ const { t } = useI18n(); const hovering = ref(false); -const props = defineProps<{ unreadCount?: number }>(); +const props = defineProps<{ unreadCount?: number; type?: 'assistant' | 'builder' }>(); const emit = defineEmits<{ click: [e: MouseEvent]; @@ -40,7 +40,13 @@ function onMouseLeave() {
- +
diff --git a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts index 5d3601c5c6..9e9898d143 100644 --- a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts +++ b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts @@ -25,6 +25,24 @@ const Template: StoryFn = (args, { argTypes }) => ({ methods, }); +const TemplateWithInputPlaceholder: StoryFn = (args, { argTypes }) => ({ + setup: () => ({ args }), + props: Object.keys(argTypes), + components: { + AskAssistantChat, + }, + template: ` +
+ + + +
+ `, + methods, +}); + export const DefaultPlaceholderChat = Template.bind({}); DefaultPlaceholderChat.args = { user: { @@ -33,6 +51,14 @@ DefaultPlaceholderChat.args = { }, }; +export const InputPlaceholderChat = TemplateWithInputPlaceholder.bind({}); +DefaultPlaceholderChat.args = { + user: { + firstName: 'Max', + lastName: 'Test', + }, +}; + export const Chat = Template.bind({}); Chat.args = { user: { @@ -78,7 +104,7 @@ Chat.args = { id: '2', type: 'block', role: 'assistant', - title: 'Credential doesn’t have correct permissions to send a message', + title: "Credential doesn't have correct permissions to send a message", content: 'Solution steps:\n1. Lorem ipsum dolor sit amet, consectetur **adipiscing** elit. Proin id nulla placerat, tristique ex at, euismod dui.\n2. Copy this into somewhere\n3. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id nulla placerat, tristique ex at, euismod dui.\n4. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id nulla placerat, tristique ex at, euismod dui. \n Testing more code \n - Unordered item 1 \n - Unordered item 2', read: false, @@ -117,7 +143,7 @@ JustSummary.args = { id: '123', role: 'assistant', type: 'block', - title: 'Credential doesn’t have correct permissions to send a message', + title: "Credential doesn't have correct permissions to send a message", content: 'Solution steps:\n1. Lorem ipsum dolor sit amet, consectetur **adipiscing** elit. Proin id nulla placerat, tristique ex at, euismod dui.\n2. Copy this into somewhere\n3. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id nulla placerat, tristique ex at, euismod dui.\n4. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin id nulla placerat, tristique ex at, euismod dui. \n Testing more code \n - Unordered item 1 \n - Unordered item 2', read: false, @@ -136,7 +162,7 @@ SummaryTitleStreaming.args = { id: '123', role: 'assistant', type: 'block', - title: 'Credential doesn’t have', + title: "Credential doesn't have", content: '', read: false, }, @@ -155,7 +181,7 @@ SummaryContentStreaming.args = { id: '123', role: 'assistant', type: 'block', - title: 'Credential doesn’t have correct permissions to send a message', + title: "Credential doesn't have correct permissions to send a message", content: 'Solution steps:\n1. Lorem ipsum dolor sit amet, consectetur', read: false, }, @@ -372,3 +398,94 @@ RichTextMessage.args = { }, ]), }; + +export const WorkflowStepsChat = Template.bind({}); +WorkflowStepsChat.args = { + user: { + firstName: 'Max', + lastName: 'Test', + }, + messages: getMessages([ + { + id: '123', + type: 'workflow-step', + role: 'assistant', + steps: [ + 'Create a new HTTP Trigger node', + 'Add a Transform node to process the data', + 'Connect to your database using PostgreSQL node', + 'Send confirmation email with SendGrid node', + ], + read: false, + }, + ]), +}; + +export const WorkflowNodesChat = Template.bind({}); +WorkflowNodesChat.args = { + user: { + firstName: 'Max', + lastName: 'Test', + }, + messages: getMessages([ + { + id: '124', + type: 'workflow-node', + role: 'assistant', + nodes: ['HTTP Trigger', 'Transform', 'PostgreSQL', 'SendGrid'], + read: false, + }, + ]), +}; + +export const ComposedNodesChat = Template.bind({}); +ComposedNodesChat.args = { + user: { + firstName: 'Max', + lastName: 'Test', + }, + messages: getMessages([ + { + id: '125', + type: 'workflow-composed', + role: 'assistant', + nodes: [ + { + name: 'HTTP Trigger', + type: 'n8n-nodes-base.httpTrigger', + parameters: { + path: '/webhook', + authentication: 'none', + }, + position: [100, 100], + }, + { + name: 'Transform', + type: 'n8n-nodes-base.set', + parameters: { + values: { field: 'value' }, + }, + position: [300, 100], + }, + ], + read: false, + }, + ]), +}; + +export const RateWorkflowMessage = Template.bind({}); +RateWorkflowMessage.args = { + user: { + firstName: 'Max', + lastName: 'Test', + }, + messages: getMessages([ + { + id: '126', + type: 'rate-workflow', + role: 'assistant', + content: 'Is this workflow helpful?', + read: false, + }, + ]), +}; 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 2b895be0bb..72796a1274 100644 --- a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.vue +++ b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.vue @@ -1,39 +1,26 @@ @@ -127,9 +118,10 @@ async function onCopyButtonClick(content: string, e: MouseEvent) {
- +
+
@@ -138,140 +130,91 @@ async function onCopyButtonClick(content: string, e: MouseEvent) {
-
-
- - - - {{ - t('assistantChat.aiAssistantName') - }} - {{ t('assistantChat.you') }} -
-
-
-
- {{ message.title }} - -
-
- - -
-
-
-
- -
-
-
- - {{ t('assistantChat.copy') }} - -
-
-
- -
-
+ + - ⚠️ {{ message.content }} - - {{ t('generic.retry') }} - -
-
- -
-
- - {{ t('assistantChat.sessionEndMessage.1') }} - - - - {{ t('assistantChat.sessionEndMessage.2') }} - -
+ :message="message" + :is-first-of-role="i === 0 || message.role !== messages[i - 1].role" + :user="user" + /> + + + + + + +
@@ -289,7 +232,7 @@ async function onCopyButtonClick(content: string, e: MouseEvent) {
-
+
-
Hi {{ user?.firstName }} 👋
-
-

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

-

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

-

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

+
+
+
-