nastechai-desktop 18.1.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 (546) hide show
  1. package/.prettierrc +11 -0
  2. package/DESIGN.md +167 -0
  3. package/README.md +141 -0
  4. package/assets/icon.icns +0 -0
  5. package/assets/icon.ico +0 -0
  6. package/assets/icon.png +0 -0
  7. package/components.json +21 -0
  8. package/electron/backend-env.cjs +112 -0
  9. package/electron/backend-env.test.cjs +111 -0
  10. package/electron/backend-probes.cjs +106 -0
  11. package/electron/backend-probes.test.cjs +82 -0
  12. package/electron/backend-ready.cjs +66 -0
  13. package/electron/bootstrap-platform.cjs +91 -0
  14. package/electron/bootstrap-platform.test.cjs +111 -0
  15. package/electron/bootstrap-runner.cjs +720 -0
  16. package/electron/bootstrap-runner.test.cjs +138 -0
  17. package/electron/connection-config.cjs +254 -0
  18. package/electron/connection-config.test.cjs +329 -0
  19. package/electron/dashboard-token.cjs +99 -0
  20. package/electron/dashboard-token.test.cjs +142 -0
  21. package/electron/desktop-uninstall.cjs +232 -0
  22. package/electron/desktop-uninstall.test.cjs +246 -0
  23. package/electron/entitlements.mac.inherit.plist +14 -0
  24. package/electron/entitlements.mac.plist +14 -0
  25. package/electron/fs-read-dir.cjs +109 -0
  26. package/electron/fs-read-dir.test.cjs +364 -0
  27. package/electron/gateway-ws-probe.cjs +188 -0
  28. package/electron/gateway-ws-probe.test.cjs +122 -0
  29. package/electron/git-root.cjs +54 -0
  30. package/electron/git-root.test.cjs +40 -0
  31. package/electron/git-worktrees.cjs +174 -0
  32. package/electron/hardening.cjs +184 -0
  33. package/electron/hardening.test.cjs +116 -0
  34. package/electron/main.cjs +5762 -0
  35. package/electron/oauth-net-request.cjs +20 -0
  36. package/electron/oauth-net-request.test.cjs +34 -0
  37. package/electron/preload.cjs +135 -0
  38. package/electron/session-windows.cjs +99 -0
  39. package/electron/session-windows.test.cjs +177 -0
  40. package/electron/update-remote.cjs +56 -0
  41. package/electron/update-remote.test.cjs +78 -0
  42. package/electron/vscode-marketplace.cjs +331 -0
  43. package/electron/vscode-marketplace.test.cjs +113 -0
  44. package/electron/windows-child-process.test.cjs +57 -0
  45. package/electron/windows-user-env.cjs +76 -0
  46. package/electron/windows-user-env.test.cjs +90 -0
  47. package/electron/workspace-cwd.cjs +38 -0
  48. package/electron/workspace-cwd.test.cjs +45 -0
  49. package/eslint.config.mjs +122 -0
  50. package/index.html +17 -0
  51. package/package.json +254 -0
  52. package/pr-assets/session-source-folders.png +0 -0
  53. package/preview-demo.html +65 -0
  54. package/public/apple-touch-icon.png +0 -0
  55. package/public/ds-assets/filler-bg0.jpg +0 -0
  56. package/public/nastech-frames/nastech-frame-0.png +0 -0
  57. package/public/nastech-frames/nastech-frame-1.png +0 -0
  58. package/public/nastech-frames/nastech-frame-2.png +0 -0
  59. package/public/nastech-frames/nastech-frame-3.png +0 -0
  60. package/public/nastech-frames/nastech-frame-4.png +0 -0
  61. package/public/nastech-frames/nastech-frame-5.png +0 -0
  62. package/public/nastech-frames/nastech-frame-6.png +0 -0
  63. package/public/nastech-frames/nastech-frame-7.png +0 -0
  64. package/public/nastech-girl.jpg +0 -0
  65. package/public/nastech-sprite.png +0 -0
  66. package/public/nastech.png +0 -0
  67. package/scripts/after-pack.cjs +41 -0
  68. package/scripts/assert-dist-built.cjs +70 -0
  69. package/scripts/assert-dist-built.test.cjs +84 -0
  70. package/scripts/assert-root-install.cjs +13 -0
  71. package/scripts/before-build.cjs +11 -0
  72. package/scripts/before-pack.cjs +78 -0
  73. package/scripts/before-pack.test.cjs +53 -0
  74. package/scripts/click-session.mjs +51 -0
  75. package/scripts/dev-no-hmr.mjs +22 -0
  76. package/scripts/diag-jump.mjs +115 -0
  77. package/scripts/diag-scroll-reset.mjs +229 -0
  78. package/scripts/eval.mjs +21 -0
  79. package/scripts/leak-typing.mjs +222 -0
  80. package/scripts/measure-jump.mjs +108 -0
  81. package/scripts/measure-latency.mjs +184 -0
  82. package/scripts/measure-real-stream.mjs +252 -0
  83. package/scripts/measure-submit.mjs +179 -0
  84. package/scripts/measure-synthetic-stream.mjs +322 -0
  85. package/scripts/notarize-artifact.cjs +77 -0
  86. package/scripts/notarize.cjs +100 -0
  87. package/scripts/patch-electron-builder-mac-binary.cjs +59 -0
  88. package/scripts/probe-renderer.mjs +38 -0
  89. package/scripts/probe-thread.mjs +40 -0
  90. package/scripts/profile-long-stream.mjs +191 -0
  91. package/scripts/profile-real-stream.mjs +137 -0
  92. package/scripts/profile-synth-stream.mjs +103 -0
  93. package/scripts/profile-typing-lag.md +381 -0
  94. package/scripts/profile-typing.mjs +260 -0
  95. package/scripts/reload-renderer.mjs +25 -0
  96. package/scripts/reload.mjs +36 -0
  97. package/scripts/set-exe-identity.cjs +94 -0
  98. package/scripts/stage-native-deps.cjs +159 -0
  99. package/scripts/test-desktop.mjs +425 -0
  100. package/scripts/write-build-stamp.cjs +126 -0
  101. package/src/app/agents/index.tsx +398 -0
  102. package/src/app/artifacts/index.test.ts +62 -0
  103. package/src/app/artifacts/index.tsx +906 -0
  104. package/src/app/chat/chat-drop-overlay.tsx +48 -0
  105. package/src/app/chat/chat-swap-overlay.tsx +47 -0
  106. package/src/app/chat/composer/attachments.tsx +114 -0
  107. package/src/app/chat/composer/completion-drawer.tsx +63 -0
  108. package/src/app/chat/composer/context-menu.tsx +172 -0
  109. package/src/app/chat/composer/controls.tsx +289 -0
  110. package/src/app/chat/composer/drop-affordance.ts +2 -0
  111. package/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
  112. package/src/app/chat/composer/focus.ts +134 -0
  113. package/src/app/chat/composer/help-hint.tsx +59 -0
  114. package/src/app/chat/composer/hooks/use-at-completions.ts +141 -0
  115. package/src/app/chat/composer/hooks/use-live-completion-adapter.ts +119 -0
  116. package/src/app/chat/composer/hooks/use-mic-recorder.ts +291 -0
  117. package/src/app/chat/composer/hooks/use-slash-completions.ts +114 -0
  118. package/src/app/chat/composer/hooks/use-voice-conversation.ts +390 -0
  119. package/src/app/chat/composer/hooks/use-voice-recorder.ts +116 -0
  120. package/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
  121. package/src/app/chat/composer/index.tsx +1611 -0
  122. package/src/app/chat/composer/inline-refs.ts +138 -0
  123. package/src/app/chat/composer/model-pill.tsx +86 -0
  124. package/src/app/chat/composer/queue-panel.tsx +130 -0
  125. package/src/app/chat/composer/rich-editor.test.ts +18 -0
  126. package/src/app/chat/composer/rich-editor.ts +165 -0
  127. package/src/app/chat/composer/skin-slash-popover.tsx +61 -0
  128. package/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
  129. package/src/app/chat/composer/status-stack/index.tsx +202 -0
  130. package/src/app/chat/composer/status-stack/status-row.tsx +155 -0
  131. package/src/app/chat/composer/text-utils.test.ts +77 -0
  132. package/src/app/chat/composer/text-utils.ts +107 -0
  133. package/src/app/chat/composer/trigger-popover.test.tsx +42 -0
  134. package/src/app/chat/composer/trigger-popover.tsx +116 -0
  135. package/src/app/chat/composer/types.ts +64 -0
  136. package/src/app/chat/composer/url-dialog.tsx +82 -0
  137. package/src/app/chat/composer/voice-activity.tsx +252 -0
  138. package/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
  139. package/src/app/chat/hooks/use-composer-actions.ts +525 -0
  140. package/src/app/chat/hooks/use-file-drop-zone.ts +118 -0
  141. package/src/app/chat/index.tsx +390 -0
  142. package/src/app/chat/perf-probe.tsx +269 -0
  143. package/src/app/chat/right-rail/index.ts +1 -0
  144. package/src/app/chat/right-rail/preview-console-state.ts +82 -0
  145. package/src/app/chat/right-rail/preview-console.tsx +290 -0
  146. package/src/app/chat/right-rail/preview-file.tsx +559 -0
  147. package/src/app/chat/right-rail/preview-pane.test.tsx +43 -0
  148. package/src/app/chat/right-rail/preview-pane.tsx +657 -0
  149. package/src/app/chat/right-rail/preview.tsx +171 -0
  150. package/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
  151. package/src/app/chat/scroll-to-bottom-button.tsx +74 -0
  152. package/src/app/chat/sidebar/cron-jobs-section.tsx +325 -0
  153. package/src/app/chat/sidebar/index.tsx +1219 -0
  154. package/src/app/chat/sidebar/load-more-row.tsx +30 -0
  155. package/src/app/chat/sidebar/order.test.ts +21 -0
  156. package/src/app/chat/sidebar/order.ts +17 -0
  157. package/src/app/chat/sidebar/profile-switcher.tsx +516 -0
  158. package/src/app/chat/sidebar/session-actions-menu.tsx +264 -0
  159. package/src/app/chat/sidebar/session-row.tsx +257 -0
  160. package/src/app/chat/sidebar/virtual-session-list.tsx +154 -0
  161. package/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
  162. package/src/app/chat/sidebar/workspace-groups.ts +326 -0
  163. package/src/app/chat/thread-loading.test.ts +34 -0
  164. package/src/app/chat/thread-loading.ts +26 -0
  165. package/src/app/command-center/index.tsx +654 -0
  166. package/src/app/command-palette/index.tsx +513 -0
  167. package/src/app/command-palette/marketplace-theme-page.tsx +157 -0
  168. package/src/app/cron/index.tsx +942 -0
  169. package/src/app/cron/job-state.ts +29 -0
  170. package/src/app/desktop-controller.tsx +938 -0
  171. package/src/app/floating-hud.ts +22 -0
  172. package/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
  173. package/src/app/gateway/hooks/use-gateway-boot.ts +387 -0
  174. package/src/app/gateway/hooks/use-gateway-request.ts +138 -0
  175. package/src/app/hooks/use-keybinds.ts +186 -0
  176. package/src/app/hooks/use-refresh-hotkey.ts +45 -0
  177. package/src/app/hooks/use-route-enum-param.ts +38 -0
  178. package/src/app/index.tsx +1 -0
  179. package/src/app/layout-constants.ts +13 -0
  180. package/src/app/messaging/index.tsx +648 -0
  181. package/src/app/messaging/platform-icon.tsx +93 -0
  182. package/src/app/model-picker-overlay.tsx +42 -0
  183. package/src/app/model-visibility-overlay.tsx +31 -0
  184. package/src/app/overlays/overlay-chrome.tsx +66 -0
  185. package/src/app/overlays/overlay-search-input.tsx +33 -0
  186. package/src/app/overlays/overlay-split-layout.tsx +130 -0
  187. package/src/app/overlays/overlay-view.tsx +91 -0
  188. package/src/app/page-search-shell.tsx +75 -0
  189. package/src/app/profiles/create-profile-dialog.tsx +154 -0
  190. package/src/app/profiles/delete-profile-dialog.tsx +65 -0
  191. package/src/app/profiles/index.tsx +671 -0
  192. package/src/app/profiles/rename-profile-dialog.tsx +125 -0
  193. package/src/app/right-sidebar/files/dnd-manager.ts +27 -0
  194. package/src/app/right-sidebar/files/ipc.test.ts +100 -0
  195. package/src/app/right-sidebar/files/ipc.ts +161 -0
  196. package/src/app/right-sidebar/files/remote-picker.tsx +177 -0
  197. package/src/app/right-sidebar/files/tree.tsx +224 -0
  198. package/src/app/right-sidebar/files/use-project-tree.test.ts +190 -0
  199. package/src/app/right-sidebar/files/use-project-tree.ts +268 -0
  200. package/src/app/right-sidebar/index.test.tsx +75 -0
  201. package/src/app/right-sidebar/index.tsx +395 -0
  202. package/src/app/right-sidebar/store.ts +15 -0
  203. package/src/app/right-sidebar/terminal/buffer.ts +65 -0
  204. package/src/app/right-sidebar/terminal/index.tsx +98 -0
  205. package/src/app/right-sidebar/terminal/persistent.tsx +122 -0
  206. package/src/app/right-sidebar/terminal/selection.ts +75 -0
  207. package/src/app/right-sidebar/terminal/use-terminal-session.ts +504 -0
  208. package/src/app/routes.ts +88 -0
  209. package/src/app/session/hooks/use-context-suggestions.ts +58 -0
  210. package/src/app/session/hooks/use-cwd-actions.ts +109 -0
  211. package/src/app/session/hooks/use-message-stream.ts +957 -0
  212. package/src/app/session/hooks/use-model-controls.test.tsx +198 -0
  213. package/src/app/session/hooks/use-model-controls.ts +106 -0
  214. package/src/app/session/hooks/use-nastech-config.ts +74 -0
  215. package/src/app/session/hooks/use-preview-routing.test.tsx +168 -0
  216. package/src/app/session/hooks/use-preview-routing.ts +223 -0
  217. package/src/app/session/hooks/use-prompt-actions.test.tsx +316 -0
  218. package/src/app/session/hooks/use-prompt-actions.ts +1030 -0
  219. package/src/app/session/hooks/use-route-resume.test.tsx +136 -0
  220. package/src/app/session/hooks/use-route-resume.ts +115 -0
  221. package/src/app/session/hooks/use-session-actions.test.tsx +119 -0
  222. package/src/app/session/hooks/use-session-actions.ts +885 -0
  223. package/src/app/session/hooks/use-session-state-cache.test.tsx +118 -0
  224. package/src/app/session/hooks/use-session-state-cache.ts +191 -0
  225. package/src/app/session-picker-overlay.tsx +32 -0
  226. package/src/app/session-switcher.tsx +107 -0
  227. package/src/app/settings/about-settings.tsx +173 -0
  228. package/src/app/settings/appearance-settings.tsx +162 -0
  229. package/src/app/settings/config-settings.tsx +384 -0
  230. package/src/app/settings/constants.ts +545 -0
  231. package/src/app/settings/credential-key-ui.tsx +373 -0
  232. package/src/app/settings/env-credentials.tsx +198 -0
  233. package/src/app/settings/env-var-actions-menu.tsx +136 -0
  234. package/src/app/settings/field-copy.ts +56 -0
  235. package/src/app/settings/gateway-settings.tsx +620 -0
  236. package/src/app/settings/helpers.test.ts +138 -0
  237. package/src/app/settings/helpers.ts +151 -0
  238. package/src/app/settings/index.tsx +237 -0
  239. package/src/app/settings/keys-settings.tsx +96 -0
  240. package/src/app/settings/mcp-settings.tsx +271 -0
  241. package/src/app/settings/model-settings.test.tsx +157 -0
  242. package/src/app/settings/model-settings.tsx +559 -0
  243. package/src/app/settings/notifications-settings.tsx +150 -0
  244. package/src/app/settings/primitives.tsx +115 -0
  245. package/src/app/settings/providers-settings.test.tsx +100 -0
  246. package/src/app/settings/providers-settings.tsx +258 -0
  247. package/src/app/settings/sessions-settings.tsx +276 -0
  248. package/src/app/settings/toolset-config-panel.test.tsx +289 -0
  249. package/src/app/settings/toolset-config-panel.tsx +449 -0
  250. package/src/app/settings/types.ts +42 -0
  251. package/src/app/settings/uninstall-section.tsx +185 -0
  252. package/src/app/settings/use-deep-link-highlight.ts +60 -0
  253. package/src/app/shell/app-shell.tsx +167 -0
  254. package/src/app/shell/gateway-menu-panel.tsx +150 -0
  255. package/src/app/shell/hooks/use-overlay-routing.ts +71 -0
  256. package/src/app/shell/hooks/use-status-snapshot.ts +57 -0
  257. package/src/app/shell/hooks/use-statusbar-items.tsx +403 -0
  258. package/src/app/shell/keybind-panel.tsx +220 -0
  259. package/src/app/shell/model-edit-submenu.test.tsx +84 -0
  260. package/src/app/shell/model-edit-submenu.tsx +245 -0
  261. package/src/app/shell/model-menu-panel.tsx +295 -0
  262. package/src/app/shell/sidebar-label.tsx +22 -0
  263. package/src/app/shell/statusbar-controls.tsx +185 -0
  264. package/src/app/shell/titlebar-controls.tsx +244 -0
  265. package/src/app/shell/titlebar.test.ts +26 -0
  266. package/src/app/shell/titlebar.ts +45 -0
  267. package/src/app/shell/use-group-registry.ts +39 -0
  268. package/src/app/skills/index.test.tsx +103 -0
  269. package/src/app/skills/index.tsx +371 -0
  270. package/src/app/types.ts +99 -0
  271. package/src/app/updates-overlay.tsx +369 -0
  272. package/src/components/Backdrop.tsx +114 -0
  273. package/src/components/assistant-ui/ansi-text.tsx +34 -0
  274. package/src/components/assistant-ui/clarify-tool.tsx +281 -0
  275. package/src/components/assistant-ui/directive-text.test.ts +39 -0
  276. package/src/components/assistant-ui/directive-text.tsx +389 -0
  277. package/src/components/assistant-ui/markdown-text.test.ts +204 -0
  278. package/src/components/assistant-ui/markdown-text.tsx +497 -0
  279. package/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
  280. package/src/components/assistant-ui/message-render-boundary.tsx +48 -0
  281. package/src/components/assistant-ui/streaming.test.tsx +739 -0
  282. package/src/components/assistant-ui/thread-list.tsx +307 -0
  283. package/src/components/assistant-ui/thread-virtualizer.tsx +512 -0
  284. package/src/components/assistant-ui/thread.tsx +1474 -0
  285. package/src/components/assistant-ui/todo-tool.tsx +109 -0
  286. package/src/components/assistant-ui/tool-approval-group.test.tsx +158 -0
  287. package/src/components/assistant-ui/tool-approval.test.tsx +81 -0
  288. package/src/components/assistant-ui/tool-approval.tsx +209 -0
  289. package/src/components/assistant-ui/tool-fallback-model.test.ts +66 -0
  290. package/src/components/assistant-ui/tool-fallback-model.ts +1368 -0
  291. package/src/components/assistant-ui/tool-fallback.tsx +466 -0
  292. package/src/components/assistant-ui/tooltip-icon-button.tsx +33 -0
  293. package/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
  294. package/src/components/assistant-ui/user-message-text.tsx +150 -0
  295. package/src/components/boot-failure-overlay.tsx +246 -0
  296. package/src/components/boot-failure-reauth.test.ts +100 -0
  297. package/src/components/boot-failure-reauth.ts +81 -0
  298. package/src/components/brand-mark.tsx +19 -0
  299. package/src/components/chat/activity-timer-text.tsx +24 -0
  300. package/src/components/chat/activity-timer.test.tsx +43 -0
  301. package/src/components/chat/activity-timer.ts +64 -0
  302. package/src/components/chat/code-card.tsx +78 -0
  303. package/src/components/chat/compact-markdown.tsx +113 -0
  304. package/src/components/chat/composer-dock.ts +31 -0
  305. package/src/components/chat/diff-lines.tsx +54 -0
  306. package/src/components/chat/disclosure-row.tsx +63 -0
  307. package/src/components/chat/generated-image-context.tsx +19 -0
  308. package/src/components/chat/generated-image-result.tsx +174 -0
  309. package/src/components/chat/image-generation-placeholder.tsx +279 -0
  310. package/src/components/chat/intro-copy.jsonl +75 -0
  311. package/src/components/chat/intro.tsx +182 -0
  312. package/src/components/chat/preview-attachment.tsx +125 -0
  313. package/src/components/chat/shiki-highlighter.tsx +107 -0
  314. package/src/components/chat/status-row.tsx +70 -0
  315. package/src/components/chat/status-section.tsx +42 -0
  316. package/src/components/chat/terminal-output.tsx +50 -0
  317. package/src/components/chat/zoomable-image.tsx +177 -0
  318. package/src/components/desktop-install-overlay.tsx +595 -0
  319. package/src/components/desktop-onboarding-overlay.test.tsx +100 -0
  320. package/src/components/desktop-onboarding-overlay.tsx +1286 -0
  321. package/src/components/error-boundary.tsx +77 -0
  322. package/src/components/gateway-connecting-overlay.test.tsx +143 -0
  323. package/src/components/gateway-connecting-overlay.tsx +183 -0
  324. package/src/components/haptics-provider.tsx +19 -0
  325. package/src/components/language-switcher.test.tsx +53 -0
  326. package/src/components/language-switcher.tsx +175 -0
  327. package/src/components/model-picker.tsx +340 -0
  328. package/src/components/model-visibility-dialog.tsx +155 -0
  329. package/src/components/notifications.tsx +196 -0
  330. package/src/components/page-loader.tsx +34 -0
  331. package/src/components/pane-shell/context.ts +14 -0
  332. package/src/components/pane-shell/index.ts +4 -0
  333. package/src/components/pane-shell/pane-shell.test.tsx +333 -0
  334. package/src/components/pane-shell/pane-shell.tsx +330 -0
  335. package/src/components/prompt-overlays.tsx +234 -0
  336. package/src/components/session-picker.tsx +108 -0
  337. package/src/components/status-dot.tsx +26 -0
  338. package/src/components/ui/action-status.tsx +25 -0
  339. package/src/components/ui/alert.tsx +53 -0
  340. package/src/components/ui/badge.tsx +35 -0
  341. package/src/components/ui/braille-spinner.tsx +61 -0
  342. package/src/components/ui/button.tsx +81 -0
  343. package/src/components/ui/checkbox.tsx +27 -0
  344. package/src/components/ui/codicon.tsx +20 -0
  345. package/src/components/ui/command.tsx +111 -0
  346. package/src/components/ui/confirm-dialog.tsx +109 -0
  347. package/src/components/ui/context-menu.tsx +141 -0
  348. package/src/components/ui/control.ts +25 -0
  349. package/src/components/ui/copy-button.test.tsx +36 -0
  350. package/src/components/ui/copy-button.tsx +229 -0
  351. package/src/components/ui/dialog.tsx +152 -0
  352. package/src/components/ui/disclosure-caret.tsx +20 -0
  353. package/src/components/ui/dropdown-menu.tsx +291 -0
  354. package/src/components/ui/error-state.tsx +50 -0
  355. package/src/components/ui/fade-text.tsx +110 -0
  356. package/src/components/ui/glyph-spinner.tsx +63 -0
  357. package/src/components/ui/input.tsx +22 -0
  358. package/src/components/ui/kbd.tsx +37 -0
  359. package/src/components/ui/loader.tsx +558 -0
  360. package/src/components/ui/log-view.tsx +17 -0
  361. package/src/components/ui/pagination.tsx +114 -0
  362. package/src/components/ui/popover.tsx +44 -0
  363. package/src/components/ui/scroll-area.tsx +43 -0
  364. package/src/components/ui/search-field.tsx +80 -0
  365. package/src/components/ui/segmented-control.tsx +51 -0
  366. package/src/components/ui/select.tsx +92 -0
  367. package/src/components/ui/separator.tsx +26 -0
  368. package/src/components/ui/sheet.tsx +116 -0
  369. package/src/components/ui/sidebar.tsx +674 -0
  370. package/src/components/ui/skeleton.tsx +7 -0
  371. package/src/components/ui/switch.tsx +49 -0
  372. package/src/components/ui/tabs.tsx +36 -0
  373. package/src/components/ui/text-tab.tsx +43 -0
  374. package/src/components/ui/textarea.tsx +11 -0
  375. package/src/components/ui/tool-icon.tsx +65 -0
  376. package/src/components/ui/tooltip.tsx +69 -0
  377. package/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
  378. package/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
  379. package/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
  380. package/src/global.d.ts +457 -0
  381. package/src/hooks/use-image-download.ts +85 -0
  382. package/src/hooks/use-media-query.ts +24 -0
  383. package/src/hooks/use-mobile.ts +3 -0
  384. package/src/hooks/use-resize-observer.ts +38 -0
  385. package/src/hooks/use-worktree-info.ts +68 -0
  386. package/src/i18n/catalog.ts +12 -0
  387. package/src/i18n/context.test.tsx +232 -0
  388. package/src/i18n/context.tsx +183 -0
  389. package/src/i18n/define-locale.ts +41 -0
  390. package/src/i18n/en.ts +1779 -0
  391. package/src/i18n/index.ts +20 -0
  392. package/src/i18n/ja.ts +1890 -0
  393. package/src/i18n/languages.test.ts +43 -0
  394. package/src/i18n/languages.ts +86 -0
  395. package/src/i18n/runtime.test.ts +75 -0
  396. package/src/i18n/runtime.ts +53 -0
  397. package/src/i18n/types.ts +1452 -0
  398. package/src/i18n/zh-hant.ts +1849 -0
  399. package/src/i18n/zh.ts +1923 -0
  400. package/src/lib/ansi.test.ts +123 -0
  401. package/src/lib/ansi.ts +175 -0
  402. package/src/lib/chat-messages.test.ts +708 -0
  403. package/src/lib/chat-messages.ts +885 -0
  404. package/src/lib/chat-runtime.test.ts +18 -0
  405. package/src/lib/chat-runtime.ts +335 -0
  406. package/src/lib/clipboard.ts +28 -0
  407. package/src/lib/commit-changelog.test.ts +114 -0
  408. package/src/lib/commit-changelog.ts +177 -0
  409. package/src/lib/completion-sound.ts +519 -0
  410. package/src/lib/desktop-fs.test.ts +116 -0
  411. package/src/lib/desktop-fs.ts +113 -0
  412. package/src/lib/desktop-slash-commands.test.ts +126 -0
  413. package/src/lib/desktop-slash-commands.ts +286 -0
  414. package/src/lib/embedded-images.test.ts +35 -0
  415. package/src/lib/embedded-images.ts +60 -0
  416. package/src/lib/external-link.test.tsx +168 -0
  417. package/src/lib/external-link.tsx +303 -0
  418. package/src/lib/gateway-events.test.ts +27 -0
  419. package/src/lib/gateway-events.ts +49 -0
  420. package/src/lib/gateway-ws-url.test.ts +78 -0
  421. package/src/lib/gateway-ws-url.ts +91 -0
  422. package/src/lib/generated-images.test.ts +97 -0
  423. package/src/lib/generated-images.ts +116 -0
  424. package/src/lib/haptics.ts +129 -0
  425. package/src/lib/icons.ts +203 -0
  426. package/src/lib/incremental-external-store-runtime.ts +188 -0
  427. package/src/lib/katex-memo.ts +260 -0
  428. package/src/lib/keybinds/actions.ts +125 -0
  429. package/src/lib/keybinds/combo.test.ts +86 -0
  430. package/src/lib/keybinds/combo.ts +169 -0
  431. package/src/lib/local-preview.ts +126 -0
  432. package/src/lib/markdown-code.test.ts +23 -0
  433. package/src/lib/markdown-code.ts +195 -0
  434. package/src/lib/markdown-preprocess.ts +386 -0
  435. package/src/lib/media.remote.test.ts +58 -0
  436. package/src/lib/media.ts +111 -0
  437. package/src/lib/model-status-label.test.ts +31 -0
  438. package/src/lib/model-status-label.ts +103 -0
  439. package/src/lib/mutable-ref.ts +6 -0
  440. package/src/lib/preview-targets.test.ts +27 -0
  441. package/src/lib/preview-targets.ts +63 -0
  442. package/src/lib/profile-color.ts +58 -0
  443. package/src/lib/provider-setup-errors.test.ts +26 -0
  444. package/src/lib/provider-setup-errors.ts +12 -0
  445. package/src/lib/query-client.ts +13 -0
  446. package/src/lib/remend-tail.test.ts +105 -0
  447. package/src/lib/remend-tail.ts +108 -0
  448. package/src/lib/runtime-readiness.test.ts +65 -0
  449. package/src/lib/runtime-readiness.ts +147 -0
  450. package/src/lib/session-export.ts +57 -0
  451. package/src/lib/session-search.test.ts +58 -0
  452. package/src/lib/session-search.ts +19 -0
  453. package/src/lib/session-source.ts +62 -0
  454. package/src/lib/speech-text.ts +35 -0
  455. package/src/lib/statusbar.ts +91 -0
  456. package/src/lib/storage.test.ts +25 -0
  457. package/src/lib/storage.ts +107 -0
  458. package/src/lib/todos.test.ts +35 -0
  459. package/src/lib/todos.ts +51 -0
  460. package/src/lib/tool-result-summary.test.ts +106 -0
  461. package/src/lib/tool-result-summary.ts +467 -0
  462. package/src/lib/update-copy.test.ts +38 -0
  463. package/src/lib/update-copy.ts +44 -0
  464. package/src/lib/use-enter-animation.ts +100 -0
  465. package/src/lib/utils.ts +6 -0
  466. package/src/lib/voice-playback.ts +128 -0
  467. package/src/lib/yolo-session.ts +26 -0
  468. package/src/main.tsx +43 -0
  469. package/src/nastech.test.ts +49 -0
  470. package/src/nastech.ts +718 -0
  471. package/src/store/activity.ts +100 -0
  472. package/src/store/boot.ts +91 -0
  473. package/src/store/clarify.test.ts +81 -0
  474. package/src/store/clarify.ts +69 -0
  475. package/src/store/command-palette.ts +20 -0
  476. package/src/store/compaction.test.ts +53 -0
  477. package/src/store/compaction.ts +38 -0
  478. package/src/store/completion-sound.ts +32 -0
  479. package/src/store/composer-input-history.test.ts +147 -0
  480. package/src/store/composer-input-history.ts +158 -0
  481. package/src/store/composer-queue.test.ts +148 -0
  482. package/src/store/composer-queue.ts +239 -0
  483. package/src/store/composer-status.test.ts +99 -0
  484. package/src/store/composer-status.ts +277 -0
  485. package/src/store/composer.test.ts +106 -0
  486. package/src/store/composer.ts +184 -0
  487. package/src/store/cron.ts +19 -0
  488. package/src/store/gateway.ts +290 -0
  489. package/src/store/haptics.ts +17 -0
  490. package/src/store/keybinds.ts +139 -0
  491. package/src/store/layout.ts +176 -0
  492. package/src/store/model-presets.test.ts +51 -0
  493. package/src/store/model-presets.ts +86 -0
  494. package/src/store/model-visibility.test.ts +37 -0
  495. package/src/store/model-visibility.ts +108 -0
  496. package/src/store/native-notifications.test.ts +192 -0
  497. package/src/store/native-notifications.ts +203 -0
  498. package/src/store/notifications.ts +165 -0
  499. package/src/store/onboarding.test.ts +372 -0
  500. package/src/store/onboarding.ts +866 -0
  501. package/src/store/panes.test.ts +146 -0
  502. package/src/store/panes.ts +145 -0
  503. package/src/store/preview.test.ts +135 -0
  504. package/src/store/preview.ts +466 -0
  505. package/src/store/profile.test.ts +89 -0
  506. package/src/store/profile.ts +365 -0
  507. package/src/store/prompts.test.ts +121 -0
  508. package/src/store/prompts.ts +115 -0
  509. package/src/store/session-switcher.test.ts +115 -0
  510. package/src/store/session-switcher.ts +128 -0
  511. package/src/store/session-sync.ts +25 -0
  512. package/src/store/session.test.ts +131 -0
  513. package/src/store/session.ts +255 -0
  514. package/src/store/subagents.test.ts +111 -0
  515. package/src/store/subagents.ts +260 -0
  516. package/src/store/thread-scroll.ts +46 -0
  517. package/src/store/todos.test.ts +47 -0
  518. package/src/store/todos.ts +64 -0
  519. package/src/store/tool-diffs.ts +23 -0
  520. package/src/store/tool-dismiss.ts +45 -0
  521. package/src/store/tool-view.ts +91 -0
  522. package/src/store/translucency.ts +38 -0
  523. package/src/store/updates.test.ts +77 -0
  524. package/src/store/updates.ts +315 -0
  525. package/src/store/voice-playback.ts +24 -0
  526. package/src/store/windows.test.ts +143 -0
  527. package/src/store/windows.ts +77 -0
  528. package/src/styles.css +1235 -0
  529. package/src/themes/color.ts +142 -0
  530. package/src/themes/context.tsx +339 -0
  531. package/src/themes/index.ts +3 -0
  532. package/src/themes/install.test.ts +119 -0
  533. package/src/themes/install.ts +95 -0
  534. package/src/themes/presets.test.ts +33 -0
  535. package/src/themes/presets.ts +293 -0
  536. package/src/themes/profile-theme.test.ts +41 -0
  537. package/src/themes/types.ts +66 -0
  538. package/src/themes/use-skin-command.ts +60 -0
  539. package/src/themes/user-themes.test.ts +63 -0
  540. package/src/themes/user-themes.ts +122 -0
  541. package/src/themes/vscode.test.ts +171 -0
  542. package/src/themes/vscode.ts +343 -0
  543. package/src/types/nastech.ts +646 -0
  544. package/src/vite-env.d.ts +1 -0
  545. package/tsconfig.json +25 -0
  546. package/vite.config.ts +56 -0
