@nyaruka/temba-components 0.139.0 → 0.141.0

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 (385) hide show
  1. package/.github/workflows/cla.yml +1 -1
  2. package/.github/workflows/copilot-setup-steps.yml +6 -1
  3. package/.lintstagedrc.js +10 -0
  4. package/CHANGELOG.md +32 -0
  5. package/demo/data/flows/sample-flow.json +24 -0
  6. package/dist/locales/es.js +5 -5
  7. package/dist/locales/es.js.map +1 -1
  8. package/dist/locales/fr.js +5 -5
  9. package/dist/locales/fr.js.map +1 -1
  10. package/dist/locales/locale-codes.js +11 -2
  11. package/dist/locales/locale-codes.js.map +1 -1
  12. package/dist/locales/pt.js +5 -5
  13. package/dist/locales/pt.js.map +1 -1
  14. package/dist/temba-components.js +702 -338
  15. package/dist/temba-components.js.map +1 -1
  16. package/out-tsc/src/display/Chat.js +10 -7
  17. package/out-tsc/src/display/Chat.js.map +1 -1
  18. package/out-tsc/src/display/Dropdown.js +3 -1
  19. package/out-tsc/src/display/Dropdown.js.map +1 -1
  20. package/out-tsc/src/display/FloatingTab.js +4 -4
  21. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  22. package/out-tsc/src/display/Thumbnail.js +163 -5
  23. package/out-tsc/src/display/Thumbnail.js.map +1 -1
  24. package/out-tsc/src/flow/CanvasNode.js +65 -23
  25. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  26. package/out-tsc/src/flow/Editor.js +369 -49
  27. package/out-tsc/src/flow/Editor.js.map +1 -1
  28. package/out-tsc/src/flow/NodeEditor.js +118 -10
  29. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  30. package/out-tsc/src/flow/Plumber.js +61 -14
  31. package/out-tsc/src/flow/Plumber.js.map +1 -1
  32. package/out-tsc/src/flow/StickyNote.js +13 -4
  33. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  34. package/out-tsc/src/flow/actions/add_contact_groups.js +4 -1
  35. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  36. package/out-tsc/src/flow/actions/add_input_labels.js +4 -1
  37. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  38. package/out-tsc/src/flow/actions/audio-player.js +112 -0
  39. package/out-tsc/src/flow/actions/audio-player.js.map +1 -0
  40. package/out-tsc/src/flow/actions/enter_flow.js +43 -0
  41. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
  42. package/out-tsc/src/flow/actions/play_audio.js +57 -4
  43. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  44. package/out-tsc/src/flow/actions/remove_contact_groups.js +6 -1
  45. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  46. package/out-tsc/src/flow/actions/say_msg.js +86 -3
  47. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  48. package/out-tsc/src/flow/actions/send_broadcast.js +6 -2
  49. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  50. package/out-tsc/src/flow/actions/set_contact_channel.js +13 -0
  51. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  52. package/out-tsc/src/flow/actions/set_contact_status.js +7 -5
  53. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  54. package/out-tsc/src/flow/actions/start_session.js +10 -3
  55. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  56. package/out-tsc/src/flow/config.js +11 -3
  57. package/out-tsc/src/flow/config.js.map +1 -1
  58. package/out-tsc/src/flow/nodes/shared-rules.js +1 -1
  59. package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -1
  60. package/out-tsc/src/flow/nodes/split_by_contact_field.js +18 -5
  61. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
  62. package/out-tsc/src/flow/nodes/split_by_expression.js +1 -1
  63. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
  64. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +0 -1
  65. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  66. package/out-tsc/src/flow/nodes/split_by_random.js +0 -1
  67. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  68. package/out-tsc/src/flow/nodes/split_by_run_result.js +10 -4
  69. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
  70. package/out-tsc/src/flow/nodes/terminal.js +7 -0
  71. package/out-tsc/src/flow/nodes/terminal.js.map +1 -0
  72. package/out-tsc/src/flow/nodes/wait_for_audio.js +77 -0
  73. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
  74. package/out-tsc/src/flow/nodes/wait_for_dial.js +151 -0
  75. package/out-tsc/src/flow/nodes/wait_for_dial.js.map +1 -0
  76. package/out-tsc/src/flow/nodes/wait_for_digits.js +61 -1
  77. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  78. package/out-tsc/src/flow/nodes/wait_for_menu.js +173 -2
  79. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  80. package/out-tsc/src/flow/nodes/wait_for_response.js +1 -1
  81. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  82. package/out-tsc/src/flow/operators.js +21 -5
  83. package/out-tsc/src/flow/operators.js.map +1 -1
  84. package/out-tsc/src/flow/types.js.map +1 -1
  85. package/out-tsc/src/flow/utils.js +79 -3
  86. package/out-tsc/src/flow/utils.js.map +1 -1
  87. package/out-tsc/src/form/ArrayEditor.js +4 -2
  88. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  89. package/out-tsc/src/form/FieldRenderer.js +56 -0
  90. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  91. package/out-tsc/src/interfaces.js +1 -0
  92. package/out-tsc/src/interfaces.js.map +1 -1
  93. package/out-tsc/src/layout/Dialog.js +51 -7
  94. package/out-tsc/src/layout/Dialog.js.map +1 -1
  95. package/out-tsc/src/layout/Modax.js +20 -2
  96. package/out-tsc/src/layout/Modax.js.map +1 -1
  97. package/out-tsc/src/list/ContentMenu.js +14 -1
  98. package/out-tsc/src/list/ContentMenu.js.map +1 -1
  99. package/out-tsc/src/locales/es.js +5 -5
  100. package/out-tsc/src/locales/es.js.map +1 -1
  101. package/out-tsc/src/locales/fr.js +5 -5
  102. package/out-tsc/src/locales/fr.js.map +1 -1
  103. package/out-tsc/src/locales/locale-codes.js +11 -2
  104. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  105. package/out-tsc/src/locales/pt.js +5 -5
  106. package/out-tsc/src/locales/pt.js.map +1 -1
  107. package/out-tsc/src/simulator/Simulator.js +21 -4
  108. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  109. package/out-tsc/src/store/AppState.js +102 -3
  110. package/out-tsc/src/store/AppState.js.map +1 -1
  111. package/out-tsc/test/actions/add_contact_groups.test.js +35 -0
  112. package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -1
  113. package/out-tsc/test/actions/add_input_labels.test.js +53 -0
  114. package/out-tsc/test/actions/add_input_labels.test.js.map +1 -0
  115. package/out-tsc/test/actions/enter_flow.test.js +71 -0
  116. package/out-tsc/test/actions/enter_flow.test.js.map +1 -0
  117. package/out-tsc/test/actions/play_audio.test.js +118 -0
  118. package/out-tsc/test/actions/play_audio.test.js.map +1 -0
  119. package/out-tsc/test/actions/remove_contact_groups.test.js +24 -0
  120. package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -1
  121. package/out-tsc/test/actions/say_msg.test.js +158 -0
  122. package/out-tsc/test/actions/say_msg.test.js.map +1 -0
  123. package/out-tsc/test/actions/send_broadcast.test.js +41 -0
  124. package/out-tsc/test/actions/send_broadcast.test.js.map +1 -1
  125. package/out-tsc/test/actions/set_contact_channel.test.js +67 -0
  126. package/out-tsc/test/actions/set_contact_channel.test.js.map +1 -0
  127. package/out-tsc/test/actions/set_contact_field.test.js +52 -0
  128. package/out-tsc/test/actions/set_contact_field.test.js.map +1 -0
  129. package/out-tsc/test/actions/set_contact_language.test.js +39 -0
  130. package/out-tsc/test/actions/set_contact_language.test.js.map +1 -0
  131. package/out-tsc/test/actions/set_contact_name.test.js +28 -0
  132. package/out-tsc/test/actions/set_contact_name.test.js.map +1 -0
  133. package/out-tsc/test/actions/set_contact_status.test.js +44 -0
  134. package/out-tsc/test/actions/set_contact_status.test.js.map +1 -0
  135. package/out-tsc/test/actions/set_run_result.test.js +47 -0
  136. package/out-tsc/test/actions/set_run_result.test.js.map +1 -0
  137. package/out-tsc/test/actions/start_session.test.js +76 -0
  138. package/out-tsc/test/actions/start_session.test.js.map +1 -1
  139. package/out-tsc/test/nodes/split_by_contact_field.test.js +50 -0
  140. package/out-tsc/test/nodes/split_by_contact_field.test.js.map +1 -1
  141. package/out-tsc/test/nodes/split_by_run_result.test.js +82 -0
  142. package/out-tsc/test/nodes/split_by_run_result.test.js.map +1 -1
  143. package/out-tsc/test/nodes/split_by_ticket.test.js +139 -0
  144. package/out-tsc/test/nodes/split_by_ticket.test.js.map +1 -0
  145. package/out-tsc/test/nodes/split_by_webhook.test.js +111 -0
  146. package/out-tsc/test/nodes/split_by_webhook.test.js.map +1 -0
  147. package/out-tsc/test/nodes/wait_for_audio.test.js +156 -0
  148. package/out-tsc/test/nodes/wait_for_audio.test.js.map +1 -0
  149. package/out-tsc/test/nodes/wait_for_dial.test.js +336 -0
  150. package/out-tsc/test/nodes/wait_for_dial.test.js.map +1 -0
  151. package/out-tsc/test/nodes/wait_for_digits.test.js +198 -84
  152. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  153. package/out-tsc/test/nodes/wait_for_menu.test.js +340 -0
  154. package/out-tsc/test/nodes/wait_for_menu.test.js.map +1 -0
  155. package/out-tsc/test/temba-flow-collision.test.js +261 -6
  156. package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
  157. package/out-tsc/test/temba-flow-editor.test.js +187 -0
  158. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  159. package/out-tsc/test/temba-flow-plumber.test.js +19 -0
  160. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  161. package/out-tsc/test/temba-node-type-selector.test.js +6 -6
  162. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
  163. package/out-tsc/test/temba-select.test.js +4 -1
  164. package/out-tsc/test/temba-select.test.js.map +1 -1
  165. package/out-tsc/test/utils.test.js +4 -2
  166. package/out-tsc/test/utils.test.js.map +1 -1
  167. package/package.json +3 -9
  168. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  169. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  170. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  171. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  172. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  173. package/screenshots/truth/actions/add_contact_urn/render/expression-facebook.png +0 -0
  174. package/screenshots/truth/actions/add_contact_urn/render/expression-phone.png +0 -0
  175. package/screenshots/truth/actions/add_contact_urn/render/facebook-id.png +0 -0
  176. package/screenshots/truth/actions/add_contact_urn/render/instagram-handle.png +0 -0
  177. package/screenshots/truth/actions/add_contact_urn/render/line-id.png +0 -0
  178. package/screenshots/truth/actions/add_contact_urn/render/phone-number.png +0 -0
  179. package/screenshots/truth/actions/add_contact_urn/render/telegram-id.png +0 -0
  180. package/screenshots/truth/actions/add_contact_urn/render/viber-id.png +0 -0
  181. package/screenshots/truth/actions/add_contact_urn/render/wechat-id.png +0 -0
  182. package/screenshots/truth/actions/add_contact_urn/render/whatsapp.png +0 -0
  183. package/screenshots/truth/actions/add_input_labels/editor/multiple-labels.png +0 -0
  184. package/screenshots/truth/actions/add_input_labels/editor/single-label.png +0 -0
  185. package/screenshots/truth/actions/add_input_labels/render/multiple-labels.png +0 -0
  186. package/screenshots/truth/actions/add_input_labels/render/single-label.png +0 -0
  187. package/screenshots/truth/actions/enter_flow/editor/basic-flow.png +0 -0
  188. package/screenshots/truth/actions/enter_flow/editor/long-flow-name.png +0 -0
  189. package/screenshots/truth/actions/enter_flow/render/basic-flow.png +0 -0
  190. package/screenshots/truth/actions/enter_flow/render/long-flow-name.png +0 -0
  191. package/screenshots/truth/actions/play_audio/editor/expression-url.png +0 -0
  192. package/screenshots/truth/actions/play_audio/editor/static-url.png +0 -0
  193. package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
  194. package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
  195. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  196. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  197. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  198. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  199. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  200. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  201. package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
  202. package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
  203. package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
  204. package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
  205. package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
  206. package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
  207. package/screenshots/truth/actions/send_broadcast/render/contacts-only.png +0 -0
  208. package/screenshots/truth/actions/send_broadcast/render/groups-and-contacts.png +0 -0
  209. package/screenshots/truth/actions/send_broadcast/render/groups-only.png +0 -0
  210. package/screenshots/truth/actions/send_broadcast/render/many-groups.png +0 -0
  211. package/screenshots/truth/actions/send_broadcast/render/multiline-text.png +0 -0
  212. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  213. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  214. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  215. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  216. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  217. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  218. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  219. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  220. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  221. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  222. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  223. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  224. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  225. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  226. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  227. package/screenshots/truth/actions/set_contact_channel/editor/sms-channel.png +0 -0
  228. package/screenshots/truth/actions/set_contact_channel/editor/whatsapp-channel.png +0 -0
  229. package/screenshots/truth/actions/set_contact_channel/render/sms-channel.png +0 -0
  230. package/screenshots/truth/actions/set_contact_channel/render/whatsapp-channel.png +0 -0
  231. package/screenshots/truth/actions/set_contact_field/editor/clear-value.png +0 -0
  232. package/screenshots/truth/actions/set_contact_field/editor/set-value.png +0 -0
  233. package/screenshots/truth/actions/set_contact_field/render/clear-value.png +0 -0
  234. package/screenshots/truth/actions/set_contact_field/render/set-value.png +0 -0
  235. package/screenshots/truth/actions/set_contact_language/editor/english.png +0 -0
  236. package/screenshots/truth/actions/set_contact_language/editor/french.png +0 -0
  237. package/screenshots/truth/actions/set_contact_language/render/english.png +0 -0
  238. package/screenshots/truth/actions/set_contact_language/render/french.png +0 -0
  239. package/screenshots/truth/actions/set_contact_name/editor/expression-name.png +0 -0
  240. package/screenshots/truth/actions/set_contact_name/editor/static-name.png +0 -0
  241. package/screenshots/truth/actions/set_contact_name/render/expression-name.png +0 -0
  242. package/screenshots/truth/actions/set_contact_name/render/static-name.png +0 -0
  243. package/screenshots/truth/actions/set_contact_status/editor/active.png +0 -0
  244. package/screenshots/truth/actions/set_contact_status/editor/archived.png +0 -0
  245. package/screenshots/truth/actions/set_contact_status/editor/blocked.png +0 -0
  246. package/screenshots/truth/actions/set_contact_status/render/active.png +0 -0
  247. package/screenshots/truth/actions/set_contact_status/render/archived.png +0 -0
  248. package/screenshots/truth/actions/set_contact_status/render/blocked.png +0 -0
  249. package/screenshots/truth/actions/set_run_result/editor/expression-value.png +0 -0
  250. package/screenshots/truth/actions/set_run_result/editor/with-category.png +0 -0
  251. package/screenshots/truth/actions/set_run_result/render/expression-value.png +0 -0
  252. package/screenshots/truth/actions/set_run_result/render/with-category.png +0 -0
  253. package/screenshots/truth/actions/start_session/render/contact-query.png +0 -0
  254. package/screenshots/truth/actions/start_session/render/contacts-only.png +0 -0
  255. package/screenshots/truth/actions/start_session/render/create-contact.png +0 -0
  256. package/screenshots/truth/actions/start_session/render/groups-and-contacts.png +0 -0
  257. package/screenshots/truth/actions/start_session/render/groups-only.png +0 -0
  258. package/screenshots/truth/actions/start_session/render/many-recipients.png +0 -0
  259. package/screenshots/truth/editor/router.png +0 -0
  260. package/screenshots/truth/editor/wait.png +0 -0
  261. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  262. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  263. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  264. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  265. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  266. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  267. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  268. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  269. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  270. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  271. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  272. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  273. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  274. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  275. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  276. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  277. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  278. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  279. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  280. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  281. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  282. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  283. package/screenshots/truth/nodes/wait_for_audio/editor/basic-audio-wait.png +0 -0
  284. package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
  285. package/screenshots/truth/nodes/wait_for_dial/editor/basic-dial.png +0 -0
  286. package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
  287. package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
  288. package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
  289. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  290. package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
  291. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  292. package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
  293. package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
  294. package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.png +0 -0
  295. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  296. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  297. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  298. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  299. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  300. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  301. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  302. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  303. package/src/display/Chat.ts +13 -7
  304. package/src/display/Dropdown.ts +3 -1
  305. package/src/display/FloatingTab.ts +4 -4
  306. package/src/display/Thumbnail.ts +162 -2
  307. package/src/flow/CanvasNode.ts +70 -24
  308. package/src/flow/Editor.ts +440 -99
  309. package/src/flow/NodeEditor.ts +137 -9
  310. package/src/flow/Plumber.ts +89 -14
  311. package/src/flow/StickyNote.ts +14 -4
  312. package/src/flow/actions/add_contact_groups.ts +4 -1
  313. package/src/flow/actions/add_input_labels.ts +4 -1
  314. package/src/flow/actions/audio-player.ts +127 -0
  315. package/src/flow/actions/enter_flow.ts +44 -0
  316. package/src/flow/actions/play_audio.ts +64 -5
  317. package/src/flow/actions/remove_contact_groups.ts +6 -1
  318. package/src/flow/actions/say_msg.ts +94 -4
  319. package/src/flow/actions/send_broadcast.ts +6 -2
  320. package/src/flow/actions/set_contact_channel.ts +13 -1
  321. package/src/flow/actions/set_contact_status.ts +7 -5
  322. package/src/flow/actions/start_session.ts +10 -3
  323. package/src/flow/config.ts +11 -3
  324. package/src/flow/nodes/shared-rules.ts +1 -1
  325. package/src/flow/nodes/split_by_contact_field.ts +16 -5
  326. package/src/flow/nodes/split_by_expression.ts +1 -1
  327. package/src/flow/nodes/split_by_llm_categorize.ts +0 -1
  328. package/src/flow/nodes/split_by_random.ts +0 -1
  329. package/src/flow/nodes/split_by_run_result.ts +10 -4
  330. package/src/flow/nodes/terminal.ts +9 -0
  331. package/src/flow/nodes/wait_for_audio.ts +88 -0
  332. package/src/flow/nodes/wait_for_dial.ts +176 -0
  333. package/src/flow/nodes/wait_for_digits.ts +87 -2
  334. package/src/flow/nodes/wait_for_menu.ts +209 -3
  335. package/src/flow/nodes/wait_for_response.ts +1 -1
  336. package/src/flow/operators.ts +23 -5
  337. package/src/flow/types.ts +23 -1
  338. package/src/flow/utils.ts +82 -3
  339. package/src/form/ArrayEditor.ts +4 -2
  340. package/src/form/FieldRenderer.ts +71 -1
  341. package/src/interfaces.ts +2 -1
  342. package/src/layout/Dialog.ts +52 -7
  343. package/src/layout/Modax.ts +19 -2
  344. package/src/list/ContentMenu.ts +15 -1
  345. package/src/locales/es.ts +18 -13
  346. package/src/locales/fr.ts +18 -13
  347. package/src/locales/locale-codes.ts +11 -2
  348. package/src/locales/pt.ts +18 -13
  349. package/src/simulator/Simulator.ts +25 -4
  350. package/src/store/AppState.ts +120 -1
  351. package/src/store/flow-definition.d.ts +2 -0
  352. package/test/actions/add_contact_groups.test.ts +38 -0
  353. package/test/actions/add_input_labels.test.ts +67 -0
  354. package/test/actions/enter_flow.test.ts +88 -0
  355. package/test/actions/play_audio.test.ts +155 -0
  356. package/test/actions/remove_contact_groups.test.ts +29 -0
  357. package/test/actions/say_msg.test.ts +196 -0
  358. package/test/actions/send_broadcast.test.ts +44 -0
  359. package/test/actions/set_contact_channel.test.ts +88 -0
  360. package/test/actions/set_contact_field.test.ts +68 -0
  361. package/test/actions/set_contact_language.test.ts +55 -0
  362. package/test/actions/set_contact_name.test.ts +39 -0
  363. package/test/actions/set_contact_status.test.ts +64 -0
  364. package/test/actions/set_run_result.test.ts +61 -0
  365. package/test/actions/start_session.test.ts +82 -0
  366. package/test/nodes/split_by_contact_field.test.ts +59 -0
  367. package/test/nodes/split_by_run_result.test.ts +100 -0
  368. package/test/nodes/split_by_ticket.test.ts +157 -0
  369. package/test/nodes/split_by_webhook.test.ts +131 -0
  370. package/test/nodes/wait_for_audio.test.ts +182 -0
  371. package/test/nodes/wait_for_dial.test.ts +382 -0
  372. package/test/nodes/wait_for_digits.test.ts +233 -109
  373. package/test/nodes/wait_for_menu.test.ts +383 -0
  374. package/test/temba-flow-collision.test.ts +286 -6
  375. package/test/temba-flow-editor.test.ts +240 -0
  376. package/test/temba-flow-plumber.test.ts +62 -0
  377. package/test/temba-node-type-selector.test.ts +6 -6
  378. package/test/temba-select.test.ts +6 -1
  379. package/test/utils.test.ts +4 -2
  380. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  381. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  382. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  383. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  384. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  385. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
