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 @@