@nyaruka/temba-components 0.131.1 → 0.131.3

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 (485) hide show
  1. package/.github/workflows/publish.yml +4 -1
  2. package/CHANGELOG.md +75 -1
  3. package/demo/components/floating-tabs/example.html +400 -0
  4. package/demo/components/flow/index.html +1 -1
  5. package/demo/data/flows/food-order.json +2 -2
  6. package/demo/data/flows/sample-flow.json +113 -125
  7. package/demo/data/flows/voicemail.json +613 -0
  8. package/demo/index.html +6 -0
  9. package/dist/locales/es.js +5 -5
  10. package/dist/locales/es.js.map +1 -1
  11. package/dist/locales/fr.js +5 -5
  12. package/dist/locales/fr.js.map +1 -1
  13. package/dist/locales/locale-codes.js +11 -2
  14. package/dist/locales/locale-codes.js.map +1 -1
  15. package/dist/locales/pt.js +5 -5
  16. package/dist/locales/pt.js.map +1 -1
  17. package/dist/static/svg/index.svg +1 -1
  18. package/dist/temba-components.js +1773 -662
  19. package/dist/temba-components.js.map +1 -1
  20. package/out-tsc/src/Icons.js +4 -1
  21. package/out-tsc/src/Icons.js.map +1 -1
  22. package/out-tsc/src/display/FloatingTab.js +167 -0
  23. package/out-tsc/src/display/FloatingTab.js.map +1 -0
  24. package/out-tsc/src/display/ProgressBar.js +22 -2
  25. package/out-tsc/src/display/ProgressBar.js.map +1 -1
  26. package/out-tsc/src/events.js.map +1 -1
  27. package/out-tsc/src/flow/CanvasMenu.js +200 -0
  28. package/out-tsc/src/flow/CanvasMenu.js.map +1 -0
  29. package/out-tsc/src/flow/CanvasNode.js +489 -47
  30. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  31. package/out-tsc/src/flow/Editor.js +1417 -67
  32. package/out-tsc/src/flow/Editor.js.map +1 -1
  33. package/out-tsc/src/flow/NodeEditor.js +479 -112
  34. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  35. package/out-tsc/src/flow/NodeTypeSelector.js +540 -0
  36. package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -0
  37. package/out-tsc/src/flow/StickyNote.js +12 -3
  38. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  39. package/out-tsc/src/flow/actions/add_contact_groups.js +4 -3
  40. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  41. package/out-tsc/src/flow/actions/add_contact_urn.js +63 -4
  42. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  43. package/out-tsc/src/flow/actions/add_input_labels.js +4 -3
  44. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  45. package/out-tsc/src/flow/actions/play_audio.js +3 -2
  46. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  47. package/out-tsc/src/flow/actions/remove_contact_groups.js +7 -5
  48. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  49. package/out-tsc/src/flow/actions/request_optin.js +3 -2
  50. package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
  51. package/out-tsc/src/flow/actions/say_msg.js +3 -2
  52. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  53. package/out-tsc/src/flow/actions/send_broadcast.js +77 -23
  54. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  55. package/out-tsc/src/flow/actions/send_email.js +5 -5
  56. package/out-tsc/src/flow/actions/send_email.js.map +1 -1
  57. package/out-tsc/src/flow/actions/send_msg.js +101 -21
  58. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  59. package/out-tsc/src/flow/actions/set_contact_channel.js +6 -9
  60. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  61. package/out-tsc/src/flow/actions/set_contact_field.js +20 -20
  62. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  63. package/out-tsc/src/flow/actions/set_contact_language.js +3 -2
  64. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  65. package/out-tsc/src/flow/actions/set_contact_name.js +3 -12
  66. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  67. package/out-tsc/src/flow/actions/set_contact_status.js +3 -2
  68. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  69. package/out-tsc/src/flow/actions/set_run_result.js +4 -3
  70. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  71. package/out-tsc/src/flow/actions/start_session.js +181 -6
  72. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  73. package/out-tsc/src/flow/config.js +11 -23
  74. package/out-tsc/src/flow/config.js.map +1 -1
  75. package/out-tsc/src/flow/currencies.js +45 -0
  76. package/out-tsc/src/flow/currencies.js.map +1 -0
  77. package/out-tsc/src/flow/nodes/shared-rules.js +257 -0
  78. package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -0
  79. package/out-tsc/src/flow/nodes/shared.js +71 -0
  80. package/out-tsc/src/flow/nodes/shared.js.map +1 -0
  81. package/out-tsc/src/flow/nodes/split_by_airtime.js +211 -5
  82. package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
  83. package/out-tsc/src/flow/nodes/split_by_contact_field.js +152 -3
  84. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
  85. package/out-tsc/src/flow/nodes/split_by_expression.js +73 -2
  86. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
  87. package/out-tsc/src/flow/nodes/split_by_groups.js +18 -10
  88. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
  89. package/out-tsc/src/flow/nodes/split_by_intent.js +8 -0
  90. package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -0
  91. package/out-tsc/src/flow/nodes/split_by_llm.js +11 -3
  92. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  93. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +10 -3
  94. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  95. package/out-tsc/src/flow/nodes/split_by_random.js +10 -4
  96. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  97. package/out-tsc/src/flow/nodes/split_by_resthook.js +113 -0
  98. package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -0
  99. package/out-tsc/src/flow/nodes/split_by_run_result.js +211 -3
  100. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
  101. package/out-tsc/src/flow/nodes/split_by_scheme.js +158 -2
  102. package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
  103. package/out-tsc/src/flow/nodes/split_by_subflow.js +13 -5
  104. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  105. package/out-tsc/src/flow/nodes/split_by_ticket.js +10 -3
  106. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
  107. package/out-tsc/src/flow/nodes/split_by_webhook.js +10 -3
  108. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  109. package/out-tsc/src/flow/nodes/wait_for_digits.js +3 -2
  110. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  111. package/out-tsc/src/flow/nodes/wait_for_menu.js +3 -2
  112. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  113. package/out-tsc/src/flow/nodes/wait_for_response.js +38 -568
  114. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  115. package/out-tsc/src/flow/types.js +86 -12
  116. package/out-tsc/src/flow/types.js.map +1 -1
  117. package/out-tsc/src/flow/utils.js +101 -14
  118. package/out-tsc/src/flow/utils.js.map +1 -1
  119. package/out-tsc/src/form/FieldRenderer.js +2 -4
  120. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  121. package/out-tsc/src/interfaces.js +3 -0
  122. package/out-tsc/src/interfaces.js.map +1 -1
  123. package/out-tsc/src/layout/FloatingWindow.js +346 -0
  124. package/out-tsc/src/layout/FloatingWindow.js.map +1 -0
  125. package/out-tsc/src/list/SortableList.js +98 -33
  126. package/out-tsc/src/list/SortableList.js.map +1 -1
  127. package/out-tsc/src/live/ContactChat.js +6 -25
  128. package/out-tsc/src/live/ContactChat.js.map +1 -1
  129. package/out-tsc/src/locales/es.js +5 -5
  130. package/out-tsc/src/locales/es.js.map +1 -1
  131. package/out-tsc/src/locales/fr.js +5 -5
  132. package/out-tsc/src/locales/fr.js.map +1 -1
  133. package/out-tsc/src/locales/locale-codes.js +11 -2
  134. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  135. package/out-tsc/src/locales/pt.js +5 -5
  136. package/out-tsc/src/locales/pt.js.map +1 -1
  137. package/out-tsc/src/store/AppState.js +120 -0
  138. package/out-tsc/src/store/AppState.js.map +1 -1
  139. package/out-tsc/src/utils.js +254 -13
  140. package/out-tsc/src/utils.js.map +1 -1
  141. package/out-tsc/temba-modules.js +8 -0
  142. package/out-tsc/temba-modules.js.map +1 -1
  143. package/out-tsc/test/ActionHelper.js +3 -3
  144. package/out-tsc/test/ActionHelper.js.map +1 -1
  145. package/out-tsc/test/NodeHelper.js +6 -3
  146. package/out-tsc/test/NodeHelper.js.map +1 -1
  147. package/out-tsc/test/actions/add_contact_urn.test.js +202 -0
  148. package/out-tsc/test/actions/add_contact_urn.test.js.map +1 -0
  149. package/out-tsc/test/actions/send_broadcast.test.js +148 -0
  150. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -0
  151. package/out-tsc/test/actions/send_email.test.js +17 -23
  152. package/out-tsc/test/actions/send_email.test.js.map +1 -1
  153. package/out-tsc/test/actions/send_msg.test.js +33 -15
  154. package/out-tsc/test/actions/send_msg.test.js.map +1 -1
  155. package/out-tsc/test/actions/start_session.test.js +116 -0
  156. package/out-tsc/test/actions/start_session.test.js.map +1 -0
  157. package/out-tsc/test/nodes/split_by_airtime.test.js +604 -0
  158. package/out-tsc/test/nodes/split_by_airtime.test.js.map +1 -0
  159. package/out-tsc/test/nodes/split_by_contact_field.test.js +387 -0
  160. package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -0
  161. package/out-tsc/test/nodes/split_by_expression.test.js +614 -0
  162. package/out-tsc/test/nodes/split_by_expression.test.js.map +1 -0
  163. package/out-tsc/test/nodes/split_by_random.test.js +3 -3
  164. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  165. package/out-tsc/test/nodes/split_by_resthook.test.js +337 -0
  166. package/out-tsc/test/nodes/split_by_resthook.test.js.map +1 -0
  167. package/out-tsc/test/nodes/split_by_run_result.test.js +920 -0
  168. package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -0
  169. package/out-tsc/test/nodes/split_by_scheme.test.js +399 -0
  170. package/out-tsc/test/nodes/split_by_scheme.test.js.map +1 -0
  171. package/out-tsc/test/nodes/split_by_subflow.test.js +333 -0
  172. package/out-tsc/test/nodes/split_by_subflow.test.js.map +1 -0
  173. package/out-tsc/test/nodes/wait_for_digits.test.js +2 -2
  174. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  175. package/out-tsc/test/nodes/wait_for_response.test.js +2 -1
  176. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  177. package/out-tsc/test/temba-action-drag-between-nodes.test.js +252 -0
  178. package/out-tsc/test/temba-action-drag-between-nodes.test.js.map +1 -0
  179. package/out-tsc/test/temba-canvas-menu.test.js +122 -0
  180. package/out-tsc/test/temba-canvas-menu.test.js.map +1 -0
  181. package/out-tsc/test/temba-floating-tab.test.js +91 -0
  182. package/out-tsc/test/temba-floating-tab.test.js.map +1 -0
  183. package/out-tsc/test/temba-floating-window.test.js +301 -0
  184. package/out-tsc/test/temba-floating-window.test.js.map +1 -0
  185. package/out-tsc/test/temba-flow-editor-node.test.js +202 -2
  186. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  187. package/out-tsc/test/temba-flow-editor.test.js +7 -8
  188. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  189. package/out-tsc/test/temba-localization.test.js +471 -0
  190. package/out-tsc/test/temba-localization.test.js.map +1 -0
  191. package/out-tsc/test/temba-node-editor.test.js +3 -1
  192. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  193. package/out-tsc/test/temba-node-type-selector.test.js +265 -0
  194. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -0
  195. package/out-tsc/test/temba-omnibox.test.js +2 -1
  196. package/out-tsc/test/temba-omnibox.test.js.map +1 -1
  197. package/out-tsc/test/temba-sortable-list.test.js +51 -0
  198. package/out-tsc/test/temba-sortable-list.test.js.map +1 -1
  199. package/out-tsc/test/temba-utils-index.test.js +1 -27
  200. package/out-tsc/test/temba-utils-index.test.js.map +1 -1
  201. package/out-tsc/test/utils.test.js +20 -0
  202. package/out-tsc/test/utils.test.js.map +1 -1
  203. package/package.json +2 -1
  204. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  205. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  206. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  207. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  208. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  209. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  210. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  211. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  212. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  213. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  214. package/screenshots/truth/actions/add_contact_urn/editor/expression-facebook.png +0 -0
  215. package/screenshots/truth/actions/add_contact_urn/editor/expression-phone.png +0 -0
  216. package/screenshots/truth/actions/add_contact_urn/editor/facebook-id.png +0 -0
  217. package/screenshots/truth/actions/add_contact_urn/editor/instagram-handle.png +0 -0
  218. package/screenshots/truth/actions/add_contact_urn/editor/line-id.png +0 -0
  219. package/screenshots/truth/actions/add_contact_urn/editor/phone-number.png +0 -0
  220. package/screenshots/truth/actions/add_contact_urn/editor/telegram-id.png +0 -0
  221. package/screenshots/truth/actions/add_contact_urn/editor/viber-id.png +0 -0
  222. package/screenshots/truth/actions/add_contact_urn/editor/wechat-id.png +0 -0
  223. package/screenshots/truth/actions/add_contact_urn/editor/whatsapp.png +0 -0
  224. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  225. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  226. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  227. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  228. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  229. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  230. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  231. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  232. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  233. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  234. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  235. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  236. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  237. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  238. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  239. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  240. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  241. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  242. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  243. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  244. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  245. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  246. package/screenshots/truth/actions/send_broadcast/editor/contacts-only.png +0 -0
  247. package/screenshots/truth/actions/send_broadcast/editor/groups-and-contacts.png +0 -0
  248. package/screenshots/truth/actions/send_broadcast/editor/groups-only.png +0 -0
  249. package/screenshots/truth/actions/send_broadcast/editor/many-groups.png +0 -0
  250. package/screenshots/truth/actions/send_broadcast/editor/multiline-text.png +0 -0
  251. package/screenshots/truth/actions/send_broadcast/editor/with-attachments.png +0 -0
  252. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  253. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  254. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  255. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  256. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  257. package/screenshots/truth/actions/send_broadcast/render/with-attachments.png +0 -0
  258. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  259. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  260. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  261. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  262. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  263. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  264. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  265. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  266. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  267. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  268. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  269. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  270. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  271. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  272. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  273. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  274. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  275. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  276. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  277. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  278. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  279. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  280. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  281. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  282. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  283. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  284. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  285. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  286. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  287. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  288. package/screenshots/truth/actions/start_session/editor/contact-query.png +0 -0
  289. package/screenshots/truth/actions/start_session/editor/contacts-only.png +0 -0
  290. package/screenshots/truth/actions/start_session/editor/create-contact.png +0 -0
  291. package/screenshots/truth/actions/start_session/editor/groups-and-contacts.png +0 -0
  292. package/screenshots/truth/actions/start_session/editor/groups-only.png +0 -0
  293. package/screenshots/truth/actions/start_session/editor/many-recipients.png +0 -0
  294. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  295. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  296. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  297. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  298. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  299. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  300. package/screenshots/truth/canvas-menu/open.png +0 -0
  301. package/screenshots/truth/editor/router.png +0 -0
  302. package/screenshots/truth/editor/wait.png +0 -0
  303. package/screenshots/truth/floating-tab/default.png +0 -0
  304. package/screenshots/truth/floating-tab/gray.png +0 -0
  305. package/screenshots/truth/floating-tab/green.png +0 -0
  306. package/screenshots/truth/floating-tab/hidden.png +0 -0
  307. package/screenshots/truth/floating-tab/hover.png +0 -0
  308. package/screenshots/truth/floating-tab/purple.png +0 -0
  309. package/screenshots/truth/floating-window/chromeless.png +0 -0
  310. package/screenshots/truth/floating-window/custom-size.png +0 -0
  311. package/screenshots/truth/floating-window/default.png +0 -0
  312. package/screenshots/truth/floating-window/with-header.png +0 -0
  313. package/screenshots/truth/list/fields-dragging.png +0 -0
  314. package/screenshots/truth/list/sortable-dragging.png +0 -0
  315. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  316. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  317. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  318. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  319. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  320. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  321. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  322. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  323. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  324. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  325. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  326. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  327. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  328. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  329. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  330. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  331. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  332. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  333. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  334. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  335. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  336. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  337. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  338. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  339. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  340. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  341. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  342. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  343. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  344. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  345. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  346. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  347. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  348. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  349. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  350. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  351. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  352. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  353. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  354. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  355. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  356. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  357. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  358. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  359. package/src/Icons.ts +4 -1
  360. package/src/display/FloatingTab.ts +174 -0
  361. package/src/display/ProgressBar.ts +22 -2
  362. package/src/events.ts +2 -8
  363. package/src/flow/CanvasMenu.ts +217 -0
  364. package/src/flow/CanvasNode.ts +596 -40
  365. package/src/flow/Editor.ts +1721 -45
  366. package/src/flow/NodeEditor.ts +621 -144
  367. package/src/flow/NodeTypeSelector.ts +636 -0
  368. package/src/flow/StickyNote.ts +12 -3
  369. package/src/flow/actions/add_contact_groups.ts +5 -4
  370. package/src/flow/actions/add_contact_urn.ts +78 -4
  371. package/src/flow/actions/add_input_labels.ts +5 -4
  372. package/src/flow/actions/play_audio.ts +3 -2
  373. package/src/flow/actions/remove_contact_groups.ts +16 -6
  374. package/src/flow/actions/request_optin.ts +3 -2
  375. package/src/flow/actions/say_msg.ts +3 -2
  376. package/src/flow/actions/send_broadcast.ts +86 -23
  377. package/src/flow/actions/send_email.ts +12 -6
  378. package/src/flow/actions/send_msg.ts +155 -34
  379. package/src/flow/actions/set_contact_channel.ts +6 -11
  380. package/src/flow/actions/set_contact_field.ts +21 -25
  381. package/src/flow/actions/set_contact_language.ts +11 -4
  382. package/src/flow/actions/set_contact_name.ts +4 -15
  383. package/src/flow/actions/set_contact_status.ts +4 -3
  384. package/src/flow/actions/set_run_result.ts +5 -4
  385. package/src/flow/actions/start_session.ts +210 -6
  386. package/src/flow/config.ts +11 -23
  387. package/src/flow/currencies.ts +51 -0
  388. package/src/flow/nodes/shared-rules.ts +301 -0
  389. package/src/flow/nodes/shared.ts +87 -0
  390. package/src/flow/nodes/split_by_airtime.ts +255 -5
  391. package/src/flow/nodes/split_by_contact_field.ts +195 -3
  392. package/src/flow/nodes/split_by_expression.ts +104 -2
  393. package/src/flow/nodes/split_by_groups.ts +26 -11
  394. package/src/flow/nodes/split_by_intent.ts +8 -0
  395. package/src/flow/nodes/split_by_llm.ts +22 -4
  396. package/src/flow/nodes/split_by_llm_categorize.ts +22 -5
  397. package/src/flow/nodes/split_by_random.ts +16 -6
  398. package/src/flow/nodes/split_by_resthook.ts +140 -0
  399. package/src/flow/nodes/split_by_run_result.ts +259 -3
  400. package/src/flow/nodes/split_by_scheme.ts +202 -2
  401. package/src/flow/nodes/split_by_subflow.ts +17 -5
  402. package/src/flow/nodes/split_by_ticket.ts +15 -4
  403. package/src/flow/nodes/split_by_webhook.ts +17 -6
  404. package/src/flow/nodes/wait_for_digits.ts +3 -2
  405. package/src/flow/nodes/wait_for_menu.ts +3 -2
  406. package/src/flow/nodes/wait_for_response.ts +59 -680
  407. package/src/flow/types.ts +156 -23
  408. package/src/flow/utils.ts +108 -14
  409. package/src/form/FieldRenderer.ts +2 -4
  410. package/src/interfaces.ts +3 -0
  411. package/src/layout/FloatingWindow.ts +386 -0
  412. package/src/list/SortableList.ts +109 -34
  413. package/src/live/ContactChat.ts +7 -25
  414. package/src/locales/es.ts +18 -13
  415. package/src/locales/fr.ts +18 -13
  416. package/src/locales/locale-codes.ts +11 -2
  417. package/src/locales/pt.ts +18 -13
  418. package/src/store/AppState.ts +173 -0
  419. package/src/store/flow-definition.d.ts +2 -5
  420. package/src/utils.ts +332 -12
  421. package/static/api/channels.json +46 -0
  422. package/static/api/llms.json +18 -0
  423. package/static/api/resthooks.json +31 -0
  424. package/static/svg/index.svg +1 -1
  425. package/static/svg/work/traced/lightning-02.svg +1 -0
  426. package/static/svg/work/used/lightning-02.svg +3 -0
  427. package/temba-modules.ts +8 -0
  428. package/test/ActionHelper.ts +3 -3
  429. package/test/NodeHelper.ts +6 -3
  430. package/test/actions/add_contact_urn.test.ts +287 -0
  431. package/test/actions/send_broadcast.test.ts +190 -0
  432. package/test/actions/send_email.test.ts +17 -23
  433. package/test/actions/send_msg.test.ts +39 -15
  434. package/test/actions/start_session.test.ts +151 -0
  435. package/test/nodes/split_by_airtime.test.ts +673 -0
  436. package/test/nodes/split_by_contact_field.test.ts +451 -0
  437. package/test/nodes/split_by_expression.test.ts +751 -0
  438. package/test/nodes/split_by_random.test.ts +3 -3
  439. package/test/nodes/split_by_resthook.test.ts +398 -0
  440. package/test/nodes/split_by_run_result.test.ts +1109 -0
  441. package/test/nodes/split_by_scheme.test.ts +486 -0
  442. package/test/nodes/split_by_subflow.test.ts +381 -0
  443. package/test/nodes/wait_for_digits.test.ts +2 -2
  444. package/test/nodes/wait_for_response.test.ts +2 -1
  445. package/test/temba-action-drag-between-nodes.test.ts +301 -0
  446. package/test/temba-canvas-menu.test.ts +156 -0
  447. package/test/temba-floating-tab.test.ts +110 -0
  448. package/test/temba-floating-window.test.ts +477 -0
  449. package/test/temba-flow-editor-node.test.ts +246 -2
  450. package/test/temba-flow-editor.test.ts +7 -8
  451. package/test/temba-localization.test.ts +611 -0
  452. package/test/temba-node-editor.test.ts +3 -1
  453. package/test/temba-node-type-selector.test.ts +355 -0
  454. package/test/temba-omnibox.test.ts +2 -1
  455. package/test/temba-sortable-list.test.ts +69 -0
  456. package/test/temba-utils-index.test.ts +0 -35
  457. package/test/utils.test.ts +22 -0
  458. package/test-assets/contacts/history.json +14 -21
  459. package/test-assets/select/llms.json +2 -2
  460. package/web-dev-server.config.mjs +49 -1
  461. package/web-test-runner.config.mjs +0 -1
  462. package/out-tsc/src/flow/actions/call_classifier.js +0 -11
  463. package/out-tsc/src/flow/actions/call_classifier.js.map +0 -1
  464. package/out-tsc/src/flow/actions/call_resthook.js +0 -11
  465. package/out-tsc/src/flow/actions/call_resthook.js.map +0 -1
  466. package/out-tsc/src/flow/actions/split_by_expression_example.js +0 -77
  467. package/out-tsc/src/flow/actions/split_by_expression_example.js.map +0 -1
  468. package/out-tsc/src/flow/actions/transfer_airtime.js +0 -11
  469. package/out-tsc/src/flow/actions/transfer_airtime.js.map +0 -1
  470. package/out-tsc/src/flow/nodes/wait_for_audio.js +0 -7
  471. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +0 -1
  472. package/out-tsc/src/flow/nodes/wait_for_image.js +0 -7
  473. package/out-tsc/src/flow/nodes/wait_for_image.js.map +0 -1
  474. package/out-tsc/src/flow/nodes/wait_for_location.js +0 -7
  475. package/out-tsc/src/flow/nodes/wait_for_location.js.map +0 -1
  476. package/out-tsc/src/flow/nodes/wait_for_video.js +0 -7
  477. package/out-tsc/src/flow/nodes/wait_for_video.js.map +0 -1
  478. package/src/flow/actions/call_classifier.ts +0 -12
  479. package/src/flow/actions/call_resthook.ts +0 -12
  480. package/src/flow/actions/split_by_expression_example.ts +0 -88
  481. package/src/flow/actions/transfer_airtime.ts +0 -12
  482. package/src/flow/nodes/wait_for_audio.ts +0 -7
  483. package/src/flow/nodes/wait_for_image.ts +0 -7
  484. package/src/flow/nodes/wait_for_location.ts +0 -7
  485. package/src/flow/nodes/wait_for_video.ts +0 -7
