@nyaruka/temba-components 0.131.0 → 0.131.2

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 (430) hide show
  1. package/.github/workflows/publish.yml +4 -1
  2. package/CHANGELOG.md +67 -1
  3. package/demo/data/flows/food-order.json +2 -2
  4. package/demo/data/flows/sample-flow.json +74 -125
  5. package/dist/static/svg/index.svg +1 -1
  6. package/dist/temba-components.js +1156 -619
  7. package/dist/temba-components.js.map +1 -1
  8. package/out-tsc/src/Icons.js +4 -1
  9. package/out-tsc/src/Icons.js.map +1 -1
  10. package/out-tsc/src/events.js.map +1 -1
  11. package/out-tsc/src/flow/CanvasMenu.js +200 -0
  12. package/out-tsc/src/flow/CanvasMenu.js.map +1 -0
  13. package/out-tsc/src/flow/CanvasNode.js +327 -19
  14. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  15. package/out-tsc/src/flow/Editor.js +562 -66
  16. package/out-tsc/src/flow/Editor.js.map +1 -1
  17. package/out-tsc/src/flow/NodeEditor.js +240 -93
  18. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  19. package/out-tsc/src/flow/NodeTypeSelector.js +499 -0
  20. package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -0
  21. package/out-tsc/src/flow/actions/add_contact_groups.js +3 -3
  22. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  23. package/out-tsc/src/flow/actions/add_contact_urn.js +62 -4
  24. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  25. package/out-tsc/src/flow/actions/add_input_labels.js +3 -3
  26. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  27. package/out-tsc/src/flow/actions/play_audio.js +2 -2
  28. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  29. package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -5
  30. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  31. package/out-tsc/src/flow/actions/request_optin.js +2 -2
  32. package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
  33. package/out-tsc/src/flow/actions/say_msg.js +2 -2
  34. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  35. package/out-tsc/src/flow/actions/send_broadcast.js +76 -23
  36. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  37. package/out-tsc/src/flow/actions/send_email.js +4 -5
  38. package/out-tsc/src/flow/actions/send_email.js.map +1 -1
  39. package/out-tsc/src/flow/actions/send_msg.js +9 -19
  40. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  41. package/out-tsc/src/flow/actions/set_contact_channel.js +5 -9
  42. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  43. package/out-tsc/src/flow/actions/set_contact_field.js +19 -20
  44. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  45. package/out-tsc/src/flow/actions/set_contact_language.js +2 -2
  46. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  47. package/out-tsc/src/flow/actions/set_contact_name.js +2 -12
  48. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  49. package/out-tsc/src/flow/actions/set_contact_status.js +2 -2
  50. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  51. package/out-tsc/src/flow/actions/set_run_result.js +3 -3
  52. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  53. package/out-tsc/src/flow/actions/start_session.js +180 -6
  54. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  55. package/out-tsc/src/flow/config.js +11 -15
  56. package/out-tsc/src/flow/config.js.map +1 -1
  57. package/out-tsc/src/flow/currencies.js +45 -0
  58. package/out-tsc/src/flow/currencies.js.map +1 -0
  59. package/out-tsc/src/flow/nodes/shared-rules.js +257 -0
  60. package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -0
  61. package/out-tsc/src/flow/nodes/shared.js +17 -0
  62. package/out-tsc/src/flow/nodes/shared.js.map +1 -0
  63. package/out-tsc/src/flow/nodes/split_by_airtime.js +205 -5
  64. package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
  65. package/out-tsc/src/flow/nodes/split_by_contact_field.js +147 -3
  66. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
  67. package/out-tsc/src/flow/nodes/split_by_expression.js +68 -2
  68. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
  69. package/out-tsc/src/flow/nodes/split_by_groups.js +12 -9
  70. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
  71. package/out-tsc/src/flow/nodes/split_by_intent.js +7 -0
  72. package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -0
  73. package/out-tsc/src/flow/nodes/split_by_llm.js +3 -2
  74. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  75. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +2 -2
  76. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  77. package/out-tsc/src/flow/nodes/split_by_random.js +3 -3
  78. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  79. package/out-tsc/src/flow/nodes/split_by_resthook.js +108 -0
  80. package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -0
  81. package/out-tsc/src/flow/nodes/split_by_run_result.js +206 -3
  82. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
  83. package/out-tsc/src/flow/nodes/split_by_scheme.js +153 -2
  84. package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
  85. package/out-tsc/src/flow/nodes/split_by_subflow.js +6 -4
  86. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  87. package/out-tsc/src/flow/nodes/split_by_ticket.js +3 -2
  88. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
  89. package/out-tsc/src/flow/nodes/split_by_webhook.js +3 -2
  90. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  91. package/out-tsc/src/flow/nodes/wait_for_audio.js +2 -2
  92. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -1
  93. package/out-tsc/src/flow/nodes/wait_for_digits.js +2 -2
  94. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  95. package/out-tsc/src/flow/nodes/wait_for_image.js +2 -2
  96. package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -1
  97. package/out-tsc/src/flow/nodes/wait_for_location.js +2 -2
  98. package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -1
  99. package/out-tsc/src/flow/nodes/wait_for_menu.js +2 -2
  100. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  101. package/out-tsc/src/flow/nodes/wait_for_response.js +32 -567
  102. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  103. package/out-tsc/src/flow/nodes/wait_for_video.js +2 -2
  104. package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -1
  105. package/out-tsc/src/flow/types.js +71 -12
  106. package/out-tsc/src/flow/types.js.map +1 -1
  107. package/out-tsc/src/flow/utils.js +101 -14
  108. package/out-tsc/src/flow/utils.js.map +1 -1
  109. package/out-tsc/src/form/ContactSearch.js +1 -1
  110. package/out-tsc/src/form/ContactSearch.js.map +1 -1
  111. package/out-tsc/src/form/FieldRenderer.js +2 -4
  112. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  113. package/out-tsc/src/interfaces.js +3 -0
  114. package/out-tsc/src/interfaces.js.map +1 -1
  115. package/out-tsc/src/list/SortableList.js +98 -33
  116. package/out-tsc/src/list/SortableList.js.map +1 -1
  117. package/out-tsc/src/live/ContactChat.js +15 -18
  118. package/out-tsc/src/live/ContactChat.js.map +1 -1
  119. package/out-tsc/src/store/AppState.js +53 -0
  120. package/out-tsc/src/store/AppState.js.map +1 -1
  121. package/out-tsc/src/utils.js +254 -13
  122. package/out-tsc/src/utils.js.map +1 -1
  123. package/out-tsc/temba-modules.js +4 -0
  124. package/out-tsc/temba-modules.js.map +1 -1
  125. package/out-tsc/test/ActionHelper.js +3 -3
  126. package/out-tsc/test/ActionHelper.js.map +1 -1
  127. package/out-tsc/test/NodeHelper.js +6 -3
  128. package/out-tsc/test/NodeHelper.js.map +1 -1
  129. package/out-tsc/test/actions/add_contact_urn.test.js +202 -0
  130. package/out-tsc/test/actions/add_contact_urn.test.js.map +1 -0
  131. package/out-tsc/test/actions/send_broadcast.test.js +148 -0
  132. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -0
  133. package/out-tsc/test/actions/send_email.test.js +17 -23
  134. package/out-tsc/test/actions/send_email.test.js.map +1 -1
  135. package/out-tsc/test/actions/send_msg.test.js +33 -15
  136. package/out-tsc/test/actions/send_msg.test.js.map +1 -1
  137. package/out-tsc/test/actions/start_session.test.js +116 -0
  138. package/out-tsc/test/actions/start_session.test.js.map +1 -0
  139. package/out-tsc/test/nodes/split_by_airtime.test.js +604 -0
  140. package/out-tsc/test/nodes/split_by_airtime.test.js.map +1 -0
  141. package/out-tsc/test/nodes/split_by_contact_field.test.js +387 -0
  142. package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -0
  143. package/out-tsc/test/nodes/split_by_expression.test.js +614 -0
  144. package/out-tsc/test/nodes/split_by_expression.test.js.map +1 -0
  145. package/out-tsc/test/nodes/split_by_random.test.js +3 -3
  146. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  147. package/out-tsc/test/nodes/split_by_resthook.test.js +337 -0
  148. package/out-tsc/test/nodes/split_by_resthook.test.js.map +1 -0
  149. package/out-tsc/test/nodes/split_by_run_result.test.js +920 -0
  150. package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -0
  151. package/out-tsc/test/nodes/split_by_scheme.test.js +399 -0
  152. package/out-tsc/test/nodes/split_by_scheme.test.js.map +1 -0
  153. package/out-tsc/test/nodes/split_by_subflow.test.js +333 -0
  154. package/out-tsc/test/nodes/split_by_subflow.test.js.map +1 -0
  155. package/out-tsc/test/nodes/wait_for_digits.test.js +2 -2
  156. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  157. package/out-tsc/test/nodes/wait_for_response.test.js +2 -1
  158. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  159. package/out-tsc/test/temba-action-drag-between-nodes.test.js +252 -0
  160. package/out-tsc/test/temba-action-drag-between-nodes.test.js.map +1 -0
  161. package/out-tsc/test/temba-canvas-menu.test.js +122 -0
  162. package/out-tsc/test/temba-canvas-menu.test.js.map +1 -0
  163. package/out-tsc/test/temba-flow-editor-node.test.js +85 -2
  164. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  165. package/out-tsc/test/temba-flow-editor.test.js +7 -8
  166. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  167. package/out-tsc/test/temba-node-editor.test.js +3 -1
  168. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  169. package/out-tsc/test/temba-node-type-selector.test.js +115 -0
  170. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -0
  171. package/out-tsc/test/temba-omnibox.test.js +2 -1
  172. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  173. package/out-tsc/test/temba-sortable-list.test.js +51 -0
  174. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  175. package/out-tsc/test/temba-utils-index.test.js +1 -27
  176. package/out-tsc/test/temba-utils-index.test.js.map +1 -1
  177. package/out-tsc/test/utils.test.js +2 -0
  178. package/out-tsc/test/utils.test.js.map +1 -1
  179. package/package.json +2 -1
  180. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  181. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  182. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  183. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  184. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  185. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  186. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  187. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  188. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  189. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  190. package/screenshots/truth/actions/add_contact_urn/editor/expression-facebook.png +0 -0
  191. package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
  192. package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
  193. package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
  194. package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
  195. package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
  196. package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
  197. package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
  198. package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
  199. package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
  200. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  201. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  202. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  203. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  204. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  205. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  206. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  207. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  208. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  209. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  210. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  211. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  212. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  213. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  214. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  215. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  216. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  217. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  218. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  219. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  220. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  221. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  222. package/screenshots/truth/actions/send_broadcast/editor/contacts-only.png +0 -0
  223. package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
  224. package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
  225. package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
  226. package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
  227. package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
  228. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  229. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  230. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  231. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  232. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  233. package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
  234. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  235. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  236. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  237. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  238. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  239. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  240. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  241. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  242. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  243. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  244. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  245. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  246. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  247. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  248. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  249. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  250. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  251. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  252. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  253. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  254. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  255. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  256. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  257. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  258. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  259. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  260. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  261. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  262. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  263. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  264. package/screenshots/truth/actions/start_session/editor/contact-query.png +0 -0
  265. package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
  266. package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
  267. package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
  268. package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
  269. package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
  270. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  271. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  272. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  273. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  274. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  275. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  276. package/screenshots/truth/canvas-menu/open.png +0 -0
  277. package/screenshots/truth/editor/router.png +0 -0
  278. package/screenshots/truth/editor/wait.png +0 -0
  279. package/screenshots/truth/list/fields-dragging.png +0 -0
  280. package/screenshots/truth/list/sortable-dragging.png +0 -0
  281. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  282. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  283. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  284. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  285. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  286. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  287. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  288. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  289. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  290. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  291. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  292. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  293. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  294. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  295. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  296. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  297. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  298. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  299. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  300. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  301. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  302. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  303. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  304. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  305. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  306. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  307. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  308. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  309. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  310. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  311. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  312. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  313. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  314. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  315. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  316. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  317. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  318. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  319. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  320. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  321. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  322. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  323. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  324. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  325. package/src/Icons.ts +4 -1
  326. package/src/events.ts +2 -6
  327. package/src/flow/CanvasMenu.ts +217 -0
  328. package/src/flow/CanvasNode.ts +408 -10
  329. package/src/flow/Editor.ts +683 -44
  330. package/src/flow/NodeEditor.ts +304 -125
  331. package/src/flow/NodeTypeSelector.ts +592 -0
  332. package/src/flow/actions/add_contact_groups.ts +4 -4
  333. package/src/flow/actions/add_contact_urn.ts +76 -4
  334. package/src/flow/actions/add_input_labels.ts +4 -4
  335. package/src/flow/actions/play_audio.ts +2 -2
  336. package/src/flow/actions/remove_contact_groups.ts +14 -6
  337. package/src/flow/actions/request_optin.ts +2 -2
  338. package/src/flow/actions/say_msg.ts +2 -2
  339. package/src/flow/actions/send_broadcast.ts +85 -23
  340. package/src/flow/actions/send_email.ts +10 -6
  341. package/src/flow/actions/send_msg.ts +22 -32
  342. package/src/flow/actions/set_contact_channel.ts +5 -11
  343. package/src/flow/actions/set_contact_field.ts +20 -25
  344. package/src/flow/actions/set_contact_language.ts +9 -4
  345. package/src/flow/actions/set_contact_name.ts +3 -15
  346. package/src/flow/actions/set_contact_status.ts +3 -3
  347. package/src/flow/actions/set_run_result.ts +4 -4
  348. package/src/flow/actions/start_session.ts +208 -6
  349. package/src/flow/config.ts +13 -15
  350. package/src/flow/currencies.ts +51 -0
  351. package/src/flow/nodes/shared-rules.ts +301 -0
  352. package/src/flow/nodes/shared.ts +18 -0
  353. package/src/flow/nodes/split_by_airtime.ts +238 -5
  354. package/src/flow/nodes/split_by_contact_field.ts +185 -3
  355. package/src/flow/nodes/split_by_expression.ts +94 -2
  356. package/src/flow/nodes/split_by_groups.ts +15 -10
  357. package/src/flow/nodes/split_by_intent.ts +7 -0
  358. package/src/flow/nodes/split_by_llm.ts +4 -3
  359. package/src/flow/nodes/split_by_llm_categorize.ts +4 -4
  360. package/src/flow/nodes/split_by_random.ts +5 -5
  361. package/src/flow/nodes/split_by_resthook.ts +130 -0
  362. package/src/flow/nodes/split_by_run_result.ts +249 -3
  363. package/src/flow/nodes/split_by_scheme.ts +192 -2
  364. package/src/flow/nodes/split_by_subflow.ts +6 -4
  365. package/src/flow/nodes/split_by_ticket.ts +4 -3
  366. package/src/flow/nodes/split_by_webhook.ts +6 -5
  367. package/src/flow/nodes/wait_for_audio.ts +2 -2
  368. package/src/flow/nodes/wait_for_digits.ts +2 -2
  369. package/src/flow/nodes/wait_for_image.ts +2 -2
  370. package/src/flow/nodes/wait_for_location.ts +2 -2
  371. package/src/flow/nodes/wait_for_menu.ts +2 -2
  372. package/src/flow/nodes/wait_for_response.ts +48 -679
  373. package/src/flow/nodes/wait_for_video.ts +2 -2
  374. package/src/flow/types.ts +109 -23
  375. package/src/flow/utils.ts +108 -14
  376. package/src/form/ContactSearch.ts +1 -1
  377. package/src/form/FieldRenderer.ts +2 -4
  378. package/src/interfaces.ts +3 -0
  379. package/src/list/SortableList.ts +109 -34
  380. package/src/live/ContactChat.ts +15 -18
  381. package/src/store/AppState.ts +69 -0
  382. package/src/store/flow-definition.d.ts +2 -5
  383. package/src/utils.ts +332 -12
  384. package/static/api/channels.json +46 -0
  385. package/static/api/resthooks.json +31 -0
  386. package/static/svg/index.svg +1 -1
  387. package/static/svg/work/traced/lightning-02.svg +1 -0
  388. package/static/svg/work/used/lightning-02.svg +3 -0
  389. package/temba-modules.ts +4 -0
  390. package/test/ActionHelper.ts +3 -3
  391. package/test/NodeHelper.ts +6 -3
  392. package/test/actions/add_contact_urn.test.ts +287 -0
  393. package/test/actions/send_broadcast.test.ts +190 -0
  394. package/test/actions/send_email.test.ts +17 -23
  395. package/test/actions/send_msg.test.ts +39 -15
  396. package/test/actions/start_session.test.ts +151 -0
  397. package/test/nodes/split_by_airtime.test.ts +673 -0
  398. package/test/nodes/split_by_contact_field.test.ts +451 -0
  399. package/test/nodes/split_by_expression.test.ts +751 -0
  400. package/test/nodes/split_by_random.test.ts +3 -3
  401. package/test/nodes/split_by_resthook.test.ts +398 -0
  402. package/test/nodes/split_by_run_result.test.ts +1109 -0
  403. package/test/nodes/split_by_scheme.test.ts +486 -0
  404. package/test/nodes/split_by_subflow.test.ts +381 -0
  405. package/test/nodes/wait_for_digits.test.ts +2 -2
  406. package/test/nodes/wait_for_response.test.ts +2 -1
  407. package/test/temba-action-drag-between-nodes.test.ts +301 -0
  408. package/test/temba-canvas-menu.test.ts +156 -0
  409. package/test/temba-flow-editor-node.test.ts +102 -2
  410. package/test/temba-flow-editor.test.ts +7 -8
  411. package/test/temba-node-editor.test.ts +3 -1
  412. package/test/temba-node-type-selector.test.ts +152 -0
  413. package/test/temba-omnibox.test.ts +2 -1
  414. package/test/temba-sortable-list.test.ts +69 -0
  415. package/test/temba-utils-index.test.ts +0 -35
  416. package/test/utils.test.ts +2 -0
  417. package/test-assets/contacts/history.json +14 -20
  418. package/web-dev-server.config.mjs +3 -1
  419. package/out-tsc/src/flow/actions/call_classifier.js +0 -11
  420. package/out-tsc/src/flow/actions/call_classifier.js.map +0 -1
  421. package/out-tsc/src/flow/actions/call_resthook.js +0 -11
  422. package/out-tsc/src/flow/actions/call_resthook.js.map +0 -1
  423. package/out-tsc/src/flow/actions/split_by_expression_example.js +0 -77
  424. package/out-tsc/src/flow/actions/split_by_expression_example.js.map +0 -1
  425. package/out-tsc/src/flow/actions/transfer_airtime.js +0 -11
  426. package/out-tsc/src/flow/actions/transfer_airtime.js.map +0 -1
  427. package/src/flow/actions/call_classifier.ts +0 -12
  428. package/src/flow/actions/call_resthook.ts +0 -12
  429. package/src/flow/actions/split_by_expression_example.ts +0 -88
  430. package/src/flow/actions/transfer_airtime.ts +0 -12
