@nyaruka/temba-components 0.129.3 → 0.129.4

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 (304) hide show
  1. package/.eslintrc.js +1 -0
  2. package/.github/workflows/build.yml +135 -3
  3. package/CHANGELOG.md +18 -0
  4. package/demo/data/flows/sample-flow.json +110 -87
  5. package/demo/field-config-demo.html +135 -0
  6. package/dist/temba-components.js +1257 -675
  7. package/dist/temba-components.js.map +1 -1
  8. package/docs/ActionEditor-Migration.md +118 -0
  9. package/out-tsc/src/events.js.map +1 -1
  10. package/out-tsc/src/flow/{EditorNode.js → CanvasNode.js} +345 -42
  11. package/out-tsc/src/flow/CanvasNode.js.map +1 -0
  12. package/out-tsc/src/flow/Editor.js +107 -3
  13. package/out-tsc/src/flow/Editor.js.map +1 -1
  14. package/out-tsc/src/flow/NodeEditor.js +1200 -0
  15. package/out-tsc/src/flow/NodeEditor.js.map +1 -0
  16. package/out-tsc/src/flow/Plumber.js +0 -6
  17. package/out-tsc/src/flow/Plumber.js.map +1 -1
  18. package/out-tsc/src/flow/actions/add_contact_groups.js +40 -0
  19. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -0
  20. package/out-tsc/src/flow/actions/add_contact_urn.js +16 -0
  21. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -0
  22. package/out-tsc/src/flow/actions/add_input_labels.js +11 -0
  23. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -0
  24. package/out-tsc/src/flow/actions/call_classifier.js +11 -0
  25. package/out-tsc/src/flow/actions/call_classifier.js.map +1 -0
  26. package/out-tsc/src/flow/actions/call_llm.js +11 -0
  27. package/out-tsc/src/flow/actions/call_llm.js.map +1 -0
  28. package/out-tsc/src/flow/actions/call_resthook.js +11 -0
  29. package/out-tsc/src/flow/actions/call_resthook.js.map +1 -0
  30. package/out-tsc/src/flow/actions/call_webhook.js +122 -0
  31. package/out-tsc/src/flow/actions/call_webhook.js.map +1 -0
  32. package/out-tsc/src/flow/actions/enter_flow.js +14 -0
  33. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
  34. package/out-tsc/src/flow/actions/open_ticket.js +11 -0
  35. package/out-tsc/src/flow/actions/open_ticket.js.map +1 -0
  36. package/out-tsc/src/flow/actions/play_audio.js +11 -0
  37. package/out-tsc/src/flow/actions/play_audio.js.map +1 -0
  38. package/out-tsc/src/flow/actions/remove_contact_groups.js +62 -0
  39. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -0
  40. package/out-tsc/src/flow/actions/request_optin.js +11 -0
  41. package/out-tsc/src/flow/actions/request_optin.js.map +1 -0
  42. package/out-tsc/src/flow/actions/say_msg.js +11 -0
  43. package/out-tsc/src/flow/actions/say_msg.js.map +1 -0
  44. package/out-tsc/src/flow/actions/send_broadcast.js +33 -0
  45. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -0
  46. package/out-tsc/src/flow/actions/send_email.js +56 -0
  47. package/out-tsc/src/flow/actions/send_email.js.map +1 -0
  48. package/out-tsc/src/flow/actions/send_msg.js +55 -0
  49. package/out-tsc/src/flow/actions/send_msg.js.map +1 -0
  50. package/out-tsc/src/flow/actions/set_contact_channel.js +12 -0
  51. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -0
  52. package/out-tsc/src/flow/actions/set_contact_field.js +12 -0
  53. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -0
  54. package/out-tsc/src/flow/actions/set_contact_language.js +10 -0
  55. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -0
  56. package/out-tsc/src/flow/actions/set_contact_name.js +10 -0
  57. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -0
  58. package/out-tsc/src/flow/actions/set_contact_status.js +10 -0
  59. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -0
  60. package/out-tsc/src/flow/actions/set_run_result.js +10 -0
  61. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -0
  62. package/out-tsc/src/flow/actions/split_by_expression_example.js +77 -0
  63. package/out-tsc/src/flow/actions/split_by_expression_example.js.map +1 -0
  64. package/out-tsc/src/flow/actions/start_session.js +11 -0
  65. package/out-tsc/src/flow/actions/start_session.js.map +1 -0
  66. package/out-tsc/src/flow/actions/transfer_airtime.js +11 -0
  67. package/out-tsc/src/flow/actions/transfer_airtime.js.map +1 -0
  68. package/out-tsc/src/flow/config.js +88 -193
  69. package/out-tsc/src/flow/config.js.map +1 -1
  70. package/out-tsc/src/flow/nodes/execute_actions.js +4 -0
  71. package/out-tsc/src/flow/nodes/execute_actions.js.map +1 -0
  72. package/out-tsc/src/flow/nodes/split_by_airtime.js +9 -0
  73. package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -0
  74. package/out-tsc/src/flow/nodes/split_by_contact_field.js +7 -0
  75. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -0
  76. package/out-tsc/src/flow/nodes/split_by_expression.js +7 -0
  77. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -0
  78. package/out-tsc/src/flow/nodes/split_by_groups.js +7 -0
  79. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -0
  80. package/out-tsc/src/flow/nodes/split_by_random.js +10 -0
  81. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -0
  82. package/out-tsc/src/flow/nodes/split_by_run_result.js +7 -0
  83. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -0
  84. package/out-tsc/src/flow/nodes/split_by_scheme.js +7 -0
  85. package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -0
  86. package/out-tsc/src/flow/nodes/split_by_subflow.js +9 -0
  87. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -0
  88. package/out-tsc/src/flow/nodes/split_by_webhook.js +18 -0
  89. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -0
  90. package/out-tsc/src/flow/nodes/wait_for_audio.js +7 -0
  91. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
  92. package/out-tsc/src/flow/nodes/wait_for_digits.js +7 -0
  93. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -0
  94. package/out-tsc/src/flow/nodes/wait_for_image.js +7 -0
  95. package/out-tsc/src/flow/nodes/wait_for_image.js.map +1 -0
  96. package/out-tsc/src/flow/nodes/wait_for_location.js +7 -0
  97. package/out-tsc/src/flow/nodes/wait_for_location.js.map +1 -0
  98. package/out-tsc/src/flow/nodes/wait_for_menu.js +7 -0
  99. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -0
  100. package/out-tsc/src/flow/nodes/wait_for_response.js +7 -0
  101. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -0
  102. package/out-tsc/src/flow/nodes/wait_for_video.js +7 -0
  103. package/out-tsc/src/flow/nodes/wait_for_video.js.map +1 -0
  104. package/out-tsc/src/flow/types.js +79 -0
  105. package/out-tsc/src/flow/types.js.map +1 -0
  106. package/out-tsc/src/flow/utils.js +65 -0
  107. package/out-tsc/src/flow/utils.js.map +1 -0
  108. package/out-tsc/src/form/ArrayEditor.js +199 -0
  109. package/out-tsc/src/form/ArrayEditor.js.map +1 -0
  110. package/out-tsc/src/form/BaseListEditor.js +128 -0
  111. package/out-tsc/src/form/BaseListEditor.js.map +1 -0
  112. package/out-tsc/src/form/Checkbox.js +17 -2
  113. package/out-tsc/src/form/Checkbox.js.map +1 -1
  114. package/out-tsc/src/form/Completion.js +6 -0
  115. package/out-tsc/src/form/Completion.js.map +1 -1
  116. package/out-tsc/src/form/FormField.js +110 -11
  117. package/out-tsc/src/form/FormField.js.map +1 -1
  118. package/out-tsc/src/form/KeyValueEditor.js +223 -0
  119. package/out-tsc/src/form/KeyValueEditor.js.map +1 -0
  120. package/out-tsc/src/form/select/Select.js +77 -32
  121. package/out-tsc/src/form/select/Select.js.map +1 -1
  122. package/out-tsc/src/interfaces.js +6 -0
  123. package/out-tsc/src/interfaces.js.map +1 -1
  124. package/out-tsc/src/live/ContactChat.js +2 -76
  125. package/out-tsc/src/live/ContactChat.js.map +1 -1
  126. package/out-tsc/temba-modules.js +9 -2
  127. package/out-tsc/temba-modules.js.map +1 -1
  128. package/out-tsc/test/ActionHelper.js +116 -0
  129. package/out-tsc/test/ActionHelper.js.map +1 -0
  130. package/out-tsc/test/actions/add_contact_groups.test.js +66 -0
  131. package/out-tsc/test/actions/add_contact_groups.test.js.map +1 -0
  132. package/out-tsc/test/actions/remove_contact_groups.test.js +226 -0
  133. package/out-tsc/test/actions/remove_contact_groups.test.js.map +1 -0
  134. package/out-tsc/test/actions/send_email.test.js +160 -0
  135. package/out-tsc/test/actions/send_email.test.js.map +1 -0
  136. package/out-tsc/test/actions/send_msg.test.js +95 -0
  137. package/out-tsc/test/actions/send_msg.test.js.map +1 -0
  138. package/out-tsc/test/temba-action-editing-integration.test.js +183 -0
  139. package/out-tsc/test/temba-action-editing-integration.test.js.map +1 -0
  140. package/out-tsc/test/temba-checkbox.test.js +1 -1
  141. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  142. package/out-tsc/test/temba-field-config.test.js +133 -0
  143. package/out-tsc/test/temba-field-config.test.js.map +1 -0
  144. package/out-tsc/test/temba-flow-editor-node.test.js +14 -14
  145. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  146. package/out-tsc/test/temba-node-editor.test.js +283 -0
  147. package/out-tsc/test/temba-node-editor.test.js.map +1 -0
  148. package/out-tsc/test/temba-select.test.js +85 -0
  149. package/out-tsc/test/temba-select.test.js.map +1 -1
  150. package/package.json +1 -1
  151. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  152. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  153. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  154. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  155. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  156. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  157. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  158. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  159. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  160. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  161. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  162. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  163. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  164. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  165. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  166. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  167. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  168. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  169. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  170. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  171. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  172. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  173. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  174. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  175. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  176. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  177. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  178. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  179. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  180. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  181. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  182. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  183. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  184. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  185. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  186. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  187. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  188. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  189. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  190. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  191. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  192. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  193. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  194. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  195. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  196. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  197. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  198. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  199. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  200. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  201. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  202. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  203. package/screenshots/truth/editor/router.png +0 -0
  204. package/screenshots/truth/editor/send_msg.png +0 -0
  205. package/screenshots/truth/editor/set_contact_language.png +0 -0
  206. package/screenshots/truth/editor/set_contact_name.png +0 -0
  207. package/screenshots/truth/editor/set_run_result.png +0 -0
  208. package/screenshots/truth/editor/wait.png +0 -0
  209. package/screenshots/truth/formfield/markdown-errors.png +0 -0
  210. package/screenshots/truth/formfield/plain-text-errors.png +0 -0
  211. package/screenshots/truth/formfield/widget-only-markdown-errors.png +0 -0
  212. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
  213. package/src/events.ts +1 -40
  214. package/src/flow/{EditorNode.ts → CanvasNode.ts} +424 -48
  215. package/src/flow/Editor.ts +140 -4
  216. package/src/flow/NodeEditor.ts +1443 -0
  217. package/src/flow/Plumber.ts +0 -9
  218. package/src/flow/actions/add_contact_groups.ts +42 -0
  219. package/src/flow/actions/add_contact_urn.ts +17 -0
  220. package/src/flow/actions/add_input_labels.ts +12 -0
  221. package/src/flow/actions/call_classifier.ts +12 -0
  222. package/src/flow/actions/call_llm.ts +12 -0
  223. package/src/flow/actions/call_resthook.ts +12 -0
  224. package/src/flow/actions/call_webhook.ts +133 -0
  225. package/src/flow/actions/enter_flow.ts +15 -0
  226. package/src/flow/actions/open_ticket.ts +12 -0
  227. package/src/flow/actions/play_audio.ts +12 -0
  228. package/src/flow/actions/remove_contact_groups.ts +66 -0
  229. package/src/flow/actions/request_optin.ts +12 -0
  230. package/src/flow/actions/say_msg.ts +12 -0
  231. package/src/flow/actions/send_broadcast.ts +35 -0
  232. package/src/flow/actions/send_email.ts +60 -0
  233. package/src/flow/actions/send_msg.ts +58 -0
  234. package/src/flow/actions/set_contact_channel.ts +13 -0
  235. package/src/flow/actions/set_contact_field.ts +13 -0
  236. package/src/flow/actions/set_contact_language.ts +11 -0
  237. package/src/flow/actions/set_contact_name.ts +11 -0
  238. package/src/flow/actions/set_contact_status.ts +11 -0
  239. package/src/flow/actions/set_run_result.ts +11 -0
  240. package/src/flow/actions/split_by_expression_example.ts +88 -0
  241. package/src/flow/actions/start_session.ts +12 -0
  242. package/src/flow/actions/transfer_airtime.ts +12 -0
  243. package/src/flow/config.ts +93 -232
  244. package/src/flow/nodes/execute_actions.ts +5 -0
  245. package/src/flow/nodes/split_by_airtime.ts +9 -0
  246. package/src/flow/nodes/split_by_contact_field.ts +7 -0
  247. package/src/flow/nodes/split_by_expression.ts +7 -0
  248. package/src/flow/nodes/split_by_groups.ts +7 -0
  249. package/src/flow/nodes/split_by_random.ts +10 -0
  250. package/src/flow/nodes/split_by_run_result.ts +7 -0
  251. package/src/flow/nodes/split_by_scheme.ts +7 -0
  252. package/src/flow/nodes/split_by_subflow.ts +9 -0
  253. package/src/flow/nodes/split_by_webhook.ts +19 -0
  254. package/src/flow/nodes/wait_for_audio.ts +7 -0
  255. package/src/flow/nodes/wait_for_digits.ts +7 -0
  256. package/src/flow/nodes/wait_for_image.ts +7 -0
  257. package/src/flow/nodes/wait_for_location.ts +7 -0
  258. package/src/flow/nodes/wait_for_menu.ts +7 -0
  259. package/src/flow/nodes/wait_for_response.ts +7 -0
  260. package/src/flow/nodes/wait_for_video.ts +7 -0
  261. package/src/flow/types.ts +352 -0
  262. package/src/flow/utils.ts +76 -0
  263. package/src/form/ArrayEditor.ts +240 -0
  264. package/src/form/BaseListEditor.ts +177 -0
  265. package/src/form/Checkbox.ts +22 -3
  266. package/src/form/Completion.ts +6 -0
  267. package/src/form/FormField.ts +115 -11
  268. package/src/form/KeyValueEditor.ts +251 -0
  269. package/src/form/select/Select.ts +89 -32
  270. package/src/interfaces.ts +7 -2
  271. package/src/live/ContactChat.ts +3 -97
  272. package/src/store/flow-definition.d.ts +6 -1
  273. package/static/api/contacts.json +30 -0
  274. package/static/api/groups.json +4 -426
  275. package/static/api/locations.json +24 -0
  276. package/static/api/media.json +5 -0
  277. package/static/api/optins.json +16 -0
  278. package/static/api/orgs.json +13 -0
  279. package/static/api/topics.json +21 -0
  280. package/static/api/users.json +26 -0
  281. package/static/css/temba-components.css +3 -6
  282. package/temba-modules.ts +9 -2
  283. package/test/ActionHelper.ts +142 -0
  284. package/test/actions/add_contact_groups.test.ts +89 -0
  285. package/test/actions/remove_contact_groups.test.ts +265 -0
  286. package/test/actions/send_email.test.ts +214 -0
  287. package/test/actions/send_msg.test.ts +130 -0
  288. package/test/temba-action-editing-integration.test.ts +240 -0
  289. package/test/temba-checkbox.test.ts +1 -1
  290. package/test/temba-field-config.test.ts +152 -0
  291. package/test/temba-flow-editor-node.test.ts +18 -18
  292. package/test/temba-node-editor.test.ts +353 -0
  293. package/test/temba-select.test.ts +127 -0
  294. package/test-assets/contacts/history.json +11 -33
  295. package/web-dev-server.config.mjs +34 -0
  296. package/.github/workflows/coverage.yml +0 -80
  297. package/demo/sticky-note-demo.html +0 -155
  298. package/out-tsc/src/flow/EditorNode.js.map +0 -1
  299. package/out-tsc/src/flow/render.js +0 -358
  300. package/out-tsc/src/flow/render.js.map +0 -1
  301. package/out-tsc/test/temba-flow-render.test.js +0 -794
  302. package/out-tsc/test/temba-flow-render.test.js.map +0 -1
  303. package/src/flow/render.ts +0 -443
  304. package/test/temba-flow-render.test.ts +0 -1003
