mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
feat(editor): Add AI Assistant support chat (#10656)
This commit is contained in:
parent
899b0a19ef
commit
3a8078068e
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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!"
|
||||
},
|
||||
|
|
|
@ -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');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
]),
|
||||
};
|
||||
|
|
|
@ -163,11 +163,16 @@ function growInput() {
|
|||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-if="message.role === 'user'" v-html="renderMarkdown(message.content)"></span>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span
|
||||
<div
|
||||
v-else
|
||||
:class="$style.assistantText"
|
||||
v-html="renderMarkdown(message.content)"
|
||||
></span>
|
||||
></div>
|
||||
<div
|
||||
v-if="message?.codeSnippet"
|
||||
:class="$style['code-snippet']"
|
||||
v-html="renderMarkdown(message.codeSnippet).trim()"
|
||||
></div>
|
||||
<BlinkingCursor
|
||||
v-if="streaming && i === messages?.length - 1 && message.role === 'assistant'"
|
||||
/>
|
||||
|
@ -243,20 +248,16 @@ function growInput() {
|
|||
</p>
|
||||
<p>
|
||||
{{ t('assistantChat.placeholder.2') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ t('assistantChat.placeholder.3') }}
|
||||
<InlineAskAssistantButton size="small" :static="true" />
|
||||
{{ t('assistantChat.placeholder.4') }}
|
||||
{{ t('assistantChat.placeholder.3') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ t('assistantChat.placeholder.5') }}
|
||||
{{ t('assistantChat.placeholder.4') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="messages?.length"
|
||||
:class="{ [$style.inputWrapper]: true, [$style.disabledInput]: sessionEnded }"
|
||||
data-test-id="chat-input-wrapper"
|
||||
>
|
||||
|
@ -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 {
|
||||
|
|
|
@ -132,7 +132,7 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
|
|||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span
|
||||
<div
|
||||
class="assistantText"
|
||||
>
|
||||
<p>
|
||||
|
@ -144,9 +144,10 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
|
|||
</p>
|
||||
|
||||
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
@ -449,6 +450,7 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
|
|||
|
||||
</span>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
@ -842,13 +844,10 @@ exports[`AskAssistantChat > renders default placeholder chat correctly 1`] = `
|
|||
class="info"
|
||||
>
|
||||
<p>
|
||||
I'm your Assistant, here to guide you through your journey with n8n.
|
||||
I can answer most questions about building workflows in n8n.
|
||||
</p>
|
||||
<p>
|
||||
While I'm still learning, I'm already equipped to help you debug any errors you might encounter.
|
||||
</p>
|
||||
<p>
|
||||
If you run into an issue with a node, you'll see the
|
||||
For specific tasks, you’ll see the
|
||||
<button
|
||||
class="button"
|
||||
style="height: 18px;"
|
||||
|
@ -901,15 +900,33 @@ exports[`AskAssistantChat > renders default placeholder chat correctly 1`] = `
|
|||
</div>
|
||||
</div>
|
||||
</button>
|
||||
button
|
||||
button in the UI.
|
||||
</p>
|
||||
<p>
|
||||
Clicking it will start a chat with me, and I'll do my best to assist you!
|
||||
How can I help?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--v-if-->
|
||||
<div
|
||||
class="inputWrapper"
|
||||
data-test-id="chat-input-wrapper"
|
||||
>
|
||||
<textarea
|
||||
data-test-id="chat-input"
|
||||
placeholder="Enter your response..."
|
||||
rows="1"
|
||||
wrap="hard"
|
||||
/>
|
||||
<n8n-icon-button
|
||||
class="sendButton"
|
||||
data-test-id="send-message-button"
|
||||
disabled="true"
|
||||
icon="paper-plane"
|
||||
size="large"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -1046,7 +1063,7 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = `
|
|||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span
|
||||
<div
|
||||
class="assistantText"
|
||||
>
|
||||
<p>
|
||||
|
@ -1058,9 +1075,10 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = `
|
|||
</p>
|
||||
|
||||
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
@ -1295,7 +1313,7 @@ exports[`AskAssistantChat > renders streaming chat correctly 1`] = `
|
|||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span
|
||||
<div
|
||||
class="assistantText"
|
||||
>
|
||||
<p>
|
||||
|
@ -1307,9 +1325,10 @@ exports[`AskAssistantChat > renders streaming chat correctly 1`] = `
|
|||
</p>
|
||||
|
||||
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
|
|
@ -39,13 +39,10 @@ export default {
|
|||
'assistantChat.you': 'You',
|
||||
'assistantChat.quickRepliesTitle': 'Quick reply 👇',
|
||||
'assistantChat.placeholder.1': () =>
|
||||
"I'm your Assistant, here to guide you through your journey with n8n.",
|
||||
'assistantChat.placeholder.2':
|
||||
"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!",
|
||||
'I can answer most questions about building workflows in n8n.',
|
||||
'assistantChat.placeholder.2': 'For specific tasks, you’ll see the',
|
||||
'assistantChat.placeholder.3': 'button in the UI.',
|
||||
'assistantChat.placeholder.4': 'How can I help?',
|
||||
'assistantChat.inputPlaceholder': 'Enter your response...',
|
||||
'inlineAskAssistantButton.asked': 'Asked',
|
||||
} as N8nLocale;
|
||||
|
|
|
@ -3,6 +3,7 @@ export namespace ChatUI {
|
|||
role: 'assistant' | 'user';
|
||||
type: 'text';
|
||||
content: string;
|
||||
codeSnippet?: string;
|
||||
}
|
||||
|
||||
export interface SummaryBlock {
|
||||
|
|
|
@ -27,14 +27,16 @@ function onResizeDebounced(data: { direction: string; x: number; width: number }
|
|||
}
|
||||
|
||||
async function onUserMessage(content: string, quickReplyType?: string, isFeedback = false) {
|
||||
// If there is no current session running, initialize the support chat session
|
||||
if (!assistantStore.currentSessionId) {
|
||||
await assistantStore.initSupportChat(content);
|
||||
} else {
|
||||
await assistantStore.sendMessage({ text: content, quickReplyType });
|
||||
const task = 'error';
|
||||
const solutionCount =
|
||||
task === 'error'
|
||||
? assistantStore.chatMessages.filter(
|
||||
}
|
||||
const task = assistantStore.isSupportChatSessionInProgress ? 'support' : 'error';
|
||||
const solutionCount = assistantStore.chatMessages.filter(
|
||||
(msg) => msg.role === 'assistant' && !['text', 'event'].includes(msg.type),
|
||||
).length
|
||||
: null;
|
||||
).length;
|
||||
if (isFeedback) {
|
||||
telemetry.track('User gave feedback', {
|
||||
task,
|
||||
|
|
|
@ -143,7 +143,7 @@ defineExpose({
|
|||
|
||||
<style lang="scss" module>
|
||||
.floatingNodes {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
|
|
@ -79,9 +79,13 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
|
|||
ENABLED_VIEWS.includes(route.name as VIEWS),
|
||||
);
|
||||
|
||||
const assistantMessages = computed(() =>
|
||||
chatMessages.value.filter((msg) => msg.role === 'assistant'),
|
||||
);
|
||||
const usersMessages = computed(() => chatMessages.value.filter((msg) => msg.role === 'user'));
|
||||
|
||||
const isSessionEnded = computed(() => {
|
||||
const assistantMessages = chatMessages.value.filter((msg) => msg.role === 'assistant');
|
||||
const lastAssistantMessage = assistantMessages[assistantMessages.length - 1];
|
||||
const lastAssistantMessage = assistantMessages.value[assistantMessages.value.length - 1];
|
||||
|
||||
const sessionExplicitlyEnded =
|
||||
lastAssistantMessage?.type === 'event' && lastAssistantMessage?.eventName === 'end-session';
|
||||
|
@ -106,6 +110,10 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
|
|||
).length,
|
||||
);
|
||||
|
||||
const isSupportChatSessionInProgress = computed(() => {
|
||||
return currentSessionId.value !== undefined && chatSessionError.value === undefined;
|
||||
});
|
||||
|
||||
watch(route, () => {
|
||||
const activeWorkflowId = workflowsStore.workflowId;
|
||||
if (!currentSessionId.value || currentSessionWorkflowId.value === activeWorkflowId) {
|
||||
|
@ -137,17 +145,21 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
|
|||
// Looks smoother if we wait for slide animation to finish before updating the grid width
|
||||
setTimeout(() => {
|
||||
uiStore.appGridWidth = window.innerWidth;
|
||||
// If session has ended, reset the chat
|
||||
if (isSessionEnded.value) {
|
||||
resetAssistantChat();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function addAssistantMessages(assistantMessages: ChatRequest.MessageResponse[], id: string) {
|
||||
function addAssistantMessages(newMessages: ChatRequest.MessageResponse[], id: string) {
|
||||
const read = chatWindowOpen.value;
|
||||
const messages = [...chatMessages.value].filter(
|
||||
(msg) => !(msg.id === id && msg.role === 'assistant'),
|
||||
);
|
||||
assistantThinkingMessage.value = undefined;
|
||||
// TODO: simplify
|
||||
assistantMessages.forEach((msg) => {
|
||||
newMessages.forEach((msg) => {
|
||||
if (msg.type === 'message') {
|
||||
messages.push({
|
||||
id,
|
||||
|
@ -155,6 +167,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
|
|||
role: 'assistant',
|
||||
content: msg.text,
|
||||
quickReplies: msg.quickReplies,
|
||||
codeSnippet: msg.codeSnippet,
|
||||
read,
|
||||
});
|
||||
} else if (msg.type === 'code-diff') {
|
||||
|
@ -262,10 +275,15 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
|
|||
'Assistant session started',
|
||||
{
|
||||
chat_session_id: currentSessionId.value,
|
||||
task: 'error',
|
||||
task: isSupportChatSessionInProgress.value ? 'support' : 'error',
|
||||
},
|
||||
{ withPostHog: true },
|
||||
);
|
||||
// Track first user message in support chat now that we have a session id
|
||||
if (usersMessages.value.length === 1 && isSupportChatSessionInProgress.value) {
|
||||
const firstUserMessage = usersMessages.value[0] as ChatUI.TextMessage;
|
||||
trackUserMessage(firstUserMessage.content, false);
|
||||
}
|
||||
} else if (currentSessionId.value !== response.sessionId) {
|
||||
return;
|
||||
}
|
||||
|
@ -289,6 +307,33 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
|
|||
}, 4000);
|
||||
}
|
||||
|
||||
async function initSupportChat(userMessage: string) {
|
||||
const id = getRandomId();
|
||||
resetAssistantChat();
|
||||
chatSessionError.value = undefined;
|
||||
currentSessionActiveExecutionId.value = undefined;
|
||||
currentSessionWorkflowId.value = workflowsStore.workflowId;
|
||||
addUserMessage(userMessage, id);
|
||||
addLoadingAssistantMessage(locale.baseText('aiAssistant.thinkingSteps.thinking'));
|
||||
streaming.value = true;
|
||||
chatWithAssistant(
|
||||
rootStore.restApiContext,
|
||||
{
|
||||
payload: {
|
||||
role: 'user',
|
||||
type: 'init-support-chat',
|
||||
user: {
|
||||
firstName: usersStore.currentUser?.firstName ?? '',
|
||||
},
|
||||
question: userMessage,
|
||||
},
|
||||
},
|
||||
(msg) => onEachStreamingMessage(msg, id),
|
||||
() => onDoneStreaming(id),
|
||||
(e) => handleServiceError(e, id),
|
||||
);
|
||||
}
|
||||
|
||||
async function initErrorHelper(context: ChatRequest.ErrorContext) {
|
||||
const id = getRandomId();
|
||||
if (chatSessionError.value) {
|
||||
|
@ -433,18 +478,26 @@ 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,
|
||||
});
|
||||
trackUserMessage(chatMessage.text, !!chatMessage.quickReplyType);
|
||||
} catch (e: unknown) {
|
||||
// in case of assert
|
||||
handleServiceError(e, id);
|
||||
}
|
||||
}
|
||||
|
||||
function trackUserMessage(message: string, isQuickReply: boolean) {
|
||||
if (!currentSessionId.value) {
|
||||
return;
|
||||
}
|
||||
telemetry.track('User sent message in Assistant', {
|
||||
message,
|
||||
is_quick_reply: isQuickReply,
|
||||
chat_session_id: currentSessionId.value,
|
||||
message_number: usersMessages.value.length,
|
||||
task: isSupportChatSessionInProgress.value ? 'support' : 'error',
|
||||
});
|
||||
}
|
||||
|
||||
function updateParameters(nodeName: string, parameters: INodeParameters) {
|
||||
if (ndvStore.activeNodeName === nodeName) {
|
||||
Object.keys(parameters).forEach((key) => {
|
||||
|
@ -584,6 +637,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
|
|||
updateWindowWidth,
|
||||
isNodeErrorActive,
|
||||
initErrorHelper,
|
||||
initSupportChat,
|
||||
sendMessage,
|
||||
applyCodeDiff,
|
||||
undoCodeDiff,
|
||||
|
@ -591,5 +645,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
|
|||
chatWindowOpen,
|
||||
addAssistantMessages,
|
||||
assistantThinkingMessage,
|
||||
chatSessionError,
|
||||
isSupportChatSessionInProgress,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -32,6 +32,15 @@ export namespace ChatRequest {
|
|||
authType?: { name: string; value: string };
|
||||
}
|
||||
|
||||
export interface InitSupportChat {
|
||||
role: 'user';
|
||||
type: 'init-support-chat';
|
||||
user: {
|
||||
firstName: string;
|
||||
};
|
||||
question: string;
|
||||
}
|
||||
|
||||
export type InteractionEventName = 'node-execution-succeeded' | 'node-execution-errored';
|
||||
|
||||
interface EventRequestPayload {
|
||||
|
@ -50,7 +59,7 @@ export namespace ChatRequest {
|
|||
|
||||
export type RequestPayload =
|
||||
| {
|
||||
payload: InitErrorHelper;
|
||||
payload: InitErrorHelper | InitSupportChat;
|
||||
}
|
||||
| {
|
||||
payload: EventRequestPayload | UserChatMessage;
|
||||
|
@ -77,6 +86,7 @@ export namespace ChatRequest {
|
|||
type: 'message';
|
||||
text: string;
|
||||
step?: 'n8n_documentation' | 'n8n_forum';
|
||||
codeSnippet?: string;
|
||||
}
|
||||
|
||||
interface AssistantSummaryMessage {
|
||||
|
|
Loading…
Reference in a new issue