@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,253 @@
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 { getStore } from '../../store/Store';
16
+
17
+ // delimit index options (first through 20th)
18
+ const DELIMIT_INDEX_OPTIONS = [
19
+ { value: '0', name: 'first result' },
20
+ { value: '1', name: 'second result' },
21
+ { value: '2', name: 'third result' },
22
+ { value: '3', name: 'fourth result' },
23
+ { value: '4', name: 'fifth result' },
24
+ { value: '5', name: 'sixth result' },
25
+ { value: '6', name: 'seventh result' },
26
+ { value: '7', name: 'eighth result' },
27
+ { value: '8', name: 'ninth result' },
28
+ { value: '9', name: 'tenth result' },
29
+ { value: '10', name: '11th result' },
30
+ { value: '11', name: '12th result' },
31
+ { value: '12', name: '13th result' },
32
+ { value: '13', name: '14th result' },
33
+ { value: '14', name: '15th result' },
34
+ { value: '15', name: '16th result' },
35
+ { value: '16', name: '17th result' },
36
+ { value: '17', name: '18th result' },
37
+ { value: '18', name: '19th result' },
38
+ { value: '19', name: '20th result' }
39
+ ];
40
+
41
+ // delimit by options - includes "don't delimit" and delimiter characters
42
+ const DELIMIT_BY_OPTIONS = [
43
+ { value: '', name: "Don't delimit result" },
44
+ { value: ' ', name: 'Delimited by spaces' },
45
+ { value: '.', name: 'Delimited by periods' },
46
+ { value: '+', name: 'Delimited by plusses' }
47
+ ];
2
48
 
