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,466 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type ToolCallMessagePartProps, useAuiState } from '@assistant-ui/react'
|
|
4
|
+
import { useStore } from '@nanostores/react'
|
|
5
|
+
import { createContext, type FC, type PropsWithChildren, type ReactNode, useContext, useMemo } from 'react'
|
|
6
|
+
|
|
7
|
+
import { AnsiText } from '@/components/assistant-ui/ansi-text'
|
|
8
|
+
import { useElapsedSeconds } from '@/components/chat/activity-timer'
|
|
9
|
+
import { ActivityTimerText } from '@/components/chat/activity-timer-text'
|
|
10
|
+
import { CompactMarkdown } from '@/components/chat/compact-markdown'
|
|
11
|
+
import { DiffLines } from '@/components/chat/diff-lines'
|
|
12
|
+
import { DisclosureRow } from '@/components/chat/disclosure-row'
|
|
13
|
+
import { PreviewAttachment } from '@/components/chat/preview-attachment'
|
|
14
|
+
import { ZoomableImage } from '@/components/chat/zoomable-image'
|
|
15
|
+
import { BrailleSpinner } from '@/components/ui/braille-spinner'
|
|
16
|
+
import { Codicon } from '@/components/ui/codicon'
|
|
17
|
+
import { CopyButton } from '@/components/ui/copy-button'
|
|
18
|
+
import { FadeText } from '@/components/ui/fade-text'
|
|
19
|
+
import { useI18n } from '@/i18n'
|
|
20
|
+
import { PrettyLink, LinkifiedText as SharedLinkifiedText, urlSlugTitleLabel } from '@/lib/external-link'
|
|
21
|
+
import { AlertCircle, CheckCircle2 } from '@/lib/icons'
|
|
22
|
+
import { useEnterAnimation } from '@/lib/use-enter-animation'
|
|
23
|
+
import { cn } from '@/lib/utils'
|
|
24
|
+
import { $toolInlineDiffs } from '@/store/tool-diffs'
|
|
25
|
+
import { $toolDisclosureOpen, $toolViewMode, setToolDisclosureOpen } from '@/store/tool-view'
|
|
26
|
+
|
|
27
|
+
import { PendingToolApproval } from './tool-approval'
|
|
28
|
+
import {
|
|
29
|
+
buildToolView,
|
|
30
|
+
cleanVisibleText,
|
|
31
|
+
inlineDiffFromResult,
|
|
32
|
+
isPreviewableTarget,
|
|
33
|
+
looksRedundant,
|
|
34
|
+
type SearchResultRow,
|
|
35
|
+
selectMessageRunning,
|
|
36
|
+
stripInlineDiffChrome,
|
|
37
|
+
toolCopyPayload,
|
|
38
|
+
type ToolPart,
|
|
39
|
+
toolPartDisclosureId,
|
|
40
|
+
type ToolStatus
|
|
41
|
+
} from './tool-fallback-model'
|
|
42
|
+
|
|
43
|
+
// `true` when a ToolEntry is rendered inside an embedding wrapper that owns
|
|
44
|
+
// the per-row chrome (timer / preview). The flat ToolGroupSlot sets this
|
|
45
|
+
// false, so every row currently owns its own chrome; kept as a seam for any
|
|
46
|
+
// future embedding surface.
|
|
47
|
+
const ToolEmbedContext = createContext(false)
|
|
48
|
+
|
|
49
|
+
// Shared header chrome for tool rows. Both the single-tool DisclosureRow
|
|
50
|
+
// and the multi-tool group header pass through these constants so a
|
|
51
|
+
// "Patch" row and a "Tool actions · 2 steps" row are visually identical.
|
|
52
|
+
const TOOL_HEADER_TITLE_CLASS =
|
|
53
|
+
'text-[length:var(--conversation-tool-font-size)] font-medium leading-(--conversation-line-height) text-(--ui-text-secondary)'
|
|
54
|
+
|
|
55
|
+
const TOOL_HEADER_DURATION_CLASS = 'shrink-0 text-[0.625rem] tabular-nums text-(--ui-text-tertiary)'
|
|
56
|
+
|
|
57
|
+
const TOOL_HEADER_SUBTITLE_CLASS =
|
|
58
|
+
'text-[length:var(--conversation-caption-font-size)] leading-(--conversation-caption-line-height) text-(--ui-text-tertiary)'
|
|
59
|
+
|
|
60
|
+
const TOOL_HEADER_GLYPH_WRAP_CLASS = 'grid size-3.5 shrink-0 place-items-center self-center'
|
|
61
|
+
|
|
62
|
+
// Glass-style section label that sits above any pre/JSON/output block.
|
|
63
|
+
// Lowercase tracking + tiny size so it reads as a quiet field label rather
|
|
64
|
+
// than a chrome heading. Used for "COMMAND OUTPUT", "INPUT", "OUTPUT", etc.
|
|
65
|
+
const TOOL_SECTION_LABEL_CLASS = 'mb-1 text-[0.65rem] font-medium uppercase tracking-[0.08em] text-(--ui-text-tertiary)'
|
|
66
|
+
|
|
67
|
+
// Inset scroll surface for any detail body. The expanded tool row owns the
|
|
68
|
+
// border; the payload itself is just clipped raw text.
|
|
69
|
+
const TOOL_SECTION_SURFACE_CLASS =
|
|
70
|
+
'max-h-20 max-w-full overflow-auto bg-transparent px-2 py-1.5 text-(--ui-text-secondary)'
|
|
71
|
+
|
|
72
|
+
const TOOL_SECTION_PRE_CLASS = cn(TOOL_SECTION_SURFACE_CLASS, 'font-mono text-[0.7rem] leading-relaxed')
|
|
73
|
+
|
|
74
|
+
interface ToolStatusCopy {
|
|
75
|
+
statusDone: string
|
|
76
|
+
statusError: string
|
|
77
|
+
statusRecovered: string
|
|
78
|
+
statusRunning: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function rawTechnicalTrace(args: unknown, result: unknown): string {
|
|
82
|
+
const parts = [args, result]
|
|
83
|
+
.filter(value => value !== undefined && value !== null)
|
|
84
|
+
.map(value => {
|
|
85
|
+
if (typeof value === 'string') {
|
|
86
|
+
return value
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
return JSON.stringify(value)
|
|
91
|
+
} catch {
|
|
92
|
+
return String(value)
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
|
|
97
|
+
return parts.join('\n')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function statusGlyph(status: ToolStatus, copy: ToolStatusCopy): ReactNode {
|
|
101
|
+
if (status === 'running') {
|
|
102
|
+
return (
|
|
103
|
+
<BrailleSpinner
|
|
104
|
+
ariaLabel={copy.statusRunning}
|
|
105
|
+
className="size-3.5 shrink-0 text-[0.95rem] text-(--ui-text-tertiary)"
|
|
106
|
+
spinner="breathe"
|
|
107
|
+
/>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (status === 'error') {
|
|
112
|
+
return <AlertCircle aria-label={copy.statusError} className="size-3.5 shrink-0 text-destructive" />
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (status === 'warning') {
|
|
116
|
+
return (
|
|
117
|
+
<AlertCircle
|
|
118
|
+
aria-label={copy.statusRecovered}
|
|
119
|
+
className="size-3.5 shrink-0 text-amber-600 dark:text-amber-400"
|
|
120
|
+
/>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<CheckCircle2
|
|
126
|
+
aria-label={copy.statusDone}
|
|
127
|
+
className="size-3.5 shrink-0 text-emerald-600/85 dark:text-emerald-400/85"
|
|
128
|
+
/>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Leading glyph for any tool-row header. Status (running/error/warning)
|
|
133
|
+
// takes precedence; otherwise falls back to the tool's codicon. Returns
|
|
134
|
+
// null when neither applies so callers can render unconditionally.
|
|
135
|
+
function ToolGlyph({ copy, icon, status }: { copy: ToolStatusCopy; icon?: string; status?: ToolStatus }) {
|
|
136
|
+
const node = status ? (
|
|
137
|
+
statusGlyph(status, copy)
|
|
138
|
+
) : icon ? (
|
|
139
|
+
<Codicon className="text-(--ui-text-tertiary)" name={icon} size="0.875rem" />
|
|
140
|
+
) : null
|
|
141
|
+
|
|
142
|
+
return node ? <span className={TOOL_HEADER_GLYPH_WRAP_CLASS}>{node}</span> : null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Which status (if any) should pre-empt the tool's icon in the leading
|
|
146
|
+
// slot. Success is silent — the row reads as "done" without a checkmark.
|
|
147
|
+
function leadingStatus(isPending: boolean, status: ToolStatus): ToolStatus | undefined {
|
|
148
|
+
if (isPending) {
|
|
149
|
+
return 'running'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return status === 'success' ? undefined : status
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function SearchResultsList({ hits }: { hits: SearchResultRow[] }) {
|
|
156
|
+
return (
|
|
157
|
+
<ol className="m-0 grid list-none gap-2.5 p-0">
|
|
158
|
+
{hits.map((hit, index) => {
|
|
159
|
+
const key = `${hit.url || hit.title}-${index}`
|
|
160
|
+
const trimmedTitle = hit.title.trim()
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<li className="grid min-w-0 gap-0.5" key={key}>
|
|
164
|
+
{hit.url ? (
|
|
165
|
+
<PrettyLink
|
|
166
|
+
className={cn(TOOL_HEADER_TITLE_CLASS, 'block max-w-full')}
|
|
167
|
+
fallbackLabel={trimmedTitle || urlSlugTitleLabel(hit.url)}
|
|
168
|
+
href={hit.url}
|
|
169
|
+
label={trimmedTitle || undefined}
|
|
170
|
+
/>
|
|
171
|
+
) : (
|
|
172
|
+
<span className={TOOL_HEADER_TITLE_CLASS}>{trimmedTitle}</span>
|
|
173
|
+
)}
|
|
174
|
+
{hit.snippet && <p className={cn(TOOL_HEADER_SUBTITLE_CLASS, 'm-0 line-clamp-3')}>{hit.snippet}</p>}
|
|
175
|
+
</li>
|
|
176
|
+
)
|
|
177
|
+
})}
|
|
178
|
+
</ol>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function LinkifiedText({ className, text }: { className?: string; text: string }) {
|
|
183
|
+
return <SharedLinkifiedText className={className} pretty text={cleanVisibleText(text)} />
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
interface ToolEntryProps {
|
|
187
|
+
part: ToolPart
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function useDisclosureOpen(disclosureId: string, fallbackOpen = false): boolean {
|
|
191
|
+
const persistedOpen = useStore($toolDisclosureOpen(disclosureId))
|
|
192
|
+
|
|
193
|
+
return persistedOpen ?? fallbackOpen
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function ToolEntry({ part }: ToolEntryProps) {
|
|
197
|
+
const { t } = useI18n()
|
|
198
|
+
const copy = t.assistant.tool
|
|
199
|
+
const messageId = useAuiState(s => s.message.id)
|
|
200
|
+
const messageRunning = useAuiState(selectMessageRunning)
|
|
201
|
+
const embedded = useContext(ToolEmbedContext)
|
|
202
|
+
const toolViewMode = useStore($toolViewMode)
|
|
203
|
+
const disclosureId = `tool-entry:${messageId}:${toolPartDisclosureId(part)}`
|
|
204
|
+
const open = useDisclosureOpen(disclosureId)
|
|
205
|
+
const isPending = messageRunning && part.result === undefined
|
|
206
|
+
// Only animate entries that mount while their message is actively
|
|
207
|
+
// streaming — historical sessions mount with `messageRunning === false`,
|
|
208
|
+
// so they paint statically without a settle cascade. The wrapping group
|
|
209
|
+
// handles its own enter animation, so embedded children skip it.
|
|
210
|
+
const enterRef = useEnterAnimation(messageRunning && !embedded, `tool-entry:${disclosureId}`)
|
|
211
|
+
const elapsed = useElapsedSeconds(isPending, `tool:${disclosureId}`)
|
|
212
|
+
const liveDiffs = useStore($toolInlineDiffs)
|
|
213
|
+
const sideDiff = part.toolCallId ? liveDiffs[part.toolCallId] || '' : ''
|
|
214
|
+
const inlineDiff = stripInlineDiffChrome(sideDiff) || inlineDiffFromResult(part.result)
|
|
215
|
+
|
|
216
|
+
// Stale parts (no result, but message stopped running) get a synthetic
|
|
217
|
+
// empty result so buildToolView treats them as completed-no-output.
|
|
218
|
+
const view = useMemo(() => {
|
|
219
|
+
const p = !isPending && part.result === undefined ? { ...part, result: {} } : part
|
|
220
|
+
|
|
221
|
+
return buildToolView(p, inlineDiff)
|
|
222
|
+
}, [inlineDiff, isPending, part])
|
|
223
|
+
|
|
224
|
+
const detailSections = useMemo(() => {
|
|
225
|
+
if (!view.detail) {
|
|
226
|
+
return { body: '', summary: '' }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (view.status !== 'error') {
|
|
230
|
+
return { body: view.detail, summary: '' }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const chunks = view.detail
|
|
234
|
+
.split(/\n\s*\n+/)
|
|
235
|
+
.map(chunk => chunk.trim())
|
|
236
|
+
.filter(Boolean)
|
|
237
|
+
|
|
238
|
+
const [summary = '', ...rest] = chunks
|
|
239
|
+
const subtitleNorm = view.subtitle.trim().toLowerCase()
|
|
240
|
+
const summaryDuplicatesSubtitle = summary && summary.toLowerCase() === subtitleNorm
|
|
241
|
+
|
|
242
|
+
if (summaryDuplicatesSubtitle) {
|
|
243
|
+
return { body: rest.join('\n\n').trim(), summary: '' }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return { body: rest.join('\n\n').trim(), summary }
|
|
247
|
+
}, [view.detail, view.status, view.subtitle])
|
|
248
|
+
|
|
249
|
+
const detailMatchesSubtitle = looksRedundant(view.subtitle, view.detail)
|
|
250
|
+
|
|
251
|
+
const showDetail =
|
|
252
|
+
(view.status === 'error' && Boolean(detailSections.summary || detailSections.body)) ||
|
|
253
|
+
(view.status !== 'error' &&
|
|
254
|
+
Boolean(view.detail) &&
|
|
255
|
+
!looksRedundant(view.title, view.detail) &&
|
|
256
|
+
!detailMatchesSubtitle)
|
|
257
|
+
|
|
258
|
+
const renderDetailAsCode =
|
|
259
|
+
view.status !== 'error' &&
|
|
260
|
+
(part.toolName === 'terminal' || part.toolName === 'execute_code' || part.toolName === 'read_file')
|
|
261
|
+
|
|
262
|
+
const hasSearchHits = Boolean(view.searchHits?.length)
|
|
263
|
+
const searchResultsLabel = part.toolName === 'web_search' ? 'Search results' : view.detailLabel
|
|
264
|
+
|
|
265
|
+
const showRawSearchDrilldown =
|
|
266
|
+
part.toolName === 'web_search' &&
|
|
267
|
+
part.result !== undefined &&
|
|
268
|
+
toolViewMode !== 'technical' &&
|
|
269
|
+
Boolean(view.rawResult.trim())
|
|
270
|
+
|
|
271
|
+
const hasExpandableContent = Boolean(
|
|
272
|
+
(view.previewTarget && isPreviewableTarget(view.previewTarget)) ||
|
|
273
|
+
view.imageUrl ||
|
|
274
|
+
view.inlineDiff ||
|
|
275
|
+
showDetail ||
|
|
276
|
+
hasSearchHits ||
|
|
277
|
+
toolViewMode === 'technical'
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
const copyAction = useMemo(() => toolCopyPayload(part, view), [part, view])
|
|
281
|
+
|
|
282
|
+
const trailing =
|
|
283
|
+
isPending && !embedded ? (
|
|
284
|
+
<ActivityTimerText className={TOOL_HEADER_DURATION_CLASS} seconds={elapsed} />
|
|
285
|
+
) : !isPending && copyAction.text ? (
|
|
286
|
+
<CopyButton appearance="tool-row" label={copyAction.label} stopPropagation text={copyAction.text} />
|
|
287
|
+
) : undefined
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<div
|
|
291
|
+
className={cn(
|
|
292
|
+
'min-w-0 max-w-full overflow-hidden text-[length:var(--conversation-tool-font-size)] text-(--ui-text-tertiary)',
|
|
293
|
+
open && 'rounded-[0.625rem] border border-(--ui-stroke-tertiary)'
|
|
294
|
+
)}
|
|
295
|
+
data-slot="tool-block"
|
|
296
|
+
ref={enterRef}
|
|
297
|
+
>
|
|
298
|
+
<div className={cn(open && 'border-b border-(--ui-stroke-tertiary) px-2 py-1.5')}>
|
|
299
|
+
<DisclosureRow
|
|
300
|
+
onToggle={hasExpandableContent ? () => setToolDisclosureOpen(disclosureId, !open) : undefined}
|
|
301
|
+
open={open}
|
|
302
|
+
trailing={trailing}
|
|
303
|
+
>
|
|
304
|
+
<span className="flex min-w-0 items-center gap-1.5">
|
|
305
|
+
<ToolGlyph copy={copy} icon={view.icon} status={leadingStatus(isPending, view.status)} />
|
|
306
|
+
<FadeText
|
|
307
|
+
className={cn(
|
|
308
|
+
TOOL_HEADER_TITLE_CLASS,
|
|
309
|
+
isPending && 'shimmer text-(--ui-text-tertiary)',
|
|
310
|
+
view.status === 'error' && 'text-destructive',
|
|
311
|
+
view.status === 'warning' && 'text-amber-700 dark:text-amber-300'
|
|
312
|
+
)}
|
|
313
|
+
>
|
|
314
|
+
{view.title}
|
|
315
|
+
</FadeText>
|
|
316
|
+
{!isPending && view.countLabel && <span className={TOOL_HEADER_DURATION_CLASS}>{view.countLabel}</span>}
|
|
317
|
+
{!isPending && view.durationLabel && (
|
|
318
|
+
<span className={TOOL_HEADER_DURATION_CLASS}>{view.durationLabel}</span>
|
|
319
|
+
)}
|
|
320
|
+
</span>
|
|
321
|
+
</DisclosureRow>
|
|
322
|
+
</div>
|
|
323
|
+
{isPending && <PendingToolApproval part={part} />}
|
|
324
|
+
{open && (
|
|
325
|
+
<div className="grid w-full min-w-0 max-w-full gap-1.5 overflow-hidden p-1.5">
|
|
326
|
+
{!embedded && view.previewTarget && isPreviewableTarget(view.previewTarget) && (
|
|
327
|
+
<PreviewAttachment source="tool-result" target={view.previewTarget} />
|
|
328
|
+
)}
|
|
329
|
+
{view.imageUrl && (
|
|
330
|
+
<div className="max-w-72 overflow-hidden rounded-[0.25rem] border border-(--ui-stroke-tertiary)">
|
|
331
|
+
<ZoomableImage alt={copy.outputAlt} className="h-auto w-full object-cover" src={view.imageUrl} />
|
|
332
|
+
</div>
|
|
333
|
+
)}
|
|
334
|
+
{hasSearchHits && view.searchHits && (
|
|
335
|
+
<div className="max-w-full text-xs leading-relaxed text-(--ui-text-secondary)">
|
|
336
|
+
{searchResultsLabel && <p className={TOOL_SECTION_LABEL_CLASS}>{searchResultsLabel}</p>}
|
|
337
|
+
<SearchResultsList hits={view.searchHits} />
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
340
|
+
{showDetail &&
|
|
341
|
+
toolViewMode !== 'technical' &&
|
|
342
|
+
(view.status === 'error' ? (
|
|
343
|
+
detailSections.summary || detailSections.body ? (
|
|
344
|
+
<div className="max-w-full text-xs leading-relaxed text-destructive">
|
|
345
|
+
{detailSections.summary && (
|
|
346
|
+
<LinkifiedText className="block font-medium" text={detailSections.summary} />
|
|
347
|
+
)}
|
|
348
|
+
{detailSections.body && (
|
|
349
|
+
<pre
|
|
350
|
+
className={cn(
|
|
351
|
+
'max-h-56 overflow-auto whitespace-pre-wrap wrap-anywhere font-mono text-[0.7rem] leading-[1.55] text-destructive/90',
|
|
352
|
+
detailSections.summary && 'mt-1.5'
|
|
353
|
+
)}
|
|
354
|
+
>
|
|
355
|
+
{detailSections.body}
|
|
356
|
+
</pre>
|
|
357
|
+
)}
|
|
358
|
+
</div>
|
|
359
|
+
) : null
|
|
360
|
+
) : view.stdout || view.stderr ? (
|
|
361
|
+
// Stdout + stderr split: render both as labeled blocks. stderr
|
|
362
|
+
// is intentionally NOT painted destructive — many CLIs log
|
|
363
|
+
// informational output there.
|
|
364
|
+
<div className="max-w-full text-xs leading-relaxed text-(--ui-text-secondary)">
|
|
365
|
+
{view.detailLabel && <p className={TOOL_SECTION_LABEL_CLASS}>{view.detailLabel}</p>}
|
|
366
|
+
{view.stdout && (
|
|
367
|
+
<div className="space-y-0.5">
|
|
368
|
+
{view.stderr && <p className={TOOL_SECTION_LABEL_CLASS}>stdout</p>}
|
|
369
|
+
<pre className={cn(TOOL_SECTION_PRE_CLASS, 'whitespace-pre-wrap wrap-anywhere')}>
|
|
370
|
+
{view.rendersAnsi ? <AnsiText text={view.stdout} /> : view.stdout}
|
|
371
|
+
</pre>
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
374
|
+
{view.stderr && (
|
|
375
|
+
<div className={cn('space-y-0.5', view.stdout && 'mt-1.5')}>
|
|
376
|
+
<p className={TOOL_SECTION_LABEL_CLASS}>stderr</p>
|
|
377
|
+
<pre
|
|
378
|
+
className={cn(
|
|
379
|
+
TOOL_SECTION_PRE_CLASS,
|
|
380
|
+
'whitespace-pre-wrap wrap-anywhere text-(--ui-text-tertiary)'
|
|
381
|
+
)}
|
|
382
|
+
>
|
|
383
|
+
{view.rendersAnsi ? <AnsiText text={view.stderr} /> : view.stderr}
|
|
384
|
+
</pre>
|
|
385
|
+
</div>
|
|
386
|
+
)}
|
|
387
|
+
</div>
|
|
388
|
+
) : (
|
|
389
|
+
<div className="max-w-full text-xs leading-relaxed text-(--ui-text-secondary)">
|
|
390
|
+
{view.detailLabel && <p className={TOOL_SECTION_LABEL_CLASS}>{view.detailLabel}</p>}
|
|
391
|
+
{renderDetailAsCode ? (
|
|
392
|
+
<pre className={cn(TOOL_SECTION_PRE_CLASS, 'whitespace-pre-wrap wrap-anywhere')}>
|
|
393
|
+
{view.rendersAnsi ? <AnsiText text={view.detail} /> : view.detail}
|
|
394
|
+
</pre>
|
|
395
|
+
) : (
|
|
396
|
+
<CompactMarkdown className={cn(TOOL_SECTION_SURFACE_CLASS, 'wrap-anywhere')} text={view.detail} />
|
|
397
|
+
)}
|
|
398
|
+
</div>
|
|
399
|
+
))}
|
|
400
|
+
{showRawSearchDrilldown && (
|
|
401
|
+
<details className="max-w-full">
|
|
402
|
+
<summary className={cn(TOOL_SECTION_LABEL_CLASS, 'mb-0')}>{copy.rawResponse}</summary>
|
|
403
|
+
<pre className={cn(TOOL_SECTION_PRE_CLASS, 'mt-1 whitespace-pre-wrap wrap-anywhere')}>
|
|
404
|
+
{view.rawResult}
|
|
405
|
+
</pre>
|
|
406
|
+
</details>
|
|
407
|
+
)}
|
|
408
|
+
{toolViewMode === 'technical' && (
|
|
409
|
+
<pre className={cn(TOOL_SECTION_PRE_CLASS, 'whitespace-pre-wrap wrap-anywhere')}>
|
|
410
|
+
{rawTechnicalTrace(part.args, part.result)}
|
|
411
|
+
</pre>
|
|
412
|
+
)}
|
|
413
|
+
</div>
|
|
414
|
+
)}
|
|
415
|
+
{open && view.inlineDiff && <DiffLines text={view.inlineDiff} />}
|
|
416
|
+
</div>
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Flat, Cursor-style tool list. assistant-ui hands us a *range* of
|
|
422
|
+
* consecutive tool-call parts, but how that range is sliced is unstable: a
|
|
423
|
+
* live stream interleaves narration/reasoning between calls (many tiny
|
|
424
|
+
* ranges), while the settled message reconstructs every tool_call back-to-back
|
|
425
|
+
* (one big range). Rendering a "Tool actions · N steps" group off that range
|
|
426
|
+
* therefore reshuffled the whole turn the instant it settled.
|
|
427
|
+
*
|
|
428
|
+
* So we never group: each tool is a standalone row, and the wrapper just lays
|
|
429
|
+
* its children out on the tight `--tool-row-gap` rhythm. One range or ten,
|
|
430
|
+
* fragmented or consecutive, the result is pixel-identical — a tight, stable
|
|
431
|
+
* stack. The wrapper stays a single `<div>` of stable identity so children
|
|
432
|
+
* never remount as the range grows mid-stream. `ToolEmbedContext` is false so
|
|
433
|
+
* every row owns its own chrome (timer / preview / copy / inline approval).
|
|
434
|
+
*/
|
|
435
|
+
export const ToolGroupSlot: FC<PropsWithChildren<{ endIndex: number; startIndex: number }>> = ({
|
|
436
|
+
children,
|
|
437
|
+
startIndex
|
|
438
|
+
}) => {
|
|
439
|
+
const messageId = useAuiState(s => s.message.id)
|
|
440
|
+
const messageRunning = useAuiState(selectMessageRunning)
|
|
441
|
+
const enterRef = useEnterAnimation(messageRunning, `tool-group:${messageId}:${startIndex}`)
|
|
442
|
+
|
|
443
|
+
return (
|
|
444
|
+
<ToolEmbedContext.Provider value={false}>
|
|
445
|
+
<div
|
|
446
|
+
className="grid min-w-0 max-w-full gap-(--tool-row-gap) overflow-hidden"
|
|
447
|
+
data-slot="tool-block"
|
|
448
|
+
ref={enterRef}
|
|
449
|
+
>
|
|
450
|
+
{children}
|
|
451
|
+
</div>
|
|
452
|
+
</ToolEmbedContext.Provider>
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Per-tool fallback. Now strictly returns a single ToolEntry — the
|
|
458
|
+
* grouping decision lives in ToolGroupSlot above, so this never swaps
|
|
459
|
+
* its return type and the underlying ToolEntry stays mounted across
|
|
460
|
+
* group-shape changes.
|
|
461
|
+
*/
|
|
462
|
+
export const ToolFallback = ({ toolCallId, toolName, args, isError, result }: ToolCallMessagePartProps) => {
|
|
463
|
+
const part: ToolPart = { args, isError, result, toolCallId, toolName, type: 'tool-call' }
|
|
464
|
+
|
|
465
|
+
return <ToolEntry part={part} />
|
|
466
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type ComponentPropsWithRef, forwardRef } from 'react'
|
|
4
|
+
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import { Tip } from '@/components/ui/tooltip'
|
|
7
|
+
import { cn } from '@/lib/utils'
|
|
8
|
+
|
|
9
|
+
export interface TooltipIconButtonProps extends ComponentPropsWithRef<typeof Button> {
|
|
10
|
+
tooltip: string
|
|
11
|
+
side?: 'top' | 'bottom' | 'left' | 'right'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const TooltipIconButton = forwardRef<HTMLButtonElement, TooltipIconButtonProps>(
|
|
15
|
+
({ children, tooltip, side = 'bottom', className, ...rest }, ref) => {
|
|
16
|
+
return (
|
|
17
|
+
<Tip label={tooltip} side={side}>
|
|
18
|
+
<Button
|
|
19
|
+
size="icon-xs"
|
|
20
|
+
variant="ghost"
|
|
21
|
+
{...rest}
|
|
22
|
+
aria-label={tooltip}
|
|
23
|
+
className={cn('aui-button-icon', className)}
|
|
24
|
+
ref={ref}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</Button>
|
|
28
|
+
</Tip>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
TooltipIconButton.displayName = 'TooltipIconButton'
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { ExportedMessageRepository } from '@assistant-ui/core/internal'
|
|
2
|
+
// Clicking a user bubble must open the inline edit composer — through the
|
|
3
|
+
// app's incremental external-store runtime (which reimplements capability
|
|
4
|
+
// resolution, incl. `edit: onEdit !== undefined`) and the stock runtime.
|
|
5
|
+
//
|
|
6
|
+
// Note: this covers the React/runtime wiring only. The Electron-level failure
|
|
7
|
+
// mode (titlebar -webkit-app-region:drag swallowing clicks on *stuck* sticky
|
|
8
|
+
// bubbles) is not reproducible in jsdom — see USER_BUBBLE_BASE_CLASS's no-drag
|
|
9
|
+
// carve-out in thread.tsx.
|
|
10
|
+
import { AssistantRuntimeProvider, type ThreadMessage, useExternalStoreRuntime } from '@assistant-ui/react'
|
|
11
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
12
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
13
|
+
|
|
14
|
+
import { useIncrementalExternalStoreRuntime } from '@/lib/incremental-external-store-runtime'
|
|
15
|
+
|
|
16
|
+
import { Thread } from './thread'
|
|
17
|
+
|
|
18
|
+
const createdAt = new Date('2026-05-01T00:00:00.000Z')
|
|
19
|
+
|
|
20
|
+
class TestResizeObserver {
|
|
21
|
+
observe() {}
|
|
22
|
+
unobserve() {}
|
|
23
|
+
disconnect() {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
vi.stubGlobal('ResizeObserver', TestResizeObserver)
|
|
27
|
+
vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) =>
|
|
28
|
+
window.setTimeout(() => callback(performance.now()), 0)
|
|
29
|
+
)
|
|
30
|
+
vi.stubGlobal('cancelAnimationFrame', (id: number) => window.clearTimeout(id))
|
|
31
|
+
|
|
32
|
+
Element.prototype.scrollTo = function scrollTo() {}
|
|
33
|
+
|
|
34
|
+
function stubOffsetDimension(
|
|
35
|
+
prop: 'offsetHeight' | 'offsetWidth',
|
|
36
|
+
clientProp: 'clientHeight' | 'clientWidth',
|
|
37
|
+
fallback: number
|
|
38
|
+
) {
|
|
39
|
+
const previous = Object.getOwnPropertyDescriptor(HTMLElement.prototype, prop)
|
|
40
|
+
|
|
41
|
+
Object.defineProperty(HTMLElement.prototype, prop, {
|
|
42
|
+
configurable: true,
|
|
43
|
+
get() {
|
|
44
|
+
return previous?.get?.call(this) || (this as HTMLElement)[clientProp] || fallback
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
stubOffsetDimension('offsetWidth', 'clientWidth', 800)
|
|
50
|
+
stubOffsetDimension('offsetHeight', 'clientHeight', 600)
|
|
51
|
+
|
|
52
|
+
function userMessage(): ThreadMessage {
|
|
53
|
+
return {
|
|
54
|
+
id: 'user-1',
|
|
55
|
+
role: 'user',
|
|
56
|
+
content: [{ type: 'text', text: 'edit me please' }],
|
|
57
|
+
attachments: [],
|
|
58
|
+
createdAt,
|
|
59
|
+
metadata: { custom: {} }
|
|
60
|
+
} as ThreadMessage
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function assistantMessage(): ThreadMessage {
|
|
64
|
+
return {
|
|
65
|
+
id: 'assistant-1',
|
|
66
|
+
role: 'assistant',
|
|
67
|
+
content: [{ type: 'text', text: 'done' }],
|
|
68
|
+
status: { type: 'complete', reason: 'stop' },
|
|
69
|
+
createdAt,
|
|
70
|
+
metadata: {
|
|
71
|
+
unstable_state: null,
|
|
72
|
+
unstable_annotations: [],
|
|
73
|
+
unstable_data: [],
|
|
74
|
+
steps: [],
|
|
75
|
+
custom: {}
|
|
76
|
+
}
|
|
77
|
+
} as ThreadMessage
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Mirrors chat/index.tsx: incremental runtime + messageRepository + onEdit.
|
|
81
|
+
function IncrementalHarness({ onEdit }: { onEdit: () => Promise<void> }) {
|
|
82
|
+
const repository = ExportedMessageRepository.fromArray([userMessage(), assistantMessage()])
|
|
83
|
+
|
|
84
|
+
const runtime = useIncrementalExternalStoreRuntime<ThreadMessage>({
|
|
85
|
+
messageRepository: repository,
|
|
86
|
+
isRunning: false,
|
|
87
|
+
setMessages: () => {},
|
|
88
|
+
onNew: async () => {},
|
|
89
|
+
onEdit,
|
|
90
|
+
onCancel: async () => {},
|
|
91
|
+
onReload: async () => {}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
96
|
+
<Thread />
|
|
97
|
+
</AssistantRuntimeProvider>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Control: stock external store runtime.
|
|
102
|
+
function StockHarness({ onEdit }: { onEdit: () => Promise<void> }) {
|
|
103
|
+
const runtime = useExternalStoreRuntime<ThreadMessage>({
|
|
104
|
+
messages: [userMessage(), assistantMessage()],
|
|
105
|
+
isRunning: false,
|
|
106
|
+
onNew: async () => {},
|
|
107
|
+
onEdit
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<AssistantRuntimeProvider runtime={runtime}>
|
|
112
|
+
<Thread />
|
|
113
|
+
</AssistantRuntimeProvider>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
describe('click-to-edit user message', () => {
|
|
118
|
+
it('opens the edit composer with the incremental runtime', async () => {
|
|
119
|
+
const { container } = render(<IncrementalHarness onEdit={async () => {}} />)
|
|
120
|
+
|
|
121
|
+
const bubble = await screen.findByRole('button', { name: 'Edit message' })
|
|
122
|
+
|
|
123
|
+
fireEvent.click(bubble)
|
|
124
|
+
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(container.querySelector('[data-slot="aui_edit-composer-root"]')).toBeTruthy()
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('opens the edit composer with the stock runtime', async () => {
|
|
131
|
+
const { container } = render(<StockHarness onEdit={async () => {}} />)
|
|
132
|
+
|
|
133
|
+
const bubble = await screen.findByRole('button', { name: 'Edit message' })
|
|
134
|
+
|
|
135
|
+
fireEvent.click(bubble)
|
|
136
|
+
|
|
137
|
+
await waitFor(() => {
|
|
138
|
+
expect(container.querySelector('[data-slot="aui_edit-composer-root"]')).toBeTruthy()
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
})
|