@@ -0,0 +1,140 @@
1
+ import { ACTION_GROUPS, FormData, NodeConfig, FlowTypes } from '../types';
2
+ import { CallResthook, Node } from '../../store/flow-definition';
3
+ import { generateUUID, createSuccessFailureRouter } from '../../utils';
4
+ import { html } from 'lit';
5
+ import {
6
+ resultNameField,
7
+ categoriesToLocalizationFormData,
8
+ localizationFormDataToCategories
9
+ } from './shared';
10
+
11
+ export const split_by_resthook: NodeConfig = {
12
+ type: 'split_by_resthook',
13
+ name: 'Call Resthook',
14
+ group: ACTION_GROUPS.services,
15
+ flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],
16
+ showAsAction: true,
17
+ form: {
18
+ resthook: {
19
+ type: 'select',
20
+ label: 'Resthook',
21
+ required: true,
22
+ searchable: true,
23
+ clearable: false,
24
+ placeholder: 'Select a resthook...',
25
+ endpoint: '/api/v2/resthooks.json',
26
+ valueKey: 'resthook',
27
+ nameKey: 'resthook',
28
+ helpText: 'Select the resthook to call'
29
+ },
30
+ result_name: resultNameField
31
+ },
32
+ layout: ['resthook', 'result_name'],
33
+ validate: (formData: FormData) => {
34
+ const errors: { [key: string]: string } = {};
35
+
36
+ // validate resthook is provided
37
+ if (!formData.resthook || formData.resthook.length === 0) {
38
+ errors.resthook = 'A resthook is required';
39
+ }
40
+
41
+ return {
42
+ valid: Object.keys(errors).length === 0,
43
+ errors
44
+ };
45
+ },
46
+ render: (node: Node) => {
47
+ const callResthookAction = node.actions?.find(
48
+ (action) => action.type === 'call_resthook'
49
+ ) as CallResthook;
50
+ return html`
51
+ <div
52
+ class="body"
53
+ style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto;"
54
+ >
55
+ ${callResthookAction?.resthook || 'Configure resthook'}
56
+ </div>
57
+ `;
58
+ },
59
+ toFormData: (node: Node) => {
60
+ // extract data from the existing node structure
61
+ const callResthookAction = node.actions?.find(
62
+ (action) => action.type === 'call_resthook'
63
+ ) as CallResthook;
64
+
65
+ return {
66
+ uuid: node.uuid,
67
+ resthook: callResthookAction?.resthook
68
+ ? [
69
+ {
70
+ resthook: callResthookAction.resthook
71
+ }
72
+ ]
73
+ : [],
74
+ result_name: node.router?.result_name || ''
75
+ };
76
+ },
77
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
78
+ // get resthook selection
79
+ const resthookSelection =
80
+ Array.isArray(formData.resthook) && formData.resthook.length > 0
81
+ ? formData.resthook[0]
82
+ : null;
83
+
84
+ if (!resthookSelection) {
85
+ return originalNode;
86
+ }
87
+
88
+ // find existing call_resthook action to preserve its UUID
89
+ const existingCallResthookAction = originalNode.actions?.find(
90
+ (action) => action.type === 'call_resthook'
91
+ );
92
+ const callResthookUuid = existingCallResthookAction?.uuid || generateUUID();
93
+
94
+ // create call_resthook action
95
+ const callResthookAction: CallResthook = {
96
+ type: 'call_resthook',
97
+ uuid: callResthookUuid,
98
+ resthook: resthookSelection.resthook
99
+ };
100
+
101
+ // create categories and exits for Success and Failure
102
+ const existingCategories = originalNode.router?.categories || [];
103
+ const existingExits = originalNode.exits || [];
104
+ const existingCases = originalNode.router?.cases || [];
105
+
106
+ const { router, exits } = createSuccessFailureRouter(
107
+ '@webhook.json.status',
108
+ {
109
+ type: 'has_text',
110
+ arguments: []
111
+ },
112
+ existingCategories,
113
+ existingExits,
114
+ existingCases
115
+ );
116
+
117
+ // Build final router with result_name
118
+ const finalRouter: any = {
119
+ ...router
120
+ };
121
+
122
+ // Only set result_name if provided
123
+ if (formData.result_name && formData.result_name.trim() !== '') {
124
+ finalRouter.result_name = formData.result_name.trim();
125
+ }
126
+
127
+ // return the complete node
128
+ return {
129
+ uuid: originalNode.uuid,
130
+ actions: [callResthookAction],
131
+ router: finalRouter,
132
+ exits: exits
133
+ };
134
+ },
135
+
136
+ // Localization support for categories
137
+ localizable: 'categories',
138
+ toLocalizationFormData: categoriesToLocalizationFormData,
139
+ fromLocalizationFormData: localizationFormDataToCategories
140
+ };
@@ -1,7 +1,263 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, FormData, NodeConfig, FlowTypes } from '../types';
2
+ import { Node } from '../../store/flow-definition';
3
+ import { createRulesRouter } from '../../utils';
4
+ import {
5
+ getWaitForResponseOperators,
6
+ operatorsToSelectOptions,
7
+ getOperatorConfig
8
+ } from '../operators';
9
+ import {
10
+ resultNameField,
11
+ categoriesToLocalizationFormData,
12
+ localizationFormDataToCategories
13
+ } from './shared';
14
+ import {
15
+ createRulesArrayConfig,
16
+ extractUserRules,
17
+ casesToFormRules
18
+ } from './shared-rules';
19
+ import { getStore } from '../../store/Store';
20
+
21
+ // delimit index options (first through 20th)
22
+ const DELIMIT_INDEX_OPTIONS = [
23
+ { value: '0', name: 'first result' },
24
+ { value: '1', name: 'second result' },
25
+ { value: '2', name: 'third result' },
26
+ { value: '3', name: 'fourth result' },
27
+ { value: '4', name: 'fifth result' },
28
+ { value: '5', name: 'sixth result' },
29
+ { value: '6', name: 'seventh result' },
30
+ { value: '7', name: 'eighth result' },
31
+ { value: '8', name: 'ninth result' },
32
+ { value: '9', name: 'tenth result' },
33
+ { value: '10', name: '11th result' },
34
+ { value: '11', name: '12th result' },
35
+ { value: '12', name: '13th result' },
36
+ { value: '13', name: '14th result' },
37
+ { value: '14', name: '15th result' },
38
+ { value: '15', name: '16th result' },
39
+ { value: '16', name: '17th result' },
40
+ { value: '17', name: '18th result' },
41
+ { value: '18', name: '19th result' },
42
+ { value: '19', name: '20th result' }
43
+ ];
44
+
45
+ // delimit by options - includes "don't delimit" and delimiter characters
46
+ const DELIMIT_BY_OPTIONS = [
47
+ { value: '', name: "Don't delimit result" },
48
+ { value: ' ', name: 'Delimited by spaces' },
49
+ { value: '.', name: 'Delimited by periods' },
50
+ { value: '+', name: 'Delimited by plusses' }
51
+ ];
2
52
 
