@invect/ui 0.0.1
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/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/Invect-CWpIwZ5F.js +92738 -0
- package/dist/Invect.d.ts +25 -0
- package/dist/InvectShell.d.ts +14 -0
- package/dist/api/agent-tools.api.d.ts +1 -0
- package/dist/api/client.d.ts +207 -0
- package/dist/api/credentials.api.d.ts +47 -0
- package/dist/api/executions.api.d.ts +43 -0
- package/dist/api/flows.api.d.ts +100 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/node-data.api.d.ts +66 -0
- package/dist/api/query-keys.d.ts +22 -0
- package/dist/api/triggers.api.d.ts +44 -0
- package/dist/api/types.d.ts +147 -0
- package/dist/api/use-flow-run-stream.d.ts +12 -0
- package/dist/assets/invect-branding.d.ts +4 -0
- package/dist/assets/provider-icons/index.d.ts +8 -0
- package/dist/babel-C9OtljFZ.js +9721 -0
- package/dist/components/PageLayout.d.ts +17 -0
- package/dist/components/chat/ChatInput.d.ts +17 -0
- package/dist/components/chat/ChatMessageList.d.ts +14 -0
- package/dist/components/chat/ChatModelSelector.d.ts +11 -0
- package/dist/components/chat/ChatPanel.d.ts +19 -0
- package/dist/components/chat/ChatPromptOverlay.d.ts +13 -0
- package/dist/components/chat/ChatProviderSelector.d.ts +9 -0
- package/dist/components/chat/ChatSettingsPanel.d.ts +11 -0
- package/dist/components/chat/InlineCredentialSetup.d.ts +9 -0
- package/dist/components/chat/MarkdownRenderer.d.ts +7 -0
- package/dist/components/chat/chat-memory.d.ts +21 -0
- package/dist/components/chat/chat.store.d.ts +416 -0
- package/dist/components/chat/index.d.ts +5 -0
- package/dist/components/chat/use-chat.d.ts +28 -0
- package/dist/components/credentials/CreateCredentialModal.d.ts +13 -0
- package/dist/components/credentials/CredentialDetailDialog.d.ts +17 -0
- package/dist/components/credentials/EditCredentialModal.d.ts +11 -0
- package/dist/components/credentials/OAuth2ConnectButton.d.ts +38 -0
- package/dist/components/credentials/OAuth2ProviderSelector.d.ts +15 -0
- package/dist/components/credentials/credential-utils.d.ts +12 -0
- package/dist/components/credentials/index.d.ts +9 -0
- package/dist/components/dashboard/FailedRunsAlert.d.ts +3 -0
- package/dist/components/dashboard/FlowCard.d.ts +7 -0
- package/dist/components/dashboard/RecentActivityTable.d.ts +9 -0
- package/dist/components/dashboard/StatCard.d.ts +10 -0
- package/dist/components/dashboard/index.d.ts +5 -0
- package/dist/components/dashboard/status-helpers.d.ts +5 -0
- package/dist/components/flow-editor/ActionsSidebar.d.ts +2 -0
- package/dist/components/flow-editor/FlowEditor.d.ts +21 -0
- package/dist/components/flow-editor/FlowHeader.d.ts +9 -0
- package/dist/components/flow-editor/FlowLayout.d.ts +24 -0
- package/dist/components/flow-editor/ModeSwitcher.d.ts +7 -0
- package/dist/components/flow-editor/NodeSidebar.d.ts +24 -0
- package/dist/components/flow-editor/RunControls.d.ts +12 -0
- package/dist/components/flow-editor/ToolConfigPanel.d.ts +16 -0
- package/dist/components/flow-editor/ValidationPanel.d.ts +5 -0
- package/dist/components/flow-editor/flow-editor.store.d.ts +1 -0
- package/dist/components/flow-editor/index.d.ts +6 -0
- package/dist/components/flow-editor/inline-edit.d.ts +10 -0
- package/dist/components/flow-editor/node-config-panel/ConfigFieldWithTemplate.d.ts +26 -0
- package/dist/components/flow-editor/node-config-panel/CredentialCombobox.d.ts +21 -0
- package/dist/components/flow-editor/node-config-panel/CredentialsSection.d.ts +19 -0
- package/dist/components/flow-editor/node-config-panel/DroppableInput.d.ts +20 -0
- package/dist/components/flow-editor/node-config-panel/DynamicSelectField.d.ts +22 -0
- package/dist/components/flow-editor/node-config-panel/JsonPreviewPanel.d.ts +25 -0
- package/dist/components/flow-editor/node-config-panel/NodeConfigPanel.d.ts +14 -0
- package/dist/components/flow-editor/node-config-panel/NodeConfigPanelHeader.d.ts +15 -0
- package/dist/components/flow-editor/node-config-panel/ParametersSection.d.ts +16 -0
- package/dist/components/flow-editor/node-config-panel/SearchableSelectField.d.ts +17 -0
- package/dist/components/flow-editor/node-config-panel/SwitchCasesField.d.ts +18 -0
- package/dist/components/flow-editor/node-config-panel/hooks/index.d.ts +2 -0
- package/dist/components/flow-editor/node-config-panel/hooks/use-node-config-panel-state.d.ts +24 -0
- package/dist/components/flow-editor/node-config-panel/hooks/use-node-execution.d.ts +46 -0
- package/dist/components/flow-editor/node-config-panel/hooks/use-upstream-slots.d.ts +16 -0
- package/dist/components/flow-editor/node-config-panel/panels/AgentToolsPanel.d.ts +18 -0
- package/dist/components/flow-editor/node-config-panel/panels/ConfigurationPanel.d.ts +49 -0
- package/dist/components/flow-editor/node-config-panel/panels/DataMapperPane.d.ts +40 -0
- package/dist/components/flow-editor/node-config-panel/panels/InputPanel.d.ts +49 -0
- package/dist/components/flow-editor/node-config-panel/panels/OutputPanel.d.ts +7 -0
- package/dist/components/flow-editor/node-config-panel/panels/index.d.ts +4 -0
- package/dist/components/flow-editor/node-config-panel/types.d.ts +19 -0
- package/dist/components/flow-editor/node-config-panel/use-node-config-panel-store.d.ts +49 -0
- package/dist/components/flow-editor/node-config-panel/use-node-config-state.d.ts +26 -0
- package/dist/components/flow-editor/node-config-panel/use-run-node.d.ts +16 -0
- package/dist/components/flow-editor/node-config-panel/utils.d.ts +9 -0
- package/dist/components/flow-editor/serialize-to-sdk.d.ts +20 -0
- package/dist/components/flow-editor/toolbar-context.d.ts +2 -0
- package/dist/components/flow-editor/use-copy-paste.d.ts +7 -0
- package/dist/components/flow-editor/use-copy-paste.types.d.ts +38 -0
- package/dist/components/flow-editor/use-flow-editor.d.ts +44 -0
- package/dist/components/flow-runs-table/FlowRunsTable.d.ts +6 -0
- package/dist/components/flow-runs-table/index.d.ts +1 -0
- package/dist/components/flow-viewer/FlowRunsView.d.ts +7 -0
- package/dist/components/flow-viewer/FlowStatusView.d.ts +21 -0
- package/dist/components/flow-viewer/RunSelector.d.ts +13 -0
- package/dist/components/flow-viewer/RunsSidebar.d.ts +14 -0
- package/dist/components/flow-viewer/agent-tool-executions-list.d.ts +7 -0
- package/dist/components/flow-viewer/index.d.ts +1 -0
- package/dist/components/flow-viewer/logs-panel.d.ts +18 -0
- package/dist/components/flow-viewer/use-execution-log-data.d.ts +113 -0
- package/dist/components/graph/BatchFlowEdge.d.ts +33 -0
- package/dist/components/graph/LayoutSelector.d.ts +9 -0
- package/dist/components/graph/index.d.ts +47 -0
- package/dist/components/graph/styleUtils.d.ts +124 -0
- package/dist/components/nodes/AgentConfigPanel.d.ts +24 -0
- package/dist/components/nodes/AgentNode.d.ts +8 -0
- package/dist/components/nodes/AgentToolsBox.d.ts +41 -0
- package/dist/components/nodes/NodeAppendix.d.ts +19 -0
- package/dist/components/nodes/NodeStatusIndicator.d.ts +30 -0
- package/dist/components/nodes/NodeViewContext.d.ts +18 -0
- package/dist/components/nodes/ToolParamField.d.ts +28 -0
- package/dist/components/nodes/ToolSelectorModal.d.ts +80 -0
- package/dist/components/nodes/ToolSelectorParts.d.ts +30 -0
- package/dist/components/nodes/UniversalNode.d.ts +2 -0
- package/dist/components/nodes/createContextAwareNodes.d.ts +6 -0
- package/dist/components/nodes/index.d.ts +22 -0
- package/dist/components/nodes/nodeRegistry.d.ts +13 -0
- package/dist/components/nodes/withNodeContext.d.ts +7 -0
- package/dist/components/shared/InvectLoader.d.ts +8 -0
- package/dist/components/shared/InvectLogo.d.ts +9 -0
- package/dist/components/shared/ProviderIcon.d.ts +23 -0
- package/dist/components/side-menu/side-menu.d.ts +4 -0
- package/dist/components/sidebar/BaseSidebar.d.ts +17 -0
- package/dist/components/sidebar/index.d.ts +1 -0
- package/dist/components/triggers/CronPreview.d.ts +12 -0
- package/dist/components/triggers/index.d.ts +1 -0
- package/dist/components/ui/alert-dialog.d.ts +18 -0
- package/dist/components/ui/badge.d.ts +9 -0
- package/dist/components/ui/button.d.ts +13 -0
- package/dist/components/ui/card.d.ts +9 -0
- package/dist/components/ui/codemirror-js-editor.d.ts +25 -0
- package/dist/components/ui/codemirror-json-editor.d.ts +18 -0
- package/dist/components/ui/codemirror-nunjucks-editor.d.ts +13 -0
- package/dist/components/ui/codemirror-vscode-theme.d.ts +24 -0
- package/dist/components/ui/collapsible.d.ts +6 -0
- package/dist/components/ui/command.d.ts +18 -0
- package/dist/components/ui/dialog.d.ts +18 -0
- package/dist/components/ui/dropdown-menu.d.ts +25 -0
- package/dist/components/ui/empty-state.d.ts +21 -0
- package/dist/components/ui/input.d.ts +3 -0
- package/dist/components/ui/label.d.ts +4 -0
- package/dist/components/ui/popover.d.ts +10 -0
- package/dist/components/ui/resizable.d.ts +8 -0
- package/dist/components/ui/scroll-area.d.ts +5 -0
- package/dist/components/ui/select.d.ts +18 -0
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/slider.d.ts +4 -0
- package/dist/components/ui/switch.d.ts +3 -0
- package/dist/components/ui/table.d.ts +10 -0
- package/dist/components/ui/textarea.d.ts +3 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/components/ui/tree-view.d.ts +107 -0
- package/dist/contexts/AgentToolCallbacksContext.d.ts +23 -0
- package/dist/contexts/ApiContext.d.ts +11 -0
- package/dist/contexts/FlowDataContext.d.ts +9 -0
- package/dist/contexts/NodeRegistryContext.d.ts +14 -0
- package/dist/contexts/PluginRegistryContext.d.ts +39 -0
- package/dist/contexts/ThemeProvider.d.ts +18 -0
- package/dist/contexts/ValidationContext.d.ts +22 -0
- package/dist/demo/DemoInvect.d.ts +11 -0
- package/dist/demo/FlowViewer.d.ts +31 -0
- package/dist/demo/demo-api-client.d.ts +33 -0
- package/dist/demo/index.d.ts +6 -0
- package/dist/demo/sample-data.d.ts +1538 -0
- package/dist/demo.d.ts +2 -0
- package/dist/demo.js +2774 -0
- package/dist/estree-ClbRfS-1.js +7076 -0
- package/dist/fonts/geist-cyrillic-wght-normal.woff2 +0 -0
- package/dist/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/geist-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/iosevka-latin-400-normal.woff2 +0 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/use-document-title.d.ts +1 -0
- package/dist/hooks/use-flow-data.d.ts +22 -0
- package/dist/hooks/use-invect-portal-class.d.ts +21 -0
- package/dist/hooks/useFlowEditorStore.d.ts +1 -0
- package/dist/index.css +3 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +717 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/prettier.d.ts +13 -0
- package/dist/routes/all-flow-runs.d.ts +5 -0
- package/dist/routes/credentials.d.ts +5 -0
- package/dist/routes/flow-route-layout.d.ts +19 -0
- package/dist/routes/flow-runs.d.ts +5 -0
- package/dist/routes/flow.d.ts +5 -0
- package/dist/routes/home.d.ts +5 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/standalone-C3Df7W52.js +3463 -0
- package/dist/stores/executionViewStore.d.ts +64 -0
- package/dist/stores/flow-editor.store.d.ts +137 -0
- package/dist/stores/flowEditorStore.d.ts +1 -0
- package/dist/stores/index.d.ts +2 -0
- package/dist/stores/uiStore.d.ts +45 -0
- package/dist/types/agent-tools.types.d.ts +53 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/node-definition.types.d.ts +85 -0
- package/dist/types/plugin.types.d.ts +100 -0
- package/dist/utils/credentialBranding.d.ts +8 -0
- package/dist/utils/credentialFiltering.d.ts +20 -0
- package/dist/utils/flowTransformations.d.ts +16 -0
- package/dist/utils/layoutUtils.d.ts +23 -0
- package/dist/utils/nodeReferenceUtils.d.ts +37 -0
- package/dist/vendor.d.ts +5 -0
- package/package.json +130 -0
- package/src/.DS_Store +0 -0
- package/src/Invect.tsx +229 -0
- package/src/InvectShell.tsx +55 -0
- package/src/api/agent-tools.api.ts +23 -0
- package/src/api/client.ts +899 -0
- package/src/api/credentials.api.ts +197 -0
- package/src/api/executions.api.ts +228 -0
- package/src/api/flows.api.ts +195 -0
- package/src/api/index.ts +17 -0
- package/src/api/node-data.api.ts +167 -0
- package/src/api/query-keys.ts +44 -0
- package/src/api/triggers.api.ts +120 -0
- package/src/api/types.ts +212 -0
- package/src/api/use-flow-run-stream.ts +206 -0
- package/src/app.css +560 -0
- package/src/assets/.DS_Store +0 -0
- package/src/assets/favicon.ico +0 -0
- package/src/assets/fonts/geist-cyrillic-wght-normal.woff2 +0 -0
- package/src/assets/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
- package/src/assets/fonts/geist-latin-wght-normal.woff2 +0 -0
- package/src/assets/fonts/iosevka-latin-400-normal.woff2 +0 -0
- package/src/assets/invect-branding.ts +51 -0
- package/src/assets/provider-icons/anthropic.svg +1 -0
- package/src/assets/provider-icons/anthropic_light.svg +1 -0
- package/src/assets/provider-icons/github.svg +1 -0
- package/src/assets/provider-icons/github_light.svg +1 -0
- package/src/assets/provider-icons/gmail.svg +1 -0
- package/src/assets/provider-icons/google_calendar.svg +1 -0
- package/src/assets/provider-icons/google_docs.svg +1 -0
- package/src/assets/provider-icons/google_drive.svg +1 -0
- package/src/assets/provider-icons/google_sheets.svg +1 -0
- package/src/assets/provider-icons/index.ts +55 -0
- package/src/assets/provider-icons/linear.svg +1 -0
- package/src/assets/provider-icons/openai.svg +1 -0
- package/src/assets/provider-icons/postgres.svg +1 -0
- package/src/assets/provider-icons/slack.svg +1 -0
- package/src/assets/small-loader-dark.svg +22 -0
- package/src/assets/small-loader-light.svg +22 -0
- package/src/assets/small.svg +7 -0
- package/src/components/.DS_Store +0 -0
- package/src/components/PageLayout.tsx +55 -0
- package/src/components/chat/ChatInput.tsx +115 -0
- package/src/components/chat/ChatMessageList.tsx +788 -0
- package/src/components/chat/ChatModelSelector.tsx +208 -0
- package/src/components/chat/ChatPanel.tsx +243 -0
- package/src/components/chat/ChatPromptOverlay.tsx +150 -0
- package/src/components/chat/ChatProviderSelector.tsx +135 -0
- package/src/components/chat/ChatSettingsPanel.tsx +277 -0
- package/src/components/chat/InlineCredentialSetup.tsx +343 -0
- package/src/components/chat/MarkdownRenderer.tsx +140 -0
- package/src/components/chat/chat-memory.ts +88 -0
- package/src/components/chat/chat.store.ts +479 -0
- package/src/components/chat/index.ts +5 -0
- package/src/components/chat/use-chat.ts +473 -0
- package/src/components/credentials/CreateCredentialModal.tsx +609 -0
- package/src/components/credentials/CredentialDetailDialog.tsx +882 -0
- package/src/components/credentials/EditCredentialModal.tsx +399 -0
- package/src/components/credentials/OAuth2ConnectButton.tsx +288 -0
- package/src/components/credentials/OAuth2ProviderSelector.tsx +360 -0
- package/src/components/credentials/credential-utils.ts +99 -0
- package/src/components/credentials/index.ts +10 -0
- package/src/components/dashboard/FailedRunsAlert.tsx +67 -0
- package/src/components/dashboard/FlowCard.tsx +64 -0
- package/src/components/dashboard/RecentActivityTable.tsx +92 -0
- package/src/components/dashboard/StatCard.tsx +32 -0
- package/src/components/dashboard/index.ts +5 -0
- package/src/components/dashboard/status-helpers.tsx +102 -0
- package/src/components/flow-editor/ActionsSidebar.tsx +503 -0
- package/src/components/flow-editor/FlowEditor.tsx +1002 -0
- package/src/components/flow-editor/FlowHeader.tsx +87 -0
- package/src/components/flow-editor/FlowLayout.tsx +117 -0
- package/src/components/flow-editor/ModeSwitcher.tsx +49 -0
- package/src/components/flow-editor/NodeSidebar.tsx +343 -0
- package/src/components/flow-editor/RunControls.tsx +109 -0
- package/src/components/flow-editor/ToolConfigPanel.tsx +434 -0
- package/src/components/flow-editor/ValidationPanel.tsx +167 -0
- package/src/components/flow-editor/flow-editor.store.ts +2 -0
- package/src/components/flow-editor/index.ts +6 -0
- package/src/components/flow-editor/inline-edit.tsx +111 -0
- package/src/components/flow-editor/node-config-panel/ConfigFieldWithTemplate.tsx +334 -0
- package/src/components/flow-editor/node-config-panel/CredentialCombobox.tsx +217 -0
- package/src/components/flow-editor/node-config-panel/CredentialsSection.tsx +154 -0
- package/src/components/flow-editor/node-config-panel/DroppableInput.tsx +45 -0
- package/src/components/flow-editor/node-config-panel/DynamicSelectField.tsx +223 -0
- package/src/components/flow-editor/node-config-panel/JsonPreviewPanel.tsx +134 -0
- package/src/components/flow-editor/node-config-panel/NodeConfigPanel.tsx +650 -0
- package/src/components/flow-editor/node-config-panel/NodeConfigPanelHeader.tsx +91 -0
- package/src/components/flow-editor/node-config-panel/ParametersSection.tsx +144 -0
- package/src/components/flow-editor/node-config-panel/SearchableSelectField.tsx +126 -0
- package/src/components/flow-editor/node-config-panel/SwitchCasesField.tsx +212 -0
- package/src/components/flow-editor/node-config-panel/hooks/index.ts +2 -0
- package/src/components/flow-editor/node-config-panel/hooks/use-node-config-panel-state.ts +284 -0
- package/src/components/flow-editor/node-config-panel/hooks/use-node-execution.ts +287 -0
- package/src/components/flow-editor/node-config-panel/hooks/use-upstream-slots.ts +310 -0
- package/src/components/flow-editor/node-config-panel/panels/AgentToolsPanel.tsx +837 -0
- package/src/components/flow-editor/node-config-panel/panels/ConfigurationPanel.tsx +383 -0
- package/src/components/flow-editor/node-config-panel/panels/DataMapperPane.tsx +456 -0
- package/src/components/flow-editor/node-config-panel/panels/InputPanel.tsx +338 -0
- package/src/components/flow-editor/node-config-panel/panels/OutputPanel.tsx +109 -0
- package/src/components/flow-editor/node-config-panel/panels/index.ts +4 -0
- package/src/components/flow-editor/node-config-panel/types.ts +20 -0
- package/src/components/flow-editor/node-config-panel/use-node-config-panel-store.ts +283 -0
- package/src/components/flow-editor/node-config-panel/use-node-config-state.ts +172 -0
- package/src/components/flow-editor/node-config-panel/use-run-node.ts +147 -0
- package/src/components/flow-editor/node-config-panel/utils.ts +73 -0
- package/src/components/flow-editor/serialize-to-sdk.ts +204 -0
- package/src/components/flow-editor/toolbar-context.ts +9 -0
- package/src/components/flow-editor/use-copy-paste.ts +575 -0
- package/src/components/flow-editor/use-copy-paste.types.ts +35 -0
- package/src/components/flow-editor/use-flow-editor.ts +241 -0
- package/src/components/flow-runs-table/FlowRunsTable.tsx +631 -0
- package/src/components/flow-runs-table/index.ts +1 -0
- package/src/components/flow-viewer/FlowRunsView.tsx +268 -0
- package/src/components/flow-viewer/FlowStatusView.tsx +351 -0
- package/src/components/flow-viewer/RunSelector.tsx +422 -0
- package/src/components/flow-viewer/RunsSidebar.tsx +125 -0
- package/src/components/flow-viewer/agent-tool-executions-list.tsx +298 -0
- package/src/components/flow-viewer/index.ts +1 -0
- package/src/components/flow-viewer/logs-panel.tsx +567 -0
- package/src/components/flow-viewer/use-execution-log-data.ts +374 -0
- package/src/components/graph/BatchFlowEdge.tsx +229 -0
- package/src/components/graph/LayoutSelector.tsx +42 -0
- package/src/components/graph/index.ts +61 -0
- package/src/components/graph/styleUtils.ts +375 -0
- package/src/components/nodes/.DS_Store +0 -0
- package/src/components/nodes/AgentConfigPanel.tsx +1033 -0
- package/src/components/nodes/AgentNode.tsx +298 -0
- package/src/components/nodes/AgentToolsBox.tsx +193 -0
- package/src/components/nodes/NodeAppendix.tsx +98 -0
- package/src/components/nodes/NodeStatusIndicator.tsx +74 -0
- package/src/components/nodes/NodeViewContext.tsx +45 -0
- package/src/components/nodes/ToolParamField.tsx +282 -0
- package/src/components/nodes/ToolSelectorModal.tsx +648 -0
- package/src/components/nodes/ToolSelectorParts.tsx +505 -0
- package/src/components/nodes/UniversalNode.tsx +356 -0
- package/src/components/nodes/createContextAwareNodes.ts +19 -0
- package/src/components/nodes/index.ts +45 -0
- package/src/components/nodes/nodeRegistry.ts +50 -0
- package/src/components/nodes/withNodeContext.tsx +55 -0
- package/src/components/shared/InvectLoader.tsx +59 -0
- package/src/components/shared/InvectLogo.tsx +59 -0
- package/src/components/shared/ProviderIcon.tsx +115 -0
- package/src/components/side-menu/side-menu.tsx +267 -0
- package/src/components/sidebar/BaseSidebar.tsx +148 -0
- package/src/components/sidebar/index.ts +1 -0
- package/src/components/triggers/CronPreview.tsx +243 -0
- package/src/components/triggers/index.ts +1 -0
- package/src/components/ui/alert-dialog.tsx +152 -0
- package/src/components/ui/badge.tsx +39 -0
- package/src/components/ui/button.tsx +58 -0
- package/src/components/ui/card.tsx +75 -0
- package/src/components/ui/codemirror-js-editor.tsx +432 -0
- package/src/components/ui/codemirror-json-editor.tsx +816 -0
- package/src/components/ui/codemirror-nunjucks-editor.tsx +451 -0
- package/src/components/ui/codemirror-vscode-theme.ts +243 -0
- package/src/components/ui/collapsible.tsx +12 -0
- package/src/components/ui/command.tsx +162 -0
- package/src/components/ui/dialog.tsx +140 -0
- package/src/components/ui/dropdown-menu.tsx +232 -0
- package/src/components/ui/empty-state.tsx +93 -0
- package/src/components/ui/input.tsx +26 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/popover.tsx +53 -0
- package/src/components/ui/resizable.tsx +61 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/select.tsx +179 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/slider.tsx +58 -0
- package/src/components/ui/switch.tsx +22 -0
- package/src/components/ui/table.tsx +90 -0
- package/src/components/ui/textarea.tsx +23 -0
- package/src/components/ui/tooltip.tsx +54 -0
- package/src/components/ui/tree-view.tsx +574 -0
- package/src/contexts/AgentToolCallbacksContext.tsx +31 -0
- package/src/contexts/ApiContext.tsx +51 -0
- package/src/contexts/FlowDataContext.tsx +21 -0
- package/src/contexts/NodeRegistryContext.tsx +54 -0
- package/src/contexts/PluginRegistryContext.tsx +182 -0
- package/src/contexts/ThemeProvider.tsx +106 -0
- package/src/contexts/ValidationContext.tsx +122 -0
- package/src/demo/DemoInvect.tsx +42 -0
- package/src/demo/FlowViewer.tsx +294 -0
- package/src/demo/demo-api-client.ts +246 -0
- package/src/demo/index.ts +28 -0
- package/src/demo/sample-data.ts +1980 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-document-title.ts +8 -0
- package/src/hooks/use-flow-data.ts +144 -0
- package/src/hooks/use-invect-portal-class.ts +27 -0
- package/src/hooks/useFlowEditorStore.ts +2 -0
- package/src/index.ts +70 -0
- package/src/lib/utils.ts +6 -0
- package/src/prettier.d.ts +13 -0
- package/src/routes/all-flow-runs.tsx +27 -0
- package/src/routes/credentials.tsx +362 -0
- package/src/routes/flow-route-layout.tsx +113 -0
- package/src/routes/flow-runs.tsx +22 -0
- package/src/routes/flow.tsx +22 -0
- package/src/routes/home.tsx +282 -0
- package/src/services/index.ts +6 -0
- package/src/stores/executionViewStore.ts +211 -0
- package/src/stores/flow-editor.store.ts +738 -0
- package/src/stores/flowEditorStore.ts +2 -0
- package/src/stores/index.ts +10 -0
- package/src/stores/uiStore.ts +189 -0
- package/src/types/agent-tools.types.ts +64 -0
- package/src/types/index.ts +5 -0
- package/src/types/node-definition.types.ts +104 -0
- package/src/types/plugin.types.ts +123 -0
- package/src/utils/credentialBranding.ts +116 -0
- package/src/utils/credentialFiltering.ts +68 -0
- package/src/utils/flowTransformations.ts +137 -0
- package/src/utils/layoutUtils.ts +127 -0
- package/src/utils/nodeReferenceUtils.ts +135 -0
- package/src/vendor.d.ts +7 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import { useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import type { Node, Edge, ReactFlowInstance } from '@xyflow/react';
|
|
3
|
+
import { nanoid } from 'nanoid';
|
|
4
|
+
import { useFlowEditorStore } from './flow-editor.store';
|
|
5
|
+
import { useNodeRegistry } from '~/contexts/NodeRegistryContext';
|
|
6
|
+
import { generateUniqueDisplayName, generateUniqueReferenceId } from '~/utils/nodeReferenceUtils';
|
|
7
|
+
import type { ClipboardData, ClipboardNode, ClipboardEdge } from './use-copy-paste.types';
|
|
8
|
+
import { serializeToSDK } from './serialize-to-sdk';
|
|
9
|
+
import { parseSDKText, type ParsedSDK } from '@invect/core/sdk';
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Types
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
interface UseCopyPasteOptions {
|
|
16
|
+
flowId: string;
|
|
17
|
+
reactFlowInstance: ReactFlowInstance | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
/** Deep clone JSON-safe data */
|
|
25
|
+
function cloneData<T>(data: T): T {
|
|
26
|
+
return JSON.parse(JSON.stringify(data));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Check if a keyboard event target is in an editable context (inputs, modals, etc.) */
|
|
30
|
+
function isEditingContext(el: HTMLElement): boolean {
|
|
31
|
+
if (
|
|
32
|
+
el.tagName === 'INPUT' ||
|
|
33
|
+
el.tagName === 'TEXTAREA' ||
|
|
34
|
+
el.tagName === 'SELECT' ||
|
|
35
|
+
el.isContentEditable
|
|
36
|
+
) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (el.closest('.cm-editor') || el.closest('[role="dialog"]')) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Strip credential IDs from params (used for cross-flow paste).
|
|
47
|
+
* Also strips credentialId from nested addedTools params.
|
|
48
|
+
*/
|
|
49
|
+
function stripCredentials(params: Record<string, unknown>): Record<string, unknown> {
|
|
50
|
+
const cleaned = { ...params };
|
|
51
|
+
delete cleaned.credentialId;
|
|
52
|
+
|
|
53
|
+
if (Array.isArray(cleaned.addedTools)) {
|
|
54
|
+
cleaned.addedTools = (cleaned.addedTools as Array<Record<string, unknown>>).map((tool) => {
|
|
55
|
+
const toolCleaned = { ...tool };
|
|
56
|
+
if (typeof toolCleaned.params === 'object' && toolCleaned.params !== null) {
|
|
57
|
+
const toolParams = { ...(toolCleaned.params as Record<string, unknown>) };
|
|
58
|
+
delete toolParams.credentialId;
|
|
59
|
+
toolCleaned.params = toolParams;
|
|
60
|
+
}
|
|
61
|
+
return toolCleaned;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return cleaned;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Regenerate instanceIds for addedTools in agent node params.
|
|
70
|
+
*/
|
|
71
|
+
function regenToolInstanceIds(params: Record<string, unknown>): Record<string, unknown> {
|
|
72
|
+
if (!Array.isArray(params.addedTools)) {
|
|
73
|
+
return params;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
...params,
|
|
78
|
+
addedTools: (params.addedTools as Array<Record<string, unknown>>).map((tool) => ({
|
|
79
|
+
...tool,
|
|
80
|
+
instanceId: nanoid(),
|
|
81
|
+
})),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Convert parseSDKText output into a ClipboardData structure that
|
|
87
|
+
* materializePaste can consume. Maps FlowNodeDefinitions → ClipboardNode
|
|
88
|
+
* and edge tuples → ClipboardEdge.
|
|
89
|
+
*/
|
|
90
|
+
function sdkResultToClipboard({ nodes, edges }: ParsedSDK, flowId: string): ClipboardData {
|
|
91
|
+
// Compute absolute positions for each node (use parsed position or default horizontal layout)
|
|
92
|
+
const positions = nodes.map((n, i) => n.position ?? { x: i * 250, y: 0 });
|
|
93
|
+
|
|
94
|
+
// Compute bounding box origin for relative positioning
|
|
95
|
+
const minX = positions.length > 0 ? Math.min(...positions.map((p) => p.x)) : 0;
|
|
96
|
+
const minY = positions.length > 0 ? Math.min(...positions.map((p) => p.y)) : 0;
|
|
97
|
+
|
|
98
|
+
const clipboardNodes: ClipboardNode[] = nodes.map((n, i) => {
|
|
99
|
+
const ref = n.referenceId ?? n.id;
|
|
100
|
+
const pos = positions[i];
|
|
101
|
+
return {
|
|
102
|
+
originalId: n.id,
|
|
103
|
+
type: n.type,
|
|
104
|
+
relativePosition: { x: pos.x - minX, y: pos.y - minY },
|
|
105
|
+
absolutePosition: pos,
|
|
106
|
+
data: {
|
|
107
|
+
display_name: n.label ?? ref,
|
|
108
|
+
reference_id: ref,
|
|
109
|
+
params: n.params ?? {},
|
|
110
|
+
...(n.mapper !== undefined && { mapper: n.mapper }),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Build referenceId → node id mapping for edge resolution
|
|
116
|
+
const refToId = new Map<string, string>();
|
|
117
|
+
for (const n of nodes) {
|
|
118
|
+
const ref = n.referenceId ?? n.id;
|
|
119
|
+
refToId.set(ref, n.id);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const clipboardEdges: ClipboardEdge[] = [];
|
|
123
|
+
for (const e of edges) {
|
|
124
|
+
if (Array.isArray(e)) {
|
|
125
|
+
const sourceId = refToId.get(e[0]) ?? `node-${e[0]}`;
|
|
126
|
+
const targetId = refToId.get(e[1]) ?? `node-${e[1]}`;
|
|
127
|
+
clipboardEdges.push({
|
|
128
|
+
originalId: `edge-${nanoid()}`,
|
|
129
|
+
source: sourceId,
|
|
130
|
+
target: targetId,
|
|
131
|
+
...(e[2] ? { sourceHandle: e[2] } : {}),
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
const sourceId = refToId.get(e.from) ?? `node-${e.from}`;
|
|
135
|
+
const targetId = refToId.get(e.to) ?? `node-${e.to}`;
|
|
136
|
+
clipboardEdges.push({
|
|
137
|
+
originalId: `edge-${nanoid()}`,
|
|
138
|
+
source: sourceId,
|
|
139
|
+
target: targetId,
|
|
140
|
+
...(e.handle ? { sourceHandle: e.handle } : {}),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
sourceFlowId: flowId,
|
|
147
|
+
nodes: clipboardNodes,
|
|
148
|
+
edges: clipboardEdges,
|
|
149
|
+
copyTime: Date.now(),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Hook
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
export function useCopyPaste({ flowId, reactFlowInstance }: UseCopyPasteOptions) {
|
|
158
|
+
const clipboardRef = useRef<ClipboardData | null>(null);
|
|
159
|
+
const mousePositionRef = useRef<{ x: number; y: number } | null>(null);
|
|
160
|
+
const { getNodeDefinition } = useNodeRegistry();
|
|
161
|
+
|
|
162
|
+
// Track mouse position over the react-flow canvas for paste anchoring
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
165
|
+
mousePositionRef.current = { x: e.clientX, y: e.clientY };
|
|
166
|
+
};
|
|
167
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
168
|
+
return () => document.removeEventListener('mousemove', handleMouseMove);
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
// Serialize selection into ClipboardData
|
|
173
|
+
// -------------------------------------------------------------------------
|
|
174
|
+
const serializeSelection = useCallback((): ClipboardData | null => {
|
|
175
|
+
const { nodes, edges } = useFlowEditorStore.getState();
|
|
176
|
+
const selectedNodes = nodes.filter((n) => n.selected);
|
|
177
|
+
if (selectedNodes.length === 0) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const selectedIdSet = new Set(selectedNodes.map((n) => n.id));
|
|
182
|
+
|
|
183
|
+
// Only capture edges where both endpoints are selected
|
|
184
|
+
const internalEdges = edges.filter(
|
|
185
|
+
(e) => selectedIdSet.has(e.source) && selectedIdSet.has(e.target),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Compute bounding box origin
|
|
189
|
+
const minX = Math.min(...selectedNodes.map((n) => n.position.x));
|
|
190
|
+
const minY = Math.min(...selectedNodes.map((n) => n.position.y));
|
|
191
|
+
|
|
192
|
+
const clipboardNodes: ClipboardNode[] = selectedNodes.map((node) => {
|
|
193
|
+
const data = node.data as Record<string, unknown>;
|
|
194
|
+
const params = (data.params as Record<string, unknown>) ?? {};
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
originalId: node.id,
|
|
198
|
+
type: (data.type as string) ?? node.type ?? 'unknown',
|
|
199
|
+
relativePosition: {
|
|
200
|
+
x: node.position.x - minX,
|
|
201
|
+
y: node.position.y - minY,
|
|
202
|
+
},
|
|
203
|
+
absolutePosition: {
|
|
204
|
+
x: node.position.x,
|
|
205
|
+
y: node.position.y,
|
|
206
|
+
},
|
|
207
|
+
data: {
|
|
208
|
+
display_name: (data.display_name as string) ?? '',
|
|
209
|
+
reference_id: (data.reference_id as string) ?? '',
|
|
210
|
+
params: cloneData(params),
|
|
211
|
+
...(data.mapper !== undefined && { mapper: cloneData(data.mapper) }),
|
|
212
|
+
...(data._loop !== undefined && { _loop: cloneData(data._loop) }),
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const clipboardEdges: ClipboardEdge[] = internalEdges.map((edge) => ({
|
|
218
|
+
originalId: edge.id,
|
|
219
|
+
source: edge.source,
|
|
220
|
+
target: edge.target,
|
|
221
|
+
...(edge.sourceHandle !== undefined && { sourceHandle: edge.sourceHandle }),
|
|
222
|
+
...(edge.targetHandle !== undefined && { targetHandle: edge.targetHandle }),
|
|
223
|
+
}));
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
sourceFlowId: flowId,
|
|
227
|
+
nodes: clipboardNodes,
|
|
228
|
+
edges: clipboardEdges,
|
|
229
|
+
copyTime: Date.now(),
|
|
230
|
+
};
|
|
231
|
+
}, [flowId]);
|
|
232
|
+
|
|
233
|
+
// -------------------------------------------------------------------------
|
|
234
|
+
// Materialize ClipboardData into the store
|
|
235
|
+
// -------------------------------------------------------------------------
|
|
236
|
+
const materializePaste = useCallback(
|
|
237
|
+
(clipboard: ClipboardData, anchor: { x: number; y: number }) => {
|
|
238
|
+
const { nodes: existingNodes } = useFlowEditorStore.getState();
|
|
239
|
+
const isCrossFlow = clipboard.sourceFlowId !== flowId;
|
|
240
|
+
const isEmptyFlow = existingNodes.length === 0;
|
|
241
|
+
|
|
242
|
+
// Build the ID remap table
|
|
243
|
+
const idMap = new Map<string, string>();
|
|
244
|
+
for (const cn of clipboard.nodes) {
|
|
245
|
+
idMap.set(cn.originalId, `${cn.type}-${nanoid()}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Track nodes we add so dedup works across the paste batch
|
|
249
|
+
const batchNodes: Array<{ data?: { display_name?: string; reference_id?: string } }> = [
|
|
250
|
+
...existingNodes,
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
const skippedNodeIds = new Set<string>();
|
|
254
|
+
const newNodes: Node[] = [];
|
|
255
|
+
|
|
256
|
+
for (const cn of clipboard.nodes) {
|
|
257
|
+
// maxInstances check
|
|
258
|
+
const definition = getNodeDefinition(cn.type);
|
|
259
|
+
if (definition?.maxInstances !== undefined) {
|
|
260
|
+
const currentCount = existingNodes.filter(
|
|
261
|
+
(n) => (n.data as Record<string, unknown>)?.type === cn.type,
|
|
262
|
+
).length;
|
|
263
|
+
const pastedCount = newNodes.filter(
|
|
264
|
+
(n) => (n.data as Record<string, unknown>)?.type === cn.type,
|
|
265
|
+
).length;
|
|
266
|
+
if (currentCount + pastedCount >= definition.maxInstances) {
|
|
267
|
+
skippedNodeIds.add(cn.originalId);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const newId = idMap.get(cn.originalId);
|
|
273
|
+
if (!newId) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
const displayName = generateUniqueDisplayName(cn.data.display_name, batchNodes);
|
|
277
|
+
const referenceId = generateUniqueReferenceId(displayName, batchNodes);
|
|
278
|
+
|
|
279
|
+
let params = cloneData(cn.data.params);
|
|
280
|
+
if (isCrossFlow) {
|
|
281
|
+
params = stripCredentials(params);
|
|
282
|
+
}
|
|
283
|
+
params = regenToolInstanceIds(params);
|
|
284
|
+
|
|
285
|
+
// When the flow is empty, use the absolute/content positions directly;
|
|
286
|
+
// otherwise anchor relative positions to the cursor/viewport point.
|
|
287
|
+
const position =
|
|
288
|
+
isEmptyFlow && cn.absolutePosition
|
|
289
|
+
? { x: cn.absolutePosition.x, y: cn.absolutePosition.y }
|
|
290
|
+
: { x: anchor.x + cn.relativePosition.x, y: anchor.y + cn.relativePosition.y };
|
|
291
|
+
|
|
292
|
+
const node: Node = {
|
|
293
|
+
id: newId,
|
|
294
|
+
type: cn.type,
|
|
295
|
+
position,
|
|
296
|
+
selected: true,
|
|
297
|
+
data: {
|
|
298
|
+
id: newId,
|
|
299
|
+
type: cn.type,
|
|
300
|
+
display_name: displayName,
|
|
301
|
+
reference_id: referenceId,
|
|
302
|
+
status: 'idle',
|
|
303
|
+
params,
|
|
304
|
+
...(cn.data.mapper !== undefined && { mapper: cloneData(cn.data.mapper) }),
|
|
305
|
+
...(cn.data._loop !== undefined && { _loop: cloneData(cn.data._loop) }),
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
newNodes.push(node);
|
|
310
|
+
batchNodes.push(node);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Remap edges — drop any whose endpoint was skipped
|
|
314
|
+
const newEdges: Edge[] = [];
|
|
315
|
+
for (const ce of clipboard.edges) {
|
|
316
|
+
if (skippedNodeIds.has(ce.source) || skippedNodeIds.has(ce.target)) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const newSource = idMap.get(ce.source);
|
|
320
|
+
const newTarget = idMap.get(ce.target);
|
|
321
|
+
if (!newSource || !newTarget) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
newEdges.push({
|
|
326
|
+
id: `edge-${nanoid()}`,
|
|
327
|
+
source: newSource,
|
|
328
|
+
target: newTarget,
|
|
329
|
+
...(ce.sourceHandle !== undefined && { sourceHandle: ce.sourceHandle }),
|
|
330
|
+
...(ce.targetHandle !== undefined && { targetHandle: ce.targetHandle }),
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (newNodes.length > 0) {
|
|
335
|
+
console.log(
|
|
336
|
+
'[Copy/Paste] materializePaste: adding',
|
|
337
|
+
newNodes.length,
|
|
338
|
+
'nodes,',
|
|
339
|
+
newEdges.length,
|
|
340
|
+
'edges',
|
|
341
|
+
);
|
|
342
|
+
console.log(
|
|
343
|
+
'[Copy/Paste] newNodes:',
|
|
344
|
+
newNodes.map((n) => ({
|
|
345
|
+
id: n.id,
|
|
346
|
+
type: n.type,
|
|
347
|
+
pos: n.position,
|
|
348
|
+
data: {
|
|
349
|
+
type: (n.data as Record<string, unknown>).type,
|
|
350
|
+
display_name: (n.data as Record<string, unknown>).display_name,
|
|
351
|
+
},
|
|
352
|
+
})),
|
|
353
|
+
);
|
|
354
|
+
useFlowEditorStore.getState().pasteNodesAndEdges(newNodes, newEdges);
|
|
355
|
+
const storeState = useFlowEditorStore.getState();
|
|
356
|
+
console.log('[Copy/Paste] Store after paste: total nodes =', storeState.nodes.length);
|
|
357
|
+
} else {
|
|
358
|
+
console.log('[Copy/Paste] materializePaste: no nodes to add (all skipped?)');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (skippedNodeIds.size > 0) {
|
|
362
|
+
console.log(
|
|
363
|
+
`[Copy/Paste] Skipped ${skippedNodeIds.size} node(s) — maxInstances limit reached`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
[flowId, getNodeDefinition],
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
// -------------------------------------------------------------------------
|
|
371
|
+
// Compute paste anchor point
|
|
372
|
+
// -------------------------------------------------------------------------
|
|
373
|
+
const getPasteAnchor = useCallback(
|
|
374
|
+
(offset?: { x: number; y: number }): { x: number; y: number } => {
|
|
375
|
+
if (offset) {
|
|
376
|
+
return offset;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Try to paste at mouse cursor position in flow coords
|
|
380
|
+
if (reactFlowInstance && mousePositionRef.current) {
|
|
381
|
+
return reactFlowInstance.screenToFlowPosition(mousePositionRef.current);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Fallback: center of viewport
|
|
385
|
+
if (reactFlowInstance) {
|
|
386
|
+
return reactFlowInstance.screenToFlowPosition({
|
|
387
|
+
x: window.innerWidth / 2,
|
|
388
|
+
y: window.innerHeight / 2,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return { x: 250, y: 150 };
|
|
393
|
+
},
|
|
394
|
+
[reactFlowInstance],
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
// -------------------------------------------------------------------------
|
|
398
|
+
// Read clipboard (in-memory first → system SDK parse fallback)
|
|
399
|
+
// -------------------------------------------------------------------------
|
|
400
|
+
const readClipboard = useCallback(async (): Promise<ClipboardData | null> => {
|
|
401
|
+
// Prefer in-memory ref — has correct relative positions from serializeSelection.
|
|
402
|
+
// This covers same-session copy/paste (the common case).
|
|
403
|
+
if (clipboardRef.current) {
|
|
404
|
+
return clipboardRef.current;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Fall back to system clipboard — handles cross-app paste (SDK text from
|
|
408
|
+
// a text editor, another browser tab, etc.)
|
|
409
|
+
try {
|
|
410
|
+
const text = await navigator.clipboard.readText();
|
|
411
|
+
const parsed = parseSDKText(text);
|
|
412
|
+
if (parsed.nodes.length > 0) {
|
|
413
|
+
return sdkResultToClipboard(parsed, flowId);
|
|
414
|
+
}
|
|
415
|
+
} catch {
|
|
416
|
+
// Permission denied, parse failed, or empty
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return null;
|
|
420
|
+
}, [flowId]);
|
|
421
|
+
|
|
422
|
+
// -------------------------------------------------------------------------
|
|
423
|
+
// Write to clipboard (system + in-memory)
|
|
424
|
+
// -------------------------------------------------------------------------
|
|
425
|
+
const writeClipboard = useCallback(async (data: ClipboardData) => {
|
|
426
|
+
clipboardRef.current = data;
|
|
427
|
+
try {
|
|
428
|
+
// Write SDK code to the system clipboard so pasting into a text editor
|
|
429
|
+
// (VS Code, etc.) produces importable TypeScript helper calls.
|
|
430
|
+
// Internal paste always reads from clipboardRef (in-memory).
|
|
431
|
+
const sdkText = serializeToSDK(data.nodes, data.edges);
|
|
432
|
+
await navigator.clipboard.writeText(sdkText);
|
|
433
|
+
} catch {
|
|
434
|
+
// System clipboard write failed — in-memory ref is still set
|
|
435
|
+
}
|
|
436
|
+
}, []);
|
|
437
|
+
|
|
438
|
+
// -------------------------------------------------------------------------
|
|
439
|
+
// Operations
|
|
440
|
+
// -------------------------------------------------------------------------
|
|
441
|
+
|
|
442
|
+
const copy = useCallback(async () => {
|
|
443
|
+
const data = serializeSelection();
|
|
444
|
+
if (!data) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
await writeClipboard(data);
|
|
448
|
+
}, [serializeSelection, writeClipboard]);
|
|
449
|
+
|
|
450
|
+
const paste = useCallback(async () => {
|
|
451
|
+
console.log('[Copy/Paste] Paste triggered');
|
|
452
|
+
const clipboard = await readClipboard();
|
|
453
|
+
if (!clipboard) {
|
|
454
|
+
console.log('[Copy/Paste] No clipboard data available');
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
console.log('[Copy/Paste] Clipboard data:', {
|
|
458
|
+
nodeCount: clipboard.nodes.length,
|
|
459
|
+
edgeCount: clipboard.edges.length,
|
|
460
|
+
});
|
|
461
|
+
const anchor = getPasteAnchor();
|
|
462
|
+
console.log('[Copy/Paste] Paste anchor:', anchor);
|
|
463
|
+
materializePaste(clipboard, anchor);
|
|
464
|
+
}, [readClipboard, getPasteAnchor, materializePaste]);
|
|
465
|
+
|
|
466
|
+
const cut = useCallback(async () => {
|
|
467
|
+
const data = serializeSelection();
|
|
468
|
+
if (!data) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
await writeClipboard(data);
|
|
472
|
+
|
|
473
|
+
const selectedIds = useFlowEditorStore
|
|
474
|
+
.getState()
|
|
475
|
+
.nodes.filter((n) => n.selected)
|
|
476
|
+
.map((n) => n.id);
|
|
477
|
+
|
|
478
|
+
if (selectedIds.length > 0) {
|
|
479
|
+
useFlowEditorStore.getState().removeNodes(selectedIds);
|
|
480
|
+
}
|
|
481
|
+
}, [serializeSelection, writeClipboard]);
|
|
482
|
+
|
|
483
|
+
const duplicate = useCallback(() => {
|
|
484
|
+
const data = serializeSelection();
|
|
485
|
+
if (!data) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Don't write to system clipboard — duplicate is internal
|
|
490
|
+
clipboardRef.current = data;
|
|
491
|
+
|
|
492
|
+
// Compute offset from original positions
|
|
493
|
+
const { nodes } = useFlowEditorStore.getState();
|
|
494
|
+
const selectedNodes = nodes.filter((n) => n.selected);
|
|
495
|
+
if (selectedNodes.length === 0) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const minX = Math.min(...selectedNodes.map((n) => n.position.x));
|
|
500
|
+
const minY = Math.min(...selectedNodes.map((n) => n.position.y));
|
|
501
|
+
|
|
502
|
+
materializePaste(data, { x: minX + 50, y: minY + 50 });
|
|
503
|
+
}, [serializeSelection, materializePaste]);
|
|
504
|
+
|
|
505
|
+
const deleteSelection = useCallback(() => {
|
|
506
|
+
const selectedIds = useFlowEditorStore
|
|
507
|
+
.getState()
|
|
508
|
+
.nodes.filter((n) => n.selected)
|
|
509
|
+
.map((n) => n.id);
|
|
510
|
+
|
|
511
|
+
if (selectedIds.length > 0) {
|
|
512
|
+
useFlowEditorStore.getState().removeNodes(selectedIds);
|
|
513
|
+
}
|
|
514
|
+
}, []);
|
|
515
|
+
|
|
516
|
+
// -------------------------------------------------------------------------
|
|
517
|
+
// Keyboard handler
|
|
518
|
+
// -------------------------------------------------------------------------
|
|
519
|
+
useEffect(() => {
|
|
520
|
+
const handler = async (e: KeyboardEvent) => {
|
|
521
|
+
const el = e.target as HTMLElement;
|
|
522
|
+
|
|
523
|
+
// Only handle when focus is on the ReactFlow canvas, not on editable elements
|
|
524
|
+
const isOnCanvas = el.closest('.react-flow') !== null;
|
|
525
|
+
const isBodyFocused = el.tagName === 'BODY';
|
|
526
|
+
const isEditing = isEditingContext(el);
|
|
527
|
+
|
|
528
|
+
if (e.metaKey || e.ctrlKey) {
|
|
529
|
+
console.log('[Copy/Paste] Keydown:', e.key, {
|
|
530
|
+
isOnCanvas,
|
|
531
|
+
isBodyFocused,
|
|
532
|
+
isEditing,
|
|
533
|
+
tagName: el.tagName,
|
|
534
|
+
classList: el.className.substring(0, 80),
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if ((!isOnCanvas && !isBodyFocused) || isEditing) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// If the user has a text selection anywhere in the document, let the
|
|
543
|
+
// browser handle copy/cut natively (e.g. copying AI assistant output).
|
|
544
|
+
const hasTextSelection = (window.getSelection()?.toString().length ?? 0) > 0;
|
|
545
|
+
|
|
546
|
+
const isMod = e.metaKey || e.ctrlKey;
|
|
547
|
+
|
|
548
|
+
if (isMod && e.key === 'c') {
|
|
549
|
+
if (hasTextSelection) {
|
|
550
|
+
return;
|
|
551
|
+
} // let browser copy selected text
|
|
552
|
+
e.preventDefault();
|
|
553
|
+
await copy();
|
|
554
|
+
} else if (isMod && e.key === 'x') {
|
|
555
|
+
if (hasTextSelection) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
e.preventDefault();
|
|
559
|
+
await cut();
|
|
560
|
+
} else if (isMod && e.key === 'v') {
|
|
561
|
+
e.preventDefault();
|
|
562
|
+
await paste();
|
|
563
|
+
} else if (isMod && e.key === 'd') {
|
|
564
|
+
e.preventDefault();
|
|
565
|
+
duplicate();
|
|
566
|
+
} else if (e.key === 'Delete' || e.key === 'Backspace') {
|
|
567
|
+
e.preventDefault();
|
|
568
|
+
deleteSelection();
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
document.addEventListener('keydown', handler);
|
|
573
|
+
return () => document.removeEventListener('keydown', handler);
|
|
574
|
+
}, [copy, cut, paste, duplicate, deleteSelection]);
|
|
575
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the copy-paste system.
|
|
3
|
+
*
|
|
4
|
+
* Extracted so both `use-copy-paste.ts` and `serialize-to-sdk.ts` can
|
|
5
|
+
* reference the same clipboard payload shapes without circular imports.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ClipboardData {
|
|
9
|
+
sourceFlowId: string;
|
|
10
|
+
nodes: ClipboardNode[];
|
|
11
|
+
edges: ClipboardEdge[];
|
|
12
|
+
copyTime: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ClipboardNode {
|
|
16
|
+
originalId: string;
|
|
17
|
+
type: string;
|
|
18
|
+
relativePosition: { x: number; y: number };
|
|
19
|
+
absolutePosition?: { x: number; y: number };
|
|
20
|
+
data: {
|
|
21
|
+
display_name: string;
|
|
22
|
+
reference_id: string;
|
|
23
|
+
params: Record<string, unknown>;
|
|
24
|
+
mapper?: unknown;
|
|
25
|
+
_loop?: unknown;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ClipboardEdge {
|
|
30
|
+
originalId: string;
|
|
31
|
+
source: string;
|
|
32
|
+
target: string;
|
|
33
|
+
sourceHandle?: string | null;
|
|
34
|
+
targetHandle?: string | null;
|
|
35
|
+
}
|