@@ -1,7 +1,189 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, FormData, NodeConfig } from '../types';
2
+ import { Node } from '../../store/flow-definition';
3
+ import { createRulesRouter } from '../../utils';
4
+ import {
5
+ getWaitForResponseOperators,
6
+ operatorsToSelectOptions,
7
+ getOperatorConfig
8
+ } from '../operators';
9
+ import { resultNameField } from './shared';
10
+ import {
11
+ createRulesArrayConfig,
12
+ extractUserRules,
13
+ casesToFormRules
14
+ } from './shared-rules';
15
+ import { SCHEMES } from '../utils';
16
+ import { html } from 'lit';
17
+
18
+ // System contact properties that can be split on
19
+ export const CONTACT_PROPERTIES = {
20
+ name: { id: 'name', name: 'Name', type: 'property' },
21
+ language: { id: 'language', name: 'Language', type: 'property' },
22
+ status: { id: 'status', name: 'Status', type: 'property' },
23
+ channel: { id: 'channel', name: 'Channel', type: 'property' }
24
+ };
25
+
26
+ // Helper to get operand for the selected field
27
+ const getOperandForField = (field: any): string => {
28
+ // For URN schemes, split on the URN path (the actual ID value like Facebook ID)
29
+ // Note: This is different from split_by_scheme which splits on the scheme type itself
30
+ if (field.type === 'scheme') {
31
+ // field.id or field.value will be the scheme name like 'facebook', 'whatsapp', etc
32
+ const schemeId = field.id || field.value;
33
+ return `@(default(urn_parts(urns.${schemeId}).path, ""))`;
34
+ }
35
+ // For system properties, use the id
36
+ if (field.type === 'property') {
37
+ return `@contact.${field.id}`;
38
+ }
39
+ // Fallback to key
40
+ return `@fields.${field.key}`;
41
+ };
2
42
 
