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.
- package/.prettierrc +11 -0
- package/DESIGN.md +167 -0
- package/README.md +141 -0
- package/assets/icon.icns +0 -0
- package/assets/icon.ico +0 -0
- package/assets/icon.png +0 -0
- package/components.json +21 -0
- package/electron/backend-env.cjs +112 -0
- package/electron/backend-env.test.cjs +111 -0
- package/electron/backend-probes.cjs +106 -0
- package/electron/backend-probes.test.cjs +82 -0
- package/electron/backend-ready.cjs +66 -0
- package/electron/bootstrap-platform.cjs +91 -0
- package/electron/bootstrap-platform.test.cjs +111 -0
- package/electron/bootstrap-runner.cjs +720 -0
- package/electron/bootstrap-runner.test.cjs +138 -0
- package/electron/connection-config.cjs +254 -0
- package/electron/connection-config.test.cjs +329 -0
- package/electron/dashboard-token.cjs +99 -0
- package/electron/dashboard-token.test.cjs +142 -0
- package/electron/desktop-uninstall.cjs +232 -0
- package/electron/desktop-uninstall.test.cjs +246 -0
- package/electron/entitlements.mac.inherit.plist +14 -0
- package/electron/entitlements.mac.plist +14 -0
- package/electron/fs-read-dir.cjs +109 -0
- package/electron/fs-read-dir.test.cjs +364 -0
- package/electron/gateway-ws-probe.cjs +188 -0
- package/electron/gateway-ws-probe.test.cjs +122 -0
- package/electron/git-root.cjs +54 -0
- package/electron/git-root.test.cjs +40 -0
- package/electron/git-worktrees.cjs +174 -0
- package/electron/hardening.cjs +184 -0
- package/electron/hardening.test.cjs +116 -0
- package/electron/main.cjs +5762 -0
- package/electron/oauth-net-request.cjs +20 -0
- package/electron/oauth-net-request.test.cjs +34 -0
- package/electron/preload.cjs +135 -0
- package/electron/session-windows.cjs +99 -0
- package/electron/session-windows.test.cjs +177 -0
- package/electron/update-remote.cjs +56 -0
- package/electron/update-remote.test.cjs +78 -0
- package/electron/vscode-marketplace.cjs +331 -0
- package/electron/vscode-marketplace.test.cjs +113 -0
- package/electron/windows-child-process.test.cjs +57 -0
- package/electron/windows-user-env.cjs +76 -0
- package/electron/windows-user-env.test.cjs +90 -0
- package/electron/workspace-cwd.cjs +38 -0
- package/electron/workspace-cwd.test.cjs +45 -0
- package/eslint.config.mjs +122 -0
- package/index.html +17 -0
- package/package.json +254 -0
- package/pr-assets/session-source-folders.png +0 -0
- package/preview-demo.html +65 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/ds-assets/filler-bg0.jpg +0 -0
- package/public/nastech-frames/nastech-frame-0.png +0 -0
- package/public/nastech-frames/nastech-frame-1.png +0 -0
- package/public/nastech-frames/nastech-frame-2.png +0 -0
- package/public/nastech-frames/nastech-frame-3.png +0 -0
- package/public/nastech-frames/nastech-frame-4.png +0 -0
- package/public/nastech-frames/nastech-frame-5.png +0 -0
- package/public/nastech-frames/nastech-frame-6.png +0 -0
- package/public/nastech-frames/nastech-frame-7.png +0 -0
- package/public/nastech-girl.jpg +0 -0
- package/public/nastech-sprite.png +0 -0
- package/public/nastech.png +0 -0
- package/scripts/after-pack.cjs +41 -0
- package/scripts/assert-dist-built.cjs +70 -0
- package/scripts/assert-dist-built.test.cjs +84 -0
- package/scripts/assert-root-install.cjs +13 -0
- package/scripts/before-build.cjs +11 -0
- package/scripts/before-pack.cjs +78 -0
- package/scripts/before-pack.test.cjs +53 -0
- package/scripts/click-session.mjs +51 -0
- package/scripts/dev-no-hmr.mjs +22 -0
- package/scripts/diag-jump.mjs +115 -0
- package/scripts/diag-scroll-reset.mjs +229 -0
- package/scripts/eval.mjs +21 -0
- package/scripts/leak-typing.mjs +222 -0
- package/scripts/measure-jump.mjs +108 -0
- package/scripts/measure-latency.mjs +184 -0
- package/scripts/measure-real-stream.mjs +252 -0
- package/scripts/measure-submit.mjs +179 -0
- package/scripts/measure-synthetic-stream.mjs +322 -0
- package/scripts/notarize-artifact.cjs +77 -0
- package/scripts/notarize.cjs +100 -0
- package/scripts/patch-electron-builder-mac-binary.cjs +59 -0
- package/scripts/probe-renderer.mjs +38 -0
- package/scripts/probe-thread.mjs +40 -0
- package/scripts/profile-long-stream.mjs +191 -0
- package/scripts/profile-real-stream.mjs +137 -0
- package/scripts/profile-synth-stream.mjs +103 -0
- package/scripts/profile-typing-lag.md +381 -0
- package/scripts/profile-typing.mjs +260 -0
- package/scripts/reload-renderer.mjs +25 -0
- package/scripts/reload.mjs +36 -0
- package/scripts/set-exe-identity.cjs +94 -0
- package/scripts/stage-native-deps.cjs +159 -0
- package/scripts/test-desktop.mjs +425 -0
- package/scripts/write-build-stamp.cjs +126 -0
- package/src/app/agents/index.tsx +398 -0
- package/src/app/artifacts/index.test.ts +62 -0
- package/src/app/artifacts/index.tsx +906 -0
- package/src/app/chat/chat-drop-overlay.tsx +48 -0
- package/src/app/chat/chat-swap-overlay.tsx +47 -0
- package/src/app/chat/composer/attachments.tsx +114 -0
- package/src/app/chat/composer/completion-drawer.tsx +63 -0
- package/src/app/chat/composer/context-menu.tsx +172 -0
- package/src/app/chat/composer/controls.tsx +289 -0
- package/src/app/chat/composer/drop-affordance.ts +2 -0
- package/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
- package/src/app/chat/composer/focus.ts +134 -0
- package/src/app/chat/composer/help-hint.tsx +59 -0
- package/src/app/chat/composer/hooks/use-at-completions.ts +141 -0
- package/src/app/chat/composer/hooks/use-live-completion-adapter.ts +119 -0
- package/src/app/chat/composer/hooks/use-mic-recorder.ts +291 -0
- package/src/app/chat/composer/hooks/use-slash-completions.ts +114 -0
- package/src/app/chat/composer/hooks/use-voice-conversation.ts +390 -0
- package/src/app/chat/composer/hooks/use-voice-recorder.ts +116 -0
- package/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
- package/src/app/chat/composer/index.tsx +1611 -0
- package/src/app/chat/composer/inline-refs.ts +138 -0
- package/src/app/chat/composer/model-pill.tsx +86 -0
- package/src/app/chat/composer/queue-panel.tsx +130 -0
- package/src/app/chat/composer/rich-editor.test.ts +18 -0
- package/src/app/chat/composer/rich-editor.ts +165 -0
- package/src/app/chat/composer/skin-slash-popover.tsx +61 -0
- package/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
- package/src/app/chat/composer/status-stack/index.tsx +202 -0
- package/src/app/chat/composer/status-stack/status-row.tsx +155 -0
- package/src/app/chat/composer/text-utils.test.ts +77 -0
- package/src/app/chat/composer/text-utils.ts +107 -0
- package/src/app/chat/composer/trigger-popover.test.tsx +42 -0
- package/src/app/chat/composer/trigger-popover.tsx +116 -0
- package/src/app/chat/composer/types.ts +64 -0
- package/src/app/chat/composer/url-dialog.tsx +82 -0
- package/src/app/chat/composer/voice-activity.tsx +252 -0
- package/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
- package/src/app/chat/hooks/use-composer-actions.ts +525 -0
- package/src/app/chat/hooks/use-file-drop-zone.ts +118 -0
- package/src/app/chat/index.tsx +390 -0
- package/src/app/chat/perf-probe.tsx +269 -0
- package/src/app/chat/right-rail/index.ts +1 -0
- package/src/app/chat/right-rail/preview-console-state.ts +82 -0
- package/src/app/chat/right-rail/preview-console.tsx +290 -0
- package/src/app/chat/right-rail/preview-file.tsx +559 -0
- package/src/app/chat/right-rail/preview-pane.test.tsx +43 -0
- package/src/app/chat/right-rail/preview-pane.tsx +657 -0
- package/src/app/chat/right-rail/preview.tsx +171 -0
- package/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
- package/src/app/chat/scroll-to-bottom-button.tsx +74 -0
- package/src/app/chat/sidebar/cron-jobs-section.tsx +325 -0
- package/src/app/chat/sidebar/index.tsx +1219 -0
- package/src/app/chat/sidebar/load-more-row.tsx +30 -0
- package/src/app/chat/sidebar/order.test.ts +21 -0
- package/src/app/chat/sidebar/order.ts +17 -0
- package/src/app/chat/sidebar/profile-switcher.tsx +516 -0
- package/src/app/chat/sidebar/session-actions-menu.tsx +264 -0
- package/src/app/chat/sidebar/session-row.tsx +257 -0
- package/src/app/chat/sidebar/virtual-session-list.tsx +154 -0
- package/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
- package/src/app/chat/sidebar/workspace-groups.ts +326 -0
- package/src/app/chat/thread-loading.test.ts +34 -0
- package/src/app/chat/thread-loading.ts +26 -0
- package/src/app/command-center/index.tsx +654 -0
- package/src/app/command-palette/index.tsx +513 -0
- package/src/app/command-palette/marketplace-theme-page.tsx +157 -0
- package/src/app/cron/index.tsx +942 -0
- package/src/app/cron/job-state.ts +29 -0
- package/src/app/desktop-controller.tsx +938 -0
- package/src/app/floating-hud.ts +22 -0
- package/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
- package/src/app/gateway/hooks/use-gateway-boot.ts +387 -0
- package/src/app/gateway/hooks/use-gateway-request.ts +138 -0
- package/src/app/hooks/use-keybinds.ts +186 -0
- package/src/app/hooks/use-refresh-hotkey.ts +45 -0
- package/src/app/hooks/use-route-enum-param.ts +38 -0
- package/src/app/index.tsx +1 -0
- package/src/app/layout-constants.ts +13 -0
- package/src/app/messaging/index.tsx +648 -0
- package/src/app/messaging/platform-icon.tsx +93 -0
- package/src/app/model-picker-overlay.tsx +42 -0
- package/src/app/model-visibility-overlay.tsx +31 -0
- package/src/app/overlays/overlay-chrome.tsx +66 -0
- package/src/app/overlays/overlay-search-input.tsx +33 -0
- package/src/app/overlays/overlay-split-layout.tsx +130 -0
- package/src/app/overlays/overlay-view.tsx +91 -0
- package/src/app/page-search-shell.tsx +75 -0
- package/src/app/profiles/create-profile-dialog.tsx +154 -0
- package/src/app/profiles/delete-profile-dialog.tsx +65 -0
- package/src/app/profiles/index.tsx +671 -0
- package/src/app/profiles/rename-profile-dialog.tsx +125 -0
- package/src/app/right-sidebar/files/dnd-manager.ts +27 -0
- package/src/app/right-sidebar/files/ipc.test.ts +100 -0
- package/src/app/right-sidebar/files/ipc.ts +161 -0
- package/src/app/right-sidebar/files/remote-picker.tsx +177 -0
- package/src/app/right-sidebar/files/tree.tsx +224 -0
- package/src/app/right-sidebar/files/use-project-tree.test.ts +190 -0
- package/src/app/right-sidebar/files/use-project-tree.ts +268 -0
- package/src/app/right-sidebar/index.test.tsx +75 -0
- package/src/app/right-sidebar/index.tsx +395 -0
- package/src/app/right-sidebar/store.ts +15 -0
- package/src/app/right-sidebar/terminal/buffer.ts +65 -0
- package/src/app/right-sidebar/terminal/index.tsx +98 -0
- package/src/app/right-sidebar/terminal/persistent.tsx +122 -0
- package/src/app/right-sidebar/terminal/selection.ts +75 -0
- package/src/app/right-sidebar/terminal/use-terminal-session.ts +504 -0
- package/src/app/routes.ts +88 -0
- package/src/app/session/hooks/use-context-suggestions.ts +58 -0
- package/src/app/session/hooks/use-cwd-actions.ts +109 -0
- package/src/app/session/hooks/use-message-stream.ts +957 -0
- package/src/app/session/hooks/use-model-controls.test.tsx +198 -0
- package/src/app/session/hooks/use-model-controls.ts +106 -0
- package/src/app/session/hooks/use-nastech-config.ts +74 -0
- package/src/app/session/hooks/use-preview-routing.test.tsx +168 -0
- package/src/app/session/hooks/use-preview-routing.ts +223 -0
- package/src/app/session/hooks/use-prompt-actions.test.tsx +316 -0
- package/src/app/session/hooks/use-prompt-actions.ts +1030 -0
- package/src/app/session/hooks/use-route-resume.test.tsx +136 -0
- package/src/app/session/hooks/use-route-resume.ts +115 -0
- package/src/app/session/hooks/use-session-actions.test.tsx +119 -0
- package/src/app/session/hooks/use-session-actions.ts +885 -0
- package/src/app/session/hooks/use-session-state-cache.test.tsx +118 -0
- package/src/app/session/hooks/use-session-state-cache.ts +191 -0
- package/src/app/session-picker-overlay.tsx +32 -0
- package/src/app/session-switcher.tsx +107 -0
- package/src/app/settings/about-settings.tsx +173 -0
- package/src/app/settings/appearance-settings.tsx +162 -0
- package/src/app/settings/config-settings.tsx +384 -0
- package/src/app/settings/constants.ts +545 -0
- package/src/app/settings/credential-key-ui.tsx +373 -0
- package/src/app/settings/env-credentials.tsx +198 -0
- package/src/app/settings/env-var-actions-menu.tsx +136 -0
- package/src/app/settings/field-copy.ts +56 -0
- package/src/app/settings/gateway-settings.tsx +620 -0
- package/src/app/settings/helpers.test.ts +138 -0
- package/src/app/settings/helpers.ts +151 -0
- package/src/app/settings/index.tsx +237 -0
- package/src/app/settings/keys-settings.tsx +96 -0
- package/src/app/settings/mcp-settings.tsx +271 -0
- package/src/app/settings/model-settings.test.tsx +157 -0
- package/src/app/settings/model-settings.tsx +559 -0
- package/src/app/settings/notifications-settings.tsx +150 -0
- package/src/app/settings/primitives.tsx +115 -0
- package/src/app/settings/providers-settings.test.tsx +100 -0
- package/src/app/settings/providers-settings.tsx +258 -0
- package/src/app/settings/sessions-settings.tsx +276 -0
- package/src/app/settings/toolset-config-panel.test.tsx +289 -0
- package/src/app/settings/toolset-config-panel.tsx +449 -0
- package/src/app/settings/types.ts +42 -0
- package/src/app/settings/uninstall-section.tsx +185 -0
- package/src/app/settings/use-deep-link-highlight.ts +60 -0
- package/src/app/shell/app-shell.tsx +167 -0
- package/src/app/shell/gateway-menu-panel.tsx +150 -0
- package/src/app/shell/hooks/use-overlay-routing.ts +71 -0
- package/src/app/shell/hooks/use-status-snapshot.ts +57 -0
- package/src/app/shell/hooks/use-statusbar-items.tsx +403 -0
- package/src/app/shell/keybind-panel.tsx +220 -0
- package/src/app/shell/model-edit-submenu.test.tsx +84 -0
- package/src/app/shell/model-edit-submenu.tsx +245 -0
- package/src/app/shell/model-menu-panel.tsx +295 -0
- package/src/app/shell/sidebar-label.tsx +22 -0
- package/src/app/shell/statusbar-controls.tsx +185 -0
- package/src/app/shell/titlebar-controls.tsx +244 -0
- package/src/app/shell/titlebar.test.ts +26 -0
- package/src/app/shell/titlebar.ts +45 -0
- package/src/app/shell/use-group-registry.ts +39 -0
- package/src/app/skills/index.test.tsx +103 -0
- package/src/app/skills/index.tsx +371 -0
- package/src/app/types.ts +99 -0
- package/src/app/updates-overlay.tsx +369 -0
- package/src/components/Backdrop.tsx +114 -0
- package/src/components/assistant-ui/ansi-text.tsx +34 -0
- package/src/components/assistant-ui/clarify-tool.tsx +281 -0
- package/src/components/assistant-ui/directive-text.test.ts +39 -0
- package/src/components/assistant-ui/directive-text.tsx +389 -0
- package/src/components/assistant-ui/markdown-text.test.ts +204 -0
- package/src/components/assistant-ui/markdown-text.tsx +497 -0
- package/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
- package/src/components/assistant-ui/message-render-boundary.tsx +48 -0
- package/src/components/assistant-ui/streaming.test.tsx +739 -0
- package/src/components/assistant-ui/thread-list.tsx +307 -0
- package/src/components/assistant-ui/thread-virtualizer.tsx +512 -0
- package/src/components/assistant-ui/thread.tsx +1474 -0
- package/src/components/assistant-ui/todo-tool.tsx +109 -0
- package/src/components/assistant-ui/tool-approval-group.test.tsx +158 -0
- package/src/components/assistant-ui/tool-approval.test.tsx +81 -0
- package/src/components/assistant-ui/tool-approval.tsx +209 -0
- package/src/components/assistant-ui/tool-fallback-model.test.ts +66 -0
- package/src/components/assistant-ui/tool-fallback-model.ts +1368 -0
- package/src/components/assistant-ui/tool-fallback.tsx +466 -0
- package/src/components/assistant-ui/tooltip-icon-button.tsx +33 -0
- package/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
- package/src/components/assistant-ui/user-message-text.tsx +150 -0
- package/src/components/boot-failure-overlay.tsx +246 -0
- package/src/components/boot-failure-reauth.test.ts +100 -0
- package/src/components/boot-failure-reauth.ts +81 -0
- package/src/components/brand-mark.tsx +19 -0
- package/src/components/chat/activity-timer-text.tsx +24 -0
- package/src/components/chat/activity-timer.test.tsx +43 -0
- package/src/components/chat/activity-timer.ts +64 -0
- package/src/components/chat/code-card.tsx +78 -0
- package/src/components/chat/compact-markdown.tsx +113 -0
- package/src/components/chat/composer-dock.ts +31 -0
- package/src/components/chat/diff-lines.tsx +54 -0
- package/src/components/chat/disclosure-row.tsx +63 -0
- package/src/components/chat/generated-image-context.tsx +19 -0
- package/src/components/chat/generated-image-result.tsx +174 -0
- package/src/components/chat/image-generation-placeholder.tsx +279 -0
- package/src/components/chat/intro-copy.jsonl +75 -0
- package/src/components/chat/intro.tsx +182 -0
- package/src/components/chat/preview-attachment.tsx +125 -0
- package/src/components/chat/shiki-highlighter.tsx +107 -0
- package/src/components/chat/status-row.tsx +70 -0
- package/src/components/chat/status-section.tsx +42 -0
- package/src/components/chat/terminal-output.tsx +50 -0
- package/src/components/chat/zoomable-image.tsx +177 -0
- package/src/components/desktop-install-overlay.tsx +595 -0
- package/src/components/desktop-onboarding-overlay.test.tsx +100 -0
- package/src/components/desktop-onboarding-overlay.tsx +1286 -0
- package/src/components/error-boundary.tsx +77 -0
- package/src/components/gateway-connecting-overlay.test.tsx +143 -0
- package/src/components/gateway-connecting-overlay.tsx +183 -0
- package/src/components/haptics-provider.tsx +19 -0
- package/src/components/language-switcher.test.tsx +53 -0
- package/src/components/language-switcher.tsx +175 -0
- package/src/components/model-picker.tsx +340 -0
- package/src/components/model-visibility-dialog.tsx +155 -0
- package/src/components/notifications.tsx +196 -0
- package/src/components/page-loader.tsx +34 -0
- package/src/components/pane-shell/context.ts +14 -0
- package/src/components/pane-shell/index.ts +4 -0
- package/src/components/pane-shell/pane-shell.test.tsx +333 -0
- package/src/components/pane-shell/pane-shell.tsx +330 -0
- package/src/components/prompt-overlays.tsx +234 -0
- package/src/components/session-picker.tsx +108 -0
- package/src/components/status-dot.tsx +26 -0
- package/src/components/ui/action-status.tsx +25 -0
- package/src/components/ui/alert.tsx +53 -0
- package/src/components/ui/badge.tsx +35 -0
- package/src/components/ui/braille-spinner.tsx +61 -0
- package/src/components/ui/button.tsx +81 -0
- package/src/components/ui/checkbox.tsx +27 -0
- package/src/components/ui/codicon.tsx +20 -0
- package/src/components/ui/command.tsx +111 -0
- package/src/components/ui/confirm-dialog.tsx +109 -0
- package/src/components/ui/context-menu.tsx +141 -0
- package/src/components/ui/control.ts +25 -0
- package/src/components/ui/copy-button.test.tsx +36 -0
- package/src/components/ui/copy-button.tsx +229 -0
- package/src/components/ui/dialog.tsx +152 -0
- package/src/components/ui/disclosure-caret.tsx +20 -0
- package/src/components/ui/dropdown-menu.tsx +291 -0
- package/src/components/ui/error-state.tsx +50 -0
- package/src/components/ui/fade-text.tsx +110 -0
- package/src/components/ui/glyph-spinner.tsx +63 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/kbd.tsx +37 -0
- package/src/components/ui/loader.tsx +558 -0
- package/src/components/ui/log-view.tsx +17 -0
- package/src/components/ui/pagination.tsx +114 -0
- package/src/components/ui/popover.tsx +44 -0
- package/src/components/ui/scroll-area.tsx +43 -0
- package/src/components/ui/search-field.tsx +80 -0
- package/src/components/ui/segmented-control.tsx +51 -0
- package/src/components/ui/select.tsx +92 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +116 -0
- package/src/components/ui/sidebar.tsx +674 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/switch.tsx +49 -0
- package/src/components/ui/tabs.tsx +36 -0
- package/src/components/ui/text-tab.tsx +43 -0
- package/src/components/ui/textarea.tsx +11 -0
- package/src/components/ui/tool-icon.tsx +65 -0
- package/src/components/ui/tooltip.tsx +69 -0
- package/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
- package/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
- package/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
- package/src/global.d.ts +457 -0
- package/src/hooks/use-image-download.ts +85 -0
- package/src/hooks/use-media-query.ts +24 -0
- package/src/hooks/use-mobile.ts +3 -0
- package/src/hooks/use-resize-observer.ts +38 -0
- package/src/hooks/use-worktree-info.ts +68 -0
- package/src/i18n/catalog.ts +12 -0
- package/src/i18n/context.test.tsx +232 -0
- package/src/i18n/context.tsx +183 -0
- package/src/i18n/define-locale.ts +41 -0
- package/src/i18n/en.ts +1779 -0
- package/src/i18n/index.ts +20 -0
- package/src/i18n/ja.ts +1890 -0
- package/src/i18n/languages.test.ts +43 -0
- package/src/i18n/languages.ts +86 -0
- package/src/i18n/runtime.test.ts +75 -0
- package/src/i18n/runtime.ts +53 -0
- package/src/i18n/types.ts +1452 -0
- package/src/i18n/zh-hant.ts +1849 -0
- package/src/i18n/zh.ts +1923 -0
- package/src/lib/ansi.test.ts +123 -0
- package/src/lib/ansi.ts +175 -0
- package/src/lib/chat-messages.test.ts +708 -0
- package/src/lib/chat-messages.ts +885 -0
- package/src/lib/chat-runtime.test.ts +18 -0
- package/src/lib/chat-runtime.ts +335 -0
- package/src/lib/clipboard.ts +28 -0
- package/src/lib/commit-changelog.test.ts +114 -0
- package/src/lib/commit-changelog.ts +177 -0
- package/src/lib/completion-sound.ts +519 -0
- package/src/lib/desktop-fs.test.ts +116 -0
- package/src/lib/desktop-fs.ts +113 -0
- package/src/lib/desktop-slash-commands.test.ts +126 -0
- package/src/lib/desktop-slash-commands.ts +286 -0
- package/src/lib/embedded-images.test.ts +35 -0
- package/src/lib/embedded-images.ts +60 -0
- package/src/lib/external-link.test.tsx +168 -0
- package/src/lib/external-link.tsx +303 -0
- package/src/lib/gateway-events.test.ts +27 -0
- package/src/lib/gateway-events.ts +49 -0
- package/src/lib/gateway-ws-url.test.ts +78 -0
- package/src/lib/gateway-ws-url.ts +91 -0
- package/src/lib/generated-images.test.ts +97 -0
- package/src/lib/generated-images.ts +116 -0
- package/src/lib/haptics.ts +129 -0
- package/src/lib/icons.ts +203 -0
- package/src/lib/incremental-external-store-runtime.ts +188 -0
- package/src/lib/katex-memo.ts +260 -0
- package/src/lib/keybinds/actions.ts +125 -0
- package/src/lib/keybinds/combo.test.ts +86 -0
- package/src/lib/keybinds/combo.ts +169 -0
- package/src/lib/local-preview.ts +126 -0
- package/src/lib/markdown-code.test.ts +23 -0
- package/src/lib/markdown-code.ts +195 -0
- package/src/lib/markdown-preprocess.ts +386 -0
- package/src/lib/media.remote.test.ts +58 -0
- package/src/lib/media.ts +111 -0
- package/src/lib/model-status-label.test.ts +31 -0
- package/src/lib/model-status-label.ts +103 -0
- package/src/lib/mutable-ref.ts +6 -0
- package/src/lib/preview-targets.test.ts +27 -0
- package/src/lib/preview-targets.ts +63 -0
- package/src/lib/profile-color.ts +58 -0
- package/src/lib/provider-setup-errors.test.ts +26 -0
- package/src/lib/provider-setup-errors.ts +12 -0
- package/src/lib/query-client.ts +13 -0
- package/src/lib/remend-tail.test.ts +105 -0
- package/src/lib/remend-tail.ts +108 -0
- package/src/lib/runtime-readiness.test.ts +65 -0
- package/src/lib/runtime-readiness.ts +147 -0
- package/src/lib/session-export.ts +57 -0
- package/src/lib/session-search.test.ts +58 -0
- package/src/lib/session-search.ts +19 -0
- package/src/lib/session-source.ts +62 -0
- package/src/lib/speech-text.ts +35 -0
- package/src/lib/statusbar.ts +91 -0
- package/src/lib/storage.test.ts +25 -0
- package/src/lib/storage.ts +107 -0
- package/src/lib/todos.test.ts +35 -0
- package/src/lib/todos.ts +51 -0
- package/src/lib/tool-result-summary.test.ts +106 -0
- package/src/lib/tool-result-summary.ts +467 -0
- package/src/lib/update-copy.test.ts +38 -0
- package/src/lib/update-copy.ts +44 -0
- package/src/lib/use-enter-animation.ts +100 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/voice-playback.ts +128 -0
- package/src/lib/yolo-session.ts +26 -0
- package/src/main.tsx +43 -0
- package/src/nastech.test.ts +49 -0
- package/src/nastech.ts +718 -0
- package/src/store/activity.ts +100 -0
- package/src/store/boot.ts +91 -0
- package/src/store/clarify.test.ts +81 -0
- package/src/store/clarify.ts +69 -0
- package/src/store/command-palette.ts +20 -0
- package/src/store/compaction.test.ts +53 -0
- package/src/store/compaction.ts +38 -0
- package/src/store/completion-sound.ts +32 -0
- package/src/store/composer-input-history.test.ts +147 -0
- package/src/store/composer-input-history.ts +158 -0
- package/src/store/composer-queue.test.ts +148 -0
- package/src/store/composer-queue.ts +239 -0
- package/src/store/composer-status.test.ts +99 -0
- package/src/store/composer-status.ts +277 -0
- package/src/store/composer.test.ts +106 -0
- package/src/store/composer.ts +184 -0
- package/src/store/cron.ts +19 -0
- package/src/store/gateway.ts +290 -0
- package/src/store/haptics.ts +17 -0
- package/src/store/keybinds.ts +139 -0
- package/src/store/layout.ts +176 -0
- package/src/store/model-presets.test.ts +51 -0
- package/src/store/model-presets.ts +86 -0
- package/src/store/model-visibility.test.ts +37 -0
- package/src/store/model-visibility.ts +108 -0
- package/src/store/native-notifications.test.ts +192 -0
- package/src/store/native-notifications.ts +203 -0
- package/src/store/notifications.ts +165 -0
- package/src/store/onboarding.test.ts +372 -0
- package/src/store/onboarding.ts +866 -0
- package/src/store/panes.test.ts +146 -0
- package/src/store/panes.ts +145 -0
- package/src/store/preview.test.ts +135 -0
- package/src/store/preview.ts +466 -0
- package/src/store/profile.test.ts +89 -0
- package/src/store/profile.ts +365 -0
- package/src/store/prompts.test.ts +121 -0
- package/src/store/prompts.ts +115 -0
- package/src/store/session-switcher.test.ts +115 -0
- package/src/store/session-switcher.ts +128 -0
- package/src/store/session-sync.ts +25 -0
- package/src/store/session.test.ts +131 -0
- package/src/store/session.ts +255 -0
- package/src/store/subagents.test.ts +111 -0
- package/src/store/subagents.ts +260 -0
- package/src/store/thread-scroll.ts +46 -0
- package/src/store/todos.test.ts +47 -0
- package/src/store/todos.ts +64 -0
- package/src/store/tool-diffs.ts +23 -0
- package/src/store/tool-dismiss.ts +45 -0
- package/src/store/tool-view.ts +91 -0
- package/src/store/translucency.ts +38 -0
- package/src/store/updates.test.ts +77 -0
- package/src/store/updates.ts +315 -0
- package/src/store/voice-playback.ts +24 -0
- package/src/store/windows.test.ts +143 -0
- package/src/store/windows.ts +77 -0
- package/src/styles.css +1235 -0
- package/src/themes/color.ts +142 -0
- package/src/themes/context.tsx +339 -0
- package/src/themes/index.ts +3 -0
- package/src/themes/install.test.ts +119 -0
- package/src/themes/install.ts +95 -0
- package/src/themes/presets.test.ts +33 -0
- package/src/themes/presets.ts +293 -0
- package/src/themes/profile-theme.test.ts +41 -0
- package/src/themes/types.ts +66 -0
- package/src/themes/use-skin-command.ts +60 -0
- package/src/themes/user-themes.test.ts +63 -0
- package/src/themes/user-themes.ts +122 -0
- package/src/themes/vscode.test.ts +171 -0
- package/src/themes/vscode.ts +343 -0
- package/src/types/nastech.ts +646 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +25 -0
- package/vite.config.ts +56 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
4
|
+
import { Loader } from '@/components/ui/loader'
|
|
5
|
+
import { LogView } from '@/components/ui/log-view'
|
|
6
|
+
import type {
|
|
7
|
+
DesktopBootstrapEvent,
|
|
8
|
+
DesktopBootstrapStageDescriptor,
|
|
9
|
+
DesktopBootstrapStageResult,
|
|
10
|
+
DesktopBootstrapStageState,
|
|
11
|
+
DesktopBootstrapState
|
|
12
|
+
} from '@/global'
|
|
13
|
+
import { useI18n } from '@/i18n'
|
|
14
|
+
import { AlertTriangle, Check, ChevronDown, ChevronRight, Loader2 } from '@/lib/icons'
|
|
15
|
+
import { cn } from '@/lib/utils'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* DesktopInstallOverlay
|
|
19
|
+
*
|
|
20
|
+
* Renders the first-launch install progress for NasTech Agent. Mounted always;
|
|
21
|
+
* shows itself only when main.cjs reports an in-flight bootstrap (state.active)
|
|
22
|
+
* OR an error from a completed-failed bootstrap (state.error). When the
|
|
23
|
+
* bootstrap finishes successfully the overlay fades out and the rest of the
|
|
24
|
+
* app (existing onboarding overlay -> main UI) takes over.
|
|
25
|
+
*
|
|
26
|
+
* Subscribes to two channels:
|
|
27
|
+
* - getBootstrapState() -- initial snapshot on mount
|
|
28
|
+
* - onBootstrapEvent(callback) -- live event stream
|
|
29
|
+
*
|
|
30
|
+
* The reducer is intentionally simple: every event mutates an in-component
|
|
31
|
+
* snapshot the same way main.cjs mutates its server-side snapshot. We don't
|
|
32
|
+
* try to reconcile -- if we miss an event (shouldn't happen) the initial
|
|
33
|
+
* getBootstrapState() call will resync the picture on the next render.
|
|
34
|
+
*
|
|
35
|
+
* Stages flagged needs_user_input render with a deliberately subdued style:
|
|
36
|
+
* they're expected to come back as skipped=true (install.ps1 short-circuits
|
|
37
|
+
* them under -NonInteractive). The post-install configuration flow that
|
|
38
|
+
* those stages cover (API key, model, persona, gateway autostart) is handled
|
|
39
|
+
* by the existing DesktopOnboardingOverlay, NOT by the install overlay.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
interface DesktopInstallOverlayProps {
|
|
43
|
+
/** When false, the overlay never renders -- useful for dev when we want
|
|
44
|
+
* to suppress it entirely. */
|
|
45
|
+
enabled?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface StageRowProps {
|
|
49
|
+
descriptor: DesktopBootstrapStageDescriptor
|
|
50
|
+
result: DesktopBootstrapStageResult | undefined
|
|
51
|
+
isCurrent: boolean
|
|
52
|
+
now: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatStageName(name: string): string {
|
|
56
|
+
// 'system-packages' -> 'System packages'; 'uv' stays 'uv'
|
|
57
|
+
if (name.length <= 3) {
|
|
58
|
+
return name
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return name
|
|
62
|
+
.split('-')
|
|
63
|
+
.map((word, i) => (i === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word))
|
|
64
|
+
.join(' ')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function formatDuration(ms: number | null | undefined): string {
|
|
68
|
+
if (typeof ms !== 'number' || !Number.isFinite(ms)) {
|
|
69
|
+
return ''
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (ms < 1000) {
|
|
73
|
+
return `${ms} ms`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const s = ms / 1000
|
|
77
|
+
|
|
78
|
+
if (s < 60) {
|
|
79
|
+
return `${s.toFixed(1)}s`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const m = Math.floor(s / 60)
|
|
83
|
+
const rs = Math.round(s - m * 60)
|
|
84
|
+
|
|
85
|
+
return `${m}m ${rs}s`
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Live elapsed for a running stage, as m:ss (or s for sub-minute).
|
|
89
|
+
function formatElapsed(ms: number): string {
|
|
90
|
+
const s = Math.max(0, Math.floor(ms / 1000))
|
|
91
|
+
|
|
92
|
+
if (s < 60) {
|
|
93
|
+
return `${s}s`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const m = Math.floor(s / 60)
|
|
97
|
+
|
|
98
|
+
return `${m}:${String(s - m * 60).padStart(2, '0')}`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function StageRow({ descriptor, result, isCurrent, now }: StageRowProps) {
|
|
102
|
+
const { t } = useI18n()
|
|
103
|
+
const copy = t.install
|
|
104
|
+
const state: DesktopBootstrapStageState = result?.state || 'pending'
|
|
105
|
+
|
|
106
|
+
const elapsed =
|
|
107
|
+
state === 'running' && typeof result?.startedAt === 'number' ? formatElapsed(now - result.startedAt) : ''
|
|
108
|
+
|
|
109
|
+
const icon = useMemo(() => {
|
|
110
|
+
switch (state) {
|
|
111
|
+
case 'running':
|
|
112
|
+
return <Loader2 className="h-4 w-4 animate-spin text-primary" />
|
|
113
|
+
|
|
114
|
+
case 'succeeded':
|
|
115
|
+
return <Check className="h-4 w-4 text-emerald-600" />
|
|
116
|
+
|
|
117
|
+
case 'skipped':
|
|
118
|
+
return <Check className="h-4 w-4 text-muted-foreground" />
|
|
119
|
+
|
|
120
|
+
case 'failed':
|
|
121
|
+
return <AlertTriangle className="h-4 w-4 text-destructive" />
|
|
122
|
+
|
|
123
|
+
case 'pending':
|
|
124
|
+
|
|
125
|
+
default:
|
|
126
|
+
return <div className="h-2 w-2 rounded-full border border-muted-foreground/40" />
|
|
127
|
+
}
|
|
128
|
+
}, [state])
|
|
129
|
+
|
|
130
|
+
const reason = result?.json?.reason || result?.error || null
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<li
|
|
134
|
+
className={cn(
|
|
135
|
+
'flex items-start gap-3 rounded-md px-3 py-2 transition-colors',
|
|
136
|
+
isCurrent && 'bg-muted/60',
|
|
137
|
+
state === 'failed' && 'bg-destructive/10'
|
|
138
|
+
)}
|
|
139
|
+
>
|
|
140
|
+
<div className="flex h-5 w-5 flex-shrink-0 items-center justify-center">{icon}</div>
|
|
141
|
+
<div className="min-w-0 flex-1">
|
|
142
|
+
<div className="flex items-baseline justify-between gap-2">
|
|
143
|
+
<span className={cn('truncate text-sm font-medium', state === 'pending' && 'text-muted-foreground')}>
|
|
144
|
+
{formatStageName(descriptor.name)}
|
|
145
|
+
</span>
|
|
146
|
+
<span className="flex-shrink-0 text-xs tabular-nums text-muted-foreground">
|
|
147
|
+
{state === 'running'
|
|
148
|
+
? elapsed
|
|
149
|
+
? `${copy.stageStates[state]} · ${elapsed}`
|
|
150
|
+
: copy.stageStates[state]
|
|
151
|
+
: null}
|
|
152
|
+
{state === 'succeeded' || state === 'skipped' ? formatDuration(result?.durationMs) : null}
|
|
153
|
+
{state === 'failed' ? copy.stageStates[state] : null}
|
|
154
|
+
</span>
|
|
155
|
+
</div>
|
|
156
|
+
{reason && state !== 'pending' && <p className="mt-0.5 truncate text-xs text-muted-foreground">{reason}</p>}
|
|
157
|
+
</div>
|
|
158
|
+
</li>
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const EMPTY_STATE: DesktopBootstrapState = {
|
|
163
|
+
active: false,
|
|
164
|
+
manifest: null,
|
|
165
|
+
stages: {},
|
|
166
|
+
error: null,
|
|
167
|
+
log: [],
|
|
168
|
+
startedAt: null,
|
|
169
|
+
completedAt: null,
|
|
170
|
+
unsupportedPlatform: null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function applyEvent(state: DesktopBootstrapState, ev: DesktopBootstrapEvent): DesktopBootstrapState {
|
|
174
|
+
if (ev.type === 'manifest') {
|
|
175
|
+
const stages: Record<string, DesktopBootstrapStageResult> = {}
|
|
176
|
+
|
|
177
|
+
for (const stage of ev.stages) {
|
|
178
|
+
stages[stage.name] = { state: 'pending', durationMs: null, startedAt: null, json: null, error: null }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
...state,
|
|
183
|
+
active: true,
|
|
184
|
+
manifest: { type: 'manifest', stages: ev.stages, protocolVersion: ev.protocolVersion },
|
|
185
|
+
stages,
|
|
186
|
+
error: null,
|
|
187
|
+
startedAt: state.startedAt || Date.now()
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (ev.type === 'stage') {
|
|
192
|
+
const prev = state.stages[ev.name]
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
...state,
|
|
196
|
+
stages: {
|
|
197
|
+
...state.stages,
|
|
198
|
+
[ev.name]: {
|
|
199
|
+
state: ev.state,
|
|
200
|
+
durationMs: ev.durationMs ?? null,
|
|
201
|
+
// Stamp the start time on the running transition so the UI can show
|
|
202
|
+
// a live elapsed timer; preserve it across repeated running events.
|
|
203
|
+
startedAt: ev.state === 'running' ? (prev?.startedAt ?? Date.now()) : (prev?.startedAt ?? null),
|
|
204
|
+
json: ev.json ?? null,
|
|
205
|
+
error: ev.error ?? null
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (ev.type === 'log') {
|
|
212
|
+
const next = state.log.concat({ ts: Date.now(), stage: ev.stage ?? null, line: ev.line, stream: ev.stream })
|
|
213
|
+
|
|
214
|
+
while (next.length > 500) {
|
|
215
|
+
next.shift()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { ...state, log: next }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (ev.type === 'complete') {
|
|
222
|
+
return { ...state, active: false, completedAt: Date.now(), error: null }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (ev.type === 'failed') {
|
|
226
|
+
return { ...state, active: false, error: ev.error || 'unknown error' }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (ev.type === 'unsupported-platform') {
|
|
230
|
+
return {
|
|
231
|
+
...state,
|
|
232
|
+
active: false,
|
|
233
|
+
unsupportedPlatform: {
|
|
234
|
+
platform: ev.platform,
|
|
235
|
+
activeRoot: ev.activeRoot,
|
|
236
|
+
installCommand: ev.installCommand,
|
|
237
|
+
docsUrl: ev.docsUrl
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return state
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function DesktopInstallOverlay({ enabled = true }: DesktopInstallOverlayProps) {
|
|
246
|
+
const { t } = useI18n()
|
|
247
|
+
const copy = t.install
|
|
248
|
+
const [state, setState] = useState<DesktopBootstrapState>(EMPTY_STATE)
|
|
249
|
+
const [logOpen, setLogOpen] = useState(false)
|
|
250
|
+
const [copied, setCopied] = useState(false)
|
|
251
|
+
const [cancelling, setCancelling] = useState(false)
|
|
252
|
+
const [now, setNow] = useState(() => Date.now())
|
|
253
|
+
const logEndRef = useRef<HTMLDivElement | null>(null)
|
|
254
|
+
|
|
255
|
+
// Tick once a second while a bootstrap is in flight so running steps show a
|
|
256
|
+
// live elapsed timer. Stops when nothing is active to avoid idle renders.
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
if (!state.active) {
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const id = window.setInterval(() => setNow(Date.now()), 1000)
|
|
263
|
+
|
|
264
|
+
return () => window.clearInterval(id)
|
|
265
|
+
}, [state.active])
|
|
266
|
+
|
|
267
|
+
// Subscribe to bootstrap events + load initial snapshot
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
if (!enabled) {
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const desktop = window.NASTECHDesktop
|
|
274
|
+
|
|
275
|
+
if (!desktop || typeof desktop.onBootstrapEvent !== 'function') {
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let cancelled = false
|
|
280
|
+
|
|
281
|
+
desktop
|
|
282
|
+
.getBootstrapState()
|
|
283
|
+
.then(snapshot => {
|
|
284
|
+
if (!cancelled && snapshot) {
|
|
285
|
+
setState(snapshot)
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
.catch(() => {
|
|
289
|
+
// Older Electron build without the IPC handler -- bootstrap UI just
|
|
290
|
+
// stays empty, app falls through to existing onboarding flow.
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
const off = desktop.onBootstrapEvent(ev => setState(prev => applyEvent(prev, ev)))
|
|
294
|
+
|
|
295
|
+
return () => {
|
|
296
|
+
cancelled = true
|
|
297
|
+
off?.()
|
|
298
|
+
}
|
|
299
|
+
}, [enabled])
|
|
300
|
+
|
|
301
|
+
// Autoscroll log to bottom when new lines arrive AND the log is open
|
|
302
|
+
useEffect(() => {
|
|
303
|
+
if (logOpen && logEndRef.current) {
|
|
304
|
+
logEndRef.current.scrollIntoView({ behavior: 'auto', block: 'end' })
|
|
305
|
+
}
|
|
306
|
+
}, [state.log.length, logOpen])
|
|
307
|
+
|
|
308
|
+
// Auto-expand the log panel when a bootstrap fails so the user immediately
|
|
309
|
+
// sees the install.ps1 output. Without this, the failure block shows just
|
|
310
|
+
// the top-level error message and the user has to click "Show installer
|
|
311
|
+
// output" to see WHY the stage failed.
|
|
312
|
+
useEffect(() => {
|
|
313
|
+
if (state.error) {
|
|
314
|
+
setLogOpen(true)
|
|
315
|
+
}
|
|
316
|
+
}, [state.error])
|
|
317
|
+
|
|
318
|
+
// Mount logic: show whenever a bootstrap is in flight, completed-with-error,
|
|
319
|
+
// or actively running with a manifest. Hide entirely after a successful
|
|
320
|
+
// completion so the rest of the UI can take over.
|
|
321
|
+
const shouldShow = useMemo(() => {
|
|
322
|
+
if (!enabled) {
|
|
323
|
+
return false
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (state.active) {
|
|
327
|
+
return true
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (state.error) {
|
|
331
|
+
return true
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (state.unsupportedPlatform) {
|
|
335
|
+
return true
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return false
|
|
339
|
+
}, [enabled, state.active, state.error, state.unsupportedPlatform])
|
|
340
|
+
|
|
341
|
+
if (!shouldShow) {
|
|
342
|
+
return null
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Unsupported-platform branch: macOS/Linux packaged builds hit this when
|
|
346
|
+
// there's no NasTech Agent installed yet and we can't drive install.sh
|
|
347
|
+
// (no stage protocol equivalent yet). Show a copy-paste install command
|
|
348
|
+
// and the docs URL; user runs it from Terminal and relaunches the app.
|
|
349
|
+
if (state.unsupportedPlatform) {
|
|
350
|
+
const ups = state.unsupportedPlatform
|
|
351
|
+
const platformLabel = ups.platform === 'darwin' ? 'macOS' : ups.platform === 'linux' ? 'Linux' : ups.platform
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<div className="fixed inset-0 z-[1400] flex items-center justify-center bg-background/90 backdrop-blur-md">
|
|
355
|
+
<div className="w-full max-w-xl rounded-xl border border-(--stroke-nastech) bg-card p-8 shadow-nastech">
|
|
356
|
+
<h2 className="text-2xl font-semibold tracking-tight">{copy.oneTimeTitle}</h2>
|
|
357
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
358
|
+
{copy.unsupportedDesc(platformLabel)}
|
|
359
|
+
</p>
|
|
360
|
+
|
|
361
|
+
<div className="mt-4">
|
|
362
|
+
<div className="mb-1.5 text-xs font-medium text-muted-foreground">{copy.installCommand}</div>
|
|
363
|
+
<pre className="overflow-x-auto rounded-md border bg-muted/50 px-3 py-2.5 font-mono text-[12px]">
|
|
364
|
+
<code>{ups.installCommand}</code>
|
|
365
|
+
</pre>
|
|
366
|
+
<div className="mt-2 flex items-center gap-2">
|
|
367
|
+
<Button
|
|
368
|
+
onClick={() => {
|
|
369
|
+
void navigator.clipboard?.writeText(ups.installCommand).catch(() => {})
|
|
370
|
+
}}
|
|
371
|
+
size="sm"
|
|
372
|
+
variant="secondary"
|
|
373
|
+
>
|
|
374
|
+
{copy.copyCommand}
|
|
375
|
+
</Button>
|
|
376
|
+
<Button
|
|
377
|
+
onClick={() => {
|
|
378
|
+
window.NASTECHDesktop?.openExternal?.(ups.docsUrl)
|
|
379
|
+
}}
|
|
380
|
+
size="sm"
|
|
381
|
+
variant="ghost"
|
|
382
|
+
>
|
|
383
|
+
{copy.viewDocs}
|
|
384
|
+
</Button>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
<div className="mt-6 flex items-center justify-between border-t pt-4">
|
|
389
|
+
<span className="text-xs text-muted-foreground">
|
|
390
|
+
{copy.installTo} <code className="rounded bg-muted/50 px-1 py-0.5 font-mono">{ups.activeRoot}</code>
|
|
391
|
+
</span>
|
|
392
|
+
<Button onClick={() => window.location.reload()} size="sm" variant="default">
|
|
393
|
+
{copy.retryAfterRun}
|
|
394
|
+
</Button>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const stages = state.manifest?.stages || []
|
|
402
|
+
const currentStage = stages.find(s => state.stages[s.name]?.state === 'running')?.name
|
|
403
|
+
|
|
404
|
+
const completedCount = stages.filter(
|
|
405
|
+
s => state.stages[s.name]?.state === 'succeeded' || state.stages[s.name]?.state === 'skipped'
|
|
406
|
+
).length
|
|
407
|
+
|
|
408
|
+
const totalCount = stages.length
|
|
409
|
+
const failed = Boolean(state.error)
|
|
410
|
+
const progressPct = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0
|
|
411
|
+
const currentStartedAt = currentStage ? state.stages[currentStage]?.startedAt : null
|
|
412
|
+
const currentElapsed = typeof currentStartedAt === 'number' ? formatElapsed(now - currentStartedAt) : ''
|
|
413
|
+
|
|
414
|
+
return (
|
|
415
|
+
<div className="fixed inset-0 z-[1400] flex items-center justify-center bg-background/90 backdrop-blur-md p-4">
|
|
416
|
+
<div className="flex w-full max-w-2xl max-h-[90vh] flex-col rounded-xl border border-(--stroke-nastech) bg-card shadow-nastech">
|
|
417
|
+
{/* Header -- always visible, never scrolls */}
|
|
418
|
+
<div className="flex-shrink-0 p-8 pb-4">
|
|
419
|
+
<h2 className="text-2xl font-semibold tracking-tight">
|
|
420
|
+
{failed ? copy.failedTitle : state.active ? copy.settingUpTitle : copy.finishingTitle}
|
|
421
|
+
</h2>
|
|
422
|
+
<p className="mt-1.5 text-sm text-muted-foreground">
|
|
423
|
+
{failed ? copy.failedDesc : copy.activeDesc}
|
|
424
|
+
</p>
|
|
425
|
+
</div>
|
|
426
|
+
|
|
427
|
+
{/* Scrollable middle: progress, stages, error block, log */}
|
|
428
|
+
<div className="min-h-0 flex-1 overflow-y-auto px-8 pb-2">
|
|
429
|
+
{totalCount > 0 && (
|
|
430
|
+
<div className="mb-4">
|
|
431
|
+
<div className="mb-1 flex items-center justify-between text-xs text-muted-foreground">
|
|
432
|
+
<span>
|
|
433
|
+
{copy.progress(completedCount, totalCount)}
|
|
434
|
+
{currentStage && copy.currentStage(formatStageName(currentStage))}
|
|
435
|
+
{currentElapsed && ` (${currentElapsed})`}
|
|
436
|
+
</span>
|
|
437
|
+
<span className="tabular-nums">{progressPct}%</span>
|
|
438
|
+
</div>
|
|
439
|
+
<div className="h-1.5 w-full overflow-hidden rounded-full bg-muted">
|
|
440
|
+
<div
|
|
441
|
+
className={cn('h-full transition-all duration-300', failed ? 'bg-destructive' : 'bg-primary')}
|
|
442
|
+
style={{ width: `${progressPct}%` }}
|
|
443
|
+
/>
|
|
444
|
+
</div>
|
|
445
|
+
</div>
|
|
446
|
+
)}
|
|
447
|
+
|
|
448
|
+
{totalCount === 0 && state.active && (
|
|
449
|
+
<div className="mb-4 flex items-center gap-2.5 text-sm text-muted-foreground">
|
|
450
|
+
<Loader className="size-5" type="lemniscate-bloom" />
|
|
451
|
+
<span>{copy.fetchingManifest}</span>
|
|
452
|
+
</div>
|
|
453
|
+
)}
|
|
454
|
+
|
|
455
|
+
{failed && state.error && (
|
|
456
|
+
<div className="mb-4 rounded-md border border-destructive/30 bg-destructive/10 p-3 text-sm">
|
|
457
|
+
<div className="mb-1 flex items-center gap-1.5 font-medium text-destructive">
|
|
458
|
+
<AlertTriangle className="h-4 w-4" />
|
|
459
|
+
<span>{copy.error}</span>
|
|
460
|
+
</div>
|
|
461
|
+
<p className="whitespace-pre-wrap break-words text-foreground/90">{state.error}</p>
|
|
462
|
+
</div>
|
|
463
|
+
)}
|
|
464
|
+
|
|
465
|
+
{stages.length > 0 && (
|
|
466
|
+
<ol className="mb-4 space-y-1">
|
|
467
|
+
{stages.map(stage => (
|
|
468
|
+
<StageRow
|
|
469
|
+
descriptor={stage}
|
|
470
|
+
isCurrent={stage.name === currentStage}
|
|
471
|
+
key={stage.name}
|
|
472
|
+
now={now}
|
|
473
|
+
result={state.stages[stage.name]}
|
|
474
|
+
/>
|
|
475
|
+
))}
|
|
476
|
+
</ol>
|
|
477
|
+
)}
|
|
478
|
+
|
|
479
|
+
<div className="pt-3">
|
|
480
|
+
<Button
|
|
481
|
+
className="-ml-2 text-muted-foreground hover:text-foreground"
|
|
482
|
+
onClick={() => setLogOpen(v => !v)}
|
|
483
|
+
size="xs"
|
|
484
|
+
type="button"
|
|
485
|
+
variant="ghost"
|
|
486
|
+
>
|
|
487
|
+
{logOpen ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
|
|
488
|
+
<span>{logOpen ? copy.hideOutput : copy.showOutput}</span>
|
|
489
|
+
<span className="ml-1 tabular-nums">
|
|
490
|
+
({copy.lines(state.log.length)})
|
|
491
|
+
</span>
|
|
492
|
+
</Button>
|
|
493
|
+
|
|
494
|
+
{logOpen && (
|
|
495
|
+
<LogView className={cn('mt-2', failed ? 'max-h-96' : 'max-h-64')}>
|
|
496
|
+
{state.log.length === 0 ? (
|
|
497
|
+
<div>{copy.noOutput}</div>
|
|
498
|
+
) : (
|
|
499
|
+
<>
|
|
500
|
+
{state.log.map((entry, i) => (
|
|
501
|
+
<div className={cn(entry.stream === 'stderr' && 'text-muted-foreground/70')} key={i}>
|
|
502
|
+
{entry.stage ? <span className="text-muted-foreground/60">[{entry.stage}] </span> : null}
|
|
503
|
+
<span>{entry.line}</span>
|
|
504
|
+
</div>
|
|
505
|
+
))}
|
|
506
|
+
<div ref={logEndRef} />
|
|
507
|
+
</>
|
|
508
|
+
)}
|
|
509
|
+
</LogView>
|
|
510
|
+
)}
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
|
|
514
|
+
{/* Active footer: let the user actually cancel a running install. */}
|
|
515
|
+
{state.active && !failed && (
|
|
516
|
+
<div className="flex-shrink-0 bg-card p-4">
|
|
517
|
+
<div className="flex items-center justify-end">
|
|
518
|
+
<Button
|
|
519
|
+
disabled={cancelling}
|
|
520
|
+
onClick={async () => {
|
|
521
|
+
setCancelling(true)
|
|
522
|
+
|
|
523
|
+
try {
|
|
524
|
+
await window.NASTECHDesktop?.cancelBootstrap?.()
|
|
525
|
+
} catch {
|
|
526
|
+
// ignore -- the failed/cancelled event will surface the result
|
|
527
|
+
}
|
|
528
|
+
}}
|
|
529
|
+
size="sm"
|
|
530
|
+
variant="ghost"
|
|
531
|
+
>
|
|
532
|
+
{cancelling ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
|
|
533
|
+
{cancelling ? copy.cancelling : copy.cancelInstall}
|
|
534
|
+
</Button>
|
|
535
|
+
</div>
|
|
536
|
+
</div>
|
|
537
|
+
)}
|
|
538
|
+
|
|
539
|
+
{/* Footer -- always visible, never scrolls; only renders on failure */}
|
|
540
|
+
{failed && (
|
|
541
|
+
<div className="flex-shrink-0 bg-card p-4">
|
|
542
|
+
<div className="flex items-center justify-between gap-2">
|
|
543
|
+
<span className="text-xs text-muted-foreground">
|
|
544
|
+
{copy.transcriptSaved}{' '}
|
|
545
|
+
<code className="rounded bg-muted/50 px-1 py-0.5 font-mono">%LOCALAPPDATA%\NASTECH\logs\</code>
|
|
546
|
+
</span>
|
|
547
|
+
<div className="flex gap-2">
|
|
548
|
+
<Button
|
|
549
|
+
onClick={async () => {
|
|
550
|
+
const text = state.log
|
|
551
|
+
.map(entry => (entry.stage ? `[${entry.stage}] ${entry.line}` : entry.line))
|
|
552
|
+
.join('\n')
|
|
553
|
+
|
|
554
|
+
const fullText = state.error ? `Error: ${state.error}\n\n${text}` : text
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
await navigator.clipboard.writeText(fullText)
|
|
558
|
+
setCopied(true)
|
|
559
|
+
window.setTimeout(() => setCopied(false), 1500)
|
|
560
|
+
} catch {
|
|
561
|
+
// ignore -- some environments forbid clipboard writes
|
|
562
|
+
}
|
|
563
|
+
}}
|
|
564
|
+
size="sm"
|
|
565
|
+
variant="secondary"
|
|
566
|
+
>
|
|
567
|
+
{copied ? copy.copiedOutput : copy.copyOutput}
|
|
568
|
+
</Button>
|
|
569
|
+
<Button
|
|
570
|
+
onClick={async () => {
|
|
571
|
+
// Tell main.cjs to clear its latched failure BEFORE we
|
|
572
|
+
// reload. Otherwise the renderer reload calls getConnection
|
|
573
|
+
// and main short-circuits to the latched error without
|
|
574
|
+
// re-running install.ps1.
|
|
575
|
+
try {
|
|
576
|
+
await window.NASTECHDesktop?.resetBootstrap?.()
|
|
577
|
+
} catch {
|
|
578
|
+
// best-effort -- continue with reload regardless
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
window.location.reload()
|
|
582
|
+
}}
|
|
583
|
+
size="sm"
|
|
584
|
+
variant="default"
|
|
585
|
+
>
|
|
586
|
+
{copy.reloadRetry}
|
|
587
|
+
</Button>
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
</div>
|
|
591
|
+
)}
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
)
|
|
595
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { $desktopOnboarding, type DesktopOnboardingState, type OnboardingContext } from '@/store/onboarding'
|
|
5
|
+
import type { OAuthProvider } from '@/types/nastech'
|
|
6
|
+
|
|
7
|
+
import { Picker } from './desktop-onboarding-overlay'
|
|
8
|
+
|
|
9
|
+
function provider(id: string, name = id): OAuthProvider {
|
|
10
|
+
return {
|
|
11
|
+
cli_command: `NASTECH login ${id}`,
|
|
12
|
+
docs_url: `https://example.com/${id}`,
|
|
13
|
+
flow: 'pkce',
|
|
14
|
+
id,
|
|
15
|
+
name,
|
|
16
|
+
status: { logged_in: false }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function setProviders(providers: OAuthProvider[]) {
|
|
21
|
+
$desktopOnboarding.set({
|
|
22
|
+
configured: false,
|
|
23
|
+
flow: { status: 'idle' },
|
|
24
|
+
mode: 'oauth',
|
|
25
|
+
providers,
|
|
26
|
+
reason: null,
|
|
27
|
+
requested: false,
|
|
28
|
+
firstRunSkipped: false,
|
|
29
|
+
manual: false
|
|
30
|
+
} satisfies DesktopOnboardingState)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const ctx: OnboardingContext = { requestGateway: async () => undefined as never }
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
cleanup()
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
window.localStorage.clear()
|
|
40
|
+
} catch {
|
|
41
|
+
// jsdom localStorage should always be present; ignore if not.
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
$desktopOnboarding.set({
|
|
45
|
+
configured: null,
|
|
46
|
+
flow: { status: 'idle' },
|
|
47
|
+
mode: 'oauth',
|
|
48
|
+
providers: null,
|
|
49
|
+
reason: null,
|
|
50
|
+
requested: false,
|
|
51
|
+
firstRunSkipped: false,
|
|
52
|
+
manual: false
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('onboarding Picker', () => {
|
|
57
|
+
it('features NasTech Portal and hides other providers behind a disclosure', () => {
|
|
58
|
+
setProviders([provider('anthropic', 'Anthropic Claude'), provider('nastech', 'NasTech Portal')])
|
|
59
|
+
render(<Picker ctx={ctx} />)
|
|
60
|
+
|
|
61
|
+
expect(screen.getByText('NasTech Portal')).toBeTruthy()
|
|
62
|
+
expect(screen.getByText('Recommended')).toBeTruthy()
|
|
63
|
+
expect(screen.queryByText('Anthropic API Key')).toBeNull()
|
|
64
|
+
|
|
65
|
+
fireEvent.click(screen.getByRole('button', { name: 'Other providers' }))
|
|
66
|
+
|
|
67
|
+
expect(screen.getByText('Anthropic API Key')).toBeTruthy()
|
|
68
|
+
expect(screen.getByRole('button', { name: 'Collapse' })).toBeTruthy()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('shows every provider directly when NasTech Portal is absent', () => {
|
|
72
|
+
setProviders([provider('anthropic', 'Anthropic Claude'), provider('openai-codex', 'OpenAI Codex / ChatGPT')])
|
|
73
|
+
render(<Picker ctx={ctx} />)
|
|
74
|
+
|
|
75
|
+
expect(screen.getByText('Anthropic API Key')).toBeTruthy()
|
|
76
|
+
expect(screen.getByText('OpenAI OAuth (ChatGPT)')).toBeTruthy()
|
|
77
|
+
expect(screen.queryByText('Other sign-in options')).toBeNull()
|
|
78
|
+
expect(screen.queryByText('Recommended')).toBeNull()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('offers "choose later" on first run and persists the skip', () => {
|
|
82
|
+
setProviders([provider('nastech', 'NasTech Portal')])
|
|
83
|
+
render(<Picker ctx={ctx} />)
|
|
84
|
+
|
|
85
|
+
const skip = screen.getByRole('button', { name: "I'll choose a provider later" })
|
|
86
|
+
|
|
87
|
+
fireEvent.click(skip)
|
|
88
|
+
|
|
89
|
+
expect($desktopOnboarding.get().firstRunSkipped).toBe(true)
|
|
90
|
+
expect(window.localStorage.getItem('NASTECH-onboarding-skipped-v1')).toBe('1')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('hides "choose later" in manual (add-provider) mode', () => {
|
|
94
|
+
setProviders([provider('nastech', 'NasTech Portal')])
|
|
95
|
+
$desktopOnboarding.set({ ...$desktopOnboarding.get(), manual: true })
|
|
96
|
+
render(<Picker ctx={ctx} />)
|
|
97
|
+
|
|
98
|
+
expect(screen.queryByRole('button', { name: "I'll choose a provider later" })).toBeNull()
|
|
99
|
+
})
|
|
100
|
+
})
|