@@ -1,6 +1,6 @@
1
1
  import { PropertyValueMap, css, html } from 'lit';
2
2
  import { RapidElement } from '../RapidElement';
3
- import { property } from 'lit/decorators.js';
3
+ import { property, state } from 'lit/decorators.js';
4
4
  import { getClasses } from '../utils';
5
5
  import { Lightbox } from './Lightbox';
6
6
  import { WebChatIcon } from '../webchat';
@@ -67,11 +67,57 @@ export class Thumbnail extends RapidElement {
67
67
  }
68
68
 
69
69
  .thumb.document,
70
- .thumb.audio,
71
70
  .thumb.video {
72
71
  border: 1px solid #eee;
73
72
  }
74
73
 
74
+ .audio-player {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 6px;
78
+ padding: 6px 8px;
79
+ background: rgba(0, 0, 0, 0.05);
80
+ border-radius: var(--curvature);
81
+ cursor: default;
82
+ }
83
+
84
+ .audio-play-btn {
85
+ cursor: pointer;
86
+ color: #666;
87
+ display: flex;
88
+ align-items: center;
89
+ flex-shrink: 0;
90
+ }
91
+
92
+ .audio-play-btn:hover {
93
+ color: #333;
94
+ }
95
+
96
+ .audio-progress-bar {
97
+ flex: 1;
98
+ height: 3px;
99
+ background: #ddd;
100
+ border-radius: 2px;
101
+ overflow: hidden;
102
+ min-width: 60px;
103
+ cursor: pointer;
104
+ }
105
+
106
+ .audio-progress-fill {
107
+ height: 100%;
108
+ background: var(--color-primary, #2387ca);
109
+ border-radius: 2px;
110
+ transition: width 0.15s linear;
111
+ }
112
+
113
+ .audio-time {
114
+ font-size: 11px;
115
+ color: #999;
116
+ flex-shrink: 0;
117
+ min-width: 28px;
118
+ text-align: right;
119
+ }
120
+
75
121
  .wrapper:hover .thumb.icon {
76
122
  }
77
123
 
@@ -133,6 +179,74 @@ export class Thumbnail extends RapidElement {
133
179
  @property({ type: String, attribute: false })
134
180
  private tileUrl: string = '';
135
181
 
182
+ // audio player state
183
+ private audio: HTMLAudioElement | null = null;
184
+
185
+ @state()
186
+ private audioPlaying = false;
187
+
188
+ @state()
189
+ private audioProgress = 0;
190
+
191
+ @state()
192
+ private audioDuration = 0;
193
+
194
+ private handleAudioPlayClick(e: Event) {
195
+ e.stopPropagation();
196
+
197
+ if (!this.audio) {
198
+ this.audio = new Audio(this.url);
199
+ this.audio.addEventListener('timeupdate', () => {
200
+ if (this.audio.duration) {
201
+ this.audioProgress = this.audio.currentTime / this.audio.duration;
202
+ this.audioDuration = this.audio.duration;
203
+ }
204
+ });
205
+ this.audio.addEventListener('ended', () => {
206
+ this.audioPlaying = false;
207
+ this.audioProgress = 0;
208
+ });
209
+ this.audio.addEventListener('error', () => {
210
+ this.audioPlaying = false;
211
+ this.audioProgress = 0;
212
+ });
213
+ }
214
+
215
+ if (this.audioPlaying) {
216
+ this.audio.pause();
217
+ this.audioPlaying = false;
218
+ } else {
219
+ this.audio.play().catch(() => {
220
+ this.audioPlaying = false;
221
+ });
222
+ this.audioPlaying = true;
223
+ }
224
+ }
225
+
226
+ private handleProgressClick(e: MouseEvent) {
227
+ e.stopPropagation();
228
+ if (!this.audio || !this.audio.duration) return;
229
+ const bar = e.currentTarget as HTMLElement;
230
+ const rect = bar.getBoundingClientRect();
231
+ const pct = (e.clientX - rect.left) / rect.width;
232
+ this.audio.currentTime = pct * this.audio.duration;
233
+ }
234
+
235
+ private formatTime(seconds: number): string {
236
+ const s = Math.floor(seconds);
237
+ const m = Math.floor(s / 60);
238
+ const rem = s % 60;
239
+ return `${m}:${rem.toString().padStart(2, '0')}`;
240
+ }
241
+
242
+ disconnectedCallback() {
243
+ super.disconnectedCallback();
244
+ if (this.audio) {
245
+ this.audio.pause();
246
+ this.audio = null;
247
+ }
248
+ }
249
+
136
250
  // convert lat/lng to tile coordinates for OSM
137
251
  private latLngToTile(lat: number, lng: number, zoom: number) {
138
252
  const n = Math.pow(2, zoom);
@@ -227,6 +341,8 @@ export class Thumbnail extends RapidElement {
227
341
  // open location in openstreetmap
228
342
  const osmUrl = `https://www.openstreetmap.org/?mlat=${this.latitude}&mlon=${this.longitude}#map=15/${this.latitude}/${this.longitude}`;
229
343
  window.open(osmUrl, '_blank');
344
+ } else if (this.contentType === ThumbnailContentType.AUDIO) {
345
+ // audio has inline controls, no click action needed
230
346
  } else {
231
347
  window.open(this.url, '_blank');
232
348
  }
@@ -245,6 +361,9 @@ export class Thumbnail extends RapidElement {
245
361
  <div
246
362
  @click=${this.handleThumbnailClicked.bind(this)}
247
363
  class="${getClasses({ wrapper: true, zoom: this.zoom })}"
364
+ style="${this.contentType === ThumbnailContentType.AUDIO
365
+ ? 'cursor: default;'
366
+ : ''}"
248
367
  url=${this.url}
249
368
  >
250
369
  ${this.contentType === ThumbnailContentType.IMAGE && this.preview
@@ -254,6 +373,47 @@ export class Thumbnail extends RapidElement {
254
373
  class="observe thumb ${this.contentType}"
255
374
  src="${this.url}"
256
375
  ></img></div>`
376
+ : this.contentType === ThumbnailContentType.AUDIO
377
+ ? html`<div class="audio-player">
378
+ <div class="audio-play-btn" @click=${this.handleAudioPlayClick}>
379
+ ${this.audioPlaying
380
+ ? html`<svg
381
+ viewBox="0 0 24 24"
382
+ width="14"
383
+ height="14"
384
+ fill="currentColor"
385
+ >
386
+ <rect x="5" y="3" width="4" height="18" />
387
+ <rect x="15" y="3" width="4" height="18" />
388
+ </svg>`
389
+ : html`<svg
390
+ viewBox="0 0 24 24"
391
+ width="14"
392
+ height="14"
393
+ fill="currentColor"
394
+ >
395
+ <polygon points="6,3 20,12 6,21" />
396
+ </svg>`}
397
+ </div>
398
+ <div
399
+ class="audio-progress-bar"
400
+ @click=${this.handleProgressClick}
401
+ >
402
+ <div
403
+ class="audio-progress-fill"
404
+ style="width: ${this.audioProgress * 100}%"
405
+ ></div>
406
+ </div>
407
+ <div class="audio-time">
408
+ ${this.audioDuration
409
+ ? this.formatTime(
410
+ this.audioPlaying || this.audioProgress > 0
411
+ ? this.audio?.currentTime || 0
412
+ : this.audioDuration
413
+ )
414
+ : ''}
415
+ </div>
416
+ </div>`
257
417
  : html`
258
418
  ${this.contentType === ThumbnailContentType.LOCATION
259
419
  ? html`<img
@@ -9,7 +9,7 @@ import { getClasses } from '../utils';
9
9
  import { Plumber } from './Plumber';
10
10
  import { getStore } from '../store/Store';
11
11
  import { CustomEventType } from '../interfaces';
12
- import { AppState, fromStore, zustand } from '../store/AppState';
12
+ import { AppState, FlowIssue, fromStore, zustand } from '../store/AppState';
13
13
 
14
14
  const DRAG_THRESHOLD = 5;
15
15
 
@@ -49,6 +49,12 @@ export class CanvasNode extends RapidElement {
49
49
  @fromStore(zustand, (state: AppState) => state.getCurrentActivity())
50
50
  private activity!: any;
51
51
 
52
+ @fromStore(zustand, (state: AppState) => state.issuesByNode)
53
+ private issuesByNode!: Map<string, FlowIssue[]>;
54
+
55
+ @fromStore(zustand, (state: AppState) => state.issuesByAction)
56
+ private issuesByAction!: Map<string, FlowIssue[]>;
57
+
52
58
  // Track exits that are in "removing" state
53
59
  private exitRemovalTimeouts: Map<string, number> = new Map();
54
60
 
@@ -169,7 +175,7 @@ export class CanvasNode extends RapidElement {
169
175
  z-index: 10;
170
176
  transition: all 100ms ease-in-out;
171
177
  align-self: center;
172
- margin-right:0.15em;
178
+ margin-right: 0.3em;
173
179
  border: 0px solid red;
174
180
  width: 1em;
175
181
  pointer-events: auto; /* Ensure remove button can receive events */
@@ -185,6 +191,12 @@ export class CanvasNode extends RapidElement {
185
191
  pointer-events: none !important;
186
192
  }
187
193
 
194
+ /* Issue indicators - hatched red title bar */
195
+ .action-content.has-issues .cn-title,
196
+ .node.has-issues > .router .cn-title {
197
+ background: repeating-linear-gradient(120deg, tomato, tomato 6px, #ff7056 0, #ff7056 18px) !important;
198
+ }
199
+
188
200
  .action.sortable {
189
201
  display: flex;
190
202
  align-items: stretch;
@@ -548,14 +560,17 @@ export class CanvasNode extends RapidElement {
548
560
  // make our initial connections
549
561
  // We use setTimeout to allow for DOM updates to complete before querying for exits
550
562
  this.connectionTimeout = window.setTimeout(() => {
551
- for (const exit of this.node.exits) {
552
- this.plumber.makeSource(exit.uuid);
553
- if (exit.destination_uuid) {
554
- this.plumber.connectIds(
555
- this.node.uuid,
556
- exit.uuid,
557
- exit.destination_uuid
558
- );
563
+ // Terminal nodes have no visible exits
564
+ if (this.ui?.type !== 'terminal') {
565
+ for (const exit of this.node.exits) {
566
+ this.plumber.makeSource(exit.uuid);
567
+ if (exit.destination_uuid) {
568
+ this.plumber.connectIds(
569
+ this.node.uuid,
570
+ exit.uuid,
571
+ exit.destination_uuid
572
+ );
573
+ }
559
574
  }
560
575
  }
561
576
  // Note: revalidation is handled by plumber's processPendingConnections which calls repaintEverything
@@ -949,6 +964,11 @@ export class CanvasNode extends RapidElement {
949
964
  this.requestUpdate();
950
965
  }
951
966
 
967
+ private getTopCenter(el: Element): { x: number; y: number } {
968
+ const rect = el.getBoundingClientRect();
969
+ return { x: rect.left + rect.width / 2, y: rect.top };
970
+ }
971
+
952
972
  private handleActionMouseDown(event: MouseEvent, action: Action): void {
953
973
  // Don't handle clicks on the remove button, drag handle, or when action is in removing state
954
974
  const target = event.target as HTMLElement;
@@ -1002,10 +1022,18 @@ export class CanvasNode extends RapidElement {
1002
1022
  // Only fire the action edit event if we haven't dragged beyond the threshold
1003
1023
  // AND either there's no Editor parent (test case) or the Editor didn't drag the node
1004
1024
  if (distance <= DRAG_THRESHOLD && (!editor || !editorWasDragging)) {
1025
+ // Use top-center of the action element as the dialog origin
1026
+ const actionEl = event.currentTarget as Element;
1027
+ const origin = actionEl
1028
+ ? this.getTopCenter(actionEl)
1029
+ : { x: event.clientX, y: event.clientY };
1030
+
1005
1031
  // Fire event to request action editing
1006
1032
  this.fireCustomEvent(CustomEventType.ActionEditRequested, {
1007
1033
  action,
1008
- nodeUuid: this.node.uuid
1034
+ nodeUuid: this.node.uuid,
1035
+ originX: origin.x,
1036
+ originY: origin.y
1009
1037
  });
1010
1038
  }
1011
1039
  }
@@ -1125,17 +1153,24 @@ export class CanvasNode extends RapidElement {
1125
1153
  // Using literal 5 instead of DRAG_THRESHOLD since it's not imported
1126
1154
  // Fire event to request node editing if the node has a router
1127
1155
  if (this.node.router) {
1156
+ // Use top-center of the node as the dialog origin
1157
+ const origin = this.getTopCenter(this);
1158
+
1128
1159
  // If router node has exactly one action, open the action editor directly
1129
1160
  if (this.node.actions && this.node.actions.length === 1) {
1130
1161
  this.fireCustomEvent(CustomEventType.ActionEditRequested, {
1131
1162
  action: this.node.actions[0],
1132
- nodeUuid: this.node.uuid
1163
+ nodeUuid: this.node.uuid,
1164
+ originX: origin.x,
1165
+ originY: origin.y
1133
1166
  });
1134
1167
  } else {
1135
1168
  // Otherwise open the node editor as before
1136
1169
  this.fireCustomEvent(CustomEventType.NodeEditRequested, {
1137
1170
  node: this.node,
1138
- nodeUI: this.ui
1171
+ nodeUI: this.ui,
1172
+ originX: origin.x,
1173
+ originY: origin.y
1139
1174
  });
1140
1175
  }
1141
1176
  }
@@ -1310,13 +1345,11 @@ export class CanvasNode extends RapidElement {
1310
1345
  const color = config.group
1311
1346
  ? ACTION_GROUP_METADATA[config.group]?.color
1312
1347
  : '#aaaaaa';
1348
+ const isTerminal = this.ui?.type === 'terminal';
1313
1349
  return html`<div class="cn-title" style="background:${color}">
1314
- ${this.ui?.type === 'execute_actions'
1315
- ? html`<temba-icon
1316
- class="drag-handle ${this.isReadOnly() ? 'read-only-hidden' : ''}"
1317
- name="sort"
1318
- ></temba-icon>`
1319
- : this.node?.actions?.length > 1
1350
+ ${isTerminal
1351
+ ? html`<div class="title-spacer"></div>`
1352
+ : this.ui?.type === 'execute_actions' || this.node?.actions?.length > 1
1320
1353
  ? html`<temba-icon
1321
1354
  class="drag-handle ${this.isReadOnly() ? 'read-only-hidden' : ''}"
1322
1355
  name="sort"
@@ -1325,7 +1358,9 @@ export class CanvasNode extends RapidElement {
1325
1358
 
1326
1359
  <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
1327
1360
  <div
1328
- class="remove-button ${this.isReadOnly() ? 'read-only-hidden' : ''}"
1361
+ class="remove-button ${isTerminal || this.isReadOnly()
1362
+ ? 'read-only-hidden'
1363
+ : ''}"
1329
1364
  @click=${(e: MouseEvent) =>
1330
1365
  this.handleActionRemoveClick(e, action, index)}
1331
1366
  title="Remove action"
@@ -1438,6 +1473,7 @@ export class CanvasNode extends RapidElement {
1438
1473
  const displayAction = this.getLocalizedAction(action);
1439
1474
 
1440
1475
  if (config) {
1476
+ const hasIssues = this.issuesByAction?.has(action.uuid);
1441
1477
  const classes = [
1442
1478
  'action',
1443
1479
  'sortable',
@@ -1452,7 +1488,7 @@ export class CanvasNode extends RapidElement {
1452
1488
 
1453
1489
  return html`<div class="${classes}" id="action-${index}">
1454
1490
  <div
1455
- class="action-content"
1491
+ class="action-content ${hasIssues ? 'has-issues' : ''}"
1456
1492
  @mousedown=${(e: MouseEvent) =>
1457
1493
  !isDisabled && this.handleActionMouseDown(e, action)}
1458
1494
  @mouseup=${(e: MouseEvent) =>
@@ -1628,13 +1664,19 @@ export class CanvasNode extends RapidElement {
1628
1664
  const activeCount =
1629
1665
  (this.activity?.nodes && this.activity.nodes[this.node.uuid]) || 0;
1630
1666
 
1667
+ // Check for node-level issues or action-level issues on any action in this node
1668
+ const nodeHasIssues =
1669
+ this.issuesByNode?.has(this.node.uuid) ||
1670
+ this.node.actions?.some((a) => this.issuesByAction?.has(a.uuid));
1671
+
1631
1672
  return html`
1632
1673
  <div
1633
1674
  id="${this.node.uuid}"
1634
1675
  class=${getClasses({
1635
1676
  node: true,
1636
1677
  'execute-actions': this.ui.type === 'execute_actions',
1637
- 'non-localizable': isNodeDisabled
1678
+ 'non-localizable': isNodeDisabled,
1679
+ 'has-issues': nodeHasIssues
1638
1680
  })}
1639
1681
  style="left:${this.ui.position.left}px;top:${this.ui.position.top}px"
1640
1682
  >
@@ -1643,7 +1685,9 @@ export class CanvasNode extends RapidElement {
1643
1685
  ${activeCount.toLocaleString()}
1644
1686
  </div>`
1645
1687
  : ''}
1646
- ${nodeConfig && nodeConfig.type !== 'execute_actions'
1688
+ ${nodeConfig &&
1689
+ nodeConfig.type !== 'execute_actions' &&
1690
+ nodeConfig.type !== 'terminal'
1647
1691
  ? html`<div class="router" style="position: relative;">
1648
1692
  <div
1649
1693
  @mousedown=${(e: MouseEvent) => this.handleNodeMouseDown(e)}
@@ -1661,7 +1705,7 @@ export class CanvasNode extends RapidElement {
1661
1705
  : null}
1662
1706
  </div>
1663
1707
  </div>`
1664
- : this.node.actions.length > 0
1708
+ : this.node.actions?.length > 0
1665
1709
  ? this.ui.type === 'execute_actions'
1666
1710
  ? html`<temba-sortable-list
1667
1711
  dragHandle="drag-handle"
@@ -1691,6 +1735,8 @@ export class CanvasNode extends RapidElement {
1691
1735
  ${this.renderRouter(this.node.router, this.ui)}
1692
1736
  ${this.renderCategories(this.node)}
1693
1737
  </div>`
1738
+ : this.ui.type === 'terminal'
1739
+ ? ''
1694
1740
  : html`<div class="action-exits">
1695
1741
  ${repeat(
1696
1742
  this.node.exits,