3
43
  export const split_by_contact_field: NodeConfig = {
4
44
  type: 'split_by_contact_field',
5
- name: 'Split by <Contact Field Name>',
6
- color: COLORS.split
45
+ name: 'Split by Contact Field',
46
+ group: SPLIT_GROUPS.split,
47
+ dialogSize: 'large',
48
+ form: {
49
+ field: {
50
+ type: 'select',
51
+ required: true,
52
+ searchable: true,
53
+ clearable: false,
54
+ endpoint: '/api/v2/fields.json',
55
+ valueKey: 'key',
56
+ nameKey: 'name',
57
+ placeholder: 'Select a field...',
58
+ // Provide system properties as fixed options at the top
59
+ options: [
60
+ ...Object.values(CONTACT_PROPERTIES).map((prop) => ({
61
+ value: prop.id,
62
+ name: prop.name,
63
+ type: prop.type
64
+ })),
65
+ // Add all URN scheme options (they represent splitting on the URN value, like Facebook ID)
66
+ ...SCHEMES.filter((scheme) => !scheme.excludeFromSplit).map(
67
+ (scheme) => ({
68
+ value: scheme.scheme,
69
+ name: scheme.path,
70
+ type: 'scheme'
71
+ })
72
+ )
73
+ ]
74
+ },
75
+ rules: createRulesArrayConfig(
76
+ operatorsToSelectOptions(getWaitForResponseOperators()),
77
+ 'Define rules to split the contact field into categories'
78
+ ),
79
+ result_name: resultNameField
80
+ },
81
+ layout: ['field', 'rules', 'result_name'],
82
+ validate: (formData: FormData) => {
83
+ const errors: { [key: string]: string } = {};
84
+
85
+ if (!formData.field || formData.field.length === 0) {
86
+ errors.field = 'A field is required';
87
+ }
88
+
89
+ return {
90
+ valid: Object.keys(errors).length === 0,
91
+ errors
92
+ };
93
+ },
94
+ toFormData: (node: Node, nodeUI?: any) => {
95
+ // Get the field from the UI config operand (source of truth)
96
+ const field = nodeUI?.config?.operand || CONTACT_PROPERTIES.name;
97
+
98
+ // Extract rules from router cases using shared function
99
+ const rules = casesToFormRules(node);
100
+
101
+ return {
102
+ uuid: node.uuid,
103
+ field: [field],
104
+ rules: rules,
105
+ result_name: node.router?.result_name || ''
106
+ };
107
+ },
108
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
109
+ // Get selected field (it's an array from the select component)
110
+ const selectedField = formData.field?.[0];
111
+
112
+ if (!selectedField) {
113
+ return originalNode;
114
+ }
115
+
116
+ // Get operand for the selected field
117
+ const operand = getOperandForField(selectedField);
118
+
119
+ // Get user rules using shared extraction function
120
+ const userRules = extractUserRules(formData);
121
+
122
+ // Get existing router data for preservation
123
+ const existingCategories = originalNode.router?.categories || [];
124
+ const existingExits = originalNode.exits || [];
125
+ const existingCases = originalNode.router?.cases || [];
126
+
127
+ // Create router and exits using existing data when possible
128
+ const { router, exits } = createRulesRouter(
129
+ operand,
130
+ userRules,
131
+ getOperatorConfig,
132
+ existingCategories,
133
+ existingExits,
134
+ existingCases
135
+ );
136
+
137
+ // Build final router with result_name
138
+ const finalRouter: any = {
139
+ ...router
140
+ };
141
+
142
+ // Only set result_name if provided
143
+ if (formData.result_name && formData.result_name.trim() !== '') {
144
+ finalRouter.result_name = formData.result_name.trim();
145
+ }
146
+
147
+ return {
148
+ ...originalNode,
149
+ router: finalRouter,
150
+ exits: exits
151
+ };
152
+ },
153
+ toUIConfig: (formData: FormData) => {
154
+ // Get selected field (it's an array from the select component)
155
+ const selectedField = formData.field?.[0];
156
+
157
+ if (!selectedField) {
158
+ return {};
159
+ }
160
+
161
+ // For scheme types, the id should be the scheme name (facebook, whatsapp, etc)
162
+ // For custom fields, the id should be the field key
163
+ // For system properties, the id should be the property id
164
+ let operandId =
165
+ selectedField.id || selectedField.value || selectedField.key;
166
+
167
+ // If this is a scheme type, ensure we use the scheme name as the id
168
+ if (selectedField.type === 'scheme') {
169
+ operandId = selectedField.value || selectedField.id;
170
+ }
171
+
172
+ let type = selectedField.type;
173
+ if (type !== 'property' && type !== 'scheme') {
174
+ type = 'field';
175
+ }
176
+
177
+ // Return UI config with operand information for persistence
178
+ return {
179
+ operand: {
180
+ id: operandId,
181
+ name: selectedField.name || selectedField.label,
182
+ type
183
+ }
184
+ };
185
+ },
186
+ renderTitle: (node: Node, nodeUI?: any) => {
187
+ return html`<div>Split by ${nodeUI.config.operand.name}</div>`;
188
+ }
7
189
  };
