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,449 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { PageLoader } from '@/components/page-loader'
|
|
4
|
+
import { Button } from '@/components/ui/button'
|
|
5
|
+
import { Input } from '@/components/ui/input'
|
|
6
|
+
import {
|
|
7
|
+
deleteEnvVar,
|
|
8
|
+
getActionStatus,
|
|
9
|
+
getToolsetConfig,
|
|
10
|
+
revealEnvVar,
|
|
11
|
+
runToolsetPostSetup,
|
|
12
|
+
selectToolsetProvider,
|
|
13
|
+
setEnvVar
|
|
14
|
+
} from '@/nastech'
|
|
15
|
+
import { useI18n } from '@/i18n'
|
|
16
|
+
import { Check, Loader2, Save, Terminal } from '@/lib/icons'
|
|
17
|
+
import { cn } from '@/lib/utils'
|
|
18
|
+
import { upsertDesktopActionTask } from '@/store/activity'
|
|
19
|
+
import { notify, notifyError } from '@/store/notifications'
|
|
20
|
+
import type { ActionStatusResponse, ToolEnvVar, ToolProvider, ToolsetConfig } from '@/types/nastech'
|
|
21
|
+
|
|
22
|
+
import { EnvVarActionsMenu, EnvVarActionsTrigger } from './env-var-actions-menu'
|
|
23
|
+
import { Pill } from './primitives'
|
|
24
|
+
|
|
25
|
+
interface ToolsetConfigPanelProps {
|
|
26
|
+
toolset: string
|
|
27
|
+
/** Called after a key is saved/cleared or a provider chosen, so the parent
|
|
28
|
+
* can refresh the "Configured / Needs keys" pill. */
|
|
29
|
+
onConfiguredChange?: () => void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function providerConfigured(provider: ToolProvider, envState: Record<string, boolean>): boolean {
|
|
33
|
+
if (provider.env_vars.length === 0) {
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return provider.env_vars.every(ev => envState[ev.key])
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface EnvVarFieldProps {
|
|
41
|
+
envVar: ToolEnvVar
|
|
42
|
+
isSet: boolean
|
|
43
|
+
onSaved: (key: string) => void
|
|
44
|
+
onCleared: (key: string) => void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function EnvVarField({ envVar, isSet, onSaved, onCleared }: EnvVarFieldProps) {
|
|
48
|
+
const { t } = useI18n()
|
|
49
|
+
const copy = t.settings.toolsets
|
|
50
|
+
const [editing, setEditing] = useState(false)
|
|
51
|
+
const [value, setValue] = useState('')
|
|
52
|
+
const [revealed, setRevealed] = useState<string | null>(null)
|
|
53
|
+
const [busy, setBusy] = useState(false)
|
|
54
|
+
|
|
55
|
+
async function handleSave() {
|
|
56
|
+
if (!value) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setBusy(true)
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await setEnvVar(envVar.key, value)
|
|
64
|
+
setEditing(false)
|
|
65
|
+
setValue('')
|
|
66
|
+
onSaved(envVar.key)
|
|
67
|
+
notify({ kind: 'success', title: copy.savedTitle, message: copy.savedMessage(envVar.key) })
|
|
68
|
+
} catch (err) {
|
|
69
|
+
notifyError(err, copy.failedSave(envVar.key))
|
|
70
|
+
} finally {
|
|
71
|
+
setBusy(false)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function handleClear() {
|
|
76
|
+
if (!window.confirm(copy.removeConfirm(envVar.key))) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setBusy(true)
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await deleteEnvVar(envVar.key)
|
|
84
|
+
setRevealed(null)
|
|
85
|
+
onCleared(envVar.key)
|
|
86
|
+
notify({ kind: 'success', title: copy.removedTitle, message: copy.removedMessage(envVar.key) })
|
|
87
|
+
} catch (err) {
|
|
88
|
+
notifyError(err, copy.failedRemove(envVar.key))
|
|
89
|
+
} finally {
|
|
90
|
+
setBusy(false)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function handleReveal() {
|
|
95
|
+
if (revealed !== null) {
|
|
96
|
+
setRevealed(null)
|
|
97
|
+
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const result = await revealEnvVar(envVar.key)
|
|
103
|
+
setRevealed(result.value)
|
|
104
|
+
} catch (err) {
|
|
105
|
+
notifyError(err, copy.failedReveal(envVar.key))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div className="grid gap-2 rounded-lg bg-background/55 p-2.5">
|
|
111
|
+
<div className="flex flex-wrap items-start justify-between gap-2">
|
|
112
|
+
<div className="min-w-0">
|
|
113
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
114
|
+
<span className="font-mono text-xs font-medium">{envVar.key}</span>
|
|
115
|
+
<Pill tone={isSet ? 'primary' : 'muted'}>
|
|
116
|
+
{isSet && <Check className="size-3" />}
|
|
117
|
+
{isSet ? copy.set : copy.notSet}
|
|
118
|
+
</Pill>
|
|
119
|
+
</div>
|
|
120
|
+
{envVar.prompt && envVar.prompt !== envVar.key && (
|
|
121
|
+
<p className="mt-0.5 text-[0.7rem] text-muted-foreground">{envVar.prompt}</p>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
{!editing && (
|
|
125
|
+
<EnvVarActionsMenu
|
|
126
|
+
clearDisabled={busy}
|
|
127
|
+
docsUrl={envVar.url}
|
|
128
|
+
isRevealed={revealed !== null}
|
|
129
|
+
isSet={isSet}
|
|
130
|
+
label={envVar.key}
|
|
131
|
+
onClear={() => void handleClear()}
|
|
132
|
+
onEdit={() => setEditing(true)}
|
|
133
|
+
onReveal={() => void handleReveal()}
|
|
134
|
+
>
|
|
135
|
+
<EnvVarActionsTrigger label={envVar.key} onClick={event => event.stopPropagation()} />
|
|
136
|
+
</EnvVarActionsMenu>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{isSet && revealed !== null && (
|
|
141
|
+
<div className="rounded-md bg-background px-2.5 py-1.5 font-mono text-xs text-foreground">
|
|
142
|
+
{revealed || '---'}
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
|
|
146
|
+
{editing && (
|
|
147
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
148
|
+
<Input
|
|
149
|
+
autoFocus
|
|
150
|
+
className="min-w-52 flex-1 font-mono"
|
|
151
|
+
onChange={e => setValue(e.target.value)}
|
|
152
|
+
placeholder={envVar.prompt || envVar.key}
|
|
153
|
+
type={envVar.default ? 'text' : 'password'}
|
|
154
|
+
value={value}
|
|
155
|
+
/>
|
|
156
|
+
<Button disabled={busy || !value} onClick={() => void handleSave()} size="sm">
|
|
157
|
+
{busy ? <Loader2 className="size-3.5 animate-spin" /> : <Save />}
|
|
158
|
+
{t.common.save}
|
|
159
|
+
</Button>
|
|
160
|
+
<Button onClick={() => setEditing(false)} size="sm" variant="text">
|
|
161
|
+
{t.common.cancel}
|
|
162
|
+
</Button>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
interface PostSetupRunnerProps {
|
|
170
|
+
toolset: string
|
|
171
|
+
/** The provider's post_setup hook key (e.g. "camofox", "ddgs"). */
|
|
172
|
+
postSetupKey: string
|
|
173
|
+
/** Refresh the parent config after the install finishes (a backend may now
|
|
174
|
+
* report itself configured). */
|
|
175
|
+
onComplete?: () => void
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Runs a provider's post-setup install hook (npm / pip / binary) via the
|
|
180
|
+
* `/api/tools/toolsets/{name}/post-setup` spawn-action and tails the resulting
|
|
181
|
+
* log inline — the GUI equivalent of the install step `NASTECH tools` runs
|
|
182
|
+
* after you pick a backend that needs extra dependencies.
|
|
183
|
+
*/
|
|
184
|
+
function PostSetupRunner({ toolset, postSetupKey, onComplete }: PostSetupRunnerProps) {
|
|
185
|
+
const { t } = useI18n()
|
|
186
|
+
const copy = t.settings.toolsets
|
|
187
|
+
const [running, setRunning] = useState(false)
|
|
188
|
+
const [status, setStatus] = useState<ActionStatusResponse | null>(null)
|
|
189
|
+
// Guard against overlapping polls / state updates after unmount.
|
|
190
|
+
const activeRef = useRef(false)
|
|
191
|
+
|
|
192
|
+
useEffect(() => {
|
|
193
|
+
return () => {
|
|
194
|
+
activeRef.current = false
|
|
195
|
+
}
|
|
196
|
+
}, [])
|
|
197
|
+
|
|
198
|
+
const run = useCallback(async () => {
|
|
199
|
+
setRunning(true)
|
|
200
|
+
setStatus(null)
|
|
201
|
+
activeRef.current = true
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const started = await runToolsetPostSetup(toolset, postSetupKey)
|
|
205
|
+
|
|
206
|
+
// The spawn endpoint reports ok:false if it couldn't launch the action
|
|
207
|
+
// (e.g. unknown key, server-side spawn failure). Don't poll a status
|
|
208
|
+
// that will never exist — surface the failure and stop.
|
|
209
|
+
if (!started.ok) {
|
|
210
|
+
notifyError(new Error('spawn failed'), copy.postSetupFailed(postSetupKey))
|
|
211
|
+
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let last: ActionStatusResponse | null = null
|
|
216
|
+
|
|
217
|
+
// Mirror command-center's runSystemAction poll loop: poll the action log
|
|
218
|
+
// until it exits (or we hit the attempt ceiling), feeding the global
|
|
219
|
+
// activity rail as we go.
|
|
220
|
+
for (let attempt = 0; attempt < 150 && activeRef.current; attempt += 1) {
|
|
221
|
+
await new Promise(resolve => window.setTimeout(resolve, 1200))
|
|
222
|
+
|
|
223
|
+
if (!activeRef.current) {
|
|
224
|
+
break
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const polled = await getActionStatus(started.name, 300)
|
|
228
|
+
last = polled
|
|
229
|
+
setStatus(polled)
|
|
230
|
+
upsertDesktopActionTask(polled)
|
|
231
|
+
|
|
232
|
+
if (!polled.running) {
|
|
233
|
+
break
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (activeRef.current) {
|
|
238
|
+
const ok = last?.exit_code === 0
|
|
239
|
+
|
|
240
|
+
notify(
|
|
241
|
+
ok
|
|
242
|
+
? {
|
|
243
|
+
kind: 'success',
|
|
244
|
+
title: copy.postSetupCompleteTitle,
|
|
245
|
+
message: copy.postSetupCompleteMessage(postSetupKey)
|
|
246
|
+
}
|
|
247
|
+
: { kind: 'error', title: copy.postSetupErrorTitle, message: copy.postSetupErrorMessage(postSetupKey) }
|
|
248
|
+
)
|
|
249
|
+
onComplete?.()
|
|
250
|
+
}
|
|
251
|
+
} catch (err) {
|
|
252
|
+
if (activeRef.current) {
|
|
253
|
+
notifyError(err, copy.postSetupFailed(postSetupKey))
|
|
254
|
+
}
|
|
255
|
+
} finally {
|
|
256
|
+
if (activeRef.current) {
|
|
257
|
+
setRunning(false)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}, [toolset, postSetupKey, onComplete, copy])
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<div className="grid gap-2 rounded-lg bg-background/55 p-2.5">
|
|
264
|
+
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
265
|
+
<div className="min-w-0">
|
|
266
|
+
<p className="text-[0.72rem] text-muted-foreground">{copy.postSetupHint(postSetupKey)}</p>
|
|
267
|
+
</div>
|
|
268
|
+
<Button disabled={running} onClick={() => void run()} size="sm">
|
|
269
|
+
{running ? <Loader2 className="size-3.5 animate-spin" /> : <Terminal className="size-3.5" />}
|
|
270
|
+
{running ? copy.postSetupRunning : copy.postSetupRun}
|
|
271
|
+
</Button>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
{status && (status.lines.length > 0 || status.running) && (
|
|
275
|
+
<pre className="max-h-48 overflow-y-auto rounded-md bg-background px-2.5 py-1.5 font-mono text-[0.7rem] leading-relaxed text-muted-foreground whitespace-pre-wrap">
|
|
276
|
+
{status.lines.length > 0 ? status.lines.join('\n') : copy.postSetupStarting}
|
|
277
|
+
</pre>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function ToolsetConfigPanel({ toolset, onConfiguredChange }: ToolsetConfigPanelProps) {
|
|
284
|
+
const { t } = useI18n()
|
|
285
|
+
const copy = t.settings.toolsets
|
|
286
|
+
const [cfg, setCfg] = useState<ToolsetConfig | null>(null)
|
|
287
|
+
const [loading, setLoading] = useState(true)
|
|
288
|
+
const [selecting, setSelecting] = useState<string | null>(null)
|
|
289
|
+
const [activeProvider, setActiveProvider] = useState<string | null>(null)
|
|
290
|
+
// Live per-key set/unset state, seeded from the endpoint then patched locally.
|
|
291
|
+
const [envState, setEnvState] = useState<Record<string, boolean>>({})
|
|
292
|
+
|
|
293
|
+
const refresh = useCallback(async () => {
|
|
294
|
+
setLoading(true)
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const next = await getToolsetConfig(toolset)
|
|
298
|
+
setCfg(next)
|
|
299
|
+
const seeded: Record<string, boolean> = {}
|
|
300
|
+
|
|
301
|
+
for (const provider of next.providers) {
|
|
302
|
+
for (const ev of provider.env_vars) {
|
|
303
|
+
seeded[ev.key] = ev.is_set
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
setEnvState(seeded)
|
|
308
|
+
} catch (err) {
|
|
309
|
+
notifyError(err, copy.failedLoad)
|
|
310
|
+
} finally {
|
|
311
|
+
setLoading(false)
|
|
312
|
+
}
|
|
313
|
+
}, [toolset])
|
|
314
|
+
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
void refresh()
|
|
317
|
+
}, [refresh])
|
|
318
|
+
|
|
319
|
+
const providers = useMemo(() => cfg?.providers ?? [], [cfg])
|
|
320
|
+
|
|
321
|
+
// Default the expanded provider to the one actually active in config
|
|
322
|
+
// (`is_active` / `cfg.active_provider`, mirroring the CLI picker), then the
|
|
323
|
+
// first fully-configured provider, else the first provider. Without this the
|
|
324
|
+
// panel highlighted the first keyless provider (e.g. NasTech Portal) even when
|
|
325
|
+
// the user had already selected another (e.g. DuckDuckGo).
|
|
326
|
+
useEffect(() => {
|
|
327
|
+
if (activeProvider || providers.length === 0) {
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const selected =
|
|
332
|
+
providers.find(p => p.is_active) ??
|
|
333
|
+
(cfg?.active_provider ? providers.find(p => p.name === cfg.active_provider) : undefined) ??
|
|
334
|
+
providers.find(p => providerConfigured(p, envState)) ??
|
|
335
|
+
providers[0]
|
|
336
|
+
|
|
337
|
+
setActiveProvider(selected.name)
|
|
338
|
+
}, [activeProvider, providers, envState, cfg])
|
|
339
|
+
|
|
340
|
+
async function handleSelect(provider: ToolProvider) {
|
|
341
|
+
setActiveProvider(provider.name)
|
|
342
|
+
setSelecting(provider.name)
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
await selectToolsetProvider(toolset, provider.name)
|
|
346
|
+
notify({ kind: 'success', title: copy.selectedTitle, message: copy.selectedMessage(provider.name) })
|
|
347
|
+
onConfiguredChange?.()
|
|
348
|
+
} catch (err) {
|
|
349
|
+
notifyError(err, copy.failedSelect(provider.name))
|
|
350
|
+
} finally {
|
|
351
|
+
setSelecting(null)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function patchEnv(key: string, isSet: boolean) {
|
|
356
|
+
setEnvState(c => ({ ...c, [key]: isSet }))
|
|
357
|
+
onConfiguredChange?.()
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const emptyMessage = useMemo(() => {
|
|
361
|
+
if (loading || !cfg) {
|
|
362
|
+
return null
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!cfg.has_category) {
|
|
366
|
+
return copy.noProviderOptions
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (providers.length === 0) {
|
|
370
|
+
return copy.noProviders
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return null
|
|
374
|
+
}, [cfg, copy, loading, providers.length])
|
|
375
|
+
|
|
376
|
+
if (loading) {
|
|
377
|
+
return <PageLoader className="min-h-32" label={copy.loadingConfig} />
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (emptyMessage) {
|
|
381
|
+
return <p className="px-1 py-3 text-xs text-muted-foreground">{emptyMessage}</p>
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return (
|
|
385
|
+
<div className="mt-3 grid gap-2">
|
|
386
|
+
{providers.map(provider => {
|
|
387
|
+
const isActive = activeProvider === provider.name
|
|
388
|
+
const configured = providerConfigured(provider, envState)
|
|
389
|
+
|
|
390
|
+
return (
|
|
391
|
+
<div className="overflow-hidden rounded-xl bg-background/60" key={provider.name}>
|
|
392
|
+
<button
|
|
393
|
+
aria-pressed={isActive}
|
|
394
|
+
className={cn(
|
|
395
|
+
'flex w-full items-center justify-between gap-3 px-3 py-2.5 text-left transition hover:bg-accent/50',
|
|
396
|
+
isActive && 'bg-accent/40'
|
|
397
|
+
)}
|
|
398
|
+
onClick={() => void handleSelect(provider)}
|
|
399
|
+
type="button"
|
|
400
|
+
>
|
|
401
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
402
|
+
<span className="truncate text-sm font-medium">{provider.name}</span>
|
|
403
|
+
{provider.badge && <Pill>{provider.badge}</Pill>}
|
|
404
|
+
{configured && (
|
|
405
|
+
<Pill tone="primary">
|
|
406
|
+
<Check className="size-3" />
|
|
407
|
+
{copy.ready}
|
|
408
|
+
</Pill>
|
|
409
|
+
)}
|
|
410
|
+
</span>
|
|
411
|
+
{selecting === provider.name && <Loader2 className="size-3.5 shrink-0 animate-spin" />}
|
|
412
|
+
</button>
|
|
413
|
+
|
|
414
|
+
{isActive && (
|
|
415
|
+
<div className="grid gap-2 bg-muted/20 p-3">
|
|
416
|
+
{provider.tag && <p className="text-[0.72rem] text-muted-foreground">{provider.tag}</p>}
|
|
417
|
+
{provider.requires_nastech_auth && (
|
|
418
|
+
<p className="text-[0.72rem] text-muted-foreground">
|
|
419
|
+
{copy.nousIncluded}
|
|
420
|
+
</p>
|
|
421
|
+
)}
|
|
422
|
+
{provider.env_vars.length === 0 ? (
|
|
423
|
+
<p className="text-[0.72rem] text-muted-foreground">{copy.noApiKeyRequired}</p>
|
|
424
|
+
) : (
|
|
425
|
+
provider.env_vars.map(ev => (
|
|
426
|
+
<EnvVarField
|
|
427
|
+
envVar={ev}
|
|
428
|
+
isSet={Boolean(envState[ev.key])}
|
|
429
|
+
key={ev.key}
|
|
430
|
+
onCleared={key => patchEnv(key, false)}
|
|
431
|
+
onSaved={key => patchEnv(key, true)}
|
|
432
|
+
/>
|
|
433
|
+
))
|
|
434
|
+
)}
|
|
435
|
+
{provider.post_setup && (
|
|
436
|
+
<PostSetupRunner
|
|
437
|
+
onComplete={() => void refresh()}
|
|
438
|
+
postSetupKey={provider.post_setup}
|
|
439
|
+
toolset={toolset}
|
|
440
|
+
/>
|
|
441
|
+
)}
|
|
442
|
+
</div>
|
|
443
|
+
)}
|
|
444
|
+
</div>
|
|
445
|
+
)
|
|
446
|
+
})}
|
|
447
|
+
</div>
|
|
448
|
+
)
|
|
449
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Dispatch, SetStateAction } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { NasTechGateway } from '@/nastech'
|
|
4
|
+
import type { IconComponent } from '@/lib/icons'
|
|
5
|
+
import type { EnvVarInfo } from '@/types/nastech'
|
|
6
|
+
|
|
7
|
+
export type SettingsView = 'about' | 'gateway' | 'keys' | 'mcp' | 'providers' | 'sessions' | `config:${string}`
|
|
8
|
+
export type EnvPatch = Partial<Pick<EnvVarInfo, 'is_set' | 'redacted_value'>>
|
|
9
|
+
|
|
10
|
+
export interface SettingsPageProps {
|
|
11
|
+
gateway?: NasTechGateway | null
|
|
12
|
+
onClose: () => void
|
|
13
|
+
onConfigSaved?: () => void
|
|
14
|
+
onMainModelChanged?: (provider: string, model: string) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProviderGroup {
|
|
18
|
+
name: string
|
|
19
|
+
priority: number
|
|
20
|
+
entries: [string, EnvVarInfo][]
|
|
21
|
+
hasAnySet: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DesktopConfigSection {
|
|
25
|
+
id: string
|
|
26
|
+
label: string
|
|
27
|
+
icon: IconComponent
|
|
28
|
+
keys: string[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface EnvRowProps {
|
|
32
|
+
varKey: string
|
|
33
|
+
info: EnvVarInfo
|
|
34
|
+
edits: Record<string, string>
|
|
35
|
+
revealed: Record<string, string>
|
|
36
|
+
saving: string | null
|
|
37
|
+
setEdits: Dispatch<SetStateAction<Record<string, string>>>
|
|
38
|
+
onSave: (key: string) => void
|
|
39
|
+
onClear: (key: string) => void
|
|
40
|
+
onReveal: (key: string) => void
|
|
41
|
+
compact?: boolean
|
|
42
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
4
|
+
import { AlertTriangle, Loader2, Trash2 } from '@/lib/icons'
|
|
5
|
+
import { cn } from '@/lib/utils'
|
|
6
|
+
import type { DesktopUninstallMode, DesktopUninstallSummary } from '@/global'
|
|
7
|
+
|
|
8
|
+
import { SectionHeading } from './primitives'
|
|
9
|
+
|
|
10
|
+
interface ModeOption {
|
|
11
|
+
mode: DesktopUninstallMode
|
|
12
|
+
title: string
|
|
13
|
+
description: string
|
|
14
|
+
/** Shown in the confirm step so people know exactly what disappears. */
|
|
15
|
+
consequence: string
|
|
16
|
+
/** True when the option removes the Python agent (hidden if no agent). */
|
|
17
|
+
needsAgent: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const OPTIONS: ModeOption[] = [
|
|
21
|
+
{
|
|
22
|
+
mode: 'gui',
|
|
23
|
+
title: 'Uninstall Chat GUI only',
|
|
24
|
+
description: 'Remove this desktop app. The NasTech agent, your config, and chats all stay.',
|
|
25
|
+
consequence: 'the desktop Chat GUI (this app and its data)',
|
|
26
|
+
needsAgent: false
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
mode: 'lite',
|
|
30
|
+
title: 'Uninstall GUI + agent, keep my data',
|
|
31
|
+
description: 'Remove the app and the NasTech agent, but keep config, chats, and secrets for a future reinstall.',
|
|
32
|
+
consequence: 'the Chat GUI and the NasTech agent (config, chats, and secrets are kept)',
|
|
33
|
+
needsAgent: true
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
mode: 'full',
|
|
37
|
+
title: 'Uninstall everything',
|
|
38
|
+
description: 'Remove the app, the agent, and all user data — config, chats, scheduled jobs, secrets, logs.',
|
|
39
|
+
consequence: 'EVERYTHING — the Chat GUI, the NasTech agent, and all of your config, chats, secrets, and logs',
|
|
40
|
+
// full removes the agent (and user data), so it's an agent-removing option:
|
|
41
|
+
// hide it on a lite client with no local agent, same as lite. A lite client
|
|
42
|
+
// connecting to a remote backend has no local agent OR local user data the
|
|
43
|
+
// GUI installed, so gui-only is the correct (and only) option there.
|
|
44
|
+
needsAgent: true
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
export function UninstallSection() {
|
|
49
|
+
const [summary, setSummary] = useState<DesktopUninstallSummary | null>(null)
|
|
50
|
+
const [loading, setLoading] = useState(true)
|
|
51
|
+
const [pending, setPending] = useState<DesktopUninstallMode | null>(null)
|
|
52
|
+
const [running, setRunning] = useState(false)
|
|
53
|
+
const [error, setError] = useState<string | null>(null)
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
let alive = true
|
|
57
|
+
const bridge = window.NASTECHDesktop?.uninstall
|
|
58
|
+
if (!bridge) {
|
|
59
|
+
setLoading(false)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
void bridge
|
|
63
|
+
.summary()
|
|
64
|
+
.then(result => {
|
|
65
|
+
if (alive) {
|
|
66
|
+
setSummary(result)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
.catch(() => {
|
|
70
|
+
// Non-fatal — we degrade to offering the GUI-only option.
|
|
71
|
+
})
|
|
72
|
+
.finally(() => {
|
|
73
|
+
if (alive) {
|
|
74
|
+
setLoading(false)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
return () => {
|
|
78
|
+
alive = false
|
|
79
|
+
}
|
|
80
|
+
}, [])
|
|
81
|
+
|
|
82
|
+
const bridge = window.NASTECHDesktop?.uninstall
|
|
83
|
+
if (!bridge) {
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Gate the agent-removing options on whether an agent is actually present.
|
|
88
|
+
// A future lite client that ships without the bundled agent shows GUI-only.
|
|
89
|
+
const agentInstalled = summary?.agent_installed ?? false
|
|
90
|
+
const visibleOptions = OPTIONS.filter(opt => agentInstalled || !opt.needsAgent)
|
|
91
|
+
|
|
92
|
+
const handleConfirm = async () => {
|
|
93
|
+
if (!pending) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
setRunning(true)
|
|
97
|
+
setError(null)
|
|
98
|
+
try {
|
|
99
|
+
const result = await bridge.run(pending)
|
|
100
|
+
if (!result.ok) {
|
|
101
|
+
setError(result.message || result.error || 'Uninstall could not start.')
|
|
102
|
+
setRunning(false)
|
|
103
|
+
setPending(null)
|
|
104
|
+
}
|
|
105
|
+
// On success the app quits shortly; keep the spinner up until it does.
|
|
106
|
+
} catch (err) {
|
|
107
|
+
setError(err instanceof Error ? err.message : String(err))
|
|
108
|
+
setRunning(false)
|
|
109
|
+
setPending(null)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const pendingOption = OPTIONS.find(opt => opt.mode === pending) ?? null
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className="mx-auto mt-8 w-full max-w-2xl">
|
|
117
|
+
<SectionHeading icon={AlertTriangle} title="Danger zone" />
|
|
118
|
+
|
|
119
|
+
<div className="rounded-xl border border-destructive/30 bg-destructive/5 px-4 py-3">
|
|
120
|
+
{loading ? (
|
|
121
|
+
<div className="flex items-center gap-2 py-2 text-sm text-muted-foreground">
|
|
122
|
+
<Loader2 className="size-3.5 animate-spin" />
|
|
123
|
+
Checking what's installed…
|
|
124
|
+
</div>
|
|
125
|
+
) : pendingOption ? (
|
|
126
|
+
<div>
|
|
127
|
+
<p className="text-sm font-medium text-destructive">Confirm uninstall</p>
|
|
128
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
129
|
+
This removes {pendingOption.consequence}. This can't be undone.
|
|
130
|
+
</p>
|
|
131
|
+
{summary?.running_app_path && (
|
|
132
|
+
<p className="mt-1 font-mono text-[0.68rem] text-muted-foreground/60">
|
|
133
|
+
App: {summary.running_app_path}
|
|
134
|
+
</p>
|
|
135
|
+
)}
|
|
136
|
+
{error && <p className="mt-2 text-xs text-destructive">{error}</p>}
|
|
137
|
+
<div className="mt-3 flex flex-wrap items-center gap-3">
|
|
138
|
+
<Button
|
|
139
|
+
disabled={running}
|
|
140
|
+
onClick={() => void handleConfirm()}
|
|
141
|
+
size="sm"
|
|
142
|
+
variant="destructive"
|
|
143
|
+
>
|
|
144
|
+
{running && <Loader2 className="size-3 animate-spin" />}
|
|
145
|
+
{running ? 'Uninstalling…' : 'Yes, uninstall'}
|
|
146
|
+
</Button>
|
|
147
|
+
<Button disabled={running} onClick={() => setPending(null)} size="sm" variant="text">
|
|
148
|
+
Cancel
|
|
149
|
+
</Button>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
) : (
|
|
153
|
+
<div className="flex flex-col gap-2">
|
|
154
|
+
<p className="text-sm font-medium">Uninstall NasTech</p>
|
|
155
|
+
<p className="text-xs text-muted-foreground">
|
|
156
|
+
Choose how much to remove. The app closes to finish the job; reopen the installer any time to come back.
|
|
157
|
+
</p>
|
|
158
|
+
<div className="mt-1 flex flex-col gap-2">
|
|
159
|
+
{visibleOptions.map(opt => (
|
|
160
|
+
<button
|
|
161
|
+
className={cn(
|
|
162
|
+
'flex items-start gap-3 rounded-lg border border-border/60 bg-background/40 px-3 py-2.5 text-left transition',
|
|
163
|
+
'hover:border-destructive/40 hover:bg-destructive/5'
|
|
164
|
+
)}
|
|
165
|
+
key={opt.mode}
|
|
166
|
+
onClick={() => {
|
|
167
|
+
setError(null)
|
|
168
|
+
setPending(opt.mode)
|
|
169
|
+
}}
|
|
170
|
+
type="button"
|
|
171
|
+
>
|
|
172
|
+
<Trash2 className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
|
|
173
|
+
<span className="min-w-0">
|
|
174
|
+
<span className="block text-sm font-medium text-foreground">{opt.title}</span>
|
|
175
|
+
<span className="mt-0.5 block text-xs text-muted-foreground">{opt.description}</span>
|
|
176
|
+
</span>
|
|
177
|
+
</button>
|
|
178
|
+
))}
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
)
|
|
185
|
+
}
|