@nyaruka/temba-components 0.131.1 → 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 (427) hide show
  1. package/.github/workflows/publish.yml +4 -1
  2. package/CHANGELOG.md +61 -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 +1155 -618
  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/FieldRenderer.js +2 -4
  110. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  111. package/out-tsc/src/interfaces.js +3 -0
  112. package/out-tsc/src/interfaces.js.map +1 -1
  113. package/out-tsc/src/list/SortableList.js +98 -33
  114. package/out-tsc/src/list/SortableList.js.map +1 -1
  115. package/out-tsc/src/live/ContactChat.js +15 -18
  116. package/out-tsc/src/live/ContactChat.js.map +1 -1
  117. package/out-tsc/src/store/AppState.js +53 -0
  118. package/out-tsc/src/store/AppState.js.map +1 -1
  119. package/out-tsc/src/utils.js +254 -13
  120. package/out-tsc/src/utils.js.map +1 -1
  121. package/out-tsc/temba-modules.js +4 -0
  122. package/out-tsc/temba-modules.js.map +1 -1
  123. package/out-tsc/test/ActionHelper.js +3 -3
  124. package/out-tsc/test/ActionHelper.js.map +1 -1
  125. package/out-tsc/test/NodeHelper.js +6 -3
  126. package/out-tsc/test/NodeHelper.js.map +1 -1
  127. package/out-tsc/test/actions/add_contact_urn.test.js +202 -0
  128. package/out-tsc/test/actions/add_contact_urn.test.js.map +1 -0
  129. package/out-tsc/test/actions/send_broadcast.test.js +148 -0
  130. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -0
  131. package/out-tsc/test/actions/send_email.test.js +17 -23
  132. package/out-tsc/test/actions/send_email.test.js.map +1 -1
  133. package/out-tsc/test/actions/send_msg.test.js +33 -15
  134. package/out-tsc/test/actions/send_msg.test.js.map +1 -1
  135. package/out-tsc/test/actions/start_session.test.js +116 -0
  136. package/out-tsc/test/actions/start_session.test.js.map +1 -0
  137. package/out-tsc/test/nodes/split_by_airtime.test.js +604 -0
  138. package/out-tsc/test/nodes/split_by_airtime.test.js.map +1 -0
  139. package/out-tsc/test/nodes/split_by_contact_field.test.js +387 -0
  140. package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -0
  141. package/out-tsc/test/nodes/split_by_expression.test.js +614 -0
  142. package/out-tsc/test/nodes/split_by_expression.test.js.map +1 -0
  143. package/out-tsc/test/nodes/split_by_random.test.js +3 -3
  144. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  145. package/out-tsc/test/nodes/split_by_resthook.test.js +337 -0
  146. package/out-tsc/test/nodes/split_by_resthook.test.js.map +1 -0
  147. package/out-tsc/test/nodes/split_by_run_result.test.js +920 -0
  148. package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -0
  149. package/out-tsc/test/nodes/split_by_scheme.test.js +399 -0
  150. package/out-tsc/test/nodes/split_by_scheme.test.js.map +1 -0
  151. package/out-tsc/test/nodes/split_by_subflow.test.js +333 -0
  152. package/out-tsc/test/nodes/split_by_subflow.test.js.map +1 -0
  153. package/out-tsc/test/nodes/wait_for_digits.test.js +2 -2
  154. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  155. package/out-tsc/test/nodes/wait_for_response.test.js +2 -1
  156. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  157. package/out-tsc/test/temba-action-drag-between-nodes.test.js +252 -0
  158. package/out-tsc/test/temba-action-drag-between-nodes.test.js.map +1 -0
  159. package/out-tsc/test/temba-canvas-menu.test.js +122 -0
  160. package/out-tsc/test/temba-canvas-menu.test.js.map +1 -0
  161. package/out-tsc/test/temba-flow-editor-node.test.js +85 -2
  162. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  163. package/out-tsc/test/temba-flow-editor.test.js +7 -8
  164. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  165. package/out-tsc/test/temba-node-editor.test.js +3 -1
  166. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  167. package/out-tsc/test/temba-node-type-selector.test.js +115 -0
  168. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -0
  169. package/out-tsc/test/temba-omnibox.test.js +2 -1
  170. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  171. package/out-tsc/test/temba-sortable-list.test.js +51 -0
  172. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  173. package/out-tsc/test/temba-utils-index.test.js +1 -27
  174. package/out-tsc/test/temba-utils-index.test.js.map +1 -1
  175. package/out-tsc/test/utils.test.js +2 -0
  176. package/out-tsc/test/utils.test.js.map +1 -1
  177. package/package.json +2 -1
  178. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  179. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  180. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  181. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  182. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  183. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  184. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  185. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  186. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  187. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  188. package/screenshots/truth/actions/add_contact_urn/editor/expression-facebook.png +0 -0
  189. package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
  190. package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
  191. package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
  192. package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
  193. package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
  194. package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
  195. package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
  196. package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
  197. package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
  198. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  199. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  200. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  201. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  202. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  203. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  204. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  205. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  206. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  207. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  208. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  209. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  210. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  211. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  212. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  213. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  214. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  215. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  216. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  217. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  218. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  219. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  220. package/screenshots/truth/actions/send_broadcast/editor/contacts-only.png +0 -0
  221. package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
  222. package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
  223. package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
  224. package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
  225. package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
  226. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  227. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  228. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  229. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  230. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  231. package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
  232. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  233. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  234. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  235. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  236. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  237. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  238. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  239. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  240. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  241. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  242. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  243. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  244. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  245. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  246. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  247. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  248. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  249. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  250. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  251. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  252. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  253. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  254. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  255. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  256. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  257. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  258. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  259. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  260. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  261. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  262. package/screenshots/truth/actions/start_session/editor/contact-query.png +0 -0
  263. package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
  264. package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
  265. package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
  266. package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
  267. package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
  268. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  269. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  270. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  271. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  272. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  273. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  274. package/screenshots/truth/canvas-menu/open.png +0 -0
  275. package/screenshots/truth/editor/router.png +0 -0
  276. package/screenshots/truth/editor/wait.png +0 -0
  277. package/screenshots/truth/list/fields-dragging.png +0 -0
  278. package/screenshots/truth/list/sortable-dragging.png +0 -0
  279. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  280. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  281. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  282. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  283. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  284. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  285. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  286. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  287. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  288. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  289. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  290. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  291. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  292. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  293. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  294. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  295. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  296. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  297. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  298. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  299. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  300. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  301. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  302. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  303. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  304. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  305. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  306. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  307. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  308. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  309. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  310. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  311. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  312. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  313. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  314. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  315. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  316. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  317. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  318. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  319. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  320. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  321. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  322. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  323. package/src/Icons.ts +4 -1
  324. package/src/events.ts +2 -6
  325. package/src/flow/CanvasMenu.ts +217 -0
  326. package/src/flow/CanvasNode.ts +408 -10
  327. package/src/flow/Editor.ts +683 -44
  328. package/src/flow/NodeEditor.ts +304 -125
  329. package/src/flow/NodeTypeSelector.ts +592 -0
  330. package/src/flow/actions/add_contact_groups.ts +4 -4
  331. package/src/flow/actions/add_contact_urn.ts +76 -4
  332. package/src/flow/actions/add_input_labels.ts +4 -4
  333. package/src/flow/actions/play_audio.ts +2 -2
  334. package/src/flow/actions/remove_contact_groups.ts +14 -6
  335. package/src/flow/actions/request_optin.ts +2 -2
  336. package/src/flow/actions/say_msg.ts +2 -2
  337. package/src/flow/actions/send_broadcast.ts +85 -23
  338. package/src/flow/actions/send_email.ts +10 -6
  339. package/src/flow/actions/send_msg.ts +22 -32
  340. package/src/flow/actions/set_contact_channel.ts +5 -11
  341. package/src/flow/actions/set_contact_field.ts +20 -25
  342. package/src/flow/actions/set_contact_language.ts +9 -4
  343. package/src/flow/actions/set_contact_name.ts +3 -15
  344. package/src/flow/actions/set_contact_status.ts +3 -3
  345. package/src/flow/actions/set_run_result.ts +4 -4
  346. package/src/flow/actions/start_session.ts +208 -6
  347. package/src/flow/config.ts +13 -15
  348. package/src/flow/currencies.ts +51 -0
  349. package/src/flow/nodes/shared-rules.ts +301 -0
  350. package/src/flow/nodes/shared.ts +18 -0
  351. package/src/flow/nodes/split_by_airtime.ts +238 -5
  352. package/src/flow/nodes/split_by_contact_field.ts +185 -3
  353. package/src/flow/nodes/split_by_expression.ts +94 -2
  354. package/src/flow/nodes/split_by_groups.ts +15 -10
  355. package/src/flow/nodes/split_by_intent.ts +7 -0
  356. package/src/flow/nodes/split_by_llm.ts +4 -3
  357. package/src/flow/nodes/split_by_llm_categorize.ts +4 -4
  358. package/src/flow/nodes/split_by_random.ts +5 -5
  359. package/src/flow/nodes/split_by_resthook.ts +130 -0
  360. package/src/flow/nodes/split_by_run_result.ts +249 -3
  361. package/src/flow/nodes/split_by_scheme.ts +192 -2
  362. package/src/flow/nodes/split_by_subflow.ts +6 -4
  363. package/src/flow/nodes/split_by_ticket.ts +4 -3
  364. package/src/flow/nodes/split_by_webhook.ts +6 -5
  365. package/src/flow/nodes/wait_for_audio.ts +2 -2
  366. package/src/flow/nodes/wait_for_digits.ts +2 -2
  367. package/src/flow/nodes/wait_for_image.ts +2 -2
  368. package/src/flow/nodes/wait_for_location.ts +2 -2
  369. package/src/flow/nodes/wait_for_menu.ts +2 -2
  370. package/src/flow/nodes/wait_for_response.ts +48 -679
  371. package/src/flow/nodes/wait_for_video.ts +2 -2
  372. package/src/flow/types.ts +109 -23
  373. package/src/flow/utils.ts +108 -14
  374. package/src/form/FieldRenderer.ts +2 -4
  375. package/src/interfaces.ts +3 -0
  376. package/src/list/SortableList.ts +109 -34
  377. package/src/live/ContactChat.ts +15 -18
  378. package/src/store/AppState.ts +69 -0
  379. package/src/store/flow-definition.d.ts +2 -5
  380. package/src/utils.ts +332 -12
  381. package/static/api/channels.json +46 -0
  382. package/static/api/resthooks.json +31 -0
  383. package/static/svg/index.svg +1 -1
  384. package/static/svg/work/traced/lightning-02.svg +1 -0
  385. package/static/svg/work/used/lightning-02.svg +3 -0
  386. package/temba-modules.ts +4 -0
  387. package/test/ActionHelper.ts +3 -3
  388. package/test/NodeHelper.ts +6 -3
  389. package/test/actions/add_contact_urn.test.ts +287 -0
  390. package/test/actions/send_broadcast.test.ts +190 -0
  391. package/test/actions/send_email.test.ts +17 -23
  392. package/test/actions/send_msg.test.ts +39 -15
  393. package/test/actions/start_session.test.ts +151 -0
  394. package/test/nodes/split_by_airtime.test.ts +673 -0
  395. package/test/nodes/split_by_contact_field.test.ts +451 -0
  396. package/test/nodes/split_by_expression.test.ts +751 -0
  397. package/test/nodes/split_by_random.test.ts +3 -3
  398. package/test/nodes/split_by_resthook.test.ts +398 -0
  399. package/test/nodes/split_by_run_result.test.ts +1109 -0
  400. package/test/nodes/split_by_scheme.test.ts +486 -0
  401. package/test/nodes/split_by_subflow.test.ts +381 -0
  402. package/test/nodes/wait_for_digits.test.ts +2 -2
  403. package/test/nodes/wait_for_response.test.ts +2 -1
  404. package/test/temba-action-drag-between-nodes.test.ts +301 -0
  405. package/test/temba-canvas-menu.test.ts +156 -0
  406. package/test/temba-flow-editor-node.test.ts +102 -2
  407. package/test/temba-flow-editor.test.ts +7 -8
  408. package/test/temba-node-editor.test.ts +3 -1
  409. package/test/temba-node-type-selector.test.ts +152 -0
  410. package/test/temba-omnibox.test.ts +2 -1
  411. package/test/temba-sortable-list.test.ts +69 -0
  412. package/test/temba-utils-index.test.ts +0 -35
  413. package/test/utils.test.ts +2 -0
  414. package/test-assets/contacts/history.json +14 -20
  415. package/web-dev-server.config.mjs +3 -1
  416. package/out-tsc/src/flow/actions/call_classifier.js +0 -11
  417. package/out-tsc/src/flow/actions/call_classifier.js.map +0 -1
  418. package/out-tsc/src/flow/actions/call_resthook.js +0 -11
  419. package/out-tsc/src/flow/actions/call_resthook.js.map +0 -1
  420. package/out-tsc/src/flow/actions/split_by_expression_example.js +0 -77
  421. package/out-tsc/src/flow/actions/split_by_expression_example.js.map +0 -1
  422. package/out-tsc/src/flow/actions/transfer_airtime.js +0 -11
  423. package/out-tsc/src/flow/actions/transfer_airtime.js.map +0 -1
  424. package/src/flow/actions/call_classifier.ts +0 -12
  425. package/src/flow/actions/call_resthook.ts +0 -12
  426. package/src/flow/actions/split_by_expression_example.ts +0 -88
  427. package/src/flow/actions/transfer_airtime.ts +0 -12
