From fe871058e2ee9343f9240a252d7d31d0d804af68 Mon Sep 17 00:00:00 2001 From: Suguru Inoue Date: Wed, 19 Nov 2025 09:57:32 +0100 Subject: [PATCH] fix: Chat UI feedback (no-changelog) (#22010) --- .../src/features/ai/chatHub/ChatView.vue | 9 +- .../src/features/ai/chatHub/chat.store.ts | 25 ++- .../src/features/ai/chatHub/chat.types.ts | 1 + .../src/features/ai/chatHub/chat.utils.ts | 60 +++++++ .../ai/chatHub/components/ChatAgentAvatar.vue | 7 + .../ai/chatHub/components/ChatLayout.vue | 24 ++- .../ai/chatHub/components/ChatMessage.vue | 146 ++++++++++++------ .../chatHub/components/ChatMessageActions.vue | 2 +- .../ai/chatHub/components/ModelSelector.vue | 8 +- .../ai/chatHub/components/ToolsSelector.vue | 16 +- 10 files changed, 228 insertions(+), 70 deletions(-) diff --git a/packages/frontend/editor-ui/src/features/ai/chatHub/ChatView.vue b/packages/frontend/editor-ui/src/features/ai/chatHub/ChatView.vue index 7b36c17c374..e66ba320b5f 100644 --- a/packages/frontend/editor-ui/src/features/ai/chatHub/ChatView.vue +++ b/packages/frontend/editor-ui/src/features/ai/chatHub/ChatView.vue @@ -116,6 +116,7 @@ const defaultTools = useLocalStorage( ); const toolsSelection = ref(null); +const shouldSkipNextScrollTrigger = ref(false); const selectedTools = computed(() => { if (currentConversation.value?.tools) { @@ -228,6 +229,11 @@ watch( return; } + if (shouldSkipNextScrollTrigger.value) { + shouldSkipNextScrollTrigger.value = false; + return; + } + // Prevent "scroll to bottom" button from appearing when not necessary void nextTick(measure); @@ -376,7 +382,7 @@ function handleRegenerateMessage(message: ChatHubMessageDto) { return; } - const messageToRetry = message.retryOfMessageId ?? message.id; + const messageToRetry = message.id; chatStore.regenerateMessage( sessionId.value, @@ -399,6 +405,7 @@ async function handleSelectModel(selection: ChatModelDto) { } function handleSwitchAlternative(messageId: string) { + shouldSkipNextScrollTrigger.value = true; chatStore.switchAlternative(sessionId.value, messageId); } diff --git a/packages/frontend/editor-ui/src/features/ai/chatHub/chat.store.ts b/packages/frontend/editor-ui/src/features/ai/chatHub/chat.store.ts index 1ed659eb5e2..db374ead90b 100644 --- a/packages/frontend/editor-ui/src/features/ai/chatHub/chat.store.ts +++ b/packages/frontend/editor-ui/src/features/ai/chatHub/chat.store.ts @@ -43,7 +43,7 @@ import type { ChatStreamingState, } from './chat.types'; import { retry } from '@n8n/utils/retry'; -import { isMatchedAgent } from './chat.utils'; +import { buildUiMessages, isMatchedAgent } from './chat.utils'; import { createAiMessageFromStreamingState, flattenModel } from './chat.utils'; import { useTelemetry } from '@/app/composables/useTelemetry'; import { type INode } from 'n8n-workflow'; @@ -65,7 +65,7 @@ export const useChatStore = defineStore(CHAT_STORE, () => { const conversation = getConversation(sessionId); if (!conversation) return []; - return conversation.activeMessageChain.map((id) => conversation.messages[id]).filter(Boolean); + return buildUiMessages(sessionId, conversation, streaming.value); }; function ensureConversation(sessionId: ChatSessionId): ChatConversation { @@ -459,7 +459,12 @@ export const useChatStore = defineStore(CHAT_STORE, () => { alternatives: [], }); - streaming.value = { promptId: messageId, sessionId, model }; + streaming.value = { + promptId: messageId, + sessionId, + model, + retryOfMessageId: null, + }; sendMessageApi( rootStore.restApiContext, @@ -522,7 +527,12 @@ export const useChatStore = defineStore(CHAT_STORE, () => { replaceMessageContent(sessionId, editId, content); } - streaming.value = { promptId, sessionId, model }; + streaming.value = { + promptId, + sessionId, + model, + retryOfMessageId: null, + }; editMessageApi( rootStore.restApiContext, @@ -553,7 +563,12 @@ export const useChatStore = defineStore(CHAT_STORE, () => { throw new Error('No previous message to base regeneration on'); } - streaming.value = { promptId: retryId, sessionId, model }; + streaming.value = { + promptId: retryId, + sessionId, + model, + retryOfMessageId: retryId, + }; regenerateMessageApi( rootStore.restApiContext, diff --git a/packages/frontend/editor-ui/src/features/ai/chatHub/chat.types.ts b/packages/frontend/editor-ui/src/features/ai/chatHub/chat.types.ts index 22493a3e629..b3f29fb22db 100644 --- a/packages/frontend/editor-ui/src/features/ai/chatHub/chat.types.ts +++ b/packages/frontend/editor-ui/src/features/ai/chatHub/chat.types.ts @@ -77,6 +77,7 @@ export interface ChatStreamingState extends Partial @@ -30,3 +31,9 @@ defineProps<{ /> + + diff --git a/packages/frontend/editor-ui/src/features/ai/chatHub/components/ChatLayout.vue b/packages/frontend/editor-ui/src/features/ai/chatHub/components/ChatLayout.vue index f6f08c7078c..fc1ce9bba0c 100644 --- a/packages/frontend/editor-ui/src/features/ai/chatHub/components/ChatLayout.vue +++ b/packages/frontend/editor-ui/src/features/ai/chatHub/components/ChatLayout.vue @@ -1,8 +1,28 @@