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,648 @@
1
+ import type * as React from 'react'
2
+ import { useCallback, useEffect, useMemo, useState } from 'react'
3
+
4
+ import { PageLoader } from '@/components/page-loader'
5
+ import { StatusDot, type StatusTone } from '@/components/status-dot'
6
+ import { Button } from '@/components/ui/button'
7
+ import { DisclosureCaret } from '@/components/ui/disclosure-caret'
8
+ import { Input } from '@/components/ui/input'
9
+ import { Switch } from '@/components/ui/switch'
10
+ import {
11
+ getMessagingPlatforms,
12
+ type MessagingEnvVarInfo,
13
+ type MessagingPlatformInfo,
14
+ updateMessagingPlatform
15
+ } from '@/nastech'
16
+ import { type Translations, useI18n } from '@/i18n'
17
+ import { AlertTriangle, ExternalLink, Save, Trash2 } from '@/lib/icons'
18
+ import { cn } from '@/lib/utils'
19
+ import { notify, notifyError } from '@/store/notifications'
20
+
21
+ import { useRefreshHotkey } from '../hooks/use-refresh-hotkey'
22
+ import { useRouteEnumParam } from '../hooks/use-route-enum-param'
23
+ import { PageSearchShell } from '../page-search-shell'
24
+ import { CREDENTIAL_CONTROL_CLASS } from '../settings/credential-key-ui'
25
+ import { ListRow } from '../settings/primitives'
26
+ import type { SetStatusbarItemGroup } from '../shell/statusbar-controls'
27
+
28
+ import { PlatformAvatar } from './platform-icon'
29
+
30
+ interface MessagingViewProps extends React.ComponentProps<'section'> {
31
+ setStatusbarItemGroup?: SetStatusbarItemGroup
32
+ }
33
+
34
+ type EditMap = Record<string, Record<string, string>>
35
+
36
+ const PILL_TONE: Record<StatusTone, string> = {
37
+ good: 'bg-primary/10 text-primary',
38
+ muted: 'bg-muted text-muted-foreground',
39
+ warn: 'bg-amber-500/10 text-amber-600 dark:text-amber-300',
40
+ bad: 'bg-destructive/10 text-destructive'
41
+ }
42
+
43
+ const stateLabel = (state: null | string | undefined, m: Translations['messaging']) =>
44
+ state ? m.states[state] || state.replace(/_/g, ' ') : m.unknown
45
+
46
+ function stateTone({ enabled, state }: MessagingPlatformInfo): StatusTone {
47
+ if (!enabled) {
48
+ return 'muted'
49
+ }
50
+
51
+ if (state === 'connected') {
52
+ return 'good'
53
+ }
54
+
55
+ if (state === 'fatal' || state === 'startup_failed') {
56
+ return 'bad'
57
+ }
58
+
59
+ return 'warn'
60
+ }
61
+
62
+ const trimEdits = (edits: Record<string, string>): Record<string, string> =>
63
+ Object.fromEntries(
64
+ Object.entries(edits)
65
+ .map(([k, v]) => [k, v.trim()])
66
+ .filter(([, v]) => v)
67
+ )
68
+
69
+ const FIELD_COPY: Record<string, { advanced?: boolean }> = {
70
+ TELEGRAM_PROXY: { advanced: true },
71
+ DISCORD_REPLY_TO_MODE: { advanced: true },
72
+ DISCORD_ALLOW_ALL_USERS: { advanced: true },
73
+ DISCORD_HOME_CHANNEL: { advanced: true },
74
+ DISCORD_HOME_CHANNEL_NAME: { advanced: true },
75
+ BLUEBUBBLES_ALLOW_ALL_USERS: { advanced: true },
76
+ MATTERMOST_ALLOW_ALL_USERS: { advanced: true },
77
+ MATTERMOST_HOME_CHANNEL: { advanced: true },
78
+ QQ_ALLOW_ALL_USERS: { advanced: true },
79
+ QQBOT_HOME_CHANNEL: { advanced: true },
80
+ QQBOT_HOME_CHANNEL_NAME: { advanced: true },
81
+ WHATSAPP_ENABLED: { advanced: true },
82
+ WHATSAPP_MODE: { advanced: true }
83
+ }
84
+
85
+ function fieldCopy(field: MessagingEnvVarInfo, m: Translations['messaging']) {
86
+ const copy = FIELD_COPY[field.key] || {}
87
+ const localized = m.fieldCopy[field.key] || {}
88
+
89
+ return {
90
+ label: localized.label || field.prompt || field.key,
91
+ help: localized.help || field.description,
92
+ placeholder: localized.placeholder || field.prompt,
93
+ advanced: Boolean(copy.advanced || field.advanced)
94
+ }
95
+ }
96
+
97
+ export function MessagingView({ setStatusbarItemGroup: _setStatusbarItemGroup, ...props }: MessagingViewProps) {
98
+ const { t } = useI18n()
99
+ const m = t.messaging
100
+ const [platforms, setPlatforms] = useState<MessagingPlatformInfo[] | null>(null)
101
+ const [edits, setEdits] = useState<EditMap>({})
102
+ const [query, setQuery] = useState('')
103
+ const [refreshing, setRefreshing] = useState(false)
104
+ const [saving, setSaving] = useState<string | null>(null)
105
+ const platformIds = useMemo(() => platforms?.map(p => p.id) ?? [], [platforms])
106
+ const [selectedId, setSelectedId] = useRouteEnumParam('platform', platformIds, platformIds[0] ?? '')
107
+
108
+ const refreshPlatforms = useCallback(async (silent = false) => {
109
+ if (!silent) {
110
+ setRefreshing(true)
111
+ }
112
+
113
+ try {
114
+ const result = await getMessagingPlatforms()
115
+ setPlatforms(result.platforms)
116
+ } catch (err) {
117
+ if (!silent) {
118
+ notifyError(err, m.loadFailed)
119
+ }
120
+ } finally {
121
+ if (!silent) {
122
+ setRefreshing(false)
123
+ }
124
+ }
125
+ }, [m])
126
+
127
+ useRefreshHotkey(() => void refreshPlatforms())
128
+
129
+ useEffect(() => {
130
+ void refreshPlatforms()
131
+ }, [refreshPlatforms])
132
+
133
+ // Auto-poll while the user is on the messaging page so connection status
134
+ // updates without a manual "check" click. Pause when the tab is hidden.
135
+ useEffect(() => {
136
+ let cancelled = false
137
+
138
+ function tick() {
139
+ if (cancelled || document.hidden) {
140
+ return
141
+ }
142
+
143
+ void refreshPlatforms(true)
144
+ }
145
+
146
+ const id = window.setInterval(tick, 6000)
147
+
148
+ return () => {
149
+ cancelled = true
150
+ window.clearInterval(id)
151
+ }
152
+ }, [refreshPlatforms])
153
+
154
+ const selected = useMemo(() => {
155
+ if (!platforms) {
156
+ return null
157
+ }
158
+
159
+ return platforms.find(platform => platform.id === selectedId) || platforms[0] || null
160
+ }, [platforms, selectedId])
161
+
162
+ const visiblePlatforms = useMemo(() => {
163
+ if (!platforms) {
164
+ return []
165
+ }
166
+
167
+ const q = query.trim().toLowerCase()
168
+
169
+ if (!q) {
170
+ return platforms
171
+ }
172
+
173
+ return platforms.filter(platform =>
174
+ [platform.id, platform.name, platform.description, platform.state]
175
+ .filter(Boolean)
176
+ .some(value => String(value).toLowerCase().includes(q))
177
+ )
178
+ }, [platforms, query])
179
+
180
+ async function handleToggle(platform: MessagingPlatformInfo, enabled: boolean) {
181
+ setSaving(`enabled:${platform.id}`)
182
+
183
+ try {
184
+ await updateMessagingPlatform(platform.id, { enabled })
185
+ setPlatforms(
186
+ current =>
187
+ current?.map(row =>
188
+ row.id === platform.id
189
+ ? {
190
+ ...row,
191
+ enabled,
192
+ state: enabled ? (row.configured ? 'pending_restart' : 'not_configured') : 'disabled'
193
+ }
194
+ : row
195
+ ) ?? current
196
+ )
197
+ notify({
198
+ kind: 'success',
199
+ title: enabled ? m.platformEnabled(platform.name) : m.platformDisabled(platform.name),
200
+ message: m.restartToApply
201
+ })
202
+ } catch (err) {
203
+ notifyError(err, m.failedUpdate(platform.name))
204
+ } finally {
205
+ setSaving(null)
206
+ }
207
+ }
208
+
209
+ async function handleSave(platform: MessagingPlatformInfo) {
210
+ const env = trimEdits(edits[platform.id] || {})
211
+
212
+ if (Object.keys(env).length === 0) {
213
+ return
214
+ }
215
+
216
+ setSaving(`env:${platform.id}`)
217
+
218
+ try {
219
+ await updateMessagingPlatform(platform.id, { env })
220
+ setEdits(current => ({ ...current, [platform.id]: {} }))
221
+ await refreshPlatforms()
222
+ notify({
223
+ kind: 'success',
224
+ title: m.setupSaved(platform.name),
225
+ message: m.restartToReconnect
226
+ })
227
+ } catch (err) {
228
+ notifyError(err, m.failedSave(platform.name))
229
+ } finally {
230
+ setSaving(null)
231
+ }
232
+ }
233
+
234
+ async function handleClear(platform: MessagingPlatformInfo, key: string) {
235
+ setSaving(`clear:${key}`)
236
+
237
+ try {
238
+ await updateMessagingPlatform(platform.id, { clear_env: [key] })
239
+ setEdits(current => ({
240
+ ...current,
241
+ [platform.id]: {
242
+ ...(current[platform.id] || {}),
243
+ [key]: ''
244
+ }
245
+ }))
246
+ await refreshPlatforms()
247
+ notify({ kind: 'success', title: m.keyCleared(key), message: m.setupUpdated(platform.name) })
248
+ } catch (err) {
249
+ notifyError(err, m.failedClear(key))
250
+ } finally {
251
+ setSaving(null)
252
+ }
253
+ }
254
+
255
+ return (
256
+ <PageSearchShell
257
+ {...props}
258
+ onSearchChange={setQuery}
259
+ searchHidden={(platforms?.length ?? 0) === 0}
260
+ searchPlaceholder={m.search}
261
+ searchValue={query}
262
+ >
263
+ {!platforms ? (
264
+ <PageLoader label={m.loading} />
265
+ ) : (
266
+ <div className="grid h-full min-h-0 grid-cols-1 lg:grid-cols-[14rem_minmax(0,1fr)]">
267
+ <aside className="min-h-0 overflow-y-auto p-2">
268
+ <ul className="space-y-1">
269
+ {visiblePlatforms.map(platform => (
270
+ <li key={platform.id}>
271
+ <PlatformRow
272
+ active={selected?.id === platform.id}
273
+ onSelect={() => setSelectedId(platform.id)}
274
+ platform={platform}
275
+ />
276
+ </li>
277
+ ))}
278
+ </ul>
279
+ </aside>
280
+
281
+ <main className="min-h-0 overflow-hidden">
282
+ {selected && (
283
+ <PlatformDetail
284
+ edits={edits[selected.id] || {}}
285
+ onClear={key => void handleClear(selected, key)}
286
+ onEdit={(key, value) =>
287
+ setEdits(current => ({
288
+ ...current,
289
+ [selected.id]: {
290
+ ...(current[selected.id] || {}),
291
+ [key]: value
292
+ }
293
+ }))
294
+ }
295
+ onSave={() => void handleSave(selected)}
296
+ onToggle={enabled => void handleToggle(selected, enabled)}
297
+ platform={selected}
298
+ saving={saving}
299
+ />
300
+ )}
301
+ </main>
302
+ </div>
303
+ )}
304
+ </PageSearchShell>
305
+ )
306
+ }
307
+
308
+ function PlatformRow({
309
+ active,
310
+ onSelect,
311
+ platform
312
+ }: {
313
+ active: boolean
314
+ onSelect: () => void
315
+ platform: MessagingPlatformInfo
316
+ }) {
317
+ return (
318
+ <button
319
+ className={cn(
320
+ 'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left transition-colors',
321
+ active
322
+ ? 'bg-(--ui-row-active-background) text-foreground'
323
+ : 'text-(--ui-text-secondary) hover:bg-(--ui-row-hover-background) hover:text-foreground'
324
+ )}
325
+ onClick={onSelect}
326
+ type="button"
327
+ >
328
+ <PlatformAvatar platformId={platform.id} platformName={platform.name} />
329
+ <span className="flex min-w-0 flex-1 items-center justify-between gap-2">
330
+ <span className="truncate text-[length:var(--conversation-text-font-size)] font-normal">{platform.name}</span>
331
+ <StatusDot tone={stateTone(platform)} />
332
+ </span>
333
+ </button>
334
+ )
335
+ }
336
+
337
+ function PlatformDetail({
338
+ edits,
339
+ onClear,
340
+ onEdit,
341
+ onSave,
342
+ onToggle,
343
+ platform,
344
+ saving
345
+ }: {
346
+ edits: Record<string, string>
347
+ onClear: (key: string) => void
348
+ onEdit: (key: string, value: string) => void
349
+ onSave: () => void
350
+ onToggle: (enabled: boolean) => void
351
+ platform: MessagingPlatformInfo
352
+ saving: string | null
353
+ }) {
354
+ const { t } = useI18n()
355
+ const m = t.messaging
356
+ const [showAdvanced, setShowAdvanced] = useState(false)
357
+
358
+ const hasEdits = Object.keys(trimEdits(edits)).length > 0
359
+ const requiredFields = platform.env_vars.filter(field => field.required)
360
+ const optionalFields = platform.env_vars.filter(field => !field.required && !fieldCopy(field, m).advanced)
361
+ const advancedFields = platform.env_vars.filter(field => !field.required && fieldCopy(field, m).advanced)
362
+ const hiddenCount = advancedFields.length
363
+ const isSavingEnv = saving === `env:${platform.id}`
364
+
365
+ return (
366
+ <div className="flex h-full min-h-0 flex-col">
367
+ <div className="min-h-0 flex-1 overflow-y-auto">
368
+ <div className="mx-auto max-w-2xl space-y-5 px-5 py-4">
369
+ <header className="flex items-start gap-3">
370
+ <PlatformAvatar platformId={platform.id} platformName={platform.name} />
371
+ <div className="min-w-0 flex-1">
372
+ <h3 className="text-[0.9375rem] font-semibold tracking-tight">{platform.name}</h3>
373
+ <p className="mt-1 text-[length:var(--conversation-caption-font-size)] leading-(--conversation-caption-line-height) text-(--ui-text-tertiary)">
374
+ {platform.description}
375
+ </p>
376
+ <div className="mt-3 flex flex-wrap items-center gap-2">
377
+ <StatePill tone={stateTone(platform)}>{stateLabel(platform.state, m)}</StatePill>
378
+ <SetupPill active={platform.configured}>
379
+ {platform.configured ? m.credentialsSet : m.needsSetup}
380
+ </SetupPill>
381
+ {!platform.gateway_running && <SetupPill active={false}>{m.gatewayStopped}</SetupPill>}
382
+ </div>
383
+ <PlatformHint platform={platform} />
384
+ </div>
385
+ </header>
386
+
387
+ {platform.error_message && (
388
+ <div className="flex items-start gap-2 rounded-xl border border-destructive/30 bg-destructive/10 px-3 py-2 text-[length:var(--conversation-caption-font-size)] leading-(--conversation-caption-line-height) text-destructive">
389
+ <AlertTriangle className="mt-0.5 size-3.5 shrink-0" />
390
+ <span>{platform.error_message}</span>
391
+ </div>
392
+ )}
393
+
394
+ <section>
395
+ <SectionTitle>{m.getCredentials}</SectionTitle>
396
+ <p className="mt-1 text-[length:var(--conversation-caption-font-size)] leading-(--conversation-caption-line-height) text-(--ui-text-tertiary)">
397
+ {introCopy(platform, m)}
398
+ </p>
399
+ <div className="mt-3">
400
+ <Button asChild size="sm" variant="textStrong">
401
+ <a href={platform.docs_url} rel="noreferrer" target="_blank">
402
+ {m.openSetupGuide}
403
+ <ExternalLink className="size-3.5" />
404
+ </a>
405
+ </Button>
406
+ </div>
407
+ </section>
408
+
409
+ <section>
410
+ <SectionTitle>{m.required}</SectionTitle>
411
+ <div className="mt-3 grid gap-1">
412
+ {requiredFields.length > 0 ? (
413
+ requiredFields.map(field => (
414
+ <MessagingField
415
+ edits={edits}
416
+ field={field}
417
+ key={field.key}
418
+ onClear={onClear}
419
+ onEdit={onEdit}
420
+ saving={saving}
421
+ />
422
+ ))
423
+ ) : (
424
+ <p className="text-[length:var(--conversation-caption-font-size)] leading-(--conversation-caption-line-height) text-(--ui-text-tertiary)">
425
+ {m.noTokenNeeded}
426
+ </p>
427
+ )}
428
+ </div>
429
+ </section>
430
+
431
+ {optionalFields.length > 0 && (
432
+ <section>
433
+ <SectionTitle>{m.recommended}</SectionTitle>
434
+ <div className="mt-3 grid gap-1">
435
+ {optionalFields.map(field => (
436
+ <MessagingField
437
+ edits={edits}
438
+ field={field}
439
+ key={field.key}
440
+ onClear={onClear}
441
+ onEdit={onEdit}
442
+ saving={saving}
443
+ />
444
+ ))}
445
+ </div>
446
+ </section>
447
+ )}
448
+
449
+ {hiddenCount > 0 && (
450
+ <section>
451
+ <button
452
+ className="flex w-full items-center justify-between gap-2 py-0.5 text-left text-[0.7rem] font-semibold uppercase tracking-[0.14em] text-muted-foreground transition-colors hover:text-foreground"
453
+ onClick={() => setShowAdvanced(value => !value)}
454
+ type="button"
455
+ >
456
+ <span>{m.advanced(hiddenCount)}</span>
457
+ <DisclosureCaret open={showAdvanced} size="0.875rem" />
458
+ </button>
459
+ {showAdvanced && (
460
+ <div className="mt-3 grid gap-1">
461
+ {advancedFields.map(field => (
462
+ <MessagingField
463
+ edits={edits}
464
+ field={field}
465
+ key={field.key}
466
+ onClear={onClear}
467
+ onEdit={onEdit}
468
+ saving={saving}
469
+ />
470
+ ))}
471
+ </div>
472
+ )}
473
+ </section>
474
+ )}
475
+ </div>
476
+ </div>
477
+
478
+ <footer className="bg-(--ui-chat-surface-background) px-5 py-2.5">
479
+ <div className="mx-auto flex max-w-2xl flex-wrap items-center gap-2">
480
+ <Switch
481
+ aria-label={platform.enabled ? m.disableAria(platform.name) : m.enableAria(platform.name)}
482
+ checked={platform.enabled}
483
+ disabled={saving === `enabled:${platform.id}`}
484
+ onCheckedChange={onToggle}
485
+ size="xs"
486
+ />
487
+
488
+ <div className="ml-auto flex items-center gap-2">
489
+ {hasEdits && <span className="text-xs text-muted-foreground">{m.unsavedChanges}</span>}
490
+ <Button disabled={!hasEdits || isSavingEnv} onClick={onSave} size="sm">
491
+ <Save />
492
+ {isSavingEnv ? m.saving : m.saveChanges}
493
+ </Button>
494
+ </div>
495
+ </div>
496
+ </footer>
497
+ </div>
498
+ )
499
+ }
500
+
501
+ const PLATFORM_INTRO: Record<string, string> = {
502
+ telegram:
503
+ 'In Telegram, talk to @BotFather, run /newbot, and copy the token it gives you. Then grab your numeric user ID from @userinfobot.',
504
+ discord:
505
+ 'Open the Discord Developer Portal, create an application, add a Bot, then copy its token. Invite the bot to your server with the right scopes.',
506
+ slack:
507
+ 'Create a Slack app, enable Socket Mode, install it to your workspace, then copy the bot token and app-level token.',
508
+ mattermost:
509
+ 'On your Mattermost server, create a bot account or personal access token, then paste the server URL and token here.',
510
+ matrix: 'Sign in to your homeserver with the bot account, then copy the access token, user ID, and homeserver URL.',
511
+ signal:
512
+ 'Run a signal-cli REST bridge somewhere reachable, then point NasTech at the URL and the registered phone number.',
513
+ whatsapp:
514
+ 'Start the WhatsApp bridge that ships with NasTech, scan the QR code on first run, then enable the platform.',
515
+ bluebubbles:
516
+ 'Run BlueBubbles Server on a Mac with iMessage, expose its API, then point NasTech at the URL with the server password.',
517
+ homeassistant:
518
+ 'In Home Assistant, open your profile and create a long-lived access token. Paste it here along with your HA URL.',
519
+ email:
520
+ 'Use a dedicated mailbox. For Gmail/Workspace, create an app password and use imap.gmail.com / smtp.gmail.com.',
521
+ sms: 'Get your Twilio Account SID and Auth Token from the Twilio console, plus a phone number that can send SMS.',
522
+ dingtalk: 'Create a DingTalk app in the developer console, then copy the Client ID (App key) and Client Secret here.',
523
+ feishu:
524
+ 'Create a Feishu / Lark app, configure the bot capability, and copy the App ID, App secret, and event encryption keys.',
525
+ wecom:
526
+ 'Add a group robot in WeCom and copy its webhook key as WECOM_BOT_ID. Send-only — use the WeCom (app) option for two-way.',
527
+ wecom_callback:
528
+ 'Set up a WeCom self-built app, expose its callback URL, and provide the corp ID, secret, agent ID, and AES key.',
529
+ weixin:
530
+ 'Sign in to the WeChat Official Account platform, copy the AppID and Token, and point the message callback URL at NasTech.',
531
+ qqbot: 'Register an app on the QQ Open Platform (q.qq.com) and copy the App ID and Client Secret.',
532
+ api_server:
533
+ 'Expose NasTech as an OpenAI-compatible API. Set an auth key, then point Open WebUI / LobeChat / etc. at the host:port.',
534
+ webhook:
535
+ 'Run an HTTP server that other tools (GitHub, GitLab, custom apps) can POST to. Use the secret to verify signatures.'
536
+ }
537
+
538
+ const introCopy = (platform: MessagingPlatformInfo, m: Translations['messaging']) =>
539
+ m.platformIntro[platform.id] || PLATFORM_INTRO[platform.id] || platform.description
540
+
541
+ function MessagingField({
542
+ edits,
543
+ field,
544
+ onClear,
545
+ onEdit,
546
+ saving
547
+ }: {
548
+ edits: Record<string, string>
549
+ field: MessagingEnvVarInfo
550
+ onClear: (key: string) => void
551
+ onEdit: (key: string, value: string) => void
552
+ saving: string | null
553
+ }) {
554
+ const { t } = useI18n()
555
+ const m = t.messaging
556
+ const copy = fieldCopy(field, m)
557
+ const fieldId = `messaging-field-${field.key}`
558
+
559
+ return (
560
+ <ListRow
561
+ action={
562
+ <div className="flex items-center gap-2">
563
+ <Input
564
+ className={CREDENTIAL_CONTROL_CLASS}
565
+ id={fieldId}
566
+ onChange={event => onEdit(field.key, event.target.value)}
567
+ placeholder={field.is_set ? field.redacted_value || m.replaceValue : copy.placeholder}
568
+ type={field.is_password ? 'password' : 'text'}
569
+ value={edits[field.key] || ''}
570
+ />
571
+ {field.url && (
572
+ <Button asChild className="size-8 shrink-0" title={m.openDocs} variant="ghost">
573
+ <a href={field.url} rel="noreferrer" target="_blank">
574
+ <ExternalLink className="size-3.5" />
575
+ </a>
576
+ </Button>
577
+ )}
578
+ {field.is_set && (
579
+ <Button
580
+ className="size-8 shrink-0"
581
+ disabled={saving === `clear:${field.key}`}
582
+ onClick={() => onClear(field.key)}
583
+ title={m.clearField(field.key)}
584
+ variant="ghost"
585
+ >
586
+ <Trash2 className="size-3.5" />
587
+ </Button>
588
+ )}
589
+ </div>
590
+ }
591
+ description={copy.help}
592
+ title={
593
+ <span className="flex flex-wrap items-center gap-2">
594
+ <label htmlFor={fieldId}>{copy.label}</label>
595
+ {field.is_set && <span className="text-[0.66rem] font-medium text-primary">{m.saved}</span>}
596
+ </span>
597
+ }
598
+ />
599
+ )
600
+ }
601
+
602
+ function SectionTitle({ children }: { children: React.ReactNode }) {
603
+ return <h4 className="text-[0.7rem] font-semibold uppercase tracking-[0.14em] text-muted-foreground">{children}</h4>
604
+ }
605
+
606
+ function PlatformHint({ platform }: { platform: MessagingPlatformInfo }) {
607
+ const { t } = useI18n()
608
+
609
+ if (!platform.enabled || platform.state === 'connected') {
610
+ return null
611
+ }
612
+
613
+ const hint =
614
+ platform.state === 'pending_restart'
615
+ ? t.messaging.hintPendingRestart
616
+ : platform.gateway_running
617
+ ? null
618
+ : t.messaging.hintGatewayStopped
619
+
620
+ return hint ? <p className="mt-2 text-xs leading-5 text-muted-foreground">{hint}</p> : null
621
+ }
622
+
623
+ function StatePill({ children, tone }: { children: string; tone: StatusTone }) {
624
+ return (
625
+ <span
626
+ className={cn(
627
+ 'inline-flex shrink-0 items-center gap-1.5 rounded-full px-2 py-0.5 text-[0.66rem] font-medium',
628
+ PILL_TONE[tone]
629
+ )}
630
+ >
631
+ <StatusDot tone={tone} />
632
+ {children}
633
+ </span>
634
+ )
635
+ }
636
+
637
+ function SetupPill({ active, children }: { active: boolean; children: string }) {
638
+ return (
639
+ <span
640
+ className={cn(
641
+ 'inline-flex items-center rounded-full px-2 py-0.5 text-[0.66rem] font-medium',
642
+ PILL_TONE[active ? 'good' : 'muted']
643
+ )}
644
+ >
645
+ {children}
646
+ </span>
647
+ )
648
+ }