@@ -1,6 +1,8 @@
1
- import { COLORS } from '../types';
2
- import { generateUUID } from '../../utils';
1
+ import { SPLIT_GROUPS } from '../types';
2
+ import { generateUUID, createRulesRouter } from '../../utils';
3
3
  import { getWaitForResponseOperators, operatorsToSelectOptions, getOperatorConfig } from '../operators';
4
+ import { resultNameField } from './shared';
5
+ import { createRulesArrayConfig, extractUserRules, casesToFormRules } from './shared-rules';
4
6
  const TIMEOUT_OPTIONS = [
5
7
  { value: '60', name: '1 minute' },
6
8
  { value: '120', name: '2 minutes' },
@@ -21,458 +23,31 @@ const TIMEOUT_OPTIONS = [
21
23
  { value: '259200', name: '3 days' },
22
24
  { value: '604800', name: '1 week' }
23
25
  ];
24
- // Helper function to check if a category is a system category
25
- const isSystemCategory = (categoryName) => {
26
- return ['No Response', 'Other', 'All Responses', 'Timeout'].includes(categoryName);
27
- };
28
- // Helper function to check if a UUID belongs to a system category
29
- const isSystemCategoryUuid = (uuid, categories) => {
30
- const category = categories.find((cat) => cat.uuid === uuid);
31
- return category ? isSystemCategory(category.name) : false;
32
- };
33
- // Helper function to generate default category name based on operator and operands
34
- const generateDefaultCategoryName = (operator, value1, value2) => {
35
- const operatorConfig = getOperatorConfig(operator);
36
- if (!operatorConfig)
37
- return '';
38
- // Fixed category names (no operands)
39
- if (operatorConfig.operands === 0) {
40
- return operatorConfig.categoryName || '';
41
- }
42
- // Dynamic category names based on operands
43
- const cleanValue1 = (value1 || '').trim();
44
- const cleanValue2 = (value2 || '').trim();
45
- // Helper to capitalize first letter
46
- const capitalize = (str) => {
47
- if (!str)
48
- return '';
49
- return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
50
- };
51
- // Handle different operator types
52
- switch (operator) {
53
- // Word/phrase operators - capitalize first letter of value
54
- case 'has_any_word':
55
- case 'has_all_words':
56
- case 'has_phrase':
57
- case 'has_only_phrase':
58
- case 'has_beginning':
59
- return cleanValue1 ? capitalize(cleanValue1) : '';
60
- // Pattern operators - show as-is
61
- case 'has_pattern':
62
- return cleanValue1;
63
- // Number comparison operators - include symbol
64
- case 'has_number_eq':
65
- return cleanValue1 ? `= ${cleanValue1}` : '';
66
- case 'has_number_lt':
67
- return cleanValue1 ? `< ${cleanValue1}` : '';
68
- case 'has_number_lte':
69
- return cleanValue1 ? `≤ ${cleanValue1}` : '';
70
- case 'has_number_gt':
71
- return cleanValue1 ? `> ${cleanValue1}` : '';
72
- case 'has_number_gte':
73
- return cleanValue1 ? `≥ ${cleanValue1}` : '';
74
- // Number between - range format
75
- case 'has_number_between':
76
- if (cleanValue1 && cleanValue2) {
77
- return `${cleanValue1} - ${cleanValue2}`;
78
- }
79
- return '';
80
- // Date operators - format with relative expressions
81
- case 'has_date_lt':
82
- case 'has_date_lte':
83
- if (cleanValue1) {
84
- // Parse relative date expression (e.g., "today + 5" or "today - 3")
85
- const match = cleanValue1.match(/^(today)\s*([+-])\s*(\d+)$/i);
86
- if (match) {
87
- const [, base, operator, days] = match;
88
- const dayWord = days === '1' ? 'day' : 'days';
89
- return `Before ${base} ${operator} ${days} ${dayWord}`;
90
- }
91
- // Fallback for other date formats
92
- return `Before ${cleanValue1}`;
93
- }
94
- return '';
95
- case 'has_date_gt':
96
- case 'has_date_gte':
97
- if (cleanValue1) {
98
- // Parse relative date expression
99
- const match = cleanValue1.match(/^(today)\s*([+-])\s*(\d+)$/i);
100
- if (match) {
101
- const [, base, operator, days] = match;
102
- const dayWord = days === '1' ? 'day' : 'days';
103
- return `After ${base} ${operator} ${days} ${dayWord}`;
104
- }
105
- // Fallback for other date formats
106
- return `After ${cleanValue1}`;
107
- }
108
- return '';
109
- case 'has_date_eq':
110
- if (cleanValue1) {
111
- // Parse relative date expression
112
- const match = cleanValue1.match(/^(today)\s*([+-])\s*(\d+)$/i);
113
- if (match) {
114
- const [, base, operator, days] = match;
115
- const dayWord = days === '1' ? 'day' : 'days';
116
- return `${base} ${operator} ${days} ${dayWord}`;
117
- }
118
- return cleanValue1;
119
- }
120
- return '';
121
- default:
122
- // Fallback - capitalize first value
123
- return cleanValue1 ? capitalize(cleanValue1) : '';
124
- }
125
- };
126
26
  // Helper function to create a wait_for_response router with user rules
27
+ // This is a thin wrapper around createRulesRouter that adds the No Response category for timeouts
127
28
  const createWaitForResponseRouter = (userRules, existingCategories = [], existingExits = [], existingCases = []) => {
128
- const categories = [];
129
- const exits = [];
130
- const cases = [];
131
- // Filter existing categories to get only user-defined rules (exclude system categories)
132
- const existingUserCategories = existingCategories.filter((cat) => !isSystemCategory(cat.name));
133
- // Track categories as we create them (case-insensitive lookup)
134
- const createdCategories = new Map();
135
- // Process rules in their original order to preserve rule order
136
- userRules.forEach((rule, ruleIndex) => {
137
- const categoryKey = rule.category.trim().toLowerCase();
138
- const categoryName = rule.category.trim(); // Use original casing
139
- let categoryInfo = createdCategories.get(categoryKey);
140
- if (!categoryInfo) {
141
- // First time seeing this category - create it
142
- // Smart category matching: try by name first, then fall back to position
143
- let existingCategory = existingUserCategories.find((cat) => cat.name.toLowerCase() === categoryKey);
144
- // If no match by name, try by position (for category rename scenarios)
145
- const categoryCreationOrder = Array.from(createdCategories.keys()).length;
146
- if (!existingCategory &&
147
- categoryCreationOrder < existingUserCategories.length) {
148
- const candidateCategory = existingUserCategories[categoryCreationOrder];
149
- // Double-check that this candidate is not a system category UUID
150
- if (candidateCategory &&
151
- !isSystemCategoryUuid(candidateCategory.uuid, existingCategories)) {
152
- existingCategory = candidateCategory;
153
- }
154
- }
155
- const existingExit = existingCategory
156
- ? existingExits.find((exit) => exit.uuid === existingCategory.exit_uuid)
157
- : null;
158
- // Generate UUIDs, ensuring we don't reuse system category UUIDs
159
- let exitUuid = (existingExit === null || existingExit === void 0 ? void 0 : existingExit.uuid) || generateUUID();
160
- let categoryUuid = (existingCategory === null || existingCategory === void 0 ? void 0 : existingCategory.uuid) || generateUUID();
161
- // Additional safety check: if somehow we got a system category UUID, generate new ones
162
- if (isSystemCategoryUuid(categoryUuid, existingCategories)) {
163
- categoryUuid = generateUUID();
164
- exitUuid = generateUUID();
165
- }
166
- categoryInfo = {
167
- uuid: categoryUuid,
168
- name: categoryName,
169
- exit_uuid: exitUuid
170
- };
171
- createdCategories.set(categoryKey, categoryInfo);
172
- // Add category and exit
173
- categories.push({
174
- uuid: categoryUuid,
175
- name: categoryName,
176
- exit_uuid: exitUuid
177
- });
178
- exits.push({
179
- uuid: exitUuid,
180
- destination_uuid: (existingExit === null || existingExit === void 0 ? void 0 : existingExit.destination_uuid) || null
181
- });
182
- }
183
- // Create case for this rule
184
- let existingCase = existingCases[ruleIndex];
185
- // If we can't find by position, try to find by matching rule content
186
- if (!existingCase && existingCases.length > 0) {
187
- existingCase = existingCases.find((case_) => {
188
- // Find the category for this case
189
- const caseCategory = existingCategories.find((cat) => cat.uuid === case_.category_uuid);
190
- // Match by operator type and category name
191
- return (case_.type === rule.operator &&
192
- (caseCategory === null || caseCategory === void 0 ? void 0 : caseCategory.name.toLowerCase()) === categoryKey);
193
- });
194
- }
195
- const caseUuid = (existingCase === null || existingCase === void 0 ? void 0 : existingCase.uuid) || generateUUID();
196
- // Parse rule value based on operator configuration
197
- const operatorConfig = getOperatorConfig(rule.operator);
198
- let arguments_ = [];
199
- if (operatorConfig) {
200
- if (operatorConfig.operands === 0) {
201
- // No operands needed
202
- arguments_ = [];
203
- }
204
- else if (operatorConfig.operands === 2) {
205
- // Split value for two operands (e.g., "1 10" for between)
206
- arguments_ = rule.value.split(' ').filter((arg) => arg.trim());
207
- }
208
- else {
209
- // Single operand - but split words for operators that expect multiple words
210
- if (rule.value && rule.value.trim()) {
211
- // Split on spaces and filter out empty strings
212
- arguments_ = rule.value
213
- .trim()
214
- .split(/\s+/)
215
- .filter((arg) => arg.length > 0);
216
- }
217
- else {
218
- arguments_ = [];
219
- }
220
- }
221
- }
222
- else {
223
- // Fallback for unknown operators - split on spaces if value exists
224
- if (rule.value && rule.value.trim()) {
225
- arguments_ = rule.value
226
- .trim()
227
- .split(/\s+/)
228
- .filter((arg) => arg.length > 0);
229
- }
230
- else {
231
- arguments_ = [];
232
- }
233
- }
234
- cases.push({
235
- uuid: caseUuid,
236
- type: rule.operator,
237
- arguments: arguments_,
238
- category_uuid: categoryInfo.uuid
239
- });
240
- });
241
- // Add default category (always present)
242
- // Name is "Other" if there are user rules, "All Responses" if there are no user rules
243
- const defaultCategoryName = userRules.length > 0 ? 'Other' : 'All Responses';
244
- // Try to find existing default category by name (prefer exact match)
245
- let existingDefaultCategory = existingCategories.find((cat) => cat.name === defaultCategoryName);
246
- // If no exact match, try to find the other possible default category name
247
- if (!existingDefaultCategory) {
248
- const alternateName = userRules.length > 0 ? 'All Responses' : 'Other';
249
- existingDefaultCategory = existingCategories.find((cat) => cat.name === alternateName);
250
- }
251
- const existingDefaultExit = existingDefaultCategory
252
- ? existingExits.find((exit) => exit.uuid === existingDefaultCategory.exit_uuid)
253
- : null;
254
- const defaultExitUuid = (existingDefaultExit === null || existingDefaultExit === void 0 ? void 0 : existingDefaultExit.uuid) || generateUUID();
255
- const defaultCategoryUuid = (existingDefaultCategory === null || existingDefaultCategory === void 0 ? void 0 : existingDefaultCategory.uuid) || generateUUID();
256
- categories.push({
257
- uuid: defaultCategoryUuid,
258
- name: defaultCategoryName,
259
- exit_uuid: defaultExitUuid
260
- });
261
- exits.push({
262
- uuid: defaultExitUuid,
263
- destination_uuid: (existingDefaultExit === null || existingDefaultExit === void 0 ? void 0 : existingDefaultExit.destination_uuid) || null
264
- });
29
+ const { router, exits } = createRulesRouter('@input.text', userRules, getOperatorConfig, existingCategories, existingExits, existingCases);
265
30
  // Add "No Response" category last (if it exists in the original)
266
31
  const existingNoResponseCategory = existingCategories.find((cat) => cat.name === 'No Response' || cat.name === 'Timeout');
267
32
  if (existingNoResponseCategory) {
268
33
  const existingNoResponseExit = existingExits.find((exit) => exit.uuid === existingNoResponseCategory.exit_uuid);
269
34
  if (existingNoResponseExit) {
270
- categories.push(existingNoResponseCategory);
271
- exits.push(existingNoResponseExit);
35
+ router.categories.push(existingNoResponseCategory);
36
+ exits.push({
37
+ uuid: existingNoResponseExit.uuid,
38
+ destination_uuid: existingNoResponseExit.destination_uuid || null
39
+ });
272
40
  }
273
41
  }
274
- // Find the default category (either "Other" or "All Responses")
275
- const defaultCategory = categories.find((cat) => cat.name === 'Other' || cat.name === 'All Responses');
276
- return {
277
- router: {
278
- type: 'switch',
279
- categories: categories,
280
- default_category_uuid: defaultCategory === null || defaultCategory === void 0 ? void 0 : defaultCategory.uuid,
281
- operand: '@input.text',
282
- cases: cases
283
- },
284
- exits: exits
285
- };
42
+ return { router, exits };
286
43
  };
287
44
  export const wait_for_response = {
288
45
  type: 'wait_for_response',
289
46
  name: 'Wait for Response',
290
- color: COLORS.wait,
47
+ group: SPLIT_GROUPS.wait,
291
48
  dialogSize: 'large',
292
49
  form: {
293
- rules: {
294
- type: 'array',
295
- helpText: 'Define rules to categorize responses',
296
- itemLabel: 'Rule',
297
- minItems: 0,
298
- maxItems: 100,
299
- sortable: true,
300
- maintainEmptyItem: true, // Explicitly enable empty item maintenance
301
- isEmptyItem: (item) => {
302
- // Helper function to get operator value from various formats
303
- const getOperatorValue = (operator) => {
304
- if (typeof operator === 'string') {
305
- return operator.trim();
306
- }
307
- else if (Array.isArray(operator) && operator.length > 0) {
308
- // Handle array format: [{value: "has_any_word", name: "..."}]
309
- const firstOperator = operator[0];
310
- if (firstOperator &&
311
- typeof firstOperator === 'object' &&
312
- firstOperator.value) {
313
- return firstOperator.value.trim();
314
- }
315
- }
316
- else if (operator &&
317
- typeof operator === 'object' &&
318
- operator.value) {
319
- // Handle object format: {value: "has_any_word", name: "..."}
320
- return operator.value.trim();
321
- }
322
- return '';
323
- };
324
- // Check if operator and category are provided
325
- const operatorValue = getOperatorValue(item.operator);
326
- if (!operatorValue || !item.category || item.category.trim() === '') {
327
- return true;
328
- }
329
- // Check if value is required based on operator configuration
330
- const operatorConfig = getOperatorConfig(operatorValue);
331
- if (operatorConfig && operatorConfig.operands === 1) {
332
- // value1 is required for this operator
333
- return !item.value1 || item.value1.trim() === '';
334
- }
335
- else if (operatorConfig && operatorConfig.operands === 2) {
336
- // Both value1 and value2 are required for this operator
337
- return (!item.value1 ||
338
- item.value1.trim() === '' ||
339
- !item.value2 ||
340
- item.value2.trim() === '');
341
- }
342
- // No value required for this operator
343
- return false;
344
- },
345
- onItemChange: (itemIndex, field, value, allItems) => {
346
- const updatedItems = [...allItems];
347
- const item = { ...updatedItems[itemIndex] };
348
- // Helper to get operator value from various formats
349
- const getOperatorValue = (operator) => {
350
- if (typeof operator === 'string') {
351
- return operator.trim();
352
- }
353
- else if (Array.isArray(operator) && operator.length > 0) {
354
- const firstOperator = operator[0];
355
- if (firstOperator &&
356
- typeof firstOperator === 'object' &&
357
- firstOperator.value) {
358
- return firstOperator.value.trim();
359
- }
360
- }
361
- else if (operator &&
362
- typeof operator === 'object' &&
363
- operator.value) {
364
- return operator.value.trim();
365
- }
366
- return '';
367
- };
368
- // Update the changed field
369
- item[field] = value;
370
- // Get operator values (before and after the change)
371
- const oldItem = allItems[itemIndex] || {};
372
- const oldOperatorValue = field === 'operator'
373
- ? getOperatorValue(oldItem.operator)
374
- : getOperatorValue(item.operator);
375
- const newOperatorValue = getOperatorValue(item.operator);
376
- // Calculate what the default category name should be before the change
377
- const oldDefaultCategory = generateDefaultCategoryName(oldOperatorValue, field === 'value1' ? oldItem.value1 : item.value1, field === 'value2' ? oldItem.value2 : item.value2);
378
- // Calculate what the new default category name should be after the change
379
- const newDefaultCategory = generateDefaultCategoryName(newOperatorValue, item.value1, item.value2);
380
- // Determine if we should auto-update the category
381
- const shouldUpdateCategory =
382
- // Category is empty
383
- !item.category ||
384
- item.category.trim() === '' ||
385
- // Category matches the old default (user hasn't customized it)
386
- item.category === oldDefaultCategory;
387
- // Auto-populate or update category if conditions are met
388
- if (shouldUpdateCategory && newDefaultCategory) {
389
- item.category = newDefaultCategory;
390
- }
391
- updatedItems[itemIndex] = item;
392
- return updatedItems;
393
- },
394
- itemConfig: {
395
- operator: {
396
- type: 'select',
397
- required: true,
398
- multi: false, // Explicitly set as single-select
399
- options: operatorsToSelectOptions(getWaitForResponseOperators()),
400
- flavor: 'xsmall',
401
- width: '200px'
402
- },
403
- value1: {
404
- type: 'text',
405
- flavor: 'xsmall',
406
- conditions: {
407
- visible: (formData) => {
408
- // Helper function to get operator value from various formats
409
- const getOperatorValue = (operator) => {
410
- if (typeof operator === 'string') {
411
- return operator.trim();
412
- }
413
- else if (Array.isArray(operator) && operator.length > 0) {
414
- const firstOperator = operator[0];
415
- if (firstOperator &&
416
- typeof firstOperator === 'object' &&
417
- firstOperator.value) {
418
- return firstOperator.value.trim();
419
- }
420
- }
421
- else if (operator &&
422
- typeof operator === 'object' &&
423
- operator.value) {
424
- return operator.value.trim();
425
- }
426
- return '';
427
- };
428
- // Show value1 field for operators that require 1 or 2 operands
429
- const operatorValue = getOperatorValue(formData.operator);
430
- const operatorConfig = getOperatorConfig(operatorValue);
431
- return operatorConfig ? operatorConfig.operands >= 1 : true;
432
- }
433
- }
434
- },
435
- value2: {
436
- type: 'text',
437
- flavor: 'xsmall',
438
- conditions: {
439
- visible: (formData) => {
440
- // Helper function to get operator value from various formats
441
- const getOperatorValue = (operator) => {
442
- if (typeof operator === 'string') {
443
- return operator.trim();
444
- }
445
- else if (Array.isArray(operator) && operator.length > 0) {
446
- const firstOperator = operator[0];
447
- if (firstOperator &&
448
- typeof firstOperator === 'object' &&
449
- firstOperator.value) {
450
- return firstOperator.value.trim();
451
- }
452
- }
453
- else if (operator &&
454
- typeof operator === 'object' &&
455
- operator.value) {
456
- return operator.value.trim();
457
- }
458
- return '';
459
- };
460
- // Show value2 field only if operator requires exactly 2 operands
461
- const operatorValue = getOperatorValue(formData.operator);
462
- const operatorConfig = getOperatorConfig(operatorValue);
463
- return operatorConfig ? operatorConfig.operands === 2 : false;
464
- }
465
- }
466
- },
467
- category: {
468
- type: 'text',
469
- placeholder: 'Category',
470
- required: true,
471
- maxWidth: '120px',
472
- flavor: 'xsmall'
473
- }
474
- }
475
- },
50
+ rules: createRulesArrayConfig(operatorsToSelectOptions(getWaitForResponseOperators()), 'Define rules to categorize responses'),
476
51
  timeout_enabled: {
477
52
  type: 'checkbox',
478
53
  label: (formData) => {
@@ -486,7 +61,7 @@ export const wait_for_response = {
486
61
  type: 'select',
487
62
  placeholder: '5 minutes',
488
63
  multi: false,
489
- maxWidth: '150px',
64
+ maxWidth: '100px',
490
65
  flavor: 'xsmall',
491
66
  options: TIMEOUT_OPTIONS,
492
67
  conditions: {
@@ -495,12 +70,7 @@ export const wait_for_response = {
495
70
  }
496
71
  }
497
72
  },
498
- result_name: {
499
- type: 'text',
500
- label: 'Result Name',
501
- helpText: 'The name to save the response as',
502
- placeholder: 'response'
503
- }
73
+ result_name: resultNameField
504
74
  },
505
75
  layout: ['rules', 'result_name'],
506
76
  gutter: [
@@ -520,53 +90,11 @@ export const wait_for_response = {
520
90
  };
521
91
  },
522
92
  toFormData: (node) => {
523
- var _a, _b, _c, _d, _e, _f;
524
- // Extract rules from router cases
525
- const rules = [];
526
- if (((_a = node.router) === null || _a === void 0 ? void 0 : _a.cases) && ((_b = node.router) === null || _b === void 0 ? void 0 : _b.categories)) {
527
- node.router.cases.forEach((case_) => {
528
- // Find the category for this case
529
- const category = node.router.categories.find((cat) => cat.uuid === case_.category_uuid);
530
- // Skip system categories
531
- if (category && !isSystemCategory(category.name)) {
532
- // Handle different operator types
533
- const operatorConfig = getOperatorConfig(case_.type);
534
- const operatorDisplayName = operatorConfig
535
- ? operatorConfig.name
536
- : case_.type;
537
- let value1 = '';
538
- let value2 = '';
539
- if (operatorConfig && operatorConfig.operands === 0) {
540
- // No value needed for operators like has_text, has_number
541
- value1 = '';
542
- value2 = '';
543
- }
544
- else if (operatorConfig && operatorConfig.operands === 1) {
545
- // Single value for operators like has_number_lt - use value1
546
- value1 = case_.arguments.join(' ');
547
- value2 = '';
548
- }
549
- else if (operatorConfig && operatorConfig.operands === 2) {
550
- // Two separate values for operators like has_number_between
551
- value1 = case_.arguments[0] || '';
552
- value2 = case_.arguments[1] || '';
553
- }
554
- else {
555
- // Fallback: use first argument for unknown operators
556
- value1 = case_.arguments.join(' ');
557
- value2 = '';
558
- }
559
- rules.push({
560
- operator: { value: case_.type, name: operatorDisplayName },
561
- value1: value1,
562
- value2: value2,
563
- category: category.name
564
- });
565
- }
566
- });
567
- }
93
+ var _a, _b, _c, _d;
94
+ // Extract rules from router cases using shared function
95
+ const rules = casesToFormRules(node);
568
96
  // Extract timeout configuration
569
- const timeoutSeconds = (_e = (_d = (_c = node.router) === null || _c === void 0 ? void 0 : _c.wait) === null || _d === void 0 ? void 0 : _d.timeout) === null || _e === void 0 ? void 0 : _e.seconds;
97
+ const timeoutSeconds = (_c = (_b = (_a = node.router) === null || _a === void 0 ? void 0 : _a.wait) === null || _b === void 0 ? void 0 : _b.timeout) === null || _c === void 0 ? void 0 : _c.seconds;
570
98
  let timeoutOption = TIMEOUT_OPTIONS.find((opt) => opt.value === String(timeoutSeconds));
571
99
  if (!timeoutOption) {
572
100
  timeoutOption = { value: '300', name: '5 minutes' };
@@ -576,82 +104,13 @@ export const wait_for_response = {
576
104
  rules: rules,
577
105
  timeout_enabled: !!timeoutSeconds,
578
106
  timeout_duration: timeoutOption,
579
- result_name: ((_f = node.router) === null || _f === void 0 ? void 0 : _f.result_name) || 'response'
107
+ result_name: ((_d = node.router) === null || _d === void 0 ? void 0 : _d.result_name) || ''
580
108
  };
581
109
  },
582
110
  fromFormData: (formData, originalNode) => {
583
111
  var _a, _b, _c, _d, _e, _f, _g, _h;
584
- // Helper function to get operator value from various formats
585
- const getOperatorValue = (operator) => {
586
- if (typeof operator === 'string') {
587
- return operator.trim();
588
- }
589
- else if (Array.isArray(operator) && operator.length > 0) {
590
- // Handle array format: [{value: "has_any_word", name: "..."}]
591
- const firstOperator = operator[0];
592
- if (firstOperator &&
593
- typeof firstOperator === 'object' &&
594
- firstOperator.value) {
595
- return firstOperator.value.trim();
596
- }
597
- }
598
- else if (operator && typeof operator === 'object' && operator.value) {
599
- // Handle object format: {value: "has_any_word", name: "..."}
600
- return operator.value.trim();
601
- }
602
- return '';
603
- };
604
- // Get user rules
605
- const userRules = (formData.rules || [])
606
- .filter((rule) => {
607
- // Always need operator and category
608
- const operatorValue = getOperatorValue(rule === null || rule === void 0 ? void 0 : rule.operator);
609
- if (!operatorValue ||
610
- !(rule === null || rule === void 0 ? void 0 : rule.category) ||
611
- operatorValue === '' ||
612
- rule.category.trim() === '') {
613
- return false;
614
- }
615
- // Check if value is required based on operator
616
- const operatorConfig = getOperatorConfig(operatorValue);
617
- if (operatorConfig && operatorConfig.operands === 1) {
618
- // value1 is required for this operator
619
- return (rule === null || rule === void 0 ? void 0 : rule.value1) && rule.value1.trim() !== '';
620
- }
621
- else if (operatorConfig && operatorConfig.operands === 2) {
622
- // Both value1 and value2 are required for this operator
623
- return ((rule === null || rule === void 0 ? void 0 : rule.value1) &&
624
- rule.value1.trim() !== '' &&
625
- (rule === null || rule === void 0 ? void 0 : rule.value2) &&
626
- rule.value2.trim() !== '');
627
- }
628
- // No value required for this operator
629
- return true;
630
- })
631
- .map((rule) => {
632
- const operatorValue = getOperatorValue(rule.operator);
633
- const operatorConfig = getOperatorConfig(operatorValue);
634
- let value = '';
635
- if (operatorConfig && operatorConfig.operands === 1) {
636
- // Single value from value1
637
- value = rule.value1 ? rule.value1.trim() : '';
638
- }
639
- else if (operatorConfig && operatorConfig.operands === 2) {
640
- // Two values - combine them with space
641
- const val1 = rule.value1 ? rule.value1.trim() : '';
642
- const val2 = rule.value2 ? rule.value2.trim() : '';
643
- value = `${val1} ${val2}`.trim();
644
- }
645
- else {
646
- // No value needed for 0-operand operators
647
- value = '';
648
- }
649
- return {
650
- operator: operatorValue,
651
- value: value,
652
- category: rule.category.trim()
653
- };
654
- });
112
+ // Get user rules using shared extraction function
113
+ const userRules = extractUserRules(formData);
655
114
  // If no user rules, clear cases but preserve other router config
656
115
  if (userRules.length === 0) {
657
116
  // Get existing router data for preservation
@@ -691,9 +150,12 @@ export const wait_for_response = {
691
150
  );
692
151
  const router = {
693
152
  ...noRulesRouter,
694
- result_name: formData.result_name || 'response',
695
153
  cases: [] // Clear all cases when no rules
696
154
  };
155
+ // Only set result_name if provided
156
+ if (formData.result_name && formData.result_name.trim() !== '') {
157
+ router.result_name = formData.result_name.trim();
158
+ }
697
159
  // Build wait configuration based on form data
698
160
  const waitConfig = {
699
161
  type: 'msg'
@@ -752,9 +214,12 @@ export const wait_for_response = {
752
214
  const { router, exits } = createWaitForResponseRouter(userRules, existingCategories, existingExits, existingCases);
753
215
  // Build final router with wait configuration and result_name
754
216
  const finalRouter = {
755
- ...router,
756
- result_name: formData.result_name || 'response'
217
+ ...router
757
218
  };
219
+ // Only set result_name if provided
220
+ if (formData.result_name && formData.result_name.trim() !== '') {
221
+ finalRouter.result_name = formData.result_name.trim();
222
+ }
758
223
  // Build wait configuration based on form data
759
224
  const waitConfig = {
760
225
  type: 'msg'