// 预览区标签的纯操作:打开已有标签只激活,关闭激活标签时选中相邻的一个。 export interface PreviewTab { id: string name: string } export function openPreviewTab(tabs: PreviewTab[], tab: PreviewTab): { tabs: PreviewTab[]; activeId: string } { const i = tabs.findIndex(t => t.id === tab.id) if (i >= 0) { const next = tabs.slice() next[i] = { ...next[i], name: tab.name || next[i].name } return { tabs: next, activeId: tab.id } } return { tabs: [...tabs, tab], activeId: tab.id } } export function closePreviewTab( tabs: PreviewTab[], activeId: string | null, id: string, ): { tabs: PreviewTab[]; activeId: string | null } { const i = tabs.findIndex(t => t.id === id) if (i < 0) return { tabs, activeId } const next = tabs.filter(t => t.id !== id) if (activeId !== id) return { tabs: next, activeId } const neighbor = next[Math.min(i, next.length - 1)] ?? null return { tabs: next, activeId: neighbor?.id ?? null } } /** 预览标签的会话键。空态(未选会话)统一落在 '_':写入(requestPreview)与 * 渲染(previewOpen/PreviewPane)共用同一口径,空态点开工件才能出现预览区。 */ export function previewTabsKey(currentConvId: string | null | undefined): string { return currentConvId ?? '_' } /** 进入会话时把空态打开的孤儿标签('_' 键)并入会话键下;按 id 去重, * 会话侧顺序在前,'_' 键清空。无孤儿或目标即 '_' 时原样返回。 */ export function mergeOrphanPreviewTabs( tabsByConv: Record, convId: string, ): Record { const orphan = tabsByConv['_'] ?? [] if (orphan.length === 0 || convId === '_') return tabsByConv const own = tabsByConv[convId] ?? [] const ids = new Set(own.map(t => t.id)) const merged = [...own, ...orphan.filter(t => !ids.has(t.id))] return { ...tabsByConv, [convId]: merged, '_': [] } }