3
53
  export const split_by_run_result: NodeConfig = {
4
54
  type: 'split_by_run_result',
5
- name: 'Split by Flow Result',
6
- color: COLORS.split
55
+ name: 'Split by Result',
56
+ group: SPLIT_GROUPS.split,
57
+ flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],
58
+ dialogSize: 'large',
59
+ form: {
60
+ result: {
61
+ type: 'select',
62
+
63
+ required: true,
64
+ searchable: false,
65
+ clearable: false,
66
+ placeholder: 'Select a result...',
67
+ getDynamicOptions: () => {
68
+ const store = getStore();
69
+ return store
70
+ ? store
71
+ .getState()
72
+ .getFlowResults()
73
+ .map((r) => ({ value: r.key, name: r.name }))
74
+ : [];
75
+ },
76
+ valueKey: 'value',
77
+ nameKey: 'name'
78
+ },
79
+ delimit_by: {
80
+ type: 'select',
81
+ required: false,
82
+ searchable: false,
83
+ clearable: false,
84
+ options: DELIMIT_BY_OPTIONS,
85
+ valueKey: 'value',
86
+ nameKey: 'name',
87
+ maxWidth: '180px'
88
+ },
89
+ delimit_index: {
90
+ type: 'select',
91
+ required: false,
92
+ searchable: false,
93
+ clearable: false,
94
+ options: DELIMIT_INDEX_OPTIONS,
95
+ valueKey: 'value',
96
+ nameKey: 'name',
97
+ maxWidth: '140px',
98
+ conditions: {
99
+ visible: (formData: FormData) => {
100
+ const delimitBy = formData.delimit_by?.[0]?.value;
101
+ return delimitBy !== undefined && delimitBy !== '';
102
+ }
103
+ }
104
+ },
105
+ rules: createRulesArrayConfig(
106
+ operatorsToSelectOptions(getWaitForResponseOperators()),
107
+ 'Define rules to categorize the result'
108
+ ),
109
+ result_name: resultNameField
110
+ },
111
+ layout: [
112
+ {
113
+ type: 'row',
114
+ label: 'Flow Result',
115
+ helpText:
116
+ 'Select a flow result and optionally delimit it to split on a specific part',
117
+ items: ['result', 'delimit_by', 'delimit_index']
118
+ },
119
+ 'rules',
120
+ 'result_name'
121
+ ],
122
+ validate: (formData: FormData) => {
123
+ const errors: { [key: string]: string } = {};
124
+
125
+ // Validate result is provided
126
+ if (!formData.result || formData.result.length === 0) {
127
+ errors.result = 'A flow result is required';
128
+ }
129
+
130
+ return {
131
+ valid: Object.keys(errors).length === 0,
132
+ errors
133
+ };
134
+ },
135
+ toFormData: (node: Node, nodeUI?: any) => {
136
+ // Get the result from the UI config operand (source of truth)
137
+ const result = nodeUI?.config?.operand;
138
+
139
+ // Extract rules from router cases using shared function
140
+ const rules = casesToFormRules(node);
141
+
142
+ // Extract delimiter configuration by checking the actual operand string
143
+ // If operand contains field() function, we have a delimiter
144
+ const operand = node.router?.operand || '';
145
+ const fieldFunctionMatch = operand.match(
146
+ /field\(results\.[\w]+,\s*(\d+),\s*"(.+?)"\)/
147
+ );
148
+
149
+ const hasDelimiter = fieldFunctionMatch !== null;
150
+ const delimitIndex = hasDelimiter
151
+ ? parseInt(fieldFunctionMatch![1], 10)
152
+ : 0;
153
+ const delimiter = hasDelimiter ? fieldFunctionMatch![2] : '';
154
+
155
+ return {
156
+ uuid: node.uuid,
157
+ result: result ? [result] : [],
158
+ delimit_by: hasDelimiter
159
+ ? [DELIMIT_BY_OPTIONS.find((opt) => opt.value === delimiter)]
160
+ : [DELIMIT_BY_OPTIONS[0]],
161
+ delimit_index: hasDelimiter
162
+ ? [
163
+ DELIMIT_INDEX_OPTIONS.find(
164
+ (opt) => opt.value === String(delimitIndex)
165
+ )
166
+ ]
167
+ : [DELIMIT_INDEX_OPTIONS[0]],
168
+ rules: rules,
169
+ result_name: node.router?.result_name || ''
170
+ };
171
+ },
172
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
173
+ // Get selected result (it's an array from the select component)
174
+ const selectedResult = formData.result?.[0];
175
+
176
+ if (!selectedResult) {
177
+ return originalNode;
178
+ }
179
+
180
+ // Build operand based on whether delimiter is selected
181
+ let operand: string;
182
+ const delimitBy = formData.delimit_by?.[0]?.value;
183
+ const hasDelimiter = delimitBy !== undefined && delimitBy !== '';
184
+
185
+ if (hasDelimiter) {
186
+ // Get delimiter configuration
187
+ const delimitIndex = formData.delimit_index?.[0]?.value ?? '0';
188
+
189
+ // Build operand with field() function
190
+ operand = `@(field(results.${selectedResult.value}, ${delimitIndex}, "${delimitBy}"))`;
191
+ } else {
192
+ // Standard operand without delimiter
193
+ operand = `@results.${selectedResult.value}`;
194
+ }
195
+
196
+ // Get user rules using shared extraction function
197
+ const userRules = extractUserRules(formData);
198
+
199
+ // Get existing router data for preservation
200
+ const existingCategories = originalNode.router?.categories || [];
201
+ const existingExits = originalNode.exits || [];
202
+ const existingCases = originalNode.router?.cases || [];
203
+
204
+ // Create router and exits using existing data when possible
205
+ const { router, exits } = createRulesRouter(
206
+ operand,
207
+ userRules,
208
+ getOperatorConfig,
209
+ existingCategories,
210
+ existingExits,
211
+ existingCases
212
+ );
213
+
214
+ // Build final router with result_name
215
+ const finalRouter: any = {
216
+ ...router
217
+ };
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
+ }
223
+
224
+ return {
225
+ ...originalNode,
226
+ router: finalRouter,
227
+ exits: exits
228
+ };
229
+ },
230
+ toUIConfig: (formData: FormData) => {
231
+ // Get selected result (it's an array from the select component)
232
+ const selectedResult = formData.result?.[0];
233
+
234
+ if (!selectedResult) {
235
+ return {};
236
+ }
237
+
238
+ // Build UI config with operand information
239
+ const config: any = {
240
+ operand: {
241
+ id: selectedResult.value,
242
+ name: selectedResult.name,
243
+ type: 'result'
244
+ }
245
+ };
246
+
247
+ // Add delimiter configuration if selected
248
+ const delimitBy = formData.delimit_by?.[0]?.value;
249
+ const hasDelimiter = delimitBy !== undefined && delimitBy !== '';
250
+
251
+ if (hasDelimiter) {
252
+ config.index = parseInt(formData.delimit_index?.[0]?.value ?? '0', 10);
253
+ config.delimiter = delimitBy;
254
+ }
255
+
256
+ return config;
257
+ },
258
+
259
+ // Localization support for categories
260
+ localizable: 'categories',
261
+ toLocalizationFormData: categoriesToLocalizationFormData,
262
+ fromLocalizationFormData: localizationFormDataToCategories
7
263
  };
