From 41a35cee2cb883296fd58d6dc8d13613ad00a738 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Thu, 23 Apr 2026 17:41:05 +0800 Subject: [PATCH] feat(chat): reasoning activity panel - Introduced a new ReasoningSidebar component for displaying reasoning details. - Refactored MessageList and StandaloneChat components to utilize renderBlocks for improved message part handling. - Added ReasoningTimeline component to visualize reasoning steps. - Updated message handling logic to differentiate between thinking and content blocks. - Enhanced localization for reasoning-related terms in English, Russian, and Chinese. - Improved styling for various components to ensure consistency and readability. --- dashboard/src/components/chat/Chat.vue | 63 +++++ .../src/components/chat/ChatMessageList.vue | 252 +++++++++-------- dashboard/src/components/chat/MessageList.vue | 242 ++++++++-------- .../src/components/chat/ReasoningSidebar.vue | 119 ++++++++ .../src/components/chat/StandaloneChat.vue | 192 +++++++------ .../message_list_comps/IPythonToolBlock.vue | 8 +- .../message_list_comps/ReasoningBlock.vue | 170 ++++------- .../message_list_comps/ReasoningTimeline.vue | 265 ++++++++++++++++++ .../chat/message_list_comps/ToolCallItem.vue | 5 +- dashboard/src/composables/useMessages.ts | 70 +++-- .../src/i18n/locales/en-US/features/chat.json | 4 +- .../src/i18n/locales/ru-RU/features/chat.json | 4 +- .../src/i18n/locales/zh-CN/features/chat.json | 4 +- 13 files changed, 945 insertions(+), 453 deletions(-) create mode 100644 dashboard/src/components/chat/ReasoningSidebar.vue create mode 100644 dashboard/src/components/chat/message_list_comps/ReasoningTimeline.vue diff --git a/dashboard/src/components/chat/Chat.vue b/dashboard/src/components/chat/Chat.vue index dba47fa05..1e465887d 100644 --- a/dashboard/src/components/chat/Chat.vue +++ b/dashboard/src/components/chat/Chat.vue @@ -368,6 +368,7 @@ @regenerate-with-model="handleRegenerateMessage" @select-bot-text="handleBotTextSelection" @open-thread="openThreadPanel" + @open-reasoning="openReasoningPanel" @open-refs="openRefsSidebar" /> @@ -467,6 +468,11 @@ :deleting="deletingThread" @delete="deleteThread" /> + @@ -495,10 +501,12 @@ import ProjectView from "@/components/chat/ProjectView.vue"; import ChatInput from "@/components/chat/ChatInput.vue"; import ChatMessageList from "@/components/chat/ChatMessageList.vue"; import type { RegenerateModelSelection } from "@/components/chat/RegenerateMenu.vue"; +import ReasoningSidebar from "@/components/chat/ReasoningSidebar.vue"; import ThreadPanel from "@/components/chat/ThreadPanel.vue"; import RefsSidebar from "@/components/chat/message_list_comps/RefsSidebar.vue"; import { useSessions, type Session } from "@/composables/useSessions"; import { + messageBlocks as buildMessageBlocks, useMessages, type ChatRecord, type ChatThread, @@ -587,6 +595,11 @@ const shouldStickToBottom = ref(true); const replyTarget = ref(null); const threadPanelOpen = ref(false); const activeThread = ref(null); +const reasoningPanelOpen = ref(false); +const activeReasoningTarget = ref<{ + message: ChatRecord; + blockIndex: number; +} | null>(null); const deletingThread = ref(false); const refsSidebarOpen = ref(false); const selectedRefs = ref | null>(null); @@ -617,6 +630,20 @@ const chatSidebarDrawer = computed({ const isSidebarCollapsed = computed(() => lgAndUp.value ? sidebarCollapsed.value : !customizer.chatSidebarOpen, ); +const activeReasoningParts = computed(() => { + if (!activeReasoningTarget.value) return []; + const blocks = buildMessageBlocks( + activeReasoningTarget.value.message.content || { type: "bot", message: [] }, + ); + const block = blocks[activeReasoningTarget.value.blockIndex]; + return block?.kind === "thinking" ? block.parts : []; +}); + +watch(reasoningPanelOpen, (open) => { + if (!open) { + activeReasoningTarget.value = null; + } +}); const { loadingMessages, @@ -1146,16 +1173,35 @@ async function createThreadFromSelection() { } function openThreadPanel(thread: ChatThread) { + reasoningPanelOpen.value = false; + activeReasoningTarget.value = null; + refsSidebarOpen.value = false; activeThread.value = thread; threadPanelOpen.value = true; } function openRefsSidebar(refs: unknown) { + threadPanelOpen.value = false; + activeThread.value = null; + reasoningPanelOpen.value = false; + activeReasoningTarget.value = null; selectedRefs.value = refs && typeof refs === "object" ? (refs as Record) : null; refsSidebarOpen.value = true; } +function openReasoningPanel(payload: { + message: ChatRecord; + blockIndex: number; +}) { + threadPanelOpen.value = false; + activeThread.value = null; + refsSidebarOpen.value = false; + selectedRefs.value = null; + activeReasoningTarget.value = payload; + reasoningPanelOpen.value = true; +} + async function deleteThread(thread: ChatThread) { if (deletingThread.value) return; if (!(await askForConfirmation(tm("thread.confirmDelete"), confirmDialog))) return; @@ -1584,6 +1630,23 @@ kbd { font: inherit; } +:deep(.hr-node) { + margin-top: 1.25rem; + margin-bottom: 1.25rem; + opacity: 0.5; + border-top-width: .3px; +} + +:deep(.paragraph-node) { + margin: .5rem 0; + line-height: 1.7; +} + +:deep(.list-node) { + margin-top: .5rem; + margin-bottom: .5rem; +} + @media (max-width: 760px) { .messages-panel { padding: 18px 14px; diff --git a/dashboard/src/components/chat/ChatMessageList.vue b/dashboard/src/components/chat/ChatMessageList.vue index 6905ec5c8..dd940b248 100644 --- a/dashboard/src/components/chat/ChatMessageList.vue +++ b/dashboard/src/components/chat/ChatMessageList.vue @@ -119,127 +119,141 @@