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,389 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { Unstable_DirectiveFormatter, Unstable_DirectiveSegment, Unstable_TriggerItem } from '@assistant-ui/core'
|
|
4
|
+
import type { TextMessagePartComponent, TextMessagePartProps } from '@assistant-ui/react'
|
|
5
|
+
import type { FC } from 'react'
|
|
6
|
+
import { Fragment, useEffect, useMemo, useState } from 'react'
|
|
7
|
+
|
|
8
|
+
import { ZoomableImage } from '@/components/chat/zoomable-image'
|
|
9
|
+
import { extractEmbeddedImages } from '@/lib/embedded-images'
|
|
10
|
+
|
|
11
|
+
const NASTECH_REF_TYPES = ['file', 'folder', 'url', 'image', 'tool', 'line', 'terminal', 'session'] as const
|
|
12
|
+
type NasTechRefType = (typeof NASTECH_REF_TYPES)[number]
|
|
13
|
+
|
|
14
|
+
/** Single source of truth for chip icon glyphs (Tabler outline @ 24×24).
|
|
15
|
+
* Used both by the rendered <DirectiveIcon> and the raw SVG markup the
|
|
16
|
+
* contenteditable composer embeds via `directiveIconSvg`. */
|
|
17
|
+
const ICON_PATHS: Record<NasTechRefType, string[]> = {
|
|
18
|
+
file: [
|
|
19
|
+
'M14 3v4a1 1 0 0 0 1 1h4',
|
|
20
|
+
'M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2',
|
|
21
|
+
'M9 9l1 0',
|
|
22
|
+
'M9 13l6 0',
|
|
23
|
+
'M9 17l6 0'
|
|
24
|
+
],
|
|
25
|
+
folder: [
|
|
26
|
+
'M5 19l2.757 -7.351a1 1 0 0 1 .936 -.649h12.307a1 1 0 0 1 .986 1.164l-.996 5.211a2 2 0 0 1 -1.964 1.625h-14.026a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v2'
|
|
27
|
+
],
|
|
28
|
+
url: [
|
|
29
|
+
'M9 15l6 -6',
|
|
30
|
+
'M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464',
|
|
31
|
+
'M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463'
|
|
32
|
+
],
|
|
33
|
+
image: [
|
|
34
|
+
'M15 8h.01',
|
|
35
|
+
'M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12',
|
|
36
|
+
'M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5',
|
|
37
|
+
'M14 14l1 -1c.928 -.893 2.072 -.893 3 0l3 3'
|
|
38
|
+
],
|
|
39
|
+
tool: ['M7 10h3v-3l-3.5 -3.5a6 6 0 0 1 8 8l6 6a2 2 0 0 1 -3 3l-6 -6a6 6 0 0 1 -8 -8l3.5 3.5'],
|
|
40
|
+
line: ['M5 9l14 0', 'M5 15l14 0', 'M11 4l-4 16', 'M17 4l-4 16'],
|
|
41
|
+
terminal: ['M5 7l5 5l-5 5', 'M12 19l7 0'],
|
|
42
|
+
session: [
|
|
43
|
+
'M8 9h8',
|
|
44
|
+
'M8 13h6',
|
|
45
|
+
'M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3z'
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const ICON_FALLBACK = ['M8 12a4 4 0 1 0 8 0a4 4 0 1 0 -8 0', 'M16 12v1.5a2.5 2.5 0 0 0 5 0v-1.5a9 9 0 1 0 -5.5 8.28']
|
|
50
|
+
|
|
51
|
+
const SVG_ATTRS =
|
|
52
|
+
'xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"'
|
|
53
|
+
|
|
54
|
+
const iconPathsFor = (type: string) => ICON_PATHS[type as NasTechRefType] ?? ICON_FALLBACK
|
|
55
|
+
|
|
56
|
+
/** SVG markup string for embedding directly in HTML (composer contenteditable). */
|
|
57
|
+
export function directiveIconSvg(type: string) {
|
|
58
|
+
const inner = iconPathsFor(type)
|
|
59
|
+
.map(d => `<path d="${d}"/>`)
|
|
60
|
+
.join('')
|
|
61
|
+
|
|
62
|
+
return `<svg ${SVG_ATTRS} class="size-3 shrink-0 opacity-80">${inner}</svg>`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function directiveIconElement(type: string) {
|
|
66
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
|
67
|
+
svg.setAttribute('class', 'size-3 shrink-0 opacity-80')
|
|
68
|
+
svg.setAttribute('fill', 'none')
|
|
69
|
+
svg.setAttribute('stroke', 'currentColor')
|
|
70
|
+
svg.setAttribute('stroke-linecap', 'round')
|
|
71
|
+
svg.setAttribute('stroke-linejoin', 'round')
|
|
72
|
+
svg.setAttribute('stroke-width', '2')
|
|
73
|
+
svg.setAttribute('viewBox', '0 0 24 24')
|
|
74
|
+
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
|
|
75
|
+
|
|
76
|
+
for (const d of iconPathsFor(type)) {
|
|
77
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
|
|
78
|
+
path.setAttribute('d', d)
|
|
79
|
+
svg.append(path)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return svg
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const DirectiveIcon: FC<{ type: string }> = ({ type }) => (
|
|
86
|
+
<svg
|
|
87
|
+
className="size-3 shrink-0 opacity-80"
|
|
88
|
+
fill="none"
|
|
89
|
+
stroke="currentColor"
|
|
90
|
+
strokeLinecap="round"
|
|
91
|
+
strokeLinejoin="round"
|
|
92
|
+
strokeWidth={2}
|
|
93
|
+
viewBox="0 0 24 24"
|
|
94
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
95
|
+
>
|
|
96
|
+
{iconPathsFor(type).map(d => (
|
|
97
|
+
<path d={d} key={d} />
|
|
98
|
+
))}
|
|
99
|
+
</svg>
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
/** Shared chip styling — used by both the rendered <DirectiveChip> and the
|
|
103
|
+
* raw HTML composer chips in `rich-editor.ts`. Neutral subtle wash + plain
|
|
104
|
+
* muted-foreground text so chips read as quiet tags on any bubble color. */
|
|
105
|
+
export const DIRECTIVE_CHIP_CLASS =
|
|
106
|
+
'mx-0.5 inline-flex max-w-56 items-center gap-1 rounded px-1.5 py-0.5 align-middle text-[0.86em] font-normal leading-none bg-[color-mix(in_srgb,currentColor_8%,transparent)] text-muted-foreground'
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Parses our composer's `@type:value` references into directive segments
|
|
110
|
+
* so they render as inline chips in user messages instead of raw text.
|
|
111
|
+
*
|
|
112
|
+
* Supported types: file, folder, url, image. Anything else stays plain text.
|
|
113
|
+
*
|
|
114
|
+
* Mirrors the Python `agent/context_references.REFERENCE_PATTERN` syntax:
|
|
115
|
+
* the value may be wrapped in backticks, single quotes, or double quotes so
|
|
116
|
+
* paths with spaces/parens/etc. survive parsing intact.
|
|
117
|
+
*/
|
|
118
|
+
const CANONICAL_DIRECTIVE_RE = /:([\w-]{1,64})\[([^\]\n]{1,1024})\](?:\{name=([^}\n]{1,1024})\})?/g
|
|
119
|
+
|
|
120
|
+
const NASTECH_DIRECTIVE_RE = new RegExp(
|
|
121
|
+
'@(file|folder|url|image|tool|line|terminal|session):(' + '`[^`\\n]+`' + '|"[^"\\n]+"' + "|'[^'\\n]+'" + '|\\S+' + ')',
|
|
122
|
+
'g'
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const TRAILING_PUNCTUATION_RE = /[,.;!?]+$/
|
|
126
|
+
|
|
127
|
+
function unwrapRefValue(raw: string): string {
|
|
128
|
+
if (raw.length < 2) {
|
|
129
|
+
return raw
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const head = raw[0]
|
|
133
|
+
const tail = raw[raw.length - 1]
|
|
134
|
+
|
|
135
|
+
if ((head === '`' && tail === '`') || (head === '"' && tail === '"') || (head === "'" && tail === "'")) {
|
|
136
|
+
return raw.slice(1, -1)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return raw.replace(TRAILING_PUNCTUATION_RE, '')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function needsQuoting(value: string): boolean {
|
|
143
|
+
return /[\s()[\]{}<>"'`]/.test(value)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function formatRefValue(value: string): string {
|
|
147
|
+
if (!needsQuoting(value)) {
|
|
148
|
+
return value
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!value.includes('`')) {
|
|
152
|
+
return `\`${value}\``
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!value.includes('"')) {
|
|
156
|
+
return `"${value}"`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!value.includes("'")) {
|
|
160
|
+
return `'${value}'`
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return value
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const NASTECHDirectiveFormatter: Unstable_DirectiveFormatter = {
|
|
167
|
+
serialize(item: Unstable_TriggerItem): string {
|
|
168
|
+
const metadata = item.metadata as { rawText?: unknown; insertId?: unknown } | undefined
|
|
169
|
+
const rawText = typeof metadata?.rawText === 'string' ? metadata.rawText : null
|
|
170
|
+
const insertId = typeof metadata?.insertId === 'string' ? metadata.insertId : null
|
|
171
|
+
|
|
172
|
+
// Live-completion items carry the gateway's original `text` field via metadata.
|
|
173
|
+
if (rawText) {
|
|
174
|
+
// Palette starters (`@file:` with empty value) — insert verbatim so the
|
|
175
|
+
// user can keep typing the path inline.
|
|
176
|
+
if (rawText.endsWith(':') && !insertId) {
|
|
177
|
+
return rawText
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Simple references like `@diff` / `@staged`.
|
|
181
|
+
if (!insertId) {
|
|
182
|
+
return rawText
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Typed references with a value — quote when needed.
|
|
186
|
+
const kindMatch = rawText.match(/^@([^:]+):/)
|
|
187
|
+
const kind = kindMatch?.[1] ?? item.type
|
|
188
|
+
|
|
189
|
+
return `@${kind}:${formatRefValue(insertId)}`
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Fallback for legacy callers that pass raw `id` strings.
|
|
193
|
+
if (item.id === `${item.type}:`) {
|
|
194
|
+
return `@${item.id}`
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return `@${item.type}:${formatRefValue(item.id)}`
|
|
198
|
+
},
|
|
199
|
+
parse(text: string): readonly Unstable_DirectiveSegment[] {
|
|
200
|
+
return parseDirectiveText(text)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseDirectiveText(text: string): Unstable_DirectiveSegment[] {
|
|
205
|
+
const matches = [
|
|
206
|
+
...Array.from(text.matchAll(CANONICAL_DIRECTIVE_RE)).map(match => ({
|
|
207
|
+
start: match.index ?? 0,
|
|
208
|
+
end: (match.index ?? 0) + match[0].length,
|
|
209
|
+
type: match[1] || 'tool',
|
|
210
|
+
label: match[2] || match[3] || '',
|
|
211
|
+
id: match[3] || match[2] || ''
|
|
212
|
+
})),
|
|
213
|
+
...Array.from(text.matchAll(NASTECH_DIRECTIVE_RE)).map(match => {
|
|
214
|
+
const id = unwrapRefValue(match[2] || '')
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
start: match.index ?? 0,
|
|
218
|
+
end: (match.index ?? 0) + match[0].length,
|
|
219
|
+
type: match[1] || 'file',
|
|
220
|
+
label: shortLabel(match[1] as NasTechRefType, id),
|
|
221
|
+
id
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
]
|
|
225
|
+
.filter(match => match.id)
|
|
226
|
+
.sort((a, b) => a.start - b.start)
|
|
227
|
+
|
|
228
|
+
const segments: Unstable_DirectiveSegment[] = []
|
|
229
|
+
let cursor = 0
|
|
230
|
+
|
|
231
|
+
for (const match of matches) {
|
|
232
|
+
if (match.start < cursor) {
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (match.start > cursor) {
|
|
237
|
+
segments.push({ kind: 'text', text: text.slice(cursor, match.start) })
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
segments.push({
|
|
241
|
+
kind: 'mention',
|
|
242
|
+
type: match.type,
|
|
243
|
+
label: match.label,
|
|
244
|
+
id: match.id
|
|
245
|
+
})
|
|
246
|
+
cursor = match.end
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (cursor < text.length) {
|
|
250
|
+
segments.push({ kind: 'text', text: text.slice(cursor) })
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return segments
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function shortLabel(type: NasTechRefType, id: string): string {
|
|
257
|
+
if (type === 'terminal') {
|
|
258
|
+
return id || 'terminal'
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (type === 'url') {
|
|
262
|
+
try {
|
|
263
|
+
const parsed = new URL(id)
|
|
264
|
+
|
|
265
|
+
return parsed.hostname || id
|
|
266
|
+
} catch {
|
|
267
|
+
return id
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// `@session:<profile>/<id>` — show a short id; the composer chip carries the
|
|
272
|
+
// friendly title, but once sent the wire form only has the id.
|
|
273
|
+
if (type === 'session') {
|
|
274
|
+
const sid = id.split('/').filter(Boolean).pop() || id
|
|
275
|
+
|
|
276
|
+
return sid.length > 10 ? `${sid.slice(0, 8)}…` : sid
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const tail = id.split(/[\\/]/).filter(Boolean).pop()
|
|
280
|
+
|
|
281
|
+
return tail || id
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Renders text containing NasTech directives (`@file:...`, `@image:...`) as
|
|
286
|
+
* inline chips. Embedded MEDIA images render below as a thumbnail row.
|
|
287
|
+
*/
|
|
288
|
+
export function DirectiveContent({ text }: { text: string }) {
|
|
289
|
+
const { cleanedText, images } = useMemo(() => extractEmbeddedImages(text ?? ''), [text])
|
|
290
|
+
const segments = useMemo(() => NASTECHDirectiveFormatter.parse(cleanedText), [cleanedText])
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<span className="whitespace-pre-line" data-slot="aui_directive-text">
|
|
294
|
+
{segments.map((segment, index) =>
|
|
295
|
+
segment.kind === 'text' ? (
|
|
296
|
+
<Fragment key={`t-${index}`}>{segment.text}</Fragment>
|
|
297
|
+
) : segment.type === 'image' ? (
|
|
298
|
+
<DirectiveImage id={segment.id} key={`img-${index}-${segment.id}`} label={segment.label} />
|
|
299
|
+
) : (
|
|
300
|
+
<DirectiveChip id={segment.id} key={`m-${index}-${segment.id}`} label={segment.label} type={segment.type} />
|
|
301
|
+
)
|
|
302
|
+
)}
|
|
303
|
+
{images.length > 0 && (
|
|
304
|
+
<span className="mt-2 flex flex-wrap gap-2" data-slot="aui_embedded-images">
|
|
305
|
+
{images.map((src, index) => (
|
|
306
|
+
<ZoomableImage
|
|
307
|
+
alt=""
|
|
308
|
+
className="max-h-48 max-w-full rounded-lg border border-border/60 object-contain"
|
|
309
|
+
draggable={false}
|
|
310
|
+
key={`img-${index}`}
|
|
311
|
+
slot="aui_embedded-image"
|
|
312
|
+
src={src}
|
|
313
|
+
/>
|
|
314
|
+
))}
|
|
315
|
+
</span>
|
|
316
|
+
)}
|
|
317
|
+
</span>
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** assistant-ui adapter: same renderer, exposed as a TextMessagePartComponent. */
|
|
322
|
+
export const DirectiveText: TextMessagePartComponent = ({ text }: TextMessagePartProps) => (
|
|
323
|
+
<DirectiveContent text={text ?? ''} />
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
/** Image refs render as a thumbnail rather than a chip — matches how persisted
|
|
327
|
+
* messages render after the backend embeds the data URL, so the UX is stable
|
|
328
|
+
* across initial send and refresh. */
|
|
329
|
+
const DirectiveImage: FC<{ id: string; label: string }> = ({ id, label }) => {
|
|
330
|
+
const remote = /^(?:https?|data):/i.test(id)
|
|
331
|
+
const [src, setSrc] = useState<string | null>(remote ? id : null)
|
|
332
|
+
const [failed, setFailed] = useState(false)
|
|
333
|
+
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (remote || !id) {
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let alive = true
|
|
340
|
+
void window.NASTECHDesktop
|
|
341
|
+
?.readFileDataUrl(id)
|
|
342
|
+
.then(url => alive && setSrc(url))
|
|
343
|
+
.catch(() => alive && setFailed(true))
|
|
344
|
+
|
|
345
|
+
return () => {
|
|
346
|
+
alive = false
|
|
347
|
+
}
|
|
348
|
+
}, [id, remote])
|
|
349
|
+
|
|
350
|
+
if (failed) {
|
|
351
|
+
return <DirectiveChip id={id} label={label} type="image" />
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!src) {
|
|
355
|
+
return (
|
|
356
|
+
<span
|
|
357
|
+
aria-hidden
|
|
358
|
+
className="inline-block size-12 shrink-0 animate-pulse rounded-md bg-[color-mix(in_srgb,currentColor_8%,transparent)]"
|
|
359
|
+
/>
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return (
|
|
364
|
+
<ZoomableImage
|
|
365
|
+
alt={label}
|
|
366
|
+
className="max-h-32 max-w-48 rounded-md border border-border/40 object-contain"
|
|
367
|
+
draggable={false}
|
|
368
|
+
slot="aui_directive-image"
|
|
369
|
+
src={src}
|
|
370
|
+
/>
|
|
371
|
+
)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const DirectiveChip: FC<{
|
|
375
|
+
type: string
|
|
376
|
+
label: string
|
|
377
|
+
id: string
|
|
378
|
+
}> = ({ type, label, id }) => (
|
|
379
|
+
<span
|
|
380
|
+
className={DIRECTIVE_CHIP_CLASS}
|
|
381
|
+
data-directive-id={id}
|
|
382
|
+
data-directive-type={type}
|
|
383
|
+
data-slot="aui_directive-chip"
|
|
384
|
+
title={id}
|
|
385
|
+
>
|
|
386
|
+
<DirectiveIcon type={type} />
|
|
387
|
+
<span className="truncate">{label}</span>
|
|
388
|
+
</span>
|
|
389
|
+
)
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { preprocessMarkdown } from '@/lib/markdown-preprocess'
|
|
4
|
+
|
|
5
|
+
describe('preprocessMarkdown', () => {
|
|
6
|
+
it('strips inline accidental triple-backtick starts', () => {
|
|
7
|
+
const input = [
|
|
8
|
+
'Working as intended.',
|
|
9
|
+
"Here's your scene: ``` http://localhost:8812/",
|
|
10
|
+
'',
|
|
11
|
+
'- **Multicolored cube**',
|
|
12
|
+
'- **Rotates**'
|
|
13
|
+
].join('\n')
|
|
14
|
+
|
|
15
|
+
const output = preprocessMarkdown(input)
|
|
16
|
+
|
|
17
|
+
expect(output).not.toContain('```')
|
|
18
|
+
expect(output).toContain("Here's your scene:")
|
|
19
|
+
expect(output).not.toContain('http://localhost:8812/')
|
|
20
|
+
expect(output).toContain('- **Multicolored cube**')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('demotes invalid fenced prose blocks with closers', () => {
|
|
24
|
+
const fence = '```'
|
|
25
|
+
|
|
26
|
+
const input = [
|
|
27
|
+
`${fence} http://localhost:8812/`,
|
|
28
|
+
'- **Scroll wheel** - zoom',
|
|
29
|
+
'- **Right-drag/pan** - disabled',
|
|
30
|
+
fence
|
|
31
|
+
].join('\n')
|
|
32
|
+
|
|
33
|
+
const output = preprocessMarkdown(input)
|
|
34
|
+
|
|
35
|
+
expect(output).not.toContain('```')
|
|
36
|
+
expect(output).not.toContain('http://localhost:8812/')
|
|
37
|
+
expect(output).toContain('- **Scroll wheel** - zoom')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('drops fences around a preview-only URL block', () => {
|
|
41
|
+
const fence = '```'
|
|
42
|
+
const input = ['Server is back.', '', fence, 'http://localhost:8812/', fence].join('\n')
|
|
43
|
+
|
|
44
|
+
const output = preprocessMarkdown(input)
|
|
45
|
+
|
|
46
|
+
expect(output).toContain('Server is back.')
|
|
47
|
+
expect(output).not.toContain('```')
|
|
48
|
+
expect(output).not.toContain('http://localhost:8812/')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('demotes prose sentence masquerading as fence info', () => {
|
|
52
|
+
const input = ['```Heads up - a bunny got added', '- Pure white (`#ffffff`)', '- Ambient dropped to 0.18'].join(
|
|
53
|
+
'\n'
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const output = preprocessMarkdown(input)
|
|
57
|
+
|
|
58
|
+
expect(output).not.toContain('```heads')
|
|
59
|
+
expect(output).toContain('Heads up - a bunny got added')
|
|
60
|
+
expect(output).toContain('- Pure white (`#ffffff`)')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('keeps valid code fences intact', () => {
|
|
64
|
+
const fence = '```'
|
|
65
|
+
const input = [`${fence}ts`, 'const value = 1;', fence].join('\n')
|
|
66
|
+
|
|
67
|
+
const output = preprocessMarkdown(input)
|
|
68
|
+
|
|
69
|
+
expect(output).toContain('```ts')
|
|
70
|
+
expect(output).toContain('const value = 1;')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('keeps dangling real code fences during streaming', () => {
|
|
74
|
+
const input = ['```ts', 'const value = 1;'].join('\n')
|
|
75
|
+
const output = preprocessMarkdown(input)
|
|
76
|
+
|
|
77
|
+
expect(output.startsWith('```ts')).toBe(true)
|
|
78
|
+
expect(output).toContain('const value = 1;')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('demotes dangling prose fences', () => {
|
|
82
|
+
const input = ['```', '- Pure white (`#ffffff`)', '- Ambient dropped to 0.18'].join('\n')
|
|
83
|
+
const output = preprocessMarkdown(input)
|
|
84
|
+
|
|
85
|
+
expect(output).not.toContain('```')
|
|
86
|
+
expect(output).toContain('- Pure white (`#ffffff`)')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('autolinks raw urls in prose', () => {
|
|
90
|
+
const output = preprocessMarkdown(
|
|
91
|
+
'Book here:\nhttps://www.getyourguide.com/culebra-island-l145468/from-fajardo-tour-t19894/'
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
expect(output).toContain('<https://www.getyourguide.com/culebra-island-l145468/from-fajardo-tour-t19894/>')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('strips orphan numeric citation markers outside code spans', () => {
|
|
98
|
+
const output = preprocessMarkdown('This is the source[0], but keep `items[0]` untouched.')
|
|
99
|
+
|
|
100
|
+
expect(output).toContain('source,')
|
|
101
|
+
expect(output).not.toContain('source[0]')
|
|
102
|
+
expect(output).toContain('`items[0]`')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('demotes title/url blocks wrapped in malformed inline fences', () => {
|
|
106
|
+
const input = [
|
|
107
|
+
'**🚢 TOMORROW (Fajardo, crystal clear cays, pickup avail):**',
|
|
108
|
+
'',
|
|
109
|
+
'Icacos Full-Day Catamaran — 6hr, $140, small group, pickup```',
|
|
110
|
+
'https://www.getyourguide.com/fajardo-l882/from-fajardo-icacos-island-full-day-catamaran-trip-t19891/',
|
|
111
|
+
'```Sail Getaway Luxury Cat (Cordillera Cays, water slide, unlimited rum) — 6hr, $195```',
|
|
112
|
+
'https://www.getyourguide.com/fajardo-l882/icacos-all-inclusive-sailing-catamaran-beach-and-snorkel-t466138/'
|
|
113
|
+
].join('\n')
|
|
114
|
+
|
|
115
|
+
const output = preprocessMarkdown(input)
|
|
116
|
+
|
|
117
|
+
expect(output).not.toContain('```')
|
|
118
|
+
expect(output).toContain('Sail Getaway Luxury Cat')
|
|
119
|
+
expect(output).toContain(
|
|
120
|
+
'<https://www.getyourguide.com/fajardo-l882/from-fajardo-icacos-island-full-day-catamaran-trip-t19891/>'
|
|
121
|
+
)
|
|
122
|
+
expect(output).toContain(
|
|
123
|
+
'<https://www.getyourguide.com/fajardo-l882/icacos-all-inclusive-sailing-catamaran-beach-and-snorkel-t466138/>'
|
|
124
|
+
)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('autolinks urls glued to prices and removes orphan fence tails', () => {
|
|
128
|
+
const input = [
|
|
129
|
+
'**🐢 TODAY (from San Juan, no driving):**',
|
|
130
|
+
'',
|
|
131
|
+
'Sea Turtles & Manatees Snorkel + Free Rum — 1.5hr,',
|
|
132
|
+
'~$56```https://www.getyourguide.com/san-juan-puerto-rico-l355/san-juan-snorkel-sea-turtles-manatees-free-video-rum-t879147/ Old San Juan Sunset Cruise w/ Drinks + Hotel Pickup — 1.5hr, ~$99 (drinks, no snorkel)```',
|
|
133
|
+
'https://www.getyourguide.com/en-gb/san-juan-puerto-rico-l355/san-juan-old-san-juan-sunset-cruise-with-drinks-transfer-t405191/'
|
|
134
|
+
].join('\n')
|
|
135
|
+
|
|
136
|
+
const output = preprocessMarkdown(input)
|
|
137
|
+
|
|
138
|
+
expect(output).not.toContain('```')
|
|
139
|
+
// Currency dollar amounts get escaped to `\$` in the preprocessor
|
|
140
|
+
// so they don't get parsed as math delimiters by remark-math (we
|
|
141
|
+
// enable singleDollarTextMath, which would otherwise greedy-match
|
|
142
|
+
// `$56...$99` as one big inline math span). The escape is invisible
|
|
143
|
+
// to the user — `\$` renders as a literal `$` in the final output.
|
|
144
|
+
expect(output).toContain(
|
|
145
|
+
'~\\$56<https://www.getyourguide.com/san-juan-puerto-rico-l355/san-juan-snorkel-sea-turtles-manatees-free-video-rum-t879147/> Old San Juan Sunset Cruise'
|
|
146
|
+
)
|
|
147
|
+
expect(output).toContain(
|
|
148
|
+
'<https://www.getyourguide.com/en-gb/san-juan-puerto-rico-l355/san-juan-old-san-juan-sunset-cruise-with-drinks-transfer-t405191/>'
|
|
149
|
+
)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('demotes url-only fenced blocks to clickable markdown links', () => {
|
|
153
|
+
const input = [
|
|
154
|
+
'Sea Turtles & Manatees Snorkel + Free Rum — 1.5hr, ~$56',
|
|
155
|
+
'```',
|
|
156
|
+
'https://www.getyourguide.com/san-juan-puerto-rico-l355/san-juan-snorkel-sea-turtles-manatees-free-video-rum-t879147/',
|
|
157
|
+
'```',
|
|
158
|
+
'',
|
|
159
|
+
'Old San Juan Sunset Cruise w/ Drinks + Hotel Pickup — 1.5hr, ~$99',
|
|
160
|
+
'```',
|
|
161
|
+
'https://www.getyourguide.com/en-gb/san-juan-puerto-rico-l355/san-juan-old-san-juan-sunset-cruise-with-drinks-transfer-t405191/',
|
|
162
|
+
'```'
|
|
163
|
+
].join('\n')
|
|
164
|
+
|
|
165
|
+
const output = preprocessMarkdown(input)
|
|
166
|
+
|
|
167
|
+
expect(output).not.toContain('```')
|
|
168
|
+
expect(output).toContain(
|
|
169
|
+
'<https://www.getyourguide.com/san-juan-puerto-rico-l355/san-juan-snorkel-sea-turtles-manatees-free-video-rum-t879147/>'
|
|
170
|
+
)
|
|
171
|
+
expect(output).toContain(
|
|
172
|
+
'<https://www.getyourguide.com/en-gb/san-juan-puerto-rico-l355/san-juan-old-san-juan-sunset-cruise-with-drinks-transfer-t405191/>'
|
|
173
|
+
)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('does not swallow trailing emphasis asterisks into an autolinked url', () => {
|
|
177
|
+
const input = '**PR opened: https://github.com/nastech-ai/NasTech-Agent/pull/12345**'
|
|
178
|
+
|
|
179
|
+
const output = preprocessMarkdown(input)
|
|
180
|
+
|
|
181
|
+
// The URL is autolinked WITHOUT the trailing `**` glued into the href,
|
|
182
|
+
// and the bold emphasis run stays intact so it renders as bold + a link.
|
|
183
|
+
expect(output).toContain('<https://github.com/nastech-ai/NasTech-Agent/pull/12345>')
|
|
184
|
+
expect(output).not.toContain('pull/12345**>')
|
|
185
|
+
expect(output).not.toContain('12345*')
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('stops an autolinked url at mid-string bold markers', () => {
|
|
189
|
+
const input = 'See https://github.com/foo/bar**bold** for details.'
|
|
190
|
+
|
|
191
|
+
const output = preprocessMarkdown(input)
|
|
192
|
+
|
|
193
|
+
expect(output).toContain('<https://github.com/foo/bar>')
|
|
194
|
+
expect(output).toContain('**bold**')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('keeps underscores and tildes inside autolinked url paths', () => {
|
|
198
|
+
const input = 'Docs at https://example.com/a_b/c~d/page'
|
|
199
|
+
|
|
200
|
+
const output = preprocessMarkdown(input)
|
|
201
|
+
|
|
202
|
+
expect(output).toContain('<https://example.com/a_b/c~d/page>')
|
|
203
|
+
})
|
|
204
|
+
})
|