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,938 @@
|
|
|
1
|
+
import { useStore } from '@nanostores/react'
|
|
2
|
+
import { useQueryClient } from '@tanstack/react-query'
|
|
3
|
+
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef } from 'react'
|
|
4
|
+
import { Navigate, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom'
|
|
5
|
+
|
|
6
|
+
import { BootFailureOverlay } from '@/components/boot-failure-overlay'
|
|
7
|
+
import { DesktopInstallOverlay } from '@/components/desktop-install-overlay'
|
|
8
|
+
import { DesktopOnboardingOverlay } from '@/components/desktop-onboarding-overlay'
|
|
9
|
+
import { GatewayConnectingOverlay } from '@/components/gateway-connecting-overlay'
|
|
10
|
+
import { Pane, PaneMain } from '@/components/pane-shell'
|
|
11
|
+
import { useSkinCommand } from '@/themes/use-skin-command'
|
|
12
|
+
|
|
13
|
+
import { formatRefValue } from '../components/assistant-ui/directive-text'
|
|
14
|
+
import { getCronJobs, getSessionMessages, listAllProfileSessions, type SessionInfo, triggerCronJob } from '../nastech'
|
|
15
|
+
import { preserveLocalAssistantErrors, toChatMessages } from '../lib/chat-messages'
|
|
16
|
+
import { setCronFocusJobId, setCronJobs } from '../store/cron'
|
|
17
|
+
import {
|
|
18
|
+
$panesFlipped,
|
|
19
|
+
$pinnedSessionIds,
|
|
20
|
+
$sessionsLimit,
|
|
21
|
+
bumpSessionsLimit,
|
|
22
|
+
FILE_BROWSER_DEFAULT_WIDTH,
|
|
23
|
+
FILE_BROWSER_MAX_WIDTH,
|
|
24
|
+
FILE_BROWSER_MIN_WIDTH,
|
|
25
|
+
pinSession,
|
|
26
|
+
SIDEBAR_DEFAULT_WIDTH,
|
|
27
|
+
SIDEBAR_MAX_WIDTH,
|
|
28
|
+
SIDEBAR_SESSIONS_PAGE_SIZE,
|
|
29
|
+
unpinSession
|
|
30
|
+
} from '../store/layout'
|
|
31
|
+
import { $filePreviewTarget, $previewTarget, closeActiveRightRailTab } from '../store/preview'
|
|
32
|
+
import {
|
|
33
|
+
$activeGatewayProfile,
|
|
34
|
+
$freshSessionRequest,
|
|
35
|
+
$profileScope,
|
|
36
|
+
ALL_PROFILES,
|
|
37
|
+
normalizeProfileKey,
|
|
38
|
+
refreshActiveProfile
|
|
39
|
+
} from '../store/profile'
|
|
40
|
+
import {
|
|
41
|
+
$activeSessionId,
|
|
42
|
+
$currentCwd,
|
|
43
|
+
$freshDraftReady,
|
|
44
|
+
$gatewayState,
|
|
45
|
+
$selectedStoredSessionId,
|
|
46
|
+
$sessions,
|
|
47
|
+
$workingSessionIds,
|
|
48
|
+
CRON_SECTION_LIMIT,
|
|
49
|
+
mergeSessionPage,
|
|
50
|
+
sessionPinId,
|
|
51
|
+
setAwaitingResponse,
|
|
52
|
+
setBusy,
|
|
53
|
+
setCronSessions,
|
|
54
|
+
setCurrentBranch,
|
|
55
|
+
setCurrentCwd,
|
|
56
|
+
setCurrentModel,
|
|
57
|
+
setCurrentProvider,
|
|
58
|
+
setMessages,
|
|
59
|
+
setSessionProfileTotals,
|
|
60
|
+
setSessions,
|
|
61
|
+
setSessionsLoading,
|
|
62
|
+
setSessionsTotal
|
|
63
|
+
} from '../store/session'
|
|
64
|
+
import { openUpdatesWindow, startUpdatePoller, stopUpdatePoller } from '../store/updates'
|
|
65
|
+
|
|
66
|
+
import { ChatView } from './chat'
|
|
67
|
+
import { useComposerActions } from './chat/hooks/use-composer-actions'
|
|
68
|
+
import {
|
|
69
|
+
ChatPreviewRail,
|
|
70
|
+
PREVIEW_RAIL_MAX_WIDTH,
|
|
71
|
+
PREVIEW_RAIL_MIN_WIDTH,
|
|
72
|
+
PREVIEW_RAIL_PANE_WIDTH
|
|
73
|
+
} from './chat/right-rail'
|
|
74
|
+
import { ChatSidebar } from './chat/sidebar'
|
|
75
|
+
import { CommandPalette } from './command-palette'
|
|
76
|
+
import { useGatewayBoot } from './gateway/hooks/use-gateway-boot'
|
|
77
|
+
import { useGatewayRequest } from './gateway/hooks/use-gateway-request'
|
|
78
|
+
import { useKeybinds } from './hooks/use-keybinds'
|
|
79
|
+
import { ModelPickerOverlay } from './model-picker-overlay'
|
|
80
|
+
import { ModelVisibilityOverlay } from './model-visibility-overlay'
|
|
81
|
+
import { RightSidebarPane } from './right-sidebar'
|
|
82
|
+
import { $terminalTakeover } from './right-sidebar/store'
|
|
83
|
+
import { PersistentTerminal, TerminalSlot } from './right-sidebar/terminal/persistent'
|
|
84
|
+
import { CRON_ROUTE, NEW_CHAT_ROUTE, routeSessionId, sessionRoute, SETTINGS_ROUTE } from './routes'
|
|
85
|
+
import { useContextSuggestions } from './session/hooks/use-context-suggestions'
|
|
86
|
+
import { useCwdActions } from './session/hooks/use-cwd-actions'
|
|
87
|
+
import { useNasTechConfig } from './session/hooks/use-nastech-config'
|
|
88
|
+
import { useMessageStream } from './session/hooks/use-message-stream'
|
|
89
|
+
import { useModelControls } from './session/hooks/use-model-controls'
|
|
90
|
+
import { usePreviewRouting } from './session/hooks/use-preview-routing'
|
|
91
|
+
import { usePromptActions } from './session/hooks/use-prompt-actions'
|
|
92
|
+
import { useRouteResume } from './session/hooks/use-route-resume'
|
|
93
|
+
import { useSessionActions } from './session/hooks/use-session-actions'
|
|
94
|
+
import { useSessionStateCache } from './session/hooks/use-session-state-cache'
|
|
95
|
+
import { AppShell } from './shell/app-shell'
|
|
96
|
+
import { useOverlayRouting } from './shell/hooks/use-overlay-routing'
|
|
97
|
+
import { useStatusSnapshot } from './shell/hooks/use-status-snapshot'
|
|
98
|
+
import { useStatusbarItems } from './shell/hooks/use-statusbar-items'
|
|
99
|
+
import { ModelMenuPanel } from './shell/model-menu-panel'
|
|
100
|
+
import type { StatusbarItem } from './shell/statusbar-controls'
|
|
101
|
+
import type { TitlebarTool } from './shell/titlebar-controls'
|
|
102
|
+
import { useGroupRegistry } from './shell/use-group-registry'
|
|
103
|
+
import { UpdatesOverlay } from './updates-overlay'
|
|
104
|
+
|
|
105
|
+
const AgentsView = lazy(async () => ({ default: (await import('./agents')).AgentsView }))
|
|
106
|
+
const ArtifactsView = lazy(async () => ({ default: (await import('./artifacts')).ArtifactsView }))
|
|
107
|
+
const CommandCenterView = lazy(async () => ({ default: (await import('./command-center')).CommandCenterView }))
|
|
108
|
+
const CronView = lazy(async () => ({ default: (await import('./cron')).CronView }))
|
|
109
|
+
const MessagingView = lazy(async () => ({ default: (await import('./messaging')).MessagingView }))
|
|
110
|
+
const ProfilesView = lazy(async () => ({ default: (await import('./profiles')).ProfilesView }))
|
|
111
|
+
const SettingsView = lazy(async () => ({ default: (await import('./settings')).SettingsView }))
|
|
112
|
+
const SkillsView = lazy(async () => ({ default: (await import('./skills')).SkillsView }))
|
|
113
|
+
|
|
114
|
+
// Latest cron-job sessions surfaced in the collapsed "Cron jobs" section. The
|
|
115
|
+
// Cron sessions are written by a background scheduler tick (the desktop
|
|
116
|
+
// backend), so no user action signals the UI. Poll the bounded cron list on
|
|
117
|
+
// this cadence while the app is open + visible so new runs surface promptly
|
|
118
|
+
// instead of waiting for the next user-triggered refreshSessions().
|
|
119
|
+
const CRON_POLL_INTERVAL_MS = 30_000
|
|
120
|
+
|
|
121
|
+
// Cheap signature compare so the poll only swaps the atom (and re-renders the
|
|
122
|
+
// sidebar) when the visible cron rows actually changed.
|
|
123
|
+
function sameCronSignature(a: SessionInfo[], b: SessionInfo[]): boolean {
|
|
124
|
+
if (a.length !== b.length) {return false}
|
|
125
|
+
|
|
126
|
+
return a.every((session, i) => session.id === b[i]?.id && session.title === b[i]?.title)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Rows a session refresh must preserve even if the aggregator omits them:
|
|
130
|
+
// in-flight first turns (message_count 0), pinned rows aged off the page, and
|
|
131
|
+
// the actively-viewed chat (its "working" flag clears a beat before the
|
|
132
|
+
// aggregator sees the persisted row). Pass `scope` to only keep the active row
|
|
133
|
+
// when it belongs to the profile being paged.
|
|
134
|
+
function sessionsToKeep(scope?: string): Set<string> {
|
|
135
|
+
const keep = new Set<string>([...$workingSessionIds.get(), ...$pinnedSessionIds.get()])
|
|
136
|
+
const active = $selectedStoredSessionId.get()
|
|
137
|
+
|
|
138
|
+
if (active) {
|
|
139
|
+
const session = scope ? $sessions.get().find(s => s.id === active) : null
|
|
140
|
+
|
|
141
|
+
if (!scope || !session || normalizeProfileKey(session.profile) === scope) {
|
|
142
|
+
keep.add(active)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return keep
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function DesktopController() {
|
|
150
|
+
const queryClient = useQueryClient()
|
|
151
|
+
const location = useLocation()
|
|
152
|
+
const navigate = useNavigate()
|
|
153
|
+
|
|
154
|
+
const busyRef = useRef(false)
|
|
155
|
+
const creatingSessionRef = useRef(false)
|
|
156
|
+
const refreshSessionsRequestRef = useRef(0)
|
|
157
|
+
|
|
158
|
+
const gatewayState = useStore($gatewayState)
|
|
159
|
+
const activeSessionId = useStore($activeSessionId)
|
|
160
|
+
const currentCwd = useStore($currentCwd)
|
|
161
|
+
const freshDraftReady = useStore($freshDraftReady)
|
|
162
|
+
const filePreviewTarget = useStore($filePreviewTarget)
|
|
163
|
+
const previewTarget = useStore($previewTarget)
|
|
164
|
+
const selectedStoredSessionId = useStore($selectedStoredSessionId)
|
|
165
|
+
const terminalTakeover = useStore($terminalTakeover)
|
|
166
|
+
const panesFlipped = useStore($panesFlipped)
|
|
167
|
+
const profileScope = useStore($profileScope)
|
|
168
|
+
|
|
169
|
+
const routedSessionId = routeSessionId(location.pathname)
|
|
170
|
+
const routeToken = `${location.pathname}:${location.search}:${location.hash}`
|
|
171
|
+
const routeTokenRef = useRef(routeToken)
|
|
172
|
+
routeTokenRef.current = routeToken
|
|
173
|
+
const getRouteToken = useCallback(() => routeTokenRef.current, [])
|
|
174
|
+
|
|
175
|
+
const {
|
|
176
|
+
agentsOpen,
|
|
177
|
+
chatOpen,
|
|
178
|
+
closeOverlayToPreviousRoute,
|
|
179
|
+
commandCenterInitialSection,
|
|
180
|
+
commandCenterOpen,
|
|
181
|
+
cronOpen,
|
|
182
|
+
currentView,
|
|
183
|
+
openAgents,
|
|
184
|
+
openCommandCenterSection,
|
|
185
|
+
profilesOpen,
|
|
186
|
+
settingsOpen,
|
|
187
|
+
toggleCommandCenter
|
|
188
|
+
} = useOverlayRouting()
|
|
189
|
+
|
|
190
|
+
const terminalTakeoverActive = chatOpen && terminalTakeover
|
|
191
|
+
|
|
192
|
+
const titlebarToolGroups = useGroupRegistry<TitlebarTool>()
|
|
193
|
+
const statusbarItemGroups = useGroupRegistry<StatusbarItem>()
|
|
194
|
+
const setTitlebarToolGroup = titlebarToolGroups.set
|
|
195
|
+
const setStatusbarItemGroup = statusbarItemGroups.set
|
|
196
|
+
|
|
197
|
+
const {
|
|
198
|
+
activeSessionIdRef,
|
|
199
|
+
ensureSessionState,
|
|
200
|
+
runtimeIdByStoredSessionIdRef,
|
|
201
|
+
selectedStoredSessionIdRef,
|
|
202
|
+
sessionStateByRuntimeIdRef,
|
|
203
|
+
syncSessionStateToView,
|
|
204
|
+
updateSessionState
|
|
205
|
+
} = useSessionStateCache({
|
|
206
|
+
activeSessionId,
|
|
207
|
+
busyRef,
|
|
208
|
+
selectedStoredSessionId,
|
|
209
|
+
setAwaitingResponse,
|
|
210
|
+
setBusy,
|
|
211
|
+
setMessages
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
const { connectionRef, gatewayRef, requestGateway } = useGatewayRequest()
|
|
215
|
+
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
window.NASTECHDesktop?.setPreviewShortcutActive?.(Boolean(chatOpen && (filePreviewTarget || previewTarget)))
|
|
218
|
+
}, [chatOpen, filePreviewTarget, previewTarget])
|
|
219
|
+
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
startUpdatePoller()
|
|
222
|
+
const unsubscribe = window.NASTECHDesktop?.onOpenUpdatesRequested?.(() => openUpdatesWindow())
|
|
223
|
+
|
|
224
|
+
return () => {
|
|
225
|
+
unsubscribe?.()
|
|
226
|
+
stopUpdatePoller()
|
|
227
|
+
}
|
|
228
|
+
}, [])
|
|
229
|
+
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
232
|
+
if (!$filePreviewTarget.get() && !$previewTarget.get()) {
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === 'w') {
|
|
237
|
+
event.preventDefault()
|
|
238
|
+
event.stopPropagation()
|
|
239
|
+
closeActiveRightRailTab()
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const unsubscribe = window.NASTECHDesktop?.onClosePreviewRequested?.(closeActiveRightRailTab)
|
|
244
|
+
|
|
245
|
+
window.addEventListener('keydown', onKeyDown, { capture: true })
|
|
246
|
+
|
|
247
|
+
return () => {
|
|
248
|
+
unsubscribe?.()
|
|
249
|
+
window.removeEventListener('keydown', onKeyDown, { capture: true })
|
|
250
|
+
}
|
|
251
|
+
}, [])
|
|
252
|
+
|
|
253
|
+
// Cron-job sessions as their own list (latest N). Independent of the recents
|
|
254
|
+
// page so the two never compete for slots. Cheap + bounded. Kept (even though
|
|
255
|
+
// the sidebar now lists cron *jobs*, not run sessions) so a pinned cron run
|
|
256
|
+
// still resolves into the Pinned section via sessionByAnyId.
|
|
257
|
+
const refreshCronSessions = useCallback(async () => {
|
|
258
|
+
try {
|
|
259
|
+
const { sessions } = await listAllProfileSessions(CRON_SECTION_LIMIT, 1, 'exclude', 'recent', 'all', {
|
|
260
|
+
source: 'cron'
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
setCronSessions(prev => (sameCronSignature(prev, sessions) ? prev : sessions))
|
|
264
|
+
} catch {
|
|
265
|
+
// Non-fatal: the cron section just stays empty/stale.
|
|
266
|
+
}
|
|
267
|
+
}, [])
|
|
268
|
+
|
|
269
|
+
// Cron *jobs* drive the sidebar "Cron jobs" section. Jobs are created
|
|
270
|
+
// synchronously (agent tool call or the cron UI), so refreshing here right
|
|
271
|
+
// after an agent turn surfaces a new job immediately; the interval poll keeps
|
|
272
|
+
// next-run/state fresh as the scheduler advances them.
|
|
273
|
+
const refreshCronJobs = useCallback(async () => {
|
|
274
|
+
try {
|
|
275
|
+
const jobs = await getCronJobs()
|
|
276
|
+
|
|
277
|
+
setCronJobs(jobs)
|
|
278
|
+
} catch {
|
|
279
|
+
// Non-fatal: the cron section just keeps its last-known jobs.
|
|
280
|
+
}
|
|
281
|
+
}, [])
|
|
282
|
+
|
|
283
|
+
const refreshSessions = useCallback(async () => {
|
|
284
|
+
const requestId = refreshSessionsRequestRef.current + 1
|
|
285
|
+
refreshSessionsRequestRef.current = requestId
|
|
286
|
+
setSessionsLoading(true)
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const limit = $sessionsLimit.get()
|
|
290
|
+
|
|
291
|
+
// Require at least one message so abandoned/empty "Untitled" drafts (one
|
|
292
|
+
// was created per TUI/desktop launch before the lazy-create fix) don't
|
|
293
|
+
// clutter the sidebar.
|
|
294
|
+
// Unified cross-profile list (served read-only off each profile's
|
|
295
|
+
// state.db; no per-profile backend is spawned). Single-profile users get
|
|
296
|
+
// the same rows tagged profile="default". Cron sessions are excluded here
|
|
297
|
+
// and fetched separately (refreshCronSessions) so the scheduler's
|
|
298
|
+
// always-newest rows can't consume the recents page budget.
|
|
299
|
+
// Scope the fetch to the active profile (not always 'all') so a profile
|
|
300
|
+
// with few recent sessions isn't windowed out of the cross-profile
|
|
301
|
+
// recency page — the empty-history-on-profile-switch bug.
|
|
302
|
+
const sessionProfile = profileScope === ALL_PROFILES ? 'all' : profileScope
|
|
303
|
+
const result = await listAllProfileSessions(limit, 1, 'exclude', 'recent', sessionProfile, {
|
|
304
|
+
excludeSources: ['cron']
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
if (refreshSessionsRequestRef.current === requestId) {
|
|
308
|
+
setSessions(prev => mergeSessionPage(prev, result.sessions, sessionsToKeep()))
|
|
309
|
+
setSessionsTotal(typeof result.total === 'number' ? result.total : result.sessions.length)
|
|
310
|
+
setSessionProfileTotals(result.profile_totals ?? {})
|
|
311
|
+
}
|
|
312
|
+
} finally {
|
|
313
|
+
if (refreshSessionsRequestRef.current === requestId) {
|
|
314
|
+
setSessionsLoading(false)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
void refreshCronSessions()
|
|
319
|
+
void refreshCronJobs()
|
|
320
|
+
}, [profileScope, refreshCronSessions, refreshCronJobs])
|
|
321
|
+
|
|
322
|
+
const loadMoreSessions = useCallback(() => {
|
|
323
|
+
bumpSessionsLimit()
|
|
324
|
+
void refreshSessions()
|
|
325
|
+
}, [refreshSessions])
|
|
326
|
+
|
|
327
|
+
// ALL-profiles view pages one profile at a time: fetch that profile's next
|
|
328
|
+
// page and merge it in place, leaving every other profile's rows untouched.
|
|
329
|
+
const loadMoreSessionsForProfile = useCallback(async (profile: string) => {
|
|
330
|
+
const key = normalizeProfileKey(profile)
|
|
331
|
+
const inKey = (s: SessionInfo) => normalizeProfileKey(s.profile) === key
|
|
332
|
+
const loaded = $sessions.get().filter(inKey).length
|
|
333
|
+
|
|
334
|
+
const result = await listAllProfileSessions(loaded + SIDEBAR_SESSIONS_PAGE_SIZE, 1, 'exclude', 'recent', key, {
|
|
335
|
+
excludeSources: ['cron']
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
const keep = sessionsToKeep(key)
|
|
339
|
+
|
|
340
|
+
setSessions(prev => [...prev.filter(s => !inKey(s)), ...mergeSessionPage(prev.filter(inKey), result.sessions, keep)])
|
|
341
|
+
|
|
342
|
+
const total = result.profile_totals?.[key] ?? result.total ?? result.sessions.length
|
|
343
|
+
setSessionProfileTotals(prev => ({ ...prev, [key]: Math.max(total, result.sessions.length) }))
|
|
344
|
+
}, [])
|
|
345
|
+
|
|
346
|
+
const toggleSelectedPin = useCallback(() => {
|
|
347
|
+
const sessionId = $selectedStoredSessionId.get()
|
|
348
|
+
|
|
349
|
+
if (!sessionId) {
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Pin on the durable lineage-root id so the pin survives auto-compression.
|
|
354
|
+
const session = $sessions.get().find(s => s.id === sessionId || s._lineage_root_id === sessionId)
|
|
355
|
+
const pinId = session ? sessionPinId(session) : sessionId
|
|
356
|
+
|
|
357
|
+
if ($pinnedSessionIds.get().includes(pinId)) {
|
|
358
|
+
unpinSession(pinId)
|
|
359
|
+
} else {
|
|
360
|
+
pinSession(pinId)
|
|
361
|
+
}
|
|
362
|
+
}, [])
|
|
363
|
+
|
|
364
|
+
const { gatewayLogLines, inferenceStatus, statusSnapshot } = useStatusSnapshot(gatewayState, requestGateway)
|
|
365
|
+
|
|
366
|
+
const updateActiveSessionRuntimeInfo = useCallback(
|
|
367
|
+
(info: { branch?: string; cwd?: string }) => {
|
|
368
|
+
const sessionId = activeSessionIdRef.current
|
|
369
|
+
|
|
370
|
+
if (!sessionId) {
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
updateSessionState(sessionId, state => ({
|
|
375
|
+
...state,
|
|
376
|
+
branch: info.branch ?? state.branch,
|
|
377
|
+
cwd: info.cwd ?? state.cwd
|
|
378
|
+
}))
|
|
379
|
+
},
|
|
380
|
+
[activeSessionIdRef, updateSessionState]
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
const { changeSessionCwd, refreshProjectBranch } = useCwdActions({
|
|
384
|
+
activeSessionId,
|
|
385
|
+
activeSessionIdRef,
|
|
386
|
+
onSessionRuntimeInfo: updateActiveSessionRuntimeInfo,
|
|
387
|
+
requestGateway
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
const { refreshNasTechConfig, sttEnabled, voiceMaxRecordingSeconds } = useNasTechConfig({
|
|
391
|
+
activeSessionIdRef,
|
|
392
|
+
refreshProjectBranch
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
const { refreshCurrentModel, selectModel, updateModelOptionsCache } = useModelControls({
|
|
396
|
+
activeSessionId,
|
|
397
|
+
queryClient,
|
|
398
|
+
requestGateway
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
const openProviderSettings = useCallback(() => {
|
|
402
|
+
navigate(`${SETTINGS_ROUTE}?tab=providers`)
|
|
403
|
+
}, [navigate])
|
|
404
|
+
|
|
405
|
+
const modelMenuContent = useMemo(
|
|
406
|
+
() =>
|
|
407
|
+
gatewayState === 'open' ? (
|
|
408
|
+
<ModelMenuPanel
|
|
409
|
+
gateway={gatewayRef.current || undefined}
|
|
410
|
+
onSelectModel={selectModel}
|
|
411
|
+
requestGateway={requestGateway}
|
|
412
|
+
/>
|
|
413
|
+
) : null,
|
|
414
|
+
[gatewayRef, gatewayState, requestGateway, selectModel]
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
useContextSuggestions({
|
|
418
|
+
activeSessionId,
|
|
419
|
+
activeSessionIdRef,
|
|
420
|
+
currentCwd,
|
|
421
|
+
gatewayState,
|
|
422
|
+
requestGateway
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
const hydrateFromStoredSession = useCallback(
|
|
426
|
+
async (
|
|
427
|
+
attempts = 1,
|
|
428
|
+
storedSessionId = selectedStoredSessionIdRef.current,
|
|
429
|
+
runtimeSessionId = activeSessionIdRef.current
|
|
430
|
+
) => {
|
|
431
|
+
if (!storedSessionId || !runtimeSessionId) {
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const storedProfile = $sessions.get().find(session => session.id === storedSessionId)?.profile
|
|
436
|
+
|
|
437
|
+
for (let index = 0; index < Math.max(1, attempts); index += 1) {
|
|
438
|
+
try {
|
|
439
|
+
const latest = await getSessionMessages(storedSessionId, storedProfile)
|
|
440
|
+
updateSessionState(
|
|
441
|
+
runtimeSessionId,
|
|
442
|
+
state => ({
|
|
443
|
+
...state,
|
|
444
|
+
messages: preserveLocalAssistantErrors(toChatMessages(latest.messages), state.messages)
|
|
445
|
+
}),
|
|
446
|
+
storedSessionId
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
return
|
|
450
|
+
} catch {
|
|
451
|
+
// Best-effort fallback when live stream payloads are empty.
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (index < attempts - 1) {
|
|
455
|
+
await new Promise(resolve => window.setTimeout(resolve, 250))
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
[activeSessionIdRef, selectedStoredSessionIdRef, updateSessionState]
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
const { handleGatewayEvent } = useMessageStream({
|
|
463
|
+
activeSessionIdRef,
|
|
464
|
+
hydrateFromStoredSession,
|
|
465
|
+
queryClient,
|
|
466
|
+
refreshNasTechConfig,
|
|
467
|
+
refreshSessions,
|
|
468
|
+
updateSessionState
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
const { handleDesktopGatewayEvent, restartPreviewServer } = usePreviewRouting({
|
|
472
|
+
activeSessionIdRef,
|
|
473
|
+
baseHandleGatewayEvent: handleGatewayEvent,
|
|
474
|
+
currentCwd,
|
|
475
|
+
currentView,
|
|
476
|
+
requestGateway,
|
|
477
|
+
routedSessionId,
|
|
478
|
+
selectedStoredSessionId
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
const {
|
|
482
|
+
archiveSession,
|
|
483
|
+
branchCurrentSession,
|
|
484
|
+
createBackendSessionForSend,
|
|
485
|
+
openSettings,
|
|
486
|
+
removeSession,
|
|
487
|
+
resumeSession,
|
|
488
|
+
selectSidebarItem,
|
|
489
|
+
startFreshSessionDraft
|
|
490
|
+
} = useSessionActions({
|
|
491
|
+
activeSessionId,
|
|
492
|
+
activeSessionIdRef,
|
|
493
|
+
busyRef,
|
|
494
|
+
creatingSessionRef,
|
|
495
|
+
ensureSessionState,
|
|
496
|
+
getRouteToken,
|
|
497
|
+
navigate,
|
|
498
|
+
requestGateway,
|
|
499
|
+
runtimeIdByStoredSessionIdRef,
|
|
500
|
+
selectedStoredSessionId,
|
|
501
|
+
selectedStoredSessionIdRef,
|
|
502
|
+
sessionStateByRuntimeIdRef,
|
|
503
|
+
syncSessionStateToView,
|
|
504
|
+
updateSessionState
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
// Single global listener for every rebindable hotkey (incl. profile switching)
|
|
508
|
+
// plus the on-screen keybind editor's capture mode.
|
|
509
|
+
useKeybinds({
|
|
510
|
+
startFreshSession: startFreshSessionDraft,
|
|
511
|
+
toggleCommandCenter,
|
|
512
|
+
toggleSelectedPin
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
// A profile switch/create drops to a fresh new-session draft so the previously
|
|
516
|
+
// open session doesn't bleed across contexts. Skip the initial value.
|
|
517
|
+
const freshSessionRequest = useStore($freshSessionRequest)
|
|
518
|
+
const lastFreshRef = useRef(freshSessionRequest)
|
|
519
|
+
|
|
520
|
+
useEffect(() => {
|
|
521
|
+
if (freshSessionRequest === lastFreshRef.current) {
|
|
522
|
+
return
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
lastFreshRef.current = freshSessionRequest
|
|
526
|
+
startFreshSessionDraft()
|
|
527
|
+
}, [freshSessionRequest, startFreshSessionDraft])
|
|
528
|
+
|
|
529
|
+
// Swapping the live gateway to another profile must re-pull that profile's
|
|
530
|
+
// global model + active-profile pill. Both are nanostores, so the blanket
|
|
531
|
+
// invalidateQueries() the profile store fires on swap doesn't touch them —
|
|
532
|
+
// without this the statusbar keeps showing the previous profile's model
|
|
533
|
+
// (the "forgets the LLM setting" report). gatewayState stays 'open' across a
|
|
534
|
+
// swap (background sockets persist), so the open→open effect won't re-run.
|
|
535
|
+
const activeGatewayProfile = useStore($activeGatewayProfile)
|
|
536
|
+
const lastGatewayProfileRef = useRef(activeGatewayProfile)
|
|
537
|
+
|
|
538
|
+
useEffect(() => {
|
|
539
|
+
if (activeGatewayProfile === lastGatewayProfileRef.current) {
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
lastGatewayProfileRef.current = activeGatewayProfile
|
|
544
|
+
void refreshCurrentModel()
|
|
545
|
+
void refreshActiveProfile()
|
|
546
|
+
}, [activeGatewayProfile, refreshCurrentModel])
|
|
547
|
+
|
|
548
|
+
const composer = useComposerActions({
|
|
549
|
+
activeSessionId,
|
|
550
|
+
currentCwd,
|
|
551
|
+
requestGateway
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
const branchInNewChat = useCallback(
|
|
555
|
+
async (messageId?: string) => {
|
|
556
|
+
const branched = await branchCurrentSession(messageId)
|
|
557
|
+
|
|
558
|
+
if (branched) {
|
|
559
|
+
await refreshSessions().catch(() => undefined)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return branched
|
|
563
|
+
},
|
|
564
|
+
[branchCurrentSession, refreshSessions]
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
const startSessionInWorkspace = useCallback(
|
|
568
|
+
(path: null | string) => {
|
|
569
|
+
startFreshSessionDraft()
|
|
570
|
+
|
|
571
|
+
const target = path?.trim()
|
|
572
|
+
|
|
573
|
+
if (!target) {
|
|
574
|
+
return
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// The next message creates the backend session in $currentCwd, so seed
|
|
578
|
+
// it (and the branch) from the workspace the user clicked the + on.
|
|
579
|
+
setCurrentCwd(target)
|
|
580
|
+
void requestGateway<{ branch?: string; cwd?: string }>('config.get', { key: 'project', cwd: target })
|
|
581
|
+
.then(info => {
|
|
582
|
+
setCurrentCwd(info.cwd || target)
|
|
583
|
+
setCurrentBranch(info.branch || '')
|
|
584
|
+
})
|
|
585
|
+
.catch(() => undefined)
|
|
586
|
+
},
|
|
587
|
+
[requestGateway, startFreshSessionDraft]
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
const handleSkinCommand = useSkinCommand()
|
|
591
|
+
|
|
592
|
+
const {
|
|
593
|
+
cancelRun,
|
|
594
|
+
editMessage,
|
|
595
|
+
handleThreadMessagesChange,
|
|
596
|
+
reloadFromMessage,
|
|
597
|
+
steerPrompt,
|
|
598
|
+
submitText,
|
|
599
|
+
transcribeVoiceAudio
|
|
600
|
+
} = usePromptActions({
|
|
601
|
+
activeSessionId,
|
|
602
|
+
activeSessionIdRef,
|
|
603
|
+
branchCurrentSession: branchInNewChat,
|
|
604
|
+
busyRef,
|
|
605
|
+
createBackendSessionForSend,
|
|
606
|
+
handleSkinCommand,
|
|
607
|
+
refreshSessions,
|
|
608
|
+
requestGateway,
|
|
609
|
+
selectedStoredSessionIdRef,
|
|
610
|
+
startFreshSessionDraft,
|
|
611
|
+
sttEnabled,
|
|
612
|
+
updateSessionState
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
useGatewayBoot({
|
|
616
|
+
handleGatewayEvent: handleDesktopGatewayEvent,
|
|
617
|
+
onConnectionReady: c => {
|
|
618
|
+
connectionRef.current = c
|
|
619
|
+
},
|
|
620
|
+
onGatewayReady: g => {
|
|
621
|
+
gatewayRef.current = g
|
|
622
|
+
},
|
|
623
|
+
refreshNasTechConfig,
|
|
624
|
+
refreshSessions
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
useEffect(() => {
|
|
628
|
+
if (gatewayState === 'open') {
|
|
629
|
+
void refreshCurrentModel()
|
|
630
|
+
void refreshActiveProfile()
|
|
631
|
+
void refreshSessions().catch(() => undefined)
|
|
632
|
+
}
|
|
633
|
+
}, [gatewayState, refreshCurrentModel, refreshSessions])
|
|
634
|
+
|
|
635
|
+
// Keep the cron jobs section live without a user action: the scheduler ticks
|
|
636
|
+
// in the background (advancing next-run/state and creating runs), so poll the
|
|
637
|
+
// job list on an interval (and on tab re-focus) while connected.
|
|
638
|
+
useEffect(() => {
|
|
639
|
+
if (gatewayState !== 'open') {return}
|
|
640
|
+
|
|
641
|
+
const tick = () => {
|
|
642
|
+
if (document.visibilityState === 'visible') {void refreshCronJobs()}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const intervalId = window.setInterval(tick, CRON_POLL_INTERVAL_MS)
|
|
646
|
+
document.addEventListener('visibilitychange', tick)
|
|
647
|
+
|
|
648
|
+
return () => {
|
|
649
|
+
window.clearInterval(intervalId)
|
|
650
|
+
document.removeEventListener('visibilitychange', tick)
|
|
651
|
+
}
|
|
652
|
+
}, [gatewayState, refreshCronJobs])
|
|
653
|
+
|
|
654
|
+
useRouteResume({
|
|
655
|
+
activeSessionId,
|
|
656
|
+
activeSessionIdRef,
|
|
657
|
+
creatingSessionRef,
|
|
658
|
+
currentView,
|
|
659
|
+
freshDraftReady,
|
|
660
|
+
gatewayState,
|
|
661
|
+
locationPathname: location.pathname,
|
|
662
|
+
resumeSession,
|
|
663
|
+
routedSessionId,
|
|
664
|
+
runtimeIdByStoredSessionIdRef,
|
|
665
|
+
selectedStoredSessionId,
|
|
666
|
+
selectedStoredSessionIdRef,
|
|
667
|
+
startFreshSessionDraft
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
const { leftStatusbarItems, statusbarItems } = useStatusbarItems({
|
|
671
|
+
agentsOpen,
|
|
672
|
+
commandCenterOpen,
|
|
673
|
+
extraLeftItems: statusbarItemGroups.flat.left,
|
|
674
|
+
extraRightItems: statusbarItemGroups.flat.right,
|
|
675
|
+
gatewayLogLines,
|
|
676
|
+
gatewayState,
|
|
677
|
+
inferenceStatus,
|
|
678
|
+
modelMenuContent,
|
|
679
|
+
openAgents,
|
|
680
|
+
freshDraftReady,
|
|
681
|
+
openCommandCenterSection,
|
|
682
|
+
requestGateway,
|
|
683
|
+
statusSnapshot,
|
|
684
|
+
toggleCommandCenter
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
const sidebar = (
|
|
688
|
+
<ChatSidebar
|
|
689
|
+
currentView={currentView}
|
|
690
|
+
onArchiveSession={sessionId => void archiveSession(sessionId)}
|
|
691
|
+
onDeleteSession={sessionId => void removeSession(sessionId)}
|
|
692
|
+
onLoadMoreProfileSessions={loadMoreSessionsForProfile}
|
|
693
|
+
onLoadMoreSessions={loadMoreSessions}
|
|
694
|
+
onManageCronJob={jobId => {
|
|
695
|
+
setCronFocusJobId(jobId)
|
|
696
|
+
navigate(CRON_ROUTE)
|
|
697
|
+
}}
|
|
698
|
+
onNavigate={selectSidebarItem}
|
|
699
|
+
onNewSessionInWorkspace={startSessionInWorkspace}
|
|
700
|
+
onResumeSession={sessionId => navigate(sessionRoute(sessionId))}
|
|
701
|
+
onTriggerCronJob={jobId => {
|
|
702
|
+
void triggerCronJob(jobId)
|
|
703
|
+
.then(() => refreshCronJobs())
|
|
704
|
+
.catch(() => undefined)
|
|
705
|
+
}}
|
|
706
|
+
/>
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
const overlays = (
|
|
710
|
+
<>
|
|
711
|
+
<DesktopInstallOverlay />
|
|
712
|
+
{/* One PTY-backed terminal mounted forever; <TerminalSlot /> placeholders
|
|
713
|
+
decide where it shows. Toggling fullscreen never rebuilds the shell. */}
|
|
714
|
+
<PersistentTerminal cwd={currentCwd} onAddSelectionToChat={composer.addTerminalSelectionAttachment} />
|
|
715
|
+
<DesktopOnboardingOverlay
|
|
716
|
+
enabled={gatewayState === 'open'}
|
|
717
|
+
onCompleted={() => {
|
|
718
|
+
void refreshNasTechConfig()
|
|
719
|
+
void refreshCurrentModel()
|
|
720
|
+
void queryClient.invalidateQueries({ queryKey: ['model-options'] })
|
|
721
|
+
}}
|
|
722
|
+
requestGateway={requestGateway}
|
|
723
|
+
/>
|
|
724
|
+
<ModelPickerOverlay gateway={gatewayRef.current || undefined} onSelect={selectModel} />
|
|
725
|
+
<ModelVisibilityOverlay gateway={gatewayRef.current || undefined} onOpenProviders={openProviderSettings} />
|
|
726
|
+
<UpdatesOverlay />
|
|
727
|
+
<GatewayConnectingOverlay />
|
|
728
|
+
<BootFailureOverlay />
|
|
729
|
+
<CommandPalette />
|
|
730
|
+
|
|
731
|
+
{settingsOpen && (
|
|
732
|
+
<Suspense fallback={null}>
|
|
733
|
+
<SettingsView
|
|
734
|
+
gateway={gatewayRef.current}
|
|
735
|
+
onClose={closeOverlayToPreviousRoute}
|
|
736
|
+
onConfigSaved={() => {
|
|
737
|
+
void refreshNasTechConfig()
|
|
738
|
+
void refreshCurrentModel()
|
|
739
|
+
void queryClient.invalidateQueries({ queryKey: ['model-options'] })
|
|
740
|
+
}}
|
|
741
|
+
onMainModelChanged={(provider, model) => {
|
|
742
|
+
setCurrentProvider(provider)
|
|
743
|
+
setCurrentModel(model)
|
|
744
|
+
updateModelOptionsCache(provider, model, true)
|
|
745
|
+
void refreshCurrentModel()
|
|
746
|
+
void queryClient.invalidateQueries({ queryKey: ['model-options'] })
|
|
747
|
+
}}
|
|
748
|
+
/>
|
|
749
|
+
</Suspense>
|
|
750
|
+
)}
|
|
751
|
+
|
|
752
|
+
{commandCenterOpen && (
|
|
753
|
+
<Suspense fallback={null}>
|
|
754
|
+
<CommandCenterView
|
|
755
|
+
initialSection={commandCenterInitialSection}
|
|
756
|
+
onClose={closeOverlayToPreviousRoute}
|
|
757
|
+
onDeleteSession={removeSession}
|
|
758
|
+
onNavigateRoute={path => navigate(path)}
|
|
759
|
+
onOpenSession={sessionId => navigate(sessionRoute(sessionId))}
|
|
760
|
+
/>
|
|
761
|
+
</Suspense>
|
|
762
|
+
)}
|
|
763
|
+
|
|
764
|
+
{agentsOpen && (
|
|
765
|
+
<Suspense fallback={null}>
|
|
766
|
+
<AgentsView onClose={closeOverlayToPreviousRoute} />
|
|
767
|
+
</Suspense>
|
|
768
|
+
)}
|
|
769
|
+
|
|
770
|
+
{cronOpen && (
|
|
771
|
+
<Suspense fallback={null}>
|
|
772
|
+
<CronView
|
|
773
|
+
onClose={closeOverlayToPreviousRoute}
|
|
774
|
+
onOpenSession={sessionId => navigate(sessionRoute(sessionId))}
|
|
775
|
+
/>
|
|
776
|
+
</Suspense>
|
|
777
|
+
)}
|
|
778
|
+
|
|
779
|
+
{profilesOpen && (
|
|
780
|
+
<Suspense fallback={null}>
|
|
781
|
+
<ProfilesView onClose={closeOverlayToPreviousRoute} />
|
|
782
|
+
</Suspense>
|
|
783
|
+
)}
|
|
784
|
+
</>
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
const chatView = (
|
|
788
|
+
<ChatView
|
|
789
|
+
gateway={gatewayRef.current}
|
|
790
|
+
maxVoiceRecordingSeconds={voiceMaxRecordingSeconds}
|
|
791
|
+
onAddContextRef={composer.addContextRefAttachment}
|
|
792
|
+
onAddUrl={url => composer.addContextRefAttachment(`@url:${formatRefValue(url)}`, url)}
|
|
793
|
+
onAttachDroppedItems={composer.attachDroppedItems}
|
|
794
|
+
onAttachImageBlob={composer.attachImageBlob}
|
|
795
|
+
onBranchInNewChat={branchInNewChat}
|
|
796
|
+
onCancel={cancelRun}
|
|
797
|
+
onDeleteSelectedSession={() => {
|
|
798
|
+
if (selectedStoredSessionId) {
|
|
799
|
+
void removeSession(selectedStoredSessionId)
|
|
800
|
+
}
|
|
801
|
+
}}
|
|
802
|
+
onEdit={editMessage}
|
|
803
|
+
onPasteClipboardImage={() => void composer.pasteClipboardImage()}
|
|
804
|
+
onPickFiles={() => void composer.pickContextPaths('file')}
|
|
805
|
+
onPickFolders={() => void composer.pickContextPaths('folder')}
|
|
806
|
+
onPickImages={() => void composer.pickImages()}
|
|
807
|
+
onReload={reloadFromMessage}
|
|
808
|
+
onRemoveAttachment={id => void composer.removeAttachment(id)}
|
|
809
|
+
onSteer={steerPrompt}
|
|
810
|
+
onSubmit={submitText}
|
|
811
|
+
onThreadMessagesChange={handleThreadMessagesChange}
|
|
812
|
+
onToggleSelectedPin={toggleSelectedPin}
|
|
813
|
+
onTranscribeAudio={transcribeVoiceAudio}
|
|
814
|
+
/>
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
const takeoverTerminalView = (
|
|
818
|
+
<div className="relative flex h-full min-h-0 min-w-0 flex-col overflow-hidden bg-(--ui-chat-surface-background) pt-(--titlebar-height)">
|
|
819
|
+
<TerminalSlot />
|
|
820
|
+
</div>
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
// Flipped layout mirrors the default: sessions sidebar → right, file
|
|
824
|
+
// browser + preview rail → left. Same panes, swapped sides.
|
|
825
|
+
const sidebarSide = panesFlipped ? 'right' : 'left'
|
|
826
|
+
const railSide = panesFlipped ? 'left' : 'right'
|
|
827
|
+
|
|
828
|
+
const previewPane = (
|
|
829
|
+
<Pane
|
|
830
|
+
disabled={!chatOpen || (!previewTarget && !filePreviewTarget)}
|
|
831
|
+
id="preview"
|
|
832
|
+
key="preview"
|
|
833
|
+
maxWidth={PREVIEW_RAIL_MAX_WIDTH}
|
|
834
|
+
minWidth={PREVIEW_RAIL_MIN_WIDTH}
|
|
835
|
+
resizable
|
|
836
|
+
side={railSide}
|
|
837
|
+
width={PREVIEW_RAIL_PANE_WIDTH}
|
|
838
|
+
>
|
|
839
|
+
{chatOpen ? (
|
|
840
|
+
<ChatPreviewRail onRestartServer={restartPreviewServer} setTitlebarToolGroup={setTitlebarToolGroup} />
|
|
841
|
+
) : null}
|
|
842
|
+
</Pane>
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
const fileBrowserPane = (
|
|
846
|
+
<Pane
|
|
847
|
+
defaultOpen={false}
|
|
848
|
+
disabled={!chatOpen}
|
|
849
|
+
id="file-browser"
|
|
850
|
+
key="file-browser"
|
|
851
|
+
maxWidth={FILE_BROWSER_MAX_WIDTH}
|
|
852
|
+
minWidth={FILE_BROWSER_MIN_WIDTH}
|
|
853
|
+
resizable
|
|
854
|
+
side={railSide}
|
|
855
|
+
width={FILE_BROWSER_DEFAULT_WIDTH}
|
|
856
|
+
>
|
|
857
|
+
<RightSidebarPane
|
|
858
|
+
onActivateFile={composer.attachContextFilePath}
|
|
859
|
+
onActivateFolder={composer.attachContextFolderPath}
|
|
860
|
+
onChangeCwd={changeSessionCwd}
|
|
861
|
+
/>
|
|
862
|
+
</Pane>
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
return (
|
|
866
|
+
<AppShell
|
|
867
|
+
leftStatusbarItems={leftStatusbarItems}
|
|
868
|
+
leftTitlebarTools={titlebarToolGroups.flat.left}
|
|
869
|
+
onOpenSettings={openSettings}
|
|
870
|
+
overlays={overlays}
|
|
871
|
+
statusbarItems={statusbarItems}
|
|
872
|
+
titlebarTools={titlebarToolGroups.flat.right}
|
|
873
|
+
>
|
|
874
|
+
<Pane
|
|
875
|
+
disabled={terminalTakeoverActive}
|
|
876
|
+
id="chat-sidebar"
|
|
877
|
+
maxWidth={SIDEBAR_MAX_WIDTH}
|
|
878
|
+
minWidth={SIDEBAR_DEFAULT_WIDTH}
|
|
879
|
+
resizable
|
|
880
|
+
side={sidebarSide}
|
|
881
|
+
width={`${SIDEBAR_DEFAULT_WIDTH}px`}
|
|
882
|
+
>
|
|
883
|
+
{sidebar}
|
|
884
|
+
</Pane>
|
|
885
|
+
<PaneMain>
|
|
886
|
+
<Routes>
|
|
887
|
+
<Route element={terminalTakeoverActive ? takeoverTerminalView : chatView} index />
|
|
888
|
+
<Route element={terminalTakeoverActive ? takeoverTerminalView : chatView} path=":sessionId" />
|
|
889
|
+
<Route
|
|
890
|
+
element={
|
|
891
|
+
<Suspense fallback={null}>
|
|
892
|
+
<SkillsView setStatusbarItemGroup={setStatusbarItemGroup} />
|
|
893
|
+
</Suspense>
|
|
894
|
+
}
|
|
895
|
+
path="skills"
|
|
896
|
+
/>
|
|
897
|
+
<Route
|
|
898
|
+
element={
|
|
899
|
+
<Suspense fallback={null}>
|
|
900
|
+
<MessagingView setStatusbarItemGroup={setStatusbarItemGroup} />
|
|
901
|
+
</Suspense>
|
|
902
|
+
}
|
|
903
|
+
path="messaging"
|
|
904
|
+
/>
|
|
905
|
+
<Route
|
|
906
|
+
element={
|
|
907
|
+
<Suspense fallback={null}>
|
|
908
|
+
<ArtifactsView setStatusbarItemGroup={setStatusbarItemGroup} />
|
|
909
|
+
</Suspense>
|
|
910
|
+
}
|
|
911
|
+
path="artifacts"
|
|
912
|
+
/>
|
|
913
|
+
<Route element={null} path="cron" />
|
|
914
|
+
<Route element={null} path="profiles" />
|
|
915
|
+
<Route element={null} path="settings" />
|
|
916
|
+
<Route element={null} path="command-center" />
|
|
917
|
+
<Route element={null} path="agents" />
|
|
918
|
+
<Route element={<Navigate replace to={NEW_CHAT_ROUTE} />} path="new" />
|
|
919
|
+
<Route element={<LegacySessionRedirect />} path="sessions/:sessionId" />
|
|
920
|
+
<Route element={<Navigate replace to={NEW_CHAT_ROUTE} />} path="*" />
|
|
921
|
+
</Routes>
|
|
922
|
+
</PaneMain>
|
|
923
|
+
{/*
|
|
924
|
+
Order within a side maps to column order. Default (rail on the right):
|
|
925
|
+
main | preview | file-browser. Flipped (rail on the left): mirror it to
|
|
926
|
+
file-browser | preview | main so preview stays adjacent to the chat.
|
|
927
|
+
*/}
|
|
928
|
+
{panesFlipped ? fileBrowserPane : previewPane}
|
|
929
|
+
{panesFlipped ? previewPane : fileBrowserPane}
|
|
930
|
+
</AppShell>
|
|
931
|
+
)
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function LegacySessionRedirect() {
|
|
935
|
+
const { sessionId } = useParams()
|
|
936
|
+
|
|
937
|
+
return <Navigate replace to={sessionId ? sessionRoute(sessionId) : NEW_CHAT_ROUTE} />
|
|
938
|
+
}
|