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,30 @@
|
|
|
1
|
+
import { Codicon } from '@/components/ui/codicon'
|
|
2
|
+
import { useI18n } from '@/i18n'
|
|
3
|
+
|
|
4
|
+
interface SidebarLoadMoreRowProps {
|
|
5
|
+
step: number
|
|
6
|
+
onClick: () => void
|
|
7
|
+
loading?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// "Load N more" affordance shared by the recents, messaging, and cron sections.
|
|
11
|
+
// The chevron sits in the same w-3.5 column the rows use for their dot, so it
|
|
12
|
+
// lines up with the list above.
|
|
13
|
+
export function SidebarLoadMoreRow({ step, onClick, loading = false }: SidebarLoadMoreRowProps) {
|
|
14
|
+
const { t } = useI18n()
|
|
15
|
+
const label = loading ? t.sidebar.loading : step > 0 ? t.sidebar.loadCount(step) : t.sidebar.loadMore
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<button
|
|
19
|
+
className="flex min-h-5 items-center gap-1.5 self-start bg-transparent pl-2 text-left text-[0.6875rem] text-(--ui-text-tertiary) transition-colors duration-100 ease-out hover:text-foreground hover:transition-none disabled:cursor-default disabled:opacity-60 disabled:hover:text-(--ui-text-tertiary)"
|
|
20
|
+
disabled={loading}
|
|
21
|
+
onClick={onClick}
|
|
22
|
+
type="button"
|
|
23
|
+
>
|
|
24
|
+
<span className="grid w-3.5 shrink-0 place-items-center">
|
|
25
|
+
<Codicon className="opacity-70" name={loading ? 'loading' : 'chevron-down'} size="0.75rem" spinning={loading} />
|
|
26
|
+
</span>
|
|
27
|
+
<span>{label}</span>
|
|
28
|
+
</button>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { resolveManualSessionOrderIds } from './order'
|
|
4
|
+
|
|
5
|
+
describe('resolveManualSessionOrderIds', () => {
|
|
6
|
+
it('clears legacy auto-seeded order until the user manually reorders sessions', () => {
|
|
7
|
+
expect(resolveManualSessionOrderIds(['newest', 'older'], ['older', 'newest'], false)).toEqual([])
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('keeps a manual order and surfaces newly seen sessions first', () => {
|
|
11
|
+
expect(resolveManualSessionOrderIds(['newest', 'older', 'oldest'], ['oldest', 'older'], true)).toEqual([
|
|
12
|
+
'newest',
|
|
13
|
+
'oldest',
|
|
14
|
+
'older'
|
|
15
|
+
])
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('clears manual order when none of the saved ids still exist', () => {
|
|
19
|
+
expect(resolveManualSessionOrderIds(['newest'], ['gone'], true)).toEqual([])
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function resolveManualSessionOrderIds(currentIds: string[], orderIds: string[], manual: boolean): string[] {
|
|
2
|
+
if (!manual || !currentIds.length || !orderIds.length) {
|
|
3
|
+
return []
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const current = new Set(currentIds)
|
|
7
|
+
const retained = orderIds.filter(id => current.has(id))
|
|
8
|
+
|
|
9
|
+
if (!retained.length) {
|
|
10
|
+
return []
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const retainedSet = new Set(retained)
|
|
14
|
+
const fresh = currentIds.filter(id => !retainedSet.has(id))
|
|
15
|
+
|
|
16
|
+
return [...fresh, ...retained]
|
|
17
|
+
}
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closestCenter,
|
|
3
|
+
DndContext,
|
|
4
|
+
type DragEndEvent,
|
|
5
|
+
type DragOverEvent,
|
|
6
|
+
type DragStartEvent,
|
|
7
|
+
KeyboardSensor,
|
|
8
|
+
type Modifier,
|
|
9
|
+
PointerSensor,
|
|
10
|
+
useSensor,
|
|
11
|
+
useSensors
|
|
12
|
+
} from '@dnd-kit/core'
|
|
13
|
+
import {
|
|
14
|
+
arrayMove,
|
|
15
|
+
horizontalListSortingStrategy,
|
|
16
|
+
SortableContext,
|
|
17
|
+
sortableKeyboardCoordinates,
|
|
18
|
+
useSortable
|
|
19
|
+
} from '@dnd-kit/sortable'
|
|
20
|
+
import { CSS } from '@dnd-kit/utilities'
|
|
21
|
+
import { useStore } from '@nanostores/react'
|
|
22
|
+
import { useEffect, useRef, useState } from 'react'
|
|
23
|
+
import { useNavigate } from 'react-router-dom'
|
|
24
|
+
|
|
25
|
+
import { Button } from '@/components/ui/button'
|
|
26
|
+
import { Codicon } from '@/components/ui/codicon'
|
|
27
|
+
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu'
|
|
28
|
+
import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/popover'
|
|
29
|
+
import { Tip, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
|
30
|
+
import { useI18n } from '@/i18n'
|
|
31
|
+
import { triggerHaptic } from '@/lib/haptics'
|
|
32
|
+
import { PROFILE_SWATCHES, profileColorSoft, resolveProfileColor } from '@/lib/profile-color'
|
|
33
|
+
import { cn } from '@/lib/utils'
|
|
34
|
+
import {
|
|
35
|
+
$activeGatewayProfile,
|
|
36
|
+
$profileColors,
|
|
37
|
+
$profileCreateRequest,
|
|
38
|
+
$profileOrder,
|
|
39
|
+
$profiles,
|
|
40
|
+
$profileScope,
|
|
41
|
+
ALL_PROFILES,
|
|
42
|
+
normalizeProfileKey,
|
|
43
|
+
refreshActiveProfile,
|
|
44
|
+
selectProfile,
|
|
45
|
+
setProfileColor,
|
|
46
|
+
setProfileOrder,
|
|
47
|
+
setShowAllProfiles,
|
|
48
|
+
sortByProfileOrder
|
|
49
|
+
} from '@/store/profile'
|
|
50
|
+
import type { ProfileInfo } from '@/types/nastech'
|
|
51
|
+
|
|
52
|
+
import { CreateProfileDialog } from '../../profiles/create-profile-dialog'
|
|
53
|
+
import { DeleteProfileDialog } from '../../profiles/delete-profile-dialog'
|
|
54
|
+
import { RenameProfileDialog } from '../../profiles/rename-profile-dialog'
|
|
55
|
+
import { PROFILES_ROUTE } from '../../routes'
|
|
56
|
+
|
|
57
|
+
const RAIL_GAP = 4 // px — matches gap-1 between squares.
|
|
58
|
+
|
|
59
|
+
// easeOutBack — a little overshoot so squares spring into their new slot rather
|
|
60
|
+
// than sliding in flat. Neighbors reflow on RAIL_TRANSITION; the dragged square
|
|
61
|
+
// glides between snapped cells on the snappier DRAG_TRANSITION.
|
|
62
|
+
const SPRING = 'cubic-bezier(0.34, 1.56, 0.64, 1)'
|
|
63
|
+
const RAIL_TRANSITION = { duration: 300, easing: SPRING }
|
|
64
|
+
const DRAG_TRANSITION = `transform 200ms ${SPRING}`
|
|
65
|
+
|
|
66
|
+
// The rail is a single horizontal strip of fixed cells. Pin drags to the x-axis
|
|
67
|
+
// (no cross-axis scrollbar), snap to whole cells so a square steps slot-to-slot
|
|
68
|
+
// instead of gliding, and clamp to the occupied strip so it can't float past the
|
|
69
|
+
// last profile onto the "+".
|
|
70
|
+
const stepThroughCells: Modifier = ({ containerNodeRect, draggingNodeRect, transform }) => {
|
|
71
|
+
if (!draggingNodeRect || !containerNodeRect) {
|
|
72
|
+
return { ...transform, y: 0 }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const pitch = draggingNodeRect.width + RAIL_GAP
|
|
76
|
+
const minX = containerNodeRect.left - draggingNodeRect.left
|
|
77
|
+
const maxX = containerNodeRect.right - draggingNodeRect.right
|
|
78
|
+
const snapped = Math.round(transform.x / pitch) * pitch
|
|
79
|
+
|
|
80
|
+
return { ...transform, x: Math.min(maxX, Math.max(minX, snapped)), y: 0 }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Arc-Spaces-style profile rail at the sidebar foot: a default↔all toggle pinned
|
|
84
|
+
// left, the colored named profiles scrolling between, and Manage pinned right.
|
|
85
|
+
// The active profile pops in its own color — the "where am I" cue. Single-
|
|
86
|
+
// profile users see only the "+" (create their first profile); everything else
|
|
87
|
+
// appears once a second profile exists.
|
|
88
|
+
export function ProfileRail() {
|
|
89
|
+
const { t } = useI18n()
|
|
90
|
+
const p = t.profiles
|
|
91
|
+
const profiles = useStore($profiles)
|
|
92
|
+
const scope = useStore($profileScope)
|
|
93
|
+
const gatewayProfile = useStore($activeGatewayProfile)
|
|
94
|
+
const order = useStore($profileOrder)
|
|
95
|
+
const colors = useStore($profileColors)
|
|
96
|
+
const navigate = useNavigate()
|
|
97
|
+
|
|
98
|
+
const [createOpen, setCreateOpen] = useState(false)
|
|
99
|
+
const [pendingRename, setPendingRename] = useState<null | ProfileInfo>(null)
|
|
100
|
+
const [pendingDelete, setPendingDelete] = useState<null | ProfileInfo>(null)
|
|
101
|
+
const scrollRef = useRef<HTMLDivElement>(null)
|
|
102
|
+
|
|
103
|
+
// A plain mouse wheel only emits deltaY; map it to horizontal scroll so the
|
|
104
|
+
// rail is navigable without a trackpad. Trackpad x-scroll (deltaX) passes
|
|
105
|
+
// through. Native + non-passive so we can preventDefault and not bleed the
|
|
106
|
+
// gesture into the sessions list above.
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
const el = scrollRef.current
|
|
109
|
+
|
|
110
|
+
if (!el) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const onWheel = (event: WheelEvent) => {
|
|
115
|
+
if (el.scrollWidth <= el.clientWidth || Math.abs(event.deltaY) <= Math.abs(event.deltaX)) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
el.scrollLeft += event.deltaY
|
|
120
|
+
event.preventDefault()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
el.addEventListener('wheel', onWheel, { passive: false })
|
|
124
|
+
|
|
125
|
+
return () => el.removeEventListener('wheel', onWheel)
|
|
126
|
+
}, [])
|
|
127
|
+
|
|
128
|
+
const isAll = scope === ALL_PROFILES
|
|
129
|
+
const activeKey = normalizeProfileKey(gatewayProfile)
|
|
130
|
+
const defaultProfile = profiles.find(profile => profile.is_default)
|
|
131
|
+
const onDefault = !isAll && activeKey === 'default'
|
|
132
|
+
|
|
133
|
+
const named = sortByProfileOrder(profiles.filter(profile => !profile.is_default), order)
|
|
134
|
+
const multiProfile = profiles.length > 1
|
|
135
|
+
|
|
136
|
+
// distance constraint: a small drag reorders, a tap still selects the profile.
|
|
137
|
+
const sensors = useSensors(
|
|
138
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
|
|
139
|
+
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
// Tick a haptic each time the drag crosses into a new cell, and a satisfying
|
|
143
|
+
// confirm on a committed reorder.
|
|
144
|
+
const lastOverRef = useRef<string | null>(null)
|
|
145
|
+
|
|
146
|
+
const handleDragStart = ({ active }: DragStartEvent) => {
|
|
147
|
+
lastOverRef.current = String(active.id)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const handleDragOver = ({ over }: DragOverEvent) => {
|
|
151
|
+
const id = over ? String(over.id) : null
|
|
152
|
+
|
|
153
|
+
if (id && id !== lastOverRef.current) {
|
|
154
|
+
lastOverRef.current = id
|
|
155
|
+
triggerHaptic('selection')
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const handleDragEnd = ({ active, over }: DragEndEvent) => {
|
|
160
|
+
lastOverRef.current = null
|
|
161
|
+
|
|
162
|
+
if (!over || active.id === over.id) {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const ids = named.map(profile => profile.name)
|
|
167
|
+
const from = ids.indexOf(String(active.id))
|
|
168
|
+
const to = ids.indexOf(String(over.id))
|
|
169
|
+
|
|
170
|
+
if (from >= 0 && to >= 0) {
|
|
171
|
+
setProfileOrder(arrayMove(ids, from, to))
|
|
172
|
+
triggerHaptic('success')
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Re-pull the running profile + list on mount so a profile created elsewhere
|
|
177
|
+
// shows up; cheap and best-effort.
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
void refreshActiveProfile()
|
|
180
|
+
}, [])
|
|
181
|
+
|
|
182
|
+
// Open the create dialog when the `profile.create` hotkey fires (the dialog
|
|
183
|
+
// state lives here, so the global keybind bumps a request atom we watch).
|
|
184
|
+
const createRequest = useStore($profileCreateRequest)
|
|
185
|
+
const lastCreateRef = useRef(createRequest)
|
|
186
|
+
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (createRequest === lastCreateRef.current) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
lastCreateRef.current = createRequest
|
|
193
|
+
setCreateOpen(true)
|
|
194
|
+
}, [createRequest])
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div aria-label="Profiles" className="flex items-center gap-0.5" role="tablist">
|
|
198
|
+
{/* One button toggles default ↔ all: home face when scoped to a profile,
|
|
199
|
+
layers face when showing everything. Pinned left like Manage is right.
|
|
200
|
+
Hidden until a second profile exists. */}
|
|
201
|
+
{multiProfile &&
|
|
202
|
+
(defaultProfile ? (
|
|
203
|
+
// On default → toggle to all. Anywhere else (all view or a named
|
|
204
|
+
// profile) → return to default. So leaving a profile never lands on all.
|
|
205
|
+
<ProfilePill
|
|
206
|
+
active={isAll || onDefault}
|
|
207
|
+
glyph={isAll ? 'layers' : 'home'}
|
|
208
|
+
label={onDefault ? p.showAllProfiles : p.switchToProfile(defaultProfile.name)}
|
|
209
|
+
onSelect={() => (onDefault ? setShowAllProfiles(true) : selectProfile(defaultProfile.name))}
|
|
210
|
+
/>
|
|
211
|
+
) : (
|
|
212
|
+
<ProfilePill active={isAll} glyph="layers" label={p.allProfiles} onSelect={() => setShowAllProfiles(true)} />
|
|
213
|
+
))}
|
|
214
|
+
|
|
215
|
+
{/* Single-profile: the active default's home icon next to the create +. */}
|
|
216
|
+
{!multiProfile && defaultProfile && (
|
|
217
|
+
<ProfilePill
|
|
218
|
+
active
|
|
219
|
+
glyph="home"
|
|
220
|
+
label={defaultProfile.name}
|
|
221
|
+
onSelect={() => selectProfile(defaultProfile.name)}
|
|
222
|
+
/>
|
|
223
|
+
)}
|
|
224
|
+
|
|
225
|
+
<div
|
|
226
|
+
className="flex min-w-0 flex-1 items-center gap-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
227
|
+
ref={scrollRef}
|
|
228
|
+
>
|
|
229
|
+
{multiProfile && (
|
|
230
|
+
<DndContext
|
|
231
|
+
collisionDetection={closestCenter}
|
|
232
|
+
modifiers={[stepThroughCells]}
|
|
233
|
+
onDragEnd={handleDragEnd}
|
|
234
|
+
onDragOver={handleDragOver}
|
|
235
|
+
onDragStart={handleDragStart}
|
|
236
|
+
sensors={sensors}
|
|
237
|
+
>
|
|
238
|
+
<SortableContext items={named.map(profile => profile.name)} strategy={horizontalListSortingStrategy}>
|
|
239
|
+
{/* relative → the strip is the dragged square's offsetParent, so the
|
|
240
|
+
clamp modifier bounds drags to the occupied cells (not the +). */}
|
|
241
|
+
<div className="relative flex items-center gap-1">
|
|
242
|
+
{named.map(profile => (
|
|
243
|
+
<ProfileSquare
|
|
244
|
+
active={!isAll && normalizeProfileKey(profile.name) === activeKey}
|
|
245
|
+
color={resolveProfileColor(profile.name, colors)}
|
|
246
|
+
key={profile.name}
|
|
247
|
+
label={profile.name}
|
|
248
|
+
onDelete={() => setPendingDelete(profile)}
|
|
249
|
+
onRecolor={color => setProfileColor(profile.name, color)}
|
|
250
|
+
onRename={() => setPendingRename(profile)}
|
|
251
|
+
onSelect={() => selectProfile(profile.name)}
|
|
252
|
+
/>
|
|
253
|
+
))}
|
|
254
|
+
</div>
|
|
255
|
+
</SortableContext>
|
|
256
|
+
</DndContext>
|
|
257
|
+
)}
|
|
258
|
+
|
|
259
|
+
<Tip label={p.newProfile}>
|
|
260
|
+
<button
|
|
261
|
+
aria-label={p.newProfile}
|
|
262
|
+
className="grid size-5 shrink-0 place-items-center rounded-[3px] text-(--ui-text-tertiary) opacity-55 transition hover:bg-(--ui-control-hover-background) hover:text-foreground hover:opacity-100"
|
|
263
|
+
onClick={() => setCreateOpen(true)}
|
|
264
|
+
type="button"
|
|
265
|
+
>
|
|
266
|
+
<Codicon name="add" size="0.75rem" />
|
|
267
|
+
</button>
|
|
268
|
+
</Tip>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
{multiProfile && (
|
|
272
|
+
<ProfilePill active={false} glyph="ellipsis" label={p.manageProfiles} onSelect={() => navigate(PROFILES_ROUTE)} />
|
|
273
|
+
)}
|
|
274
|
+
|
|
275
|
+
{/* Land in the new profile on a fresh chat (selectProfile triggers the
|
|
276
|
+
new-session reset), not stuck on the session you were just in. */}
|
|
277
|
+
<CreateProfileDialog
|
|
278
|
+
onClose={() => setCreateOpen(false)}
|
|
279
|
+
onCreated={async name => {
|
|
280
|
+
await refreshActiveProfile()
|
|
281
|
+
selectProfile(name)
|
|
282
|
+
}}
|
|
283
|
+
open={createOpen}
|
|
284
|
+
/>
|
|
285
|
+
|
|
286
|
+
<RenameProfileDialog
|
|
287
|
+
currentName={pendingRename?.name ?? ''}
|
|
288
|
+
onClose={() => setPendingRename(null)}
|
|
289
|
+
onRenamed={refreshActiveProfile}
|
|
290
|
+
open={pendingRename !== null}
|
|
291
|
+
/>
|
|
292
|
+
|
|
293
|
+
<DeleteProfileDialog
|
|
294
|
+
onClose={() => setPendingDelete(null)}
|
|
295
|
+
onDeleted={refreshActiveProfile}
|
|
296
|
+
open={pendingDelete !== null}
|
|
297
|
+
profile={pendingDelete}
|
|
298
|
+
/>
|
|
299
|
+
</div>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
interface ProfilePillProps {
|
|
304
|
+
active: boolean
|
|
305
|
+
// home / All / Manage are glyph action buttons (navigation, not identity).
|
|
306
|
+
glyph: string
|
|
307
|
+
label: string
|
|
308
|
+
onSelect: () => void
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function ProfilePill({ active, glyph, label, onSelect }: ProfilePillProps) {
|
|
312
|
+
return (
|
|
313
|
+
<Tip label={label}>
|
|
314
|
+
<Button
|
|
315
|
+
aria-label={label}
|
|
316
|
+
aria-pressed={active}
|
|
317
|
+
className={cn(
|
|
318
|
+
'bg-transparent text-(--ui-text-tertiary) hover:bg-(--ui-control-hover-background) hover:text-foreground',
|
|
319
|
+
active && 'bg-(--ui-control-active-background) text-foreground'
|
|
320
|
+
)}
|
|
321
|
+
onClick={onSelect}
|
|
322
|
+
size="icon-xs"
|
|
323
|
+
type="button"
|
|
324
|
+
variant="ghost"
|
|
325
|
+
>
|
|
326
|
+
<Codicon name={glyph} size="0.875rem" />
|
|
327
|
+
</Button>
|
|
328
|
+
</Tip>
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
interface ProfileSquareProps {
|
|
333
|
+
active: boolean
|
|
334
|
+
color: null | string
|
|
335
|
+
label: string
|
|
336
|
+
onSelect: () => void
|
|
337
|
+
onRecolor: (color: null | string) => void
|
|
338
|
+
onRename: () => void
|
|
339
|
+
onDelete: () => void
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Hold this long without moving (a drag would have started first) to open the
|
|
343
|
+
// color picker — the "hard press" gesture, distinct from tap-to-select.
|
|
344
|
+
const LONG_PRESS_MS = 450
|
|
345
|
+
|
|
346
|
+
// A profile *is* its colored square — no icon-button chrome. Soft profile-tint
|
|
347
|
+
// fill + the initial in the full color; the active one pops to full opacity with
|
|
348
|
+
// a color ring. These pack tightly so the rail reads as a strip of profiles,
|
|
349
|
+
// drag-sort to reorder (a tap below the drag threshold still selects), and
|
|
350
|
+
// right-click to rename/delete. The button carries both the tooltip and
|
|
351
|
+
// context-menu triggers via nested asChild Slots, so a single element keeps the
|
|
352
|
+
// dnd listeners, hover tip, and right-click menu.
|
|
353
|
+
function ProfileSquare({ active, color, label, onDelete, onRecolor, onRename, onSelect }: ProfileSquareProps) {
|
|
354
|
+
const { t } = useI18n()
|
|
355
|
+
const p = t.profiles
|
|
356
|
+
const hue = color ?? 'var(--ui-text-quaternary)'
|
|
357
|
+
const [pickerOpen, setPickerOpen] = useState(false)
|
|
358
|
+
const pressTimer = useRef<null | number>(null)
|
|
359
|
+
const suppressClick = useRef(false)
|
|
360
|
+
|
|
361
|
+
const { attributes, isDragging, listeners, setNodeRef, transform, transition } = useSortable({
|
|
362
|
+
id: label,
|
|
363
|
+
transition: RAIL_TRANSITION
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
const clearPress = () => {
|
|
367
|
+
if (pressTimer.current != null) {
|
|
368
|
+
clearTimeout(pressTimer.current)
|
|
369
|
+
pressTimer.current = null
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// A real drag (movement past the dnd threshold) cancels the pending hold, so a
|
|
374
|
+
// reorder never doubles as a color pick. Also tidy up on unmount.
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
if (isDragging) {
|
|
377
|
+
clearPress()
|
|
378
|
+
}
|
|
379
|
+
}, [isDragging])
|
|
380
|
+
useEffect(() => clearPress, [])
|
|
381
|
+
|
|
382
|
+
const base = CSS.Transform.toString(transform)
|
|
383
|
+
const ring = active ? `inset 0 0 0 1.5px ${hue}` : ''
|
|
384
|
+
const lift = isDragging ? '0 6px 16px -4px rgb(0 0 0 / 0.4)' : ''
|
|
385
|
+
|
|
386
|
+
const pickColor = (next: null | string) => {
|
|
387
|
+
onRecolor(next)
|
|
388
|
+
setPickerOpen(false)
|
|
389
|
+
triggerHaptic('selection')
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return (
|
|
393
|
+
<Popover onOpenChange={setPickerOpen} open={pickerOpen}>
|
|
394
|
+
<ContextMenu>
|
|
395
|
+
<TooltipProvider delayDuration={0}>
|
|
396
|
+
<Tooltip>
|
|
397
|
+
<PopoverAnchor asChild>
|
|
398
|
+
<ContextMenuTrigger asChild>
|
|
399
|
+
<TooltipTrigger asChild>
|
|
400
|
+
<button
|
|
401
|
+
className={cn(
|
|
402
|
+
'grid size-5 shrink-0 cursor-grab touch-none select-none place-items-center rounded-[3px] text-[0.5625rem] font-semibold uppercase leading-none transition-opacity hover:opacity-100',
|
|
403
|
+
active ? 'opacity-100' : 'opacity-55',
|
|
404
|
+
isDragging && 'z-10 cursor-grabbing opacity-100'
|
|
405
|
+
)}
|
|
406
|
+
ref={setNodeRef}
|
|
407
|
+
style={{
|
|
408
|
+
backgroundColor: profileColorSoft(hue, active ? 30 : 22),
|
|
409
|
+
boxShadow: [ring, lift].filter(Boolean).join(', ') || undefined,
|
|
410
|
+
color: color ?? undefined,
|
|
411
|
+
// Glide the dragged square between snapped cells with a little
|
|
412
|
+
// overshoot (no scale — the overflow-x strip would clip it).
|
|
413
|
+
transform: base,
|
|
414
|
+
transition: isDragging ? DRAG_TRANSITION : transition
|
|
415
|
+
}}
|
|
416
|
+
type="button"
|
|
417
|
+
{...attributes}
|
|
418
|
+
{...listeners}
|
|
419
|
+
aria-label={label}
|
|
420
|
+
aria-pressed={active}
|
|
421
|
+
// Hold-to-recolor rides alongside the dnd pointer listener (call
|
|
422
|
+
// it first so drag tracking still arms), then a timer opens the
|
|
423
|
+
// picker and flags the trailing click so it doesn't also select.
|
|
424
|
+
onClick={() => {
|
|
425
|
+
if (suppressClick.current) {
|
|
426
|
+
suppressClick.current = false
|
|
427
|
+
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
onSelect()
|
|
432
|
+
}}
|
|
433
|
+
onPointerCancel={clearPress}
|
|
434
|
+
onPointerDown={event => {
|
|
435
|
+
listeners?.onPointerDown?.(event)
|
|
436
|
+
|
|
437
|
+
if (event.button !== 0) {
|
|
438
|
+
return
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
suppressClick.current = false
|
|
442
|
+
clearPress()
|
|
443
|
+
pressTimer.current = window.setTimeout(() => {
|
|
444
|
+
suppressClick.current = true
|
|
445
|
+
triggerHaptic('success')
|
|
446
|
+
setPickerOpen(true)
|
|
447
|
+
}, LONG_PRESS_MS)
|
|
448
|
+
}}
|
|
449
|
+
onPointerLeave={clearPress}
|
|
450
|
+
onPointerUp={clearPress}
|
|
451
|
+
>
|
|
452
|
+
{label.replace(/[^a-z0-9]/gi, '').charAt(0) || '?'}
|
|
453
|
+
</button>
|
|
454
|
+
</TooltipTrigger>
|
|
455
|
+
</ContextMenuTrigger>
|
|
456
|
+
</PopoverAnchor>
|
|
457
|
+
<TooltipContent>{label}</TooltipContent>
|
|
458
|
+
</Tooltip>
|
|
459
|
+
</TooltipProvider>
|
|
460
|
+
|
|
461
|
+
{/* The rail sits at the very bottom, so pad off the chrome (esp. the
|
|
462
|
+
statusbar) — Radix then flips the menu up instead of squishing it. */}
|
|
463
|
+
<ContextMenuContent
|
|
464
|
+
aria-label={p.actionsFor(label)}
|
|
465
|
+
className="w-40"
|
|
466
|
+
collisionPadding={{ bottom: 44, left: 8, right: 8, top: 8 }}
|
|
467
|
+
>
|
|
468
|
+
<ContextMenuItem onSelect={() => setPickerOpen(true)}>
|
|
469
|
+
<Codicon name="symbol-color" size="0.875rem" />
|
|
470
|
+
<span>{p.color}</span>
|
|
471
|
+
</ContextMenuItem>
|
|
472
|
+
<ContextMenuItem onSelect={onRename}>
|
|
473
|
+
<Codicon name="edit" size="0.875rem" />
|
|
474
|
+
<span>{p.rename}</span>
|
|
475
|
+
</ContextMenuItem>
|
|
476
|
+
<ContextMenuItem className="text-destructive focus:text-destructive" onSelect={onDelete} variant="destructive">
|
|
477
|
+
<Codicon name="trash" size="0.875rem" />
|
|
478
|
+
<span>{t.common.delete}</span>
|
|
479
|
+
</ContextMenuItem>
|
|
480
|
+
</ContextMenuContent>
|
|
481
|
+
</ContextMenu>
|
|
482
|
+
|
|
483
|
+
<PopoverContent
|
|
484
|
+
aria-label={p.colorFor(label)}
|
|
485
|
+
className="w-auto p-2"
|
|
486
|
+
collisionPadding={{ bottom: 44, left: 8, right: 8, top: 8 }}
|
|
487
|
+
side="top"
|
|
488
|
+
>
|
|
489
|
+
<div className="grid grid-cols-6 gap-1.5">
|
|
490
|
+
{PROFILE_SWATCHES.map(swatch => (
|
|
491
|
+
<button
|
|
492
|
+
aria-label={p.setColor(swatch)}
|
|
493
|
+
className="size-5 rounded-full transition-transform hover:scale-110"
|
|
494
|
+
key={swatch}
|
|
495
|
+
onClick={() => pickColor(swatch)}
|
|
496
|
+
style={{
|
|
497
|
+
backgroundColor: swatch,
|
|
498
|
+
boxShadow: swatch === color ? '0 0 0 2px var(--ui-bg-elevated), 0 0 0 3.5px currentColor' : undefined,
|
|
499
|
+
color: swatch
|
|
500
|
+
}}
|
|
501
|
+
type="button"
|
|
502
|
+
/>
|
|
503
|
+
))}
|
|
504
|
+
</div>
|
|
505
|
+
<button
|
|
506
|
+
className="mt-2 flex w-full items-center justify-center gap-1.5 rounded-md py-1 text-xs text-(--ui-text-tertiary) transition hover:bg-(--ui-control-hover-background) hover:text-foreground"
|
|
507
|
+
onClick={() => pickColor(null)}
|
|
508
|
+
type="button"
|
|
509
|
+
>
|
|
510
|
+
<Codicon name="sync" size="0.75rem" />
|
|
511
|
+
{p.autoColor}
|
|
512
|
+
</button>
|
|
513
|
+
</PopoverContent>
|
|
514
|
+
</Popover>
|
|
515
|
+
)
|
|
516
|
+
}
|