From e439123a616071b1cb710c5d3085ce00c8f07219 Mon Sep 17 00:00:00 2001 From: Dorian Date: Tue, 3 Mar 2026 23:06:56 +0000 Subject: [PATCH] feat(chat): add conversation branching UI (M8.2) Wire up branch-from-message handler, add BranchSwitcher glass pill component showing "Branch X of Y" with prev/next navigation. Clean up isDark conditionals in chat components (dark-only app). Co-Authored-By: Claude Opus 4.6 --- .../src/components/chat/BranchSwitcher.vue | 55 ++++++++++++++++++ .../app/src/components/chat/ChatHeader.vue | 57 +++++-------------- .../app/src/components/chat/ChatMessage.vue | 12 +++- .../app/src/components/chat/ChatWindow.vue | 16 ++++-- 4 files changed, 91 insertions(+), 49 deletions(-) create mode 100644 packages/app/src/components/chat/BranchSwitcher.vue diff --git a/packages/app/src/components/chat/BranchSwitcher.vue b/packages/app/src/components/chat/BranchSwitcher.vue new file mode 100644 index 00000000..459d662d --- /dev/null +++ b/packages/app/src/components/chat/BranchSwitcher.vue @@ -0,0 +1,55 @@ + + + diff --git a/packages/app/src/components/chat/ChatHeader.vue b/packages/app/src/components/chat/ChatHeader.vue index eecdfdc8..fd333cf8 100644 --- a/packages/app/src/components/chat/ChatHeader.vue +++ b/packages/app/src/components/chat/ChatHeader.vue @@ -6,10 +6,7 @@
@@ -107,17 +91,14 @@ :style="chatListDropdownStyle" @click.stop > -

Saved chats

+

Saved chats

+
() -const { isDark } = useTheme() - // Editing state const isEditing = ref(false) const editContent = ref('') diff --git a/packages/app/src/components/chat/ChatWindow.vue b/packages/app/src/components/chat/ChatWindow.vue index 4f7dd04d..e946ffb8 100644 --- a/packages/app/src/components/chat/ChatWindow.vue +++ b/packages/app/src/components/chat/ChatWindow.vue @@ -10,6 +10,8 @@ @close="$emit('close')" /> + +
- +
-

+

Start a conversation

@@ -59,6 +61,7 @@ :triggering-query="getTriggeringQuery(messages, virtualRow.index)" @edit="handleEdit" @regenerate="handleRegenerate" + @branch="handleBranch" />
@@ -85,13 +88,13 @@ import { computed, ref, watch, nextTick } from 'vue' import { useVirtualizer } from '@tanstack/vue-virtual' import { useChatStore } from '@/stores/chat' import { useAI } from '@/composables/useAI' -import { useTheme } from '@/composables/useTheme' import { useContentPanel } from '@/composables/useContentPanel' import ChatHeader from './ChatHeader.vue' import ChatMessage from './ChatMessage.vue' import ChatInput from './ChatInput.vue' import StreamingDots from './StreamingDots.vue' import PromptIndex from './PromptIndex.vue' +import BranchSwitcher from './BranchSwitcher.vue' import ErrorBoundary from '@/components/ui/ErrorBoundary.vue' import type { Message } from '@aiui/core/types/message' @@ -115,7 +118,6 @@ defineEmits<{ const chatStore = useChatStore() const { sendMessage, stopGeneration, editAndResend, regenerateLastResponse } = useAI() -const { isDark } = useTheme() const { updatePanelFromText, panelOpen, activeTab, availableTabs, setActiveTab } = useContentPanel() import { useCodeContext } from '@/composables/useCodeContext' const codeContext = useCodeContext() @@ -173,6 +175,12 @@ async function handleRegenerate() { await regenerateLastResponse() } +function handleBranch(messageId: string) { + const convId = chatStore.activeConversationId + if (!convId) return + chatStore.branchFromMessage(convId, messageId) +} + function handleExtract(text: string) { // Run content extraction without sending to AI const convId = chatStore.activeConversationId ?? chatStore.createConversation()