@@ -1,7 +1,99 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, FormData, NodeConfig } from '../types';
2
+ import { Node } from '../../store/flow-definition';
3
+ import { createRulesRouter } from '../../utils';
4
+ import {
5
+ getWaitForResponseOperators,
6
+ operatorsToSelectOptions,
7
+ getOperatorConfig
8
+ } from '../operators';
9
+ import { resultNameField } from './shared';
10
+ import {
11
+ createRulesArrayConfig,
12
+ extractUserRules,
13
+ casesToFormRules
14
+ } from './shared-rules';
2
15
 
3
16
  export const split_by_expression: NodeConfig = {
4
17
  type: 'split_by_expression',
5
18
  name: 'Split by Expression',
6
- color: COLORS.split
19
+ group: SPLIT_GROUPS.split,
20
+ dialogSize: 'large',
21
+ form: {
22
+ operand: {
23
+ type: 'text',
24
+ label: 'Expression',
25
+ helpText: 'The expression to evaluate and split on',
26
+ required: true,
27
+ evaluated: true,
28
+ placeholder: '@fields.age'
29
+ },
30
+ rules: createRulesArrayConfig(
31
+ operatorsToSelectOptions(getWaitForResponseOperators()),
32
+ 'Define rules to categorize the expression result'
33
+ ),
34
+ result_name: resultNameField
35
+ },
36
+ layout: ['operand', 'rules', 'result_name'],
37
+ validate: (formData: FormData) => {
38
+ const errors: { [key: string]: string } = {};
39
+
40
+ // Validate operand is provided
41
+ if (!formData.operand || formData.operand.trim() === '') {
42
+ errors.operand = 'Expression is required';
43
+ }
44
+
45
+ return {
46
+ valid: Object.keys(errors).length === 0,
47
+ errors
48
+ };
49
+ },
50
+ toFormData: (node: Node) => {
51
+ // Extract rules from router cases using shared function
52
+ const rules = casesToFormRules(node);
53
+
54
+ return {
55
+ uuid: node.uuid,
56
+ operand: node.router?.operand || '@input.text',
57
+ rules: rules,
58
+ result_name: node.router?.result_name || ''
59
+ };
60
+ },
61
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
62
+ // Get user rules using shared extraction function
63
+ const userRules = extractUserRules(formData);
64
+
65
+ // Get operand from form data
66
+ const operand = formData.operand?.trim() || '@input.text';
67
+
68
+ // Get existing router data for preservation
69
+ const existingCategories = originalNode.router?.categories || [];
70
+ const existingExits = originalNode.exits || [];
71
+ const existingCases = originalNode.router?.cases || [];
72
+
73
+ // Create router and exits using existing data when possible
74
+ const { router, exits } = createRulesRouter(
75
+ operand,
76
+ userRules,
77
+ getOperatorConfig,
78
+ existingCategories,
79
+ existingExits,
80
+ existingCases
81
+ );
82
+
83
+ // Build final router with result_name
84
+ const finalRouter: any = {
85
+ ...router
86
+ };
87
+
88
+ // Only set result_name if provided
89
+ if (formData.result_name && formData.result_name.trim() !== '') {
90
+ finalRouter.result_name = formData.result_name.trim();
91
+ }
92
+
93
+ return {
94
+ ...originalNode,
95
+ router: finalRouter,
96
+ exits: exits
97
+ };
98
+ }
7
99
  };
