@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
@@ -6,6 +6,7 @@ import {
6
6
  FlowDefinition,
7
7
  FlowPosition,
8
8
  Node,
9
+ NodeUI,
9
10
  Router,
10
11
  StickyNote
11
12
  } from './flow-definition';
@@ -95,6 +96,7 @@ export interface AppState {
95
96
  uuid: string,
96
97
  node: { actions: Action[]; uuid: string; exits: Exit[]; router?: Router }
97
98
  ): unknown;
99
+ updateNodeUIConfig(uuid: string, config: Record<string, any>): unknown;
98
100
  updateConnection(
99
101
  nodeUuid: string,
100
102
  exitUuid: string,
@@ -105,6 +107,8 @@ export interface AppState {
105
107
  removeStickyNotes: (uuids: string[]) => void;
106
108
  updateStickyNote(uuid: string, sticky: StickyNote): void;
107
109
  createStickyNote(position: FlowPosition): string;
110
+ createNode(nodeType: string, position: FlowPosition): string;
111
+ addNode(node: Node, nodeUI: NodeUI): void;
108
112
  }
109
113
 
110
114
  export const zustand = createStore<AppState>()(
@@ -290,6 +294,18 @@ export const zustand = createStore<AppState>()(
290
294
  });
291
295
  },
292
296
 
297
+ updateNodeUIConfig: (uuid: string, config: Record<string, any>) => {
298
+ set((state: AppState) => {
299
+ if (state.flowDefinition._ui.nodes[uuid]) {
300
+ if (!state.flowDefinition._ui.nodes[uuid].config) {
301
+ state.flowDefinition._ui.nodes[uuid].config = {};
302
+ }
303
+ Object.assign(state.flowDefinition._ui.nodes[uuid].config, config);
304
+ }
305
+ state.dirtyDate = new Date();
306
+ });
307
+ },
308
+
293
309
  updateConnection: (
294
310
  nodeUuid: string,
295
311
  exitUuid: string,
@@ -338,6 +354,59 @@ export const zustand = createStore<AppState>()(
338
354
  state.dirtyDate = new Date();
339
355
  });
340
356
  return uuid;
357
+ },
358
+
359
+ createNode: (nodeType: string, position: FlowPosition): string => {
360
+ const uuid = generateUUID();
361
+ const exitUuid = generateUUID();
362
+
363
+ set((state: AppState) => {
364
+ // Create a basic node with a single exit
365
+ const newNode: Node = {
366
+ uuid,
367
+ actions: [],
368
+ exits: [
369
+ {
370
+ uuid: exitUuid,
371
+ destination_uuid: null
372
+ }
373
+ ]
374
+ };
375
+
376
+ // Add the node to the flow definition
377
+ state.flowDefinition.nodes.push(newNode);
378
+
379
+ // Set up UI for the node
380
+ if (!state.flowDefinition._ui.nodes) {
381
+ state.flowDefinition._ui.nodes = {};
382
+ }
383
+
384
+ state.flowDefinition._ui.nodes[uuid] = {
385
+ position,
386
+ type: nodeType as any,
387
+ config: {}
388
+ };
389
+
390
+ state.dirtyDate = new Date();
391
+ });
392
+
393
+ return uuid;
394
+ },
395
+
396
+ addNode: (node: Node, nodeUI: NodeUI) => {
397
+ set((state: AppState) => {
398
+ // Add the node to the flow definition
399
+ state.flowDefinition.nodes.push(node);
400
+
401
+ // Set up UI for the node
402
+ if (!state.flowDefinition._ui.nodes) {
403
+ state.flowDefinition._ui.nodes = {};
404
+ }
405
+
406
+ state.flowDefinition._ui.nodes[node.uuid] = nodeUI;
407
+
408
+ state.dirtyDate = new Date();
409
+ });
341
410
  }
342
411
  }))
343
412
  )
@@ -147,6 +147,7 @@ export interface SendBroadcast extends Action {
147
147
  text: string;
148
148
  groups: Group[];
149
149
  contacts: NamedObject[];
150
+ attachments?: string[];
150
151
  }
151
152
 