@@ -1,7 +1,207 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { SPLIT_GROUPS, FormData, NodeConfig, FlowTypes } from '../types';
2
+ import { Node, Category, Exit, Case } from '../../store/flow-definition.d';
3
+ import { generateUUID } from '../../utils';
4
+ import { SCHEMES } from '../utils';
5
+ import {
6
+ resultNameField,
7
+ categoriesToLocalizationFormData,
8
+ localizationFormDataToCategories
9
+ } from './shared';
10
+
11
+ // Helper function to get scheme options for the select dropdown
12
+ const getSchemeOptions = () => {
13
+ return SCHEMES.map((scheme) => ({
14
+ value: scheme.scheme,
15
+ name: scheme.name
16
+ }));
17
+ };
18
+
19
+ // Helper function to create a switch router with scheme cases
20
+ const createSchemeRouter = (
21
+ selectedSchemes: string[],
22
+ existingCategories: Category[] = [],
23
+ existingExits: Exit[] = [],
24
+ existingCases: Case[] = [],
25
+ resultName: string = ''
26
+ ) => {
27
+ const categories: Category[] = [];
28
+ const exits: Exit[] = [];
29
+ const cases: Case[] = [];
30
+
31
+ // Create categories, exits, and cases for each selected scheme
32
+ selectedSchemes.forEach((scheme) => {
33
+ const schemeObj = SCHEMES.find((s) => s.scheme === scheme);
34
+ const schemeName = schemeObj?.name || scheme;
35
+
36
+ // Try to find existing category by scheme name
37
+ const existingCategory = existingCategories.find(
38
+ (cat) => cat.name === schemeName
39
+ );
40
+ const existingExit = existingCategory
41
+ ? existingExits.find((exit) => exit.uuid === existingCategory.exit_uuid)
42
+ : null;
43
+ const existingCase = existingCases.find((c) => c.arguments?.[0] === scheme);
44
+
45
+ const exitUuid = existingExit?.uuid || generateUUID();
46
+ const categoryUuid = existingCategory?.uuid || generateUUID();
47
+ const caseUuid = existingCase?.uuid || generateUUID();
48
+
49
+ categories.push({
50
+ uuid: categoryUuid,
51
+ name: schemeName,
52
+ exit_uuid: exitUuid
53
+ });
54
+
55
+ exits.push({
56
+ uuid: exitUuid,
57
+ destination_uuid: existingExit?.destination_uuid || null
58
+ });
59
+
60
+ cases.push({
61
+ uuid: caseUuid,
62
+ type: 'has_only_phrase',
63
+ arguments: [scheme],
64
+ category_uuid: categoryUuid
65
+ });
66
+ });
67
+
68
+ // Add default "Other" category for schemes not in the selected list
69
+ const existingOtherCategory = existingCategories.find((cat) => {
70
+ const matchesSelected = selectedSchemes.some((scheme) => {
71
+ const schemeObj = SCHEMES.find((s) => s.scheme === scheme);
72
+ return schemeObj?.name === cat.name;
73
+ });
74
+ return cat.name === 'Other' && !matchesSelected;
75
+ });
76
+ const existingOtherExit = existingOtherCategory
77
+ ? existingExits.find(
78
+ (exit) => exit.uuid === existingOtherCategory.exit_uuid
79
+ )
80
+ : null;
81
+
82
+ const otherExitUuid = existingOtherExit?.uuid || generateUUID();
83
+ const otherCategoryUuid = existingOtherCategory?.uuid || generateUUID();
84
+
85
+ categories.push({
86
+ uuid: otherCategoryUuid,
87
+ name: 'Other',
88
+ exit_uuid: otherExitUuid
89
+ });
90
+
91
+ exits.push({
92
+ uuid: otherExitUuid,
93
+ destination_uuid: existingOtherExit?.destination_uuid || null
94
+ });
95
+
96
+ return {
97
+ router: {
98
+ type: 'switch' as const,
99
+ cases: cases,
100
+ categories: categories,
101
+ default_category_uuid: otherCategoryUuid,
102
+ operand: '@(urn_parts(contact.urn).scheme)',
103
+ result_name: resultName
104
+ },
105
+ exits: exits
106
+ };
107
+ };
2
108
 
