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,64 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+
3
+ // Module-level registry so timers survive component unmount/remount (e.g.
4
+ // when a tool row scrolls out and back). Keyed by caller-supplied timerKey;
5
+ // anonymous timers (no key) start fresh each mount.
6
+ const startedAtByKey = new Map<string, number>()
7
+
8
+ function startedAt(key?: string): number {
9
+ if (!key) {
10
+ return Date.now()
11
+ }
12
+
13
+ const existing = startedAtByKey.get(key)
14
+
15
+ if (existing !== undefined) {
16
+ return existing
17
+ }
18
+
19
+ const now = Date.now()
20
+ startedAtByKey.set(key, now)
21
+
22
+ return now
23
+ }
24
+
25
+ export function formatElapsed(seconds: number): string {
26
+ if (seconds < 60) {
27
+ return `${seconds}s`
28
+ }
29
+
30
+ return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`
31
+ }
32
+
33
+ export function useElapsedSeconds(active = true, timerKey?: string): number {
34
+ const start = useRef(startedAt(timerKey))
35
+ const lastKey = useRef(timerKey)
36
+ const [elapsed, setElapsed] = useState(() => Math.max(0, Math.floor((Date.now() - start.current) / 1000)))
37
+
38
+ if (lastKey.current !== timerKey) {
39
+ start.current = startedAt(timerKey)
40
+ lastKey.current = timerKey
41
+ }
42
+
43
+ useEffect(() => {
44
+ if (!active) {
45
+ return
46
+ }
47
+
48
+ if (timerKey) {
49
+ start.current = startedAt(timerKey)
50
+ }
51
+
52
+ const tick = () => setElapsed(Math.max(0, Math.floor((Date.now() - start.current) / 1000)))
53
+ tick()
54
+ const id = window.setInterval(tick, 1000)
55
+
56
+ return () => window.clearInterval(id)
57
+ }, [active, timerKey])
58
+
59
+ return elapsed
60
+ }
61
+
62
+ export function __resetElapsedTimerRegistryForTests() {
63
+ startedAtByKey.clear()
64
+ }
@@ -0,0 +1,78 @@
1
+ import * as React from 'react'
2
+
3
+ import { Codicon, type CodiconProps } from '@/components/ui/codicon'
4
+ import { cn } from '@/lib/utils'
5
+
6
+ /**
7
+ * Rounded-card shell for fenced code (and any equivalent: diffs, raw payloads,
8
+ * etc.) sized for the conversation column. Mirrors the expanded tool-row
9
+ * pattern so code blocks read as the same family of artifact.
10
+ */
11
+ function CodeCard({ className, ...props }: React.ComponentProps<'div'>) {
12
+ return (
13
+ <div
14
+ className={cn(
15
+ 'min-w-0 max-w-full overflow-hidden rounded-[0.625rem] border border-border text-[length:var(--conversation-tool-font-size)] text-muted-foreground',
16
+ className
17
+ )}
18
+ data-slot="code-card"
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ function CodeCardHeader({ className, ...props }: React.ComponentProps<'div'>) {
25
+ return (
26
+ <div
27
+ className={cn('flex items-center justify-between gap-2 border-b border-border px-2 py-1.5', className)}
28
+ data-slot="code-card-header"
29
+ {...props}
30
+ />
31
+ )
32
+ }
33
+
34
+ function CodeCardTitle({ className, children, ...props }: React.ComponentProps<'span'>) {
35
+ return (
36
+ <span
37
+ className={cn(
38
+ 'flex min-w-0 items-center gap-1.5 truncate text-[length:var(--conversation-tool-font-size)] font-medium leading-(--conversation-line-height) text-foreground/80',
39
+ className
40
+ )}
41
+ data-slot="code-card-title"
42
+ {...props}
43
+ >
44
+ {children}
45
+ </span>
46
+ )
47
+ }
48
+
49
+ function CodeCardIcon({ className, ...props }: CodiconProps) {
50
+ return (
51
+ <Codicon
52
+ className={cn('shrink-0 text-[0.875rem] leading-none text-muted-foreground', className)}
53
+ data-slot="code-card-icon"
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+
59
+ function CodeCardSubtitle({ className, ...props }: React.ComponentProps<'span'>) {
60
+ return (
61
+ <span className={cn('font-normal text-muted-foreground', className)} data-slot="code-card-subtitle" {...props} />
62
+ )
63
+ }
64
+
65
+ function CodeCardBody({ className, ...props }: React.ComponentProps<'div'>) {
66
+ return (
67
+ <div
68
+ className={cn(
69
+ 'p-1.5 font-mono text-[0.7rem] leading-relaxed text-foreground/90 [&_pre]:m-0 [&_pre]:overflow-x-auto [&_pre]:bg-transparent! [&_pre]:px-2 [&_pre]:py-1.5 [&_pre]:font-mono [&_pre]:leading-relaxed',
70
+ className
71
+ )}
72
+ data-slot="code-card-body"
73
+ {...props}
74
+ />
75
+ )
76
+ }
77
+
78
+ export { CodeCard, CodeCardBody, CodeCardHeader, CodeCardIcon, CodeCardSubtitle, CodeCardTitle }
@@ -0,0 +1,113 @@
1
+ import type { ComponentProps, ElementType, FC } from 'react'
2
+ import { Streamdown } from 'streamdown'
3
+
4
+ import { ExternalLink, ExternalLinkIcon } from '@/lib/external-link'
5
+ import { cn } from '@/lib/utils'
6
+
7
+ // Compact markdown renderer for tool detail bodies. Same Streamdown pipeline
8
+ // as the file preview pane, with tighter typography and external-link routing
9
+ // so tools that emit markdown (tables, headings, links) render properly
10
+ // instead of being dumped as raw text.
11
+
12
+ const TAG_CLASSES = {
13
+ blockquote: 'mt-2 mb-2 border-l-2 border-border/70 pl-2.5 italic text-muted-foreground/85',
14
+ h1: 'mt-3 mb-1.5 text-sm font-semibold tracking-tight text-foreground first:mt-0',
15
+ h2: 'mt-3 mb-1.5 text-[0.82rem] font-semibold tracking-tight text-foreground first:mt-0',
16
+ h3: 'mt-2.5 mb-1 text-[0.78rem] font-semibold text-foreground first:mt-0',
17
+ h4: 'mt-2 mb-1 text-[0.74rem] font-semibold text-foreground first:mt-0',
18
+ hr: 'my-2 border-border/50',
19
+ li: 'marker:text-muted-foreground/60',
20
+ ol: 'mb-2 list-decimal pl-5 last:mb-0',
21
+ p: 'mb-1.5 leading-relaxed last:mb-0',
22
+ pre: 'mb-2 overflow-x-auto rounded-md border border-border/60 bg-background/70 p-2 font-mono text-[0.7rem] leading-[1.55] last:mb-0',
23
+ td: 'px-2 py-1 align-top leading-snug',
24
+ th: 'px-2 py-1 text-left text-[0.62rem] font-semibold uppercase tracking-[0.08em] text-muted-foreground/80',
25
+ thead: 'bg-muted/40',
26
+ ul: 'mb-2 list-disc pl-5 last:mb-0'
27
+ } as const
28
+
29
+ function tagged<T extends keyof typeof TAG_CLASSES>(Tag: T) {
30
+ const Component = (({ className, ...rest }: ComponentProps<T>) => {
31
+ const Element = Tag as ElementType
32
+
33
+ return <Element className={cn(TAG_CLASSES[Tag], className)} {...rest} />
34
+ }) as FC<ComponentProps<T>>
35
+
36
+ Component.displayName = `Md.${Tag}`
37
+
38
+ return Component
39
+ }
40
+
41
+ function MarkdownAnchor({ children, className, href, ...rest }: ComponentProps<'a'>) {
42
+ if (!href || !/^https?:\/\//i.test(href)) {
43
+ return (
44
+ <a
45
+ className={cn('font-medium underline underline-offset-4 decoration-current/20', className)}
46
+ href={href}
47
+ {...rest}
48
+ >
49
+ {children}
50
+ </a>
51
+ )
52
+ }
53
+
54
+ return (
55
+ <ExternalLink className={cn('decoration-current/20', className)} href={href} showExternalIcon={false}>
56
+ {children}
57
+ <ExternalLinkIcon />
58
+ </ExternalLink>
59
+ )
60
+ }
61
+
62
+ function MarkdownCode({ className, ...rest }: ComponentProps<'code'>) {
63
+ return (
64
+ <code
65
+ className={cn('rounded bg-muted/80 px-1 py-px font-mono text-[0.86em] text-muted-foreground', className)}
66
+ {...rest}
67
+ />
68
+ )
69
+ }
70
+
71
+ function MarkdownTable({ className, ...rest }: ComponentProps<'table'>) {
72
+ return (
73
+ <div className="mb-2 max-w-full overflow-x-auto rounded-md border border-border/60 last:mb-0">
74
+ <table
75
+ className={cn(
76
+ 'w-full border-collapse text-[0.72rem] [&_tr]:border-b [&_tr]:border-border/50 last:[&_tr]:border-0',
77
+ className
78
+ )}
79
+ {...rest}
80
+ />
81
+ </div>
82
+ )
83
+ }
84
+
85
+ const COMPONENTS = {
86
+ a: MarkdownAnchor,
87
+ blockquote: tagged('blockquote'),
88
+ code: MarkdownCode,
89
+ h1: tagged('h1'),
90
+ h2: tagged('h2'),
91
+ h3: tagged('h3'),
92
+ h4: tagged('h4'),
93
+ hr: tagged('hr'),
94
+ li: tagged('li'),
95
+ ol: tagged('ol'),
96
+ p: tagged('p'),
97
+ pre: tagged('pre'),
98
+ table: MarkdownTable,
99
+ td: tagged('td'),
100
+ th: tagged('th'),
101
+ thead: tagged('thead'),
102
+ ul: tagged('ul')
103
+ }
104
+
105
+ export function CompactMarkdown({ className, text }: { className?: string; text: string }) {
106
+ return (
107
+ <div className={cn('max-w-full text-xs leading-relaxed text-muted-foreground/90 wrap-anywhere', className)}>
108
+ <Streamdown components={COMPONENTS} controls={false} mode="static" parseIncompleteMarkdown={false}>
109
+ {text}
110
+ </Streamdown>
111
+ </div>
112
+ )
113
+ }
@@ -0,0 +1,31 @@
1
+ import { cn } from '@/lib/utils'
2
+
3
+ /**
4
+ * The composer surface and everything docked to it (slash·@ popover, `?` help)
5
+ * paint ONE shared `--composer-fill` var. The state ladder (rest / scrolled /
6
+ * focused / drawer-open) lives in styles.css on `[data-slot='composer-root']`,
7
+ * so the two layers can never disagree — drawer-open forces an opaque fill via
8
+ * `:has()`, because translucent glass sampling different backdrops (thread vs
9
+ * fade gradient) renders as different colors even with identical tints.
10
+ */
11
+ export const composerFill = 'bg-(--composer-fill)'
12
+
13
+ /** Backdrop treatment for the composer input surface. Harmless when the fill
14
+ * goes opaque (drawer open) — nothing shows through to blur. */
15
+ export const composerSurfaceGlass = cn(
16
+ 'backdrop-blur-[0.75rem] backdrop-saturate-[1.12] [-webkit-backdrop-filter:blur(0.75rem)_saturate(1.12)]',
17
+ 'transition-[background-color] duration-150 ease-out'
18
+ )
19
+
20
+ const composerDockEdge = (edge: 'bottom' | 'top') =>
21
+ cn('border border-border/65', edge === 'top' ? 'rounded-t-2xl border-b-0' : 'rounded-b-2xl border-t-0')
22
+
23
+ /** Glassy docked card — the status stack / queue. Paints the SAME
24
+ * `--composer-fill` as the surface, so rest / scrolled / focused / drawer-open
25
+ * all match the composer by construction. */
26
+ export const composerDockCard = (edge: 'bottom' | 'top' = 'top') =>
27
+ cn(composerDockEdge(edge), composerFill, composerSurfaceGlass)
28
+
29
+ /** Fused docked card — completion drawers. Shares `--composer-fill` with the
30
+ * composer surface, which goes opaque while a drawer is open. */
31
+ export const composerFusedDockCard = (edge: 'bottom' | 'top' = 'top') => cn(composerDockEdge(edge), composerFill)
@@ -0,0 +1,54 @@
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '@/lib/utils'
4
+
5
+ /**
6
+ * Per-line classed renderer for unified diffs. Lives outside `CodeCard` so
7
+ * tool-result panels (already nested inside a tool card) don't double-shell;
8
+ * for markdown ` ```diff ` fences the standard `CodeCard` + Shiki path runs
9
+ * instead and gives equivalent coloring.
10
+ */
11
+ interface DiffLineKind {
12
+ className?: string
13
+ match: (line: string) => boolean
14
+ }
15
+
16
+ const DIFF_LINE_KINDS: DiffLineKind[] = [
17
+ {
18
+ className: 'text-emerald-700 dark:text-emerald-300',
19
+ match: line => line.startsWith('+') && !line.startsWith('+++')
20
+ },
21
+ { className: 'text-rose-700 dark:text-rose-300', match: line => line.startsWith('-') && !line.startsWith('---') },
22
+ { className: 'text-sky-700 dark:text-sky-300', match: line => line.startsWith('@@') },
23
+ {
24
+ className: 'text-muted-foreground/70',
25
+ match: line => line.startsWith('---') || line.startsWith('+++') || / → /.test(line.slice(0, 60))
26
+ }
27
+ ]
28
+
29
+ function classifyLine(line: string): string | undefined {
30
+ return DIFF_LINE_KINDS.find(kind => kind.match(line))?.className
31
+ }
32
+
33
+ interface DiffLinesProps extends Omit<React.ComponentProps<'pre'>, 'children'> {
34
+ text: string
35
+ }
36
+
37
+ export function DiffLines({ className, text, ...props }: DiffLinesProps) {
38
+ return (
39
+ <pre
40
+ className={cn(
41
+ 'mt-1 mb-1.5 max-h-96 max-w-full min-w-0 overflow-auto rounded-md border border-border/60 bg-muted/35 px-2.5 py-1.5 font-mono text-[0.7rem] leading-relaxed text-muted-foreground',
42
+ className
43
+ )}
44
+ data-slot="diff-lines"
45
+ {...props}
46
+ >
47
+ {text.split('\n').map((line, index) => (
48
+ <span className={cn('block min-w-max whitespace-pre', classifyLine(line))} key={`${index}-${line}`}>
49
+ {line || ' '}
50
+ </span>
51
+ ))}
52
+ </pre>
53
+ )
54
+ }
@@ -0,0 +1,63 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { DisclosureCaret } from '@/components/ui/disclosure-caret'
4
+ import { cn } from '@/lib/utils'
5
+
6
+ // Shared header row for any collapsible block (thinking, tool group, single
7
+ // tool). Each parent supplies its own outer wrapper (with the data-slot CSS
8
+ // uses to escape the message padding) and its own expanded body.
9
+ //
10
+ // Affordance:
11
+ // - No leading chevron; a caret appears to the RIGHT of the text on hover
12
+ // (and stays visible when the row is open).
13
+ // - The hover background is a tight content-shaped pill — sized to the
14
+ // title text, NOT the full row — and reaches just past the chevron with
15
+ // `-mx-1.5 px-1.5` so it reads as a soft hit-target rather than a slab
16
+ // stretching to the message edge.
17
+ export function DisclosureRow({
18
+ children,
19
+ onToggle,
20
+ open,
21
+ trailing
22
+ }: {
23
+ children: ReactNode
24
+ onToggle?: () => void
25
+ open: boolean
26
+ trailing?: ReactNode
27
+ }) {
28
+ return (
29
+ <div className="group/disclosure-row relative flex w-full max-w-full min-w-0 text-(--ui-text-tertiary)">
30
+ <button
31
+ aria-expanded={onToggle ? open : undefined}
32
+ className={cn(
33
+ // max-w-fit so the click target hugs the title text width — no
34
+ // background fill, just the cursor + the affordance caret.
35
+ 'flex min-w-0 max-w-fit items-start gap-1.5 text-left transition-colors',
36
+ onToggle ? 'hover:text-foreground focus-visible:text-foreground focus-visible:outline-none' : 'cursor-default'
37
+ )}
38
+ disabled={!onToggle}
39
+ onClick={onToggle}
40
+ type="button"
41
+ >
42
+ <span className="flex min-w-0 flex-col gap-0.5">{children}</span>
43
+ {onToggle && (
44
+ // Wrapper height matches the title row's actual line-height so the
45
+ // caret centres with the title, not the whole subtitle stack.
46
+ <span
47
+ className={cn(
48
+ 'flex h-(--conversation-line-height) shrink-0 items-center justify-center transition-opacity duration-150',
49
+ open
50
+ ? 'opacity-80'
51
+ : 'opacity-0 group-hover/disclosure-row:opacity-80 group-focus-within/disclosure-row:opacity-80'
52
+ )}
53
+ >
54
+ <DisclosureCaret open={open} />
55
+ </span>
56
+ )}
57
+ </button>
58
+ {trailing && (
59
+ <span className="absolute right-1 top-0 flex h-(--conversation-line-height) items-center">{trailing}</span>
60
+ )}
61
+ </div>
62
+ )
63
+ }
@@ -0,0 +1,19 @@
1
+ 'use client'
2
+
3
+ import { createContext, type ReactNode, useContext, useMemo, useState } from 'react'
4
+
5
+ type Value = {
6
+ isPending: boolean
7
+ setPending: (pending: boolean) => void
8
+ }
9
+
10
+ const Ctx = createContext<Value | null>(null)
11
+
12
+ export function GeneratedImageProvider({ children }: { children: ReactNode }) {
13
+ const [isPending, setPending] = useState(false)
14
+ const value = useMemo(() => ({ isPending, setPending }), [isPending])
15
+
16
+ return <Ctx.Provider value={value}>{children}</Ctx.Provider>
17
+ }
18
+
19
+ export const useGeneratedImageContext = () => useContext(Ctx)
@@ -0,0 +1,174 @@
1
+ 'use client'
2
+
3
+ import { type FC, useEffect, useState } from 'react'
4
+
5
+ import { DiffusionCanvas } from '@/components/chat/image-generation-placeholder'
6
+ import { ImageActionButton, ImageLightbox } from '@/components/chat/zoomable-image'
7
+ import { useImageDownload } from '@/hooks/use-image-download'
8
+ import { useI18n } from '@/i18n'
9
+ import { generatedImageFromResult } from '@/lib/generated-images'
10
+ import { filePathFromMediaPath, gatewayMediaDataUrl, isRemoteGateway, mediaExternalUrl, mediaName } from '@/lib/media'
11
+ import { cn } from '@/lib/utils'
12
+
13
+ // Aspect hint from the tool args sizes the frame *before* the image loads, so
14
+ // the placeholder and the resolved image occupy the same box — no layout shift.
15
+ const ASPECT_HINTS: Record<string, number> = {
16
+ landscape: 16 / 9,
17
+ square: 1,
18
+ portrait: 9 / 16
19
+ }
20
+
21
+ function hintedRatio(aspectRatio?: string): number {
22
+ return ASPECT_HINTS[String(aspectRatio ?? '').toLowerCase().trim()] ?? ASPECT_HINTS.landscape
23
+ }
24
+
25
+ function isInlineSrc(path: string): boolean {
26
+ return /^(?:https?|data):/i.test(path)
27
+ }
28
+
29
+ async function resolveImageSrc(path: string): Promise<string> {
30
+ if (isInlineSrc(path)) {
31
+ return path
32
+ }
33
+
34
+ if (window.NASTECHDesktop && isRemoteGateway()) {
35
+ return gatewayMediaDataUrl(path)
36
+ }
37
+
38
+ if (!window.NASTECHDesktop?.readFileDataUrl) {
39
+ return mediaExternalUrl(path)
40
+ }
41
+
42
+ return window.NASTECHDesktop.readFileDataUrl(filePathFromMediaPath(path))
43
+ }
44
+
45
+ export const GeneratedImage: FC<{ aspectRatio?: string; result?: unknown }> = ({ aspectRatio, result }) => {
46
+ const { t } = useI18n()
47
+ const copy = t.desktop
48
+ const image = result === undefined ? null : generatedImageFromResult(result)
49
+ const pending = result === undefined
50
+
51
+ const [ratio, setRatio] = useState(() => hintedRatio(aspectRatio))
52
+ const [src, setSrc] = useState(() => (image && isInlineSrc(image) ? image : ''))
53
+ const [loaded, setLoaded] = useState(false)
54
+ const [canvasGone, setCanvasGone] = useState(false)
55
+ const [failed, setFailed] = useState(false)
56
+ const [lightboxOpen, setLightboxOpen] = useState(false)
57
+ const { download, saving } = useImageDownload(src)
58
+
59
+ useEffect(() => setRatio(hintedRatio(aspectRatio)), [aspectRatio])
60
+
61
+ // Resolve the deliverable path (local read / gateway proxy / remote URL). The
62
+ // <img> stays mounted under the placeholder and only fades in once it decodes,
63
+ // so the frame keeps its hinted size and never jumps.
64
+ useEffect(() => {
65
+ let cancelled = false
66
+ setFailed(false)
67
+ setLoaded(false)
68
+ setCanvasGone(false)
69
+ setSrc(image && isInlineSrc(image) ? image : '')
70
+
71
+ if (!image || isInlineSrc(image)) {
72
+ return
73
+ }
74
+
75
+ void resolveImageSrc(image)
76
+ .then(resolved => !cancelled && setSrc(resolved))
77
+ .catch(() => !cancelled && setFailed(true))
78
+
79
+ return () => {
80
+ cancelled = true
81
+ }
82
+ }, [image])
83
+
84
+ // Completed but no usable image (generation failed): the agent's prose carries
85
+ // the explanation, so render nothing here.
86
+ if (!pending && !image) {
87
+ return null
88
+ }
89
+
90
+ if (failed && image) {
91
+ return (
92
+ <a
93
+ className="mt-2 inline-block font-semibold text-foreground underline underline-offset-4 decoration-current/20 wrap-anywhere"
94
+ href="#"
95
+ onClick={event => {
96
+ event.preventDefault()
97
+ void window.NASTECHDesktop?.openExternal(mediaExternalUrl(image))
98
+ }}
99
+ >
100
+ {copy.openImage}: {mediaName(image)}
101
+ </a>
102
+ )
103
+ }
104
+
105
+ return (
106
+ <>
107
+ <span
108
+ aria-label={pending ? t.assistant.tool.renderingImage : undefined}
109
+ aria-live={pending ? 'polite' : undefined}
110
+ className="group/image relative block max-w-full overflow-hidden rounded-2xl transition-[width,height] duration-300 ease-out"
111
+ data-slot="aui_generated-image"
112
+ role={pending ? 'status' : undefined}
113
+ style={{
114
+ aspectRatio: ratio,
115
+ // Width is capped so the derived height (width / ratio) never exceeds
116
+ // --image-preview-height; the box then matches the image exactly with
117
+ // no letterboxing.
118
+ width: `min(calc(var(--image-preview-height) * ${ratio}), var(--image-preview-max-width), 100%)`
119
+ }}
120
+ >
121
+ {!canvasGone && (
122
+ <div
123
+ className={cn('absolute inset-0 transition-opacity duration-500 ease-out', loaded && 'opacity-0')}
124
+ onTransitionEnd={() => loaded && setCanvasGone(true)}
125
+ >
126
+ <DiffusionCanvas />
127
+ </div>
128
+ )}
129
+ {src && (
130
+ <button
131
+ className="absolute inset-0 block size-full cursor-zoom-in"
132
+ onClick={() => setLightboxOpen(true)}
133
+ title={copy.openImage}
134
+ type="button"
135
+ >
136
+ <img
137
+ alt="Generated image"
138
+ className={cn(
139
+ 'absolute inset-0 size-full object-contain opacity-0 transition-opacity duration-500 ease-out',
140
+ loaded && 'opacity-100'
141
+ )}
142
+ draggable={false}
143
+ onError={() => setFailed(true)}
144
+ onLoad={event => {
145
+ const { naturalHeight, naturalWidth } = event.currentTarget
146
+
147
+ if (naturalWidth && naturalHeight) {
148
+ setRatio(naturalWidth / naturalHeight)
149
+ }
150
+
151
+ setLoaded(true)
152
+ }}
153
+ src={src}
154
+ />
155
+ </button>
156
+ )}
157
+ {loaded && src && (
158
+ <ImageActionButton className="group-hover/image:opacity-100" copy={copy} onClick={download} saving={saving} />
159
+ )}
160
+ </span>
161
+ {src && (
162
+ <ImageLightbox
163
+ alt="Generated image"
164
+ copy={copy}
165
+ onClick={download}
166
+ onOpenChange={setLightboxOpen}
167
+ open={lightboxOpen}
168
+ saving={saving}
169
+ src={src}
170
+ />
171
+ )}
172
+ </>
173
+ )
174
+ }