3
49
  export const split_by_run_result: NodeConfig = {
4
50
  type: 'split_by_run_result',
5
- name: 'Split by Flow Result',
6
- color: COLORS.split
51
+ name: 'Split by Result',
52
+ group: SPLIT_GROUPS.split,
53
+ dialogSize: 'large',
54
+ form: {
55
+ result: {
56
+ type: 'select',
57
+
58
+ required: true,
59
+ searchable: false,
60
+ clearable: false,
61
+ placeholder: 'Select a result...',
62
+ getDynamicOptions: () => {
63
+ const store = getStore();
64
+ return store
65
+ ? store
66
+ .getState()
67
+ .getFlowResults()
68
+ .map((r) => ({ value: r.key, name: r.name }))
69
+ : [];
70
+ },
71
+ valueKey: 'value',
72
+ nameKey: 'name'
73
+ },
74
+ delimit_by: {
75
+ type: 'select',
76
+ required: false,
77
+ searchable: false,
78
+ clearable: false,
79
+ options: DELIMIT_BY_OPTIONS,
80
+ valueKey: 'value',
81
+ nameKey: 'name',
82
+ maxWidth: '180px'
83
+ },
84
+ delimit_index: {
85
+ type: 'select',
86
+ required: false,
87
+ searchable: false,
88
+ clearable: false,
89
+ options: DELIMIT_INDEX_OPTIONS,
90
+ valueKey: 'value',
91
+ nameKey: 'name',
92
+ maxWidth: '140px',
93
+ conditions: {
94
+ visible: (formData: FormData) => {
95
+ const delimitBy = formData.delimit_by?.[0]?.value;
96
+ return delimitBy !== undefined && delimitBy !== '';
97
+ }
98
+ }
99
+ },
100
+ rules: createRulesArrayConfig(
101
+ operatorsToSelectOptions(getWaitForResponseOperators()),
102
+ 'Define rules to categorize the result'
103
+ ),
104
+ result_name: resultNameField
105
+ },
106
+ layout: [
107
+ {
108
+ type: 'row',
109
+ label: 'Flow Result',
110
+ helpText:
111
+ 'Select a flow result and optionally delimit it to split on a specific part',
112
+ items: ['result', 'delimit_by', 'delimit_index']
113
+ },
114
+ 'rules',
115
+ 'result_name'
116
+ ],
117
+ validate: (formData: FormData) => {
118
+ const errors: { [key: string]: string } = {};
119
+
120
+ // Validate result is provided
121
+ if (!formData.result || formData.result.length === 0) {
122
+ errors.result = 'A flow result is required';
123
+ }
124
+
125
+ return {
126
+ valid: Object.keys(errors).length === 0,
127
+ errors
128
+ };
129
+ },
130
+ toFormData: (node: Node, nodeUI?: any) => {
131
+ // Get the result from the UI config operand (source of truth)
132
+ const result = nodeUI?.config?.operand;
133
+
134
+ // Extract rules from router cases using shared function
135
+ const rules = casesToFormRules(node);
136
+
137
+ // Extract delimiter configuration by checking the actual operand string
138
+ // If operand contains field() function, we have a delimiter
139
+ const operand = node.router?.operand || '';
140
+ const fieldFunctionMatch = operand.match(
141
+ /field\(results\.[\w]+,\s*(\d+),\s*"(.+?)"\)/
142
+ );
143
+
144
+ const hasDelimiter = fieldFunctionMatch !== null;
145
+ const delimitIndex = hasDelimiter
146
+ ? parseInt(fieldFunctionMatch![1], 10)
147
+ : 0;
148
+ const delimiter = hasDelimiter ? fieldFunctionMatch![2] : '';
149
+
150
+ return {
151
+ uuid: node.uuid,
152
+ result: result ? [result] : [],
153
+ delimit_by: hasDelimiter
154
+ ? [DELIMIT_BY_OPTIONS.find((opt) => opt.value === delimiter)]
155
+ : [DELIMIT_BY_OPTIONS[0]],
156
+ delimit_index: hasDelimiter
157
+ ? [
158
+ DELIMIT_INDEX_OPTIONS.find(
159
+ (opt) => opt.value === String(delimitIndex)
160
+ )
161
+ ]
162
+ : [DELIMIT_INDEX_OPTIONS[0]],
163
+ rules: rules,
164
+ result_name: node.router?.result_name || ''
165
+ };
166
+ },
167
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
168
+ // Get selected result (it's an array from the select component)
169
+ const selectedResult = formData.result?.[0];
170
+
171
+ if (!selectedResult) {
172
+ return originalNode;
173
+ }
174
+
175
+ // Build operand based on whether delimiter is selected
176
+ let operand: string;
177
+ const delimitBy = formData.delimit_by?.[0]?.value;
178
+ const hasDelimiter = delimitBy !== undefined && delimitBy !== '';
179
+
180
+ if (hasDelimiter) {
181
+ // Get delimiter configuration
182
+ const delimitIndex = formData.delimit_index?.[0]?.value ?? '0';
183
+
184
+ // Build operand with field() function
185
+ operand = `@(field(results.${selectedResult.value}, ${delimitIndex}, "${delimitBy}"))`;
186
+ } else {
187
+ // Standard operand without delimiter
188
+ operand = `@results.${selectedResult.value}`;
189
+ }
190
+
191
+ // Get user rules using shared extraction function
192
+ const userRules = extractUserRules(formData);
193
+
194
+ // Get existing router data for preservation
195
+ const existingCategories = originalNode.router?.categories || [];
196
+ const existingExits = originalNode.exits || [];
197
+ const existingCases = originalNode.router?.cases || [];
198
+
199
+ // Create router and exits using existing data when possible
200
+ const { router, exits } = createRulesRouter(
201
+ operand,
202
+ userRules,
203
+ getOperatorConfig,
204
+ existingCategories,
205
+ existingExits,
206
+ existingCases
207
+ );
208
+
209
+ // Build final router with result_name
210
+ const finalRouter: any = {
211
+ ...router
212
+ };
213
+
214
+ // Only set result_name if provided
215
+ if (formData.result_name && formData.result_name.trim() !== '') {
216
+ finalRouter.result_name = formData.result_name.trim();
217
+ }
218
+
219
+ return {
220
+ ...originalNode,
221
+ router: finalRouter,
222
+ exits: exits
223
+ };
224
+ },
225
+ toUIConfig: (formData: FormData) => {
226
+ // Get selected result (it's an array from the select component)
227
+ const selectedResult = formData.result?.[0];
228
+
229
+ if (!selectedResult) {
230
+ return {};
231
+ }
232
+
233
+ // Build UI config with operand information
234
+ const config: any = {
235
+ operand: {
236
+ id: selectedResult.value,
237
+ name: selectedResult.name,
238
+ type: 'result'
239
+ }
240
+ };
241
+
242
+ // Add delimiter configuration if selected
243
+ const delimitBy = formData.delimit_by?.[0]?.value;
244
+ const hasDelimiter = delimitBy !== undefined && delimitBy !== '';
245
+
246
+ if (hasDelimiter) {
247
+ config.index = parseInt(formData.delimit_index?.[0]?.value ?? '0', 10);
248
+ config.delimiter = delimitBy;
249
+ }
250
+
251
+ return config;
252
+ }
7
253
  };
