feat(sidechat): 侧边对话——每对话一 Tab(Codex 对齐)+ 完整上下文继承 + 保存为新会话 (#286)
三轮实测驱动迭代:种子信封透传(surfaceOp 校验)、每线程一 Tab 交互重做完、四连修(fork 丢 this / 自绘 history+save 图标 / collectOwnEvents 大页回源修重开丢工具行 / descriptor 入种子修目录 corrupt 行 + 前缀过滤保拓扑零噪音)。739 单测 + 挂载冒烟 12/12 全绿。
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* The 6 built-in tab descriptors: the plugin registers its own pages
|
||||
* (editor / git / terminal / browser / subagent / diff) through
|
||||
* The 7 built-in tab descriptors: the plugin registers its own pages
|
||||
* (editor / git / subagent / sidechat / terminal / browser / diff) through
|
||||
* the same {@link BetterSidebarService} external plugins use — eating its
|
||||
* own dogfood. The terminal descriptor owns its quota (`TERMINAL_LIMIT`)
|
||||
* and mints `terminal:<uuid>` ids through `createTab`; the browser mints
|
||||
* `browser:<n>` the same way (no quota). The editor IS the files window
|
||||
* (the old standalone explorer merged into it).
|
||||
*/
|
||||
import { IconBranchOutline16, IconCodeOutline16, IconFolderOpen16, IconPanelLeftOutline16, IconThinkOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import { IconBranchOutline16, IconCodeOutline16, IconFolderOpen16, IconNewChatOutline16, IconPanelLeftOutline16, IconThinkOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { Context } from '../../context-types.ts'
|
||||
import { allLeaves, isAgentTabId, type SidebarState } from '../state.ts'
|
||||
import { t } from '../locales.ts'
|
||||
@@ -17,6 +17,8 @@ import { lazyChunkComponent } from '../lazy-chunk.tsx'
|
||||
import { GitView } from '../GitView.tsx'
|
||||
import { DiffTab } from '../DiffTab.tsx'
|
||||
import { SubagentView } from '../SubagentView.tsx'
|
||||
import { consumeSidechatSeed, SideChatView, sidechatThreadIdOf } from '../SideChatView.tsx'
|
||||
import { api } from '../api.ts'
|
||||
import { BrowserView } from '../BrowserView.tsx'
|
||||
import { IconTerminalOutline16, IconDiffOutline16, IconGlobeOutline16 } from '../icons.tsx'
|
||||
import { TERMINAL_FONT_SIZE_MAX, TERMINAL_FONT_SIZE_MIN } from '../../prefs-shared.ts'
|
||||
@@ -165,6 +167,51 @@ export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): read
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'sidechat',
|
||||
title: () => t('sideChat'),
|
||||
icon: (size: number) => <IconNewChatOutline16 size={size} />,
|
||||
order: 35,
|
||||
// Codex-style: EVERY side conversation is its own tab. A plain open
|
||||
// mints a fresh tab flagged `autoCreate` (the view creates the EMPTY
|
||||
// thread on mount); a thread switch from the header menu parks the
|
||||
// target id for a deterministic `sidechat:<threadId>` reattach tab.
|
||||
createTab: () => {
|
||||
const threadId = consumeSidechatSeed()
|
||||
if (threadId !== undefined) {
|
||||
return {
|
||||
tab: {
|
||||
id: `sidechat:${threadId}`,
|
||||
type: 'sidechat',
|
||||
title: t('sideChat'),
|
||||
meta: { threadId },
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
tab: {
|
||||
id: `sidechat:new-${crypto.randomUUID()}`,
|
||||
type: 'sidechat',
|
||||
title: t('sideChatUntitled'),
|
||||
meta: { autoCreate: true },
|
||||
},
|
||||
}
|
||||
},
|
||||
// One tab per thread: an already-open thread focuses instead of
|
||||
// duplicating; unbound fresh tabs never dedupe (each mints its own).
|
||||
dedupeKey: (tab) => sidechatThreadIdOf(tab),
|
||||
// Closing the tab releases the thread's live agent; the session and
|
||||
// its history stay persisted (reopen from any thread's header menu).
|
||||
onClose: (tab) => {
|
||||
const threadId = sidechatThreadIdOf(tab)
|
||||
if (threadId !== undefined) {
|
||||
void api.sidechatDispose(threadId).catch(() => {})
|
||||
}
|
||||
},
|
||||
component: ({ ctx, scope, tab, visible }) => (
|
||||
<SideChatView ctx={ctx} scope={scope} tab={tab} visible={visible} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'terminal',
|
||||
title: () => t('terminal'),
|
||||
|
||||
Reference in New Issue
Block a user