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,91 @@
|
|
|
1
|
+
import type { NasTechConnection } from '@/global'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The desktop main process exposes `getGatewayWsUrl()` to re-mint a WebSocket
|
|
5
|
+
* URL immediately before every `gateway.connect()`. For OAuth-gated remote
|
|
6
|
+
* gateways the WS ticket is single-use with a ~30s TTL, so the ticket baked
|
|
7
|
+
* into the cached `conn.wsUrl` is stale (and, after the first connect, already
|
|
8
|
+
* consumed). For local/token gateways the URL carries a long-lived token and
|
|
9
|
+
* never needs re-minting.
|
|
10
|
+
*
|
|
11
|
+
* Resolution rules:
|
|
12
|
+
*
|
|
13
|
+
* - OAuth: the fresh mint is the *only* viable URL. If it fails, do NOT fall
|
|
14
|
+
* back to `conn.wsUrl` — that ticket is dead and the connect is guaranteed to
|
|
15
|
+
* fail with an opaque "connection closed" error. Instead, let the mint error
|
|
16
|
+
* propagate so the caller can surface the gateway's reauth message
|
|
17
|
+
* ("session has expired… Sign in again").
|
|
18
|
+
*
|
|
19
|
+
* - token / local, or when the preload method is genuinely absent (older
|
|
20
|
+
* preload shapes): fall back to `conn.wsUrl`. The token URL is long-lived, so
|
|
21
|
+
* the fallback is safe and preserves compatibility.
|
|
22
|
+
*
|
|
23
|
+
* The error thrown for OAuth mint failures is tagged with `needsOauthLogin` so
|
|
24
|
+
* callers can distinguish "the user must re-authenticate" from a generic
|
|
25
|
+
* transport failure.
|
|
26
|
+
*/
|
|
27
|
+
export interface ResolveGatewayWsUrlDeps {
|
|
28
|
+
/** `window.NASTECHDesktop.getGatewayWsUrl`, if the preload exposes it. The
|
|
29
|
+
* optional profile selects which backend to mint for — critical when swapping
|
|
30
|
+
* to a pooled profile, since the default mint resolves the primary backend. */
|
|
31
|
+
getGatewayWsUrl?: (profile?: null | string) => Promise<string>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class GatewayReauthRequiredError extends Error {
|
|
35
|
+
readonly needsOauthLogin = true
|
|
36
|
+
|
|
37
|
+
constructor(message: string, options?: { cause?: unknown }) {
|
|
38
|
+
super(message, options)
|
|
39
|
+
this.name = 'GatewayReauthRequiredError'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isGatewayReauthRequired(error: unknown): error is GatewayReauthRequiredError {
|
|
44
|
+
return (
|
|
45
|
+
error instanceof GatewayReauthRequiredError ||
|
|
46
|
+
(typeof error === 'object' && error !== null && (error as { needsOauthLogin?: unknown }).needsOauthLogin === true)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function resolveGatewayWsUrl(
|
|
51
|
+
desktop: ResolveGatewayWsUrlDeps,
|
|
52
|
+
conn: Pick<NasTechConnection, 'authMode' | 'profile' | 'wsUrl'>
|
|
53
|
+
): Promise<string> {
|
|
54
|
+
const mint = desktop.getGatewayWsUrl
|
|
55
|
+
// Mint for THIS connection's profile, not the primary. Without it a pooled
|
|
56
|
+
// profile swap re-mints the default backend's URL and connects to the wrong
|
|
57
|
+
// backend.
|
|
58
|
+
const profile = conn.profile ?? null
|
|
59
|
+
|
|
60
|
+
if (conn.authMode === 'oauth') {
|
|
61
|
+
if (!mint) {
|
|
62
|
+
// OAuth gateway but no way to mint a fresh ticket: the cached ticket is
|
|
63
|
+
// dead, so connecting with it cannot succeed. Surface a reauth error
|
|
64
|
+
// rather than silently attempting a doomed connect.
|
|
65
|
+
throw new GatewayReauthRequiredError(
|
|
66
|
+
'Your remote gateway session needs to be refreshed. Open Settings → Gateway and click "Sign in" again.'
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
return await mint(profile)
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new GatewayReauthRequiredError(
|
|
74
|
+
'Your remote gateway session has expired. Open Settings → Gateway and click "Sign in" again.',
|
|
75
|
+
{ cause: error }
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// token / local: the URL carries a long-lived token. Re-mint when available
|
|
81
|
+
// (cheap, keeps parity), but the cached URL is a safe fallback.
|
|
82
|
+
if (mint) {
|
|
83
|
+
const fresh = await mint(profile).catch(() => null)
|
|
84
|
+
|
|
85
|
+
if (fresh) {
|
|
86
|
+
return fresh
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return conn.wsUrl
|
|
91
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
dedupeGeneratedImageEchoesInParts,
|
|
5
|
+
generatedImageEchoSources,
|
|
6
|
+
generatedImageFromResult,
|
|
7
|
+
stripGeneratedImageEchoes
|
|
8
|
+
} from './generated-images'
|
|
9
|
+
|
|
10
|
+
describe('generatedImageFromResult', () => {
|
|
11
|
+
it('prefers the host-visible image path', () => {
|
|
12
|
+
expect(
|
|
13
|
+
generatedImageFromResult({
|
|
14
|
+
agent_visible_image: '/container/cache/cat.png',
|
|
15
|
+
host_image: '/Users/me/.nastech/cache/images/cat.png',
|
|
16
|
+
image: '/Users/me/.nastech/cache/images/cat.png',
|
|
17
|
+
success: true
|
|
18
|
+
})
|
|
19
|
+
).toBe('/Users/me/.nastech/cache/images/cat.png')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('ignores failed image generation results', () => {
|
|
23
|
+
expect(generatedImageFromResult({ image: 'https://cdn.example/cat.png', success: false })).toBeNull()
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('stripGeneratedImageEchoes', () => {
|
|
28
|
+
it('removes repeated generated image markdown without removing prose', () => {
|
|
29
|
+
expect(
|
|
30
|
+
stripGeneratedImageEchoes('Here you go.\n\n', [
|
|
31
|
+
'https://cdn.example/cat.png'
|
|
32
|
+
])
|
|
33
|
+
).toBe('Here you go.')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('removes media links for generated local image paths', () => {
|
|
37
|
+
expect(
|
|
38
|
+
stripGeneratedImageEchoes('Saved image: [Image: cat.png](#media:%2Ftmp%2Fcat.png)', ['/tmp/cat.png'])
|
|
39
|
+
).toBe('Saved image:')
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('generatedImageEchoSources', () => {
|
|
44
|
+
it('collects every path variant the model might restate', () => {
|
|
45
|
+
expect(
|
|
46
|
+
generatedImageEchoSources([
|
|
47
|
+
{
|
|
48
|
+
result: { agent_visible_image: '/sandbox/cat.png', host_image: '/host/cat.png', image: '/host/cat.png', success: true },
|
|
49
|
+
toolName: 'image_generate',
|
|
50
|
+
type: 'tool-call'
|
|
51
|
+
}
|
|
52
|
+
])
|
|
53
|
+
).toEqual(['/host/cat.png', '/sandbox/cat.png'])
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('dedupeGeneratedImageEchoesInParts', () => {
|
|
58
|
+
it('keeps the agent prose while removing the duplicated image', () => {
|
|
59
|
+
expect(
|
|
60
|
+
dedupeGeneratedImageEchoesInParts([
|
|
61
|
+
{ text: 'Here is your peacock!  Enjoy.', type: 'text' },
|
|
62
|
+
{ result: { host_image: '/host/p.png', image: '/host/p.png', success: true }, toolName: 'image_generate', type: 'tool-call' }
|
|
63
|
+
])
|
|
64
|
+
).toEqual([
|
|
65
|
+
{ text: 'Here is your peacock! Enjoy.', type: 'text' },
|
|
66
|
+
{ result: { host_image: '/host/p.png', image: '/host/p.png', success: true }, toolName: 'image_generate', type: 'tool-call' }
|
|
67
|
+
])
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('strips a sandbox path the model restated instead of the host path', () => {
|
|
71
|
+
expect(
|
|
72
|
+
dedupeGeneratedImageEchoesInParts([
|
|
73
|
+
{ text: '', type: 'text' },
|
|
74
|
+
{
|
|
75
|
+
result: { agent_visible_image: '/sandbox/cat.png', host_image: '/host/cat.png', image: '/host/cat.png', success: true },
|
|
76
|
+
toolName: 'image_generate',
|
|
77
|
+
type: 'tool-call'
|
|
78
|
+
}
|
|
79
|
+
])
|
|
80
|
+
).toEqual([
|
|
81
|
+
{
|
|
82
|
+
result: { agent_visible_image: '/sandbox/cat.png', host_image: '/host/cat.png', image: '/host/cat.png', success: true },
|
|
83
|
+
toolName: 'image_generate',
|
|
84
|
+
type: 'tool-call'
|
|
85
|
+
}
|
|
86
|
+
])
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('leaves pending generations untouched so the agent prose survives', () => {
|
|
90
|
+
const parts = [
|
|
91
|
+
{ text: 'Another peacock, coming up!', type: 'text' },
|
|
92
|
+
{ result: undefined, toolName: 'image_generate', type: 'tool-call' }
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
expect(dedupeGeneratedImageEchoesInParts(parts)).toEqual(parts)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
type ToolLike = {
|
|
2
|
+
result?: unknown
|
|
3
|
+
toolName?: unknown
|
|
4
|
+
type?: unknown
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
type TextLike = {
|
|
8
|
+
text?: unknown
|
|
9
|
+
type?: unknown
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Path-ish result fields the model may echo into its prose. Display prefers the
|
|
13
|
+
// host path (gateway-deliverable); stripping must catch every variant so a
|
|
14
|
+
// sandbox path the model restated doesn't slip through as a duplicate image.
|
|
15
|
+
const DISPLAY_KEYS = ['host_image', 'image'] as const
|
|
16
|
+
const ECHO_KEYS = ['host_image', 'image', 'agent_visible_image'] as const
|
|
17
|
+
|
|
18
|
+
function recordFromUnknown(value: unknown): Record<string, unknown> | null {
|
|
19
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
20
|
+
return value as Record<string, unknown>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(value)
|
|
29
|
+
|
|
30
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : null
|
|
31
|
+
} catch {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function stringFields(record: Record<string, unknown>, keys: readonly string[]): string[] {
|
|
37
|
+
return keys.map(key => record[key]).filter((v): v is string => typeof v === 'string' && v.trim().length > 0)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function regexEscape(value: string): string {
|
|
41
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function unique(values: string[]): string[] {
|
|
45
|
+
return [...new Set(values.filter(Boolean))]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function imageResult(part: ToolLike): Record<string, unknown> | null {
|
|
49
|
+
if (part.type !== 'tool-call' || part.toolName !== 'image_generate') {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const record = recordFromUnknown(part.result)
|
|
54
|
+
|
|
55
|
+
return record && record.success !== false ? record : null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Display source for a completed `image_generate` result (host path wins). */
|
|
59
|
+
export function generatedImageFromResult(result: unknown): string | null {
|
|
60
|
+
const record = recordFromUnknown(result)
|
|
61
|
+
|
|
62
|
+
if (!record || record.success === false) {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return stringFields(record, DISPLAY_KEYS)[0] ?? null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Every path/URL a generated image might appear as in prose, for de-duping. */
|
|
70
|
+
export function generatedImageEchoSources(parts: readonly ToolLike[]): string[] {
|
|
71
|
+
return unique(parts.flatMap(part => stringFields(imageResult(part) ?? {}, ECHO_KEYS)))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Strip a generated image out of prose so it only ever shows in the tool slot.
|
|
75
|
+
* Once a generation succeeded (`sources` is non-empty) we drop every embedded
|
|
76
|
+
* image and media link from that message — the model frequently restates the
|
|
77
|
+
* remote URL while the result holds the local path, so matching the exact
|
|
78
|
+
* source is not enough. Bare occurrences of the known paths/URLs are removed
|
|
79
|
+
* too. Surrounding prose is preserved. */
|
|
80
|
+
export function stripGeneratedImageEchoes(text: string, sources: readonly string[]): string {
|
|
81
|
+
if (!text || sources.length === 0) {
|
|
82
|
+
return text
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let next = text
|
|
86
|
+
.replace(/!\[[^\]\n]*\]\([^)\n]*\)/g, '')
|
|
87
|
+
.replace(/\[[^\]\n]*\]\(\s*#media:[^)\n]*\)/g, '')
|
|
88
|
+
|
|
89
|
+
for (const source of unique([...sources])) {
|
|
90
|
+
next = next.replace(new RegExp(String.raw`(^|[\s([{])<?${regexEscape(source)}>?(?=$|[\s)\]},.!?])`, 'g'), '$1')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return next
|
|
94
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
95
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
96
|
+
.replace(/[ \t]{2,}/g, ' ')
|
|
97
|
+
.trim()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Strip generated-image echoes from text parts, dropping any part left empty.
|
|
101
|
+
* The image lives in the tool slot; prose keeps the agent's actual words. */
|
|
102
|
+
export function dedupeGeneratedImageEchoesInParts<T extends TextLike & ToolLike>(parts: readonly T[]): T[] {
|
|
103
|
+
const sources = generatedImageEchoSources(parts)
|
|
104
|
+
|
|
105
|
+
if (!sources.length) {
|
|
106
|
+
return [...parts]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return parts
|
|
110
|
+
.map(part =>
|
|
111
|
+
part.type === 'text' && typeof part.text === 'string'
|
|
112
|
+
? { ...part, text: stripGeneratedImageEchoes(part.text, sources) }
|
|
113
|
+
: part
|
|
114
|
+
)
|
|
115
|
+
.filter(part => part.type !== 'text' || (typeof part.text === 'string' && part.text.trim().length > 0))
|
|
116
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { HapticInput, TriggerOptions } from 'web-haptics'
|
|
2
|
+
|
|
3
|
+
import { $hapticsMuted } from '@/store/haptics'
|
|
4
|
+
|
|
5
|
+
export type HapticIntent =
|
|
6
|
+
| 'cancel'
|
|
7
|
+
| 'close'
|
|
8
|
+
| 'crisp'
|
|
9
|
+
| 'error'
|
|
10
|
+
| 'open'
|
|
11
|
+
| 'selection'
|
|
12
|
+
| 'streamDone'
|
|
13
|
+
| 'streamStart'
|
|
14
|
+
| 'submit'
|
|
15
|
+
| 'success'
|
|
16
|
+
| 'tap'
|
|
17
|
+
| 'warning'
|
|
18
|
+
|
|
19
|
+
interface HapticConfig {
|
|
20
|
+
options?: TriggerOptions
|
|
21
|
+
pattern: HapticInput
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const airyTap = [{ duration: 16, intensity: 0.52 }]
|
|
25
|
+
|
|
26
|
+
const crispTap = [{ duration: 10, intensity: 0.92 }]
|
|
27
|
+
|
|
28
|
+
const friendlySuccess = [
|
|
29
|
+
{ duration: 28, intensity: 0.5 },
|
|
30
|
+
{ delay: 42, duration: 30, intensity: 0.68 },
|
|
31
|
+
{ delay: 48, duration: 38, intensity: 0.86 }
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
const softArrive = [
|
|
35
|
+
{ duration: 18, intensity: 0.42 },
|
|
36
|
+
{ delay: 36, duration: 22, intensity: 0.66 }
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
const softLeave = [
|
|
40
|
+
{ duration: 22, intensity: 0.58 },
|
|
41
|
+
{ delay: 32, duration: 16, intensity: 0.34 }
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
const HAPTIC_INTENTS: Record<HapticIntent, HapticConfig> = {
|
|
45
|
+
cancel: {
|
|
46
|
+
pattern: [
|
|
47
|
+
{ duration: 34, intensity: 0.72 },
|
|
48
|
+
{ delay: 54, duration: 26, intensity: 0.38 }
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
close: { pattern: softLeave },
|
|
52
|
+
crisp: { pattern: crispTap },
|
|
53
|
+
error: {
|
|
54
|
+
pattern: [
|
|
55
|
+
{ duration: 34, intensity: 0.82 },
|
|
56
|
+
{ delay: 42, duration: 34, intensity: 0.72 },
|
|
57
|
+
{ delay: 58, duration: 44, intensity: 0.86 }
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
open: { pattern: softArrive },
|
|
61
|
+
selection: { pattern: airyTap },
|
|
62
|
+
streamDone: { pattern: friendlySuccess },
|
|
63
|
+
streamStart: { pattern: [{ duration: 10, intensity: 0.32 }] },
|
|
64
|
+
submit: {
|
|
65
|
+
pattern: [
|
|
66
|
+
{ duration: 24, intensity: 0.58 },
|
|
67
|
+
{ delay: 48, duration: 36, intensity: 0.82 }
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
success: { pattern: friendlySuccess },
|
|
71
|
+
tap: {
|
|
72
|
+
pattern: [
|
|
73
|
+
{ duration: 14, intensity: 0.58 },
|
|
74
|
+
{ delay: 30, duration: 12, intensity: 0.42 }
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
warning: {
|
|
78
|
+
pattern: [
|
|
79
|
+
{ duration: 34, intensity: 0.64 },
|
|
80
|
+
{ delay: 84, duration: 42, intensity: 0.5 }
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type HapticTrigger = (input?: HapticInput, options?: TriggerOptions) => Promise<void> | undefined
|
|
86
|
+
|
|
87
|
+
let registeredTrigger: HapticTrigger | null = null
|
|
88
|
+
let lastSelectionAt = 0
|
|
89
|
+
|
|
90
|
+
// Global rolling rate-limit. A runaway upstream loop (auth-expiry error-toast
|
|
91
|
+
// storms, reconnect flaps) can request dozens of haptics a second, which the
|
|
92
|
+
// trackpad actuator renders as a frantic "clickity" buzz. Cap firings to
|
|
93
|
+
// RATE_LIMIT per RATE_WINDOW so no source can machine-gun the actuator;
|
|
94
|
+
// intentional UI haptics are human-paced and never approach the ceiling.
|
|
95
|
+
const RATE_WINDOW = 1000
|
|
96
|
+
const RATE_LIMIT = 5
|
|
97
|
+
let recentFires: number[] = []
|
|
98
|
+
|
|
99
|
+
export function registerHapticTrigger(trigger: HapticTrigger | null) {
|
|
100
|
+
registeredTrigger = trigger
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function triggerHaptic(intent: HapticIntent = 'selection') {
|
|
104
|
+
if ($hapticsMuted.get() || !registeredTrigger) {
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const now = performance.now()
|
|
109
|
+
|
|
110
|
+
if (intent === 'selection') {
|
|
111
|
+
if (now - lastSelectionAt < 50) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
lastSelectionAt = now
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
recentFires = recentFires.filter(t => now - t < RATE_WINDOW)
|
|
119
|
+
|
|
120
|
+
if (recentFires.length >= RATE_LIMIT) {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
recentFires.push(now)
|
|
125
|
+
|
|
126
|
+
const config = HAPTIC_INTENTS[intent]
|
|
127
|
+
|
|
128
|
+
void registeredTrigger(config.pattern, config.options)?.catch(() => undefined)
|
|
129
|
+
}
|
package/src/lib/icons.ts
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IconActivity as Activity,
|
|
3
|
+
IconAlertCircle as AlertCircle,
|
|
4
|
+
IconAlertTriangle as AlertTriangle,
|
|
5
|
+
IconArchive as Archive,
|
|
6
|
+
IconArchiveOff as ArchiveOff,
|
|
7
|
+
IconArrowUp as ArrowUp,
|
|
8
|
+
IconArrowUpRight as ArrowUpRight,
|
|
9
|
+
IconAt as AtSign,
|
|
10
|
+
IconWaveSine as AudioLines,
|
|
11
|
+
IconChartBar as BarChart3,
|
|
12
|
+
IconBrain as Brain,
|
|
13
|
+
IconBug as Bug,
|
|
14
|
+
IconCheck as Check,
|
|
15
|
+
IconCircleCheck as CheckCircle2,
|
|
16
|
+
IconCheck as CheckIcon,
|
|
17
|
+
IconChevronDown as ChevronDown,
|
|
18
|
+
IconChevronDown as ChevronDownIcon,
|
|
19
|
+
IconChevronLeft as ChevronLeft,
|
|
20
|
+
IconChevronLeft as ChevronLeftIcon,
|
|
21
|
+
IconChevronRight as ChevronRight,
|
|
22
|
+
IconChevronRight as ChevronRightIcon,
|
|
23
|
+
IconCircle as CircleIcon,
|
|
24
|
+
IconClipboard as Clipboard,
|
|
25
|
+
IconClock as Clock,
|
|
26
|
+
IconCommand as Command,
|
|
27
|
+
IconCopy as Copy,
|
|
28
|
+
IconCopy as CopyIcon,
|
|
29
|
+
IconCpu as Cpu,
|
|
30
|
+
IconDownload as Download,
|
|
31
|
+
IconExternalLink as ExternalLink,
|
|
32
|
+
IconEye as Eye,
|
|
33
|
+
IconEyeOff as EyeOff,
|
|
34
|
+
IconPhoto as FileImage,
|
|
35
|
+
IconFileText as FileText,
|
|
36
|
+
IconFolderOpen as FolderOpen,
|
|
37
|
+
IconGitBranch as GitBranch,
|
|
38
|
+
IconGitBranch as GitBranchIcon,
|
|
39
|
+
IconGlobe as Globe,
|
|
40
|
+
IconHash as Hash,
|
|
41
|
+
IconHelpCircle as HelpCircle,
|
|
42
|
+
IconPhoto as ImageIcon,
|
|
43
|
+
IconInfoCircle as Info,
|
|
44
|
+
IconKey as KeyRound,
|
|
45
|
+
IconLayersIntersect2 as Layers3,
|
|
46
|
+
IconLink as Link,
|
|
47
|
+
IconLink as Link2,
|
|
48
|
+
IconLink as LinkIcon,
|
|
49
|
+
IconLoader2 as Loader2,
|
|
50
|
+
IconLoader2 as Loader2Icon,
|
|
51
|
+
IconLock as Lock,
|
|
52
|
+
IconLogin as LogIn,
|
|
53
|
+
IconMessageCircle as MessageCircle,
|
|
54
|
+
IconMessage2 as MessageSquareText,
|
|
55
|
+
IconMicrophone as Mic,
|
|
56
|
+
IconMicrophoneOff as MicOff,
|
|
57
|
+
IconDeviceDesktop as Monitor,
|
|
58
|
+
IconDeviceDesktopAnalytics as MonitorPlay,
|
|
59
|
+
IconMoon as Moon,
|
|
60
|
+
IconDots as MoreHorizontal,
|
|
61
|
+
IconDots as MoreHorizontalIcon,
|
|
62
|
+
IconDotsVertical as MoreVertical,
|
|
63
|
+
IconNotebook as NotebookTabs,
|
|
64
|
+
IconPackage as Package,
|
|
65
|
+
IconPalette as Palette,
|
|
66
|
+
IconLayoutBottombar as PanelBottom,
|
|
67
|
+
IconLayoutSidebar as PanelLeftIcon,
|
|
68
|
+
IconPlayerPause as Pause,
|
|
69
|
+
IconPencil as Pencil,
|
|
70
|
+
IconPencil as PencilIcon,
|
|
71
|
+
IconPencil as PencilLine,
|
|
72
|
+
IconPin as Pin,
|
|
73
|
+
IconPlayerPlay as Play,
|
|
74
|
+
IconPlus as Plus,
|
|
75
|
+
IconRefresh as RefreshCw,
|
|
76
|
+
IconRefresh as RefreshCwIcon,
|
|
77
|
+
IconDeviceFloppy as Save,
|
|
78
|
+
IconSearch as Search,
|
|
79
|
+
IconSearch as SearchIcon,
|
|
80
|
+
IconSend as Send,
|
|
81
|
+
IconSettings as Settings,
|
|
82
|
+
IconSettings2 as Settings2,
|
|
83
|
+
IconAdjustmentsHorizontal as SlidersHorizontal,
|
|
84
|
+
IconSparkles as Sparkles,
|
|
85
|
+
IconSquare as Square,
|
|
86
|
+
IconSteeringWheel as SteeringWheel,
|
|
87
|
+
IconSun as Sun,
|
|
88
|
+
IconTerminal2 as Terminal,
|
|
89
|
+
IconTrash as Trash2,
|
|
90
|
+
IconUsers as Users,
|
|
91
|
+
IconVolume2 as Volume2,
|
|
92
|
+
IconVolume2 as Volume2Icon,
|
|
93
|
+
IconVolumeOff as VolumeX,
|
|
94
|
+
IconVolumeOff as VolumeXIcon,
|
|
95
|
+
IconTool as Wrench,
|
|
96
|
+
IconX as X,
|
|
97
|
+
IconX as XIcon,
|
|
98
|
+
IconBolt as Zap,
|
|
99
|
+
IconBoltFilled as ZapFilled
|
|
100
|
+
} from '@tabler/icons-react'
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
Activity,
|
|
104
|
+
AlertCircle,
|
|
105
|
+
AlertTriangle,
|
|
106
|
+
Archive,
|
|
107
|
+
ArchiveOff,
|
|
108
|
+
ArrowUp,
|
|
109
|
+
ArrowUpRight,
|
|
110
|
+
AtSign,
|
|
111
|
+
AudioLines,
|
|
112
|
+
BarChart3,
|
|
113
|
+
Brain,
|
|
114
|
+
Bug,
|
|
115
|
+
Check,
|
|
116
|
+
CheckCircle2,
|
|
117
|
+
CheckIcon,
|
|
118
|
+
ChevronDown,
|
|
119
|
+
ChevronDownIcon,
|
|
120
|
+
ChevronLeft,
|
|
121
|
+
ChevronLeftIcon,
|
|
122
|
+
ChevronRight,
|
|
123
|
+
ChevronRightIcon,
|
|
124
|
+
CircleIcon,
|
|
125
|
+
Clipboard,
|
|
126
|
+
Clock,
|
|
127
|
+
Command,
|
|
128
|
+
Copy,
|
|
129
|
+
CopyIcon,
|
|
130
|
+
Cpu,
|
|
131
|
+
Download,
|
|
132
|
+
ExternalLink,
|
|
133
|
+
Eye,
|
|
134
|
+
EyeOff,
|
|
135
|
+
FileImage,
|
|
136
|
+
FileText,
|
|
137
|
+
FolderOpen,
|
|
138
|
+
GitBranch,
|
|
139
|
+
GitBranchIcon,
|
|
140
|
+
Globe,
|
|
141
|
+
Hash,
|
|
142
|
+
HelpCircle,
|
|
143
|
+
ImageIcon,
|
|
144
|
+
Info,
|
|
145
|
+
KeyRound,
|
|
146
|
+
Layers3,
|
|
147
|
+
Link,
|
|
148
|
+
Link2,
|
|
149
|
+
LinkIcon,
|
|
150
|
+
Loader2,
|
|
151
|
+
Loader2Icon,
|
|
152
|
+
Lock,
|
|
153
|
+
LogIn,
|
|
154
|
+
MessageCircle,
|
|
155
|
+
MessageSquareText,
|
|
156
|
+
Mic,
|
|
157
|
+
MicOff,
|
|
158
|
+
Monitor,
|
|
159
|
+
MonitorPlay,
|
|
160
|
+
Moon,
|
|
161
|
+
MoreHorizontal,
|
|
162
|
+
MoreHorizontalIcon,
|
|
163
|
+
MoreVertical,
|
|
164
|
+
NotebookTabs,
|
|
165
|
+
Package,
|
|
166
|
+
Palette,
|
|
167
|
+
PanelBottom,
|
|
168
|
+
PanelLeftIcon,
|
|
169
|
+
Pause,
|
|
170
|
+
Pencil,
|
|
171
|
+
PencilIcon,
|
|
172
|
+
PencilLine,
|
|
173
|
+
Pin,
|
|
174
|
+
Play,
|
|
175
|
+
Plus,
|
|
176
|
+
RefreshCw,
|
|
177
|
+
RefreshCwIcon,
|
|
178
|
+
Save,
|
|
179
|
+
Search,
|
|
180
|
+
SearchIcon,
|
|
181
|
+
Send,
|
|
182
|
+
Settings,
|
|
183
|
+
Settings2,
|
|
184
|
+
SlidersHorizontal,
|
|
185
|
+
Sparkles,
|
|
186
|
+
Square,
|
|
187
|
+
SteeringWheel,
|
|
188
|
+
Sun,
|
|
189
|
+
Terminal,
|
|
190
|
+
Trash2,
|
|
191
|
+
Users,
|
|
192
|
+
Volume2,
|
|
193
|
+
Volume2Icon,
|
|
194
|
+
VolumeX,
|
|
195
|
+
VolumeXIcon,
|
|
196
|
+
Wrench,
|
|
197
|
+
X,
|
|
198
|
+
XIcon,
|
|
199
|
+
Zap,
|
|
200
|
+
ZapFilled
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export type { Icon as IconComponent } from '@tabler/icons-react'
|