@flowdrop/flowdrop 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) hide show
  1. package/README.md +50 -50
  2. package/dist/adapters/WorkflowAdapter.d.ts +1 -1
  3. package/dist/adapters/WorkflowAdapter.js +25 -25
  4. package/dist/adapters/agentspec/AgentSpecAdapter.d.ts +2 -2
  5. package/dist/adapters/agentspec/AgentSpecAdapter.js +133 -122
  6. package/dist/adapters/agentspec/agentAdapter.d.ts +2 -2
  7. package/dist/adapters/agentspec/agentAdapter.js +10 -10
  8. package/dist/adapters/agentspec/autoLayout.d.ts +1 -1
  9. package/dist/adapters/agentspec/autoLayout.js +2 -2
  10. package/dist/adapters/agentspec/componentTypeDefaults.d.ts +1 -1
  11. package/dist/adapters/agentspec/componentTypeDefaults.js +120 -120
  12. package/dist/adapters/agentspec/defaultNodeTypes.d.ts +2 -2
  13. package/dist/adapters/agentspec/defaultNodeTypes.js +307 -307
  14. package/dist/adapters/agentspec/index.d.ts +10 -10
  15. package/dist/adapters/agentspec/index.js +6 -6
  16. package/dist/adapters/agentspec/validator.d.ts +2 -2
  17. package/dist/adapters/agentspec/validator.js +22 -20
  18. package/dist/api/enhanced-client.d.ts +3 -3
  19. package/dist/api/enhanced-client.js +73 -72
  20. package/dist/components/App.svelte +1090 -961
  21. package/dist/components/App.svelte.d.ts +9 -6
  22. package/dist/components/CanvasBanner.stories.svelte +23 -20
  23. package/dist/components/CanvasBanner.stories.svelte.d.ts +1 -1
  24. package/dist/components/CanvasBanner.svelte +52 -46
  25. package/dist/components/ConfigForm.svelte +1164 -1065
  26. package/dist/components/ConfigForm.svelte.d.ts +2 -2
  27. package/dist/components/ConfigModal.svelte +180 -180
  28. package/dist/components/ConfigModal.svelte.d.ts +1 -1
  29. package/dist/components/ConfigPanel.stories.svelte +35 -35
  30. package/dist/components/ConfigPanel.stories.svelte.d.ts +1 -1
  31. package/dist/components/ConfigPanel.svelte +178 -167
  32. package/dist/components/ConfigPanel.svelte.d.ts +1 -1
  33. package/dist/components/ConnectionLine.svelte +25 -25
  34. package/dist/components/EdgeRefresher.svelte +26 -26
  35. package/dist/components/FlowDropEdge.stories.svelte +179 -143
  36. package/dist/components/FlowDropEdge.svelte +147 -147
  37. package/dist/components/FlowDropEdge.svelte.d.ts +1 -1
  38. package/dist/components/FlowDropZone.svelte +63 -60
  39. package/dist/components/FlowDropZone.svelte.d.ts +1 -1
  40. package/dist/components/LoadingSpinner.stories.svelte +19 -19
  41. package/dist/components/LoadingSpinner.stories.svelte.d.ts +1 -1
  42. package/dist/components/LoadingSpinner.svelte +21 -21
  43. package/dist/components/LoadingSpinner.svelte.d.ts +1 -1
  44. package/dist/components/Logo.stories.svelte +13 -13
  45. package/dist/components/Logo.stories.svelte.d.ts +1 -1
  46. package/dist/components/Logo.svelte +101 -95
  47. package/dist/components/LogsSidebar.svelte +553 -546
  48. package/dist/components/LogsSidebar.svelte.d.ts +1 -1
  49. package/dist/components/MarkdownDisplay.stories.svelte +29 -23
  50. package/dist/components/MarkdownDisplay.stories.svelte.d.ts +1 -1
  51. package/dist/components/MarkdownDisplay.svelte +16 -14
  52. package/dist/components/Navbar.stories.svelte +43 -38
  53. package/dist/components/Navbar.stories.svelte.d.ts +1 -1
  54. package/dist/components/Navbar.svelte +760 -706
  55. package/dist/components/Navbar.svelte.d.ts +1 -1
  56. package/dist/components/NodeSidebar.svelte +905 -746
  57. package/dist/components/NodeSidebar.svelte.d.ts +5 -1
  58. package/dist/components/NodeStatusOverlay.stories.svelte +82 -70
  59. package/dist/components/NodeStatusOverlay.stories.svelte.d.ts +1 -1
  60. package/dist/components/NodeStatusOverlay.svelte +295 -280
  61. package/dist/components/NodeStatusOverlay.svelte.d.ts +3 -3
  62. package/dist/components/PipelineStatus.svelte +326 -300
  63. package/dist/components/PipelineStatus.svelte.d.ts +4 -4
  64. package/dist/components/PortCoordinateTracker.svelte +49 -47
  65. package/dist/components/PortCoordinateTracker.svelte.d.ts +1 -1
  66. package/dist/components/ReadOnlyDetails.svelte +156 -156
  67. package/dist/components/SchemaForm.stories.svelte +106 -98
  68. package/dist/components/SchemaForm.stories.svelte.d.ts +1 -1
  69. package/dist/components/SchemaForm.svelte +490 -463
  70. package/dist/components/SchemaForm.svelte.d.ts +2 -2
  71. package/dist/components/SettingsModal.svelte +226 -223
  72. package/dist/components/SettingsModal.svelte.d.ts +1 -1
  73. package/dist/components/SettingsPanel.svelte +637 -601
  74. package/dist/components/SettingsPanel.svelte.d.ts +1 -1
  75. package/dist/components/StatusIcon.stories.svelte +62 -49
  76. package/dist/components/StatusIcon.stories.svelte.d.ts +1 -1
  77. package/dist/components/StatusIcon.svelte +87 -87
  78. package/dist/components/StatusIcon.svelte.d.ts +2 -2
  79. package/dist/components/StatusLabel.stories.svelte +12 -12
  80. package/dist/components/StatusLabel.stories.svelte.d.ts +1 -1
  81. package/dist/components/StatusLabel.svelte +19 -19
  82. package/dist/components/ThemeToggle.stories.svelte +16 -16
  83. package/dist/components/ThemeToggle.stories.svelte.d.ts +1 -1
  84. package/dist/components/ThemeToggle.svelte +180 -169
  85. package/dist/components/ThemeToggle.svelte.d.ts +1 -1
  86. package/dist/components/UniversalNode.svelte +150 -138
  87. package/dist/components/UniversalNode.svelte.d.ts +3 -3
  88. package/dist/components/WorkflowEditor.svelte +1069 -1014
  89. package/dist/components/WorkflowEditor.svelte.d.ts +4 -4
  90. package/dist/components/form/FormArray.svelte +1034 -973
  91. package/dist/components/form/FormArray.svelte.d.ts +1 -1
  92. package/dist/components/form/FormAutocomplete.svelte +1021 -978
  93. package/dist/components/form/FormAutocomplete.svelte.d.ts +1 -1
  94. package/dist/components/form/FormCheckboxGroup.stories.svelte +23 -20
  95. package/dist/components/form/FormCheckboxGroup.stories.svelte.d.ts +1 -1
  96. package/dist/components/form/FormCheckboxGroup.svelte +136 -136
  97. package/dist/components/form/FormCodeEditor.svelte +452 -434
  98. package/dist/components/form/FormField.svelte +366 -355
  99. package/dist/components/form/FormField.svelte.d.ts +2 -2
  100. package/dist/components/form/FormFieldLight.svelte +400 -384
  101. package/dist/components/form/FormFieldLight.svelte.d.ts +1 -1
  102. package/dist/components/form/FormFieldWrapper.stories.svelte +42 -42
  103. package/dist/components/form/FormFieldWrapper.stories.svelte.d.ts +1 -1
  104. package/dist/components/form/FormFieldWrapper.svelte +100 -93
  105. package/dist/components/form/FormFieldWrapper.svelte.d.ts +1 -1
  106. package/dist/components/form/FormFieldset.svelte +108 -108
  107. package/dist/components/form/FormFieldset.svelte.d.ts +2 -2
  108. package/dist/components/form/FormMarkdownEditor.svelte +758 -725
  109. package/dist/components/form/FormNumberField.stories.svelte +25 -25
  110. package/dist/components/form/FormNumberField.stories.svelte.d.ts +1 -1
  111. package/dist/components/form/FormNumberField.svelte +88 -88
  112. package/dist/components/form/FormRangeField.stories.svelte +20 -20
  113. package/dist/components/form/FormRangeField.stories.svelte.d.ts +1 -1
  114. package/dist/components/form/FormRangeField.svelte +234 -226
  115. package/dist/components/form/FormSelect.stories.svelte +38 -38
  116. package/dist/components/form/FormSelect.stories.svelte.d.ts +1 -1
  117. package/dist/components/form/FormSelect.svelte +101 -101
  118. package/dist/components/form/FormSelect.svelte.d.ts +1 -1
  119. package/dist/components/form/FormTemplateEditor.svelte +847 -798
  120. package/dist/components/form/FormTemplateEditor.svelte.d.ts +1 -1
  121. package/dist/components/form/FormTextField.stories.svelte +29 -23
  122. package/dist/components/form/FormTextField.stories.svelte.d.ts +1 -1
  123. package/dist/components/form/FormTextField.svelte +68 -68
  124. package/dist/components/form/FormTextarea.stories.svelte +28 -25
  125. package/dist/components/form/FormTextarea.stories.svelte.d.ts +1 -1
  126. package/dist/components/form/FormTextarea.svelte +74 -74
  127. package/dist/components/form/FormToggle.stories.svelte +23 -20
  128. package/dist/components/form/FormToggle.stories.svelte.d.ts +1 -1
  129. package/dist/components/form/FormToggle.svelte +98 -98
  130. package/dist/components/form/FormUISchemaRenderer.svelte +120 -113
  131. package/dist/components/form/FormUISchemaRenderer.svelte.d.ts +3 -3
  132. package/dist/components/form/index.d.ts +19 -19
  133. package/dist/components/form/index.js +18 -18
  134. package/dist/components/form/templateAutocomplete.d.ts +2 -2
  135. package/dist/components/form/templateAutocomplete.js +64 -55
  136. package/dist/components/form/types.d.ts +6 -6
  137. package/dist/components/form/types.js +9 -4
  138. package/dist/components/icons/AlertCircleIcon.svelte +11 -0
  139. package/dist/components/icons/AlertCircleIcon.svelte.d.ts +26 -0
  140. package/dist/components/icons/CogIcon.svelte +11 -0
  141. package/dist/components/icons/CogIcon.svelte.d.ts +26 -0
  142. package/dist/components/interrupt/ChoicePrompt.stories.svelte +54 -38
  143. package/dist/components/interrupt/ChoicePrompt.stories.svelte.d.ts +1 -1
  144. package/dist/components/interrupt/ChoicePrompt.svelte +407 -383
  145. package/dist/components/interrupt/ChoicePrompt.svelte.d.ts +1 -1
  146. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte +48 -48
  147. package/dist/components/interrupt/ConfirmationPrompt.stories.svelte.d.ts +1 -1
  148. package/dist/components/interrupt/ConfirmationPrompt.svelte +280 -274
  149. package/dist/components/interrupt/ConfirmationPrompt.svelte.d.ts +1 -1
  150. package/dist/components/interrupt/FormPrompt.svelte +223 -218
  151. package/dist/components/interrupt/FormPrompt.svelte.d.ts +1 -1
  152. package/dist/components/interrupt/InterruptBubble.svelte +617 -583
  153. package/dist/components/interrupt/InterruptBubble.svelte.d.ts +2 -2
  154. package/dist/components/interrupt/ReviewPrompt.stories.svelte +66 -56
  155. package/dist/components/interrupt/ReviewPrompt.stories.svelte.d.ts +1 -1
  156. package/dist/components/interrupt/ReviewPrompt.svelte +861 -841
  157. package/dist/components/interrupt/ReviewPrompt.svelte.d.ts +1 -1
  158. package/dist/components/interrupt/TextInputPrompt.stories.svelte +38 -33
  159. package/dist/components/interrupt/TextInputPrompt.stories.svelte.d.ts +1 -1
  160. package/dist/components/interrupt/TextInputPrompt.svelte +333 -328
  161. package/dist/components/interrupt/TextInputPrompt.svelte.d.ts +1 -1
  162. package/dist/components/interrupt/index.d.ts +5 -5
  163. package/dist/components/interrupt/index.js +5 -5
  164. package/dist/components/layouts/MainLayout.svelte +724 -691
  165. package/dist/components/layouts/MainLayout.svelte.d.ts +6 -6
  166. package/dist/components/nodes/GatewayNode.stories.svelte +100 -99
  167. package/dist/components/nodes/GatewayNode.svelte +605 -571
  168. package/dist/components/nodes/GatewayNode.svelte.d.ts +3 -3
  169. package/dist/components/nodes/IdeaNode.stories.svelte +44 -43
  170. package/dist/components/nodes/IdeaNode.svelte +451 -437
  171. package/dist/components/nodes/IdeaNode.svelte.d.ts +1 -1
  172. package/dist/components/nodes/NotesNode.stories.svelte +65 -64
  173. package/dist/components/nodes/NotesNode.svelte +380 -369
  174. package/dist/components/nodes/NotesNode.svelte.d.ts +1 -1
  175. package/dist/components/nodes/SimpleNode.stories.svelte +145 -144
  176. package/dist/components/nodes/SimpleNode.svelte +486 -424
  177. package/dist/components/nodes/SimpleNode.svelte.d.ts +1 -1
  178. package/dist/components/nodes/SquareNode.stories.svelte +73 -73
  179. package/dist/components/nodes/SquareNode.svelte +439 -380
  180. package/dist/components/nodes/SquareNode.svelte.d.ts +1 -1
  181. package/dist/components/nodes/TerminalNode.stories.svelte +13 -13
  182. package/dist/components/nodes/TerminalNode.svelte +709 -670
  183. package/dist/components/nodes/TerminalNode.svelte.d.ts +1 -1
  184. package/dist/components/nodes/ToolNode.stories.svelte +181 -180
  185. package/dist/components/nodes/ToolNode.svelte +505 -447
  186. package/dist/components/nodes/ToolNode.svelte.d.ts +1 -1
  187. package/dist/components/nodes/WorkflowNode.stories.svelte +70 -46
  188. package/dist/components/nodes/WorkflowNode.svelte +621 -551
  189. package/dist/components/nodes/WorkflowNode.svelte.d.ts +3 -3
  190. package/dist/components/playground/ChatPanel.svelte +945 -889
  191. package/dist/components/playground/ExecutionLogs.svelte +495 -472
  192. package/dist/components/playground/InputCollector.svelte +449 -428
  193. package/dist/components/playground/MessageBubble.stories.svelte +47 -47
  194. package/dist/components/playground/MessageBubble.stories.svelte.d.ts +1 -1
  195. package/dist/components/playground/MessageBubble.svelte +626 -610
  196. package/dist/components/playground/MessageBubble.svelte.d.ts +1 -1
  197. package/dist/components/playground/Playground.svelte +1088 -1057
  198. package/dist/components/playground/Playground.svelte.d.ts +3 -3
  199. package/dist/components/playground/PlaygroundModal.svelte +208 -204
  200. package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -3
  201. package/dist/components/playground/SessionManager.svelte +527 -521
  202. package/dist/components/playground/SessionManager.svelte.d.ts +1 -1
  203. package/dist/config/agentSpecEndpoints.d.ts +1 -1
  204. package/dist/config/agentSpecEndpoints.js +20 -20
  205. package/dist/config/constants.js +2 -2
  206. package/dist/config/defaultCategories.d.ts +1 -1
  207. package/dist/config/defaultCategories.js +86 -86
  208. package/dist/config/defaultPortConfig.d.ts +1 -1
  209. package/dist/config/defaultPortConfig.js +144 -144
  210. package/dist/config/endpoints.d.ts +4 -4
  211. package/dist/config/endpoints.js +65 -65
  212. package/dist/config/runtimeConfig.d.ts +2 -2
  213. package/dist/config/runtimeConfig.js +8 -8
  214. package/dist/core/index.d.ts +63 -59
  215. package/dist/core/index.js +35 -33
  216. package/dist/display/index.d.ts +2 -2
  217. package/dist/display/index.js +2 -2
  218. package/dist/editor/index.d.ts +62 -62
  219. package/dist/editor/index.js +53 -53
  220. package/dist/form/code.d.ts +5 -5
  221. package/dist/form/code.js +14 -14
  222. package/dist/form/fieldRegistry.d.ts +3 -3
  223. package/dist/form/fieldRegistry.js +11 -9
  224. package/dist/form/full.d.ts +8 -8
  225. package/dist/form/full.js +9 -9
  226. package/dist/form/index.d.ts +18 -18
  227. package/dist/form/index.js +16 -16
  228. package/dist/form/markdown.d.ts +4 -4
  229. package/dist/form/markdown.js +8 -8
  230. package/dist/helpers/proximityConnect.d.ts +3 -3
  231. package/dist/helpers/proximityConnect.js +34 -32
  232. package/dist/helpers/workflowEditorHelper.d.ts +5 -5
  233. package/dist/helpers/workflowEditorHelper.js +108 -96
  234. package/dist/index.d.ts +6 -6
  235. package/dist/index.js +6 -6
  236. package/dist/mocks/app-environment.js +2 -2
  237. package/dist/mocks/app-forms.js +9 -9
  238. package/dist/mocks/app-navigation.js +11 -11
  239. package/dist/mocks/app-stores.js +8 -8
  240. package/dist/playground/index.d.ts +19 -19
  241. package/dist/playground/index.js +16 -16
  242. package/dist/playground/mount.d.ts +3 -3
  243. package/dist/playground/mount.js +24 -24
  244. package/dist/registry/builtinFormats.js +13 -13
  245. package/dist/registry/builtinNodes.d.ts +2 -2
  246. package/dist/registry/builtinNodes.js +77 -77
  247. package/dist/registry/index.d.ts +4 -4
  248. package/dist/registry/index.js +4 -4
  249. package/dist/registry/nodeComponentRegistry.d.ts +8 -8
  250. package/dist/registry/nodeComponentRegistry.js +11 -9
  251. package/dist/registry/plugin.d.ts +2 -2
  252. package/dist/registry/plugin.js +11 -11
  253. package/dist/registry/workflowFormatRegistry.d.ts +3 -3
  254. package/dist/registry/workflowFormatRegistry.js +2 -2
  255. package/dist/schema/index.d.ts +1 -1
  256. package/dist/schema/index.js +2 -2
  257. package/dist/services/agentSpecExecutionService.d.ts +3 -3
  258. package/dist/services/agentSpecExecutionService.js +59 -55
  259. package/dist/services/api.d.ts +2 -2
  260. package/dist/services/api.js +37 -37
  261. package/dist/services/apiVariableService.d.ts +1 -1
  262. package/dist/services/apiVariableService.js +41 -34
  263. package/dist/services/autoSaveService.js +8 -8
  264. package/dist/services/categoriesApi.d.ts +2 -2
  265. package/dist/services/categoriesApi.js +8 -8
  266. package/dist/services/draftStorage.d.ts +1 -1
  267. package/dist/services/draftStorage.js +11 -11
  268. package/dist/services/dynamicSchemaService.d.ts +1 -1
  269. package/dist/services/dynamicSchemaService.js +41 -39
  270. package/dist/services/globalSave.d.ts +2 -2
  271. package/dist/services/globalSave.js +41 -38
  272. package/dist/services/historyService.d.ts +1 -1
  273. package/dist/services/historyService.js +8 -8
  274. package/dist/services/interruptService.d.ts +1 -1
  275. package/dist/services/interruptService.js +35 -29
  276. package/dist/services/nodeExecutionService.d.ts +1 -1
  277. package/dist/services/nodeExecutionService.js +45 -44
  278. package/dist/services/playgroundService.d.ts +1 -1
  279. package/dist/services/playgroundService.js +29 -29
  280. package/dist/services/portConfigApi.d.ts +2 -2
  281. package/dist/services/portConfigApi.js +8 -8
  282. package/dist/services/settingsService.d.ts +2 -2
  283. package/dist/services/settingsService.js +25 -19
  284. package/dist/services/toastService.d.ts +4 -4
  285. package/dist/services/toastService.js +33 -33
  286. package/dist/services/variableService.d.ts +1 -1
  287. package/dist/services/variableService.js +36 -36
  288. package/dist/services/workflowStorage.d.ts +2 -2
  289. package/dist/services/workflowStorage.js +13 -13
  290. package/dist/settings/index.d.ts +7 -7
  291. package/dist/settings/index.js +6 -6
  292. package/dist/skins/default.d.ts +2 -0
  293. package/dist/skins/default.js +1 -0
  294. package/dist/skins/index.d.ts +13 -0
  295. package/dist/skins/index.js +30 -0
  296. package/dist/skins/slate.d.ts +2 -0
  297. package/dist/skins/slate.js +78 -0
  298. package/dist/stores/categoriesStore.svelte.d.ts +1 -1
  299. package/dist/stores/categoriesStore.svelte.js +5 -5
  300. package/dist/stores/editorStateMachine.svelte.d.ts +2 -2
  301. package/dist/stores/editorStateMachine.svelte.js +65 -33
  302. package/dist/stores/historyStore.svelte.d.ts +4 -4
  303. package/dist/stores/historyStore.svelte.js +4 -4
  304. package/dist/stores/interruptStore.svelte.d.ts +3 -3
  305. package/dist/stores/interruptStore.svelte.js +21 -21
  306. package/dist/stores/playgroundStore.svelte.d.ts +2 -2
  307. package/dist/stores/playgroundStore.svelte.js +25 -18
  308. package/dist/stores/portCoordinateStore.svelte.d.ts +2 -2
  309. package/dist/stores/portCoordinateStore.svelte.js +15 -8
  310. package/dist/stores/settingsStore.svelte.d.ts +2 -2
  311. package/dist/stores/settingsStore.svelte.js +62 -57
  312. package/dist/stores/workflowStore.svelte.d.ts +3 -3
  313. package/dist/stores/workflowStore.svelte.js +50 -47
  314. package/dist/stories/CanvasDecorator.svelte +35 -32
  315. package/dist/stories/CanvasDecorator.svelte.d.ts +2 -2
  316. package/dist/stories/EdgeDecorator.svelte +102 -99
  317. package/dist/stories/EdgeDecorator.svelte.d.ts +1 -1
  318. package/dist/stories/NodeDecorator.svelte +59 -53
  319. package/dist/stories/NodeDecorator.svelte.d.ts +1 -1
  320. package/dist/stories/utils.d.ts +2 -2
  321. package/dist/stories/utils.js +105 -67
  322. package/dist/styles/base.css +599 -595
  323. package/dist/styles/toast.css +14 -14
  324. package/dist/styles/tokens.css +409 -378
  325. package/dist/svelte-app.d.ts +12 -9
  326. package/dist/svelte-app.js +40 -39
  327. package/dist/themes/default.d.ts +2 -0
  328. package/dist/themes/default.js +9 -0
  329. package/dist/themes/index.d.ts +13 -0
  330. package/dist/themes/index.js +44 -0
  331. package/dist/themes/minimal.d.ts +2 -0
  332. package/dist/themes/minimal.js +11 -0
  333. package/dist/types/agentspec.d.ts +18 -18
  334. package/dist/types/agentspec.js +2 -2
  335. package/dist/types/auth.d.ts +1 -1
  336. package/dist/types/auth.js +6 -6
  337. package/dist/types/config.d.ts +6 -6
  338. package/dist/types/events.d.ts +2 -2
  339. package/dist/types/events.js +2 -2
  340. package/dist/types/index.d.ts +32 -32
  341. package/dist/types/index.js +6 -6
  342. package/dist/types/interrupt.d.ts +6 -6
  343. package/dist/types/interrupt.js +21 -21
  344. package/dist/types/interruptState.d.ts +12 -12
  345. package/dist/types/interruptState.js +66 -66
  346. package/dist/types/playground.d.ts +7 -7
  347. package/dist/types/playground.js +14 -14
  348. package/dist/types/settings.d.ts +5 -3
  349. package/dist/types/settings.js +25 -18
  350. package/dist/types/skin.d.ts +31 -0
  351. package/dist/types/skin.js +1 -0
  352. package/dist/types/theme.d.ts +35 -0
  353. package/dist/types/theme.js +1 -0
  354. package/dist/types/uischema.d.ts +4 -4
  355. package/dist/types/uischema.js +3 -3
  356. package/dist/utils/colors.d.ts +1 -1
  357. package/dist/utils/colors.js +97 -95
  358. package/dist/utils/config.d.ts +2 -2
  359. package/dist/utils/config.js +48 -48
  360. package/dist/utils/connections.d.ts +2 -2
  361. package/dist/utils/connections.js +15 -15
  362. package/dist/utils/errors.js +3 -3
  363. package/dist/utils/fetchWithAuth.d.ts +1 -1
  364. package/dist/utils/fetchWithAuth.js +2 -2
  365. package/dist/utils/handleIds.d.ts +2 -2
  366. package/dist/utils/handleIds.js +8 -8
  367. package/dist/utils/handlePositioning.d.ts +1 -1
  368. package/dist/utils/handlePositioning.js +2 -2
  369. package/dist/utils/icons.d.ts +1 -1
  370. package/dist/utils/icons.js +74 -74
  371. package/dist/utils/logger.d.ts +1 -1
  372. package/dist/utils/logger.js +7 -7
  373. package/dist/utils/nodeStatus.d.ts +1 -1
  374. package/dist/utils/nodeStatus.js +48 -48
  375. package/dist/utils/nodeTypes.d.ts +1 -1
  376. package/dist/utils/nodeTypes.js +21 -20
  377. package/dist/utils/nodeWrapper.d.ts +7 -7
  378. package/dist/utils/nodeWrapper.js +21 -19
  379. package/dist/utils/performanceUtils.d.ts +1 -1
  380. package/dist/utils/performanceUtils.js +2 -1
  381. package/dist/utils/sanitize.js +1 -1
  382. package/dist/utils/uischema.d.ts +2 -2
  383. package/dist/utils/uischema.js +8 -8
  384. package/dist/utils/validation.js +20 -8
  385. package/package.json +1 -1
