diff --git a/cypress/e2e/45-ai-assistant.cy.ts b/cypress/e2e/45-ai-assistant.cy.ts
index 37d38f739f..6be8a8db60 100644
--- a/cypress/e2e/45-ai-assistant.cy.ts
+++ b/cypress/e2e/45-ai-assistant.cy.ts
@@ -130,14 +130,15 @@ describe('AI Assistant::enabled', () => {
ndv.getters.nodeExecuteButton().click();
aiAssistant.getters.nodeErrorViewAssistantButton().click();
cy.wait('@chatRequest');
- aiAssistant.getters.quickReplies().should('have.length', 2);
- aiAssistant.getters.quickReplies().eq(0).click();
+ aiAssistant.getters.quickReplyButtons().should('have.length', 2);
+ aiAssistant.getters.quickReplyButtons().eq(0).click();
cy.wait('@chatRequest');
aiAssistant.getters.chatMessagesUser().should('have.length', 1);
aiAssistant.getters.chatMessagesUser().eq(0).should('contain.text', "Sure, let's do it");
});
- it('should send message to assistant when node is executed', () => {
+ 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',
@@ -148,10 +149,46 @@ describe('AI Assistant::enabled', () => {
aiAssistant.getters.nodeErrorViewAssistantButton().click();
cy.wait('@chatRequest');
aiAssistant.getters.chatMessagesAssistant().should('have.length', 1);
- // Executing the same node should sende a new message to the assistant automatically
+ 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) => {
+ if (['init-error-helper', 'message'].includes(req.body.payload.type)) {
+ res.send({ statusCode: 200, fixture: 'aiAssistant/simple_message_response.json' });
+ } else if (req.body.payload.type === 'event') {
+ res.send({ statusCode: 200, fixture: 'aiAssistant/node_execution_error_response.json' });
+ } else {
+ res.send({ statusCode: 500 });
+ }
+ });
+ }).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);
ndv.getters.nodeExecuteButton().click();
cy.wait('@chatRequest');
- aiAssistant.getters.chatMessagesAssistant().should('have.length', 2);
+ // Respond 'Yes' to the quick reply (request new suggestion)
+ aiAssistant.getters.quickReplies().contains('Yes').click();
+ cy.wait('@chatRequest');
+ // No quick replies at this point
+ aiAssistant.getters.quickReplies().should('not.exist');
+ ndv.getters.nodeExecuteButton().click();
+ // But after executing the node again, quick replies should be shown
+ aiAssistant.getters.chatMessagesAssistant().should('have.length', 4);
+ aiAssistant.getters.quickReplies().should('have.length', 2);
});
it('should warn before starting a new session', () => {
diff --git a/cypress/fixtures/aiAssistant/node_execution_error_response.json b/cypress/fixtures/aiAssistant/node_execution_error_response.json
new file mode 100644
index 0000000000..5fabc17034
--- /dev/null
+++ b/cypress/fixtures/aiAssistant/node_execution_error_response.json
@@ -0,0 +1,20 @@
+{
+ "sessionId": "1",
+ "messages": [
+ {
+ "role": "assistant",
+ "type": "message",
+ "text": "It seems like my suggestion did not work. Do you want me to come up with a different suggestion? You can also provide more context via the chat.",
+ "quickReplies": [
+ {
+ "text": "Yes",
+ "type": "new-suggestion"
+ },
+ {
+ "text": "No, I don't think you can help",
+ "type": "event:end-session"
+ }
+ ]
+ }
+ ]
+}
diff --git a/cypress/pages/features/ai-assistant.ts b/cypress/pages/features/ai-assistant.ts
index abca07fbbe..dbd8491923 100644
--- a/cypress/pages/features/ai-assistant.ts
+++ b/cypress/pages/features/ai-assistant.ts
@@ -26,7 +26,8 @@ export class AIAssistant extends BasePage {
chatMessagesAssistant: () => cy.getByTestId('chat-message-assistant'),
chatMessagesUser: () => cy.getByTestId('chat-message-user'),
chatMessagesSystem: () => cy.getByTestId('chat-message-system'),
- quickReplies: () => cy.getByTestId('quick-replies').find('button'),
+ quickReplies: () => cy.getByTestId('quick-replies'),
+ quickReplyButtons: () => this.getters.quickReplies().find('button'),
newAssistantSessionModal: () => cy.getByTestId('new-assistant-session-modal'),
codeDiffs: () => cy.getByTestId('code-diff-suggestion'),
applyCodeDiffButtons: () => cy.getByTestId('replace-code-button'),
diff --git a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts
index ec44622844..93c90b5089 100644
--- a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts
+++ b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.stories.ts
@@ -238,3 +238,12 @@ EndOfSessionChat.args = {
},
]),
};
+
+export const AssistantThinkingChat = Template.bind({});
+AssistantThinkingChat.args = {
+ user: {
+ firstName: 'Max',
+ lastName: 'Test',
+ },
+ loadingMessage: 'Thinking...',
+};
diff --git a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue
index a1ae2e8e88..1a1c50891f 100644
--- a/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue
+++ b/packages/design-system/src/components/AskAssistantChat/AskAssistantChat.vue
@@ -3,6 +3,7 @@ import { computed, ref } from 'vue';
import AssistantIcon from '../AskAssistantIcon/AssistantIcon.vue';
import AssistantText from '../AskAssistantText/AssistantText.vue';
import AssistantAvatar from '../AskAssistantAvatar/AssistantAvatar.vue';
+import AssistantLoadingMessage from '../AskAssistantLoadingMessage/AssistantLoadingMessage.vue';
import CodeDiff from '../CodeDiff/CodeDiff.vue';
import type { ChatUI } from '../../types/assistant';
import BlinkingCursor from '../BlinkingCursor/BlinkingCursor.vue';
@@ -33,11 +34,13 @@ interface Props {
};
messages?: ChatUI.AssistantMessage[];
streaming?: boolean;
+ loadingMessage?: string;
+ sessionId?: string;
}
const emit = defineEmits<{
close: [];
- message: [string, string | undefined];
+ message: [string, string?, boolean?];
codeReplace: [number];
codeUndo: [number];
}>();
@@ -58,17 +61,21 @@ const sendDisabled = computed(() => {
return !textInputValue.value || props.streaming || sessionEnded.value;
});
+const showPlaceholder = computed(() => {
+ return !props.messages?.length && !props.loadingMessage && !props.sessionId;
+});
+
function isEndOfSessionEvent(event?: ChatUI.AssistantMessage) {
return event?.type === 'event' && event?.eventName === 'end-session';
}
function onQuickReply(opt: ChatUI.QuickReply) {
- emit('message', opt.text, opt.type);
+ emit('message', opt.text, opt.type, opt.isFeedback);
}
function onSendMessage() {
if (sendDisabled.value) return;
- emit('message', textInputValue.value, undefined);
+ emit('message', textInputValue.value);
textInputValue.value = '';
if (chatInput.value) {
chatInput.value.style.height = 'auto';
@@ -221,26 +228,30 @@ function growInput() {
-
-
+
+
Hi {{ user?.firstName }} 👋
- {{
- t('assistantChat.placeholder.1', [
- `${user?.firstName}`,
- t('assistantChat.aiAssistantName'),
- ])
- }}
+ {{ t('assistantChat.placeholder.1') }}
{{ t('assistantChat.placeholder.2') }}
-
- {{ t('assistantChat.placeholder.3') }}
+ {{ t('assistantChat.placeholder.3') }}
+
{{ t('assistantChat.placeholder.4') }}
+
+ {{ t('assistantChat.placeholder.5') }}
+
@@ -325,6 +336,10 @@ p {
.messages {
padding: var(--spacing-xs);
+
+ & + & {
+ padding-top: 0;
+ }
}
.message {
@@ -391,6 +406,8 @@ p {
}
.textMessage {
+ display: flex;
+ align-items: center;
font-size: var(--font-size-2xs);
}
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 826732bdb8..fec00dc9a5 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
@@ -730,6 +730,7 @@ Testing more code
+
@@ -1131,6 +1136,7 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = `
+
renders streaming chat correctly 1`] = `
+
({
+ setup: () => ({ args }),
+ props: Object.keys(argTypes),
+ components: {
+ AssistantLoadingMessage,
+ },
+ template: `
`,
+});
+
+export const Default = Template.bind({});
+Default.args = {
+ message: 'Searching n8n documentation for the best possible answer...',
+};
+
+export const NarrowContainer = Template.bind({});
+NarrowContainer.args = {
+ ...Default.args,
+ templateWidth: '200px',
+};
diff --git a/packages/design-system/src/components/AskAssistantLoadingMessage/AssistantLoadingMessage.vue b/packages/design-system/src/components/AskAssistantLoadingMessage/AssistantLoadingMessage.vue
new file mode 100644
index 0000000000..2aa045565e
--- /dev/null
+++ b/packages/design-system/src/components/AskAssistantLoadingMessage/AssistantLoadingMessage.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+ {{ message }}
+
+
+
+
+
+
+
+
diff --git a/packages/design-system/src/components/AskAssistantLoadingMessage/DemoComponent.stories.ts b/packages/design-system/src/components/AskAssistantLoadingMessage/DemoComponent.stories.ts
new file mode 100644
index 0000000000..870127f0d3
--- /dev/null
+++ b/packages/design-system/src/components/AskAssistantLoadingMessage/DemoComponent.stories.ts
@@ -0,0 +1,30 @@
+import DemoComponent from './DemoComponent.vue';
+import type { StoryFn } from '@storybook/vue3';
+
+export default {
+ title: 'Assistant/AskAssistantLoadingMessageTransitions',
+ component: DemoComponent,
+ argTypes: {},
+};
+
+const Template: StoryFn = (args, { argTypes }) => ({
+ setup: () => ({ args }),
+ props: Object.keys(argTypes),
+ components: {
+ DemoComponent,
+ },
+ template: '
',
+});
+
+export const Default = Template.bind({});
+Default.args = {};
+
+export const Horizontal = Template.bind({});
+Horizontal.args = {
+ animationType: 'slide-horizontal',
+};
+
+export const Fade = Template.bind({});
+Fade.args = {
+ animationType: 'fade',
+};
diff --git a/packages/design-system/src/components/AskAssistantLoadingMessage/DemoComponent.vue b/packages/design-system/src/components/AskAssistantLoadingMessage/DemoComponent.vue
new file mode 100644
index 0000000000..deaf903af5
--- /dev/null
+++ b/packages/design-system/src/components/AskAssistantLoadingMessage/DemoComponent.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
diff --git a/packages/design-system/src/components/AskAssistantLoadingMessage/__tests__/AskAssistantLoadingMessage.spec.ts b/packages/design-system/src/components/AskAssistantLoadingMessage/__tests__/AskAssistantLoadingMessage.spec.ts
new file mode 100644
index 0000000000..86f9b8ae51
--- /dev/null
+++ b/packages/design-system/src/components/AskAssistantLoadingMessage/__tests__/AskAssistantLoadingMessage.spec.ts
@@ -0,0 +1,13 @@
+import { render } from '@testing-library/vue';
+import AssistantLoadingMessage from '../AssistantLoadingMessage.vue';
+
+describe('AssistantLoadingMessage', () => {
+ it('renders loading message correctly', () => {
+ const { container } = render(AssistantLoadingMessage, {
+ props: {
+ loadingMessage: 'Thinking...',
+ },
+ });
+ expect(container).toMatchSnapshot();
+ });
+});
diff --git a/packages/design-system/src/components/AskAssistantLoadingMessage/__tests__/__snapshots__/AskAssistantLoadingMessage.spec.ts.snap b/packages/design-system/src/components/AskAssistantLoadingMessage/__tests__/__snapshots__/AskAssistantLoadingMessage.spec.ts.snap
new file mode 100644
index 0000000000..32d8db1bba
--- /dev/null
+++ b/packages/design-system/src/components/AskAssistantLoadingMessage/__tests__/__snapshots__/AskAssistantLoadingMessage.spec.ts.snap
@@ -0,0 +1,71 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`AssistantLoadingMessage > renders loading message correctly 1`] = `
+
+`;
diff --git a/packages/design-system/src/locale/lang/en.ts b/packages/design-system/src/locale/lang/en.ts
index 449a617f91..a9357c4ce9 100644
--- a/packages/design-system/src/locale/lang/en.ts
+++ b/packages/design-system/src/locale/lang/en.ts
@@ -38,12 +38,14 @@ export default {
'assistantChat.sessionEndMessage.2': 'button in n8n',
'assistantChat.you': 'You',
'assistantChat.quickRepliesTitle': 'Quick reply 👇',
- 'assistantChat.placeholder.1': (options: string[]) =>
- `Hi ${options[0][0] || 'there'}, I'm ${options[0][1]} and I'm here to assist you with building workflows.`,
+ 'assistantChat.placeholder.1': () =>
+ "I'm your Assistant, here to guide you through your journey with n8n.",
'assistantChat.placeholder.2':
- "Whenever you encounter a task that I can help with, you'll see the",
- 'assistantChat.placeholder.3': 'button.',
- 'assistantChat.placeholder.4': 'Clicking it starts a chat session with me.',
+ "While I'm still learning, I'm already equipped to help you debug any errors you might encounter.",
+ 'assistantChat.placeholder.3': "If you run into an issue with a node, you'll see the",
+ 'assistantChat.placeholder.4': 'button',
+ 'assistantChat.placeholder.5':
+ "Clicking it will start a chat with me, and I'll do my best to assist you!",
'assistantChat.inputPlaceholder': 'Enter your response...',
'inlineAskAssistantButton.asked': 'Asked',
} as N8nLocale;
diff --git a/packages/design-system/src/types/assistant.ts b/packages/design-system/src/types/assistant.ts
index 40e1340d46..b1cab7a748 100644
--- a/packages/design-system/src/types/assistant.ts
+++ b/packages/design-system/src/types/assistant.ts
@@ -32,6 +32,7 @@ export namespace ChatUI {
export interface QuickReply {
type: string;
text: string;
+ isFeedback?: boolean;
}
export interface ErrorMessage {
diff --git a/packages/editor-ui/src/components/AskAssistant/AskAssistantChat.vue b/packages/editor-ui/src/components/AskAssistant/AskAssistantChat.vue
index ab4e62efa3..9ae4b29a5f 100644
--- a/packages/editor-ui/src/components/AskAssistant/AskAssistantChat.vue
+++ b/packages/editor-ui/src/components/AskAssistant/AskAssistantChat.vue
@@ -16,6 +16,8 @@ const user = computed(() => ({
lastName: usersStore.currentUser?.lastName ?? '',
}));
+const loadingMessage = computed(() => assistantStore.assistantThinkingMessage);
+
function onResize(data: { direction: string; x: number; width: number }) {
assistantStore.updateWindowWidth(data.width);
}
@@ -24,7 +26,7 @@ function onResizeDebounced(data: { direction: string; x: number; width: number }
void useDebounce().callDebounced(onResize, { debounceTime: 10, trailing: true }, data);
}
-async function onUserMessage(content: string, quickReplyType?: string) {
+async function onUserMessage(content: string, quickReplyType?: string, isFeedback = false) {
await assistantStore.sendMessage({ text: content, quickReplyType });
const task = 'error';
const solutionCount =
@@ -33,9 +35,10 @@ async function onUserMessage(content: string, quickReplyType?: string) {
(msg) => msg.role === 'assistant' && !['text', 'event'].includes(msg.type),
).length
: null;
- if (quickReplyType === 'all-good' || quickReplyType === 'still-stuck') {
+ if (isFeedback) {
telemetry.track('User gave feedback', {
task,
+ chat_session_id: assistantStore.currentSessionId,
is_quick_reply: !!quickReplyType,
is_positive: quickReplyType === 'all-good',
solution_count: solutionCount,
@@ -83,6 +86,8 @@ function onClose() {
:user="user"
:messages="assistantStore.chatMessages"
:streaming="assistantStore.streaming"
+ :loading-message="loadingMessage"
+ :session-id="assistantStore.currentSessionId"
@close="onClose"
@message="onUserMessage"
@code-replace="onCodeReplace"
diff --git a/packages/editor-ui/src/composables/useToast.ts b/packages/editor-ui/src/composables/useToast.ts
index 609683b7e1..ea0a367de3 100644
--- a/packages/editor-ui/src/composables/useToast.ts
+++ b/packages/editor-ui/src/composables/useToast.ts
@@ -22,7 +22,7 @@ const messageDefaults: Partial
> = {
position: 'bottom-right',
zIndex: 1900, // above NDV and below the modals
offset: 64,
- appendTo: '#node-view-root',
+ appendTo: '#app-grid',
customClass: 'content-toast',
};
diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json
index e1c12c350d..5afaf7f2a3 100644
--- a/packages/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/editor-ui/src/plugins/i18n/locales/en.json
@@ -147,6 +147,8 @@
"aiAssistant.serviceError.message": "Unable to connect to n8n's AI service",
"aiAssistant.codeUpdated.message.title": "Assistant modified workflow",
"aiAssistant.codeUpdated.message.body": "Open the {nodeName} node to see the changes",
+ "aiAssistant.thinkingSteps.analyzingError": "Analyzing the error...",
+ "aiAssistant.thinkingSteps.thinking": "Thinking...",
"banners.confirmEmail.message.1": "To secure your account and prevent future access issues, please confirm your",
"banners.confirmEmail.message.2": "email address.",
"banners.confirmEmail.button": "Confirm email",
diff --git a/packages/editor-ui/src/stores/__tests__/assistant.store.test.ts b/packages/editor-ui/src/stores/__tests__/assistant.store.test.ts
index 279f9e0b01..ffca695066 100644
--- a/packages/editor-ui/src/stores/__tests__/assistant.store.test.ts
+++ b/packages/editor-ui/src/stores/__tests__/assistant.store.test.ts
@@ -311,7 +311,6 @@ describe('AI Assistant store', () => {
};
const assistantStore = useAssistantStore();
await assistantStore.initErrorHelper(context);
- expect(assistantStore.chatMessages.length).toBe(2);
expect(apiSpy).toHaveBeenCalled();
});
});
diff --git a/packages/editor-ui/src/stores/assistant.store.ts b/packages/editor-ui/src/stores/assistant.store.ts
index bad9e8aee2..05299ad6c0 100644
--- a/packages/editor-ui/src/stores/assistant.store.ts
+++ b/packages/editor-ui/src/stores/assistant.store.ts
@@ -31,7 +31,7 @@ import { useUIStore } from './ui.store';
export const MAX_CHAT_WIDTH = 425;
export const MIN_CHAT_WIDTH = 250;
-export const DEFAULT_CHAT_WIDTH = 325;
+export const DEFAULT_CHAT_WIDTH = 330;
export const ENABLED_VIEWS = [...EDITABLE_CANVAS_VIEWS, VIEWS.EXECUTION_PREVIEW];
const READABLE_TYPES = ['code-diff', 'text', 'block'];
@@ -63,6 +63,10 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
const currentSessionActiveExecutionId = ref();
const currentSessionWorkflowId = ref();
const lastUnread = ref();
+ const nodeExecutionStatus = ref<'not_executed' | 'success' | 'error'>('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();
const isExperimentEnabled = computed(
() => getVariant(AI_ASSISTANT_EXPERIMENT.name) === AI_ASSISTANT_EXPERIMENT.variant,
@@ -117,6 +121,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
lastUnread.value = undefined;
currentSessionActiveExecutionId.value = undefined;
suggestions.value = {};
+ nodeExecutionStatus.value = 'not_executed';
}
// As assistant sidebar opens and closes, use window width to calculate the container width
@@ -140,6 +145,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
const messages = [...chatMessages.value].filter(
(msg) => !(msg.id === id && msg.role === 'assistant'),
);
+ assistantThinkingMessage.value = undefined;
// TODO: simplify
assistantMessages.forEach((msg) => {
if (msg.type === 'message') {
@@ -190,6 +196,8 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
quickReplies: msg.quickReplies,
read,
});
+ } else if (msg.type === 'intermediate-step') {
+ assistantThinkingMessage.value = msg.text;
}
});
chatMessages.value = messages;
@@ -226,14 +234,8 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
});
}
- function addEmptyAssistantMessage(id: string) {
- chatMessages.value.push({
- id,
- role: 'assistant',
- type: 'text',
- content: '',
- read: false,
- });
+ function addLoadingAssistantMessage(message: string) {
+ assistantThinkingMessage.value = message;
}
function addUserMessage(content: string, id: string) {
@@ -249,6 +251,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
function handleServiceError(e: unknown, id: string) {
assert(e instanceof Error);
stopStreaming();
+ assistantThinkingMessage.value = undefined;
addAssistantError(`${locale.baseText('aiAssistant.serviceError.message')}: (${e.message})`, id);
}
@@ -316,7 +319,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
const availableAuthOptions = getNodeAuthOptions(nodeType);
authType = availableAuthOptions.find((option) => option.value === credentialInUse);
}
- addEmptyAssistantMessage(id);
+ addLoadingAssistantMessage(locale.baseText('aiAssistant.thinkingSteps.analyzingError'));
openChat();
streaming.value = true;
@@ -351,7 +354,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
assert(currentSessionId.value);
const id = getRandomId();
- addEmptyAssistantMessage(id);
+ addLoadingAssistantMessage(locale.baseText('aiAssistant.thinkingSteps.thinking'));
streaming.value = true;
chatWithAssistant(
rootStore.restApiContext,
@@ -369,21 +372,30 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
(e) => handleServiceError(e, id),
);
}
-
async function onNodeExecution(pushEvent: IPushDataNodeExecuteAfter) {
if (!chatSessionError.value || pushEvent.nodeName !== chatSessionError.value.node.name) {
return;
}
- if (pushEvent.data.error) {
+ if (pushEvent.data.error && nodeExecutionStatus.value !== 'error') {
await sendEvent('node-execution-errored', pushEvent.data.error);
- } else if (pushEvent.data.executionStatus === 'success') {
+ nodeExecutionStatus.value = 'error';
+ telemetry.track('User executed node after assistant suggestion', {
+ task: 'error',
+ chat_session_id: currentSessionId.value,
+ success: false,
+ });
+ } else if (
+ pushEvent.data.executionStatus === 'success' &&
+ nodeExecutionStatus.value !== 'success'
+ ) {
await sendEvent('node-execution-succeeded');
+ nodeExecutionStatus.value = 'success';
+ telemetry.track('User executed node after assistant suggestion', {
+ task: 'error',
+ chat_session_id: currentSessionId.value,
+ success: true,
+ });
}
- telemetry.track('User executed node after assistant suggestion', {
- task: 'error',
- chat_session_id: currentSessionId.value,
- success: pushEvent.data.executionStatus === 'success',
- });
}
async function sendMessage(
@@ -396,10 +408,16 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
const id = getRandomId();
try {
addUserMessage(chatMessage.text, id);
- addEmptyAssistantMessage(id);
+ addLoadingAssistantMessage(locale.baseText('aiAssistant.thinkingSteps.thinking'));
streaming.value = true;
assert(currentSessionId.value);
+ if (
+ chatMessage.quickReplyType === 'new-suggestion' &&
+ nodeExecutionStatus.value !== 'not_executed'
+ ) {
+ nodeExecutionStatus.value = 'not_executed';
+ }
chatWithAssistant(
rootStore.restApiContext,
{
@@ -415,6 +433,12 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
() => onDoneStreaming(id),
(e) => handleServiceError(e, id),
);
+ telemetry.track('User sent message in Assistant', {
+ message: chatMessage.text,
+ is_quick_reply: !!chatMessage.quickReplyType,
+ chat_session_id: currentSessionId.value,
+ message_number: chatMessages.value.filter((msg) => msg.role === 'user').length,
+ });
} catch (e: unknown) {
// in case of assert
handleServiceError(e, id);
@@ -566,5 +590,6 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
resetAssistantChat,
chatWindowOpen,
addAssistantMessages,
+ assistantThinkingMessage,
};
});
diff --git a/packages/editor-ui/src/types/assistant.types.ts b/packages/editor-ui/src/types/assistant.types.ts
index 86f3576afe..6cbfa61ed0 100644
--- a/packages/editor-ui/src/types/assistant.types.ts
+++ b/packages/editor-ui/src/types/assistant.types.ts
@@ -76,6 +76,7 @@ export namespace ChatRequest {
role: 'assistant';
type: 'message';
text: string;
+ step?: 'n8n_documentation' | 'n8n_forum';
}
interface AssistantSummaryMessage {
@@ -98,8 +99,21 @@ export namespace ChatRequest {
text: string;
}
+ interface AgentThinkingStep {
+ role: 'assistant';
+ type: 'intermediate-step';
+ text: string;
+ step: string;
+ }
+
export type MessageResponse =
- | ((AssistantChatMessage | CodeDiffMessage | AssistantSummaryMessage | AgentChatMessage) & {
+ | ((
+ | AssistantChatMessage
+ | CodeDiffMessage
+ | AssistantSummaryMessage
+ | AgentChatMessage
+ | AgentThinkingStep
+ ) & {
quickReplies?: QuickReplyOption[];
})
| EndSessionMessage;