3
109
  export const split_by_scheme: NodeConfig = {
4
110
  type: 'split_by_scheme',
5
111
  name: 'Split by URN Type',
6
- color: COLORS.split
112
+ group: SPLIT_GROUPS.split,
113
+ flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],
114
+ form: {
115
+ schemes: {
116
+ type: 'select',
117
+ label: 'Channel Types',
118
+ helpText:
119
+ "The contact's URN is the address they used to reach you such as their phone number or a Facebook ID. Select which URN types to split by.",
120
+ required: true,
121
+ options: getSchemeOptions(),
122
+ multi: true,
123
+ searchable: true,
124
+ placeholder: 'Select the channels to split by...'
125
+ },
126
+ result_name: resultNameField
127
+ },
128
+ layout: ['schemes', 'result_name'],
129
+ validate: (formData: FormData) => {
130
+ const errors: { [key: string]: string } = {};
131
+
132
+ if (
133
+ !formData.schemes ||
134
+ !Array.isArray(formData.schemes) ||
135
+ formData.schemes.length === 0
136
+ ) {
137
+ errors.schemes = 'At least one channel type is required';
138
+ }
139
+
140
+ return {
141
+ valid: Object.keys(errors).length === 0,
142
+ errors
143
+ };
144
+ },
145
+ toFormData: (node: Node) => {
146
+ // Extract schemes from the existing node structure
147
+ const schemes: string[] = [];
148
+
149
+ if (node.router?.cases) {
150
+ node.router.cases.forEach((c: Case) => {
151
+ if (c.type === 'has_only_phrase' && c.arguments?.length > 0) {
152
+ schemes.push(c.arguments[0]);
153
+ }
154
+ });
155
+ }
156
+
157
+ return {
158
+ uuid: node.uuid,
159
+ schemes: schemes.map((scheme) => {
160
+ const schemeObj = SCHEMES.find((s) => s.scheme === scheme);
161
+ return {
162
+ value: scheme,
163
+ name: schemeObj?.name || scheme
164
+ };
165
+ }),
166
+ result_name: node.router?.result_name || ''
167
+ };
168
+ },
169
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
170
+ // Get selected schemes (handle both array of objects and array of strings)
171
+ const selectedSchemes = (formData.schemes || [])
172
+ .filter((scheme: any) => scheme)
173
+ .map((scheme: any) =>
174
+ typeof scheme === 'string' ? scheme : scheme.value
175
+ );
176
+
177
+ // Create router and exits using existing data when possible
178
+ const existingCategories = originalNode.router?.categories || [];
179
+ const existingExits = originalNode.exits || [];
180
+ const existingCases = originalNode.router?.cases || [];
181
+
182
+ const { router, exits } = createSchemeRouter(
183
+ selectedSchemes,
184
+ existingCategories,
185
+ existingExits,
186
+ existingCases,
187
+ formData.result_name || ''
188
+ );
189
+
190
+ // Return the complete node
191
+ return {
192
+ uuid: originalNode.uuid,
193
+ actions: originalNode.actions || [],
194
+ router: router,
195
+ exits: exits
196
+ };
197
+ },
198
+ router: {
199
+ type: 'switch',
200
+ operand: '@(urn_parts(contact.urn).scheme)'
201
+ },
202
+
203
+ // Localization support for categories
204
+ localizable: 'categories',
205
+ toLocalizationFormData: categoriesToLocalizationFormData,
206
+ fromLocalizationFormData: localizationFormDataToCategories
7
207
  };