@@ -1,7 +1,197 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, FormData, NodeConfig } from '../types';
2
+ import { Node, Category, Exit, Case } from '../../store/flow-definition.d';
3
+ import { generateUUID } from '../../utils';
4
+ import { SCHEMES } from '../utils';
5
+ import { resultNameField } from './shared';
6
+
7
+ // Helper function to get scheme options for the select dropdown
8
+ const getSchemeOptions = () => {
9
+ return SCHEMES.map((scheme) => ({
10
+ value: scheme.scheme,
11
+ name: scheme.name
12
+ }));
13
+ };
14
+
15
+ // Helper function to create a switch router with scheme cases
16
+ const createSchemeRouter = (
17
+ selectedSchemes: string[],
18
+ existingCategories: Category[] = [],
19
+ existingExits: Exit[] = [],
20
+ existingCases: Case[] = [],
21
+ resultName: string = ''
22
+ ) => {
23
+ const categories: Category[] = [];
24
+ const exits: Exit[] = [];
25
+ const cases: Case[] = [];
26
+
27
+ // Create categories, exits, and cases for each selected scheme
28
+ selectedSchemes.forEach((scheme) => {
29
+ const schemeObj = SCHEMES.find((s) => s.scheme === scheme);
30
+ const schemeName = schemeObj?.name || scheme;
31
+
32
+ // Try to find existing category by scheme name
33
+ const existingCategory = existingCategories.find(
34
+ (cat) => cat.name === schemeName
35
+ );
36
+ const existingExit = existingCategory
37
+ ? existingExits.find((exit) => exit.uuid === existingCategory.exit_uuid)
38
+ : null;
39
+ const existingCase = existingCases.find((c) => c.arguments?.[0] === scheme);
40
+
41
+ const exitUuid = existingExit?.uuid || generateUUID();
42
+ const categoryUuid = existingCategory?.uuid || generateUUID();
43
+ const caseUuid = existingCase?.uuid || generateUUID();
44
+
45
+ categories.push({
46
+ uuid: categoryUuid,
47
+ name: schemeName,
48
+ exit_uuid: exitUuid
49
+ });
50
+
51
+ exits.push({
52
+ uuid: exitUuid,
53
+ destination_uuid: existingExit?.destination_uuid || null
54
+ });
55
+
56
+ cases.push({
57
+ uuid: caseUuid,
58
+ type: 'has_only_phrase',
59
+ arguments: [scheme],
60
+ category_uuid: categoryUuid
61
+ });
62
+ });
63
+
64
+ // Add default "Other" category for schemes not in the selected list
65
+ const existingOtherCategory = existingCategories.find((cat) => {
66
+ const matchesSelected = selectedSchemes.some((scheme) => {
67
+ const schemeObj = SCHEMES.find((s) => s.scheme === scheme);
68
+ return schemeObj?.name === cat.name;
69
+ });
70
+ return cat.name === 'Other' && !matchesSelected;
71
+ });
72
+ const existingOtherExit = existingOtherCategory
73
+ ? existingExits.find(
74
+ (exit) => exit.uuid === existingOtherCategory.exit_uuid
75
+ )
76
+ : null;
77
+
78
+ const otherExitUuid = existingOtherExit?.uuid || generateUUID();
79
+ const otherCategoryUuid = existingOtherCategory?.uuid || generateUUID();
80
+
81
+ categories.push({
82
+ uuid: otherCategoryUuid,
83
+ name: 'Other',
84
+ exit_uuid: otherExitUuid
85
+ });
86
+
87
+ exits.push({
88
+ uuid: otherExitUuid,
89
+ destination_uuid: existingOtherExit?.destination_uuid || null
90
+ });
91
+
92
+ return {
93
+ router: {
94
+ type: 'switch' as const,
95
+ cases: cases,
96
+ categories: categories,
97
+ default_category_uuid: otherCategoryUuid,
98
+ operand: '@(urn_parts(contact.urn).scheme)',
99
+ result_name: resultName
100
+ },
101
+ exits: exits
102
+ };
103
+ };
2
104
 
