@chatcode/cco-better-sidebar 0.18.0-alpha.0

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.
Files changed (329) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +554 -0
  3. package/README_EN.md +563 -0
  4. package/assets/logo.svg +1 -0
  5. package/cordis.patch.yml +79 -0
  6. package/lib/client-editor.js +55744 -0
  7. package/lib/client-mermaid.js +208256 -0
  8. package/lib/client-registry.js +30378 -0
  9. package/lib/client-terminal.js +16894 -0
  10. package/lib/client.js +30378 -0
  11. package/lib/index.js +6897 -0
  12. package/lib/invariant.js +22 -0
  13. package/lib/types/agent-opens.d.ts +65 -0
  14. package/lib/types/agent-pty.d.ts +226 -0
  15. package/lib/types/browser-probe.d.ts +15 -0
  16. package/lib/types/bundle-route.d.ts +12 -0
  17. package/lib/types/chatcode-update.d.ts +47 -0
  18. package/lib/types/client/AccountMenu.d.ts +1 -0
  19. package/lib/types/client/AiRatioSection.d.ts +3 -0
  20. package/lib/types/client/BrowserView.d.ts +39 -0
  21. package/lib/types/client/ChatcodeGate.d.ts +6 -0
  22. package/lib/types/client/CodingPlanModelsSettings.d.ts +9 -0
  23. package/lib/types/client/DiffTab.d.ts +6 -0
  24. package/lib/types/client/DiffView.d.ts +51 -0
  25. package/lib/types/client/EditorHost.d.ts +13 -0
  26. package/lib/types/client/FenceErrorNotice.d.ts +5 -0
  27. package/lib/types/client/FileTree.d.ts +41 -0
  28. package/lib/types/client/FreeWindow.d.ts +29 -0
  29. package/lib/types/client/GitView.d.ts +12 -0
  30. package/lib/types/client/MarkdownHtml.d.ts +32 -0
  31. package/lib/types/client/OrphanedTab.d.ts +11 -0
  32. package/lib/types/client/PdfView.d.ts +6 -0
  33. package/lib/types/client/ReleaseNotesDialog.d.ts +11 -0
  34. package/lib/types/client/RenderBoundary.d.ts +32 -0
  35. package/lib/types/client/SandboxStatusBar.d.ts +10 -0
  36. package/lib/types/client/SideCardSection.d.ts +74 -0
  37. package/lib/types/client/Sidebar.d.ts +8 -0
  38. package/lib/types/client/TabBar.d.ts +54 -0
  39. package/lib/types/client/TerminalView.d.ts +24 -0
  40. package/lib/types/client/TextEditor.d.ts +10 -0
  41. package/lib/types/client/TreePanel.d.ts +27 -0
  42. package/lib/types/client/UpdateDialog.d.ts +10 -0
  43. package/lib/types/client/UpdateSettingsPanel.d.ts +1 -0
  44. package/lib/types/client/UpdateSettingsSection.d.ts +10 -0
  45. package/lib/types/client/UploadOverlay.d.ts +20 -0
  46. package/lib/types/client/UsageHeatmapSection.d.ts +4 -0
  47. package/lib/types/client/add-plugin-modal.d.ts +15 -0
  48. package/lib/types/client/api.d.ts +345 -0
  49. package/lib/types/client/binary-download.d.ts +12 -0
  50. package/lib/types/client/breakpoints.d.ts +17 -0
  51. package/lib/types/client/browser.d.ts +60 -0
  52. package/lib/types/client/builtins/index.d.ts +18 -0
  53. package/lib/types/client/builtins/tabs.d.ts +11 -0
  54. package/lib/types/client/builtins/viewers.d.ts +3 -0
  55. package/lib/types/client/chat-presentation.d.ts +13 -0
  56. package/lib/types/client/chunk-loader.d.ts +124 -0
  57. package/lib/types/client/chunks/editor.d.ts +9 -0
  58. package/lib/types/client/chunks/mermaid.d.ts +10 -0
  59. package/lib/types/client/chunks/terminal.d.ts +9 -0
  60. package/lib/types/client/cm-themes.d.ts +24 -0
  61. package/lib/types/client/conversation-draft.d.ts +14 -0
  62. package/lib/types/client/desktop-env.d.ts +35 -0
  63. package/lib/types/client/editor-load.d.ts +66 -0
  64. package/lib/types/client/frame-batcher.d.ts +24 -0
  65. package/lib/types/client/icons.d.ts +66 -0
  66. package/lib/types/client/image-types.d.ts +3 -0
  67. package/lib/types/client/ime-guard.d.ts +36 -0
  68. package/lib/types/client/index.d.ts +19 -0
  69. package/lib/types/client/intercept.d.ts +44 -0
  70. package/lib/types/client/lang.d.ts +14 -0
  71. package/lib/types/client/layout-push.d.ts +15 -0
  72. package/lib/types/client/lazy-chunk.d.ts +25 -0
  73. package/lib/types/client/link-intercept.d.ts +42 -0
  74. package/lib/types/client/locales-ar.d.ts +18 -0
  75. package/lib/types/client/locales-de.d.ts +4 -0
  76. package/lib/types/client/locales-fr.d.ts +10 -0
  77. package/lib/types/client/locales-hi.d.ts +17 -0
  78. package/lib/types/client/locales-id.d.ts +15 -0
  79. package/lib/types/client/locales-it.d.ts +8 -0
  80. package/lib/types/client/locales-ja.d.ts +17 -0
  81. package/lib/types/client/locales-ko.d.ts +9 -0
  82. package/lib/types/client/locales-nl.d.ts +16 -0
  83. package/lib/types/client/locales-pl.d.ts +19 -0
  84. package/lib/types/client/locales-pt.d.ts +1 -0
  85. package/lib/types/client/locales-ru.d.ts +13 -0
  86. package/lib/types/client/locales-sv.d.ts +1 -0
  87. package/lib/types/client/locales-th.d.ts +17 -0
  88. package/lib/types/client/locales-tr.d.ts +17 -0
  89. package/lib/types/client/locales-vi.d.ts +17 -0
  90. package/lib/types/client/locales-zh-HK.d.ts +32 -0
  91. package/lib/types/client/locales-zh-MO.d.ts +32 -0
  92. package/lib/types/client/locales-zh-TW.d.ts +32 -0
  93. package/lib/types/client/locales.d.ts +571 -0
  94. package/lib/types/client/markdown-html.d.ts +96 -0
  95. package/lib/types/client/markdown-images.d.ts +45 -0
  96. package/lib/types/client/markdown-labels.d.ts +21 -0
  97. package/lib/types/client/md-toc.d.ts +4 -0
  98. package/lib/types/client/mermaid-blocks.d.ts +47 -0
  99. package/lib/types/client/mermaid-sanitize.d.ts +2 -0
  100. package/lib/types/client/mermaid.d.ts +12 -0
  101. package/lib/types/client/open-when-sized.d.ts +20 -0
  102. package/lib/types/client/open-with-settings.d.ts +4 -0
  103. package/lib/types/client/open-with.d.ts +87 -0
  104. package/lib/types/client/openpath-intercept.d.ts +52 -0
  105. package/lib/types/client/paths.d.ts +43 -0
  106. package/lib/types/client/pdf-types.d.ts +2 -0
  107. package/lib/types/client/pinned.d.ts +86 -0
  108. package/lib/types/client/plugin-settings.d.ts +8 -0
  109. package/lib/types/client/plugins-shared.d.ts +27 -0
  110. package/lib/types/client/plugins-tabs.d.ts +3 -0
  111. package/lib/types/client/plugins-viewers.d.ts +3 -0
  112. package/lib/types/client/prefs.d.ts +41 -0
  113. package/lib/types/client/produced-files.d.ts +29 -0
  114. package/lib/types/client/selection-payload.d.ts +27 -0
  115. package/lib/types/client/service.d.ts +445 -0
  116. package/lib/types/client/session-pins.d.ts +14 -0
  117. package/lib/types/client/settings-nav-icon.d.ts +27 -0
  118. package/lib/types/client/shell-presets.d.ts +48 -0
  119. package/lib/types/client/split-pane.d.ts +38 -0
  120. package/lib/types/client/state.d.ts +480 -0
  121. package/lib/types/client/tab-content-memo.d.ts +40 -0
  122. package/lib/types/client/terminal-font.d.ts +78 -0
  123. package/lib/types/client/terminal-links.d.ts +119 -0
  124. package/lib/types/client/theme.d.ts +45 -0
  125. package/lib/types/client/titlebar-strip.d.ts +23 -0
  126. package/lib/types/client/ui-tweaks/appicons.d.ts +16 -0
  127. package/lib/types/client/ui-tweaks/archive.d.ts +42 -0
  128. package/lib/types/client/ui-tweaks/branding.d.ts +45 -0
  129. package/lib/types/client/ui-tweaks/cachehit.d.ts +41 -0
  130. package/lib/types/client/ui-tweaks/gitbar.d.ts +103 -0
  131. package/lib/types/client/ui-tweaks/index.d.ts +279 -0
  132. package/lib/types/client/ui-tweaks/mcp.d.ts +28 -0
  133. package/lib/types/client/ui-tweaks/notifier.d.ts +125 -0
  134. package/lib/types/client/ui-tweaks/whale.d.ts +55 -0
  135. package/lib/types/client/upload.d.ts +69 -0
  136. package/lib/types/client/useChatcodeAccount.d.ts +10 -0
  137. package/lib/types/client/wco.d.ts +47 -0
  138. package/lib/types/codingplan.d.ts +37 -0
  139. package/lib/types/config.d.ts +95 -0
  140. package/lib/types/context-types.d.ts +540 -0
  141. package/lib/types/fs-operations.d.ts +30 -0
  142. package/lib/types/fs-search.d.ts +24 -0
  143. package/lib/types/fs-tree.d.ts +51 -0
  144. package/lib/types/git.d.ts +124 -0
  145. package/lib/types/html-route.d.ts +59 -0
  146. package/lib/types/index.d.ts +47 -0
  147. package/lib/types/invariant.d.ts +15 -0
  148. package/lib/types/login-route.d.ts +2 -0
  149. package/lib/types/open-external.d.ts +24 -0
  150. package/lib/types/path-security.d.ts +26 -0
  151. package/lib/types/prefs-shared.d.ts +257 -0
  152. package/lib/types/pty-deps.d.ts +79 -0
  153. package/lib/types/pty-manager.d.ts +168 -0
  154. package/lib/types/tools.d.ts +34 -0
  155. package/lib/types/trust-fence.d.ts +24 -0
  156. package/lib/types/ui-tweaks/archive.d.ts +71 -0
  157. package/lib/types/ui-tweaks/config.d.ts +186 -0
  158. package/lib/types/ui-tweaks/git-web.d.ts +43 -0
  159. package/lib/types/ui-tweaks/git.d.ts +311 -0
  160. package/lib/types/ui-tweaks/index.d.ts +17 -0
  161. package/lib/types/ui-tweaks/mcp.d.ts +103 -0
  162. package/lib/types/ui-tweaks/turn-outcome.d.ts +57 -0
  163. package/lib/types/ui-tweaks/web.d.ts +55 -0
  164. package/lib/types/wire.d.ts +38 -0
  165. package/package.json +213 -0
  166. package/scripts/install.ps1 +310 -0
  167. package/scripts/install.sh +287 -0
  168. package/src/agent-opens.ts +265 -0
  169. package/src/agent-pty.ts +527 -0
  170. package/src/browser-probe.ts +26 -0
  171. package/src/bundle-route.ts +129 -0
  172. package/src/chatcode-update.ts +193 -0
  173. package/src/client/AccountMenu.tsx +177 -0
  174. package/src/client/AiRatioSection.module.css +329 -0
  175. package/src/client/AiRatioSection.tsx +292 -0
  176. package/src/client/BrowserView.tsx +289 -0
  177. package/src/client/ChatcodeGate.tsx +35 -0
  178. package/src/client/CodingPlanModelsSettings.module.css +118 -0
  179. package/src/client/CodingPlanModelsSettings.tsx +101 -0
  180. package/src/client/DiffTab.tsx +114 -0
  181. package/src/client/DiffView.tsx +305 -0
  182. package/src/client/EditorHost.tsx +563 -0
  183. package/src/client/FenceErrorNotice.tsx +40 -0
  184. package/src/client/FileTree.tsx +701 -0
  185. package/src/client/FreeWindow.tsx +337 -0
  186. package/src/client/GitView.tsx +722 -0
  187. package/src/client/MarkdownHtml.tsx +296 -0
  188. package/src/client/OrphanedTab.tsx +27 -0
  189. package/src/client/PdfView.tsx +110 -0
  190. package/src/client/ReleaseNotesDialog.tsx +91 -0
  191. package/src/client/RenderBoundary.tsx +49 -0
  192. package/src/client/SandboxStatusBar.tsx +60 -0
  193. package/src/client/SideCardSection.module.css +932 -0
  194. package/src/client/SideCardSection.tsx +1169 -0
  195. package/src/client/Sidebar.tsx +1857 -0
  196. package/src/client/TabBar.tsx +365 -0
  197. package/src/client/TerminalView.tsx +424 -0
  198. package/src/client/TextEditor.tsx +468 -0
  199. package/src/client/TreePanel.tsx +279 -0
  200. package/src/client/UpdateDialog.tsx +186 -0
  201. package/src/client/UpdateSettingsPanel.tsx +29 -0
  202. package/src/client/UpdateSettingsSection.tsx +22 -0
  203. package/src/client/UploadOverlay.tsx +62 -0
  204. package/src/client/UsageHeatmapSection.module.css +440 -0
  205. package/src/client/UsageHeatmapSection.tsx +301 -0
  206. package/src/client/add-plugin-modal.tsx +199 -0
  207. package/src/client/api.ts +433 -0
  208. package/src/client/binary-download.tsx +23 -0
  209. package/src/client/breakpoints.ts +61 -0
  210. package/src/client/browser.ts +167 -0
  211. package/src/client/builtins/index.ts +37 -0
  212. package/src/client/builtins/tabs.tsx +275 -0
  213. package/src/client/builtins/viewers.tsx +125 -0
  214. package/src/client/chat-presentation.ts +95 -0
  215. package/src/client/chunk-loader.ts +350 -0
  216. package/src/client/chunks/editor.tsx +9 -0
  217. package/src/client/chunks/mermaid.tsx +10 -0
  218. package/src/client/chunks/terminal.tsx +9 -0
  219. package/src/client/cm-themes.ts +129 -0
  220. package/src/client/conversation-draft.ts +29 -0
  221. package/src/client/css-modules.d.ts +5 -0
  222. package/src/client/desktop-env.ts +72 -0
  223. package/src/client/editor-load.ts +92 -0
  224. package/src/client/frame-batcher.ts +56 -0
  225. package/src/client/icons.tsx +170 -0
  226. package/src/client/image-types.ts +8 -0
  227. package/src/client/ime-guard.ts +47 -0
  228. package/src/client/index.tsx +501 -0
  229. package/src/client/intercept.tsx +172 -0
  230. package/src/client/lang.ts +225 -0
  231. package/src/client/layout-push.ts +37 -0
  232. package/src/client/layout.css +156 -0
  233. package/src/client/lazy-chunk.tsx +89 -0
  234. package/src/client/link-intercept.ts +73 -0
  235. package/src/client/locales-ar.ts +387 -0
  236. package/src/client/locales-de.ts +372 -0
  237. package/src/client/locales-fr.ts +379 -0
  238. package/src/client/locales-hi.ts +386 -0
  239. package/src/client/locales-id.ts +384 -0
  240. package/src/client/locales-it.ts +377 -0
  241. package/src/client/locales-ja.ts +486 -0
  242. package/src/client/locales-ko.ts +378 -0
  243. package/src/client/locales-nl.ts +384 -0
  244. package/src/client/locales-pl.ts +388 -0
  245. package/src/client/locales-pt.ts +369 -0
  246. package/src/client/locales-ru.ts +384 -0
  247. package/src/client/locales-sv.ts +369 -0
  248. package/src/client/locales-th.ts +386 -0
  249. package/src/client/locales-tr.ts +386 -0
  250. package/src/client/locales-vi.ts +386 -0
  251. package/src/client/locales-zh-HK.ts +401 -0
  252. package/src/client/locales-zh-MO.ts +401 -0
  253. package/src/client/locales-zh-TW.ts +401 -0
  254. package/src/client/locales.ts +1229 -0
  255. package/src/client/markdown-html.ts +331 -0
  256. package/src/client/markdown-images.ts +137 -0
  257. package/src/client/markdown-labels.tsx +31 -0
  258. package/src/client/md-toc.tsx +127 -0
  259. package/src/client/mermaid-blocks.ts +110 -0
  260. package/src/client/mermaid-sanitize.ts +93 -0
  261. package/src/client/mermaid.tsx +406 -0
  262. package/src/client/open-when-sized.ts +43 -0
  263. package/src/client/open-with-settings.tsx +135 -0
  264. package/src/client/open-with.ts +221 -0
  265. package/src/client/openpath-intercept.ts +79 -0
  266. package/src/client/paths.ts +63 -0
  267. package/src/client/pdf-types.ts +4 -0
  268. package/src/client/pinned.ts +184 -0
  269. package/src/client/plugin-settings.ts +43 -0
  270. package/src/client/plugins-shared.ts +29 -0
  271. package/src/client/plugins-tabs.ts +153 -0
  272. package/src/client/plugins-viewers.ts +44 -0
  273. package/src/client/prefs.ts +216 -0
  274. package/src/client/produced-files.ts +120 -0
  275. package/src/client/selection-payload.ts +86 -0
  276. package/src/client/service.ts +873 -0
  277. package/src/client/session-pins.tsx +334 -0
  278. package/src/client/settings-nav-icon.ts +55 -0
  279. package/src/client/shell-presets.ts +83 -0
  280. package/src/client/sidebar.module.css +3607 -0
  281. package/src/client/split-pane.tsx +333 -0
  282. package/src/client/state.ts +1646 -0
  283. package/src/client/tab-content-memo.ts +55 -0
  284. package/src/client/terminal-font.ts +224 -0
  285. package/src/client/terminal-links.ts +194 -0
  286. package/src/client/theme.ts +110 -0
  287. package/src/client/titlebar-strip.ts +37 -0
  288. package/src/client/ui-tweaks/appicons.ts +24 -0
  289. package/src/client/ui-tweaks/archive.tsx +281 -0
  290. package/src/client/ui-tweaks/branding.tsx +232 -0
  291. package/src/client/ui-tweaks/cachehit.tsx +122 -0
  292. package/src/client/ui-tweaks/gitbar.tsx +2559 -0
  293. package/src/client/ui-tweaks/index.tsx +1696 -0
  294. package/src/client/ui-tweaks/mcp.tsx +582 -0
  295. package/src/client/ui-tweaks/notifier.ts +514 -0
  296. package/src/client/ui-tweaks/whale.tsx +171 -0
  297. package/src/client/upload.ts +187 -0
  298. package/src/client/useChatcodeAccount.ts +103 -0
  299. package/src/client/wco.ts +120 -0
  300. package/src/codingplan.ts +146 -0
  301. package/src/config.ts +207 -0
  302. package/src/context-types.ts +559 -0
  303. package/src/fs-operations.ts +97 -0
  304. package/src/fs-search.ts +113 -0
  305. package/src/fs-tree.ts +158 -0
  306. package/src/git.ts +446 -0
  307. package/src/html-route.ts +106 -0
  308. package/src/index.ts +1524 -0
  309. package/src/invariant.ts +32 -0
  310. package/src/login-route.ts +26 -0
  311. package/src/open-external.ts +90 -0
  312. package/src/path-security.ts +81 -0
  313. package/src/prefs-shared.ts +303 -0
  314. package/src/pty-deps.ts +241 -0
  315. package/src/pty-manager.ts +376 -0
  316. package/src/tools.ts +482 -0
  317. package/src/trust-fence.ts +84 -0
  318. package/src/ui-tweaks/archive.ts +293 -0
  319. package/src/ui-tweaks/config.ts +257 -0
  320. package/src/ui-tweaks/git-web.ts +421 -0
  321. package/src/ui-tweaks/git.ts +1524 -0
  322. package/src/ui-tweaks/index.ts +61 -0
  323. package/src/ui-tweaks/mcp.ts +535 -0
  324. package/src/ui-tweaks/turn-outcome.ts +105 -0
  325. package/src/ui-tweaks/web.ts +225 -0
  326. package/src/wire.ts +110 -0
  327. package/vendor/addon-fit.js +2 -0
  328. package/vendor/xterm.css +218 -0
  329. package/vendor/xterm.js +5 -0