@@ -1,13 +1,15 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, FormData, NodeConfig } from '../types';
2
2
  import { Node, Category, Exit, Case } from '../../store/flow-definition.d';
3
3
  import { generateUUID } from '../../utils';
4
+ import { resultNameField } from './shared';
4
5
 
5
6
  // Helper function to create a switch router with group cases
6
7
  const createGroupRouter = (
7
8
  userGroups: { uuid: string; name: string }[],
8
9
  existingCategories: Category[] = [],
9
10
  existingExits: Exit[] = [],
10
- existingCases: Case[] = []
11
+ existingCases: Case[] = [],
12
+ resultName: string = ''
11
13
  ) => {
12
14
  const categories: Category[] = [];
13
15
  const exits: Exit[] = [];
@@ -82,7 +84,7 @@ const createGroupRouter = (
82
84
  categories: categories,
83
85
  default_category_uuid: otherCategoryUuid,
84
86
  operand: '@contact.groups',
85
- result_name: ''
87
+ result_name: resultName
86
88
  },
87
89
  exits: exits
88
90
  };
@@ -91,7 +93,7 @@ const createGroupRouter = (
91
93
  export const split_by_groups: NodeConfig = {
92
94
  type: 'split_by_groups',
93
95
  name: 'Split by Group',
94
- color: COLORS.split,
96
+ group: SPLIT_GROUPS.split,
95
97
  form: {
96
98
  groups: {
97
99
  type: 'select',
@@ -121,10 +123,11 @@ export const split_by_groups: NodeConfig = {
121
123
  }
122
124
  return null;
123
125
  }
124
- }
126
+ },
127
+ result_name: resultNameField
125
128
  },