@@ -0,0 +1,449 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
+
3
+ import { PageLoader } from '@/components/page-loader'
4
+ import { Button } from '@/components/ui/button'
5
+ import { Input } from '@/components/ui/input'
6
+ import {
7
+ deleteEnvVar,
8
+ getActionStatus,
9
+ getToolsetConfig,
10
+ revealEnvVar,
11
+ runToolsetPostSetup,
12
+ selectToolsetProvider,
13
+ setEnvVar
14
+ } from '@/nastech'
15
+ import { useI18n } from '@/i18n'
16
+ import { Check, Loader2, Save, Terminal } from '@/lib/icons'
17
+ import { cn } from '@/lib/utils'
18
+ import { upsertDesktopActionTask } from '@/store/activity'
19
+ import { notify, notifyError } from '@/store/notifications'
20
+ import type { ActionStatusResponse, ToolEnvVar, ToolProvider, ToolsetConfig } from '@/types/nastech'
21
+
22
+ import { EnvVarActionsMenu, EnvVarActionsTrigger } from './env-var-actions-menu'
23
+ import { Pill } from './primitives'
24
+
25
+ interface ToolsetConfigPanelProps {
26
+ toolset: string
27
+ /** Called after a key is saved/cleared or a provider chosen, so the parent
28
+ * can refresh the "Configured / Needs keys" pill. */
29
+ onConfiguredChange?: () => void
30
+ }
31
+
32
+ function providerConfigured(provider: ToolProvider, envState: Record<string, boolean>): boolean {
33
+ if (provider.env_vars.length === 0) {
34
+ return true
35
+ }
36
+
37
+ return provider.env_vars.every(ev => envState[ev.key])
38
+ }
39
+
40
+ interface EnvVarFieldProps {
41
+ envVar: ToolEnvVar
42
+ isSet: boolean
43
+ onSaved: (key: string) => void
44
+ onCleared: (key: string) => void
45
+ }
46
+
47
+ function EnvVarField({ envVar, isSet, onSaved, onCleared }: EnvVarFieldProps) {
48
+ const { t } = useI18n()
49
+ const copy = t.settings.toolsets
50
+ const [editing, setEditing] = useState(false)
51
+ const [value, setValue] = useState('')
52
+ const [revealed, setRevealed] = useState<string | null>(null)
53
+ const [busy, setBusy] = useState(false)
54
+
55
+ async function handleSave() {
56
+ if (!value) {
57
+ return
58
+ }
59
+
60
+ setBusy(true)
61
+
62
+ try {
63
+ await setEnvVar(envVar.key, value)
64
+ setEditing(false)
65
+ setValue('')
66
+ onSaved(envVar.key)
67
+ notify({ kind: 'success', title: copy.savedTitle, message: copy.savedMessage(envVar.key) })
68
+ } catch (err) {
69
+ notifyError(err, copy.failedSave(envVar.key))
70
+ } finally {
71
+ setBusy(false)
72
+ }
73
+ }
74
+
75
+ async function handleClear() {
76
+ if (!window.confirm(copy.removeConfirm(envVar.key))) {
77
+ return
78
+ }
79
+
80
+ setBusy(true)
81
+
82
+ try {
83
+ await deleteEnvVar(envVar.key)
84
+ setRevealed(null)
85
+ onCleared(envVar.key)
86
+ notify({ kind: 'success', title: copy.removedTitle, message: copy.removedMessage(envVar.key) })
87
+ } catch (err) {
88
+ notifyError(err, copy.failedRemove(envVar.key))
89
+ } finally {
90
+ setBusy(false)
91
+ }
92
+ }
93
+
94
+ async function handleReveal() {
95
+ if (revealed !== null) {
96
+ setRevealed(null)
97
+
98
+ return
99
+ }
100
+
101
+ try {
102
+ const result = await revealEnvVar(envVar.key)
103
+ setRevealed(result.value)
104
+ } catch (err) {
105
+ notifyError(err, copy.failedReveal(envVar.key))
106
+ }
107
+ }
108
+
109
+ return (
110
+ <div className="grid gap-2 rounded-lg bg-background/55 p-2.5">
111
+ <div className="flex flex-wrap items-start justify-between gap-2">
112
+ <div className="min-w-0">
113
+ <div className="flex flex-wrap items-center gap-2">
114
+ <span className="font-mono text-xs font-medium">{envVar.key}</span>
115
+ <Pill tone={isSet ? 'primary' : 'muted'}>
116
+ {isSet && <Check className="size-3" />}
117
+ {isSet ? copy.set : copy.notSet}
118
+ </Pill>
119
+ </div>
120
+ {envVar.prompt && envVar.prompt !== envVar.key && (
121
+ <p className="mt-0.5 text-[0.7rem] text-muted-foreground">{envVar.prompt}</p>
122
+ )}
123
+ </div>
124
+ {!editing && (
125
+ <EnvVarActionsMenu
126
+ clearDisabled={busy}
127
+ docsUrl={envVar.url}
128
+ isRevealed={revealed !== null}
129
+ isSet={isSet}
130
+ label={envVar.key}
131
+ onClear={() => void handleClear()}
132
+ onEdit={() => setEditing(true)}
133
+ onReveal={() => void handleReveal()}
134
+ >
135
+ <EnvVarActionsTrigger label={envVar.key} onClick={event => event.stopPropagation()} />
136
+ </EnvVarActionsMenu>
137
+ )}
138
+ </div>
139
+
140
+ {isSet && revealed !== null && (
141
+ <div className="rounded-md bg-background px-2.5 py-1.5 font-mono text-xs text-foreground">
142
+ {revealed || '---'}
143
+ </div>
144
+ )}
145
+
146
+ {editing && (
147
+ <div className="flex flex-wrap items-center gap-2">
148
+ <Input
149
+ autoFocus
150
+ className="min-w-52 flex-1 font-mono"
151
+ onChange={e => setValue(e.target.value)}
152
+ placeholder={envVar.prompt || envVar.key}
153
+ type={envVar.default ? 'text' : 'password'}
154
+ value={value}
155
+ />
156
+ <Button disabled={busy || !value} onClick={() => void handleSave()} size="sm">
157
+ {busy ? <Loader2 className="size-3.5 animate-spin" /> : <Save />}
158
+ {t.common.save}
159
+ </Button>
160
+ <Button onClick={() => setEditing(false)} size="sm" variant="text">
161
+ {t.common.cancel}
162
+ </Button>
163
+ </div>
164
+ )}
165
+ </div>
166
+ )
167
+ }
168
+
169
+ interface PostSetupRunnerProps {
170
+ toolset: string
171
+ /** The provider's post_setup hook key (e.g. "camofox", "ddgs"). */
172
+ postSetupKey: string
173
+ /** Refresh the parent config after the install finishes (a backend may now
174
+ * report itself configured). */
175
+ onComplete?: () => void
176
+ }
177
+
178
+ /**
179
+ * Runs a provider's post-setup install hook (npm / pip / binary) via the
180
+ * `/api/tools/toolsets/{name}/post-setup` spawn-action and tails the resulting
181
+ * log inline — the GUI equivalent of the install step `NASTECH tools` runs
182
+ * after you pick a backend that needs extra dependencies.
183
+ */
184
+ function PostSetupRunner({ toolset, postSetupKey, onComplete }: PostSetupRunnerProps) {
185
+ const { t } = useI18n()
186
+ const copy = t.settings.toolsets
187
+ const [running, setRunning] = useState(false)
188
+ const [status, setStatus] = useState<ActionStatusResponse | null>(null)
189
+ // Guard against overlapping polls / state updates after unmount.
190
+ const activeRef = useRef(false)
191
+
192
+ useEffect(() => {
193
+ return () => {
194
+ activeRef.current = false
195
+ }
196
+ }, [])
197
+
198
+ const run = useCallback(async () => {
199
+ setRunning(true)
200
+ setStatus(null)
201
+ activeRef.current = true
202
+
203
+ try {
204
+ const started = await runToolsetPostSetup(toolset, postSetupKey)
205
+
206
+ // The spawn endpoint reports ok:false if it couldn't launch the action
207
+ // (e.g. unknown key, server-side spawn failure). Don't poll a status
208
+ // that will never exist — surface the failure and stop.
209
+ if (!started.ok) {
210
+ notifyError(new Error('spawn failed'), copy.postSetupFailed(postSetupKey))
211
+
212
+ return
213
+ }
214
+
215
+ let last: ActionStatusResponse | null = null
216
+
217
+ // Mirror command-center's runSystemAction poll loop: poll the action log
218
+ // until it exits (or we hit the attempt ceiling), feeding the global
219
+ // activity rail as we go.
220
+ for (let attempt = 0; attempt < 150 && activeRef.current; attempt += 1) {
221
+ await new Promise(resolve => window.setTimeout(resolve, 1200))
222
+
223
+ if (!activeRef.current) {
224
+ break
225
+ }
226
+
227
+ const polled = await getActionStatus(started.name, 300)
228
+ last = polled
229
+ setStatus(polled)
230
+ upsertDesktopActionTask(polled)
231
+
232
+ if (!polled.running) {
233
+ break
234
+ }
235
+ }
236
+
237
+ if (activeRef.current) {
238
+ const ok = last?.exit_code === 0
239
+
240
+ notify(
241
+ ok
242
+ ? {
243
+ kind: 'success',
244
+ title: copy.postSetupCompleteTitle,
245
+ message: copy.postSetupCompleteMessage(postSetupKey)
246
+ }
247
+ : { kind: 'error', title: copy.postSetupErrorTitle, message: copy.postSetupErrorMessage(postSetupKey) }
248
+ )
249
+ onComplete?.()
250
+ }
251
+ } catch (err) {
252
+ if (activeRef.current) {
253
+ notifyError(err, copy.postSetupFailed(postSetupKey))
254
+ }
255
+ } finally {
256
+ if (activeRef.current) {
257
+ setRunning(false)
258
+ }
259
+ }
260
+ }, [toolset, postSetupKey, onComplete, copy])
261
+
262
+ return (
263
+ <div className="grid gap-2 rounded-lg bg-background/55 p-2.5">
264
+ <div className="flex flex-wrap items-center justify-between gap-2">
265
+ <div className="min-w-0">
266
+ <p className="text-[0.72rem] text-muted-foreground">{copy.postSetupHint(postSetupKey)}</p>
267
+ </div>
268
+ <Button disabled={running} onClick={() => void run()} size="sm">
269
+ {running ? <Loader2 className="size-3.5 animate-spin" /> : <Terminal className="size-3.5" />}
270
+ {running ? copy.postSetupRunning : copy.postSetupRun}
271
+ </Button>
272
+ </div>
273
+
274
+ {status && (status.lines.length > 0 || status.running) && (
275
+ <pre className="max-h-48 overflow-y-auto rounded-md bg-background px-2.5 py-1.5 font-mono text-[0.7rem] leading-relaxed text-muted-foreground whitespace-pre-wrap">
276
+ {status.lines.length > 0 ? status.lines.join('\n') : copy.postSetupStarting}
277
+ </pre>
278
+ )}
279
+ </div>
280
+ )
281
+ }
282
+
283
+ export function ToolsetConfigPanel({ toolset, onConfiguredChange }: ToolsetConfigPanelProps) {
284
+ const { t } = useI18n()
285
+ const copy = t.settings.toolsets
286
+ const [cfg, setCfg] = useState<ToolsetConfig | null>(null)
287
+ const [loading, setLoading] = useState(true)
288
+ const [selecting, setSelecting] = useState<string | null>(null)
289
+ const [activeProvider, setActiveProvider] = useState<string | null>(null)
290
+ // Live per-key set/unset state, seeded from the endpoint then patched locally.
291
+ const [envState, setEnvState] = useState<Record<string, boolean>>({})
292
+
293
+ const refresh = useCallback(async () => {
294
+ setLoading(true)
295
+
296
+ try {
297
+ const next = await getToolsetConfig(toolset)
298
+ setCfg(next)
299
+ const seeded: Record<string, boolean> = {}
300
+
301
+ for (const provider of next.providers) {
302
+ for (const ev of provider.env_vars) {
303
+ seeded[ev.key] = ev.is_set
304
+ }
305
+ }
306
+
307
+ setEnvState(seeded)
308
+ } catch (err) {
309
+ notifyError(err, copy.failedLoad)
310
+ } finally {
311
+ setLoading(false)
312
+ }
313
+ }, [toolset])
314
+
315
+ useEffect(() => {
316
+ void refresh()
317
+ }, [refresh])
318
+
319
+ const providers = useMemo(() => cfg?.providers ?? [], [cfg])
320
+
321
+ // Default the expanded provider to the one actually active in config
322
+ // (`is_active` / `cfg.active_provider`, mirroring the CLI picker), then the
323
+ // first fully-configured provider, else the first provider. Without this the
324
+ // panel highlighted the first keyless provider (e.g. NasTech Portal) even when
325
+ // the user had already selected another (e.g. DuckDuckGo).
326
+ useEffect(() => {
327
+ if (activeProvider || providers.length === 0) {
328
+ return
329
+ }
330
+
331
+ const selected =
332
+ providers.find(p => p.is_active) ??
333
+ (cfg?.active_provider ? providers.find(p => p.name === cfg.active_provider) : undefined) ??
334
+ providers.find(p => providerConfigured(p, envState)) ??
335
+ providers[0]
336
+
337
+ setActiveProvider(selected.name)
338
+ }, [activeProvider, providers, envState, cfg])
339
+
340
+ async function handleSelect(provider: ToolProvider) {
341
+ setActiveProvider(provider.name)
342
+ setSelecting(provider.name)
343
+
344
+ try {
345
+ await selectToolsetProvider(toolset, provider.name)
346
+ notify({ kind: 'success', title: copy.selectedTitle, message: copy.selectedMessage(provider.name) })
347
+ onConfiguredChange?.()
348
+ } catch (err) {
349
+ notifyError(err, copy.failedSelect(provider.name))
350
+ } finally {
351
+ setSelecting(null)
352
+ }
353
+ }
354
+
355
+ function patchEnv(key: string, isSet: boolean) {
356
+ setEnvState(c => ({ ...c, [key]: isSet }))
357
+ onConfiguredChange?.()
358
+ }
359
+
360
+ const emptyMessage = useMemo(() => {
361
+ if (loading || !cfg) {
362
+ return null
363
+ }
364
+
365
+ if (!cfg.has_category) {
366
+ return copy.noProviderOptions
367
+ }
368
+
369
+ if (providers.length === 0) {
370
+ return copy.noProviders
371
+ }
372
+
373
+ return null
374
+ }, [cfg, copy, loading, providers.length])
375
+
376
+ if (loading) {
377
+ return <PageLoader className="min-h-32" label={copy.loadingConfig} />
378
+ }
379
+
380
+ if (emptyMessage) {
381
+ return <p className="px-1 py-3 text-xs text-muted-foreground">{emptyMessage}</p>
382
+ }
383
+
384
+ return (
385
+ <div className="mt-3 grid gap-2">
386
+ {providers.map(provider => {
387
+ const isActive = activeProvider === provider.name
388
+ const configured = providerConfigured(provider, envState)
389
+
390
+ return (
391
+ <div className="overflow-hidden rounded-xl bg-background/60" key={provider.name}>
392
+ <button
393
+ aria-pressed={isActive}
394
+ className={cn(
395
+ 'flex w-full items-center justify-between gap-3 px-3 py-2.5 text-left transition hover:bg-accent/50',
396
+ isActive && 'bg-accent/40'
397
+ )}
398
+ onClick={() => void handleSelect(provider)}
399
+ type="button"
400
+ >
401
+ <span className="flex min-w-0 items-center gap-2">
402
+ <span className="truncate text-sm font-medium">{provider.name}</span>
403
+ {provider.badge && <Pill>{provider.badge}</Pill>}
404
+ {configured && (
405
+ <Pill tone="primary">
406
+ <Check className="size-3" />
407
+ {copy.ready}
408
+ </Pill>
409
+ )}
410
+ </span>
411
+ {selecting === provider.name && <Loader2 className="size-3.5 shrink-0 animate-spin" />}
412
+ </button>
413
+
414
+ {isActive && (
415
+ <div className="grid gap-2 bg-muted/20 p-3">
416
+ {provider.tag && <p className="text-[0.72rem] text-muted-foreground">{provider.tag}</p>}
417
+ {provider.requires_nastech_auth && (
418
+ <p className="text-[0.72rem] text-muted-foreground">
419
+ {copy.nousIncluded}
420
+ </p>
421
+ )}
422
+ {provider.env_vars.length === 0 ? (
423
+ <p className="text-[0.72rem] text-muted-foreground">{copy.noApiKeyRequired}</p>
424
+ ) : (
425
+ provider.env_vars.map(ev => (
426
+ <EnvVarField
427
+ envVar={ev}
428
+ isSet={Boolean(envState[ev.key])}
429
+ key={ev.key}
430
+ onCleared={key => patchEnv(key, false)}
431
+ onSaved={key => patchEnv(key, true)}
432
+ />
433
+ ))
434
+ )}
435
+ {provider.post_setup && (
436
+ <PostSetupRunner
437
+ onComplete={() => void refresh()}
438
+ postSetupKey={provider.post_setup}
439
+ toolset={toolset}
440
+ />
441
+ )}
442
+ </div>
443
+ )}
444
+ </div>
445
+ )
446
+ })}
447
+ </div>
448
+ )
449
+ }
@@ -0,0 +1,42 @@
1
+ import type { Dispatch, SetStateAction } from 'react'
2
+
3
+ import type { NasTechGateway } from '@/nastech'
4
+ import type { IconComponent } from '@/lib/icons'
5
+ import type { EnvVarInfo } from '@/types/nastech'
6
+
7
+ export type SettingsView = 'about' | 'gateway' | 'keys' | 'mcp' | 'providers' | 'sessions' | `config:${string}`
8
+ export type EnvPatch = Partial<Pick<EnvVarInfo, 'is_set' | 'redacted_value'>>
9
+
10
+ export interface SettingsPageProps {
11
+ gateway?: NasTechGateway | null
12
+ onClose: () => void
13
+ onConfigSaved?: () => void
14
+ onMainModelChanged?: (provider: string, model: string) => void
15
+ }
16
+
17
+ export interface ProviderGroup {
18
+ name: string
19
+ priority: number
20
+ entries: [string, EnvVarInfo][]
21
+ hasAnySet: boolean
22
+ }
23
+
24
+ export interface DesktopConfigSection {
25
+ id: string
26
+ label: string
27
+ icon: IconComponent
28
+ keys: string[]
29
+ }
30
+
31
+ export interface EnvRowProps {
32
+ varKey: string
33
+ info: EnvVarInfo
34
+ edits: Record<string, string>
35
+ revealed: Record<string, string>
36
+ saving: string | null
37
+ setEdits: Dispatch<SetStateAction<Record<string, string>>>
38
+ onSave: (key: string) => void
39
+ onClear: (key: string) => void
40
+ onReveal: (key: string) => void
41
+ compact?: boolean
42
+ }
@@ -0,0 +1,185 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ import { Button } from '@/components/ui/button'
4
+ import { AlertTriangle, Loader2, Trash2 } from '@/lib/icons'
5
+ import { cn } from '@/lib/utils'
6
+ import type { DesktopUninstallMode, DesktopUninstallSummary } from '@/global'
7
+
8
+ import { SectionHeading } from './primitives'
9
+
10
+ interface ModeOption {
11
+ mode: DesktopUninstallMode
12
+ title: string
13
+ description: string
14
+ /** Shown in the confirm step so people know exactly what disappears. */
15
+ consequence: string
16
+ /** True when the option removes the Python agent (hidden if no agent). */
17
+ needsAgent: boolean
18
+ }
19
+
20
+ const OPTIONS: ModeOption[] = [
21
+ {
22
+ mode: 'gui',
23
+ title: 'Uninstall Chat GUI only',
24
+ description: 'Remove this desktop app. The NasTech agent, your config, and chats all stay.',
25
+ consequence: 'the desktop Chat GUI (this app and its data)',
26
+ needsAgent: false
27
+ },
28
+ {
29
+ mode: 'lite',
30
+ title: 'Uninstall GUI + agent, keep my data',
31
+ description: 'Remove the app and the NasTech agent, but keep config, chats, and secrets for a future reinstall.',
32
+ consequence: 'the Chat GUI and the NasTech agent (config, chats, and secrets are kept)',
33
+ needsAgent: true
34
+ },
35
+ {
36
+ mode: 'full',
37
+ title: 'Uninstall everything',
38
+ description: 'Remove the app, the agent, and all user data — config, chats, scheduled jobs, secrets, logs.',
39
+ consequence: 'EVERYTHING — the Chat GUI, the NasTech agent, and all of your config, chats, secrets, and logs',
40
+ // full removes the agent (and user data), so it's an agent-removing option:
41
+ // hide it on a lite client with no local agent, same as lite. A lite client
42
+ // connecting to a remote backend has no local agent OR local user data the
43
+ // GUI installed, so gui-only is the correct (and only) option there.
44
+ needsAgent: true
45
+ }
46
+ ]
47
+
48
+ export function UninstallSection() {
49
+ const [summary, setSummary] = useState<DesktopUninstallSummary | null>(null)
50
+ const [loading, setLoading] = useState(true)
51
+ const [pending, setPending] = useState<DesktopUninstallMode | null>(null)
52
+ const [running, setRunning] = useState(false)
53
+ const [error, setError] = useState<string | null>(null)
54
+
55
+ useEffect(() => {
56
+ let alive = true
57
+ const bridge = window.NASTECHDesktop?.uninstall
58
+ if (!bridge) {
59
+ setLoading(false)
60
+ return
61
+ }
62
+ void bridge
63
+ .summary()
64
+ .then(result => {
65
+ if (alive) {
66
+ setSummary(result)
67
+ }
68
+ })
69
+ .catch(() => {
70
+ // Non-fatal — we degrade to offering the GUI-only option.
71
+ })
72
+ .finally(() => {
73
+ if (alive) {
74
+ setLoading(false)
75
+ }
76
+ })
77
+ return () => {
78
+ alive = false
79
+ }
80
+ }, [])
81
+
82
+ const bridge = window.NASTECHDesktop?.uninstall
83
+ if (!bridge) {
84
+ return null
85
+ }
86
+
87
+ // Gate the agent-removing options on whether an agent is actually present.
88
+ // A future lite client that ships without the bundled agent shows GUI-only.
89
+ const agentInstalled = summary?.agent_installed ?? false
90
+ const visibleOptions = OPTIONS.filter(opt => agentInstalled || !opt.needsAgent)
91
+
92
+ const handleConfirm = async () => {
93
+ if (!pending) {
94
+ return
95
+ }
96
+ setRunning(true)
97
+ setError(null)
98
+ try {
99
+ const result = await bridge.run(pending)
100
+ if (!result.ok) {
101
+ setError(result.message || result.error || 'Uninstall could not start.')
102
+ setRunning(false)
103
+ setPending(null)
104
+ }
105
+ // On success the app quits shortly; keep the spinner up until it does.
106
+ } catch (err) {
107
+ setError(err instanceof Error ? err.message : String(err))
108
+ setRunning(false)
109
+ setPending(null)
110
+ }
111
+ }
112
+
113
+ const pendingOption = OPTIONS.find(opt => opt.mode === pending) ?? null
114
+
115
+ return (
116
+ <div className="mx-auto mt-8 w-full max-w-2xl">
117
+ <SectionHeading icon={AlertTriangle} title="Danger zone" />
118
+
119
+ <div className="rounded-xl border border-destructive/30 bg-destructive/5 px-4 py-3">
120
+ {loading ? (
121
+ <div className="flex items-center gap-2 py-2 text-sm text-muted-foreground">
122
+ <Loader2 className="size-3.5 animate-spin" />
123
+ Checking what&apos;s installed…
124
+ </div>
125
+ ) : pendingOption ? (
126
+ <div>
127
+ <p className="text-sm font-medium text-destructive">Confirm uninstall</p>
128
+ <p className="mt-1 text-xs text-muted-foreground">
129
+ This removes {pendingOption.consequence}. This can&apos;t be undone.
130
+ </p>
131
+ {summary?.running_app_path && (
132
+ <p className="mt-1 font-mono text-[0.68rem] text-muted-foreground/60">
133
+ App: {summary.running_app_path}
134
+ </p>
135
+ )}
136
+ {error && <p className="mt-2 text-xs text-destructive">{error}</p>}
137
+ <div className="mt-3 flex flex-wrap items-center gap-3">
138
+ <Button
139
+ disabled={running}
140
+ onClick={() => void handleConfirm()}
141
+ size="sm"
142
+ variant="destructive"
143
+ >
144
+ {running && <Loader2 className="size-3 animate-spin" />}
145
+ {running ? 'Uninstalling…' : 'Yes, uninstall'}
146
+ </Button>
147
+ <Button disabled={running} onClick={() => setPending(null)} size="sm" variant="text">
148
+ Cancel
149
+ </Button>
150
+ </div>
151
+ </div>
152
+ ) : (
153
+ <div className="flex flex-col gap-2">
154
+ <p className="text-sm font-medium">Uninstall NasTech</p>
155
+ <p className="text-xs text-muted-foreground">
156
+ Choose how much to remove. The app closes to finish the job; reopen the installer any time to come back.
157
+ </p>
158
+ <div className="mt-1 flex flex-col gap-2">
159
+ {visibleOptions.map(opt => (
160
+ <button
161
+ className={cn(
162
+ 'flex items-start gap-3 rounded-lg border border-border/60 bg-background/40 px-3 py-2.5 text-left transition',
163
+ 'hover:border-destructive/40 hover:bg-destructive/5'
164
+ )}
165
+ key={opt.mode}
166
+ onClick={() => {
167
+ setError(null)
168
+ setPending(opt.mode)
169
+ }}
170
+ type="button"
171
+ >
172
+ <Trash2 className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
173
+ <span className="min-w-0">
174
+ <span className="block text-sm font-medium text-foreground">{opt.title}</span>
175
+ <span className="mt-0.5 block text-xs text-muted-foreground">{opt.description}</span>
176
+ </span>
177
+ </button>
178
+ ))}
179
+ </div>
180
+ </div>
181
+ )}
182
+ </div>
183
+ </div>
184
+ )
185
+ }