3
105
  export const split_by_scheme: NodeConfig = {
4
106
  type: 'split_by_scheme',
5
107
  name: 'Split by URN Type',
6
- color: COLORS.split
108
+ group: SPLIT_GROUPS.split,
109
+ form: {
110
+ schemes: {
111
+ type: 'select',
112
+ label: 'Channel Types',
113
+ helpText:
114
+ "The contact's URN is the address they used to reach you such as their phone number or a Facebook ID. Select which URN types to split by.",
115
+ required: true,
116
+ options: getSchemeOptions(),
117
+ multi: true,
118
+ searchable: true,
119
+ placeholder: 'Select the channels to split by...'
120
+ },
121
+ result_name: resultNameField
122
+ },
123
+ layout: ['schemes', 'result_name'],
124
+ validate: (formData: FormData) => {
125
+ const errors: { [key: string]: string } = {};
126
+
127
+ if (
128
+ !formData.schemes ||
129
+ !Array.isArray(formData.schemes) ||
130
+ formData.schemes.length === 0
131
+ ) {
132
+ errors.schemes = 'At least one channel type is required';
133
+ }
134
+
135
+ return {
136
+ valid: Object.keys(errors).length === 0,
137
+ errors
138
+ };
139
+ },
140
+ toFormData: (node: Node) => {
141
+ // Extract schemes from the existing node structure
142
+ const schemes: string[] = [];
143
+
144
+ if (node.router?.cases) {
145
+ node.router.cases.forEach((c: Case) => {
146
+ if (c.type === 'has_only_phrase' && c.arguments?.length > 0) {
147
+ schemes.push(c.arguments[0]);
148
+ }
149
+ });
150
+ }
151
+
152
+ return {
153
+ uuid: node.uuid,
154
+ schemes: schemes.map((scheme) => {
155
+ const schemeObj = SCHEMES.find((s) => s.scheme === scheme);
156
+ return {
157
+ value: scheme,
158
+ name: schemeObj?.name || scheme
159
+ };
160
+ }),
161
+ result_name: node.router?.result_name || ''
162
+ };
163
+ },
164
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
165
+ // Get selected schemes (handle both array of objects and array of strings)
166
+ const selectedSchemes = (formData.schemes || [])
167
+ .filter((scheme: any) => scheme)
168
+ .map((scheme: any) =>
169
+ typeof scheme === 'string' ? scheme : scheme.value
170
+ );
171
+
172
+ // Create router and exits using existing data when possible
173
+ const existingCategories = originalNode.router?.categories || [];
174
+ const existingExits = originalNode.exits || [];
175
+ const existingCases = originalNode.router?.cases || [];
176
+
177
+ const { router, exits } = createSchemeRouter(
178
+ selectedSchemes,
179
+ existingCategories,
180
+ existingExits,
181
+ existingCases,
182
+ formData.result_name || ''
183
+ );
184
+
185
+ // Return the complete node
186
+ return {
187
+ uuid: originalNode.uuid,
188
+ actions: originalNode.actions || [],
189
+ router: router,
190
+ exits: exits
191
+ };
192
+ },
193
+ router: {
194
+ type: 'switch',
195
+ operand: '@(urn_parts(contact.urn).scheme)'
196
+ }
7
197
  };
@@ -1,12 +1,14 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { ACTION_GROUPS, FormData, NodeConfig } from '../types';
2
2
  import { Node } from '../../store/flow-definition';
3
3
  import { generateUUID } from '../../utils';
4
4
  import { html } from 'lit';
5
+ import { renderNamedObjects } from '../utils';
5
6
 
