@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,788 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatMessageList — Renders the chat message list, streaming indicator, and empty states.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
|
6
|
+
import {
|
|
7
|
+
MessageSquare,
|
|
8
|
+
Bot,
|
|
9
|
+
Loader2,
|
|
10
|
+
AlertCircle,
|
|
11
|
+
ChevronRight,
|
|
12
|
+
ChevronDown,
|
|
13
|
+
Check,
|
|
14
|
+
XCircle,
|
|
15
|
+
X,
|
|
16
|
+
Copy,
|
|
17
|
+
ClipboardCheck,
|
|
18
|
+
Pencil,
|
|
19
|
+
Circle,
|
|
20
|
+
CheckCircle2,
|
|
21
|
+
SkipForward,
|
|
22
|
+
} from 'lucide-react';
|
|
23
|
+
import { cn } from '~/lib/utils';
|
|
24
|
+
import { ScrollArea } from '~/components/ui/scroll-area';
|
|
25
|
+
import { Button } from '~/components/ui/button';
|
|
26
|
+
import { Textarea } from '~/components/ui/textarea';
|
|
27
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/collapsible';
|
|
28
|
+
import type { ChatMessage } from './chat.store';
|
|
29
|
+
import { useChatStore } from './chat.store';
|
|
30
|
+
import { MarkdownRenderer } from './MarkdownRenderer';
|
|
31
|
+
import { InlineCredentialSetup } from './InlineCredentialSetup';
|
|
32
|
+
import { CreateCredentialModal } from '~/components/credentials/CreateCredentialModal';
|
|
33
|
+
import { useCreateCredential } from '~/api/credentials.api';
|
|
34
|
+
|
|
35
|
+
// =====================================
|
|
36
|
+
// ChatMessageList
|
|
37
|
+
// =====================================
|
|
38
|
+
|
|
39
|
+
interface ChatMessageListProps {
|
|
40
|
+
messages: ChatMessage[];
|
|
41
|
+
isStreaming: boolean;
|
|
42
|
+
isLoadingHistory: boolean;
|
|
43
|
+
streamingText: string;
|
|
44
|
+
error: string | null;
|
|
45
|
+
hasConfiguredCredential: boolean;
|
|
46
|
+
hasAvailableLlmCredentials: boolean;
|
|
47
|
+
onOpenSettings: () => void;
|
|
48
|
+
onSendMessage: (text: string) => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function ChatMessageList({
|
|
52
|
+
messages,
|
|
53
|
+
isStreaming,
|
|
54
|
+
isLoadingHistory,
|
|
55
|
+
streamingText,
|
|
56
|
+
error,
|
|
57
|
+
hasConfiguredCredential,
|
|
58
|
+
hasAvailableLlmCredentials: _hasAvailableLlmCredentials,
|
|
59
|
+
onOpenSettings: _onOpenSettings,
|
|
60
|
+
onSendMessage,
|
|
61
|
+
}: ChatMessageListProps) {
|
|
62
|
+
const _scrollRef = useRef<HTMLDivElement>(null);
|
|
63
|
+
|
|
64
|
+
// Auto-scroll: only scroll to bottom if already near bottom (within 80px)
|
|
65
|
+
const viewportRef = useRef<HTMLDivElement | null>(null);
|
|
66
|
+
const isNearBottomRef = useRef(true);
|
|
67
|
+
|
|
68
|
+
// Capture ref to the ScrollArea viewport for scroll tracking
|
|
69
|
+
const scrollAreaRef = useCallback((node: HTMLDivElement | null) => {
|
|
70
|
+
if (!node) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const viewport = node.querySelector(
|
|
74
|
+
'[data-radix-scroll-area-viewport]',
|
|
75
|
+
) as HTMLDivElement | null;
|
|
76
|
+
if (viewport) {
|
|
77
|
+
viewportRef.current = viewport;
|
|
78
|
+
}
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
const handleScroll = useCallback(() => {
|
|
82
|
+
const el = viewportRef.current;
|
|
83
|
+
if (!el) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const threshold = 80;
|
|
87
|
+
isNearBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
// Scroll to bottom on initial render / when history finishes loading
|
|
91
|
+
const hasScrolledOnLoad = useRef(false);
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (
|
|
94
|
+
!isLoadingHistory &&
|
|
95
|
+
messages.length > 0 &&
|
|
96
|
+
!hasScrolledOnLoad.current &&
|
|
97
|
+
viewportRef.current
|
|
98
|
+
) {
|
|
99
|
+
viewportRef.current.scrollTop = viewportRef.current.scrollHeight;
|
|
100
|
+
hasScrolledOnLoad.current = true;
|
|
101
|
+
}
|
|
102
|
+
}, [isLoadingHistory, messages]);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (isNearBottomRef.current && viewportRef.current) {
|
|
106
|
+
viewportRef.current.scrollTop = viewportRef.current.scrollHeight;
|
|
107
|
+
}
|
|
108
|
+
}, [messages, streamingText]);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<ScrollArea className="flex-1 min-h-0" onScrollCapture={handleScroll} ref={scrollAreaRef}>
|
|
112
|
+
<div className="flex flex-col gap-0.5 p-4">
|
|
113
|
+
{isLoadingHistory && (
|
|
114
|
+
<div className="flex items-center justify-center gap-2 py-8 text-sm text-muted-foreground">
|
|
115
|
+
<Loader2 className="size-4 animate-spin" />
|
|
116
|
+
<span>Loading conversation…</span>
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{!isLoadingHistory && messages.length === 0 && !isStreaming && (
|
|
121
|
+
<div className="flex flex-col items-center justify-center flex-1 text-center min-h-70">
|
|
122
|
+
{!hasConfiguredCredential ? (
|
|
123
|
+
<InlineCredentialSetup />
|
|
124
|
+
) : (
|
|
125
|
+
<ChatSuggestionPrompts onSelect={onSendMessage} />
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
|
|
130
|
+
{messages.map((msg, idx) => {
|
|
131
|
+
// Determine if this plan tool call is the most recent one
|
|
132
|
+
const isPlanMsg =
|
|
133
|
+
msg.role === 'assistant' &&
|
|
134
|
+
msg.toolMeta &&
|
|
135
|
+
(msg.toolMeta.toolName === 'set_plan' || msg.toolMeta.toolName === 'update_plan');
|
|
136
|
+
const isLatestPlan =
|
|
137
|
+
isPlanMsg &&
|
|
138
|
+
!messages
|
|
139
|
+
.slice(idx + 1)
|
|
140
|
+
.some(
|
|
141
|
+
(m) =>
|
|
142
|
+
m.role === 'assistant' &&
|
|
143
|
+
m.toolMeta &&
|
|
144
|
+
(m.toolMeta.toolName === 'set_plan' || m.toolMeta.toolName === 'update_plan'),
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<ChatMessageBubble
|
|
149
|
+
key={msg.id}
|
|
150
|
+
message={msg}
|
|
151
|
+
isLatestPlan={!!isLatestPlan}
|
|
152
|
+
onEditAndResend={
|
|
153
|
+
!isStreaming
|
|
154
|
+
? (newContent) => {
|
|
155
|
+
useChatStore.getState().truncateFrom(msg.id);
|
|
156
|
+
onSendMessage(newContent);
|
|
157
|
+
}
|
|
158
|
+
: undefined
|
|
159
|
+
}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
})}
|
|
163
|
+
|
|
164
|
+
{/* Streaming indicator */}
|
|
165
|
+
{isStreaming && streamingText && (
|
|
166
|
+
<div className="flex gap-2 py-2">
|
|
167
|
+
<div className="flex items-start pt-1 shrink-0">
|
|
168
|
+
<div className="flex items-center justify-center rounded-full size-5 bg-primary/10">
|
|
169
|
+
<Bot className="size-3 text-primary" />
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
<div className="min-w-0 px-3 py-2 overflow-hidden text-xs border rounded-lg rounded-tl-sm bg-muted/30 border-border/40">
|
|
173
|
+
<MarkdownRenderer content={streamingText} />
|
|
174
|
+
<span className="inline-block ml-0.5 w-1.5 h-4 bg-primary/60 animate-pulse rounded-sm" />
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{isStreaming && !streamingText && (
|
|
180
|
+
<div className="flex items-center gap-2 py-2 text-xs ml-7 text-muted-foreground">
|
|
181
|
+
<Loader2 className="size-3 animate-spin text-primary/60" />
|
|
182
|
+
<span>Thinking…</span>
|
|
183
|
+
</div>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
{/* Error */}
|
|
187
|
+
{error && (
|
|
188
|
+
<div className="flex items-start gap-2 p-3 mt-2 text-sm rounded-lg bg-destructive/10 text-destructive">
|
|
189
|
+
<AlertCircle className="mt-0.5 size-4 shrink-0" />
|
|
190
|
+
<div className="flex-1 min-w-0">
|
|
191
|
+
<span>{error}</span>
|
|
192
|
+
</div>
|
|
193
|
+
<button
|
|
194
|
+
type="button"
|
|
195
|
+
onClick={() => useChatStore.getState().setError(null)}
|
|
196
|
+
className="shrink-0 p-0.5 rounded hover:bg-destructive/20 transition-colors"
|
|
197
|
+
title="Dismiss"
|
|
198
|
+
>
|
|
199
|
+
<X className="size-3" />
|
|
200
|
+
</button>
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
{/* Suggested follow-up actions */}
|
|
205
|
+
<SuggestionChips onSelect={onSendMessage} isStreaming={isStreaming} />
|
|
206
|
+
</div>
|
|
207
|
+
</ScrollArea>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// =====================================
|
|
212
|
+
// SuggestionChips
|
|
213
|
+
// =====================================
|
|
214
|
+
|
|
215
|
+
function SuggestionChips({
|
|
216
|
+
onSelect,
|
|
217
|
+
isStreaming,
|
|
218
|
+
}: {
|
|
219
|
+
onSelect: (prompt: string) => void;
|
|
220
|
+
isStreaming: boolean;
|
|
221
|
+
}) {
|
|
222
|
+
const suggestions = useChatStore((s) => s.suggestions);
|
|
223
|
+
if (suggestions.length === 0 || isStreaming) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div className="flex flex-wrap gap-1.5 mt-2 ml-7">
|
|
229
|
+
{suggestions.map((s, i) => (
|
|
230
|
+
<button
|
|
231
|
+
key={i}
|
|
232
|
+
type="button"
|
|
233
|
+
onClick={() => onSelect(s.prompt)}
|
|
234
|
+
className="px-2.5 py-1 text-xs rounded-full border border-border/60 bg-muted/40 text-foreground/80 hover:bg-primary/10 hover:border-primary/30 hover:text-primary transition-colors"
|
|
235
|
+
>
|
|
236
|
+
{s.label}
|
|
237
|
+
</button>
|
|
238
|
+
))}
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// =====================================
|
|
244
|
+
// ChatSuggestionPrompts
|
|
245
|
+
// =====================================
|
|
246
|
+
|
|
247
|
+
const SUGGESTION_PROMPTS = [
|
|
248
|
+
{ label: 'Add a node', text: 'Add a JQ transform node after my input that filters active items' },
|
|
249
|
+
{ label: 'Debug flow', text: 'Analyze my current flow and suggest improvements' },
|
|
250
|
+
{ label: 'Connect nodes', text: 'Connect the remaining unlinked nodes in my flow' },
|
|
251
|
+
{ label: 'Explain flow', text: 'Walk me through what this flow does step by step' },
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
function ChatSuggestionPrompts({ onSelect }: { onSelect: (text: string) => void }) {
|
|
255
|
+
return (
|
|
256
|
+
<div className="flex flex-col items-center gap-4 px-2">
|
|
257
|
+
<div className="flex items-center justify-center rounded-full size-10 bg-primary/10">
|
|
258
|
+
<MessageSquare className="size-5 text-primary" />
|
|
259
|
+
</div>
|
|
260
|
+
<div>
|
|
261
|
+
<p className="text-sm font-medium text-foreground">What can I help with?</p>
|
|
262
|
+
<p className="mt-0.5 text-xs text-muted-foreground">Build, edit, or debug your flows.</p>
|
|
263
|
+
</div>
|
|
264
|
+
<div className="flex flex-col w-full gap-1.5">
|
|
265
|
+
{SUGGESTION_PROMPTS.map((s) => (
|
|
266
|
+
<button
|
|
267
|
+
key={s.label}
|
|
268
|
+
type="button"
|
|
269
|
+
onClick={() => onSelect(s.text)}
|
|
270
|
+
className="flex items-center gap-2 px-3 py-2 text-xs text-left transition-colors border rounded-lg text-foreground/80 border-border/50 bg-muted/20 hover:bg-accent/50 hover:border-border"
|
|
271
|
+
>
|
|
272
|
+
<ChevronRight className="size-3 text-muted-foreground/40 shrink-0" />
|
|
273
|
+
<span>{s.label}</span>
|
|
274
|
+
</button>
|
|
275
|
+
))}
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// =====================================
|
|
282
|
+
// ChatMessageBubble
|
|
283
|
+
// =====================================
|
|
284
|
+
|
|
285
|
+
function ChatMessageBubble({
|
|
286
|
+
message,
|
|
287
|
+
isLatestPlan,
|
|
288
|
+
onEditAndResend,
|
|
289
|
+
}: {
|
|
290
|
+
message: ChatMessage;
|
|
291
|
+
isLatestPlan: boolean;
|
|
292
|
+
onEditAndResend?: (content: string) => void;
|
|
293
|
+
}) {
|
|
294
|
+
if (message.role === 'user') {
|
|
295
|
+
return <UserMessageBubble message={message} onEditAndResend={onEditAndResend} />;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (message.role === 'assistant' && message.toolMeta) {
|
|
299
|
+
return <ToolCallBubble toolMeta={message.toolMeta} isLatestPlan={isLatestPlan} />;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (message.role === 'assistant' && message.content.trim()) {
|
|
303
|
+
return <AssistantMessageBubble message={message} />;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// =====================================
|
|
310
|
+
// UserMessageBubble — with edit+resend
|
|
311
|
+
// =====================================
|
|
312
|
+
|
|
313
|
+
function UserMessageBubble({
|
|
314
|
+
message,
|
|
315
|
+
onEditAndResend,
|
|
316
|
+
}: {
|
|
317
|
+
message: ChatMessage;
|
|
318
|
+
onEditAndResend?: (content: string) => void;
|
|
319
|
+
}) {
|
|
320
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
321
|
+
const [editValue, setEditValue] = useState(message.content);
|
|
322
|
+
const editRef = useRef<HTMLTextAreaElement>(null);
|
|
323
|
+
const [collapsed, setCollapsed] = useState(true);
|
|
324
|
+
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
if (isEditing && editRef.current) {
|
|
327
|
+
editRef.current.focus();
|
|
328
|
+
editRef.current.selectionStart = editRef.current.value.length;
|
|
329
|
+
}
|
|
330
|
+
}, [isEditing]);
|
|
331
|
+
|
|
332
|
+
const handleSubmitEdit = useCallback(() => {
|
|
333
|
+
const trimmed = editValue.trim();
|
|
334
|
+
if (!trimmed || !onEditAndResend) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
setIsEditing(false);
|
|
338
|
+
onEditAndResend(trimmed);
|
|
339
|
+
}, [editValue, onEditAndResend]);
|
|
340
|
+
|
|
341
|
+
const handleEditKeyDown = useCallback(
|
|
342
|
+
(e: React.KeyboardEvent) => {
|
|
343
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
344
|
+
e.preventDefault();
|
|
345
|
+
handleSubmitEdit();
|
|
346
|
+
}
|
|
347
|
+
if (e.key === 'Escape') {
|
|
348
|
+
setIsEditing(false);
|
|
349
|
+
setEditValue(message.content);
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
[handleSubmitEdit, message.content],
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
if (isEditing) {
|
|
356
|
+
return (
|
|
357
|
+
<div className="flex justify-end py-1.5">
|
|
358
|
+
<div className="max-w-[85%] w-full">
|
|
359
|
+
<Textarea
|
|
360
|
+
ref={editRef}
|
|
361
|
+
value={editValue}
|
|
362
|
+
onChange={(e) => setEditValue(e.target.value)}
|
|
363
|
+
onKeyDown={handleEditKeyDown}
|
|
364
|
+
className="text-xs min-h-[40px] max-h-[120px] resize-none"
|
|
365
|
+
rows={2}
|
|
366
|
+
/>
|
|
367
|
+
<div className="flex justify-end gap-1 mt-1">
|
|
368
|
+
<Button
|
|
369
|
+
variant="ghost"
|
|
370
|
+
size="sm"
|
|
371
|
+
className="h-6 text-[10px] px-2"
|
|
372
|
+
onClick={() => {
|
|
373
|
+
setIsEditing(false);
|
|
374
|
+
setEditValue(message.content);
|
|
375
|
+
}}
|
|
376
|
+
>
|
|
377
|
+
Cancel
|
|
378
|
+
</Button>
|
|
379
|
+
<Button
|
|
380
|
+
variant="default"
|
|
381
|
+
size="sm"
|
|
382
|
+
className="h-6 text-[10px] px-2"
|
|
383
|
+
onClick={handleSubmitEdit}
|
|
384
|
+
disabled={!editValue.trim()}
|
|
385
|
+
>
|
|
386
|
+
Send
|
|
387
|
+
</Button>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const isLongContent = message.content.length > 300 || message.content.split('\n').length > 8;
|
|
395
|
+
|
|
396
|
+
// For long content, split into typed text (before first newline) and pasted body (after)
|
|
397
|
+
const firstNewline = message.content.indexOf('\n');
|
|
398
|
+
const typedPart = isLongContent && firstNewline > 0 ? message.content.slice(0, firstNewline) : '';
|
|
399
|
+
const pastedPart = isLongContent
|
|
400
|
+
? firstNewline > 0
|
|
401
|
+
? message.content.slice(firstNewline + 1)
|
|
402
|
+
: message.content
|
|
403
|
+
: '';
|
|
404
|
+
const pastedLineCount = isLongContent ? pastedPart.split('\n').length : 0;
|
|
405
|
+
const pastedCharCount = pastedPart.length;
|
|
406
|
+
|
|
407
|
+
return (
|
|
408
|
+
<div className="flex flex-col items-end py-1.5 group/user">
|
|
409
|
+
<div className="relative text-xs leading-relaxed min-w-0 text-foreground bg-primary/10 rounded-2xl rounded-tr-sm px-3 py-2 max-w-[95%]">
|
|
410
|
+
{isLongContent ? (
|
|
411
|
+
<div className="flex flex-col gap-1.5">
|
|
412
|
+
{typedPart && <span className="whitespace-pre-wrap">{typedPart}</span>}
|
|
413
|
+
<button
|
|
414
|
+
type="button"
|
|
415
|
+
onClick={() => setCollapsed(!collapsed)}
|
|
416
|
+
className="flex items-center gap-1.5 w-full rounded-md border border-foreground/10 bg-foreground/5 px-2 py-1.5 text-[10px] text-foreground/70 hover:bg-foreground/10 transition-colors"
|
|
417
|
+
>
|
|
418
|
+
{collapsed ? (
|
|
419
|
+
<ChevronRight className="size-3 shrink-0" />
|
|
420
|
+
) : (
|
|
421
|
+
<ChevronDown className="size-3 shrink-0" />
|
|
422
|
+
)}
|
|
423
|
+
<span className="font-medium">Pasted text</span>
|
|
424
|
+
<span className="ml-auto text-foreground/40">
|
|
425
|
+
{pastedLineCount} lines · {pastedCharCount} chars
|
|
426
|
+
</span>
|
|
427
|
+
</button>
|
|
428
|
+
{!collapsed && (
|
|
429
|
+
<div className="overflow-y-auto max-h-48 rounded-md border border-foreground/10 bg-foreground/5 px-2.5 py-2 text-[11px] leading-relaxed whitespace-pre-wrap font-mono overscroll-contain">
|
|
430
|
+
{pastedPart}
|
|
431
|
+
</div>
|
|
432
|
+
)}
|
|
433
|
+
</div>
|
|
434
|
+
) : (
|
|
435
|
+
<span className="whitespace-pre-wrap">{message.content}</span>
|
|
436
|
+
)}
|
|
437
|
+
{onEditAndResend && (
|
|
438
|
+
<button
|
|
439
|
+
type="button"
|
|
440
|
+
onClick={() => setIsEditing(true)}
|
|
441
|
+
className="absolute p-1 transition-opacity -translate-y-1/2 rounded opacity-0 -left-7 top-1/2 group-hover/user:opacity-100 text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
442
|
+
title="Edit and resend"
|
|
443
|
+
>
|
|
444
|
+
<Pencil className="size-3" />
|
|
445
|
+
</button>
|
|
446
|
+
)}
|
|
447
|
+
</div>
|
|
448
|
+
<span className="text-[10px] mt-0.5 text-muted-foreground/0 group-hover/user:text-muted-foreground/40 transition-colors">
|
|
449
|
+
{new Date(message.createdAt).toLocaleTimeString([], {
|
|
450
|
+
hour: '2-digit',
|
|
451
|
+
minute: '2-digit',
|
|
452
|
+
})}
|
|
453
|
+
</span>
|
|
454
|
+
</div>
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// =====================================
|
|
459
|
+
// AssistantMessageBubble — with copy
|
|
460
|
+
// =====================================
|
|
461
|
+
|
|
462
|
+
function AssistantMessageBubble({ message }: { message: ChatMessage }) {
|
|
463
|
+
const [copied, setCopied] = useState(false);
|
|
464
|
+
|
|
465
|
+
const handleCopy = useCallback(() => {
|
|
466
|
+
navigator.clipboard.writeText(message.content).then(() => {
|
|
467
|
+
setCopied(true);
|
|
468
|
+
setTimeout(() => setCopied(false), 1500);
|
|
469
|
+
});
|
|
470
|
+
}, [message.content]);
|
|
471
|
+
|
|
472
|
+
return (
|
|
473
|
+
<div className="flex flex-col items-start py-2 group/assistant">
|
|
474
|
+
<div className="flex items-center gap-1.5 mb-1">
|
|
475
|
+
<div className="flex items-center justify-center rounded-full size-4 bg-primary/10">
|
|
476
|
+
<Bot className="size-2.5 text-primary" />
|
|
477
|
+
</div>
|
|
478
|
+
<span className="text-[10px] text-muted-foreground/50 font-medium">Chat</span>
|
|
479
|
+
</div>
|
|
480
|
+
<div className="relative min-w-0 px-3 py-2 overflow-hidden text-xs border rounded-lg rounded-tl-sm text-foreground bg-muted/30 border-border/40 max-w-[95%]">
|
|
481
|
+
<MarkdownRenderer content={message.content} />
|
|
482
|
+
<button
|
|
483
|
+
type="button"
|
|
484
|
+
onClick={handleCopy}
|
|
485
|
+
className="absolute p-1 transition-opacity rounded opacity-0 right-1 bottom-1 group-hover/assistant:opacity-100 text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
486
|
+
title="Copy message"
|
|
487
|
+
>
|
|
488
|
+
{copied ? (
|
|
489
|
+
<ClipboardCheck className="text-success size-3" />
|
|
490
|
+
) : (
|
|
491
|
+
<Copy className="size-3" />
|
|
492
|
+
)}
|
|
493
|
+
</button>
|
|
494
|
+
</div>
|
|
495
|
+
<span className="text-[10px] mt-0.5 text-muted-foreground/0 group-hover/assistant:text-muted-foreground/40 transition-colors">
|
|
496
|
+
{new Date(message.createdAt).toLocaleTimeString([], {
|
|
497
|
+
hour: '2-digit',
|
|
498
|
+
minute: '2-digit',
|
|
499
|
+
})}
|
|
500
|
+
</span>
|
|
501
|
+
</div>
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// =====================================
|
|
506
|
+
// ToolCallBubble
|
|
507
|
+
// =====================================
|
|
508
|
+
|
|
509
|
+
function ToolCallBubble({
|
|
510
|
+
toolMeta,
|
|
511
|
+
isLatestPlan,
|
|
512
|
+
}: {
|
|
513
|
+
toolMeta: NonNullable<ChatMessage['toolMeta']>;
|
|
514
|
+
isLatestPlan: boolean;
|
|
515
|
+
}) {
|
|
516
|
+
const [open, setOpen] = useState(false);
|
|
517
|
+
|
|
518
|
+
const isPending = toolMeta.status === 'pending';
|
|
519
|
+
const isError = toolMeta.status === 'error';
|
|
520
|
+
const isPlanTool = toolMeta.toolName === 'set_plan' || toolMeta.toolName === 'update_plan';
|
|
521
|
+
const isCredentialSetup = toolMeta.toolName === 'suggest_credential_setup';
|
|
522
|
+
|
|
523
|
+
// Credential setup tools get a special inline rendering with an action button
|
|
524
|
+
if (isCredentialSetup && !isPending && !isError && toolMeta.result?.data) {
|
|
525
|
+
return <CredentialSetupBubble data={toolMeta.result.data as Record<string, unknown>} />;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Plan tools get a special inline rendering instead of collapsed JSON
|
|
529
|
+
if (isPlanTool && !isPending && !isError && toolMeta.result?.data) {
|
|
530
|
+
return (
|
|
531
|
+
<PlanStepsBubble
|
|
532
|
+
data={toolMeta.result.data as Record<string, unknown>}
|
|
533
|
+
isLatestPlan={isLatestPlan}
|
|
534
|
+
/>
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const statusIcon = isPending ? (
|
|
539
|
+
<Loader2 className="size-3 animate-spin text-primary/60" />
|
|
540
|
+
) : isError ? (
|
|
541
|
+
<XCircle className="size-3 text-destructive" />
|
|
542
|
+
) : (
|
|
543
|
+
<Check className="size-3 text-success" />
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
const hasResult = toolMeta.result !== null && toolMeta.result !== undefined;
|
|
547
|
+
const isExpandable = !isPending && hasResult;
|
|
548
|
+
|
|
549
|
+
// oxlint-disable typescript/no-non-null-assertion -- guarded by isExpandable which requires hasResult
|
|
550
|
+
const expandableData = isExpandable
|
|
551
|
+
? isError
|
|
552
|
+
? { error: toolMeta.result!.error }
|
|
553
|
+
: (toolMeta.result!.data ?? toolMeta.result)
|
|
554
|
+
: undefined;
|
|
555
|
+
// oxlint-enable typescript/no-non-null-assertion
|
|
556
|
+
|
|
557
|
+
const toolLabel = toolMeta.toolName.replace(/_/g, ' ');
|
|
558
|
+
|
|
559
|
+
const durationLabel = useMemo(() => {
|
|
560
|
+
if (!toolMeta.durationMs) {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
if (toolMeta.durationMs < 1000) {
|
|
564
|
+
return `${toolMeta.durationMs}ms`;
|
|
565
|
+
}
|
|
566
|
+
return `${(toolMeta.durationMs / 1000).toFixed(1)}s`;
|
|
567
|
+
}, [toolMeta.durationMs]);
|
|
568
|
+
|
|
569
|
+
const collapsedSummary = useMemo(() => {
|
|
570
|
+
if (isPending) {
|
|
571
|
+
return 'running…';
|
|
572
|
+
}
|
|
573
|
+
if (isError) {
|
|
574
|
+
return toolMeta.result?.error ?? 'failed';
|
|
575
|
+
}
|
|
576
|
+
if (!toolMeta.result?.data) {
|
|
577
|
+
return 'done';
|
|
578
|
+
}
|
|
579
|
+
const d = toolMeta.result.data;
|
|
580
|
+
if (typeof d === 'string') {
|
|
581
|
+
return d.length > 60 ? d.slice(0, 60) + '…' : d;
|
|
582
|
+
}
|
|
583
|
+
if (typeof d === 'object' && d !== null) {
|
|
584
|
+
const keys = Object.keys(d as Record<string, unknown>);
|
|
585
|
+
if (keys.length <= 3) {
|
|
586
|
+
return keys.join(', ');
|
|
587
|
+
}
|
|
588
|
+
return `${keys.length} fields`;
|
|
589
|
+
}
|
|
590
|
+
return 'done';
|
|
591
|
+
}, [isPending, isError, toolMeta.result]);
|
|
592
|
+
|
|
593
|
+
return (
|
|
594
|
+
<div className="my-1">
|
|
595
|
+
<Collapsible open={open} onOpenChange={isExpandable ? setOpen : undefined}>
|
|
596
|
+
<CollapsibleTrigger
|
|
597
|
+
disabled={!isExpandable}
|
|
598
|
+
className={cn(
|
|
599
|
+
'flex items-center gap-2 w-full rounded-lg px-2.5 py-0.5 text-[11px] transition-colors',
|
|
600
|
+
isPending && 'text-primary/80',
|
|
601
|
+
isError && 'text-destructive/80',
|
|
602
|
+
isExpandable && 'cursor-pointer hover:bg-muted/40',
|
|
603
|
+
!isExpandable && 'cursor-default',
|
|
604
|
+
)}
|
|
605
|
+
>
|
|
606
|
+
{statusIcon}
|
|
607
|
+
<span className="font-medium capitalize text-foreground/80 shrink-0">{toolLabel}</span>
|
|
608
|
+
{durationLabel && !open && (
|
|
609
|
+
<span className="text-[10px] text-muted-foreground/40 shrink-0">{durationLabel}</span>
|
|
610
|
+
)}
|
|
611
|
+
{!open && (
|
|
612
|
+
<span
|
|
613
|
+
className={cn(
|
|
614
|
+
'truncate text-[10px] ml-1',
|
|
615
|
+
isError ? 'text-destructive/60' : 'text-muted-foreground/50',
|
|
616
|
+
)}
|
|
617
|
+
>
|
|
618
|
+
{collapsedSummary}
|
|
619
|
+
</span>
|
|
620
|
+
)}
|
|
621
|
+
{isExpandable && (
|
|
622
|
+
<ChevronRight
|
|
623
|
+
className={cn(
|
|
624
|
+
'size-3 ml-auto shrink-0 text-muted-foreground/40 transition-transform duration-200',
|
|
625
|
+
open && 'rotate-90',
|
|
626
|
+
)}
|
|
627
|
+
/>
|
|
628
|
+
)}
|
|
629
|
+
</CollapsibleTrigger>
|
|
630
|
+
|
|
631
|
+
<CollapsibleContent>
|
|
632
|
+
<div className="mt-1 text-[10px]">
|
|
633
|
+
<ToolDataScrollable data={expandableData} isError={isError} />
|
|
634
|
+
</div>
|
|
635
|
+
</CollapsibleContent>
|
|
636
|
+
</Collapsible>
|
|
637
|
+
</div>
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// =====================================
|
|
642
|
+
// CredentialSetupBubble — renders suggest_credential_setup as an action card
|
|
643
|
+
// =====================================
|
|
644
|
+
|
|
645
|
+
function CredentialSetupBubble({ data }: { data: Record<string, unknown> }) {
|
|
646
|
+
const [showModal, setShowModal] = useState(false);
|
|
647
|
+
const createMutation = useCreateCredential();
|
|
648
|
+
|
|
649
|
+
const message = data.message as string | undefined;
|
|
650
|
+
|
|
651
|
+
return (
|
|
652
|
+
<div className="my-1.5 ml-6">
|
|
653
|
+
<div className="rounded-lg border border-border/60 bg-muted/20 px-3 py-2 text-[11px]">
|
|
654
|
+
{message && <div className="text-foreground/80 mb-2">{message}</div>}
|
|
655
|
+
<Button
|
|
656
|
+
variant="outline"
|
|
657
|
+
size="sm"
|
|
658
|
+
className="h-7 text-xs gap-1.5"
|
|
659
|
+
onClick={() => setShowModal(true)}
|
|
660
|
+
>
|
|
661
|
+
Set up credential
|
|
662
|
+
</Button>
|
|
663
|
+
</div>
|
|
664
|
+
|
|
665
|
+
<CreateCredentialModal
|
|
666
|
+
open={showModal}
|
|
667
|
+
onClose={() => setShowModal(false)}
|
|
668
|
+
onSubmit={(credentialData) => {
|
|
669
|
+
createMutation.mutate(credentialData, {
|
|
670
|
+
onSuccess: () => setShowModal(false),
|
|
671
|
+
});
|
|
672
|
+
}}
|
|
673
|
+
isLoading={createMutation.isPending}
|
|
674
|
+
/>
|
|
675
|
+
</div>
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// =====================================
|
|
680
|
+
// PlanStepsBubble — renders set_plan / update_plan as a step list
|
|
681
|
+
// =====================================
|
|
682
|
+
|
|
683
|
+
function PlanStepsBubble({
|
|
684
|
+
data,
|
|
685
|
+
isLatestPlan,
|
|
686
|
+
}: {
|
|
687
|
+
data: Record<string, unknown>;
|
|
688
|
+
isLatestPlan: boolean;
|
|
689
|
+
}) {
|
|
690
|
+
const steps = (data.steps ?? []) as Array<{
|
|
691
|
+
index: number;
|
|
692
|
+
title: string;
|
|
693
|
+
status: string;
|
|
694
|
+
}>;
|
|
695
|
+
const summary = data.summary as string | undefined;
|
|
696
|
+
const progress = data.progress as string | undefined;
|
|
697
|
+
|
|
698
|
+
const stepIcon = (status: string) => {
|
|
699
|
+
switch (status) {
|
|
700
|
+
case 'done':
|
|
701
|
+
return <CheckCircle2 className="size-3.5 text-success shrink-0" />;
|
|
702
|
+
case 'in_progress':
|
|
703
|
+
// Only animate spinner for the most recent plan bubble
|
|
704
|
+
return isLatestPlan ? (
|
|
705
|
+
<Loader2 className="size-3.5 text-primary animate-spin shrink-0" />
|
|
706
|
+
) : (
|
|
707
|
+
<CheckCircle2 className="size-3.5 text-success shrink-0" />
|
|
708
|
+
);
|
|
709
|
+
case 'skipped':
|
|
710
|
+
return <SkipForward className="size-3.5 text-muted-foreground/50 shrink-0" />;
|
|
711
|
+
default:
|
|
712
|
+
return <Circle className="size-3.5 text-muted-foreground/40 shrink-0" />;
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
return (
|
|
717
|
+
<div className="my-1.5 ml-6">
|
|
718
|
+
<div className="rounded-lg border border-border/60 bg-muted/20 px-3 py-2 text-[11px]">
|
|
719
|
+
{summary && <div className="font-medium text-foreground/80 mb-1.5">{summary}</div>}
|
|
720
|
+
{progress && <div className="text-[10px] text-muted-foreground/60 mb-1.5">{progress}</div>}
|
|
721
|
+
<ol className="space-y-1">
|
|
722
|
+
{steps.map((step) => (
|
|
723
|
+
<li key={step.index} className="flex items-start gap-1.5">
|
|
724
|
+
<span className="mt-0.5">{stepIcon(step.status)}</span>
|
|
725
|
+
<span
|
|
726
|
+
className={cn(
|
|
727
|
+
'leading-snug',
|
|
728
|
+
step.status === 'done' &&
|
|
729
|
+
'text-foreground/60 line-through decoration-foreground/20',
|
|
730
|
+
step.status === 'skipped' &&
|
|
731
|
+
'text-muted-foreground/40 line-through decoration-muted-foreground/20',
|
|
732
|
+
step.status === 'in_progress' &&
|
|
733
|
+
(isLatestPlan
|
|
734
|
+
? 'text-foreground/90 font-medium'
|
|
735
|
+
: 'text-foreground/60 line-through decoration-foreground/20'),
|
|
736
|
+
step.status === 'pending' &&
|
|
737
|
+
(isLatestPlan ? 'text-foreground/70' : 'text-foreground/60'),
|
|
738
|
+
)}
|
|
739
|
+
>
|
|
740
|
+
{step.title}
|
|
741
|
+
</span>
|
|
742
|
+
</li>
|
|
743
|
+
))}
|
|
744
|
+
</ol>
|
|
745
|
+
</div>
|
|
746
|
+
</div>
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// =====================================
|
|
751
|
+
// ToolDataScrollable
|
|
752
|
+
// =====================================
|
|
753
|
+
|
|
754
|
+
function ToolDataScrollable({ data, isError }: { data: unknown; isError: boolean }) {
|
|
755
|
+
const formatted = useMemo(() => formatToolData(data), [data]);
|
|
756
|
+
|
|
757
|
+
return (
|
|
758
|
+
<pre
|
|
759
|
+
className={cn(
|
|
760
|
+
'text-[10px] leading-relaxed whitespace-pre-wrap break-all p-2.5 rounded-lg border overflow-auto max-h-45 font-mono',
|
|
761
|
+
isError
|
|
762
|
+
? 'text-destructive/80 border-destructive/20 bg-destructive/5'
|
|
763
|
+
: 'text-muted-foreground border-border/50 bg-muted/20',
|
|
764
|
+
)}
|
|
765
|
+
>
|
|
766
|
+
{formatted}
|
|
767
|
+
</pre>
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function formatToolData(data: unknown): string {
|
|
772
|
+
if (data === null || data === undefined) {
|
|
773
|
+
return '(empty)';
|
|
774
|
+
}
|
|
775
|
+
if (typeof data === 'string') {
|
|
776
|
+
return data;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
try {
|
|
780
|
+
const json = JSON.stringify(data, null, 2);
|
|
781
|
+
if (json.length > 4000) {
|
|
782
|
+
return json.slice(0, 4000) + '\n… (truncated)';
|
|
783
|
+
}
|
|
784
|
+
return json;
|
|
785
|
+
} catch {
|
|
786
|
+
return String(data);
|
|
787
|
+
}
|
|
788
|
+
}
|