feat(editor): Add AI Assistant support chat (#10656)
This commit is contained in:
committed by
GitHub
parent
899b0a19ef
commit
3a8078068e
@@ -27,14 +27,16 @@ function onResizeDebounced(data: { direction: string; x: number; width: number }
|
||||
}
|
||||
|
||||
async function onUserMessage(content: string, quickReplyType?: string, isFeedback = false) {
|
||||
await assistantStore.sendMessage({ text: content, quickReplyType });
|
||||
const task = 'error';
|
||||
const solutionCount =
|
||||
task === 'error'
|
||||
? assistantStore.chatMessages.filter(
|
||||
(msg) => msg.role === 'assistant' && !['text', 'event'].includes(msg.type),
|
||||
).length
|
||||
: null;
|
||||
// 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 = assistantStore.isSupportChatSessionInProgress ? 'support' : 'error';
|
||||
const solutionCount = assistantStore.chatMessages.filter(
|
||||
(msg) => msg.role === 'assistant' && !['text', 'event'].includes(msg.type),
|
||||
).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 {
|
||||
|
||||
Reference in New Issue
Block a user