@@ -1,12 +1,19 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { ACTION_GROUPS, FormData, NodeConfig, FlowTypes } from '../types';
2
2
  import { Node } from '../../store/flow-definition';
3
3
  import { generateUUID } from '../../utils';
4
4
  import { html } from 'lit';
5
+ import { renderNamedObjects } from '../utils';
6
+ import {
7
+ categoriesToLocalizationFormData,
8
+ localizationFormDataToCategories
9
+ } from './shared';
5
10
 
6
11
  export const split_by_subflow: NodeConfig = {
7
12
  type: 'split_by_subflow',
8
13
  name: 'Enter a Flow',
9
- color: COLORS.execute,
14
+ group: ACTION_GROUPS.trigger,
15
+ flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],
16
+ showAsAction: true,
10
17
  form: {
11
18
  flow: {
12
19
  type: 'select',
@@ -26,7 +33,7 @@ export const split_by_subflow: NodeConfig = {
26
33
  ) as any;
27
34
  return html`
28
35
  <div class="body">
29
- ${enterFlowAction?.flow?.name || 'Configure subflow'}
36
+ ${renderNamedObjects([enterFlowAction?.flow], 'flow')}
30
37
  </div>
31
38
  `;
32
39
  },
@@ -43,7 +50,7 @@ export const split_by_subflow: NodeConfig = {
43
50
  : []
44
51
  };
45
52
  },
