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,64 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
// Module-level registry so timers survive component unmount/remount (e.g.
|
|
4
|
+
// when a tool row scrolls out and back). Keyed by caller-supplied timerKey;
|
|
5
|
+
// anonymous timers (no key) start fresh each mount.
|
|
6
|
+
const startedAtByKey = new Map<string, number>()
|
|
7
|
+
|
|
8
|
+
function startedAt(key?: string): number {
|
|
9
|
+
if (!key) {
|
|
10
|
+
return Date.now()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const existing = startedAtByKey.get(key)
|
|
14
|
+
|
|
15
|
+
if (existing !== undefined) {
|
|
16
|
+
return existing
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const now = Date.now()
|
|
20
|
+
startedAtByKey.set(key, now)
|
|
21
|
+
|
|
22
|
+
return now
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function formatElapsed(seconds: number): string {
|
|
26
|
+
if (seconds < 60) {
|
|
27
|
+
return `${seconds}s`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function useElapsedSeconds(active = true, timerKey?: string): number {
|
|
34
|
+
const start = useRef(startedAt(timerKey))
|
|
35
|
+
const lastKey = useRef(timerKey)
|
|
36
|
+
const [elapsed, setElapsed] = useState(() => Math.max(0, Math.floor((Date.now() - start.current) / 1000)))
|
|
37
|
+
|
|
38
|
+
if (lastKey.current !== timerKey) {
|
|
39
|
+
start.current = startedAt(timerKey)
|
|
40
|
+
lastKey.current = timerKey
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!active) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (timerKey) {
|
|
49
|
+
start.current = startedAt(timerKey)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const tick = () => setElapsed(Math.max(0, Math.floor((Date.now() - start.current) / 1000)))
|
|
53
|
+
tick()
|
|
54
|
+
const id = window.setInterval(tick, 1000)
|
|
55
|
+
|
|
56
|
+
return () => window.clearInterval(id)
|
|
57
|
+
}, [active, timerKey])
|
|
58
|
+
|
|
59
|
+
return elapsed
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function __resetElapsedTimerRegistryForTests() {
|
|
63
|
+
startedAtByKey.clear()
|
|
64
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
import { Codicon, type CodiconProps } from '@/components/ui/codicon'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Rounded-card shell for fenced code (and any equivalent: diffs, raw payloads,
|
|
8
|
+
* etc.) sized for the conversation column. Mirrors the expanded tool-row
|
|
9
|
+
* pattern so code blocks read as the same family of artifact.
|
|
10
|
+
*/
|
|
11
|
+
function CodeCard({ className, ...props }: React.ComponentProps<'div'>) {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
className={cn(
|
|
15
|
+
'min-w-0 max-w-full overflow-hidden rounded-[0.625rem] border border-border text-[length:var(--conversation-tool-font-size)] text-muted-foreground',
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
data-slot="code-card"
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function CodeCardHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
className={cn('flex items-center justify-between gap-2 border-b border-border px-2 py-1.5', className)}
|
|
28
|
+
data-slot="code-card-header"
|
|
29
|
+
{...props}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function CodeCardTitle({ className, children, ...props }: React.ComponentProps<'span'>) {
|
|
35
|
+
return (
|
|
36
|
+
<span
|
|
37
|
+
className={cn(
|
|
38
|
+
'flex min-w-0 items-center gap-1.5 truncate text-[length:var(--conversation-tool-font-size)] font-medium leading-(--conversation-line-height) text-foreground/80',
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
data-slot="code-card-title"
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
{children}
|
|
45
|
+
</span>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function CodeCardIcon({ className, ...props }: CodiconProps) {
|
|
50
|
+
return (
|
|
51
|
+
<Codicon
|
|
52
|
+
className={cn('shrink-0 text-[0.875rem] leading-none text-muted-foreground', className)}
|
|
53
|
+
data-slot="code-card-icon"
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function CodeCardSubtitle({ className, ...props }: React.ComponentProps<'span'>) {
|
|
60
|
+
return (
|
|
61
|
+
<span className={cn('font-normal text-muted-foreground', className)} data-slot="code-card-subtitle" {...props} />
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function CodeCardBody({ className, ...props }: React.ComponentProps<'div'>) {
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
className={cn(
|
|
69
|
+
'p-1.5 font-mono text-[0.7rem] leading-relaxed text-foreground/90 [&_pre]:m-0 [&_pre]:overflow-x-auto [&_pre]:bg-transparent! [&_pre]:px-2 [&_pre]:py-1.5 [&_pre]:font-mono [&_pre]:leading-relaxed',
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
data-slot="code-card-body"
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { CodeCard, CodeCardBody, CodeCardHeader, CodeCardIcon, CodeCardSubtitle, CodeCardTitle }
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ComponentProps, ElementType, FC } from 'react'
|
|
2
|
+
import { Streamdown } from 'streamdown'
|
|
3
|
+
|
|
4
|
+
import { ExternalLink, ExternalLinkIcon } from '@/lib/external-link'
|
|
5
|
+
import { cn } from '@/lib/utils'
|
|
6
|
+
|
|
7
|
+
// Compact markdown renderer for tool detail bodies. Same Streamdown pipeline
|
|
8
|
+
// as the file preview pane, with tighter typography and external-link routing
|
|
9
|
+
// so tools that emit markdown (tables, headings, links) render properly
|
|
10
|
+
// instead of being dumped as raw text.
|
|
11
|
+
|
|
12
|
+
const TAG_CLASSES = {
|
|
13
|
+
blockquote: 'mt-2 mb-2 border-l-2 border-border/70 pl-2.5 italic text-muted-foreground/85',
|
|
14
|
+
h1: 'mt-3 mb-1.5 text-sm font-semibold tracking-tight text-foreground first:mt-0',
|
|
15
|
+
h2: 'mt-3 mb-1.5 text-[0.82rem] font-semibold tracking-tight text-foreground first:mt-0',
|
|
16
|
+
h3: 'mt-2.5 mb-1 text-[0.78rem] font-semibold text-foreground first:mt-0',
|
|
17
|
+
h4: 'mt-2 mb-1 text-[0.74rem] font-semibold text-foreground first:mt-0',
|
|
18
|
+
hr: 'my-2 border-border/50',
|
|
19
|
+
li: 'marker:text-muted-foreground/60',
|
|
20
|
+
ol: 'mb-2 list-decimal pl-5 last:mb-0',
|
|
21
|
+
p: 'mb-1.5 leading-relaxed last:mb-0',
|
|
22
|
+
pre: 'mb-2 overflow-x-auto rounded-md border border-border/60 bg-background/70 p-2 font-mono text-[0.7rem] leading-[1.55] last:mb-0',
|
|
23
|
+
td: 'px-2 py-1 align-top leading-snug',
|
|
24
|
+
th: 'px-2 py-1 text-left text-[0.62rem] font-semibold uppercase tracking-[0.08em] text-muted-foreground/80',
|
|
25
|
+
thead: 'bg-muted/40',
|
|
26
|
+
ul: 'mb-2 list-disc pl-5 last:mb-0'
|
|
27
|
+
} as const
|
|
28
|
+
|
|
29
|
+
function tagged<T extends keyof typeof TAG_CLASSES>(Tag: T) {
|
|
30
|
+
const Component = (({ className, ...rest }: ComponentProps<T>) => {
|
|
31
|
+
const Element = Tag as ElementType
|
|
32
|
+
|
|
33
|
+
return <Element className={cn(TAG_CLASSES[Tag], className)} {...rest} />
|
|
34
|
+
}) as FC<ComponentProps<T>>
|
|
35
|
+
|
|
36
|
+
Component.displayName = `Md.${Tag}`
|
|
37
|
+
|
|
38
|
+
return Component
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function MarkdownAnchor({ children, className, href, ...rest }: ComponentProps<'a'>) {
|
|
42
|
+
if (!href || !/^https?:\/\//i.test(href)) {
|
|
43
|
+
return (
|
|
44
|
+
<a
|
|
45
|
+
className={cn('font-medium underline underline-offset-4 decoration-current/20', className)}
|
|
46
|
+
href={href}
|
|
47
|
+
{...rest}
|
|
48
|
+
>
|
|
49
|
+
{children}
|
|
50
|
+
</a>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<ExternalLink className={cn('decoration-current/20', className)} href={href} showExternalIcon={false}>
|
|
56
|
+
{children}
|
|
57
|
+
<ExternalLinkIcon />
|
|
58
|
+
</ExternalLink>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function MarkdownCode({ className, ...rest }: ComponentProps<'code'>) {
|
|
63
|
+
return (
|
|
64
|
+
<code
|
|
65
|
+
className={cn('rounded bg-muted/80 px-1 py-px font-mono text-[0.86em] text-muted-foreground', className)}
|
|
66
|
+
{...rest}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function MarkdownTable({ className, ...rest }: ComponentProps<'table'>) {
|
|
72
|
+
return (
|
|
73
|
+
<div className="mb-2 max-w-full overflow-x-auto rounded-md border border-border/60 last:mb-0">
|
|
74
|
+
<table
|
|
75
|
+
className={cn(
|
|
76
|
+
'w-full border-collapse text-[0.72rem] [&_tr]:border-b [&_tr]:border-border/50 last:[&_tr]:border-0',
|
|
77
|
+
className
|
|
78
|
+
)}
|
|
79
|
+
{...rest}
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const COMPONENTS = {
|
|
86
|
+
a: MarkdownAnchor,
|
|
87
|
+
blockquote: tagged('blockquote'),
|
|
88
|
+
code: MarkdownCode,
|
|
89
|
+
h1: tagged('h1'),
|
|
90
|
+
h2: tagged('h2'),
|
|
91
|
+
h3: tagged('h3'),
|
|
92
|
+
h4: tagged('h4'),
|
|
93
|
+
hr: tagged('hr'),
|
|
94
|
+
li: tagged('li'),
|
|
95
|
+
ol: tagged('ol'),
|
|
96
|
+
p: tagged('p'),
|
|
97
|
+
pre: tagged('pre'),
|
|
98
|
+
table: MarkdownTable,
|
|
99
|
+
td: tagged('td'),
|
|
100
|
+
th: tagged('th'),
|
|
101
|
+
thead: tagged('thead'),
|
|
102
|
+
ul: tagged('ul')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function CompactMarkdown({ className, text }: { className?: string; text: string }) {
|
|
106
|
+
return (
|
|
107
|
+
<div className={cn('max-w-full text-xs leading-relaxed text-muted-foreground/90 wrap-anywhere', className)}>
|
|
108
|
+
<Streamdown components={COMPONENTS} controls={false} mode="static" parseIncompleteMarkdown={false}>
|
|
109
|
+
{text}
|
|
110
|
+
</Streamdown>
|
|
111
|
+
</div>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The composer surface and everything docked to it (slash·@ popover, `?` help)
|
|
5
|
+
* paint ONE shared `--composer-fill` var. The state ladder (rest / scrolled /
|
|
6
|
+
* focused / drawer-open) lives in styles.css on `[data-slot='composer-root']`,
|
|
7
|
+
* so the two layers can never disagree — drawer-open forces an opaque fill via
|
|
8
|
+
* `:has()`, because translucent glass sampling different backdrops (thread vs
|
|
9
|
+
* fade gradient) renders as different colors even with identical tints.
|
|
10
|
+
*/
|
|
11
|
+
export const composerFill = 'bg-(--composer-fill)'
|
|
12
|
+
|
|
13
|
+
/** Backdrop treatment for the composer input surface. Harmless when the fill
|
|
14
|
+
* goes opaque (drawer open) — nothing shows through to blur. */
|
|
15
|
+
export const composerSurfaceGlass = cn(
|
|
16
|
+
'backdrop-blur-[0.75rem] backdrop-saturate-[1.12] [-webkit-backdrop-filter:blur(0.75rem)_saturate(1.12)]',
|
|
17
|
+
'transition-[background-color] duration-150 ease-out'
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const composerDockEdge = (edge: 'bottom' | 'top') =>
|
|
21
|
+
cn('border border-border/65', edge === 'top' ? 'rounded-t-2xl border-b-0' : 'rounded-b-2xl border-t-0')
|
|
22
|
+
|
|
23
|
+
/** Glassy docked card — the status stack / queue. Paints the SAME
|
|
24
|
+
* `--composer-fill` as the surface, so rest / scrolled / focused / drawer-open
|
|
25
|
+
* all match the composer by construction. */
|
|
26
|
+
export const composerDockCard = (edge: 'bottom' | 'top' = 'top') =>
|
|
27
|
+
cn(composerDockEdge(edge), composerFill, composerSurfaceGlass)
|
|
28
|
+
|
|
29
|
+
/** Fused docked card — completion drawers. Shares `--composer-fill` with the
|
|
30
|
+
* composer surface, which goes opaque while a drawer is open. */
|
|
31
|
+
export const composerFusedDockCard = (edge: 'bottom' | 'top' = 'top') => cn(composerDockEdge(edge), composerFill)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Per-line classed renderer for unified diffs. Lives outside `CodeCard` so
|
|
7
|
+
* tool-result panels (already nested inside a tool card) don't double-shell;
|
|
8
|
+
* for markdown ` ```diff ` fences the standard `CodeCard` + Shiki path runs
|
|
9
|
+
* instead and gives equivalent coloring.
|
|
10
|
+
*/
|
|
11
|
+
interface DiffLineKind {
|
|
12
|
+
className?: string
|
|
13
|
+
match: (line: string) => boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const DIFF_LINE_KINDS: DiffLineKind[] = [
|
|
17
|
+
{
|
|
18
|
+
className: 'text-emerald-700 dark:text-emerald-300',
|
|
19
|
+
match: line => line.startsWith('+') && !line.startsWith('+++')
|
|
20
|
+
},
|
|
21
|
+
{ className: 'text-rose-700 dark:text-rose-300', match: line => line.startsWith('-') && !line.startsWith('---') },
|
|
22
|
+
{ className: 'text-sky-700 dark:text-sky-300', match: line => line.startsWith('@@') },
|
|
23
|
+
{
|
|
24
|
+
className: 'text-muted-foreground/70',
|
|
25
|
+
match: line => line.startsWith('---') || line.startsWith('+++') || / → /.test(line.slice(0, 60))
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
function classifyLine(line: string): string | undefined {
|
|
30
|
+
return DIFF_LINE_KINDS.find(kind => kind.match(line))?.className
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface DiffLinesProps extends Omit<React.ComponentProps<'pre'>, 'children'> {
|
|
34
|
+
text: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function DiffLines({ className, text, ...props }: DiffLinesProps) {
|
|
38
|
+
return (
|
|
39
|
+
<pre
|
|
40
|
+
className={cn(
|
|
41
|
+
'mt-1 mb-1.5 max-h-96 max-w-full min-w-0 overflow-auto rounded-md border border-border/60 bg-muted/35 px-2.5 py-1.5 font-mono text-[0.7rem] leading-relaxed text-muted-foreground',
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
data-slot="diff-lines"
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{text.split('\n').map((line, index) => (
|
|
48
|
+
<span className={cn('block min-w-max whitespace-pre', classifyLine(line))} key={`${index}-${line}`}>
|
|
49
|
+
{line || ' '}
|
|
50
|
+
</span>
|
|
51
|
+
))}
|
|
52
|
+
</pre>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import { DisclosureCaret } from '@/components/ui/disclosure-caret'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
|
|
6
|
+
// Shared header row for any collapsible block (thinking, tool group, single
|
|
7
|
+
// tool). Each parent supplies its own outer wrapper (with the data-slot CSS
|
|
8
|
+
// uses to escape the message padding) and its own expanded body.
|
|
9
|
+
//
|
|
10
|
+
// Affordance:
|
|
11
|
+
// - No leading chevron; a caret appears to the RIGHT of the text on hover
|
|
12
|
+
// (and stays visible when the row is open).
|
|
13
|
+
// - The hover background is a tight content-shaped pill — sized to the
|
|
14
|
+
// title text, NOT the full row — and reaches just past the chevron with
|
|
15
|
+
// `-mx-1.5 px-1.5` so it reads as a soft hit-target rather than a slab
|
|
16
|
+
// stretching to the message edge.
|
|
17
|
+
export function DisclosureRow({
|
|
18
|
+
children,
|
|
19
|
+
onToggle,
|
|
20
|
+
open,
|
|
21
|
+
trailing
|
|
22
|
+
}: {
|
|
23
|
+
children: ReactNode
|
|
24
|
+
onToggle?: () => void
|
|
25
|
+
open: boolean
|
|
26
|
+
trailing?: ReactNode
|
|
27
|
+
}) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="group/disclosure-row relative flex w-full max-w-full min-w-0 text-(--ui-text-tertiary)">
|
|
30
|
+
<button
|
|
31
|
+
aria-expanded={onToggle ? open : undefined}
|
|
32
|
+
className={cn(
|
|
33
|
+
// max-w-fit so the click target hugs the title text width — no
|
|
34
|
+
// background fill, just the cursor + the affordance caret.
|
|
35
|
+
'flex min-w-0 max-w-fit items-start gap-1.5 text-left transition-colors',
|
|
36
|
+
onToggle ? 'hover:text-foreground focus-visible:text-foreground focus-visible:outline-none' : 'cursor-default'
|
|
37
|
+
)}
|
|
38
|
+
disabled={!onToggle}
|
|
39
|
+
onClick={onToggle}
|
|
40
|
+
type="button"
|
|
41
|
+
>
|
|
42
|
+
<span className="flex min-w-0 flex-col gap-0.5">{children}</span>
|
|
43
|
+
{onToggle && (
|
|
44
|
+
// Wrapper height matches the title row's actual line-height so the
|
|
45
|
+
// caret centres with the title, not the whole subtitle stack.
|
|
46
|
+
<span
|
|
47
|
+
className={cn(
|
|
48
|
+
'flex h-(--conversation-line-height) shrink-0 items-center justify-center transition-opacity duration-150',
|
|
49
|
+
open
|
|
50
|
+
? 'opacity-80'
|
|
51
|
+
: 'opacity-0 group-hover/disclosure-row:opacity-80 group-focus-within/disclosure-row:opacity-80'
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
<DisclosureCaret open={open} />
|
|
55
|
+
</span>
|
|
56
|
+
)}
|
|
57
|
+
</button>
|
|
58
|
+
{trailing && (
|
|
59
|
+
<span className="absolute right-1 top-0 flex h-(--conversation-line-height) items-center">{trailing}</span>
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { createContext, type ReactNode, useContext, useMemo, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
type Value = {
|
|
6
|
+
isPending: boolean
|
|
7
|
+
setPending: (pending: boolean) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const Ctx = createContext<Value | null>(null)
|
|
11
|
+
|
|
12
|
+
export function GeneratedImageProvider({ children }: { children: ReactNode }) {
|
|
13
|
+
const [isPending, setPending] = useState(false)
|
|
14
|
+
const value = useMemo(() => ({ isPending, setPending }), [isPending])
|
|
15
|
+
|
|
16
|
+
return <Ctx.Provider value={value}>{children}</Ctx.Provider>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const useGeneratedImageContext = () => useContext(Ctx)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type FC, useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { DiffusionCanvas } from '@/components/chat/image-generation-placeholder'
|
|
6
|
+
import { ImageActionButton, ImageLightbox } from '@/components/chat/zoomable-image'
|
|
7
|
+
import { useImageDownload } from '@/hooks/use-image-download'
|
|
8
|
+
import { useI18n } from '@/i18n'
|
|
9
|
+
import { generatedImageFromResult } from '@/lib/generated-images'
|
|
10
|
+
import { filePathFromMediaPath, gatewayMediaDataUrl, isRemoteGateway, mediaExternalUrl, mediaName } from '@/lib/media'
|
|
11
|
+
import { cn } from '@/lib/utils'
|
|
12
|
+
|
|
13
|
+
// Aspect hint from the tool args sizes the frame *before* the image loads, so
|
|
14
|
+
// the placeholder and the resolved image occupy the same box — no layout shift.
|
|
15
|
+
const ASPECT_HINTS: Record<string, number> = {
|
|
16
|
+
landscape: 16 / 9,
|
|
17
|
+
square: 1,
|
|
18
|
+
portrait: 9 / 16
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function hintedRatio(aspectRatio?: string): number {
|
|
22
|
+
return ASPECT_HINTS[String(aspectRatio ?? '').toLowerCase().trim()] ?? ASPECT_HINTS.landscape
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isInlineSrc(path: string): boolean {
|
|
26
|
+
return /^(?:https?|data):/i.test(path)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function resolveImageSrc(path: string): Promise<string> {
|
|
30
|
+
if (isInlineSrc(path)) {
|
|
31
|
+
return path
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (window.NASTECHDesktop && isRemoteGateway()) {
|
|
35
|
+
return gatewayMediaDataUrl(path)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!window.NASTECHDesktop?.readFileDataUrl) {
|
|
39
|
+
return mediaExternalUrl(path)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return window.NASTECHDesktop.readFileDataUrl(filePathFromMediaPath(path))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const GeneratedImage: FC<{ aspectRatio?: string; result?: unknown }> = ({ aspectRatio, result }) => {
|
|
46
|
+
const { t } = useI18n()
|
|
47
|
+
const copy = t.desktop
|
|
48
|
+
const image = result === undefined ? null : generatedImageFromResult(result)
|
|
49
|
+
const pending = result === undefined
|
|
50
|
+
|
|
51
|
+
const [ratio, setRatio] = useState(() => hintedRatio(aspectRatio))
|
|
52
|
+
const [src, setSrc] = useState(() => (image && isInlineSrc(image) ? image : ''))
|
|
53
|
+
const [loaded, setLoaded] = useState(false)
|
|
54
|
+
const [canvasGone, setCanvasGone] = useState(false)
|
|
55
|
+
const [failed, setFailed] = useState(false)
|
|
56
|
+
const [lightboxOpen, setLightboxOpen] = useState(false)
|
|
57
|
+
const { download, saving } = useImageDownload(src)
|
|
58
|
+
|
|
59
|
+
useEffect(() => setRatio(hintedRatio(aspectRatio)), [aspectRatio])
|
|
60
|
+
|
|
61
|
+
// Resolve the deliverable path (local read / gateway proxy / remote URL). The
|
|
62
|
+
// <img> stays mounted under the placeholder and only fades in once it decodes,
|
|
63
|
+
// so the frame keeps its hinted size and never jumps.
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
let cancelled = false
|
|
66
|
+
setFailed(false)
|
|
67
|
+
setLoaded(false)
|
|
68
|
+
setCanvasGone(false)
|
|
69
|
+
setSrc(image && isInlineSrc(image) ? image : '')
|
|
70
|
+
|
|
71
|
+
if (!image || isInlineSrc(image)) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
void resolveImageSrc(image)
|
|
76
|
+
.then(resolved => !cancelled && setSrc(resolved))
|
|
77
|
+
.catch(() => !cancelled && setFailed(true))
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true
|
|
81
|
+
}
|
|
82
|
+
}, [image])
|
|
83
|
+
|
|
84
|
+
// Completed but no usable image (generation failed): the agent's prose carries
|
|
85
|
+
// the explanation, so render nothing here.
|
|
86
|
+
if (!pending && !image) {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (failed && image) {
|
|
91
|
+
return (
|
|
92
|
+
<a
|
|
93
|
+
className="mt-2 inline-block font-semibold text-foreground underline underline-offset-4 decoration-current/20 wrap-anywhere"
|
|
94
|
+
href="#"
|
|
95
|
+
onClick={event => {
|
|
96
|
+
event.preventDefault()
|
|
97
|
+
void window.NASTECHDesktop?.openExternal(mediaExternalUrl(image))
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
{copy.openImage}: {mediaName(image)}
|
|
101
|
+
</a>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<>
|
|
107
|
+
<span
|
|
108
|
+
aria-label={pending ? t.assistant.tool.renderingImage : undefined}
|
|
109
|
+
aria-live={pending ? 'polite' : undefined}
|
|
110
|
+
className="group/image relative block max-w-full overflow-hidden rounded-2xl transition-[width,height] duration-300 ease-out"
|
|
111
|
+
data-slot="aui_generated-image"
|
|
112
|
+
role={pending ? 'status' : undefined}
|
|
113
|
+
style={{
|
|
114
|
+
aspectRatio: ratio,
|
|
115
|
+
// Width is capped so the derived height (width / ratio) never exceeds
|
|
116
|
+
// --image-preview-height; the box then matches the image exactly with
|
|
117
|
+
// no letterboxing.
|
|
118
|
+
width: `min(calc(var(--image-preview-height) * ${ratio}), var(--image-preview-max-width), 100%)`
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
{!canvasGone && (
|
|
122
|
+
<div
|
|
123
|
+
className={cn('absolute inset-0 transition-opacity duration-500 ease-out', loaded && 'opacity-0')}
|
|
124
|
+
onTransitionEnd={() => loaded && setCanvasGone(true)}
|
|
125
|
+
>
|
|
126
|
+
<DiffusionCanvas />
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
{src && (
|
|
130
|
+
<button
|
|
131
|
+
className="absolute inset-0 block size-full cursor-zoom-in"
|
|
132
|
+
onClick={() => setLightboxOpen(true)}
|
|
133
|
+
title={copy.openImage}
|
|
134
|
+
type="button"
|
|
135
|
+
>
|
|
136
|
+
<img
|
|
137
|
+
alt="Generated image"
|
|
138
|
+
className={cn(
|
|
139
|
+
'absolute inset-0 size-full object-contain opacity-0 transition-opacity duration-500 ease-out',
|
|
140
|
+
loaded && 'opacity-100'
|
|
141
|
+
)}
|
|
142
|
+
draggable={false}
|
|
143
|
+
onError={() => setFailed(true)}
|
|
144
|
+
onLoad={event => {
|
|
145
|
+
const { naturalHeight, naturalWidth } = event.currentTarget
|
|
146
|
+
|
|
147
|
+
if (naturalWidth && naturalHeight) {
|
|
148
|
+
setRatio(naturalWidth / naturalHeight)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
setLoaded(true)
|
|
152
|
+
}}
|
|
153
|
+
src={src}
|
|
154
|
+
/>
|
|
155
|
+
</button>
|
|
156
|
+
)}
|
|
157
|
+
{loaded && src && (
|
|
158
|
+
<ImageActionButton className="group-hover/image:opacity-100" copy={copy} onClick={download} saving={saving} />
|
|
159
|
+
)}
|
|
160
|
+
</span>
|
|
161
|
+
{src && (
|
|
162
|
+
<ImageLightbox
|
|
163
|
+
alt="Generated image"
|
|
164
|
+
copy={copy}
|
|
165
|
+
onClick={download}
|
|
166
|
+
onOpenChange={setLightboxOpen}
|
|
167
|
+
open={lightboxOpen}
|
|
168
|
+
saving={saving}
|
|
169
|
+
src={src}
|
|
170
|
+
/>
|
|
171
|
+
)}
|
|
172
|
+
</>
|
|
173
|
+
)
|
|
174
|
+
}
|