@@ -0,0 +1,279 @@
1
+ /**
2
+ * The files window's tree surface: a global file-name search box on top
3
+ * (300ms debounce; an in-flight search is aborted by the next keystroke)
4
+ * over either the shared controlled FileTree (empty query) or the flat
5
+ * result list (relative paths; click opens through the caller's mode-aware
6
+ * open). Owns its refresh tick: the icon next to the search input clears
7
+ * the tree cache. EditorHost docks it as the tab's right panel (wrapped in
8
+ * a drag-resize handle) and provides the file context-menu open escapes.
9
+ *
10
+ * Uploads (header pickers, the tree's drag-drop and "upload here" menu)
11
+ * all funnel through here: one session at a time, shown in a full-window
12
+ * progress overlay with cancel, followed by a tree refresh and a one-line
13
+ * hint under the search row (success fades, failures and cancels stay).
14
+ * OS file drags are shielded at the panel host (see Sidebar.tsx), so a
15
+ * drop over the file window uploads here and never reaches DSH's chat
16
+ * intake.
17
+ */
18
+ import { useEffect, useRef, useState, type InputHTMLAttributes } from 'react'
19
+ import clsx from 'clsx'
20
+ import { IconFolderOpen16, IconRefreshOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
21
+ import { api } from './api.ts'
22
+ import type { SidebarStore } from './state.ts'
23
+ import { FileTree } from './FileTree.tsx'
24
+ import { IconUploadOutline16 } from './icons.tsx'
25
+ import type { OpenWithTarget } from './open-with.ts'
26
+ import { t } from './locales.ts'
27
+ import { resolveSidebarPath } from './produced-files.ts'
28
+ import { UploadOverlay } from './UploadOverlay.tsx'
29
+ import {
30
+ summarizeResults, uploadHintText, uploadItemsFromFiles, uploadToDir,
31
+ UPLOAD_HINT_MS, type UploadItem,
32
+ } from './upload.ts'
33
+ import css from './sidebar.module.css'
34
+
35
+ /** One in-flight upload session (the overlay's progress source). */
36
+ interface UploadSession {
37
+ dir: string
38
+ done: number
39
+ total: number
40
+ /** Relative path of the file being uploaded ('' when none is in flight). */
41
+ current: string
42
+ controller: AbortController
43
+ }
44
+
45
+ export function TreePanel(props: {
46
+ sessionId: string
47
+ cwd: string | undefined
48
+ /** The sidebar store (passed through to the tree's fence-refusal notice). */
49
+ store: SidebarStore
50
+ expanded: string[]
51
+ revealed: string[]
52
+ onToggle: (path: string) => void
53
+ onOpenFile: (path: string) => void
54
+ /** File context-menu "open in a new tab" (passed through to FileTree). */
55
+ onOpenFileNewTab?: (path: string) => void
56
+ /** File context-menu "open to the side" (passed through to FileTree). */
57
+ onOpenFileSide?: (path: string) => void
58
+ /** The "open with" menu surface (passed through to FileTree; absent →
59
+ * the whole section is hidden). */
60
+ openWithTargets?: OpenWithTarget[]
61
+ openWithPinned?: string[]
62
+ openWithSsh?: boolean
63
+ onOpenWith?: (targetId: string, path: string) => void
64
+ onToggleOpenWithPin?: (targetId: string) => void
65
+ onReferenceFile: (path: string) => void
66
+ /** Full-window presentation: the panel fills its host instead of docking
67
+ * at a fixed width. */
68
+ full?: boolean
69
+ }) {
70
+ const { sessionId, cwd, store, expanded, revealed, onToggle, onOpenFile, onOpenFileNewTab, onOpenFileSide, openWithTargets, openWithPinned, openWithSsh, onOpenWith, onToggleOpenWithPin, onReferenceFile, full } = props
71
+ const [query, setQuery] = useState('')
72
+ const [results, setResults] = useState<{ matches: string[]; truncated: boolean } | null>(null)
73
+ const [error, setError] = useState<string | null>(null)
74
+ const [refreshTick, setRefreshTick] = useState(0)
75
+ /** One-line upload status under the search row ('' hides the hint). */
76
+ const [uploadStatus, setUploadStatus] = useState('')
77
+ /** Whether the status line is a failure/cancel (error color, stays visible). */
78
+ const [uploadFailed, setUploadFailed] = useState(false)
79
+ /** The in-flight upload session (null → no overlay, buttons enabled). */
80
+ const [upload, setUpload] = useState<UploadSession | null>(null)
81
+ /** True between the cancel click and the session settling (button disabled). */
82
+ const [cancelling, setCancelling] = useState(false)
83
+ /** Set by cancelUpload; the settle path shows 'upload cancelled' instead of
84
+ * summarizing the partial results. */
85
+ const cancelledRef = useRef(false)
86
+ const fileInputRef = useRef<HTMLInputElement>(null)
87
+ const folderInputRef = useRef<HTMLInputElement>(null)
88
+
89
+ /** Start one upload session into `dir` (absolute, inside the workspace). */
90
+ const startUpload = (dir: string, items: UploadItem[]): void => {
91
+ if (items.length === 0 || cwd === undefined || upload !== null) return
92
+ cancelledRef.current = false
93
+ const controller = new AbortController()
94
+ setUploadFailed(false)
95
+ setUploadStatus(uploadHintText(0, items.length, '', dir, t))
96
+ setUpload({ dir, done: 0, total: items.length, current: '', controller })
97
+ void uploadToDir({ sessionId, cwd }, dir, items, (done, total, current) => {
98
+ if (current !== '') setUploadStatus(uploadHintText(done, total, current, dir, t))
99
+ setUpload(session => session === null ? session : { ...session, done, total, current })
100
+ }, controller.signal).then((results) => {
101
+ setUpload(null)
102
+ setCancelling(false)
103
+ // Reload the tree whatever the outcome: files may have landed before a
104
+ // cancel, and failures leave whatever did succeed visible.
105
+ setRefreshTick(tick => tick + 1)
106
+ if (cancelledRef.current) {
107
+ setUploadStatus(t('uploadCancelled'))
108
+ setUploadFailed(true)
109
+ return
110
+ }
111
+ const status = summarizeResults(results, t)
112
+ setUploadStatus(status)
113
+ setUploadFailed(results.some(result => !result.ok))
114
+ // Success messages are transient; failures stay until the next action.
115
+ if (results.every(result => result.ok)) {
116
+ window.setTimeout(() => {
117
+ setUploadStatus(current => current === status ? '' : current)
118
+ }, UPLOAD_HINT_MS)
119
+ }
120
+ })
121
+ }
122
+
123
+ /** Cancel the in-flight upload (aborts the request; the host drops its temp). */
124
+ const cancelUpload = (): void => {
125
+ if (upload === null || cancelling) return
126
+ cancelledRef.current = true
127
+ setCancelling(true)
128
+ upload.controller.abort()
129
+ }
130
+
131
+ const folderInputProps = { webkitdirectory: '' } as InputHTMLAttributes<HTMLInputElement>
132
+
133
+ const needle = query.trim()
134
+ useEffect(() => {
135
+ if (needle === '') {
136
+ setResults(null)
137
+ setError(null)
138
+ return
139
+ }
140
+ const controller = new AbortController()
141
+ const timer = window.setTimeout(() => {
142
+ api.fsSearch({ sessionId, cwd }, needle, controller.signal).then((found) => {
143
+ setResults(found)
144
+ setError(null)
145
+ }).catch((failure: unknown) => {
146
+ if (controller.signal.aborted) return
147
+ setResults(null)
148
+ setError(failure instanceof Error ? failure.message : String(failure))
149
+ })
150
+ }, 300)
151
+ return () => {
152
+ window.clearTimeout(timer)
153
+ controller.abort()
154
+ }
155
+ }, [sessionId, cwd, needle])
156
+
157
+ const busy = upload !== null
158
+
159
+ return (
160
+ <div className={clsx(css.editorTreePanel, full === true && css.editorTreePanelFull)}>
161
+ <div className={css.editorTreeSearch}>
162
+ <input
163
+ className={css.editorSearchInput}
164
+ value={query}
165
+ placeholder={t('editorSearchPlaceholder')}
166
+ spellCheck={false}
167
+ onChange={(event) => { setQuery(event.target.value) }}
168
+ />
169
+ <button
170
+ type="button"
171
+ className={css.iconButton}
172
+ aria-label={t('refresh')}
173
+ title={t('refresh')}
174
+ onClick={() => { setRefreshTick(tick => tick + 1) }}
175
+ >
176
+ <IconRefreshOutline16 size={14} />
177
+ </button>
178
+ <button
179
+ type="button"
180
+ className={css.iconButton}
181
+ aria-label={t('uploadFiles')}
182
+ title={t('uploadFiles')}
183
+ disabled={busy}
184
+ onClick={() => { fileInputRef.current?.click() }}
185
+ >
186
+ <IconUploadOutline16 size={14} />
187
+ </button>
188
+ <button
189
+ type="button"
190
+ className={css.iconButton}
191
+ aria-label={t('uploadFolder')}
192
+ title={t('uploadFolder')}
193
+ disabled={busy}
194
+ onClick={() => { folderInputRef.current?.click() }}
195
+ >
196
+ <IconFolderOpen16 size={14} />
197
+ </button>
198
+ <input
199
+ ref={fileInputRef}
200
+ type="file"
201
+ multiple
202
+ style={{ display: 'none' }}
203
+ onChange={(event) => {
204
+ if (cwd !== undefined) startUpload(cwd, uploadItemsFromFiles(event.target.files ?? []))
205
+ event.target.value = ''
206
+ }}
207
+ />
208
+ <input
209
+ ref={folderInputRef}
210
+ type="file"
211
+ multiple
212
+ {...folderInputProps}
213
+ style={{ display: 'none' }}
214
+ onChange={(event) => {
215
+ if (cwd !== undefined) startUpload(cwd, uploadItemsFromFiles(event.target.files ?? []))
216
+ event.target.value = ''
217
+ }}
218
+ />
219
+ </div>
220
+ {uploadStatus !== '' && (
221
+ <div className={clsx(css.editorSearchHint, uploadFailed && css.editorError)} title={uploadStatus}>{uploadStatus}</div>
222
+ )}
223
+ {needle === '' ? (
224
+ <FileTree
225
+ sessionId={sessionId}
226
+ cwd={cwd}
227
+ store={store}
228
+ expanded={expanded}
229
+ revealed={revealed}
230
+ onToggle={onToggle}
231
+ onOpenFile={onOpenFile}
232
+ onOpenFileNewTab={onOpenFileNewTab}
233
+ onOpenFileSide={onOpenFileSide}
234
+ openWithTargets={openWithTargets}
235
+ openWithPinned={openWithPinned}
236
+ openWithSsh={openWithSsh}
237
+ onOpenWith={onOpenWith}
238
+ onToggleOpenWithPin={onToggleOpenWithPin}
239
+ onReferenceFile={onReferenceFile}
240
+ refreshTick={refreshTick}
241
+ onUploadRequest={startUpload}
242
+ busy={busy}
243
+ />
244
+ ) : (
245
+ <div className={css.explorerBody}>
246
+ {error !== null && <div className={clsx(css.editorSearchHint, css.editorError)}>{error}</div>}
247
+ {error === null && results === null && <div className={css.editorSearchHint}>{t('loading')}</div>}
248
+ {error === null && results !== null && results.matches.length === 0 && (
249
+ <div className={css.editorSearchHint}>{t('editorSearchNoResults')}</div>
250
+ )}
251
+ {error === null && results !== null && results.matches.map(rel => (
252
+ <button
253
+ key={rel}
254
+ type="button"
255
+ className={css.editorSearchResult}
256
+ title={rel}
257
+ onClick={() => { onOpenFile(resolveSidebarPath(cwd, rel)) }}
258
+ >
259
+ {rel}
260
+ </button>
261
+ ))}
262
+ {error === null && results?.truncated === true && (
263
+ <div className={css.editorSearchHint}>{t('editorSearchTruncated')}</div>
264
+ )}
265
+ </div>
266
+ )}
267
+ {upload !== null && (
268
+ <UploadOverlay
269
+ dir={upload.dir}
270
+ done={upload.done}
271
+ total={upload.total}
272
+ current={upload.current}
273
+ onCancel={cancelUpload}
274
+ cancelling={cancelling}
275
+ />
276
+ )}
277
+ </div>
278
+ )
279
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * "Check for updates / update now" panel. It asks the host to compare the
3
+ * ChatCode package against the npm registry, then — on a single "update now"
4
+ * click — upgrades it globally via the host. The upgrade runs on the DSH
5
+ * machine (npm install -g), not in the browser, so success is followed by a
6
+ * "restart to apply" hint.
7
+ *
8
+ * Two mount variants:
9
+ * - `variant: 'dialog'`: a full-screen modal overlay (legacy, for a menu
10
+ * entry that floats above the app).
11
+ * - `variant: 'inline'` (default here): same content but NO overlay, NO modal
12
+ * frame — it renders embedded in the host Settings panel, so it never
13
+ * stacks a second white backdrop on top of the Settings dialog's own.
14
+ */
15
+ import { useEffect, useState } from 'react'
16
+ import {
17
+ api,
18
+ type ChatCodeUpdateCheck,
19
+ type ChatCodeUpdateResult,
20
+ } from './api.ts'
21
+ import { t } from './locales.ts'
22
+ import css from './sidebar.module.css'
23
+
24
+ type Phase = 'checking' | 'idle' | 'performing' | 'done' | 'error'
25
+
26
+ interface UpdateDialogProps {
27
+ open: boolean
28
+ onClose: () => void
29
+ /** 'dialog' wraps content in a full-screen overlay; 'inline' renders the
30
+ * same content bare so it can sit inside the host Settings panel without
31
+ * a second backdrop. */
32
+ variant?: 'dialog' | 'inline'
33
+ }
34
+
35
+ /** Describe one package row, tagging whether it is out of date. */
36
+ function PackageRow({ check }: { check: ChatCodeUpdateCheck }): JSX.Element {
37
+ const current = check.current ?? t('updateNotInstalled')
38
+ const latest = check.latest ?? t('updateUnknown')
39
+ return (
40
+ <li>
41
+ <strong>{check.packageName}</strong>
42
+ <span className={css.updateVersions}>{current} → {latest}</span>
43
+ </li>
44
+ )
45
+ }
46
+
47
+ export function UpdateDialog({ open, onClose, variant = 'dialog' }: UpdateDialogProps): JSX.Element | null {
48
+ const [phase, setPhase] = useState<Phase>('checking')
49
+ const [checks, setChecks] = useState<ChatCodeUpdateCheck[]>([])
50
+ const [results, setResults] = useState<ChatCodeUpdateResult[]>([])
51
+ const [error, setError] = useState('')
52
+
53
+ useEffect(() => {
54
+ if (!open) return
55
+ setPhase('checking')
56
+ setResults([])
57
+ setError('')
58
+ const controller = new AbortController()
59
+ void api.chatcodeUpdateCheck(controller.signal)
60
+ .then(({ packages }) => {
61
+ setChecks(packages)
62
+ setPhase('idle')
63
+ })
64
+ .catch((reason: unknown) => {
65
+ setError(reason instanceof Error ? reason.message : String(reason))
66
+ setPhase('error')
67
+ })
68
+ return () => controller.abort()
69
+ }, [open])
70
+
71
+ useEffect(() => {
72
+ if (!open) return
73
+ const onKeyDown = (event: KeyboardEvent): void => {
74
+ if (event.key !== 'Escape') return
75
+ event.preventDefault()
76
+ onClose()
77
+ }
78
+ window.addEventListener('keydown', onKeyDown)
79
+ return () => window.removeEventListener('keydown', onKeyDown)
80
+ }, [open, onClose])
81
+
82
+ if (!open) return null
83
+
84
+ const anyUpdate = checks.some(check => check.needsUpdate)
85
+ const close = (): void => onClose()
86
+
87
+ const perform = async (): Promise<void> => {
88
+ setPhase('performing')
89
+ setError('')
90
+ try {
91
+ const { results: next } = await api.chatcodeUpdatePerform()
92
+ setResults(next)
93
+ setChecks(next.map(result => ({
94
+ packageName: result.packageName,
95
+ current: result.ok ? result.to : null,
96
+ latest: result.to,
97
+ needsUpdate: false,
98
+ })))
99
+ setPhase('done')
100
+ } catch (reason: unknown) {
101
+ setError(reason instanceof Error ? reason.message : String(reason))
102
+ setPhase('error')
103
+ }
104
+ }
105
+
106
+ const body = (
107
+ <div className={css.updateBody}>
108
+ {phase === 'checking' && <p>{t('updateChecking')}</p>}
109
+ {phase === 'idle' && (
110
+ <>
111
+ <ul className={css.updateList}>
112
+ {checks.map(check => <PackageRow key={check.packageName} check={check} />)}
113
+ </ul>
114
+ {anyUpdate
115
+ ? <p>{t('updateConfirm')}</p>
116
+ : <p>{t('updateUpToDate')}</p>}
117
+ </>
118
+ )}
119
+ {phase === 'performing' && <p>{t('updatePerforming')}</p>}
120
+ {phase === 'done' && (
121
+ <>
122
+ <ul className={css.updateList}>
123
+ {results.map(result => (
124
+ <li key={result.packageName}>
125
+ <strong>{result.packageName}</strong>
126
+ <span className={css.updateVersions}>{result.from ?? t('updateNotInstalled')} → {result.to ?? '?'}</span>
127
+ {result.ok && <span className={css.updateOk}> ✓</span>}
128
+ {!result.ok && <span className={css.updateError}> ✗</span>}
129
+ </li>
130
+ ))}
131
+ </ul>
132
+ {results.some(result => !result.ok) ? (
133
+ <>
134
+ <p className={css.updateErrorText}>{t('updateError')}</p>
135
+ {results.filter(result => !result.ok).map(result => (
136
+ <pre key={result.packageName} className={css.updateCommand}>{result.message}</pre>
137
+ ))}
138
+ </>
139
+ ) : (
140
+ <p>{t('updateRestartHint')}</p>
141
+ )}
142
+ </>
143
+ )}
144
+ {phase === 'error' && <p className={css.updateErrorText}>{error}</p>}
145
+ </div>
146
+ )
147
+
148
+ const footer = (
149
+ <div className={css.updateFooter}>
150
+ {phase === 'idle' && anyUpdate && (
151
+ <button type="button" className={css.releaseNotesButton} onClick={() => void perform()}>{t('updateNow')}</button>
152
+ )}
153
+ {(phase === 'done' || phase === 'error' || phase === 'idle') && (
154
+ <button type="button" className={css.releaseNotesButton} onClick={close}>{t('close')}</button>
155
+ )}
156
+ </div>
157
+ )
158
+
159
+ if (variant === 'inline') {
160
+ // No overlay, no modal frame: this content lives on the Settings pane
161
+ // itself, whose own dialog already provides the dimmed backdrop.
162
+ return (
163
+ <div className={css.updateInline}>
164
+ {body}
165
+ {footer}
166
+ </div>
167
+ )
168
+ }
169
+
170
+ return (
171
+ <div
172
+ className={css.releaseNotesOverlay}
173
+ role="presentation"
174
+ onMouseDown={event => { if (event.target === event.currentTarget) close() }}
175
+ >
176
+ <section className={css.releaseNotesDialog} role="dialog" aria-modal="true" aria-labelledby="update-notes-title">
177
+ <header className={css.releaseNotesHeader}>
178
+ <h2 id="update-notes-title">{t('updateMenu')}</h2>
179
+ <button type="button" className={css.releaseNotesIconButton} aria-label={t('close')} onClick={close}>×</button>
180
+ </header>
181
+ {body}
182
+ {footer}
183
+ </section>
184
+ </div>
185
+ )
186
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * "Check for updates / update now" entry surfaced inside the host Settings
3
+ * dialog (rendered via the "更新" settings.section panel). Keeps a slim
4
+ * inline row + the reusable {@link UpdateDialog} **inline** overlay, so no
5
+ * second modal/backdrop stacks on top of the Settings dialog's own — the
6
+ * update state unfolds directly in the panel body.
7
+ */
8
+ import { useState } from 'react'
9
+ import { t } from './locales.ts'
10
+ import { UpdateDialog } from './UpdateDialog.tsx'
11
+ import css from './sidebar.module.css'
12
+
13
+ export function UpdateSettingsPanel(): JSX.Element {
14
+ const [open, setOpen] = useState(false)
15
+ return (
16
+ <div className={css.updateSettingsBlock}>
17
+ <div className={css.updateSettingsRow}>
18
+ <div className={css.updateSettingsText}>
19
+ <span className={css.updateSettingsTitle}>{t('updateMenu')}</span>
20
+ <span className={css.updateSettingsDesc}>{t('updateSettingsDesc')}</span>
21
+ </div>
22
+ <button type="button" className={css.releaseNotesButton} onClick={() => setOpen(v => !v)}>
23
+ {t('updateNow')}
24
+ </button>
25
+ </div>
26
+ <UpdateDialog open={open} onClose={() => setOpen(false)} variant="inline" />
27
+ </div>
28
+ )
29
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The "更新" settings section: a dedicated nav entry in the DSH Settings
3
+ * shell (sibling of 侧边卡片 / Token 用量 / AI 代码占比) hosting the
4
+ * one-click ChatCode upgrade entry. It surfaces the reusable
5
+ * {@link UpdateSettingsPanel} row in the section body.
6
+ */
7
+ import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
8
+ import { UpdateSettingsPanel } from './UpdateSettingsPanel.tsx'
9
+ import css from './SideCardSection.module.css'
10
+
11
+ /** Full section props: the settings shell's runtime share (close, …). */
12
+ export type UpdateSettingsSectionProps = PropsRuntime<'settings.section'>
13
+
14
+ export function UpdateSettingsSection(_props: UpdateSettingsSectionProps): JSX.Element {
15
+ return (
16
+ <div className={css.section}>
17
+ <div className={css.group}>
18
+ <UpdateSettingsPanel />
19
+ </div>
20
+ </div>
21
+ )
22
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Full-window upload progress over the files tree: a blurred scrim (same mask
3
+ * token as the repo's Modal primitive) with a card showing the target
4
+ * directory, file-level progress, and a cancel button. Esc cancels too —
5
+ * clicking the scrim does not, so a stray click can never abort an upload.
6
+ * Rendered inside TreePanel (absolute inset-0), so it covers only the file
7
+ * window and never the conversation column.
8
+ */
9
+ import { useEffect, type ReactNode } from 'react'
10
+ import { IconUploadOutline16 } from './icons.tsx'
11
+ import { t } from './locales.ts'
12
+ import { uploadHintText } from './upload.ts'
13
+ import css from './sidebar.module.css'
14
+
15
+ export function UploadOverlay(props: {
16
+ /** Absolute upload directory (the session workspace or a tree directory). */
17
+ dir: string
18
+ done: number
19
+ total: number
20
+ /** Relative path of the file being uploaded ('' when none is in flight). */
21
+ current: string
22
+ onCancel: () => void
23
+ /** True while cancellation is in flight (disables the cancel button). */
24
+ cancelling?: boolean
25
+ }): ReactNode {
26
+ const { dir, done, total, current, onCancel, cancelling } = props
27
+ // Esc cancels (MermaidModal-style native listener; the overlay is not a
28
+ // focus-trapped dialog, so a window listener is the honest scope).
29
+ useEffect(() => {
30
+ const onKey = (event: KeyboardEvent): void => {
31
+ if (event.key === 'Escape') onCancel()
32
+ }
33
+ window.addEventListener('keydown', onKey)
34
+ return () => { window.removeEventListener('keydown', onKey) }
35
+ }, [onCancel])
36
+
37
+ const percent = total === 0 ? 0 : Math.min(100, Math.round((done / total) * 100))
38
+ return (
39
+ <div className={css.uploadOverlay} role="dialog" aria-modal="true" aria-label={t('uploadingTo', { dir })}>
40
+ <div className={css.uploadOverlayCard}>
41
+ <div className={css.uploadOverlayTitle} title={dir}>
42
+ <IconUploadOutline16 size={16} />
43
+ <span>{t('uploadingTo', { dir })}</span>
44
+ </div>
45
+ <div
46
+ className={css.uploadOverlayProgress}
47
+ role="progressbar"
48
+ aria-valuemin={0}
49
+ aria-valuemax={total}
50
+ aria-valuenow={done}
51
+ aria-valuetext={t('uploadProgress', { done, total, name: current })}
52
+ >
53
+ <div className={css.uploadOverlayProgressFill} style={{ width: `${percent}%` }} />
54
+ </div>
55
+ <div className={css.uploadOverlayStatus}>{uploadHintText(done, total, current, dir, t)}</div>
56
+ <button type="button" className={css.uploadOverlayCancel} disabled={cancelling} onClick={onCancel}>
57
+ {t('cancel')}
58
+ </button>
59
+ </div>
60
+ </div>
61
+ )
62
+ }