46
- fromFormData: (formData: any, originalNode: Node): Node => {
53
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
47
54
  // Get flow selection
48
55
  const flowSelection =
49
56
  Array.isArray(formData.flow) && formData.flow.length > 0
@@ -155,5 +162,10 @@ export const split_by_subflow: NodeConfig = {
155
162
  router: router,
156
163
  exits: exits
157
164
  };
158
- }
165
+ },
166
+
167
+ // Localization support for categories
168
+ localizable: 'categories',
169
+ toLocalizationFormData: categoriesToLocalizationFormData,
170
+ fromLocalizationFormData: localizationFormDataToCategories
159
171
  };
@@ -1,12 +1,18 @@
1
- import { COLORS, NodeConfig } from '../types';
1
+ import { ACTION_GROUPS, FormData, NodeConfig, FlowTypes } from '../types';
2
2
  import { Node, OpenTicket } from '../../store/flow-definition';
3
3
  import { generateUUID, createSuccessFailureRouter } from '../../utils';
4
4
  import { html } from 'lit';
5
+ import {
6
+ categoriesToLocalizationFormData,
7
+ localizationFormDataToCategories
8
+ } from './shared';
5
9
 
6
10
  export const split_by_ticket: NodeConfig = {
7
11
  type: 'split_by_ticket',
8
12
  name: 'Open Ticket',
9
- color: COLORS.create,
13
+ group: ACTION_GROUPS.trigger,
14
+ flowTypes: [FlowTypes.VOICE, FlowTypes.MESSAGE, FlowTypes.BACKGROUND],
15
+ showAsAction: true,
10
16
  form: {
11
17
  topic: {
12
18
  type: 'select',
@@ -81,7 +87,7 @@ export const split_by_ticket: NodeConfig = {
81
87
  note: openTicketAction?.note || ''
82
88
  };
83
89
  },
84
- fromFormData: (formData: any, originalNode: Node): Node => {
90
+ fromFormData: (formData: FormData, originalNode: Node): Node => {
85
91
  // Find existing open_ticket action to preserve its UUID
86
92
  const existingOpenTicketAction = originalNode.actions?.find(
87
93
  (action) => action.type === 'open_ticket'
@@ -137,5 +143,10 @@ export const split_by_ticket: NodeConfig = {
137
143
  router: router,
138
144
  exits: exits
139
145
  };
140
- }
146
+ },
147
+
148
+ // Localization support for categories
149
+ localizable: 'categories',
150
+ toLocalizationFormData: categoriesToLocalizationFormData,
151
+ fromLocalizationFormData: localizationFormDataToCategories
141
152
  };