@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.
Files changed (419) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/dist/Invect-CWpIwZ5F.js +92738 -0
  4. package/dist/Invect.d.ts +25 -0
  5. package/dist/InvectShell.d.ts +14 -0
  6. package/dist/api/agent-tools.api.d.ts +1 -0
  7. package/dist/api/client.d.ts +207 -0
  8. package/dist/api/credentials.api.d.ts +47 -0
  9. package/dist/api/executions.api.d.ts +43 -0
  10. package/dist/api/flows.api.d.ts +100 -0
  11. package/dist/api/index.d.ts +9 -0
  12. package/dist/api/node-data.api.d.ts +66 -0
  13. package/dist/api/query-keys.d.ts +22 -0
  14. package/dist/api/triggers.api.d.ts +44 -0
  15. package/dist/api/types.d.ts +147 -0
  16. package/dist/api/use-flow-run-stream.d.ts +12 -0
  17. package/dist/assets/invect-branding.d.ts +4 -0
  18. package/dist/assets/provider-icons/index.d.ts +8 -0
  19. package/dist/babel-C9OtljFZ.js +9721 -0
  20. package/dist/components/PageLayout.d.ts +17 -0
  21. package/dist/components/chat/ChatInput.d.ts +17 -0
  22. package/dist/components/chat/ChatMessageList.d.ts +14 -0
  23. package/dist/components/chat/ChatModelSelector.d.ts +11 -0
  24. package/dist/components/chat/ChatPanel.d.ts +19 -0
  25. package/dist/components/chat/ChatPromptOverlay.d.ts +13 -0
  26. package/dist/components/chat/ChatProviderSelector.d.ts +9 -0
  27. package/dist/components/chat/ChatSettingsPanel.d.ts +11 -0
  28. package/dist/components/chat/InlineCredentialSetup.d.ts +9 -0
  29. package/dist/components/chat/MarkdownRenderer.d.ts +7 -0
  30. package/dist/components/chat/chat-memory.d.ts +21 -0
  31. package/dist/components/chat/chat.store.d.ts +416 -0
  32. package/dist/components/chat/index.d.ts +5 -0
  33. package/dist/components/chat/use-chat.d.ts +28 -0
  34. package/dist/components/credentials/CreateCredentialModal.d.ts +13 -0
  35. package/dist/components/credentials/CredentialDetailDialog.d.ts +17 -0
  36. package/dist/components/credentials/EditCredentialModal.d.ts +11 -0
  37. package/dist/components/credentials/OAuth2ConnectButton.d.ts +38 -0
  38. package/dist/components/credentials/OAuth2ProviderSelector.d.ts +15 -0
  39. package/dist/components/credentials/credential-utils.d.ts +12 -0
  40. package/dist/components/credentials/index.d.ts +9 -0
  41. package/dist/components/dashboard/FailedRunsAlert.d.ts +3 -0
  42. package/dist/components/dashboard/FlowCard.d.ts +7 -0
  43. package/dist/components/dashboard/RecentActivityTable.d.ts +9 -0
  44. package/dist/components/dashboard/StatCard.d.ts +10 -0
  45. package/dist/components/dashboard/index.d.ts +5 -0
  46. package/dist/components/dashboard/status-helpers.d.ts +5 -0
  47. package/dist/components/flow-editor/ActionsSidebar.d.ts +2 -0
  48. package/dist/components/flow-editor/FlowEditor.d.ts +21 -0
  49. package/dist/components/flow-editor/FlowHeader.d.ts +9 -0
  50. package/dist/components/flow-editor/FlowLayout.d.ts +24 -0
  51. package/dist/components/flow-editor/ModeSwitcher.d.ts +7 -0
  52. package/dist/components/flow-editor/NodeSidebar.d.ts +24 -0
  53. package/dist/components/flow-editor/RunControls.d.ts +12 -0
  54. package/dist/components/flow-editor/ToolConfigPanel.d.ts +16 -0
  55. package/dist/components/flow-editor/ValidationPanel.d.ts +5 -0
  56. package/dist/components/flow-editor/flow-editor.store.d.ts +1 -0
  57. package/dist/components/flow-editor/index.d.ts +6 -0
  58. package/dist/components/flow-editor/inline-edit.d.ts +10 -0
  59. package/dist/components/flow-editor/node-config-panel/ConfigFieldWithTemplate.d.ts +26 -0
  60. package/dist/components/flow-editor/node-config-panel/CredentialCombobox.d.ts +21 -0
  61. package/dist/components/flow-editor/node-config-panel/CredentialsSection.d.ts +19 -0
  62. package/dist/components/flow-editor/node-config-panel/DroppableInput.d.ts +20 -0
  63. package/dist/components/flow-editor/node-config-panel/DynamicSelectField.d.ts +22 -0
  64. package/dist/components/flow-editor/node-config-panel/JsonPreviewPanel.d.ts +25 -0
  65. package/dist/components/flow-editor/node-config-panel/NodeConfigPanel.d.ts +14 -0
  66. package/dist/components/flow-editor/node-config-panel/NodeConfigPanelHeader.d.ts +15 -0
  67. package/dist/components/flow-editor/node-config-panel/ParametersSection.d.ts +16 -0
  68. package/dist/components/flow-editor/node-config-panel/SearchableSelectField.d.ts +17 -0
  69. package/dist/components/flow-editor/node-config-panel/SwitchCasesField.d.ts +18 -0
  70. package/dist/components/flow-editor/node-config-panel/hooks/index.d.ts +2 -0
  71. package/dist/components/flow-editor/node-config-panel/hooks/use-node-config-panel-state.d.ts +24 -0
  72. package/dist/components/flow-editor/node-config-panel/hooks/use-node-execution.d.ts +46 -0
  73. package/dist/components/flow-editor/node-config-panel/hooks/use-upstream-slots.d.ts +16 -0
  74. package/dist/components/flow-editor/node-config-panel/panels/AgentToolsPanel.d.ts +18 -0
  75. package/dist/components/flow-editor/node-config-panel/panels/ConfigurationPanel.d.ts +49 -0
  76. package/dist/components/flow-editor/node-config-panel/panels/DataMapperPane.d.ts +40 -0
  77. package/dist/components/flow-editor/node-config-panel/panels/InputPanel.d.ts +49 -0
  78. package/dist/components/flow-editor/node-config-panel/panels/OutputPanel.d.ts +7 -0
  79. package/dist/components/flow-editor/node-config-panel/panels/index.d.ts +4 -0
  80. package/dist/components/flow-editor/node-config-panel/types.d.ts +19 -0
  81. package/dist/components/flow-editor/node-config-panel/use-node-config-panel-store.d.ts +49 -0
  82. package/dist/components/flow-editor/node-config-panel/use-node-config-state.d.ts +26 -0
  83. package/dist/components/flow-editor/node-config-panel/use-run-node.d.ts +16 -0
  84. package/dist/components/flow-editor/node-config-panel/utils.d.ts +9 -0
  85. package/dist/components/flow-editor/serialize-to-sdk.d.ts +20 -0
  86. package/dist/components/flow-editor/toolbar-context.d.ts +2 -0
  87. package/dist/components/flow-editor/use-copy-paste.d.ts +7 -0
  88. package/dist/components/flow-editor/use-copy-paste.types.d.ts +38 -0
  89. package/dist/components/flow-editor/use-flow-editor.d.ts +44 -0
  90. package/dist/components/flow-runs-table/FlowRunsTable.d.ts +6 -0
  91. package/dist/components/flow-runs-table/index.d.ts +1 -0
  92. package/dist/components/flow-viewer/FlowRunsView.d.ts +7 -0
  93. package/dist/components/flow-viewer/FlowStatusView.d.ts +21 -0
  94. package/dist/components/flow-viewer/RunSelector.d.ts +13 -0
  95. package/dist/components/flow-viewer/RunsSidebar.d.ts +14 -0
  96. package/dist/components/flow-viewer/agent-tool-executions-list.d.ts +7 -0
  97. package/dist/components/flow-viewer/index.d.ts +1 -0
  98. package/dist/components/flow-viewer/logs-panel.d.ts +18 -0
  99. package/dist/components/flow-viewer/use-execution-log-data.d.ts +113 -0
  100. package/dist/components/graph/BatchFlowEdge.d.ts +33 -0
  101. package/dist/components/graph/LayoutSelector.d.ts +9 -0
  102. package/dist/components/graph/index.d.ts +47 -0
  103. package/dist/components/graph/styleUtils.d.ts +124 -0
  104. package/dist/components/nodes/AgentConfigPanel.d.ts +24 -0
  105. package/dist/components/nodes/AgentNode.d.ts +8 -0
  106. package/dist/components/nodes/AgentToolsBox.d.ts +41 -0
  107. package/dist/components/nodes/NodeAppendix.d.ts +19 -0
  108. package/dist/components/nodes/NodeStatusIndicator.d.ts +30 -0
  109. package/dist/components/nodes/NodeViewContext.d.ts +18 -0
  110. package/dist/components/nodes/ToolParamField.d.ts +28 -0
  111. package/dist/components/nodes/ToolSelectorModal.d.ts +80 -0
  112. package/dist/components/nodes/ToolSelectorParts.d.ts +30 -0
  113. package/dist/components/nodes/UniversalNode.d.ts +2 -0
  114. package/dist/components/nodes/createContextAwareNodes.d.ts +6 -0
  115. package/dist/components/nodes/index.d.ts +22 -0
  116. package/dist/components/nodes/nodeRegistry.d.ts +13 -0
  117. package/dist/components/nodes/withNodeContext.d.ts +7 -0
  118. package/dist/components/shared/InvectLoader.d.ts +8 -0
  119. package/dist/components/shared/InvectLogo.d.ts +9 -0
  120. package/dist/components/shared/ProviderIcon.d.ts +23 -0
  121. package/dist/components/side-menu/side-menu.d.ts +4 -0
  122. package/dist/components/sidebar/BaseSidebar.d.ts +17 -0
  123. package/dist/components/sidebar/index.d.ts +1 -0
  124. package/dist/components/triggers/CronPreview.d.ts +12 -0
  125. package/dist/components/triggers/index.d.ts +1 -0
  126. package/dist/components/ui/alert-dialog.d.ts +18 -0
  127. package/dist/components/ui/badge.d.ts +9 -0
  128. package/dist/components/ui/button.d.ts +13 -0
  129. package/dist/components/ui/card.d.ts +9 -0
  130. package/dist/components/ui/codemirror-js-editor.d.ts +25 -0
  131. package/dist/components/ui/codemirror-json-editor.d.ts +18 -0
  132. package/dist/components/ui/codemirror-nunjucks-editor.d.ts +13 -0
  133. package/dist/components/ui/codemirror-vscode-theme.d.ts +24 -0
  134. package/dist/components/ui/collapsible.d.ts +6 -0
  135. package/dist/components/ui/command.d.ts +18 -0
  136. package/dist/components/ui/dialog.d.ts +18 -0
  137. package/dist/components/ui/dropdown-menu.d.ts +25 -0
  138. package/dist/components/ui/empty-state.d.ts +21 -0
  139. package/dist/components/ui/input.d.ts +3 -0
  140. package/dist/components/ui/label.d.ts +4 -0
  141. package/dist/components/ui/popover.d.ts +10 -0
  142. package/dist/components/ui/resizable.d.ts +8 -0
  143. package/dist/components/ui/scroll-area.d.ts +5 -0
  144. package/dist/components/ui/select.d.ts +18 -0
  145. package/dist/components/ui/separator.d.ts +4 -0
  146. package/dist/components/ui/slider.d.ts +4 -0
  147. package/dist/components/ui/switch.d.ts +3 -0
  148. package/dist/components/ui/table.d.ts +10 -0
  149. package/dist/components/ui/textarea.d.ts +3 -0
  150. package/dist/components/ui/tooltip.d.ts +7 -0
  151. package/dist/components/ui/tree-view.d.ts +107 -0
  152. package/dist/contexts/AgentToolCallbacksContext.d.ts +23 -0
  153. package/dist/contexts/ApiContext.d.ts +11 -0
  154. package/dist/contexts/FlowDataContext.d.ts +9 -0
  155. package/dist/contexts/NodeRegistryContext.d.ts +14 -0
  156. package/dist/contexts/PluginRegistryContext.d.ts +39 -0
  157. package/dist/contexts/ThemeProvider.d.ts +18 -0
  158. package/dist/contexts/ValidationContext.d.ts +22 -0
  159. package/dist/demo/DemoInvect.d.ts +11 -0
  160. package/dist/demo/FlowViewer.d.ts +31 -0
  161. package/dist/demo/demo-api-client.d.ts +33 -0
  162. package/dist/demo/index.d.ts +6 -0
  163. package/dist/demo/sample-data.d.ts +1538 -0
  164. package/dist/demo.d.ts +2 -0
  165. package/dist/demo.js +2774 -0
  166. package/dist/estree-ClbRfS-1.js +7076 -0
  167. package/dist/fonts/geist-cyrillic-wght-normal.woff2 +0 -0
  168. package/dist/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
  169. package/dist/fonts/geist-latin-wght-normal.woff2 +0 -0
  170. package/dist/fonts/iosevka-latin-400-normal.woff2 +0 -0
  171. package/dist/hooks/index.d.ts +1 -0
  172. package/dist/hooks/use-document-title.d.ts +1 -0
  173. package/dist/hooks/use-flow-data.d.ts +22 -0
  174. package/dist/hooks/use-invect-portal-class.d.ts +21 -0
  175. package/dist/hooks/useFlowEditorStore.d.ts +1 -0
  176. package/dist/index.css +3 -0
  177. package/dist/index.d.ts +22 -0
  178. package/dist/index.js +717 -0
  179. package/dist/lib/utils.d.ts +2 -0
  180. package/dist/prettier.d.ts +13 -0
  181. package/dist/routes/all-flow-runs.d.ts +5 -0
  182. package/dist/routes/credentials.d.ts +5 -0
  183. package/dist/routes/flow-route-layout.d.ts +19 -0
  184. package/dist/routes/flow-runs.d.ts +5 -0
  185. package/dist/routes/flow.d.ts +5 -0
  186. package/dist/routes/home.d.ts +5 -0
  187. package/dist/services/index.d.ts +1 -0
  188. package/dist/standalone-C3Df7W52.js +3463 -0
  189. package/dist/stores/executionViewStore.d.ts +64 -0
  190. package/dist/stores/flow-editor.store.d.ts +137 -0
  191. package/dist/stores/flowEditorStore.d.ts +1 -0
  192. package/dist/stores/index.d.ts +2 -0
  193. package/dist/stores/uiStore.d.ts +45 -0
  194. package/dist/types/agent-tools.types.d.ts +53 -0
  195. package/dist/types/index.d.ts +2 -0
  196. package/dist/types/node-definition.types.d.ts +85 -0
  197. package/dist/types/plugin.types.d.ts +100 -0
  198. package/dist/utils/credentialBranding.d.ts +8 -0
  199. package/dist/utils/credentialFiltering.d.ts +20 -0
  200. package/dist/utils/flowTransformations.d.ts +16 -0
  201. package/dist/utils/layoutUtils.d.ts +23 -0
  202. package/dist/utils/nodeReferenceUtils.d.ts +37 -0
  203. package/dist/vendor.d.ts +5 -0
  204. package/package.json +130 -0
  205. package/src/.DS_Store +0 -0
  206. package/src/Invect.tsx +229 -0
  207. package/src/InvectShell.tsx +55 -0
  208. package/src/api/agent-tools.api.ts +23 -0
  209. package/src/api/client.ts +899 -0
  210. package/src/api/credentials.api.ts +197 -0
  211. package/src/api/executions.api.ts +228 -0
  212. package/src/api/flows.api.ts +195 -0
  213. package/src/api/index.ts +17 -0
  214. package/src/api/node-data.api.ts +167 -0
  215. package/src/api/query-keys.ts +44 -0
  216. package/src/api/triggers.api.ts +120 -0
  217. package/src/api/types.ts +212 -0
  218. package/src/api/use-flow-run-stream.ts +206 -0
  219. package/src/app.css +560 -0
  220. package/src/assets/.DS_Store +0 -0
  221. package/src/assets/favicon.ico +0 -0
  222. package/src/assets/fonts/geist-cyrillic-wght-normal.woff2 +0 -0
  223. package/src/assets/fonts/geist-latin-ext-wght-normal.woff2 +0 -0
  224. package/src/assets/fonts/geist-latin-wght-normal.woff2 +0 -0
  225. package/src/assets/fonts/iosevka-latin-400-normal.woff2 +0 -0
  226. package/src/assets/invect-branding.ts +51 -0
  227. package/src/assets/provider-icons/anthropic.svg +1 -0
  228. package/src/assets/provider-icons/anthropic_light.svg +1 -0
  229. package/src/assets/provider-icons/github.svg +1 -0
  230. package/src/assets/provider-icons/github_light.svg +1 -0
  231. package/src/assets/provider-icons/gmail.svg +1 -0
  232. package/src/assets/provider-icons/google_calendar.svg +1 -0
  233. package/src/assets/provider-icons/google_docs.svg +1 -0
  234. package/src/assets/provider-icons/google_drive.svg +1 -0
  235. package/src/assets/provider-icons/google_sheets.svg +1 -0
  236. package/src/assets/provider-icons/index.ts +55 -0
  237. package/src/assets/provider-icons/linear.svg +1 -0
  238. package/src/assets/provider-icons/openai.svg +1 -0
  239. package/src/assets/provider-icons/postgres.svg +1 -0
  240. package/src/assets/provider-icons/slack.svg +1 -0
  241. package/src/assets/small-loader-dark.svg +22 -0
  242. package/src/assets/small-loader-light.svg +22 -0
  243. package/src/assets/small.svg +7 -0
  244. package/src/components/.DS_Store +0 -0
  245. package/src/components/PageLayout.tsx +55 -0
  246. package/src/components/chat/ChatInput.tsx +115 -0
  247. package/src/components/chat/ChatMessageList.tsx +788 -0
  248. package/src/components/chat/ChatModelSelector.tsx +208 -0
  249. package/src/components/chat/ChatPanel.tsx +243 -0
  250. package/src/components/chat/ChatPromptOverlay.tsx +150 -0
  251. package/src/components/chat/ChatProviderSelector.tsx +135 -0
  252. package/src/components/chat/ChatSettingsPanel.tsx +277 -0
  253. package/src/components/chat/InlineCredentialSetup.tsx +343 -0
  254. package/src/components/chat/MarkdownRenderer.tsx +140 -0
  255. package/src/components/chat/chat-memory.ts +88 -0
  256. package/src/components/chat/chat.store.ts +479 -0
  257. package/src/components/chat/index.ts +5 -0
  258. package/src/components/chat/use-chat.ts +473 -0
  259. package/src/components/credentials/CreateCredentialModal.tsx +609 -0
  260. package/src/components/credentials/CredentialDetailDialog.tsx +882 -0
  261. package/src/components/credentials/EditCredentialModal.tsx +399 -0
  262. package/src/components/credentials/OAuth2ConnectButton.tsx +288 -0
  263. package/src/components/credentials/OAuth2ProviderSelector.tsx +360 -0
  264. package/src/components/credentials/credential-utils.ts +99 -0
  265. package/src/components/credentials/index.ts +10 -0
  266. package/src/components/dashboard/FailedRunsAlert.tsx +67 -0
  267. package/src/components/dashboard/FlowCard.tsx +64 -0
  268. package/src/components/dashboard/RecentActivityTable.tsx +92 -0
  269. package/src/components/dashboard/StatCard.tsx +32 -0
  270. package/src/components/dashboard/index.ts +5 -0
  271. package/src/components/dashboard/status-helpers.tsx +102 -0
  272. package/src/components/flow-editor/ActionsSidebar.tsx +503 -0
  273. package/src/components/flow-editor/FlowEditor.tsx +1002 -0
  274. package/src/components/flow-editor/FlowHeader.tsx +87 -0
  275. package/src/components/flow-editor/FlowLayout.tsx +117 -0
  276. package/src/components/flow-editor/ModeSwitcher.tsx +49 -0
  277. package/src/components/flow-editor/NodeSidebar.tsx +343 -0
  278. package/src/components/flow-editor/RunControls.tsx +109 -0
  279. package/src/components/flow-editor/ToolConfigPanel.tsx +434 -0
  280. package/src/components/flow-editor/ValidationPanel.tsx +167 -0
  281. package/src/components/flow-editor/flow-editor.store.ts +2 -0
  282. package/src/components/flow-editor/index.ts +6 -0
  283. package/src/components/flow-editor/inline-edit.tsx +111 -0
  284. package/src/components/flow-editor/node-config-panel/ConfigFieldWithTemplate.tsx +334 -0
  285. package/src/components/flow-editor/node-config-panel/CredentialCombobox.tsx +217 -0
  286. package/src/components/flow-editor/node-config-panel/CredentialsSection.tsx +154 -0
  287. package/src/components/flow-editor/node-config-panel/DroppableInput.tsx +45 -0
  288. package/src/components/flow-editor/node-config-panel/DynamicSelectField.tsx +223 -0
  289. package/src/components/flow-editor/node-config-panel/JsonPreviewPanel.tsx +134 -0
  290. package/src/components/flow-editor/node-config-panel/NodeConfigPanel.tsx +650 -0
  291. package/src/components/flow-editor/node-config-panel/NodeConfigPanelHeader.tsx +91 -0
  292. package/src/components/flow-editor/node-config-panel/ParametersSection.tsx +144 -0
  293. package/src/components/flow-editor/node-config-panel/SearchableSelectField.tsx +126 -0
  294. package/src/components/flow-editor/node-config-panel/SwitchCasesField.tsx +212 -0
  295. package/src/components/flow-editor/node-config-panel/hooks/index.ts +2 -0
  296. package/src/components/flow-editor/node-config-panel/hooks/use-node-config-panel-state.ts +284 -0
  297. package/src/components/flow-editor/node-config-panel/hooks/use-node-execution.ts +287 -0
  298. package/src/components/flow-editor/node-config-panel/hooks/use-upstream-slots.ts +310 -0
  299. package/src/components/flow-editor/node-config-panel/panels/AgentToolsPanel.tsx +837 -0
  300. package/src/components/flow-editor/node-config-panel/panels/ConfigurationPanel.tsx +383 -0
  301. package/src/components/flow-editor/node-config-panel/panels/DataMapperPane.tsx +456 -0
  302. package/src/components/flow-editor/node-config-panel/panels/InputPanel.tsx +338 -0
  303. package/src/components/flow-editor/node-config-panel/panels/OutputPanel.tsx +109 -0
  304. package/src/components/flow-editor/node-config-panel/panels/index.ts +4 -0
  305. package/src/components/flow-editor/node-config-panel/types.ts +20 -0
  306. package/src/components/flow-editor/node-config-panel/use-node-config-panel-store.ts +283 -0
  307. package/src/components/flow-editor/node-config-panel/use-node-config-state.ts +172 -0
  308. package/src/components/flow-editor/node-config-panel/use-run-node.ts +147 -0
  309. package/src/components/flow-editor/node-config-panel/utils.ts +73 -0
  310. package/src/components/flow-editor/serialize-to-sdk.ts +204 -0
  311. package/src/components/flow-editor/toolbar-context.ts +9 -0
  312. package/src/components/flow-editor/use-copy-paste.ts +575 -0
  313. package/src/components/flow-editor/use-copy-paste.types.ts +35 -0
  314. package/src/components/flow-editor/use-flow-editor.ts +241 -0
  315. package/src/components/flow-runs-table/FlowRunsTable.tsx +631 -0
  316. package/src/components/flow-runs-table/index.ts +1 -0
  317. package/src/components/flow-viewer/FlowRunsView.tsx +268 -0
  318. package/src/components/flow-viewer/FlowStatusView.tsx +351 -0
  319. package/src/components/flow-viewer/RunSelector.tsx +422 -0
  320. package/src/components/flow-viewer/RunsSidebar.tsx +125 -0
  321. package/src/components/flow-viewer/agent-tool-executions-list.tsx +298 -0
  322. package/src/components/flow-viewer/index.ts +1 -0
  323. package/src/components/flow-viewer/logs-panel.tsx +567 -0
  324. package/src/components/flow-viewer/use-execution-log-data.ts +374 -0
  325. package/src/components/graph/BatchFlowEdge.tsx +229 -0
  326. package/src/components/graph/LayoutSelector.tsx +42 -0
  327. package/src/components/graph/index.ts +61 -0
  328. package/src/components/graph/styleUtils.ts +375 -0
  329. package/src/components/nodes/.DS_Store +0 -0
  330. package/src/components/nodes/AgentConfigPanel.tsx +1033 -0
  331. package/src/components/nodes/AgentNode.tsx +298 -0
  332. package/src/components/nodes/AgentToolsBox.tsx +193 -0
  333. package/src/components/nodes/NodeAppendix.tsx +98 -0
  334. package/src/components/nodes/NodeStatusIndicator.tsx +74 -0
  335. package/src/components/nodes/NodeViewContext.tsx +45 -0
  336. package/src/components/nodes/ToolParamField.tsx +282 -0
  337. package/src/components/nodes/ToolSelectorModal.tsx +648 -0
  338. package/src/components/nodes/ToolSelectorParts.tsx +505 -0
  339. package/src/components/nodes/UniversalNode.tsx +356 -0
  340. package/src/components/nodes/createContextAwareNodes.ts +19 -0
  341. package/src/components/nodes/index.ts +45 -0
  342. package/src/components/nodes/nodeRegistry.ts +50 -0
  343. package/src/components/nodes/withNodeContext.tsx +55 -0
  344. package/src/components/shared/InvectLoader.tsx +59 -0
  345. package/src/components/shared/InvectLogo.tsx +59 -0
  346. package/src/components/shared/ProviderIcon.tsx +115 -0
  347. package/src/components/side-menu/side-menu.tsx +267 -0
  348. package/src/components/sidebar/BaseSidebar.tsx +148 -0
  349. package/src/components/sidebar/index.ts +1 -0
  350. package/src/components/triggers/CronPreview.tsx +243 -0
  351. package/src/components/triggers/index.ts +1 -0
  352. package/src/components/ui/alert-dialog.tsx +152 -0
  353. package/src/components/ui/badge.tsx +39 -0
  354. package/src/components/ui/button.tsx +58 -0
  355. package/src/components/ui/card.tsx +75 -0
  356. package/src/components/ui/codemirror-js-editor.tsx +432 -0
  357. package/src/components/ui/codemirror-json-editor.tsx +816 -0
  358. package/src/components/ui/codemirror-nunjucks-editor.tsx +451 -0
  359. package/src/components/ui/codemirror-vscode-theme.ts +243 -0
  360. package/src/components/ui/collapsible.tsx +12 -0
  361. package/src/components/ui/command.tsx +162 -0
  362. package/src/components/ui/dialog.tsx +140 -0
  363. package/src/components/ui/dropdown-menu.tsx +232 -0
  364. package/src/components/ui/empty-state.tsx +93 -0
  365. package/src/components/ui/input.tsx +26 -0
  366. package/src/components/ui/label.tsx +19 -0
  367. package/src/components/ui/popover.tsx +53 -0
  368. package/src/components/ui/resizable.tsx +61 -0
  369. package/src/components/ui/scroll-area.tsx +56 -0
  370. package/src/components/ui/select.tsx +179 -0
  371. package/src/components/ui/separator.tsx +26 -0
  372. package/src/components/ui/slider.tsx +58 -0
  373. package/src/components/ui/switch.tsx +22 -0
  374. package/src/components/ui/table.tsx +90 -0
  375. package/src/components/ui/textarea.tsx +23 -0
  376. package/src/components/ui/tooltip.tsx +54 -0
  377. package/src/components/ui/tree-view.tsx +574 -0
  378. package/src/contexts/AgentToolCallbacksContext.tsx +31 -0
  379. package/src/contexts/ApiContext.tsx +51 -0
  380. package/src/contexts/FlowDataContext.tsx +21 -0
  381. package/src/contexts/NodeRegistryContext.tsx +54 -0
  382. package/src/contexts/PluginRegistryContext.tsx +182 -0
  383. package/src/contexts/ThemeProvider.tsx +106 -0
  384. package/src/contexts/ValidationContext.tsx +122 -0
  385. package/src/demo/DemoInvect.tsx +42 -0
  386. package/src/demo/FlowViewer.tsx +294 -0
  387. package/src/demo/demo-api-client.ts +246 -0
  388. package/src/demo/index.ts +28 -0
  389. package/src/demo/sample-data.ts +1980 -0
  390. package/src/hooks/index.ts +1 -0
  391. package/src/hooks/use-document-title.ts +8 -0
  392. package/src/hooks/use-flow-data.ts +144 -0
  393. package/src/hooks/use-invect-portal-class.ts +27 -0
  394. package/src/hooks/useFlowEditorStore.ts +2 -0
  395. package/src/index.ts +70 -0
  396. package/src/lib/utils.ts +6 -0
  397. package/src/prettier.d.ts +13 -0
  398. package/src/routes/all-flow-runs.tsx +27 -0
  399. package/src/routes/credentials.tsx +362 -0
  400. package/src/routes/flow-route-layout.tsx +113 -0
  401. package/src/routes/flow-runs.tsx +22 -0
  402. package/src/routes/flow.tsx +22 -0
  403. package/src/routes/home.tsx +282 -0
  404. package/src/services/index.ts +6 -0
  405. package/src/stores/executionViewStore.ts +211 -0
  406. package/src/stores/flow-editor.store.ts +738 -0
  407. package/src/stores/flowEditorStore.ts +2 -0
  408. package/src/stores/index.ts +10 -0
  409. package/src/stores/uiStore.ts +189 -0
  410. package/src/types/agent-tools.types.ts +64 -0
  411. package/src/types/index.ts +5 -0
  412. package/src/types/node-definition.types.ts +104 -0
  413. package/src/types/plugin.types.ts +123 -0
  414. package/src/utils/credentialBranding.ts +116 -0
  415. package/src/utils/credentialFiltering.ts +68 -0
  416. package/src/utils/flowTransformations.ts +137 -0
  417. package/src/utils/layoutUtils.ts +127 -0
  418. package/src/utils/nodeReferenceUtils.ts +135 -0
  419. 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 &middot; {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
+ }