@keith9681/dsh-sidebar 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/README_EN.md +0 -0
- package/cordis.patch.yml +49 -0
- package/lib/client-editor.js +34651 -0
- package/lib/client-mermaid.js +200629 -0
- package/lib/client-registry.js +13388 -0
- package/lib/client-terminal.js +8904 -0
- package/lib/client.js +13388 -0
- package/lib/index.js +3959 -0
- package/lib/invariant.js +22 -0
- package/lib/types/agent-pty.d.ts +226 -0
- package/lib/types/browser-probe.d.ts +15 -0
- package/lib/types/bundle-route.d.ts +12 -0
- package/lib/types/client/BrowserView.d.ts +22 -0
- package/lib/types/client/DiffTab.d.ts +6 -0
- package/lib/types/client/DiffView.d.ts +51 -0
- package/lib/types/client/EditorHost.d.ts +12 -0
- package/lib/types/client/FileTree.d.ts +36 -0
- package/lib/types/client/GitView.d.ts +8 -0
- package/lib/types/client/OrphanedTab.d.ts +11 -0
- package/lib/types/client/PdfView.d.ts +6 -0
- package/lib/types/client/RenderBoundary.d.ts +32 -0
- package/lib/types/client/SandboxStatusBar.d.ts +10 -0
- package/lib/types/client/SideCardSection.d.ts +74 -0
- package/lib/types/client/SideChatView.d.ts +16 -0
- package/lib/types/client/Sidebar.d.ts +6 -0
- package/lib/types/client/SubagentView.d.ts +16 -0
- package/lib/types/client/TabBar.d.ts +43 -0
- package/lib/types/client/TerminalView.d.ts +24 -0
- package/lib/types/client/TextEditor.d.ts +10 -0
- package/lib/types/client/TreePanel.d.ts +23 -0
- package/lib/types/client/UploadOverlay.d.ts +20 -0
- package/lib/types/client/add-plugin-modal.d.ts +15 -0
- package/lib/types/client/api.d.ts +268 -0
- package/lib/types/client/binary-download.d.ts +12 -0
- package/lib/types/client/breakpoints.d.ts +12 -0
- package/lib/types/client/browser.d.ts +50 -0
- package/lib/types/client/builtins/index.d.ts +18 -0
- package/lib/types/client/builtins/tabs.d.ts +11 -0
- package/lib/types/client/builtins/viewers.d.ts +3 -0
- package/lib/types/client/chunk-loader.d.ts +124 -0
- package/lib/types/client/chunks/editor.d.ts +9 -0
- package/lib/types/client/chunks/mermaid.d.ts +10 -0
- package/lib/types/client/chunks/terminal.d.ts +9 -0
- package/lib/types/client/cm-themes.d.ts +24 -0
- package/lib/types/client/conversation-draft.d.ts +14 -0
- package/lib/types/client/desktop-env.d.ts +35 -0
- package/lib/types/client/editable-state.d.ts +27 -0
- package/lib/types/client/editor-load.d.ts +66 -0
- package/lib/types/client/frame-batcher.d.ts +24 -0
- package/lib/types/client/icons.d.ts +57 -0
- package/lib/types/client/image-types.d.ts +3 -0
- package/lib/types/client/ime-guard.d.ts +36 -0
- package/lib/types/client/index.d.ts +19 -0
- package/lib/types/client/intercept.d.ts +33 -0
- package/lib/types/client/lang.d.ts +12 -0
- package/lib/types/client/lazy-chunk.d.ts +25 -0
- package/lib/types/client/link-intercept.d.ts +42 -0
- package/lib/types/client/locales.d.ts +366 -0
- package/lib/types/client/mermaid-blocks.d.ts +41 -0
- package/lib/types/client/mermaid-sanitize.d.ts +2 -0
- package/lib/types/client/mermaid.d.ts +12 -0
- package/lib/types/client/open-when-sized.d.ts +20 -0
- package/lib/types/client/open-with-settings.d.ts +4 -0
- package/lib/types/client/open-with.d.ts +87 -0
- package/lib/types/client/openpath-intercept.d.ts +41 -0
- package/lib/types/client/paths.d.ts +35 -0
- package/lib/types/client/pdf-types.d.ts +2 -0
- package/lib/types/client/plugin-settings.d.ts +8 -0
- package/lib/types/client/plugins-shared.d.ts +27 -0
- package/lib/types/client/plugins-tabs.d.ts +3 -0
- package/lib/types/client/plugins-viewers.d.ts +3 -0
- package/lib/types/client/prefs.d.ts +41 -0
- package/lib/types/client/produced-files.d.ts +23 -0
- package/lib/types/client/selection-payload.d.ts +27 -0
- package/lib/types/client/service.d.ts +440 -0
- package/lib/types/client/settings-nav-icon.d.ts +19 -0
- package/lib/types/client/shell-presets.d.ts +48 -0
- package/lib/types/client/sidechat-transcript.d.ts +103 -0
- package/lib/types/client/split-pane.d.ts +30 -0
- package/lib/types/client/state.d.ts +359 -0
- package/lib/types/client/subagent-detect.d.ts +54 -0
- package/lib/types/client/subagent-jobs.d.ts +63 -0
- package/lib/types/client/tab-content-memo.d.ts +33 -0
- package/lib/types/client/terminal-font.d.ts +20 -0
- package/lib/types/client/theme.d.ts +45 -0
- package/lib/types/client/titlebar-strip.d.ts +23 -0
- package/lib/types/client/upload.d.ts +69 -0
- package/lib/types/client/wco.d.ts +47 -0
- package/lib/types/config.d.ts +64 -0
- package/lib/types/context-types.d.ts +603 -0
- package/lib/types/fs-operations.d.ts +28 -0
- package/lib/types/fs-search.d.ts +24 -0
- package/lib/types/fs-tree.d.ts +51 -0
- package/lib/types/git.d.ts +82 -0
- package/lib/types/html-route.d.ts +59 -0
- package/lib/types/index.d.ts +47 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/jobs-routes.d.ts +45 -0
- package/lib/types/open-external.d.ts +24 -0
- package/lib/types/prefs-shared.d.ts +225 -0
- package/lib/types/pty-deps.d.ts +78 -0
- package/lib/types/pty-manager.d.ts +168 -0
- package/lib/types/sidechat-core.d.ts +172 -0
- package/lib/types/sidechat-routes.d.ts +30 -0
- package/lib/types/subagent-activity.d.ts +44 -0
- package/lib/types/subagent-live-route.d.ts +44 -0
- package/lib/types/tools.d.ts +31 -0
- package/lib/types/trust-fence.d.ts +24 -0
- package/lib/types/wire.d.ts +38 -0
- package/package.json +173 -0
- package/scripts/install.ps1 +289 -0
- package/scripts/install.sh +287 -0
- package/src/agent-pty.ts +527 -0
- package/src/browser-probe.ts +26 -0
- package/src/bundle-route.ts +129 -0
- package/src/client/BrowserView.tsx +253 -0
- package/src/client/DiffTab.tsx +110 -0
- package/src/client/DiffView.tsx +305 -0
- package/src/client/EditorHost.tsx +525 -0
- package/src/client/FileTree.tsx +653 -0
- package/src/client/GitView.tsx +558 -0
- package/src/client/OrphanedTab.tsx +27 -0
- package/src/client/PdfView.tsx +110 -0
- package/src/client/RenderBoundary.tsx +49 -0
- package/src/client/SandboxStatusBar.tsx +60 -0
- package/src/client/SideCardSection.module.css +960 -0
- package/src/client/SideCardSection.tsx +1152 -0
- package/src/client/SideChatView.module.css +437 -0
- package/src/client/SideChatView.tsx +644 -0
- package/src/client/Sidebar.tsx +1148 -0
- package/src/client/SubagentView.module.css +547 -0
- package/src/client/SubagentView.tsx +883 -0
- package/src/client/TabBar.tsx +305 -0
- package/src/client/TerminalView.tsx +383 -0
- package/src/client/TextEditor.tsx +500 -0
- package/src/client/TreePanel.tsx +273 -0
- package/src/client/UploadOverlay.tsx +62 -0
- package/src/client/add-plugin-modal.tsx +199 -0
- package/src/client/api.ts +335 -0
- package/src/client/binary-download.tsx +23 -0
- package/src/client/breakpoints.ts +51 -0
- package/src/client/browser.ts +119 -0
- package/src/client/builtins/index.ts +37 -0
- package/src/client/builtins/tabs.tsx +331 -0
- package/src/client/builtins/viewers.tsx +125 -0
- package/src/client/chunk-loader.ts +351 -0
- package/src/client/chunks/editor.tsx +9 -0
- package/src/client/chunks/mermaid.tsx +10 -0
- package/src/client/chunks/terminal.tsx +9 -0
- package/src/client/cm-themes.ts +129 -0
- package/src/client/conversation-draft.ts +29 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/desktop-env.ts +72 -0
- package/src/client/editable-state.ts +70 -0
- package/src/client/editor-load.ts +92 -0
- package/src/client/frame-batcher.ts +56 -0
- package/src/client/icons.tsx +161 -0
- package/src/client/image-types.ts +8 -0
- package/src/client/ime-guard.ts +47 -0
- package/src/client/index.tsx +369 -0
- package/src/client/intercept.tsx +110 -0
- package/src/client/lang.ts +103 -0
- package/src/client/layout.css +94 -0
- package/src/client/lazy-chunk.tsx +89 -0
- package/src/client/link-intercept.ts +73 -0
- package/src/client/locales.ts +734 -0
- package/src/client/mermaid-blocks.ts +110 -0
- package/src/client/mermaid-sanitize.ts +93 -0
- package/src/client/mermaid.tsx +401 -0
- package/src/client/open-when-sized.ts +43 -0
- package/src/client/open-with-settings.tsx +135 -0
- package/src/client/open-with.ts +221 -0
- package/src/client/openpath-intercept.ts +62 -0
- package/src/client/paths.ts +47 -0
- package/src/client/pdf-types.ts +4 -0
- package/src/client/plugin-settings.ts +43 -0
- package/src/client/plugins-shared.ts +29 -0
- package/src/client/plugins-tabs.ts +87 -0
- package/src/client/plugins-viewers.ts +30 -0
- package/src/client/prefs.ts +207 -0
- package/src/client/produced-files.ts +91 -0
- package/src/client/selection-payload.ts +86 -0
- package/src/client/service.ts +838 -0
- package/src/client/settings-nav-icon.ts +45 -0
- package/src/client/shell-presets.ts +83 -0
- package/src/client/sidebar.module.css +2577 -0
- package/src/client/sidechat-transcript.ts +321 -0
- package/src/client/split-pane.tsx +322 -0
- package/src/client/state.ts +1175 -0
- package/src/client/subagent-detect.ts +137 -0
- package/src/client/subagent-jobs.ts +161 -0
- package/src/client/tab-content-memo.ts +46 -0
- package/src/client/terminal-font.ts +28 -0
- package/src/client/theme.ts +110 -0
- package/src/client/titlebar-strip.ts +37 -0
- package/src/client/upload.ts +187 -0
- package/src/client/wco.ts +120 -0
- package/src/config.ts +155 -0
- package/src/context-types.ts +610 -0
- package/src/fs-operations.ts +99 -0
- package/src/fs-search.ts +86 -0
- package/src/fs-tree.ts +158 -0
- package/src/git.ts +244 -0
- package/src/html-route.ts +106 -0
- package/src/index.ts +1132 -0
- package/src/invariant.ts +32 -0
- package/src/jobs-routes.ts +257 -0
- package/src/open-external.ts +90 -0
- package/src/prefs-shared.ts +268 -0
- package/src/pty-deps.ts +240 -0
- package/src/pty-manager.ts +376 -0
- package/src/sidechat-core.ts +468 -0
- package/src/sidechat-routes.ts +344 -0
- package/src/subagent-activity.ts +91 -0
- package/src/subagent-live-route.ts +93 -0
- package/src/tools.ts +479 -0
- package/src/trust-fence.ts +77 -0
- package/src/wire.ts +100 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 7 built-in tab descriptors: the plugin registers its own pages
|
|
3
|
+
* (editor / git / subagent / sidechat / terminal / browser / diff) through
|
|
4
|
+
* the same {@link BetterSidebarService} external plugins use — eating its
|
|
5
|
+
* own dogfood. The terminal descriptor owns its quota (`TERMINAL_LIMIT`)
|
|
6
|
+
* and mints `terminal:<uuid>` ids through `createTab`; the browser mints
|
|
7
|
+
* `browser:<n>` the same way (no quota). The editor IS the files window
|
|
8
|
+
* (the old standalone explorer merged into it).
|
|
9
|
+
*/
|
|
10
|
+
import { IconBranchOutline16, IconCodeOutline16, IconFolderOpen16, IconNewChatOutline16, IconPanelLeftOutline16, IconThinkOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
11
|
+
import type { Context } from '../../context-types.ts'
|
|
12
|
+
import { allLeaves, isAgentTabId, type SidebarState } from '../state.ts'
|
|
13
|
+
import { t } from '../locales.ts'
|
|
14
|
+
import { openSidebarFile } from '../intercept.tsx'
|
|
15
|
+
import { EditorHost } from '../EditorHost.tsx'
|
|
16
|
+
import { OpenWithSettings } from '../open-with-settings.tsx'
|
|
17
|
+
import { lazyChunkComponent } from '../lazy-chunk.tsx'
|
|
18
|
+
import { GitView } from '../GitView.tsx'
|
|
19
|
+
import { DiffTab } from '../DiffTab.tsx'
|
|
20
|
+
import { SubagentView } from '../SubagentView.tsx'
|
|
21
|
+
import { consumeSidechatSeed, SideChatView, sidechatThreadIdOf } from '../SideChatView.tsx'
|
|
22
|
+
import { api } from '../api.ts'
|
|
23
|
+
import { BrowserView } from '../BrowserView.tsx'
|
|
24
|
+
import { IconTerminalOutline16, IconDiffOutline16, IconGlobeOutline16 } from '../icons.tsx'
|
|
25
|
+
import { TERMINAL_FONT_SIZE_MAX, TERMINAL_FONT_SIZE_MIN } from '../../prefs-shared.ts'
|
|
26
|
+
import type { ComponentType } from 'react'
|
|
27
|
+
import type { SessionScope } from '../api.ts'
|
|
28
|
+
import type { SidebarStore } from '../state.ts'
|
|
29
|
+
import type { TabDescriptor } from '../service.ts'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Lazy wrapper over the terminal view: xterm (and its stylesheet) is fetched
|
|
33
|
+
* only when a terminal tab is first opened (see chunk-loader.ts). The
|
|
34
|
+
* wrapper keeps the descriptor contract `(props) => ReactNode` — Sidebar
|
|
35
|
+
* calls it as a plain function.
|
|
36
|
+
*
|
|
37
|
+
* TerminalView's props are { scope, tabId, store } — `tabId` is NOT part of
|
|
38
|
+
* TabComponentProps (it carries `tab: SidebarTab` instead), so the
|
|
39
|
+
* descriptor maps it explicitly; a bare pass-through would leave tabId
|
|
40
|
+
* undefined and TerminalView's isAgentTabId(tabId) would crash on
|
|
41
|
+
* `undefined.startsWith` (regression-pinned in tests/lazy-chunk.spec.tsx).
|
|
42
|
+
*/
|
|
43
|
+
const LazyTerminal = lazyChunkComponent<TerminalViewProps>(
|
|
44
|
+
'terminal',
|
|
45
|
+
(mod) => mod.TerminalView as ComponentType<TerminalViewProps> | undefined,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
/** The terminal view's props (mirror of TerminalView's own signature). */
|
|
49
|
+
interface TerminalViewProps {
|
|
50
|
+
scope: SessionScope
|
|
51
|
+
tabId: string
|
|
52
|
+
store: SidebarStore
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** How many UI-owned terminals may be open at once (agent-owned ones are uncapped). */
|
|
56
|
+
export const TERMINAL_LIMIT = 3
|
|
57
|
+
|
|
58
|
+
/** Optional per-registration builtin behavior (currently terminal title). */
|
|
59
|
+
export interface BuiltinTabOptions {
|
|
60
|
+
/** Returns the display title for newly opened terminal tabs. */
|
|
61
|
+
terminalTitle?: () => string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A client-side uuid for terminal tab identity (not shown in the UI). */
|
|
65
|
+
function terminalUuid(): string {
|
|
66
|
+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
67
|
+
return crypto.randomUUID()
|
|
68
|
+
}
|
|
69
|
+
return `t${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Count UI-owned terminals (agent:` tabs excluded — they are the model's). */
|
|
73
|
+
function uiTerminalCount(state: SidebarState): number {
|
|
74
|
+
return allLeaves(state.splits)
|
|
75
|
+
.flatMap(leaf => leaf.tabs)
|
|
76
|
+
.filter(tab => tab.type === 'terminal' && !isAgentTabId(tab.id)).length
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The 6 built-in tab descriptors. */
|
|
80
|
+
export function builtinTabs(ctx: Context, options: BuiltinTabOptions = {}): readonly TabDescriptor[] {
|
|
81
|
+
return [
|
|
82
|
+
{
|
|
83
|
+
id: 'editor',
|
|
84
|
+
// The single files window: an editor tab with no path IS the file
|
|
85
|
+
// explorer (empty hint + docked tree); with a path it previews/edits
|
|
86
|
+
// the file. Visible in the + menu in the explorer's old slot.
|
|
87
|
+
title: () => t('files'),
|
|
88
|
+
icon: (size: number) => <IconFolderOpen16 size={size} />,
|
|
89
|
+
order: 10,
|
|
90
|
+
hidden: false,
|
|
91
|
+
dedupeKey: (tab) => tab.path,
|
|
92
|
+
// Declarative settings: the file-open behavior picker (in-place switch
|
|
93
|
+
// vs per-path windows) renders as an iconed select row under the
|
|
94
|
+
// editor card's gear in the Side card settings page; the "open with"
|
|
95
|
+
// configuration (SSH host + custom editors) is the custom panel BELOW
|
|
96
|
+
// those rows — the settings seam renders rows first, custom panel after.
|
|
97
|
+
settings: {
|
|
98
|
+
toggles: [{
|
|
99
|
+
key: 'editorExplorer',
|
|
100
|
+
type: 'select',
|
|
101
|
+
title: () => t('editorExplorer'),
|
|
102
|
+
desc: () => t('editorExplorerDesc'),
|
|
103
|
+
options: [
|
|
104
|
+
{
|
|
105
|
+
value: true,
|
|
106
|
+
icon: (size: number) => <IconPanelLeftOutline16 size={size} />,
|
|
107
|
+
title: () => t('editorExplorerMerged'),
|
|
108
|
+
desc: () => t('editorExplorerMergedDesc'),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
value: false,
|
|
112
|
+
icon: (size: number) => <IconCodeOutline16 size={size} />,
|
|
113
|
+
title: () => t('editorExplorerSplit'),
|
|
114
|
+
desc: () => t('editorExplorerSplitDesc'),
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
}],
|
|
118
|
+
render: ({ pluginSettings, updatePluginSetting }) => (
|
|
119
|
+
<OpenWithSettings pluginSettings={pluginSettings} updatePluginSetting={updatePluginSetting} />
|
|
120
|
+
),
|
|
121
|
+
},
|
|
122
|
+
component: ({ ctx, store, scope, tab, expanded, onToggleDir, onReferenceFile }) => (
|
|
123
|
+
<EditorHost
|
|
124
|
+
ctx={ctx}
|
|
125
|
+
store={store}
|
|
126
|
+
scope={scope}
|
|
127
|
+
tab={tab}
|
|
128
|
+
expanded={expanded ?? []}
|
|
129
|
+
onToggleDir={onToggleDir ?? (() => { /* no-op */ })}
|
|
130
|
+
onReferenceFile={onReferenceFile ?? (() => { /* no-op */ })}
|
|
131
|
+
/>
|
|
132
|
+
),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'git',
|
|
136
|
+
title: () => t('git'),
|
|
137
|
+
icon: (size: number) => <IconBranchOutline16 size={size} />,
|
|
138
|
+
order: 20,
|
|
139
|
+
single: true,
|
|
140
|
+
component: ({ ctx, store, scope, onOpenDiff }) => (
|
|
141
|
+
<GitView
|
|
142
|
+
scope={scope}
|
|
143
|
+
onOpenFile={(path) => { openSidebarFile(ctx, store, scope.sessionId, path) }}
|
|
144
|
+
onOpenDiff={onOpenDiff ?? (() => { /* no-op */ })}
|
|
145
|
+
/>
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: 'subagent',
|
|
150
|
+
title: () => t('subagent'),
|
|
151
|
+
icon: (size: number) => <IconThinkOutline16 size={size} />,
|
|
152
|
+
order: 30,
|
|
153
|
+
single: true,
|
|
154
|
+
// Declarative settings: the auto-open switches render under this row in
|
|
155
|
+
// the Side card settings page (the Jobs page's own related settings).
|
|
156
|
+
settings: {
|
|
157
|
+
toggles: [{
|
|
158
|
+
key: 'autoOpenSubagent',
|
|
159
|
+
title: () => t('settingsSubagentTitle'),
|
|
160
|
+
desc: () => t('settingsSubagentDesc'),
|
|
161
|
+
}, {
|
|
162
|
+
key: 'autoOpenJobs',
|
|
163
|
+
title: () => t('settingsJobsTitle'),
|
|
164
|
+
desc: () => t('settingsJobsDesc'),
|
|
165
|
+
}],
|
|
166
|
+
},
|
|
167
|
+
component: ({ ctx, scope, visible, onSubagentJump }) => (
|
|
168
|
+
<SubagentView
|
|
169
|
+
sessionId={scope.sessionId}
|
|
170
|
+
ctx={ctx}
|
|
171
|
+
active={visible}
|
|
172
|
+
onOpenChild={(address) => { onSubagentJump?.(address.childSessionId) }}
|
|
173
|
+
/>
|
|
174
|
+
),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 'sidechat',
|
|
178
|
+
title: () => t('sideChat'),
|
|
179
|
+
icon: (size: number) => <IconNewChatOutline16 size={size} />,
|
|
180
|
+
order: 35,
|
|
181
|
+
// Codex-style: EVERY side conversation is its own tab. A plain open
|
|
182
|
+
// mints a fresh tab flagged `autoCreate` (the view creates the EMPTY
|
|
183
|
+
// thread on mount); a thread switch from the header menu parks the
|
|
184
|
+
// target id for a deterministic `sidechat:<threadId>` reattach tab.
|
|
185
|
+
createTab: () => {
|
|
186
|
+
const threadId = consumeSidechatSeed()
|
|
187
|
+
if (threadId !== undefined) {
|
|
188
|
+
return {
|
|
189
|
+
tab: {
|
|
190
|
+
id: `sidechat:${threadId}`,
|
|
191
|
+
type: 'sidechat',
|
|
192
|
+
title: t('sideChat'),
|
|
193
|
+
meta: { threadId },
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
tab: {
|
|
199
|
+
id: `sidechat:new-${crypto.randomUUID()}`,
|
|
200
|
+
type: 'sidechat',
|
|
201
|
+
title: t('sideChatUntitled'),
|
|
202
|
+
meta: { autoCreate: true },
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
// One tab per thread: an already-open thread focuses instead of
|
|
207
|
+
// duplicating; unbound fresh tabs never dedupe (each mints its own).
|
|
208
|
+
dedupeKey: (tab) => sidechatThreadIdOf(tab),
|
|
209
|
+
// Closing the tab releases the thread's live agent; the session and
|
|
210
|
+
// its history stay persisted (reopen from any thread's header menu).
|
|
211
|
+
onClose: (tab) => {
|
|
212
|
+
const threadId = sidechatThreadIdOf(tab)
|
|
213
|
+
if (threadId !== undefined) {
|
|
214
|
+
void api.sidechatDispose(threadId).catch(() => {})
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
component: ({ ctx, scope, tab, visible }) => (
|
|
218
|
+
<SideChatView ctx={ctx} scope={scope} tab={tab} visible={visible} />
|
|
219
|
+
),
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'terminal',
|
|
223
|
+
title: () => t('terminal'),
|
|
224
|
+
icon: (size: number) => <IconTerminalOutline16 size={size} />,
|
|
225
|
+
order: 40,
|
|
226
|
+
available: (_ctx, _scope, state) => uiTerminalCount(state) < TERMINAL_LIMIT,
|
|
227
|
+
// Declarative settings: the model-facing terminal tools switch and
|
|
228
|
+
// the custom font family/size rows render under this card in the Side
|
|
229
|
+
// card settings page (the host gates the toolset on the tools one
|
|
230
|
+
// independently; the font rows apply live to every terminal).
|
|
231
|
+
settings: {
|
|
232
|
+
toggles: [{
|
|
233
|
+
key: 'agentTerminalTools',
|
|
234
|
+
title: () => t('settingsToolsTitle'),
|
|
235
|
+
desc: () => t('settingsToolsDesc'),
|
|
236
|
+
}, {
|
|
237
|
+
key: 'terminalShell',
|
|
238
|
+
type: 'text',
|
|
239
|
+
title: () => t('settingsShellTitle'),
|
|
240
|
+
desc: () => t('settingsShellDesc'),
|
|
241
|
+
placeholder: t('settingsShellPlaceholder'),
|
|
242
|
+
}, {
|
|
243
|
+
key: 'terminalShellArgs',
|
|
244
|
+
type: 'text',
|
|
245
|
+
title: () => t('settingsShellArgsTitle'),
|
|
246
|
+
desc: () => t('settingsShellArgsDesc'),
|
|
247
|
+
placeholder: t('settingsShellArgsPlaceholder'),
|
|
248
|
+
}, {
|
|
249
|
+
key: 'terminalFontFamily',
|
|
250
|
+
type: 'text',
|
|
251
|
+
title: () => t('settingsFontFamilyTitle'),
|
|
252
|
+
desc: () => t('settingsFontFamilyDesc'),
|
|
253
|
+
placeholder: t('settingsFontFamilyPlaceholder'),
|
|
254
|
+
}, {
|
|
255
|
+
key: 'terminalFontSize',
|
|
256
|
+
type: 'number',
|
|
257
|
+
title: () => t('settingsFontSizeTitle'),
|
|
258
|
+
desc: () => t('settingsFontSizeDesc'),
|
|
259
|
+
min: TERMINAL_FONT_SIZE_MIN,
|
|
260
|
+
max: TERMINAL_FONT_SIZE_MAX,
|
|
261
|
+
unit: 'px',
|
|
262
|
+
}],
|
|
263
|
+
},
|
|
264
|
+
createTab: (state) => {
|
|
265
|
+
const count = uiTerminalCount(state)
|
|
266
|
+
if (count >= TERMINAL_LIMIT) return null
|
|
267
|
+
return {
|
|
268
|
+
tab: {
|
|
269
|
+
id: `terminal:${terminalUuid()}`,
|
|
270
|
+
type: 'terminal',
|
|
271
|
+
title: options.terminalTitle?.() ?? t('terminal'),
|
|
272
|
+
},
|
|
273
|
+
// Keep the legacy counter advancing for compatibility with older
|
|
274
|
+
// persisted states; new ids no longer use it.
|
|
275
|
+
patch: { nextTerminal: state.nextTerminal + 1 },
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
component: ({ tab, scope, store }) => <LazyTerminal scope={scope} store={store} tabId={tab.id} />,
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
id: 'browser',
|
|
282
|
+
title: () => t('browser'),
|
|
283
|
+
icon: (size: number) => <IconGlobeOutline16 size={size} />,
|
|
284
|
+
order: 50,
|
|
285
|
+
// Declarative settings: the sandbox escape hatch, the link-takeover
|
|
286
|
+
// MASTER switch, and the per-protocol takeover switches (http on /
|
|
287
|
+
// https off by default) render under this tab's row in the Side card
|
|
288
|
+
// settings page (the sandbox one is warned on).
|
|
289
|
+
settings: {
|
|
290
|
+
toggles: [{
|
|
291
|
+
key: 'browserNoSandbox',
|
|
292
|
+
title: () => t('settingsBrowserSandboxTitle'),
|
|
293
|
+
desc: () => t('settingsBrowserSandboxDesc'),
|
|
294
|
+
}, {
|
|
295
|
+
key: 'browserInterceptLinks',
|
|
296
|
+
title: () => t('settingsBrowserLinksTitle'),
|
|
297
|
+
desc: () => t('settingsBrowserLinksDesc'),
|
|
298
|
+
}, {
|
|
299
|
+
key: 'browserInterceptHttp',
|
|
300
|
+
title: () => t('settingsBrowserHttpTitle'),
|
|
301
|
+
desc: () => t('settingsBrowserHttpDesc'),
|
|
302
|
+
}, {
|
|
303
|
+
key: 'browserInterceptHttps',
|
|
304
|
+
title: () => t('settingsBrowserHttpsTitle'),
|
|
305
|
+
desc: () => t('settingsBrowserHttpsDesc'),
|
|
306
|
+
}],
|
|
307
|
+
},
|
|
308
|
+
createTab: (state) => ({
|
|
309
|
+
tab: {
|
|
310
|
+
id: `browser:${state.nextBrowser}`,
|
|
311
|
+
type: 'browser',
|
|
312
|
+
title: t('browser'),
|
|
313
|
+
},
|
|
314
|
+
patch: { nextBrowser: state.nextBrowser + 1 },
|
|
315
|
+
}),
|
|
316
|
+
component: (props) => <BrowserView {...props} />,
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: 'diff',
|
|
320
|
+
title: () => t('git'),
|
|
321
|
+
icon: (size: number) => <IconDiffOutline16 size={size} />,
|
|
322
|
+
order: -1,
|
|
323
|
+
hidden: true,
|
|
324
|
+
dedupeKey: (tab) => tab.id,
|
|
325
|
+
component: ({ scope, tab }) => (
|
|
326
|
+
tab.diff === undefined ? null
|
|
327
|
+
: <DiffTab sessionId={scope.sessionId} cwd={scope.cwd} diff={tab.diff} />
|
|
328
|
+
),
|
|
329
|
+
},
|
|
330
|
+
]
|
|
331
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 6 built-in file viewer descriptors: every preview surface is a
|
|
3
|
+
* registered viewer (image / pdf / markdown / html / code /
|
|
4
|
+
* binary-download), exactly like external plugins register theirs. Office
|
|
5
|
+
* previews (.docx / .xlsx / .pptx) are NOT built in anymore — they moved to
|
|
6
|
+
* the recommended office plugin (see plugins-viewers.ts), which registers
|
|
7
|
+
* the same ids through this service.
|
|
8
|
+
*
|
|
9
|
+
* The `binary-download` viewer sniffs NUL bytes via `detect` for unknown
|
|
10
|
+
* binaries and serves legacy doc/xls/ppt by extension; `code` is the
|
|
11
|
+
* catch-all (`exts: []`, lowest priority) that claims any file no other
|
|
12
|
+
* viewer did.
|
|
13
|
+
*
|
|
14
|
+
* The heavy viewers (the CodeMirror-backed markdown/html/code) render
|
|
15
|
+
* through {@link lazyChunkComponent} wrappers — their libraries are fetched
|
|
16
|
+
* only when such a file is first opened (see chunk-loader.ts). The
|
|
17
|
+
* descriptor metadata (id/exts/priority/detect) is identical either way,
|
|
18
|
+
* so matching semantics and external-plugin overrides are unaffected; the
|
|
19
|
+
* `component` wrapper keeps the descriptor contract `(props) => ReactNode`.
|
|
20
|
+
*
|
|
21
|
+
* Every viewer carries the declarative settings-surface fields — `title`
|
|
22
|
+
* and `icon` — so the Side card settings page can render the enable/disable
|
|
23
|
+
* inventory without hardcoding (eating our own dogfood).
|
|
24
|
+
*/
|
|
25
|
+
import { IconCodeOutline16, IconDownloadOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
26
|
+
import { lazyChunkComponent } from '../lazy-chunk.tsx'
|
|
27
|
+
import { PdfView } from '../PdfView.tsx'
|
|
28
|
+
import { BinaryDownload } from '../binary-download.tsx'
|
|
29
|
+
import {
|
|
30
|
+
IconImageOutline16,
|
|
31
|
+
IconMarkdownOutline16,
|
|
32
|
+
IconPdfOutline16,
|
|
33
|
+
IconHtmlOutline16,
|
|
34
|
+
} from '../icons.tsx'
|
|
35
|
+
import type { ComponentType } from 'react'
|
|
36
|
+
import type { FileViewerDescriptor, FileViewerProps } from '../service.ts'
|
|
37
|
+
import { t } from '../locales.ts'
|
|
38
|
+
import css from '../sidebar.module.css'
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Lazy wrapper over the chunk-resident viewer component. The `pick`
|
|
42
|
+
* function is module-level (stable identity — the wrapper effect depends
|
|
43
|
+
* on it); the cast bridges the chunk exports record to the descriptor prop
|
|
44
|
+
* shape (the view reads only its own subset of FileViewerProps).
|
|
45
|
+
*/
|
|
46
|
+
const LazyTextEditor = lazyChunkComponent<FileViewerProps>('editor', (mod) => mod.TextEditor as ComponentType<FileViewerProps> | undefined)
|
|
47
|
+
|
|
48
|
+
/** The 6 built-in file viewer descriptors. */
|
|
49
|
+
export function builtinViewers(): readonly FileViewerDescriptor[] {
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
id: 'image',
|
|
53
|
+
title: () => t('viewerImage'),
|
|
54
|
+
icon: (size: number) => <IconImageOutline16 size={size} />,
|
|
55
|
+
exts: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico', 'avif'],
|
|
56
|
+
fetchStrategy: 'mediaUrl',
|
|
57
|
+
component: ({ mediaUrl: url, title }) => (
|
|
58
|
+
<div className={css.editorImageWrap}>
|
|
59
|
+
<img className={css.editorImage} src={url} alt={title} />
|
|
60
|
+
</div>
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'pdf',
|
|
65
|
+
title: () => t('viewerPdf'),
|
|
66
|
+
icon: (size: number) => <IconPdfOutline16 size={size} />,
|
|
67
|
+
exts: ['pdf'],
|
|
68
|
+
fetchStrategy: 'mediaUrl',
|
|
69
|
+
component: ({ scope, path, title }) => (
|
|
70
|
+
<PdfView scope={scope} path={path} title={title} />
|
|
71
|
+
),
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'markdown',
|
|
75
|
+
title: () => t('viewerMarkdown'),
|
|
76
|
+
icon: (size: number) => <IconMarkdownOutline16 size={size} />,
|
|
77
|
+
exts: ['md', 'markdown'],
|
|
78
|
+
fetchStrategy: 'fsRead',
|
|
79
|
+
component: (props) => <LazyTextEditor {...props} />,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'html',
|
|
83
|
+
title: () => t('viewerHtml'),
|
|
84
|
+
icon: (size: number) => <IconHtmlOutline16 size={size} />,
|
|
85
|
+
exts: ['html', 'htm'],
|
|
86
|
+
fetchStrategy: 'fsRead',
|
|
87
|
+
// Declarative settings: the sandbox escape hatch and the default-unsafe
|
|
88
|
+
// start state render under this viewer's row in the Side card settings
|
|
89
|
+
// page (both warned on).
|
|
90
|
+
settings: {
|
|
91
|
+
toggles: [{
|
|
92
|
+
key: 'htmlViewerNoSandbox',
|
|
93
|
+
title: () => t('settingsHtmlSandboxTitle'),
|
|
94
|
+
desc: () => t('settingsHtmlSandboxDesc'),
|
|
95
|
+
}, {
|
|
96
|
+
key: 'htmlViewerDefaultUnsafe',
|
|
97
|
+
title: () => t('settingsHtmlDefaultUnsafeTitle'),
|
|
98
|
+
desc: () => t('settingsHtmlDefaultUnsafeDesc'),
|
|
99
|
+
}],
|
|
100
|
+
},
|
|
101
|
+
component: (props) => <LazyTextEditor {...props} />,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'code',
|
|
105
|
+
title: () => t('viewerCode'),
|
|
106
|
+
icon: (size: number) => <IconCodeOutline16 size={size} />,
|
|
107
|
+
exts: [],
|
|
108
|
+
priority: -100,
|
|
109
|
+
fetchStrategy: 'fsRead',
|
|
110
|
+
component: (props) => <LazyTextEditor {...props} />,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'binary-download',
|
|
114
|
+
title: () => t('viewerBinary'),
|
|
115
|
+
icon: (size: number) => <IconDownloadOutline16 size={size} />,
|
|
116
|
+
exts: ['doc', 'xls', 'ppt'],
|
|
117
|
+
priority: -50,
|
|
118
|
+
fetchStrategy: 'binary-download',
|
|
119
|
+
// NUL probe: a file whose head bytes contain a NUL is binary — claimed
|
|
120
|
+
// before the catch-all code viewer on the head re-match.
|
|
121
|
+
detect: (_path, head) => head.includes(0),
|
|
122
|
+
component: ({ scope, path }) => <BinaryDownload scope={scope} path={path} />,
|
|
123
|
+
},
|
|
124
|
+
]
|
|
125
|
+
}
|