@@ -232,10 +232,6 @@ export class Plumber {
232
232
  if (!this.jsPlumb) return;
233
233
 
234
234
  const inbound = this.jsPlumb.select({ target: nodeId });
235
-
236
- // keep track of our source ids that are connected, we'll need new endpoints
237
- const sourceIds = inbound.map((connection) => connection.sourceId);
238
-
239
235
  const exitIds =
240
236
  Array.from(
241
237
  document.getElementById(nodeId)?.querySelectorAll('.exit') || []
@@ -246,11 +242,6 @@ export class Plumber {
246
242
  inbound.deleteAll();
247
243
  this.jsPlumb.select({ source: exitIds }).deleteAll();
248
244
  this.jsPlumb.selectEndpoints({ source: exitIds }).deleteAll();
249
-
250
- // Recreate source endpoints for each source
251
- sourceIds.forEach((exitId) => {
252
- this.makeSource(exitId);
253
- });
254
245
  }
255
246
 
256
247
  public removeExitConnection(exitId: string) {
@@ -0,0 +1,42 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, AddToGroup } from '../../store/flow-definition';
4
+ import { renderNamedObjects } from '../utils';
5
+
6
+ export const add_contact_groups: ActionConfig = {
7
+ name: 'Add to Group',
8
+ color: COLORS.add,
9
+ render: (_node: Node, action: AddToGroup) => {
10
+ return html`<div>${renderNamedObjects(action.groups, 'group')}</div>`;
11
+ },
12
+
13
+ // Form-level transformations - default 1:1 mapping for this case
14
+ toFormData: (action: AddToGroup) => {
15
+ return {
16
+ groups: action.groups || [],
17
+ uuid: action.uuid
18
+ };
19
+ },
20
+ form: {
21
+ groups: {
22
+ type: 'select',
23
+ label: 'Groups',
24
+ helpText: 'Select the groups to add the contact to',
25
+ required: true,
26
+ options: [],
27
+ multi: true,
28
+ searchable: true,
29
+ endpoint: '/api/v2/groups.json',
30
+ valueKey: 'uuid',
31
+ nameKey: 'name',
32
+ placeholder: 'Search for groups...'
33
+ }
34
+ },
35
+ fromFormData: (formData: any): AddToGroup => {
36
+ return {
37
+ uuid: formData.uuid,
38
+ type: 'add_contact_groups',
39
+ groups: formData.groups || []
40
+ };
41
+ }
42
+ };
@@ -0,0 +1,17 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, AddContactUrn } from '../../store/flow-definition';
4
+ import { urnSchemeMap } from '../utils';
5
+
6
+ export const add_contact_urn: ActionConfig = {
7
+ name: 'Add Contact URN',
8
+ color: COLORS.update,
9
+ render: (_node: Node, action: AddContactUrn) => {
10
+ const friendlyScheme = urnSchemeMap[action.scheme] || action.scheme;
11
+ return html`<div
12
+ style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto;"
13
+ >
14
+ Add ${friendlyScheme} <b>${action.path}</b>
15
+ </div>`;
16
+ }
17
+ };
@@ -0,0 +1,12 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, AddInputLabels } from '../../store/flow-definition';
4
+ import { renderNamedObjects } from '../utils';
5
+
6
+ export const add_input_labels: ActionConfig = {
7
+ name: 'Add Input Labels',
8
+ color: COLORS.update,
9
+ render: (_node: Node, action: AddInputLabels) => {
10
+ return html`<div>${renderNamedObjects(action.labels, 'label')}</div>`;
11
+ }
12
+ };
@@ -0,0 +1,12 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, CallClassifier } from '../../store/flow-definition';
4
+
5
+ export const call_classifier: ActionConfig = {
6
+ name: 'Call Classifier',
7
+ color: COLORS.call,
8
+ render: (_node: Node, _action: CallClassifier) => {
9
+ // This will need to be implemented based on the actual render logic
10
+ return html`<div>Call Classifier</div>`;
11
+ }
12
+ };
@@ -0,0 +1,12 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, CallLLM } from '../../store/flow-definition';
4
+
5
+ export const call_llm: ActionConfig = {
6
+ name: 'Call AI',
7
+ color: COLORS.call,
8
+ render: (_node: Node, _action: CallLLM) => {
9
+ // This will need to be implemented based on the actual render logic
10
+ return html`<div>Call AI</div>`;
11
+ }
12
+ };
@@ -0,0 +1,12 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, CallResthook } from '../../store/flow-definition';
4
+
5
+ export const call_resthook: ActionConfig = {
6
+ name: 'Call Resthook',
7
+ color: COLORS.call,
8
+ render: (_node: Node, _action: CallResthook) => {
9
+ // This will need to be implemented based on the actual render logic
10
+ return html`<div>Call Resthook</div>`;
11
+ }
12
+ };
@@ -0,0 +1,133 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, CallWebhook } from '../../store/flow-definition';
4
+
5
+ export const call_webhook: ActionConfig = {
6
+ name: 'Call Webhook',
7
+ color: COLORS.call,
8
+ render: (_node: Node, action: CallWebhook) => {
9
+ return html`<div
10
+ style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto;"
11
+ >
12
+ ${action.url}
13
+ </div>`;
14
+ },
15
+ evaluated: ['url', 'headers', 'body'], // keep for backward compatibility
16
+ form: {
17
+ method: {
18
+ type: 'select',
19
+ required: true,
20
+ options: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH'],
21
+ maxWidth: '120px',
22
+ searchable: true
23
+ },
24
+ url: {
25
+ type: 'text',
26
+ required: true,
27
+ evaluated: true,
28
+ placeholder: 'https://example.com/webhook'
29
+ },
30
+ headers: {
31
+ type: 'key-value',
32
+ sortable: true,
33
+ keyPlaceholder: 'Header name',
34
+ valuePlaceholder: 'Header value',
35
+ minRows: 0
36
+ },
37
+ body: {
38
+ type: 'textarea',
39
+ evaluated: true,
40
+ placeholder: 'Request body content (JSON, XML, etc.)',
41
+ minHeight: 200,
42
+ dependsOn: ['method'],
43
+ computeValue: (
44
+ values: Record<string, any>,
45
+ currentValue: any,
46
+ originalValues?: Record<string, any>
47
+ ) => {
48
+ // Check if method is POST (handle both string and select object formats)
49
+ const method =
50
+ Array.isArray(values.method) && values.method.length > 0
51
+ ? values.method[0].value || values.method[0].name
52
+ : values.method;
53
+
54
+ const defaultTemplate = `@(json(object(
55
+ "contact", object(
56
+ "uuid", contact.uuid,
57
+ "name", contact.name,
58
+ "urn", contact.urn
59
+ ),
60
+ "flow", object(
61
+ "uuid", run.flow.uuid,
62
+ "name", run.flow.name
63
+ ),
64
+ "results", foreach_value(results, extract_object, "value", "category")
65
+ )))`;
66
+
67
+ if (method === 'POST') {
68
+ // For POST, provide the template if body is empty or was never set by user
69
+ if (!currentValue || currentValue.trim() === '') {
70
+ return defaultTemplate;
71
+ }
72
+ } else {
73
+ // For non-POST methods, clear the body if it was auto-generated or empty
74
+ // Check if the original body was empty (user never specified a body)
75
+ const originalBody = originalValues?.body || '';
76
+ const isOriginallyEmpty = !originalBody || originalBody.trim() === '';
77
+
78
+ // Clear if: originally empty, contains default template, or is currently empty
79
+ if (
80
+ isOriginallyEmpty ||
81
+ !currentValue ||
82
+ currentValue.trim() === '' ||
83
+ currentValue.trim() === defaultTemplate.trim()
84
+ ) {
85
+ return '';
86
+ }
87
+ }
88
+
89
+ return currentValue; // Keep existing value if user has customized it
90
+ }
91
+ }
92
+ },
93
+ layout: [
94
+ // Row with method and URL side by side
95
+ { type: 'row', items: ['method', 'url'] },
96
+ // Advanced group with nested layouts
97
+ {
98
+ type: 'group',
99
+ label: 'Headers',
100
+ items: ['headers'],
101
+ collapsible: true,
102
+ collapsed: true,
103
+ helpText: 'Configure authentication or custom headers'
104
+ },
105
+ {
106
+ type: 'group',
107
+ label: 'Body',
108
+ items: ['body'],
109
+ collapsible: true,
110
+ collapsed: true,
111
+ helpText: 'Configure the request payload'
112
+ }
113
+ ],
114
+ toFormData: (action: CallWebhook) => {
115
+ return {
116
+ uuid: action.uuid,
117
+ url: action.url || '',
118
+ method: [{ value: action.method, name: action.method }],
119
+ headers: action.headers || [],
120
+ body: action.body || ''
121
+ };
122
+ },
123
+ fromFormData: (data: Record<string, any>) => {
124
+ return {
125
+ uuid: data.uuid,
126
+ type: 'call_webhook',
127
+ url: data.url,
128
+ method: data.method[0].value,
129
+ headers: data.headers || [],
130
+ body: data.body || ''
131
+ } as CallWebhook;
132
+ }
133
+ };
@@ -0,0 +1,15 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, EnterFlow } from '../../store/flow-definition';
4
+
5
+ export const enter_flow: ActionConfig = {
6
+ name: 'Enter a Flow',
7
+ color: COLORS.execute,
8
+ render: (_node: Node, action: EnterFlow) => {
9
+ return html`<div
10
+ style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto;"
11
+ >
12
+ Enter <b>${action.flow.name}</b>
13
+ </div>`;
14
+ }
15
+ };
@@ -0,0 +1,12 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, OpenTicket } from '../../store/flow-definition';
4
+
5
+ export const open_ticket: ActionConfig = {
6
+ name: 'Open Ticket',
7
+ color: COLORS.create,
8
+ render: (_node: Node, _action: OpenTicket) => {
9
+ // This will need to be implemented based on the actual render logic
10
+ return html`<div>Open Ticket</div>`;
11
+ }
12
+ };
@@ -0,0 +1,12 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, PlayAudio } from '../../store/flow-definition';
4
+
5
+ export const play_audio: ActionConfig = {
6
+ name: 'Play Audio',
7
+ color: COLORS.send,
8
+ render: (_node: Node, _action: PlayAudio) => {
9
+ // This will need to be implemented based on the actual render logic
10
+ return html`<div>Play Audio</div>`;
11
+ }
12
+ };
@@ -0,0 +1,66 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS, ValidationResult } from '../types';
3
+ import { Node, RemoveFromGroup } from '../../store/flow-definition';
4
+ import { renderNamedObjects } from '../utils';
5
+
6
+ export const remove_contact_groups: ActionConfig = {
7
+ name: 'Remove from Group',
8
+ color: COLORS.remove,
9
+ render: (_node: Node, action: RemoveFromGroup) => {
10
+ if (action.all_groups) {
11
+ return html`<div>Remove from all groups</div>`;
12
+ }
13
+ return html`<div>${renderNamedObjects(action.groups, 'group')}</div>`;
14
+ },
15
+ toFormData: (action: RemoveFromGroup) => {
16
+ return {
17
+ uuid: action.uuid,
18
+ all_groups: action.all_groups || false,
19
+ groups: action.groups || []
20
+ };
21
+ },
22
+ form: {
23
+ groups: {
24
+ type: 'select',
25
+ label: 'Groups',
26
+ helpText: 'Select the groups to remove the contact from',
27
+ options: [],
28
+ multi: true,
29
+ searchable: true,
30
+ endpoint: '/api/v2/groups.json',
31
+ valueKey: 'uuid',
32
+ nameKey: 'name',
33
+ placeholder: 'Search for groups...',
34
+ conditions: {
35
+ visible: (formData) => !formData.all_groups
36
+ }
37
+ },
38
+ all_groups: {
39
+ type: 'checkbox',
40
+ label: 'Remove from All Groups',
41
+ helpText:
42
+ 'Check this to remove the contact from all groups instead of specific ones'
43
+ }
44
+ },
45
+ validate: (action: RemoveFromGroup): ValidationResult => {
46
+ const errors: { [key: string]: string } = {};
47
+
48
+ if (!action.all_groups && (!action.groups || action.groups.length === 0)) {
49
+ errors.groups =
50
+ 'At least one group must be selected or check "Remove from All Groups"';
51
+ }
52
+
53
+ return {
54
+ valid: Object.keys(errors).length === 0,
55
+ errors
56
+ };
57
+ },
58
+ fromFormData: (formData: any): RemoveFromGroup => {
59
+ return {
60
+ uuid: formData.uuid,
61
+ type: 'remove_contact_groups',
62
+ groups: formData.all_groups ? [] : formData.groups || [],
63
+ all_groups: formData.all_groups || false
64
+ };
65
+ }
66
+ };
@@ -0,0 +1,12 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, RequestOptin } from '../../store/flow-definition';
4
+
5
+ export const request_optin: ActionConfig = {
6
+ name: 'Request Opt-in',
7
+ color: COLORS.send,
8
+ render: (_node: Node, _action: RequestOptin) => {
9
+ // This will need to be implemented based on the actual render logic
10
+ return html`<div>Request Opt-in</div>`;
11
+ }
12
+ };
@@ -0,0 +1,12 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, SayMsg } from '../../store/flow-definition';
4
+
5
+ export const say_msg: ActionConfig = {
6
+ name: 'Say Message',
7
+ color: COLORS.send,
8
+ render: (_node: Node, _action: SayMsg) => {
9
+ // This will need to be implemented based on the actual render logic
10
+ return html`<div>Say Message</div>`;
11
+ }
12
+ };
@@ -0,0 +1,35 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, SendBroadcast } from '../../store/flow-definition';
4
+ import { renderNamedObjects } from '../utils';
5
+
6
+ export const send_broadcast: ActionConfig = {
7
+ name: 'Send Broadcast',
8
+ color: COLORS.broadcast,
9
+ render: (_node: Node, action: SendBroadcast) => {
10
+ const hasGroups = action.groups && action.groups.length > 0;
11
+ const hasContacts = action.contacts && action.contacts.length > 0;
12
+
13
+ return html`<div>
14
+ <div
15
+ style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; margin-bottom: 0.5em"
16
+ >
17
+ ${action.text}
18
+ </div>
19
+ ${hasGroups
20
+ ? html`<div style="margin-bottom: 0.25em">
21
+ <div style="font-weight: bold; margin-bottom: 0.25em">Groups:</div>
22
+ ${renderNamedObjects(action.groups, 'group')}
23
+ </div>`
24
+ : null}
25
+ ${hasContacts
26
+ ? html`<div>
27
+ <div style="font-weight: bold; margin-bottom: 0.25em">
28
+ Contacts:
29
+ </div>
30
+ ${renderNamedObjects(action.contacts, 'contact')}
31
+ </div>`
32
+ : null}
33
+ </div>`;
34
+ }
35
+ };
@@ -0,0 +1,60 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS, ValidationResult } from '../types';
3
+ import { Node, SendEmail } from '../../store/flow-definition';
4
+ import { renderStringList } from '../utils';
5
+ import { Icon } from '../../Icons';
6
+
7
+ export const send_email: ActionConfig = {
8
+ name: 'Send Email',
9
+ color: COLORS.send,
10
+ render: (_node: Node, action: SendEmail) => {
11
+ return html`<div>
12
+ <div>${renderStringList(action.addresses, Icon.email)}</div>
13
+ <div style="margin-top: 0.5em">
14
+ <div
15
+ style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto;"
16
+ >
17
+ ${action.subject}
18
+ </div>
19
+ </div>
20
+ </div>`;
21
+ },
22
+
23
+ form: {
24
+ addresses: {
25
+ type: 'select',
26
+ label: 'Recipients',
27
+ options: [],
28
+ multi: true,
29
+ searchable: true,
30
+ placeholder: 'Search for contacts...',
31
+ emails: true
32
+ },
33
+ subject: {
34
+ type: 'text',
35
+ label: 'Subject',
36
+ required: true,
37
+ placeholder: 'Enter email subject',
38
+ maxLength: 255
39
+ },
40
+ body: {
41
+ type: 'textarea',
42
+ required: true,
43
+ evaluated: true,
44
+ rows: 4,
45
+ minHeight: 75
46
+ }
47
+ },
48
+ validate: (action: SendEmail): ValidationResult => {
49
+ const errors: { [key: string]: string } = {};
50
+
51
+ if (!action.addresses || action.addresses.length === 0) {
52
+ errors.addresses = 'At least one recipient email address is required';
53
+ }
54
+
55
+ return {
56
+ valid: Object.keys(errors).length === 0,
57
+ errors
58
+ };
59
+ }
60
+ };
@@ -0,0 +1,58 @@
1
+ import { html } from 'lit-html';
2
+ import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
3
+ import { ActionConfig, COLORS, ValidationResult } from '../types';
4
+ import { Node, SendMsg } from '../../store/flow-definition';
5
+
6
+ export const send_msg: ActionConfig = {
7
+ name: 'Send Message',
8
+ color: COLORS.send,
9
+ render: (_node: Node, action: SendMsg) => {
10
+ const text = action.text.replace(/\n/g, '<br>');
11
+ return html`
12
+ ${unsafeHTML(text)}
13
+ ${action.quick_replies?.length > 0
14
+ ? html`<div class="quick-replies">
15
+ ${action.quick_replies.map((reply) => {
16
+ return html`<div class="quick-reply">${reply}</div>`;
17
+ })}
18
+ </div>`
19
+ : null}
20
+ `;
21
+ },
22
+ form: {
23
+ text: {
24
+ type: 'textarea',
25
+ label: 'Message Text',
26
+ helpText:
27
+ 'Enter the message to send. You can use expressions like @contact.name',
28
+ required: true,
29
+ evaluated: true,
30
+ rows: 5,
31
+ minHeight: 75
32
+ },
33
+ quick_replies: {
34
+ type: 'select',
35
+ label: 'Quick Replies',
36
+ helpText: 'Add quick reply options for this message',
37
+ options: [],
38
+ multi: true,
39
+ tags: true,
40
+ searchable: true,
41
+ placeholder: 'Add quick replies...',
42
+ maxItems: 10,
43
+ evaluated: true
44
+ }
45
+ },
46
+ validate: (action: SendMsg): ValidationResult => {
47
+ const errors: { [key: string]: string } = {};
48
+
49
+ if (!action.text || action.text.trim() === '') {
50
+ errors.text = 'Message text is required';
51
+ }
52
+
53
+ return {
54
+ valid: Object.keys(errors).length === 0,
55
+ errors
56
+ };
57
+ }
58
+ };
@@ -0,0 +1,13 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, SetContactChannel } from '../../store/flow-definition';
4
+
5
+ export const set_contact_channel: ActionConfig = {
6
+ name: 'Update Contact Channel',
7
+ color: COLORS.update,
8
+ render: (_node: Node, action: SetContactChannel) => {
9
+ return html`<div>
10
+ Set contact channel to <b>${action.channel.name}</b>
11
+ </div>`;
12
+ }
13
+ };
@@ -0,0 +1,13 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, SetContactField } from '../../store/flow-definition';
4
+
5
+ export const set_contact_field: ActionConfig = {
6
+ name: 'Update Contact Field',
7
+ color: COLORS.update,
8
+ render: (_node: Node, action: SetContactField) => {
9
+ return html`<div>
10
+ Set <b>${action.field.name}</b> to <b>${action.value}</b>
11
+ </div>`;
12
+ }
13
+ };
@@ -0,0 +1,11 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, SetContactLanguage } from '../../store/flow-definition';
4
+
5
+ export const set_contact_language: ActionConfig = {
6
+ name: 'Update Contact Language',
7
+ color: COLORS.update,
8
+ render: (_node: Node, action: SetContactLanguage) => {
9
+ return html`<div>Set contact language to <b>${action.language}</b></div>`;
10
+ }
11
+ };
@@ -0,0 +1,11 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, SetContactName } from '../../store/flow-definition';
4
+
5
+ export const set_contact_name: ActionConfig = {
6
+ name: 'Update Contact',
7
+ color: COLORS.update,
8
+ render: (_node: Node, action: SetContactName) => {
9
+ return html`<div>Set contact name to <b>${action.name}</b></div>`;
10
+ }
11
+ };
@@ -0,0 +1,11 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, SetContactStatus } from '../../store/flow-definition';
4
+
5
+ export const set_contact_status: ActionConfig = {
6
+ name: 'Update Contact Status',
7
+ color: COLORS.update,
8
+ render: (_node: Node, action: SetContactStatus) => {
9
+ return html`<div>Set contact status to <b>${action.status}</b></div>`;
10
+ }
11
+ };
@@ -0,0 +1,11 @@
1
+ import { html } from 'lit-html';
2
+ import { ActionConfig, COLORS } from '../types';
3
+ import { Node, SetRunResult } from '../../store/flow-definition';
4
+
5
+ export const set_run_result: ActionConfig = {
6
+ name: 'Save Flow Result',
7
+ color: COLORS.save,
8
+ render: (_node: Node, action: SetRunResult) => {
9
+ return html`<div>Save ${action.value} as <b>${action.name}</b></div>`;
10
+ }
11
+ };