152
153
  export interface EnterFlow extends Action {
@@ -161,26 +162,22 @@ export interface StartSession extends Action {
161
162
  }
162
163
 
163
164
  export interface TransferAirtime extends Action {
164
- amounts: number[];
165
- result_name: string;
165
+ amounts: Record<string, number>;
166
166
  }
167
167
 
168
168
  export interface CallClassifier extends Action {
169
169
  classifier: NamedObject;
170
170
  input: string;
171
- result_name: string;
172
171
  }
173
172
 
174
173
  export interface CallResthook extends Action {
175
174
  resthook: string;
176
- result_name: string;
177
175
  }
178
176
 
179
177
  export interface CallLLM extends Action {
180
178
  llm: NamedObject;
181
179
  instructions: string;
182
180
  input: string;
183
- result_name?: string;
184
181
  output_local?: string;
185
182
  }
186
183
 
package/src/utils.ts CHANGED
@@ -514,18 +514,6 @@ export class Stubbable {
514
514
 
515
515
  export const stubbable = new Stubbable();
516
516
 
517
- export const spreadAttributes = (obj): TemplateResult[] => {
518
- return Object.entries(obj).map(([key, value]) => {
519
- if (key.startsWith('@')) {
520
- return html`@${key.slice(1)}=${value}`;
521
- } else if (key.startsWith('.')) {
522
- return html`.${key.slice(1)}=${value}`;
523
- } else {
524
- return html`${key}=${value}`;
525
- }
526
- });
527
- };
528
-
529
517
  export const timeSince = (
530
518
  date: Date,
531
519
  options: { compareDate?: Date; hideRecentText?: boolean; suffix?: string } = {
@@ -1125,6 +1113,338 @@ export const createMultiCategoryRouter = (
1125
1113
  };
1126
1114
  };
1127
1115
 
1116
+ // Helper function to check if a category is a system category
1117
+ export const isSystemCategory = (categoryName: string): boolean => {
1118
+ return ['No Response', 'Other', 'All Responses', 'Timeout'].includes(
1119
+ categoryName
1120
+ );
1121
+ };
1122
+
1123
+ // Helper function to check if a UUID belongs to a system category
1124
+ export const isSystemCategoryUuid = (
1125
+ uuid: string,
1126
+ categories: any[]
1127
+ ): boolean => {
1128
+ const category = categories.find((cat) => cat.uuid === uuid);
1129
+ return category ? isSystemCategory(category.name) : false;
1130
+ };
1131
+
1132
+ // Helper function to generate default category name based on operator and operands
1133
+ export const generateDefaultCategoryName = (
1134
+ operator: string,
1135
+ getOperatorConfig: (type: string) => any | undefined,
1136
+ value1?: string,
1137
+ value2?: string
1138
+ ): string => {
1139
+ const operatorConfig = getOperatorConfig(operator);
1140
+ if (!operatorConfig) return '';
1141
+
1142
+ // Fixed category names (no operands)
1143
+ if (operatorConfig.operands === 0) {
1144
+ return operatorConfig.categoryName || '';
1145
+ }
1146
+
1147
+ // Dynamic category names based on operands
1148
+ const cleanValue1 = (value1 || '').trim();
1149
+ const cleanValue2 = (value2 || '').trim();
1150
+
1151
+ // Helper to capitalize first letter
1152
+ const capitalize = (str: string) => {
1153
+ if (!str) return '';
1154
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
1155
+ };
1156
+
1157
+ // Handle different operator types
1158
+ switch (operator) {
1159
+ // Word/phrase operators - capitalize first letter of value
1160
+ case 'has_any_word':
1161
+ case 'has_all_words':
1162
+ case 'has_phrase':
1163
+ case 'has_only_phrase':
1164
+ case 'has_beginning':
1165
+ return cleanValue1 ? capitalize(cleanValue1) : '';
1166
+
1167
+ // Pattern operators - show as-is
1168
+ case 'has_pattern':
1169
+ return cleanValue1;
1170
+
1171
+ // Number comparison operators - include symbol
1172
+ case 'has_number_eq':
1173
+ return cleanValue1 ? `= ${cleanValue1}` : '';
1174
+ case 'has_number_lt':
1175
+ return cleanValue1 ? `< ${cleanValue1}` : '';
1176
+ case 'has_number_lte':
1177
+ return cleanValue1 ? `≤ ${cleanValue1}` : '';
1178
+ case 'has_number_gt':
1179
+ return cleanValue1 ? `> ${cleanValue1}` : '';
1180
+ case 'has_number_gte':
1181
+ return cleanValue1 ? `≥ ${cleanValue1}` : '';
1182
+
1183
+ // Number between - range format
1184
+ case 'has_number_between':
1185
+ if (cleanValue1 && cleanValue2) {
1186
+ return `${cleanValue1} - ${cleanValue2}`;
1187
+ }
1188
+ return '';
1189
+
1190
+ // Date operators - format with relative expressions
1191
+ case 'has_date_lt':
1192
+ case 'has_date_lte':
1193
+ if (cleanValue1) {
1194
+ // Parse relative date expression (e.g., "today + 5" or "today - 3")
1195
+ const match = cleanValue1.match(/^(today)\s*([+-])\s*(\d+)$/i);
1196
+ if (match) {
1197
+ const [, base, operator, days] = match;
1198
+ const dayWord = days === '1' ? 'day' : 'days';
1199
+ return `Before ${base} ${operator} ${days} ${dayWord}`;
1200
+ }
1201
+ // Fallback for other date formats
1202
+ return `Before ${cleanValue1}`;
1203
+ }
1204
+ return '';
1205
+
1206
+ case 'has_date_gt':
1207
+ case 'has_date_gte':
1208
+ if (cleanValue1) {
1209
+ // Parse relative date expression
1210
+ const match = cleanValue1.match(/^(today)\s*([+-])\s*(\d+)$/i);
1211
+ if (match) {
1212
+ const [, base, operator, days] = match;
1213
+ const dayWord = days === '1' ? 'day' : 'days';
1214
+ return `After ${base} ${operator} ${days} ${dayWord}`;
1215
+ }
1216
+ // Fallback for other date formats
1217
+ return `After ${cleanValue1}`;
1218
+ }
1219
+ return '';
1220
+
1221
+ case 'has_date_eq':
1222
+ if (cleanValue1) {
1223
+ // Parse relative date expression
1224
+ const match = cleanValue1.match(/^(today)\s*([+-])\s*(\d+)$/i);
1225
+ if (match) {
1226
+ const [, base, operator, days] = match;
1227
+ const dayWord = days === '1' ? 'day' : 'days';
1228
+ return `${base} ${operator} ${days} ${dayWord}`;
1229
+ }
1230
+ return cleanValue1;
1231
+ }
1232
+ return '';
1233
+
1234
+ default:
1235
+ // Fallback - capitalize first value
1236
+ return cleanValue1 ? capitalize(cleanValue1) : '';
1237
+ }
1238
+ };
1239
+
1240
+ // Helper function to create a rules-based router (used by wait_for_response and split_by_expression)
1241
+ export const createRulesRouter = (
1242
+ operand: string,
1243
+ userRules: any[],
1244
+ getOperatorConfig: (type: string) => any | undefined,
1245
+ existingCategories: any[] = [],
1246
+ existingExits: any[] = [],
1247
+ existingCases: any[] = []
1248
+ ) => {
1249
+ const categories: RouterCategory[] = [];
1250
+ const exits: RouterExit[] = [];
1251
+ const cases: RouterCase[] = [];
1252
+
1253
+ // Filter existing categories to get only user-defined rules (exclude system categories)
1254
+ const existingUserCategories = existingCategories.filter(
1255
+ (cat) => !isSystemCategory(cat.name)
1256
+ );
1257
+
1258
+ // Track categories as we create them (case-insensitive lookup)
1259
+ const createdCategories = new Map<
1260
+ string,
1261
+ { uuid: string; name: string; exit_uuid: string }
1262
+ >();
1263
+
1264
+ // Process rules in their original order to preserve rule order
1265
+ userRules.forEach((rule, ruleIndex) => {
1266
+ const categoryKey = rule.category.trim().toLowerCase();
1267
+ const categoryName = rule.category.trim(); // Use original casing
1268
+
1269
+ let categoryInfo = createdCategories.get(categoryKey);
1270
+
1271
+ if (!categoryInfo) {
1272
+ // First time seeing this category - create it
1273
+
1274
+ // Smart category matching: try by name first, then fall back to position
1275
+ let existingCategory = existingUserCategories.find(
1276
+ (cat) => cat.name.toLowerCase() === categoryKey
1277
+ );
1278
+
1279
+ // If no match by name, try by position (for category rename scenarios)
1280
+ const categoryCreationOrder = Array.from(createdCategories.keys()).length;
1281
+ if (
1282
+ !existingCategory &&
1283
+ categoryCreationOrder < existingUserCategories.length
1284
+ ) {
1285
+ const candidateCategory = existingUserCategories[categoryCreationOrder];
1286
+ // Double-check that this candidate is not a system category UUID
1287
+ if (
1288
+ candidateCategory &&
1289
+ !isSystemCategoryUuid(candidateCategory.uuid, existingCategories)
1290
+ ) {
1291
+ existingCategory = candidateCategory;
1292
+ }
1293
+ }
1294
+
1295
+ const existingExit = existingCategory
1296
+ ? existingExits.find((exit) => exit.uuid === existingCategory.exit_uuid)
1297
+ : null;
1298
+
1299
+ // Generate UUIDs, ensuring we don't reuse system category UUIDs
1300
+ let exitUuid = existingExit?.uuid || generateUUID();
1301
+ let categoryUuid = existingCategory?.uuid || generateUUID();
1302
+
1303
+ // Additional safety check: if somehow we got a system category UUID, generate new ones
1304
+ if (isSystemCategoryUuid(categoryUuid, existingCategories)) {
1305
+ categoryUuid = generateUUID();
1306
+ exitUuid = generateUUID();
1307
+ }
1308
+
1309
+ categoryInfo = {
1310
+ uuid: categoryUuid,
1311
+ name: categoryName,
1312
+ exit_uuid: exitUuid
1313
+ };
1314
+
1315
+ createdCategories.set(categoryKey, categoryInfo);
1316
+
1317
+ // Add category and exit
1318
+ categories.push({
1319
+ uuid: categoryUuid,
1320
+ name: categoryName,
1321
+ exit_uuid: exitUuid
1322
+ });
1323
+
1324
+ exits.push({
1325
+ uuid: exitUuid,
1326
+ destination_uuid: existingExit?.destination_uuid || null
1327
+ });
1328
+ }
1329
+
1330
+ // Create case for this rule
1331
+ let existingCase = existingCases[ruleIndex];
1332
+
1333
+ // If we can't find by position, try to find by matching rule content
1334
+ if (!existingCase && existingCases.length > 0) {
1335
+ existingCase = existingCases.find((case_) => {
1336
+ // Find the category for this case
1337
+ const caseCategory = existingCategories.find(
1338
+ (cat) => cat.uuid === case_.category_uuid
1339
+ );
1340
+
1341
+ // Match by operator type and category name
1342
+ return (
1343
+ case_.type === rule.operator &&
1344
+ caseCategory?.name.toLowerCase() === categoryKey
1345
+ );
1346
+ });
1347
+ }
1348
+
1349
+ const caseUuid = existingCase?.uuid || generateUUID();
1350
+
1351
+ // Parse rule value based on operator configuration
1352
+ const operatorConfig = getOperatorConfig(rule.operator);
1353
+ let arguments_: string[] = [];
1354
+
1355
+ if (operatorConfig) {
1356
+ if (operatorConfig.operands === 0) {
1357
+ // No operands needed
1358
+ arguments_ = [];
1359
+ } else if (operatorConfig.operands === 2) {
1360
+ // Split value for two operands (e.g., "1 10" for between)
1361
+ arguments_ = rule.value.split(' ').filter((arg: string) => arg.trim());
1362
+ } else {
1363
+ // Single operand - but split words for operators that expect multiple words
1364
+ if (rule.value && rule.value.trim()) {
1365
+ // Split on spaces and filter out empty strings
1366
+ arguments_ = rule.value
1367
+ .trim()
1368
+ .split(/\s+/)
1369
+ .filter((arg: string) => arg.length > 0);
1370
+ } else {
1371
+ arguments_ = [];
1372
+ }
1373
+ }
1374
+ } else {
1375
+ // Fallback for unknown operators - split on spaces if value exists
1376
+ if (rule.value && rule.value.trim()) {
1377
+ arguments_ = rule.value
1378
+ .trim()
1379
+ .split(/\s+/)
1380
+ .filter((arg: string) => arg.length > 0);
1381
+ } else {
1382
+ arguments_ = [];
1383
+ }
1384
+ }
1385
+
1386
+ cases.push({
1387
+ uuid: caseUuid,
1388
+ type: rule.operator,
1389
+ arguments: arguments_,
1390
+ category_uuid: categoryInfo.uuid
1391
+ });
1392
+ });
1393
+
1394
+ // Add default category (always present)
1395
+ // Name is "Other" if there are user rules, "All Responses" if there are no user rules
1396
+ const defaultCategoryName = userRules.length > 0 ? 'Other' : 'All Responses';
1397
+
1398
+ // Try to find existing default category by name (prefer exact match)
1399
+ let existingDefaultCategory = existingCategories.find(
1400
+ (cat) => cat.name === defaultCategoryName
1401
+ );
1402
+
1403
+ // If no exact match, try to find the other possible default category name
1404
+ if (!existingDefaultCategory) {
1405
+ const alternateName = userRules.length > 0 ? 'All Responses' : 'Other';
1406
+ existingDefaultCategory = existingCategories.find(
1407
+ (cat) => cat.name === alternateName
1408
+ );
1409
+ }
1410
+
1411
+ const existingDefaultExit = existingDefaultCategory
1412
+ ? existingExits.find(
1413
+ (exit) => exit.uuid === existingDefaultCategory.exit_uuid
1414
+ )
1415
+ : null;
1416
+
1417
+ const defaultExitUuid = existingDefaultExit?.uuid || generateUUID();
1418
+ const defaultCategoryUuid = existingDefaultCategory?.uuid || generateUUID();
1419
+
1420
+ categories.push({
1421
+ uuid: defaultCategoryUuid,
1422
+ name: defaultCategoryName,
1423
+ exit_uuid: defaultExitUuid
1424
+ });
1425
+
1426
+ exits.push({
1427
+ uuid: defaultExitUuid,
1428
+ destination_uuid: existingDefaultExit?.destination_uuid || null
1429
+ });
1430
+
1431
+ // Find the default category (either "Other" or "All Responses")
1432
+ const defaultCategory = categories.find(
1433
+ (cat) => cat.name === 'Other' || cat.name === 'All Responses'
1434
+ );
1435
+
1436
+ return {
1437
+ router: {
1438
+ type: 'switch' as const,
1439
+ categories: categories,
1440
+ default_category_uuid: defaultCategory?.uuid,
1441
+ operand: operand,
1442
+ cases: cases
1443
+ },
1444
+ exits: exits
1445
+ };
1446
+ };
1447
+
1128
1448
  export const titleCase = (str: string) => {
1129
1449
  return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
1130
1450
  };
@@ -0,0 +1,46 @@
1
+ {
2
+ "next": null,
3
+ "previous": null,
4
+ "results": [
5
+ {
6
+ "uuid": "d70a531e-25d8-45dc-9a5b-19ff80cee571",
7
+ "name": "Facebook",
8
+ "address": "10644654942820",
9
+ "type": "facebook",
10
+ "country": null,
11
+ "device": null,
12
+ "last_seen": null,
13
+ "created_on": "2023-08-13T07:41:14.855156Z"
14
+ },
15
+ {
16
+ "uuid": "9afd9156-a34a-43fa-91d4-914615ae2e72",
17
+ "name": "WhatsApp",
18
+ "address": "+1234567890",
19
+ "type": "whatsapp",
20
+ "country": null,
21
+ "device": null,
22
+ "last_seen": null,
23
+ "created_on": "2024-01-15T10:30:00.000000Z"
24
+ },
25
+ {
26
+ "uuid": "1532a95a-4d34-46cb-848d-a0033287833e",
27
+ "name": "Purrington",
28
+ "address": "PurringtonBot",
29
+ "type": "telegram",
30
+ "country": null,
31
+ "device": null,
32
+ "last_seen": null,
33
+ "created_on": "2025-02-12T02:26:05.845278Z"
34
+ },
35
+ {
36
+ "uuid": "07d5e7d2-434e-4a51-b652-df45eb4dbe8c",
37
+ "name": "Purrington",
38
+ "address": "10644654942820",
39
+ "type": "facebook",
40
+ "country": null,
41
+ "device": null,
42
+ "last_seen": null,
43
+ "created_on": "2023-08-13T07:41:14.855156Z"
44
+ }
45
+ ]
46
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "next": null,
3
+ "previous": null,
4
+ "results": [
5
+ {
6
+ "resthook": "new-registration",
7
+ "created_on": "2013-08-19T19:11:21.082Z",
8
+ "modified_on": "2022-05-23T09:26:00.000Z"
9
+ },
10
+ {
11
+ "resthook": "survey-completed",
12
+ "created_on": "2014-03-10T12:30:00.000Z",
13
+ "modified_on": "2022-06-15T14:20:00.000Z"
14
+ },
15
+ {
16
+ "resthook": "payment-received",
17
+ "created_on": "2015-07-22T08:45:00.000Z",
18
+ "modified_on": "2022-08-10T11:30:00.000Z"
19
+ },
20
+ {
21
+ "resthook": "order-confirmed",
22
+ "created_on": "2016-01-05T16:20:00.000Z",
23
+ "modified_on": "2023-01-12T10:15:00.000Z"
24
+ },
25
+ {
26
+ "resthook": "appointment-scheduled",
27
+ "created_on": "2017-09-14T13:10:00.000Z",
28
+ "modified_on": "2023-03-20T15:45:00.000Z"
29
+ }
30
+ ]
31
+ }