6
7
  export const split_by_subflow: NodeConfig = {
7
8
  type: 'split_by_subflow',
8
9
  name: 'Enter a Flow',
9
- color: COLORS.execute,
10
+ group: ACTION_GROUPS.trigger,
11
+ showAsAction: true,
10
12
  form: {
11
13
  flow: {
12
14
  type: 'select',
@@ -26,7 +28,7 @@ export const split_by_subflow: NodeConfig = {
26
28
  ) as any;
27
29
  return html`
28
30
  <div class="body">
29
- ${enterFlowAction?.flow?.name || 'Configure subflow'}
31
+ ${renderNamedObjects([enterFlowAction?.flow], 'flow')}
30
32
  </div>
31
33
  `;
32
34
  },
@@ -43,7 +45,7 @@ export const split_by_subflow: NodeConfig = {
43
45
  : []
44
46
  };
45
47
  },
46
- fromFormData: (formData: any, originalNode: Node): Node => {
48
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
47
49
  // Get flow selection
48
50
  const flowSelection =
49
51
  Array.isArray(formData.flow) && formData.flow.length > 0
@@ -1,4 +1,4 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { ACTION_GROUPS, FormData, NodeConfig } from '../types';
2
2
  import { Node, OpenTicket } 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_ticket: NodeConfig = {
7
7
  type: 'split_by_ticket',
8
8
  name: 'Open Ticket',
9
- color: COLORS.create,
9
+ group: ACTION_GROUPS.trigger,
10
+ showAsAction: true,
10
11
  form: {
11
12
  topic: {
12
13
  type: 'select',
@@ -81,7 +82,7 @@ export const split_by_ticket: NodeConfig = {
81
82
  note: openTicketAction?.note || ''
82
83
  };
83
84
  },
84
- fromFormData: (formData: any, originalNode: Node): Node => {
85
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
85
86
  // Find existing open_ticket action to preserve its UUID
86
87
  const existingOpenTicketAction = originalNode.actions?.find(
87
88
  (action) => action.type === 'open_ticket'
@@ -1,4 +1,4 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { ACTION_GROUPS, FormData, NodeConfig } from '../types';
2
2
  import { CallWebhook, Node } from '../../store/flow-definition';
3
3
  import { generateUUID, createSuccessFailureRouter } from '../../utils';
4
4
  import { html } from 'lit';
@@ -19,7 +19,8 @@ const defaultPost = `@(json(object(
19
19
  export const split_by_webhook: NodeConfig = {
20
20
  type: 'split_by_webhook',
21
21
  name: 'Call Webhook',
22
- color: COLORS.call,
22
+ group: ACTION_GROUPS.services,
23
+ showAsAction: true,
23
24
  form: {
24
25
  method: {
25
26
  type: 'select',
@@ -95,7 +96,7 @@ export const split_by_webhook: NodeConfig = {
95
96
  collapsible: true,
96
97
  collapsed: true,
97
98
  helpText: 'Configure authentication or custom headers',
98
- getGroupValueCount: (formData: any) => {
99
+ getGroupValueCount: (formData: FormData) => {
99
100
  return formData.headers?.length || 0;
100
101
  }
101
102
  },
@@ -106,7 +107,7 @@ export const split_by_webhook: NodeConfig = {
106
107
  collapsible: true,
107
108
  collapsed: true,
108
109
  helpText: 'Configure the request payload',
109
- getGroupValueCount: (formData: any) => {
110
+ getGroupValueCount: (formData: FormData) => {
110
111
  return !!(
111
112
  formData.body &&
112
113
  formData.body.trim() !== '' &&
@@ -142,7 +143,7 @@ export const split_by_webhook: NodeConfig = {
142
143
  body: callWebhookAction?.body || ''
143
144
  };
144
145
  },
145
- fromFormData: (formData: any, originalNode: Node): Node => {
146
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
146
147
  // Get method selection
147
148
  const methodSelection =
148
149
  Array.isArray(formData.method) && formData.method.length > 0
@@ -1,7 +1,7 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, NodeConfig } from '../types';
2
2
 
3
3
  export const wait_for_audio: NodeConfig = {
4
4
  type: 'wait_for_audio',
5
5
  name: 'Wait for Audio',
6
- color: COLORS.wait
6
+ group: SPLIT_GROUPS.wait
7
7
  };
@@ -1,7 +1,7 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, NodeConfig } from '../types';
2
2
 
3
3
  export const wait_for_digits: NodeConfig = {
4
4
  type: 'wait_for_digits',
5
5
  name: 'Wait for Digits',
6
- color: COLORS.wait
6
+ group: SPLIT_GROUPS.wait
7
7
  };
@@ -1,7 +1,7 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, NodeConfig } from '../types';
2
2
 
3
3
  export const wait_for_image: NodeConfig = {
4
4
  type: 'wait_for_image',
5
5
  name: 'Wait for Image',
6
- color: COLORS.wait
6
+ group: SPLIT_GROUPS.wait
7
7
  };
@@ -1,7 +1,7 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, NodeConfig } from '../types';
2
2
 
3
3
  export const wait_for_location: NodeConfig = {
4
4
  type: 'wait_for_location',
5
5
  name: 'Wait for Location',
6
- color: COLORS.wait
6
+ group: SPLIT_GROUPS.wait
7
7
  };
@@ -1,7 +1,7 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, NodeConfig } from '../types';
2
2
 
3
3
  export const wait_for_menu: NodeConfig = {
4
4
  type: 'wait_for_menu',
5
5
  name: 'Wait for Menu Selection',
6
- color: COLORS.wait
6
+ group: SPLIT_GROUPS.wait
7
7
  };