@@ -7,899 +7,955 @@
7
7
  -->
8
8
 
9
9
  <script lang="ts">
10
- import Icon from '@iconify/svelte';
11
- import { tick } from 'svelte';
12
- import MessageBubble from './MessageBubble.svelte';
13
- import { InterruptBubble } from '../interrupt/index.js';
14
- import type { PlaygroundMessage } from '../../types/playground.js';
15
- import { hasEnableRunFlag } from '../../types/playground.js';
16
- import {
17
- isInterruptMetadata,
18
- extractInterruptMetadata,
19
- metadataToInterrupt
20
- } from '../../types/interrupt.js';
21
- import {
22
- getMessages,
23
- getChatMessages,
24
- getIsExecuting,
25
- getSessionStatus,
26
- getCurrentSession
27
- } from '../../stores/playgroundStore.svelte.js';
28
- import {
29
- getInterruptsMap,
30
- interruptActions,
31
- getInterruptByMessageId
32
- } from '../../stores/interruptStore.svelte.js';
33
-
34
- /**
35
- * Component props
36
- */
37
- interface Props {
38
- /** Whether to show timestamps on messages */
39
- showTimestamps?: boolean;
40
- /** Whether to auto-scroll to bottom on new messages */
41
- autoScroll?: boolean;
42
- /** Placeholder text for the input */
43
- placeholder?: string;
44
- /** Callback when user sends a message */
45
- onSendMessage?: (content: string) => void;
46
- /** Callback when user requests to stop execution */
47
- onStopExecution?: () => void;
48
- /** Whether to show log messages inline (false = hide them) */
49
- showLogsInline?: boolean;
50
- /** Whether to enable markdown rendering in messages */
51
- enableMarkdown?: boolean;
52
- /** Callback when an interrupt is resolved (to refresh messages) */
53
- onInterruptResolved?: () => void;
54
- /**
55
- * Whether to show the chat text input (default: true)
56
- * When false, only the "Run" button is displayed.
57
- */
58
- showChatInput?: boolean;
59
- /**
60
- * Whether to show the "Run" button (default: true)
61
- * When false, the Run button is hidden.
62
- */
63
- showRunButton?: boolean;
64
- /**
65
- * Predefined message to send when "Run" button is clicked
66
- * Used when showChatInput is false.
67
- */
68
- predefinedMessage?: string;
69
- /**
70
- * Whether to display system messages in compact mode.
71
- * When true, system messages appear as minimal inline text
72
- * instead of full chat bubbles to reduce visual noise.
73
- * @default true
74
- */
75
- compactSystemMessages?: boolean;
76
- }
77
-
78
- let {
79
- showTimestamps = true,
80
- autoScroll = true,
81
- placeholder = 'Type your message...',
82
- onSendMessage,
83
- onStopExecution,
84
- showLogsInline = false,
85
- enableMarkdown = true,
86
- onInterruptResolved,
87
- showChatInput = true,
88
- showRunButton = true,
89
- predefinedMessage = 'Run workflow',
90
- compactSystemMessages = true
91
- }: Props = $props();
92
-
93
- /**
94
- * Tracks whether the Run button is enabled.
95
- * Starts as true, becomes false after Run is clicked,
96
- * and is re-enabled when backend sends a message with enableRun: true metadata.
97
- */
98
- let runEnabled = $state(true);
99
-
100
- /**
101
- * Computed flag: true if both chat input and run button are hidden.
102
- * In this case, we show a helpful message to the user.
103
- */
104
- const noInputsAvailable = $derived(!showChatInput && !showRunButton);
105
-
106
- /** Input field value */
107
- let inputValue = $state('');
108
-
109
- /** Reference to the messages container for scrolling */
110
- let messagesContainer = $state<HTMLDivElement>();
111
-
112
- /** Reference to the input field */
113
- let inputField = $state<HTMLTextAreaElement>();
114
-
115
- /**
116
- * Filter messages based on showLogsInline setting
117
- */
118
- const displayMessages = $derived(showLogsInline ? getMessages() : getChatMessages());
119
-
120
- /**
121
- * Track previous message count for detecting new messages.
122
- * We only want to auto-scroll when NEW messages are added,
123
- * not when existing messages are updated.
124
- */
125
- let previousMessageCount = $state(0);
126
-
127
- /**
128
- * Check if user is near the bottom of the scroll container.
129
- * Used to determine if we should auto-scroll when new messages arrive.
130
- * If user has scrolled up to read previous messages, we don't interrupt them.
131
- *
132
- * @param threshold - Pixels from bottom to consider "near bottom"
133
- * @returns True if user is within threshold of the bottom
134
- */
135
- function isNearBottom(threshold: number = 100): boolean {
136
- if (!messagesContainer) return true;
137
- const { scrollTop, scrollHeight, clientHeight } = messagesContainer;
138
- return scrollHeight - scrollTop - clientHeight <= threshold;
139
- }
140
-
141
- /**
142
- * Check if a form element inside the messages container has focus.
143
- * When user is interacting with a form (e.g., interrupt prompt),
144
- * we should not auto-scroll as it disrupts their input.
145
- */
146
- function isFormFocused(): boolean {
147
- if (!messagesContainer) return false;
148
- const activeElement = document.activeElement;
149
- if (!activeElement) return false;
150
- // Check if active element is a form control inside the messages container
151
- const isFormControl =
152
- activeElement.tagName === 'INPUT' ||
153
- activeElement.tagName === 'TEXTAREA' ||
154
- activeElement.tagName === 'SELECT' ||
155
- activeElement.tagName === 'BUTTON' ||
156
- activeElement.getAttribute('contenteditable') === 'true';
157
- return isFormControl && messagesContainer.contains(activeElement);
158
- }
159
-
160
- /**
161
- * Check if a message is an interrupt request
162
- */
163
- function isInterruptMessage(message: PlaygroundMessage): boolean {
164
- return isInterruptMetadata(message.metadata as Record<string, unknown> | undefined);
165
- }
166
-
167
- /**
168
- * Sync interrupt messages to the interrupt store.
169
- * This effect runs when messages change and adds any new interrupt messages
170
- * to the interrupt store. We do this in an effect rather than during render
171
- * to avoid Svelte 5's state_unsafe_mutation error.
172
- *
173
- * If a message has status 'completed', the interrupt is marked as resolved
174
- * to show the "Confirmation Submitted" header, disabled buttons, and
175
- * "Response submitted" indicator.
176
- */
177
- $effect(() => {
178
- // Get all messages that are interrupt requests
179
- const interruptMessages = displayMessages.filter(isInterruptMessage);
180
-
181
- for (const message of interruptMessages) {
182
- // Check if we already have this interrupt in the store
183
- const existing = getInterruptByMessageId(message.id);
184
- if (!existing) {
185
- // Extract and validate interrupt metadata
186
- const metadata = extractInterruptMetadata(
187
- message.metadata as Record<string, unknown> | undefined
188
- );
189
- if (metadata) {
190
- const interrupt = metadataToInterrupt(metadata, message.id, message.content);
191
- interruptActions.addInterrupt(interrupt);
192
-
193
- // If the message status is 'completed', mark the interrupt as resolved
194
- // This ensures completed interrupts show proper UI state:
195
- // - "Confirmation Submitted" header
196
- // - Disabled buttons
197
- // - "Response submitted" indicator
198
- if (message.status === 'completed') {
199
- interruptActions.resolveInterrupt(interrupt.id, metadata.response_value);
200
- }
201
- }
202
- }
203
- }
204
- });
205
-
206
- /**
207
- * Reactive map of message IDs to interrupts.
208
- * This ensures the component re-renders when interrupts are added to the store.
209
- */
210
- const interruptsByMessageId = $derived(
211
- new Map(
212
- Array.from(getInterruptsMap().values())
213
- .filter((i) => i.messageId)
214
- .map((i) => [i.messageId, i])
215
- )
216
- );
217
-
218
- /**
219
- * Get interrupt data for a message from the reactive map
220
- */
221
- function getInterruptForMessage(message: PlaygroundMessage) {
222
- return interruptsByMessageId.get(message.id);
223
- }
224
-
225
- /**
226
- * Check if we should show the welcome state
227
- */
228
- const showWelcome = $derived(!getCurrentSession() && displayMessages.length === 0);
229
-
230
- /**
231
- * Check if we should show the empty chat state (session exists but no messages)
232
- */
233
- const showEmptyChat = $derived(getCurrentSession() && displayMessages.length === 0);
234
-
235
- /**
236
- * Handle sending a message
237
- */
238
- function handleSend(): void {
239
- const trimmedValue = inputValue.trim();
240
- if (!trimmedValue || getIsExecuting()) {
241
- return;
242
- }
243
-
244
- onSendMessage?.(trimmedValue);
245
- inputValue = '';
246
-
247
- // Reset textarea height
248
- if (inputField) {
249
- inputField.style.height = 'auto';
250
- }
251
-
252
- // Re-focus the input
253
- tick().then(() => {
254
- inputField?.focus();
255
- });
256
- }
257
-
258
- /**
259
- * Handle keyboard events in the input
260
- */
261
- function handleKeydown(event: KeyboardEvent): void {
262
- if (event.key === 'Enter' && !event.shiftKey) {
263
- event.preventDefault();
264
- handleSend();
265
- }
266
- }
267
-
268
- /**
269
- * Handle stop execution
270
- */
271
- function handleStop(): void {
272
- onStopExecution?.();
273
- }
274
-
275
- /**
276
- * Handle "Run" button click when chat input is hidden.
277
- * Sends the predefined message to execute the workflow.
278
- * Disables the Run button after clicking until backend re-enables it.
279
- */
280
- function handleRun(): void {
281
- if (getIsExecuting() || !runEnabled) {
282
- return;
283
- }
284
- // Disable the Run button after clicking
285
- runEnabled = false;
286
- onSendMessage?.(predefinedMessage);
287
- }
288
-
289
- /**
290
- * Track processed message IDs for enableRun detection
291
- * to avoid re-processing the same messages.
292
- */
293
- let processedEnableRunIds = $state(new Set<string>());
294
-
295
- /**
296
- * Watch for messages with enableRun: true metadata from the backend.
297
- * When detected, re-enable the Run button.
298
- */
299
- $effect(() => {
300
- // Check all messages for enableRun flag
301
- for (const message of displayMessages) {
302
- // Skip if already processed
303
- if (processedEnableRunIds.has(message.id)) {
304
- continue;
305
- }
306
- // Check if this message has the enableRun flag
307
- if (hasEnableRunFlag(message.metadata)) {
308
- // Mark as processed
309
- processedEnableRunIds = new Set([...processedEnableRunIds, message.id]);
310
- // Re-enable the Run button
311
- runEnabled = true;
312
- }
313
- }
314
- });
315
-
316
- /**
317
- * Reset runEnabled state when session changes.
318
- * This ensures a fresh state for each session.
319
- */
320
- $effect(() => {
321
- const session = getCurrentSession();
322
- if (session) {
323
- // Reset to enabled state for new/changed sessions
324
- runEnabled = true;
325
- // Clear processed IDs for the new session
326
- processedEnableRunIds = new Set();
327
- }
328
- });
329
-
330
- /**
331
- * Smart auto-scroll to bottom when NEW messages are added.
332
- *
333
- * Only scrolls if:
334
- * 1. autoScroll prop is enabled
335
- * 2. New messages were actually added (not just updates)
336
- * 3. User is already near the bottom (hasn't scrolled up to read)
337
- * 4. User is not interacting with a form inside the chat
338
- *
339
- * This prevents disruptive scrolling when:
340
- * - User is reading previous messages
341
- * - User is filling out an interrupt form
342
- * - Messages are being updated (e.g., status changes)
343
- */
344
- $effect(() => {
345
- const currentCount = displayMessages.length;
346
-
347
- // Skip if auto-scroll is disabled or no container
348
- if (!autoScroll || !messagesContainer) {
349
- previousMessageCount = currentCount;
350
- return;
351
- }
352
-
353
- // Check if this is a NEW message (count increased)
354
- const hasNewMessage = currentCount > previousMessageCount;
355
-
356
- // Update the tracked count
357
- previousMessageCount = currentCount;
358
-
359
- // Only scroll if there's a new message
360
- if (!hasNewMessage) {
361
- return;
362
- }
363
-
364
- // Don't scroll if user has scrolled up to read previous messages
365
- if (!isNearBottom()) {
366
- return;
367
- }
368
-
369
- // Don't scroll if user is interacting with a form
370
- if (isFormFocused()) {
371
- return;
372
- }
373
-
374
- // Safe to scroll to bottom
375
- tick().then(() => {
376
- if (messagesContainer) {
377
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
378
- }
379
- });
380
- });
381
-
382
- /**
383
- * Track previous executing state to detect when execution completes
384
- */
385
- let wasExecuting = $state(false);
386
-
387
- /**
388
- * Auto-focus input when execution completes or session becomes ready
389
- */
390
- $effect(() => {
391
- const currentlyExecuting = getIsExecuting();
392
-
393
- // Focus input when execution completes (was executing, now not)
394
- if (wasExecuting && !currentlyExecuting && inputField) {
395
- tick().then(() => {
396
- inputField?.focus();
397
- });
398
- }
399
-
400
- // Update tracking state
401
- wasExecuting = currentlyExecuting;
402
- });
403
-
404
- /**
405
- * Focus input when session status changes to idle or completed
406
- */
407
- $effect(() => {
408
- const status = getSessionStatus();
409
- if ((status === 'idle' || status === 'completed') && inputField && !getIsExecuting()) {
410
- tick().then(() => {
411
- inputField?.focus();
412
- });
413
- }
414
- });
415
-
416
- /**
417
- * Focus input when a new session is created/loaded
418
- */
419
- $effect(() => {
420
- const session = getCurrentSession();
421
- if (session && inputField && !getIsExecuting()) {
422
- tick().then(() => {
423
- inputField?.focus();
424
- });
425
- }
426
- });
427
-
428
- /**
429
- * Auto-resize textarea based on content
430
- */
431
- function handleInput(): void {
432
- if (inputField) {
433
- inputField.style.height = 'auto';
434
- inputField.style.height = `${Math.min(inputField.scrollHeight, 120)}px`;
435
- }
436
- }
10
+ import Icon from "@iconify/svelte";
11
+ import { tick } from "svelte";
12
+ import MessageBubble from "./MessageBubble.svelte";
13
+ import { InterruptBubble } from "../interrupt/index.js";
14
+ import type { PlaygroundMessage } from "../../types/playground.js";
15
+ import { hasEnableRunFlag } from "../../types/playground.js";
16
+ import {
17
+ isInterruptMetadata,
18
+ extractInterruptMetadata,
19
+ metadataToInterrupt,
20
+ } from "../../types/interrupt.js";
21
+ import {
22
+ getMessages,
23
+ getChatMessages,
24
+ getIsExecuting,
25
+ getSessionStatus,
26
+ getCurrentSession,
27
+ } from "../../stores/playgroundStore.svelte.js";
28
+ import {
29
+ getInterruptsMap,
30
+ interruptActions,
31
+ getInterruptByMessageId,
32
+ } from "../../stores/interruptStore.svelte.js";
33
+
34
+ /**
35
+ * Component props
36
+ */
37
+ interface Props {
38
+ /** Whether to show timestamps on messages */
39
+ showTimestamps?: boolean;
40
+ /** Whether to auto-scroll to bottom on new messages */
41
+ autoScroll?: boolean;
42
+ /** Placeholder text for the input */
43
+ placeholder?: string;
44
+ /** Callback when user sends a message */
45
+ onSendMessage?: (content: string) => void;
46
+ /** Callback when user requests to stop execution */
47
+ onStopExecution?: () => void;
48
+ /** Whether to show log messages inline (false = hide them) */
49
+ showLogsInline?: boolean;
50
+ /** Whether to enable markdown rendering in messages */
51
+ enableMarkdown?: boolean;
52
+ /** Callback when an interrupt is resolved (to refresh messages) */
53
+ onInterruptResolved?: () => void;
54
+ /**
55
+ * Whether to show the chat text input (default: true)
56
+ * When false, only the "Run" button is displayed.
57
+ */
58
+ showChatInput?: boolean;
59
+ /**
60
+ * Whether to show the "Run" button (default: true)
61
+ * When false, the Run button is hidden.
62
+ */
63
+ showRunButton?: boolean;
64
+ /**
65
+ * Predefined message to send when "Run" button is clicked
66
+ * Used when showChatInput is false.
67
+ */
68
+ predefinedMessage?: string;
69
+ /**
70
+ * Whether to display system messages in compact mode.
71
+ * When true, system messages appear as minimal inline text
72
+ * instead of full chat bubbles to reduce visual noise.
73
+ * @default true
74
+ */
75
+ compactSystemMessages?: boolean;
76
+ }
77
+
78
+ let {
79
+ showTimestamps = true,
80
+ autoScroll = true,
81
+ placeholder = "Type your message...",
82
+ onSendMessage,
83
+ onStopExecution,
84
+ showLogsInline = false,
85
+ enableMarkdown = true,
86
+ onInterruptResolved,
87
+ showChatInput = true,
88
+ showRunButton = true,
89
+ predefinedMessage = "Run workflow",
90
+ compactSystemMessages = true,
91
+ }: Props = $props();
92
+
93
+ /**
94
+ * Tracks whether the Run button is enabled.
95
+ * Starts as true, becomes false after Run is clicked,
96
+ * and is re-enabled when backend sends a message with enableRun: true metadata.
97
+ */
98
+ let runEnabled = $state(true);
99
+
100
+ /**
101
+ * Computed flag: true if both chat input and run button are hidden.
102
+ * In this case, we show a helpful message to the user.
103
+ */
104
+ const noInputsAvailable = $derived(!showChatInput && !showRunButton);
105
+
106
+ /** Input field value */
107
+ let inputValue = $state("");
108
+
109
+ /** Reference to the messages container for scrolling */
110
+ let messagesContainer = $state<HTMLDivElement>();
111
+
112
+ /** Reference to the input field */
113
+ let inputField = $state<HTMLTextAreaElement>();
114
+
115
+ /**
116
+ * Filter messages based on showLogsInline setting
117
+ */
118
+ const displayMessages = $derived(
119
+ showLogsInline ? getMessages() : getChatMessages(),
120
+ );
121
+
122
+ /**
123
+ * Track previous message count for detecting new messages.
124
+ * We only want to auto-scroll when NEW messages are added,
125
+ * not when existing messages are updated.
126
+ */
127
+ let previousMessageCount = $state(0);
128
+
129
+ /**
130
+ * Check if user is near the bottom of the scroll container.
131
+ * Used to determine if we should auto-scroll when new messages arrive.
132
+ * If user has scrolled up to read previous messages, we don't interrupt them.
133
+ *
134
+ * @param threshold - Pixels from bottom to consider "near bottom"
135
+ * @returns True if user is within threshold of the bottom
136
+ */
137
+ function isNearBottom(threshold: number = 100): boolean {
138
+ if (!messagesContainer) return true;
139
+ const { scrollTop, scrollHeight, clientHeight } = messagesContainer;
140
+ return scrollHeight - scrollTop - clientHeight <= threshold;
141
+ }
142
+
143
+ /**
144
+ * Check if a form element inside the messages container has focus.
145
+ * When user is interacting with a form (e.g., interrupt prompt),
146
+ * we should not auto-scroll as it disrupts their input.
147
+ */
148
+ function isFormFocused(): boolean {
149
+ if (!messagesContainer) return false;
150
+ const activeElement = document.activeElement;
151
+ if (!activeElement) return false;
152
+ // Check if active element is a form control inside the messages container
153
+ const isFormControl =
154
+ activeElement.tagName === "INPUT" ||
155
+ activeElement.tagName === "TEXTAREA" ||
156
+ activeElement.tagName === "SELECT" ||
157
+ activeElement.tagName === "BUTTON" ||
158
+ activeElement.getAttribute("contenteditable") === "true";
159
+ return isFormControl && messagesContainer.contains(activeElement);
160
+ }
161
+
162
+ /**
163
+ * Check if a message is an interrupt request
164
+ */
165
+ function isInterruptMessage(message: PlaygroundMessage): boolean {
166
+ return isInterruptMetadata(
167
+ message.metadata as Record<string, unknown> | undefined,
168
+ );
169
+ }
170
+
171
+ /**
172
+ * Sync interrupt messages to the interrupt store.
173
+ * This effect runs when messages change and adds any new interrupt messages
174
+ * to the interrupt store. We do this in an effect rather than during render
175
+ * to avoid Svelte 5's state_unsafe_mutation error.
176
+ *
177
+ * If a message has status 'completed', the interrupt is marked as resolved
178
+ * to show the "Confirmation Submitted" header, disabled buttons, and
179
+ * "Response submitted" indicator.
180
+ */
181
+ $effect(() => {
182
+ // Get all messages that are interrupt requests
183
+ const interruptMessages = displayMessages.filter(isInterruptMessage);
184
+
185
+ for (const message of interruptMessages) {
186
+ // Check if we already have this interrupt in the store
187
+ const existing = getInterruptByMessageId(message.id);
188
+ if (!existing) {
189
+ // Extract and validate interrupt metadata
190
+ const metadata = extractInterruptMetadata(
191
+ message.metadata as Record<string, unknown> | undefined,
192
+ );
193
+ if (metadata) {
194
+ const interrupt = metadataToInterrupt(
195
+ metadata,
196
+ message.id,
197
+ message.content,
198
+ );
199
+ interruptActions.addInterrupt(interrupt);
200
+
201
+ // If the message status is 'completed', mark the interrupt as resolved
202
+ // This ensures completed interrupts show proper UI state:
203
+ // - "Confirmation Submitted" header
204
+ // - Disabled buttons
205
+ // - "Response submitted" indicator
206
+ if (message.status === "completed") {
207
+ interruptActions.resolveInterrupt(
208
+ interrupt.id,
209
+ metadata.response_value,
210
+ );
211
+ }
212
+ }
213
+ }
214
+ }
215
+ });
216
+
217
+ /**
218
+ * Reactive map of message IDs to interrupts.
219
+ * This ensures the component re-renders when interrupts are added to the store.
220
+ */
221
+ const interruptsByMessageId = $derived(
222
+ new Map(
223
+ Array.from(getInterruptsMap().values())
224
+ .filter((i) => i.messageId)
225
+ .map((i) => [i.messageId, i]),
226
+ ),
227
+ );
228
+
229
+ /**
230
+ * Get interrupt data for a message from the reactive map
231
+ */
232
+ function getInterruptForMessage(message: PlaygroundMessage) {
233
+ return interruptsByMessageId.get(message.id);
234
+ }
235
+
236
+ /**
237
+ * Check if we should show the welcome state
238
+ */
239
+ const showWelcome = $derived(
240
+ !getCurrentSession() && displayMessages.length === 0,
241
+ );
242
+
243
+ /**
244
+ * Check if we should show the empty chat state (session exists but no messages)
245
+ */
246
+ const showEmptyChat = $derived(
247
+ getCurrentSession() && displayMessages.length === 0,
248
+ );
249
+
250
+ /**
251
+ * Handle sending a message
252
+ */
253
+ function handleSend(): void {
254
+ const trimmedValue = inputValue.trim();
255
+ if (!trimmedValue || getIsExecuting()) {
256
+ return;
257
+ }
258
+
259
+ onSendMessage?.(trimmedValue);
260
+ inputValue = "";
261
+
262
+ // Reset textarea height
263
+ if (inputField) {
264
+ inputField.style.height = "auto";
265
+ }
266
+
267
+ // Re-focus the input
268
+ tick().then(() => {
269
+ inputField?.focus();
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Handle keyboard events in the input
275
+ */
276
+ function handleKeydown(event: KeyboardEvent): void {
277
+ if (event.key === "Enter" && !event.shiftKey) {
278
+ event.preventDefault();
279
+ handleSend();
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Handle stop execution
285
+ */
286
+ function handleStop(): void {
287
+ onStopExecution?.();
288
+ }
289
+
290
+ /**
291
+ * Handle "Run" button click when chat input is hidden.
292
+ * Sends the predefined message to execute the workflow.
293
+ * Disables the Run button after clicking until backend re-enables it.
294
+ */
295
+ function handleRun(): void {
296
+ if (getIsExecuting() || !runEnabled) {
297
+ return;
298
+ }
299
+ // Disable the Run button after clicking
300
+ runEnabled = false;
301
+ onSendMessage?.(predefinedMessage);
302
+ }
303
+
304
+ /**
305
+ * Track processed message IDs for enableRun detection
306
+ * to avoid re-processing the same messages.
307
+ */
308
+ let processedEnableRunIds = $state(new Set<string>());
309
+
310
+ /**
311
+ * Watch for messages with enableRun: true metadata from the backend.
312
+ * When detected, re-enable the Run button.
313
+ */
314
+ $effect(() => {
315
+ // Check all messages for enableRun flag
316
+ for (const message of displayMessages) {
317
+ // Skip if already processed
318
+ if (processedEnableRunIds.has(message.id)) {
319
+ continue;
320
+ }
321
+ // Check if this message has the enableRun flag
322
+ if (hasEnableRunFlag(message.metadata)) {
323
+ // Mark as processed
324
+ processedEnableRunIds = new Set([...processedEnableRunIds, message.id]);
325
+ // Re-enable the Run button
326
+ runEnabled = true;
327
+ }
328
+ }
329
+ });
330
+
331
+ /**
332
+ * Reset runEnabled state when session changes.
333
+ * This ensures a fresh state for each session.
334
+ */
335
+ $effect(() => {
336
+ const session = getCurrentSession();
337
+ if (session) {
338
+ // Reset to enabled state for new/changed sessions
339
+ runEnabled = true;
340
+ // Clear processed IDs for the new session
341
+ processedEnableRunIds = new Set();
342
+ }
343
+ });
344
+
345
+ /**
346
+ * Smart auto-scroll to bottom when NEW messages are added.
347
+ *
348
+ * Only scrolls if:
349
+ * 1. autoScroll prop is enabled
350
+ * 2. New messages were actually added (not just updates)
351
+ * 3. User is already near the bottom (hasn't scrolled up to read)
352
+ * 4. User is not interacting with a form inside the chat
353
+ *
354
+ * This prevents disruptive scrolling when:
355
+ * - User is reading previous messages
356
+ * - User is filling out an interrupt form
357
+ * - Messages are being updated (e.g., status changes)
358
+ */
359
+ $effect(() => {
360
+ const currentCount = displayMessages.length;
361
+
362
+ // Skip if auto-scroll is disabled or no container
363
+ if (!autoScroll || !messagesContainer) {
364
+ previousMessageCount = currentCount;
365
+ return;
366
+ }
367
+
368
+ // Check if this is a NEW message (count increased)
369
+ const hasNewMessage = currentCount > previousMessageCount;
370
+
371
+ // Update the tracked count
372
+ previousMessageCount = currentCount;
373
+
374
+ // Only scroll if there's a new message
375
+ if (!hasNewMessage) {
376
+ return;
377
+ }
378
+
379
+ // Don't scroll if user has scrolled up to read previous messages
380
+ if (!isNearBottom()) {
381
+ return;
382
+ }
383
+
384
+ // Don't scroll if user is interacting with a form
385
+ if (isFormFocused()) {
386
+ return;
387
+ }
388
+
389
+ // Safe to scroll to bottom
390
+ tick().then(() => {
391
+ if (messagesContainer) {
392
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
393
+ }
394
+ });
395
+ });
396
+
397
+ /**
398
+ * Track previous executing state to detect when execution completes
399
+ */
400
+ let wasExecuting = $state(false);
401
+
402
+ /**
403
+ * Auto-focus input when execution completes or session becomes ready
404
+ */
405
+ $effect(() => {
406
+ const currentlyExecuting = getIsExecuting();
407
+
408
+ // Focus input when execution completes (was executing, now not)
409
+ if (wasExecuting && !currentlyExecuting && inputField) {
410
+ tick().then(() => {
411
+ inputField?.focus();
412
+ });
413
+ }
414
+
415
+ // Update tracking state
416
+ wasExecuting = currentlyExecuting;
417
+ });
418
+
419
+ /**
420
+ * Focus input when session status changes to idle or completed
421
+ */
422
+ $effect(() => {
423
+ const status = getSessionStatus();
424
+ if (
425
+ (status === "idle" || status === "completed") &&
426
+ inputField &&
427
+ !getIsExecuting()
428
+ ) {
429
+ tick().then(() => {
430
+ inputField?.focus();
431
+ });
432
+ }
433
+ });
434
+
435
+ /**
436
+ * Focus input when a new session is created/loaded
437
+ */
438
+ $effect(() => {
439
+ const session = getCurrentSession();
440
+ if (session && inputField && !getIsExecuting()) {
441
+ tick().then(() => {
442
+ inputField?.focus();
443
+ });
444
+ }
445
+ });
446
+
447
+ /**
448
+ * Auto-resize textarea based on content
449
+ */
450
+ function handleInput(): void {
451
+ if (inputField) {
452
+ inputField.style.height = "auto";
453
+ inputField.style.height = `${Math.min(inputField.scrollHeight, 120)}px`;
454
+ }
455
+ }
437
456
  </script>
438
457
 
439
458
  <div class="chat-panel">
440
- <!-- Messages Container -->
441
- <div class="chat-panel__messages" bind:this={messagesContainer}>
442
- {#if showWelcome}
443
- <!-- Welcome State (no session) -->
444
- <div class="chat-panel__welcome">
445
- <div class="chat-panel__welcome-icon">
446
- <svg
447
- width="48"
448
- height="48"
449
- viewBox="0 0 48 48"
450
- fill="none"
451
- xmlns="http://www.w3.org/2000/svg"
452
- >
453
- <path
454
- d="M8 16L24 8L40 16V32L24 40L8 32V16Z"
455
- stroke="currentColor"
456
- stroke-width="2"
457
- stroke-linejoin="round"
458
- />
459
- <path
460
- d="M8 16L24 24L40 16"
461
- stroke="currentColor"
462
- stroke-width="2"
463
- stroke-linejoin="round"
464
- />
465
- <path d="M24 24V40" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
466
- <path d="M16 12L32 20" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
467
- <path d="M16 36L32 28" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
468
- </svg>
469
- </div>
470
- {#if noInputsAvailable}
471
- <h2 class="chat-panel__welcome-title">View only</h2>
472
- <p class="chat-panel__welcome-text">
473
- This playground is in view-only mode. No inputs are available.
474
- </p>
475
- {:else if showChatInput}
476
- <h2 class="chat-panel__welcome-title">New session</h2>
477
- <p class="chat-panel__welcome-text">Test your flow with a prompt</p>
478
- {:else}
479
- <h2 class="chat-panel__welcome-title">Ready to run</h2>
480
- <p class="chat-panel__welcome-text">Click Run to execute your workflow</p>
481
- {/if}
482
- </div>
483
- {:else if showEmptyChat}
484
- <!-- Empty Chat State (session exists but no messages) -->
485
- <div class="chat-panel__welcome">
486
- <div class="chat-panel__welcome-icon">
487
- <svg
488
- width="48"
489
- height="48"
490
- viewBox="0 0 48 48"
491
- fill="none"
492
- xmlns="http://www.w3.org/2000/svg"
493
- >
494
- <path
495
- d="M8 16L24 8L40 16V32L24 40L8 32V16Z"
496
- stroke="currentColor"
497
- stroke-width="2"
498
- stroke-linejoin="round"
499
- />
500
- <path
501
- d="M8 16L24 24L40 16"
502
- stroke="currentColor"
503
- stroke-width="2"
504
- stroke-linejoin="round"
505
- />
506
- <path d="M24 24V40" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
507
- <path d="M16 12L32 20" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
508
- <path d="M16 36L32 28" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
509
- </svg>
510
- </div>
511
- {#if noInputsAvailable}
512
- <h2 class="chat-panel__welcome-title">View only</h2>
513
- <p class="chat-panel__welcome-text">
514
- This playground is in view-only mode. No inputs are available.
515
- </p>
516
- {:else if showChatInput}
517
- <h2 class="chat-panel__welcome-title">New session</h2>
518
- <p class="chat-panel__welcome-text">Test your flow with a prompt</p>
519
- {:else}
520
- <h2 class="chat-panel__welcome-title">Ready to run</h2>
521
- <p class="chat-panel__welcome-text">Click Run to execute your workflow</p>
522
- {/if}
523
- </div>
524
- {:else}
525
- <!-- Messages -->
526
- {#each displayMessages as message, index (message.id)}
527
- {#if isInterruptMessage(message)}
528
- <!-- Render interrupt inline -->
529
- {@const interrupt = getInterruptForMessage(message)}
530
- {#if interrupt}
531
- <InterruptBubble
532
- {interrupt}
533
- showTimestamp={showTimestamps}
534
- onResolved={onInterruptResolved}
535
- />
536
- {/if}
537
- {:else}
538
- <MessageBubble
539
- {message}
540
- showTimestamp={showTimestamps}
541
- isLast={index === displayMessages.length - 1}
542
- {enableMarkdown}
543
- {compactSystemMessages}
544
- />
545
- {/if}
546
- {/each}
547
-
548
- {#if getIsExecuting()}
549
- <div class="chat-panel__typing">
550
- <div class="chat-panel__typing-indicator">
551
- <span></span>
552
- <span></span>
553
- <span></span>
554
- </div>
555
- <span class="chat-panel__typing-text">Processing...</span>
556
- </div>
557
- {/if}
558
- {/if}
559
- </div>
560
-
561
- <!-- Input Area -->
562
- <div class="chat-panel__input-area">
563
- {#if noInputsAvailable}
564
- <!-- No inputs available - show informational message -->
565
- <div class="chat-panel__no-inputs">
566
- <Icon icon="mdi:information-outline" />
567
- <span>View-only mode. Workflow execution is controlled externally.</span>
568
- </div>
569
- {:else}
570
- <div
571
- class="chat-panel__input-container"
572
- class:chat-panel__input-container--run-only={!showChatInput}
573
- >
574
- {#if showChatInput}
575
- <div class="chat-panel__input-wrapper">
576
- <textarea
577
- bind:this={inputField}
578
- bind:value={inputValue}
579
- class="chat-panel__input"
580
- {placeholder}
581
- rows="1"
582
- disabled={getIsExecuting()}
583
- onkeydown={handleKeydown}
584
- oninput={handleInput}
585
- ></textarea>
586
- </div>
587
- {/if}
588
-
589
- {#if getSessionStatus() === 'running' || getIsExecuting()}
590
- <button
591
- type="button"
592
- class="chat-panel__stop-btn"
593
- onclick={handleStop}
594
- title="Stop execution"
595
- >
596
- <Icon icon="mdi:stop" />
597
- Stop
598
- </button>
599
- {:else if showChatInput}
600
- <button
601
- type="button"
602
- class="chat-panel__send-btn"
603
- onclick={handleSend}
604
- disabled={!inputValue.trim()}
605
- title="Send message"
606
- >
607
- Send
608
- </button>
609
- {:else if showRunButton}
610
- <button
611
- type="button"
612
- class="chat-panel__run-btn"
613
- onclick={handleRun}
614
- disabled={!runEnabled}
615
- title={runEnabled ? 'Run workflow' : 'Waiting for workflow to be ready...'}
616
- >
617
- <Icon icon="mdi:play" />
618
- Run
619
- </button>
620
- {/if}
621
- </div>
622
- {/if}
623
- </div>
459
+ <!-- Messages Container -->
460
+ <div class="chat-panel__messages" bind:this={messagesContainer}>
461
+ {#if showWelcome}
462
+ <!-- Welcome State (no session) -->
463
+ <div class="chat-panel__welcome">
464
+ <div class="chat-panel__welcome-icon">
465
+ <svg
466
+ width="48"
467
+ height="48"
468
+ viewBox="0 0 48 48"
469
+ fill="none"
470
+ xmlns="http://www.w3.org/2000/svg"
471
+ >
472
+ <path
473
+ d="M8 16L24 8L40 16V32L24 40L8 32V16Z"
474
+ stroke="currentColor"
475
+ stroke-width="2"
476
+ stroke-linejoin="round"
477
+ />
478
+ <path
479
+ d="M8 16L24 24L40 16"
480
+ stroke="currentColor"
481
+ stroke-width="2"
482
+ stroke-linejoin="round"
483
+ />
484
+ <path
485
+ d="M24 24V40"
486
+ stroke="currentColor"
487
+ stroke-width="2"
488
+ stroke-linejoin="round"
489
+ />
490
+ <path
491
+ d="M16 12L32 20"
492
+ stroke="currentColor"
493
+ stroke-width="2"
494
+ stroke-linejoin="round"
495
+ />
496
+ <path
497
+ d="M16 36L32 28"
498
+ stroke="currentColor"
499
+ stroke-width="2"
500
+ stroke-linejoin="round"
501
+ />
502
+ </svg>
503
+ </div>
504
+ {#if noInputsAvailable}
505
+ <h2 class="chat-panel__welcome-title">View only</h2>
506
+ <p class="chat-panel__welcome-text">
507
+ This playground is in view-only mode. No inputs are available.
508
+ </p>
509
+ {:else if showChatInput}
510
+ <h2 class="chat-panel__welcome-title">New session</h2>
511
+ <p class="chat-panel__welcome-text">Test your flow with a prompt</p>
512
+ {:else}
513
+ <h2 class="chat-panel__welcome-title">Ready to run</h2>
514
+ <p class="chat-panel__welcome-text">
515
+ Click Run to execute your workflow
516
+ </p>
517
+ {/if}
518
+ </div>
519
+ {:else if showEmptyChat}
520
+ <!-- Empty Chat State (session exists but no messages) -->
521
+ <div class="chat-panel__welcome">
522
+ <div class="chat-panel__welcome-icon">
523
+ <svg
524
+ width="48"
525
+ height="48"
526
+ viewBox="0 0 48 48"
527
+ fill="none"
528
+ xmlns="http://www.w3.org/2000/svg"
529
+ >
530
+ <path
531
+ d="M8 16L24 8L40 16V32L24 40L8 32V16Z"
532
+ stroke="currentColor"
533
+ stroke-width="2"
534
+ stroke-linejoin="round"
535
+ />
536
+ <path
537
+ d="M8 16L24 24L40 16"
538
+ stroke="currentColor"
539
+ stroke-width="2"
540
+ stroke-linejoin="round"
541
+ />
542
+ <path
543
+ d="M24 24V40"
544
+ stroke="currentColor"
545
+ stroke-width="2"
546
+ stroke-linejoin="round"
547
+ />
548
+ <path
549
+ d="M16 12L32 20"
550
+ stroke="currentColor"
551
+ stroke-width="2"
552
+ stroke-linejoin="round"
553
+ />
554
+ <path
555
+ d="M16 36L32 28"
556
+ stroke="currentColor"
557
+ stroke-width="2"
558
+ stroke-linejoin="round"
559
+ />
560
+ </svg>
561
+ </div>
562
+ {#if noInputsAvailable}
563
+ <h2 class="chat-panel__welcome-title">View only</h2>
564
+ <p class="chat-panel__welcome-text">
565
+ This playground is in view-only mode. No inputs are available.
566
+ </p>
567
+ {:else if showChatInput}
568
+ <h2 class="chat-panel__welcome-title">New session</h2>
569
+ <p class="chat-panel__welcome-text">Test your flow with a prompt</p>
570
+ {:else}
571
+ <h2 class="chat-panel__welcome-title">Ready to run</h2>
572
+ <p class="chat-panel__welcome-text">
573
+ Click Run to execute your workflow
574
+ </p>
575
+ {/if}
576
+ </div>
577
+ {:else}
578
+ <!-- Messages -->
579
+ {#each displayMessages as message, index (message.id)}
580
+ {#if isInterruptMessage(message)}
581
+ <!-- Render interrupt inline -->
582
+ {@const interrupt = getInterruptForMessage(message)}
583
+ {#if interrupt}
584
+ <InterruptBubble
585
+ {interrupt}
586
+ showTimestamp={showTimestamps}
587
+ onResolved={onInterruptResolved}
588
+ />
589
+ {/if}
590
+ {:else}
591
+ <MessageBubble
592
+ {message}
593
+ showTimestamp={showTimestamps}
594
+ isLast={index === displayMessages.length - 1}
595
+ {enableMarkdown}
596
+ {compactSystemMessages}
597
+ />
598
+ {/if}
599
+ {/each}
600
+
601
+ {#if getIsExecuting()}
602
+ <div class="chat-panel__typing">
603
+ <div class="chat-panel__typing-indicator">
604
+ <span></span>
605
+ <span></span>
606
+ <span></span>
607
+ </div>
608
+ <span class="chat-panel__typing-text">Processing...</span>
609
+ </div>
610
+ {/if}
611
+ {/if}
612
+ </div>
613
+
614
+ <!-- Input Area -->
615
+ <div class="chat-panel__input-area">
616
+ {#if noInputsAvailable}
617
+ <!-- No inputs available - show informational message -->
618
+ <div class="chat-panel__no-inputs">
619
+ <Icon icon="mdi:information-outline" />
620
+ <span>View-only mode. Workflow execution is controlled externally.</span
621
+ >
622
+ </div>
623
+ {:else}
624
+ <div
625
+ class="chat-panel__input-container"
626
+ class:chat-panel__input-container--run-only={!showChatInput}
627
+ >
628
+ {#if showChatInput}
629
+ <div class="chat-panel__input-wrapper">
630
+ <textarea
631
+ bind:this={inputField}
632
+ bind:value={inputValue}
633
+ class="chat-panel__input"
634
+ {placeholder}
635
+ rows="1"
636
+ disabled={getIsExecuting()}
637
+ onkeydown={handleKeydown}
638
+ oninput={handleInput}
639
+ ></textarea>
640
+ </div>
641
+ {/if}
642
+
643
+ {#if getSessionStatus() === "running" || getIsExecuting()}
644
+ <button
645
+ type="button"
646
+ class="chat-panel__stop-btn"
647
+ onclick={handleStop}
648
+ title="Stop execution"
649
+ >
650
+ <Icon icon="mdi:stop" />
651
+ Stop
652
+ </button>
653
+ {:else if showChatInput}
654
+ <button
655
+ type="button"
656
+ class="chat-panel__send-btn"
657
+ onclick={handleSend}
658
+ disabled={!inputValue.trim()}
659
+ title="Send message"
660
+ >
661
+ Send
662
+ </button>
663
+ {:else if showRunButton}
664
+ <button
665
+ type="button"
666
+ class="chat-panel__run-btn"
667
+ onclick={handleRun}
668
+ disabled={!runEnabled}
669
+ title={runEnabled
670
+ ? "Run workflow"
671
+ : "Waiting for workflow to be ready..."}
672
+ >
673
+ <Icon icon="mdi:play" />
674
+ Run
675
+ </button>
676
+ {/if}
677
+ </div>
678
+ {/if}
679
+ </div>
624
680
  </div>
625
681
 
626
682
  <style>
627
- .chat-panel {
628
- display: flex;
629
- flex-direction: column;
630
- height: 100%;
631
- min-height: 0; /* Critical: allows flexbox to shrink properly */
632
- background-color: var(--fd-background);
633
- }
634
-
635
- /* Messages Container - Scrollable area that takes remaining space */
636
- .chat-panel__messages {
637
- flex: 1;
638
- min-height: 0; /* Critical: allows overflow to work in flex container */
639
- overflow-y: auto;
640
- padding: var(--fd-space-3xl);
641
- scroll-behavior: smooth;
642
- }
643
-
644
- /* Welcome State */
645
- .chat-panel__welcome {
646
- display: flex;
647
- flex-direction: column;
648
- align-items: center;
649
- justify-content: center;
650
- height: 100%;
651
- text-align: center;
652
- padding: var(--fd-space-4xl);
653
- }
654
-
655
- .chat-panel__welcome-icon {
656
- display: flex;
657
- align-items: center;
658
- justify-content: center;
659
- width: 80px;
660
- height: 80px;
661
- margin-bottom: var(--fd-space-3xl);
662
- color: var(--fd-foreground);
663
- }
664
-
665
- .chat-panel__welcome-icon svg {
666
- width: 100%;
667
- height: 100%;
668
- }
669
-
670
- .chat-panel__welcome-title {
671
- font-size: var(--fd-text-2xl);
672
- font-weight: 600;
673
- color: var(--fd-foreground);
674
- margin: 0 0 var(--fd-space-xs) 0;
675
- }
676
-
677
- .chat-panel__welcome-text {
678
- font-size: var(--fd-text-base);
679
- color: var(--fd-muted-foreground);
680
- margin: 0;
681
- }
682
-
683
- /* Typing Indicator */
684
- .chat-panel__typing {
685
- display: flex;
686
- align-items: center;
687
- gap: var(--fd-space-xs);
688
- padding: var(--fd-space-md) var(--fd-space-xl);
689
- margin-top: var(--fd-space-xs);
690
- background-color: var(--fd-muted);
691
- border-radius: var(--fd-radius-2xl);
692
- width: fit-content;
693
- }
694
-
695
- .chat-panel__typing-indicator {
696
- display: flex;
697
- gap: var(--fd-space-3xs);
698
- }
699
-
700
- .chat-panel__typing-indicator span {
701
- width: var(--fd-space-2xs);
702
- height: var(--fd-space-2xs);
703
- background-color: var(--fd-muted-foreground);
704
- border-radius: var(--fd-radius-full);
705
- animation: bounce 1.4s ease-in-out infinite;
706
- }
707
-
708
- .chat-panel__typing-indicator span:nth-child(1) {
709
- animation-delay: 0s;
710
- }
711
-
712
- .chat-panel__typing-indicator span:nth-child(2) {
713
- animation-delay: 0.2s;
714
- }
715
-
716
- .chat-panel__typing-indicator span:nth-child(3) {
717
- animation-delay: 0.4s;
718
- }
719
-
720
- @keyframes bounce {
721
- 0%,
722
- 60%,
723
- 100% {
724
- transform: translateY(0);
725
- }
726
- 30% {
727
- transform: translateY(-0.25rem);
728
- }
729
- }
730
-
731
- .chat-panel__typing-text {
732
- font-size: var(--fd-text-sm);
733
- color: var(--fd-muted-foreground);
734
- }
735
-
736
- /* Input Area - Always stays at bottom, never shrinks */
737
- .chat-panel__input-area {
738
- flex-shrink: 0;
739
- padding: var(--fd-space-xl) var(--fd-space-3xl) var(--fd-space-3xl);
740
- background-color: var(--fd-background);
741
- border-top: 1px solid var(--fd-border-muted);
742
- }
743
-
744
- .chat-panel__input-container {
745
- display: flex;
746
- align-items: flex-end;
747
- gap: var(--fd-space-md);
748
- max-width: 800px;
749
- margin: 0 auto;
750
- }
751
-
752
- .chat-panel__input-wrapper {
753
- flex: 1;
754
- display: flex;
755
- align-items: flex-end;
756
- background-color: var(--fd-background);
757
- border: 1px solid var(--fd-border);
758
- border-radius: var(--fd-radius-xl);
759
- padding: var(--fd-space-sm) var(--fd-space-md);
760
- transition:
761
- border-color var(--fd-transition-fast),
762
- box-shadow var(--fd-transition-fast);
763
- }
764
-
765
- .chat-panel__input-wrapper:focus-within {
766
- border-color: var(--fd-primary);
767
- box-shadow: 0 0 0 3px var(--fd-primary-muted);
768
- }
769
-
770
- .chat-panel__input {
771
- flex: 1;
772
- border: none;
773
- outline: none;
774
- resize: none;
775
- font-family: inherit;
776
- font-size: var(--fd-text-base);
777
- line-height: var(--fd-leading-normal);
778
- max-height: 120px;
779
- background: transparent;
780
- color: var(--fd-foreground);
781
- }
782
-
783
- .chat-panel__input::placeholder {
784
- color: var(--fd-muted-foreground);
785
- }
786
-
787
- .chat-panel__input:disabled {
788
- cursor: not-allowed;
789
- opacity: 0.6;
790
- }
791
-
792
- .chat-panel__send-btn {
793
- display: flex;
794
- align-items: center;
795
- justify-content: center;
796
- padding: var(--fd-space-sm) var(--fd-space-2xl);
797
- border: none;
798
- border-radius: var(--fd-radius-lg);
799
- background-color: var(--fd-foreground);
800
- color: var(--fd-background);
801
- font-size: var(--fd-text-sm);
802
- font-weight: 500;
803
- cursor: pointer;
804
- transition: all var(--fd-transition-fast);
805
- flex-shrink: 0;
806
- }
807
-
808
- .chat-panel__send-btn:hover:not(:disabled) {
809
- opacity: 0.85;
810
- }
811
-
812
- .chat-panel__send-btn:disabled {
813
- background-color: var(--fd-border);
814
- color: var(--fd-muted-foreground);
815
- cursor: not-allowed;
816
- }
817
-
818
- .chat-panel__stop-btn {
819
- display: flex;
820
- align-items: center;
821
- gap: var(--fd-space-3xs);
822
- padding: var(--fd-space-sm) var(--fd-space-xl);
823
- border: none;
824
- border-radius: var(--fd-radius-lg);
825
- background-color: var(--fd-error);
826
- color: var(--fd-error-foreground);
827
- font-size: var(--fd-text-sm);
828
- font-weight: 500;
829
- cursor: pointer;
830
- transition: background-color var(--fd-transition-fast);
831
- flex-shrink: 0;
832
- }
833
-
834
- .chat-panel__stop-btn:hover {
835
- background-color: var(--fd-error-hover);
836
- }
837
-
838
- /* Run button (when chat input is hidden) */
839
- .chat-panel__run-btn {
840
- display: flex;
841
- align-items: center;
842
- gap: var(--fd-space-3xs);
843
- padding: var(--fd-space-sm) var(--fd-space-2xl);
844
- border: none;
845
- border-radius: var(--fd-radius-lg);
846
- background-color: var(--fd-success);
847
- color: var(--fd-success-foreground);
848
- font-size: var(--fd-text-sm);
849
- font-weight: 500;
850
- cursor: pointer;
851
- transition: all var(--fd-transition-fast);
852
- flex-shrink: 0;
853
- }
854
-
855
- .chat-panel__run-btn:hover:not(:disabled) {
856
- background-color: var(--fd-success-hover);
857
- }
858
-
859
- .chat-panel__run-btn:disabled {
860
- background-color: var(--fd-border);
861
- color: var(--fd-muted-foreground);
862
- cursor: not-allowed;
863
- }
864
-
865
- /* Container modifier for run-only mode (no text input) */
866
- .chat-panel__input-container--run-only {
867
- justify-content: flex-end;
868
- }
869
-
870
- /* No inputs available message (view-only mode) */
871
- .chat-panel__no-inputs {
872
- display: flex;
873
- align-items: center;
874
- justify-content: center;
875
- gap: var(--fd-space-xs);
876
- padding: var(--fd-space-md) var(--fd-space-xl);
877
- background-color: var(--fd-muted);
878
- border-radius: var(--fd-radius-lg);
879
- color: var(--fd-muted-foreground);
880
- font-size: var(--fd-text-sm);
881
- max-width: 800px;
882
- margin: 0 auto;
883
- }
884
-
885
- /* Responsive */
886
- @media (max-width: 640px) {
887
- .chat-panel__messages {
888
- padding: var(--fd-space-xl);
889
- }
890
-
891
- .chat-panel__input-area {
892
- padding: var(--fd-space-md) var(--fd-space-xl) var(--fd-space-xl);
893
- }
894
-
895
- .chat-panel__input-container {
896
- gap: var(--fd-space-xs);
897
- }
898
-
899
- .chat-panel__send-btn,
900
- .chat-panel__stop-btn,
901
- .chat-panel__run-btn {
902
- padding: var(--fd-space-xs) var(--fd-space-xl);
903
- }
904
- }
683
+ .chat-panel {
684
+ display: flex;
685
+ flex-direction: column;
686
+ height: 100%;
687
+ min-height: 0; /* Critical: allows flexbox to shrink properly */
688
+ background-color: var(--fd-background);
689
+ }
690
+
691
+ /* Messages Container - Scrollable area that takes remaining space */
692
+ .chat-panel__messages {
693
+ flex: 1;
694
+ min-height: 0; /* Critical: allows overflow to work in flex container */
695
+ overflow-y: auto;
696
+ padding: var(--fd-space-3xl);
697
+ scroll-behavior: smooth;
698
+ }
699
+
700
+ /* Welcome State */
701
+ .chat-panel__welcome {
702
+ display: flex;
703
+ flex-direction: column;
704
+ align-items: center;
705
+ justify-content: center;
706
+ height: 100%;
707
+ text-align: center;
708
+ padding: var(--fd-space-4xl);
709
+ }
710
+
711
+ .chat-panel__welcome-icon {
712
+ display: flex;
713
+ align-items: center;
714
+ justify-content: center;
715
+ width: 80px;
716
+ height: 80px;
717
+ margin-bottom: var(--fd-space-3xl);
718
+ color: var(--fd-foreground);
719
+ }
720
+
721
+ .chat-panel__welcome-icon svg {
722
+ width: 100%;
723
+ height: 100%;
724
+ }
725
+
726
+ .chat-panel__welcome-title {
727
+ font-size: var(--fd-text-2xl);
728
+ font-weight: 600;
729
+ color: var(--fd-foreground);
730
+ margin: 0 0 var(--fd-space-xs) 0;
731
+ }
732
+
733
+ .chat-panel__welcome-text {
734
+ font-size: var(--fd-text-base);
735
+ color: var(--fd-muted-foreground);
736
+ margin: 0;
737
+ }
738
+
739
+ /* Typing Indicator */
740
+ .chat-panel__typing {
741
+ display: flex;
742
+ align-items: center;
743
+ gap: var(--fd-space-xs);
744
+ padding: var(--fd-space-md) var(--fd-space-xl);
745
+ margin-top: var(--fd-space-xs);
746
+ background-color: var(--fd-muted);
747
+ border-radius: var(--fd-radius-2xl);
748
+ width: fit-content;
749
+ }
750
+
751
+ .chat-panel__typing-indicator {
752
+ display: flex;
753
+ gap: var(--fd-space-3xs);
754
+ }
755
+
756
+ .chat-panel__typing-indicator span {
757
+ width: var(--fd-space-2xs);
758
+ height: var(--fd-space-2xs);
759
+ background-color: var(--fd-muted-foreground);
760
+ border-radius: var(--fd-radius-full);
761
+ animation: bounce 1.4s ease-in-out infinite;
762
+ }
763
+
764
+ .chat-panel__typing-indicator span:nth-child(1) {
765
+ animation-delay: 0s;
766
+ }
767
+
768
+ .chat-panel__typing-indicator span:nth-child(2) {
769
+ animation-delay: 0.2s;
770
+ }
771
+
772
+ .chat-panel__typing-indicator span:nth-child(3) {
773
+ animation-delay: 0.4s;
774
+ }
775
+
776
+ @keyframes bounce {
777
+ 0%,
778
+ 60%,
779
+ 100% {
780
+ transform: translateY(0);
781
+ }
782
+ 30% {
783
+ transform: translateY(-0.25rem);
784
+ }
785
+ }
786
+
787
+ .chat-panel__typing-text {
788
+ font-size: var(--fd-text-sm);
789
+ color: var(--fd-muted-foreground);
790
+ }
791
+
792
+ /* Input Area - Always stays at bottom, never shrinks */
793
+ .chat-panel__input-area {
794
+ flex-shrink: 0;
795
+ padding: var(--fd-space-xl) var(--fd-space-3xl) var(--fd-space-3xl);
796
+ background-color: var(--fd-background);
797
+ border-top: 1px solid var(--fd-border-muted);
798
+ }
799
+
800
+ .chat-panel__input-container {
801
+ display: flex;
802
+ align-items: flex-end;
803
+ gap: var(--fd-space-md);
804
+ max-width: 800px;
805
+ margin: 0 auto;
806
+ }
807
+
808
+ .chat-panel__input-wrapper {
809
+ flex: 1;
810
+ display: flex;
811
+ align-items: flex-end;
812
+ background-color: var(--fd-background);
813
+ border: 1px solid var(--fd-border);
814
+ border-radius: var(--fd-radius-xl);
815
+ padding: var(--fd-space-sm) var(--fd-space-md);
816
+ transition:
817
+ border-color var(--fd-transition-fast),
818
+ box-shadow var(--fd-transition-fast);
819
+ }
820
+
821
+ .chat-panel__input-wrapper:focus-within {
822
+ border-color: var(--fd-primary);
823
+ box-shadow: 0 0 0 3px var(--fd-primary-muted);
824
+ }
825
+
826
+ .chat-panel__input {
827
+ flex: 1;
828
+ border: none;
829
+ outline: none;
830
+ resize: none;
831
+ font-family: inherit;
832
+ font-size: var(--fd-text-base);
833
+ line-height: var(--fd-leading-normal);
834
+ max-height: 120px;
835
+ background: transparent;
836
+ color: var(--fd-foreground);
837
+ }
838
+
839
+ .chat-panel__input::placeholder {
840
+ color: var(--fd-muted-foreground);
841
+ }
842
+
843
+ .chat-panel__input:disabled {
844
+ cursor: not-allowed;
845
+ opacity: 0.6;
846
+ }
847
+
848
+ .chat-panel__send-btn {
849
+ display: flex;
850
+ align-items: center;
851
+ justify-content: center;
852
+ padding: var(--fd-space-sm) var(--fd-space-2xl);
853
+ border: none;
854
+ border-radius: var(--fd-radius-lg);
855
+ background-color: var(--fd-foreground);
856
+ color: var(--fd-background);
857
+ font-size: var(--fd-text-sm);
858
+ font-weight: 500;
859
+ cursor: pointer;
860
+ transition: all var(--fd-transition-fast);
861
+ flex-shrink: 0;
862
+ }
863
+
864
+ .chat-panel__send-btn:hover:not(:disabled) {
865
+ opacity: 0.85;
866
+ }
867
+
868
+ .chat-panel__send-btn:disabled {
869
+ background-color: var(--fd-border);
870
+ color: var(--fd-muted-foreground);
871
+ cursor: not-allowed;
872
+ }
873
+
874
+ .chat-panel__stop-btn {
875
+ display: flex;
876
+ align-items: center;
877
+ gap: var(--fd-space-3xs);
878
+ padding: var(--fd-space-sm) var(--fd-space-xl);
879
+ border: none;
880
+ border-radius: var(--fd-radius-lg);
881
+ background-color: var(--fd-error);
882
+ color: var(--fd-error-foreground);
883
+ font-size: var(--fd-text-sm);
884
+ font-weight: 500;
885
+ cursor: pointer;
886
+ transition: background-color var(--fd-transition-fast);
887
+ flex-shrink: 0;
888
+ }
889
+
890
+ .chat-panel__stop-btn:hover {
891
+ background-color: var(--fd-error-hover);
892
+ }
893
+
894
+ /* Run button (when chat input is hidden) */
895
+ .chat-panel__run-btn {
896
+ display: flex;
897
+ align-items: center;
898
+ gap: var(--fd-space-3xs);
899
+ padding: var(--fd-space-sm) var(--fd-space-2xl);
900
+ border: none;
901
+ border-radius: var(--fd-radius-lg);
902
+ background-color: var(--fd-success);
903
+ color: var(--fd-success-foreground);
904
+ font-size: var(--fd-text-sm);
905
+ font-weight: 500;
906
+ cursor: pointer;
907
+ transition: all var(--fd-transition-fast);
908
+ flex-shrink: 0;
909
+ }
910
+
911
+ .chat-panel__run-btn:hover:not(:disabled) {
912
+ background-color: var(--fd-success-hover);
913
+ }
914
+
915
+ .chat-panel__run-btn:disabled {
916
+ background-color: var(--fd-border);
917
+ color: var(--fd-muted-foreground);
918
+ cursor: not-allowed;
919
+ }
920
+
921
+ /* Container modifier for run-only mode (no text input) */
922
+ .chat-panel__input-container--run-only {
923
+ justify-content: flex-end;
924
+ }
925
+
926
+ /* No inputs available message (view-only mode) */
927
+ .chat-panel__no-inputs {
928
+ display: flex;
929
+ align-items: center;
930
+ justify-content: center;
931
+ gap: var(--fd-space-xs);
932
+ padding: var(--fd-space-md) var(--fd-space-xl);
933
+ background-color: var(--fd-muted);
934
+ border-radius: var(--fd-radius-lg);
935
+ color: var(--fd-muted-foreground);
936
+ font-size: var(--fd-text-sm);
937
+ max-width: 800px;
938
+ margin: 0 auto;
939
+ }
940
+
941
+ /* Responsive */
942
+ @media (max-width: 640px) {
943
+ .chat-panel__messages {
944
+ padding: var(--fd-space-xl);
945
+ }
946
+
947
+ .chat-panel__input-area {
948
+ padding: var(--fd-space-md) var(--fd-space-xl) var(--fd-space-xl);
949
+ }
950
+
951
+ .chat-panel__input-container {
952
+ gap: var(--fd-space-xs);
953
+ }
954
+
955
+ .chat-panel__send-btn,
956
+ .chat-panel__stop-btn,
957
+ .chat-panel__run-btn {
958
+ padding: var(--fd-space-xs) var(--fd-space-xl);
959
+ }
960
+ }
905
961
  </style>