126
- layout: ['groups'],
127
- validate: (formData: any) => {
129
+ layout: ['groups', 'result_name'],
130
+ validate: (formData: FormData) => {
128
131
  const errors: { [key: string]: string } = {};
129
132
 
130
133
  if (
@@ -157,10 +160,11 @@ export const split_by_groups: NodeConfig = {
157
160
 
158
161
  return {
159
162
  uuid: node.uuid,
160
- groups: groups
163
+ groups: groups,
164
+ result_name: node.router?.result_name || ''
161
165
  };
162
166
  },
163
- fromFormData: (formData: any, originalNode: Node): Node => {
167
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
164
168
  // Get selected groups
165
169
  const selectedGroups = (formData.groups || [])
166
170
  .filter((group: any) => group?.uuid || group?.arbitrary)
@@ -178,7 +182,8 @@ export const split_by_groups: NodeConfig = {
178
182
  selectedGroups,
179
183
  existingCategories,
180
184
  existingExits,
181
- existingCases
185
+ existingCases,
186
+ formData.result_name || ''
182
187
  );
183
188
 
184
189
  // Return the complete node
@@ -0,0 +1,7 @@
1
+ import { NodeConfig, ACTION_GROUPS } from '../types';
2
+
3
+ export const split_by_llm_categorize: NodeConfig = {
4
+ type: 'split_by_intent',
5
+ name: 'Call classifier',
6
+ group: ACTION_GROUPS.services
7
+ };
@@ -1,4 +1,4 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { ACTION_GROUPS, FormData, NodeConfig } from '../types';
2
2
  import { CallLLM, Node } from '../../store/flow-definition';
3
3
  import { generateUUID, createSuccessFailureRouter } from '../../utils';
4
4
  import { html } from 'lit';
@@ -6,7 +6,8 @@ import { html } from 'lit';
6
6
  export const split_by_llm: NodeConfig = {
7
7
  type: 'split_by_llm',
8
8
  name: 'Call AI',
9
- color: COLORS.call,
9
+ group: ACTION_GROUPS.services,
10
+ showAsAction: true,
10
11
  render: (node: Node) => {
11
12
  const callLlmAction = node.actions?.find(
12
13
  (action) => action.type === 'call_llm'
@@ -68,7 +69,7 @@ export const split_by_llm: NodeConfig = {
68
69
  instructions: callLlmAction?.instructions || ''
69
70
  };
70
71
  },
71
- fromFormData: (formData: any, originalNode: Node): Node => {
72
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
72
73
  // Get LLM selection
73
74
  const llmSelection =
74
75
  Array.isArray(formData.llm) && formData.llm.length > 0
@@ -1,4 +1,4 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { FormData, NodeConfig, ACTION_GROUPS } from '../types';
2
2
  import { CallLLM, Node } from '../../store/flow-definition';
3
3
  import { generateUUID, createMultiCategoryRouter } from '../../utils';
4
4
  import { html } from 'lit';
@@ -6,7 +6,7 @@ import { html } from 'lit';
6
6
  export const split_by_llm_categorize: NodeConfig = {
7
7
  type: 'split_by_llm_categorize',
8
8
  name: 'Split by AI',
9
- color: COLORS.call,
9
+ group: ACTION_GROUPS.services,
10
10
  form: {
11
11
  llm: {
12
12
  type: 'select',
@@ -48,7 +48,7 @@ export const split_by_llm_categorize: NodeConfig = {
48
48
  }
49
49
  },
50
50
  layout: ['llm', 'input', 'categories'],
51
- validate: (formData: any) => {
51
+ validate: (formData: FormData) => {
52
52
  const errors: { [key: string]: string } = {};
53
53
 
54
54
  // Check for duplicate category names
@@ -117,7 +117,7 @@ export const split_by_llm_categorize: NodeConfig = {
117
117
  categories: categories
118
118
  };
119
119
  },
120
- fromFormData: (formData: any, originalNode: Node): Node => {
120
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
121
121
  // Get LLM selection
122
122
  const llmSelection =
123
123
  Array.isArray(formData.llm) && formData.llm.length > 0
@@ -1,4 +1,4 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, FormData, NodeConfig } from '../types';
2
2
  import { Node, Category, Exit } from '../../store/flow-definition.d';
3
3
  import { generateUUID } from '../../utils';
4
4
 
@@ -47,8 +47,8 @@ const createRandomRouter = (
47
47
 
48
48
  export const split_by_random: NodeConfig = {
49
49
  type: 'split_by_random',
50
- name: 'Split by Random',
51
- color: COLORS.split,
50
+ name: 'Random Split',
51
+ group: SPLIT_GROUPS.split,
52
52
  form: {
53
53
  categories: {
54
54
  type: 'array',
@@ -72,7 +72,7 @@ export const split_by_random: NodeConfig = {
72
72
  }
73
73
  },
74
74
  layout: ['categories'],
75
- validate: (formData: any) => {
75
+ validate: (formData: FormData) => {
76
76
  const errors: { [key: string]: string } = {};
77
77
 
78
78
  // Check for duplicate category names
@@ -129,7 +129,7 @@ export const split_by_random: NodeConfig = {
129
129
  categories: categories
130
130
  };
131
131
  },
132
- fromFormData: (formData: any, originalNode: Node): Node => {
132
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
133
133
  // Get user categories
134
134
  const userCategories = (formData.categories || [])
135
135
  .filter((item: any) => item?.name?.trim())
@@ -0,0 +1,130 @@
1
+ import { ACTION_GROUPS, FormData, NodeConfig } from '../types';
2
+ import { CallResthook, Node } from '../../store/flow-definition';
3
+ import { generateUUID, createSuccessFailureRouter } from '../../utils';
4
+ import { html } from 'lit';
5
+ import { resultNameField } from './shared';
6
+
7
+ export const split_by_resthook: NodeConfig = {
8
+ type: 'split_by_resthook',
9
+ name: 'Call Resthook',
10
+ group: ACTION_GROUPS.services,
11
+ showAsAction: true,
12
+ form: {
13
+ resthook: {
14
+ type: 'select',
15
+ label: 'Resthook',
16
+ required: true,
17
+ searchable: true,
18
+ clearable: false,
19
+ placeholder: 'Select a resthook...',
20
+ endpoint: '/api/v2/resthooks.json',
21
+ valueKey: 'resthook',
22
+ nameKey: 'resthook',
23
+ helpText: 'Select the resthook to call'
24
+ },
25
+ result_name: resultNameField
26
+ },
27
+ layout: ['resthook', 'result_name'],
28
+ validate: (formData: FormData) => {
29
+ const errors: { [key: string]: string } = {};
30
+
31
+ // validate resthook is provided
32
+ if (!formData.resthook || formData.resthook.length === 0) {
33
+ errors.resthook = 'A resthook is required';
34
+ }
35
+
36
+ return {
37
+ valid: Object.keys(errors).length === 0,
38
+ errors
39
+ };
40
+ },
41
+ render: (node: Node) => {
42
+ const callResthookAction = node.actions?.find(
43
+ (action) => action.type === 'call_resthook'
44
+ ) as CallResthook;
45
+ return html`
46
+ <div
47
+ class="body"
48
+ style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto;"
49
+ >
50
+ ${callResthookAction?.resthook || 'Configure resthook'}
51
+ </div>
52
+ `;
53
+ },
54
+ toFormData: (node: Node) => {
55
+ // extract data from the existing node structure
56
+ const callResthookAction = node.actions?.find(
57
+ (action) => action.type === 'call_resthook'
58
+ ) as CallResthook;
59
+
60
+ return {
61
+ uuid: node.uuid,
62
+ resthook: callResthookAction?.resthook
63
+ ? [
64
+ {
65
+ resthook: callResthookAction.resthook
66
+ }
67
+ ]
68
+ : [],
69
+ result_name: node.router?.result_name || ''
70
+ };
71
+ },
72
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
73
+ // get resthook selection
74
+ const resthookSelection =
75
+ Array.isArray(formData.resthook) && formData.resthook.length > 0
76
+ ? formData.resthook[0]
77
+ : null;
78
+
79
+ if (!resthookSelection) {
80
+ return originalNode;
81
+ }
82
+
83
+ // find existing call_resthook action to preserve its UUID
84
+ const existingCallResthookAction = originalNode.actions?.find(
85
+ (action) => action.type === 'call_resthook'
86
+ );
87
+ const callResthookUuid = existingCallResthookAction?.uuid || generateUUID();
88
+
89
+ // create call_resthook action
90
+ const callResthookAction: CallResthook = {
91
+ type: 'call_resthook',
92
+ uuid: callResthookUuid,
93
+ resthook: resthookSelection.resthook
94
+ };
95
+
96
+ // create categories and exits for Success and Failure
97
+ const existingCategories = originalNode.router?.categories || [];
98
+ const existingExits = originalNode.exits || [];
99
+ const existingCases = originalNode.router?.cases || [];
100
+
101
+ const { router, exits } = createSuccessFailureRouter(
102
+ '@webhook.json.status',
103
+ {
104
+ type: 'has_text',
105
+ arguments: []
106
+ },
107
+ existingCategories,
108
+ existingExits,
109
+ existingCases
110
+ );
111
+
112
+ // Build final router with result_name
113
+ const finalRouter: any = {
114
+ ...router
115
+ };
116
+
117
+ // Only set result_name if provided
118
+ if (formData.result_name && formData.result_name.trim() !== '') {
119
+ finalRouter.result_name = formData.result_name.trim();
120
+ }
121
+
122
+ // return the complete node
123
+ return {
124
+ uuid: originalNode.uuid,
125
+ actions: [callResthookAction],
126
+ router: finalRouter,
127
+ exits: exits
128
+ };
129
+ }
130
+ };