From 49196ffd4da76e577ddcaa4d0aa17dd3c29c426c Mon Sep 17 00:00:00 2001 From: xintaofei Date: Wed, 11 Mar 2026 08:14:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E6=94=AF=E6=8C=81=E5=B9=B3?= =?UTF-8?q?=E9=93=BA=E6=A8=A1=E5=BC=8F=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../conversation-detail-panel.tsx | 109 ++++++++++++++---- src/components/tabs/tab-bar.tsx | 4 + src/components/tabs/tab-item.tsx | 8 ++ src/contexts/tab-context.tsx | 14 +++ src/i18n/messages/ar.json | 4 +- src/i18n/messages/de.json | 4 +- src/i18n/messages/en.json | 4 +- src/i18n/messages/es.json | 4 +- src/i18n/messages/fr.json | 4 +- src/i18n/messages/ja.json | 4 +- src/i18n/messages/ko.json | 4 +- src/i18n/messages/pt.json | 4 +- src/i18n/messages/zh-CN.json | 4 +- src/i18n/messages/zh-TW.json | 4 +- 14 files changed, 140 insertions(+), 35 deletions(-) diff --git a/src/components/conversations/conversation-detail-panel.tsx b/src/components/conversations/conversation-detail-panel.tsx index 359d42e..20a6fc5 100644 --- a/src/components/conversations/conversation-detail-panel.tsx +++ b/src/components/conversations/conversation-detail-panel.tsx @@ -1,12 +1,21 @@ "use client" -import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react" +import { + memo, + Fragment, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react" import { Plus, RefreshCw, X } from "lucide-react" import { useTranslations } from "next-intl" import { toast } from "sonner" import { disposeTauriListener } from "@/lib/tauri-listener" import { useFolderContext } from "@/contexts/folder-context" import { useTabContext } from "@/contexts/tab-context" +import { cn } from "@/lib/utils" import { useConnectionLifecycle } from "@/hooks/use-connection-lifecycle" import { MessageListView } from "@/components/message/message-list-view" import { ConversationShell } from "@/components/chat/conversation-shell" @@ -48,6 +57,11 @@ import { ContextMenuSeparator, ContextMenuTrigger, } from "@/components/ui/context-menu" +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from "@/components/ui/resizable" interface ConversationTabViewProps { tabId: string @@ -677,8 +691,14 @@ export function ConversationDetailPanel() { } = useConversationRuntime() const { folder, newConversation, conversations, refreshConversations } = useFolderContext() - const { tabs, activeTabId, openNewConversationTab, closeTab } = - useTabContext() + const { + tabs, + activeTabId, + isTileMode, + openNewConversationTab, + closeTab, + switchTab, + } = useTabContext() const [reloadByTabId, setReloadByTabId] = useState>({}) const tabsRef = useRef(tabs) const conversationsRef = useRef(conversations) @@ -859,6 +879,8 @@ export function ConversationDetailPanel() { openNewConversationTab, ]) + const canTile = isTileMode && tabs.length > 1 + // Empty state: no tabs at all — show full-screen welcome if (hasNoTabs) { return null @@ -868,28 +890,65 @@ export function ConversationDetailPanel() {
- {tabs.map((tab) => { - const active = tab.id === activeTabId - return ( -
- -
- ) - })} + {canTile ? ( + + {tabs.map((tab, index) => { + const active = tab.id === activeTabId + return ( + + {index > 0 && } + +
{ + if (!active) switchTab(tab.id) + }} + > + +
+
+
+ ) + })} +
+ ) : ( + tabs.map((tab) => { + const active = tab.id === activeTabId + return ( +
+ +
+ ) + }) + )}
diff --git a/src/components/tabs/tab-bar.tsx b/src/components/tabs/tab-bar.tsx index 0aa97b8..41b0f27 100644 --- a/src/components/tabs/tab-bar.tsx +++ b/src/components/tabs/tab-bar.tsx @@ -13,11 +13,13 @@ export function TabBar() { const { tabs, activeTabId, + isTileMode, switchTab, closeTab, closeOtherTabs, closeAllTabs, pinTab, + toggleTileMode, reorderTabs, } = useTabContext() const { mode, activePane } = useWorkspaceContext() @@ -89,11 +91,13 @@ export function TabBar() { key={tab.id} tab={tab} isActive={tab.id === activeTabId} + isTileMode={isTileMode} onSwitch={switchTab} onClose={closeTab} onCloseOthers={closeOtherTabs} onCloseAll={closeAllTabs} onPin={pinTab} + onToggleTile={toggleTileMode} /> ))} diff --git a/src/components/tabs/tab-item.tsx b/src/components/tabs/tab-item.tsx index a7ce0b0..fabdb25 100644 --- a/src/components/tabs/tab-item.tsx +++ b/src/components/tabs/tab-item.tsx @@ -19,21 +19,25 @@ import type { TabItem as TabItemData } from "@/contexts/tab-context" interface TabItemProps { tab: TabItemData isActive: boolean + isTileMode: boolean onSwitch: (tabId: string) => void onClose: (tabId: string) => void onCloseOthers: (tabId: string) => void onCloseAll: () => void onPin: (tabId: string) => void + onToggleTile: () => void } export const TabItem = memo(function TabItem({ tab, isActive, + isTileMode, onSwitch, onClose, onCloseOthers, onCloseAll, onPin, + onToggleTile, }: TabItemProps) { const t = useTranslations("Folder.tabs") const isDragging = useRef(false) @@ -143,6 +147,10 @@ export const TabItem = memo(function TabItem({ {t("closeOthers")} + + {isTileMode ? t("untileDisplay") : t("tileDisplay")} + + {t("closeAll")} diff --git a/src/contexts/tab-context.tsx b/src/contexts/tab-context.tsx index ae1b4d2..12e335e 100644 --- a/src/contexts/tab-context.tsx +++ b/src/contexts/tab-context.tsx @@ -36,6 +36,7 @@ export type TabItem = TabItemInternal interface TabContextValue { tabs: TabItem[] activeTabId: string | null + isTileMode: boolean openTab: ( conversationId: number, agentType: AgentType, @@ -48,6 +49,7 @@ interface TabContextValue { closeAllTabs: () => void switchTab: (tabId: string) => void pinTab: (tabId: string) => void + toggleTileMode: () => void openNewConversationTab: (agentType: AgentType, workingDir: string) => void bindConversationTab: ( tabId: string, @@ -394,6 +396,8 @@ export function TabProvider({ children }: TabProviderProps) { [folder?.path, t] ) + const [isTileMode, setIsTileMode] = useState(false) + const closeTab = useCallback( (tabId: string) => { let neighborToSync: TabItemInternal | undefined @@ -450,6 +454,7 @@ export function TabProvider({ children }: TabProviderProps) { const kept = prev.filter((t) => t.id === tabId) return kept.length === prev.length ? prev : kept }) + setIsTileMode(false) const tab = rawTabsRef.current.find((t) => t.id === tabId) if (tab) { @@ -470,6 +475,7 @@ export function TabProvider({ children }: TabProviderProps) { const replacementTab = makeReplacementDraftTab(seedTab) setTabs([replacementTab]) + setIsTileMode(false) setActiveTabId(replacementTab.id) syncFolderContext(replacementTab) activateConversationPane() @@ -493,6 +499,10 @@ export function TabProvider({ children }: TabProviderProps) { ) }, []) + const toggleTileMode = useCallback(() => { + setIsTileMode((prev) => !prev) + }, []) + const reorderTabs = useCallback( (reorderedTabs: TabItem[]) => setTabs(reorderedTabs), [] @@ -575,6 +585,7 @@ export function TabProvider({ children }: TabProviderProps) { () => ({ tabs, activeTabId, + isTileMode, openTab, closeTab, closeConversationTab, @@ -582,6 +593,7 @@ export function TabProvider({ children }: TabProviderProps) { closeAllTabs, switchTab, pinTab, + toggleTileMode, openNewConversationTab, bindConversationTab, reorderTabs, @@ -589,6 +601,7 @@ export function TabProvider({ children }: TabProviderProps) { [ tabs, activeTabId, + isTileMode, openTab, closeTab, closeConversationTab, @@ -596,6 +609,7 @@ export function TabProvider({ children }: TabProviderProps) { closeAllTabs, switchTab, pinTab, + toggleTileMode, openNewConversationTab, bindConversationTab, reorderTabs, diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 1a2d3bb..3ead831 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -680,7 +680,9 @@ "closeConversationTab": "إغلاق تبويب المحادثة", "close": "إغلاق", "closeOthers": "إغلاق البقية", - "closeAll": "إغلاق الكل" + "closeAll": "إغلاق الكل", + "tileDisplay": "عرض متجانب", + "untileDisplay": "إلغاء التجانب" }, "fileWorkspace": { "files": "الملفات", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 8a753cc..c4be6a4 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -680,7 +680,9 @@ "closeConversationTab": "Konversationstab schließen", "close": "Schließen", "closeOthers": "Andere schließen", - "closeAll": "Alle schließen" + "closeAll": "Alle schließen", + "tileDisplay": "Kachelansicht", + "untileDisplay": "Kachel beenden" }, "fileWorkspace": { "files": "Dateien", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index bab22c9..522d506 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -680,7 +680,9 @@ "closeConversationTab": "Close conversation tab", "close": "Close", "closeOthers": "Close Others", - "closeAll": "Close All" + "closeAll": "Close All", + "tileDisplay": "Tile Display", + "untileDisplay": "Exit Tile" }, "fileWorkspace": { "files": "Files", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 452002a..4051655 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -680,7 +680,9 @@ "closeConversationTab": "Cerrar pestaña de conversación", "close": "Cerrar", "closeOthers": "Cerrar otros", - "closeAll": "Cerrar todo" + "closeAll": "Cerrar todo", + "tileDisplay": "Vista en mosaico", + "untileDisplay": "Salir de mosaico" }, "fileWorkspace": { "files": "Archivos", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index cbac913..7821cf1 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -680,7 +680,9 @@ "closeConversationTab": "Fermer l’onglet de conversation", "close": "Fermer", "closeOthers": "Fermer les autres", - "closeAll": "Tout fermer" + "closeAll": "Tout fermer", + "tileDisplay": "Affichage en mosaïque", + "untileDisplay": "Quitter la mosaïque" }, "fileWorkspace": { "files": "Fichiers", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 4d9456c..e1caf1f 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -680,7 +680,9 @@ "closeConversationTab": "会話タブを閉じる", "close": "閉じる", "closeOthers": "他を閉じる", - "closeAll": "すべて閉じる" + "closeAll": "すべて閉じる", + "tileDisplay": "タイル表示", + "untileDisplay": "タイル解除" }, "fileWorkspace": { "files": "ファイル", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 88216e5..e4126fa 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -680,7 +680,9 @@ "closeConversationTab": "대화 탭 닫기", "close": "닫기", "closeOthers": "다른 항목 닫기", - "closeAll": "모두 닫기" + "closeAll": "모두 닫기", + "tileDisplay": "타일 표시", + "untileDisplay": "타일 해제" }, "fileWorkspace": { "files": "파일", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index e84e395..a102831 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -680,7 +680,9 @@ "closeConversationTab": "Fechar aba de conversa", "close": "Fechar", "closeOthers": "Fechar outros", - "closeAll": "Fechar tudo" + "closeAll": "Fechar tudo", + "tileDisplay": "Exibição em mosaico", + "untileDisplay": "Sair do mosaico" }, "fileWorkspace": { "files": "Arquivos", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index f20fa13..4ed763a 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -680,7 +680,9 @@ "closeConversationTab": "关闭会话标签", "close": "关闭", "closeOthers": "关闭其它", - "closeAll": "关闭所有" + "closeAll": "关闭所有", + "tileDisplay": "平铺显示", + "untileDisplay": "取消平铺" }, "fileWorkspace": { "files": "文件", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index de3c915..a9b9e47 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -680,7 +680,9 @@ "closeConversationTab": "關閉會話分頁", "close": "關閉", "closeOthers": "關閉其它", - "closeAll": "關閉所有" + "closeAll": "關閉所有", + "tileDisplay": "平鋪顯示", + "untileDisplay": "取消平鋪" }, "fileWorkspace": { "files": "檔案",