@nyaruka/temba-components 0.129.10 → 0.130.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 (316) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/demo/components/flow/example.html +5 -1
  3. package/demo/data/flows/sample-flow.json +217 -113
  4. package/dist/temba-components.js +310 -356
  5. package/dist/temba-components.js.map +1 -1
  6. package/out-tsc/src/events.js.map +1 -1
  7. package/out-tsc/src/flow/CanvasNode.js +3 -35
  8. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  9. package/out-tsc/src/flow/Editor.js +9 -6
  10. package/out-tsc/src/flow/Editor.js.map +1 -1
  11. package/out-tsc/src/flow/NodeEditor.js +44 -11
  12. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  13. package/out-tsc/src/flow/actions/add_contact_groups.js +1 -1
  14. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  15. package/out-tsc/src/flow/actions/add_contact_urn.js +1 -1
  16. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  17. package/out-tsc/src/flow/actions/add_input_labels.js +1 -1
  18. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  19. package/out-tsc/src/flow/actions/remove_contact_groups.js +1 -1
  20. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  21. package/out-tsc/src/flow/actions/send_email.js +9 -0
  22. package/out-tsc/src/flow/actions/send_email.js.map +1 -1
  23. package/out-tsc/src/flow/actions/send_msg.js +7 -8
  24. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  25. package/out-tsc/src/flow/actions/set_contact_channel.js +25 -4
  26. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  27. package/out-tsc/src/flow/actions/set_contact_field.js +51 -1
  28. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  29. package/out-tsc/src/flow/actions/set_contact_language.js +70 -2
  30. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  31. package/out-tsc/src/flow/actions/set_contact_name.js +27 -2
  32. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  33. package/out-tsc/src/flow/actions/set_contact_status.js +32 -2
  34. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  35. package/out-tsc/src/flow/actions/set_run_result.js +13 -11
  36. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  37. package/out-tsc/src/flow/actions/split_by_expression_example.js +4 -4
  38. package/out-tsc/src/flow/actions/split_by_expression_example.js.map +1 -1
  39. package/out-tsc/src/flow/config.js +2 -8
  40. package/out-tsc/src/flow/config.js.map +1 -1
  41. package/out-tsc/src/flow/forms/index.js +2 -0
  42. package/out-tsc/src/flow/forms/index.js.map +1 -0
  43. package/out-tsc/src/flow/nodes/split_by_llm.js +101 -0
  44. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -0
  45. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +4 -89
  46. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  47. package/out-tsc/src/flow/nodes/split_by_random.js +117 -0
  48. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  49. package/out-tsc/src/flow/nodes/split_by_subflow.js +123 -3
  50. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  51. package/out-tsc/src/flow/nodes/split_by_ticket.js +114 -13
  52. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
  53. package/out-tsc/src/flow/nodes/split_by_webhook.js +158 -12
  54. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  55. package/out-tsc/src/flow/types.js.map +1 -1
  56. package/out-tsc/src/form/ArrayEditor.js +9 -25
  57. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  58. package/out-tsc/src/form/FieldRenderer.js +6 -64
  59. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  60. package/out-tsc/src/form/select/Select.js +29 -58
  61. package/out-tsc/src/form/select/Select.js.map +1 -1
  62. package/out-tsc/src/live/ContactChat.js +48 -66
  63. package/out-tsc/src/live/ContactChat.js.map +1 -1
  64. package/out-tsc/src/utils.js +118 -0
  65. package/out-tsc/src/utils.js.map +1 -1
  66. package/out-tsc/test/nodes/split_by_llm.test.js +174 -0
  67. package/out-tsc/test/nodes/split_by_llm.test.js.map +1 -0
  68. package/out-tsc/test/nodes/split_by_random.test.js +0 -6
  69. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  70. package/out-tsc/test/temba-field-renderer.test.js +6 -3
  71. package/out-tsc/test/temba-field-renderer.test.js.map +1 -1
  72. package/out-tsc/test/utils.test.js +18 -0
  73. package/out-tsc/test/utils.test.js.map +1 -1
  74. package/package.json +1 -1
  75. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  76. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  77. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  78. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  79. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  80. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  81. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  82. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  83. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  84. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  85. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  86. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  87. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  88. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  89. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  90. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  91. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  92. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  93. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  94. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  95. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  96. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  97. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  98. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  99. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  100. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  101. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  102. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  103. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  104. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  105. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  106. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  107. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  108. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  109. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  110. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  111. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  112. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  113. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  114. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  115. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  116. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  117. package/screenshots/truth/checkbox/checkbox-no-label-no-background-hover.png +0 -0
  118. package/screenshots/truth/checkbox/checkbox-whitespace-label-no-background-hover.png +0 -0
  119. package/screenshots/truth/checkbox/checkbox-with-help-text.png +0 -0
  120. package/screenshots/truth/checkbox/checked.png +0 -0
  121. package/screenshots/truth/checkbox/default.png +0 -0
  122. package/screenshots/truth/colorpicker/default.png +0 -0
  123. package/screenshots/truth/colorpicker/focused.png +0 -0
  124. package/screenshots/truth/colorpicker/initialized.png +0 -0
  125. package/screenshots/truth/colorpicker/selected.png +0 -0
  126. package/screenshots/truth/compose/attachments-tab.png +0 -0
  127. package/screenshots/truth/compose/attachments-with-files.png +0 -0
  128. package/screenshots/truth/compose/intial-text.png +0 -0
  129. package/screenshots/truth/compose/no-counter.png +0 -0
  130. package/screenshots/truth/compose/wraps-text-and-spaces.png +0 -0
  131. package/screenshots/truth/compose/wraps-text-and-url.png +0 -0
  132. package/screenshots/truth/compose/wraps-text-no-spaces.png +0 -0
  133. package/screenshots/truth/contacts/chat-failure.png +0 -0
  134. package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
  135. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  136. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  137. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  138. package/screenshots/truth/counter/summary.png +0 -0
  139. package/screenshots/truth/counter/text.png +0 -0
  140. package/screenshots/truth/counter/unicode-variables.png +0 -0
  141. package/screenshots/truth/counter/unicode.png +0 -0
  142. package/screenshots/truth/counter/variable.png +0 -0
  143. package/screenshots/truth/datepicker/date-truncated-time.png +0 -0
  144. package/screenshots/truth/datepicker/date.png +0 -0
  145. package/screenshots/truth/datepicker/initial-timezone.png +0 -0
  146. package/screenshots/truth/datepicker/range-picker-editing-start.png +0 -0
  147. package/screenshots/truth/datepicker/updated-keyboard-date.png +0 -0
  148. package/screenshots/truth/dialog/focused.png +0 -0
  149. package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
  150. package/screenshots/truth/editor/router.png +0 -0
  151. package/screenshots/truth/editor/send_msg.png +0 -0
  152. package/screenshots/truth/editor/set_contact_language.png +0 -0
  153. package/screenshots/truth/editor/set_contact_name.png +0 -0
  154. package/screenshots/truth/editor/set_run_result.png +0 -0
  155. package/screenshots/truth/editor/wait.png +0 -0
  156. package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
  157. package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
  158. package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
  159. package/screenshots/truth/field-renderer/context-comparison.png +0 -0
  160. package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
  161. package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
  162. package/screenshots/truth/field-renderer/select-multi.png +0 -0
  163. package/screenshots/truth/field-renderer/select-no-label.png +0 -0
  164. package/screenshots/truth/field-renderer/select-with-label.png +0 -0
  165. package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
  166. package/screenshots/truth/field-renderer/text-no-label.png +0 -0
  167. package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
  168. package/screenshots/truth/field-renderer/text-with-label.png +0 -0
  169. package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
  170. package/screenshots/truth/field-renderer/textarea-with-label.png +0 -0
  171. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
  172. package/screenshots/truth/list/fields-dragging.png +0 -0
  173. package/screenshots/truth/list/fields-filtered.png +0 -0
  174. package/screenshots/truth/list/fields-hovered.png +0 -0
  175. package/screenshots/truth/list/fields.png +0 -0
  176. package/screenshots/truth/list/items-selected.png +0 -0
  177. package/screenshots/truth/list/items-updated.png +0 -0
  178. package/screenshots/truth/list/items.png +0 -0
  179. package/screenshots/truth/menu/menu-focused-with items.png +0 -0
  180. package/screenshots/truth/menu/menu-refresh-1.png +0 -0
  181. package/screenshots/truth/menu/menu-refresh-2.png +0 -0
  182. package/screenshots/truth/menu/menu-submenu.png +0 -0
  183. package/screenshots/truth/menu/menu-tasks-nextup.png +0 -0
  184. package/screenshots/truth/menu/menu-tasks.png +0 -0
  185. package/screenshots/truth/message-editor/autogrow-initial-content.png +0 -0
  186. package/screenshots/truth/message-editor/default.png +0 -0
  187. package/screenshots/truth/message-editor/drag-highlight.png +0 -0
  188. package/screenshots/truth/message-editor/filtered-attachments.png +0 -0
  189. package/screenshots/truth/message-editor/with-completion.png +0 -0
  190. package/screenshots/truth/message-editor/with-properties.png +0 -0
  191. package/screenshots/truth/modax/form.png +0 -0
  192. package/screenshots/truth/modax/simple.png +0 -0
  193. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  194. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  195. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  196. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  197. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  198. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  199. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  200. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  201. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  202. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  203. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  204. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  205. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  206. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  207. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  208. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  209. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  210. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  211. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  212. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  213. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  214. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  215. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  216. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  217. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  218. package/screenshots/truth/omnibox/selected.png +0 -0
  219. package/screenshots/truth/options/block.png +0 -0
  220. package/screenshots/truth/run-list/basic.png +0 -0
  221. package/screenshots/truth/select/disabled-multi-selection.png +0 -0
  222. package/screenshots/truth/select/disabled-selection.png +0 -0
  223. package/screenshots/truth/select/disabled.png +0 -0
  224. package/screenshots/truth/select/embedded.png +0 -0
  225. package/screenshots/truth/select/empty-options.png +0 -0
  226. package/screenshots/truth/select/expression-selected.png +0 -0
  227. package/screenshots/truth/select/expressions.png +0 -0
  228. package/screenshots/truth/select/functions.png +0 -0
  229. package/screenshots/truth/select/local-options.png +0 -0
  230. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  231. package/screenshots/truth/select/multiple-initial-values.png +0 -0
  232. package/screenshots/truth/select/remote-options.png +0 -0
  233. package/screenshots/truth/select/search-enabled.png +0 -0
  234. package/screenshots/truth/select/search-multi-no-matches.png +0 -0
  235. package/screenshots/truth/select/search-selected-focus.png +0 -0
  236. package/screenshots/truth/select/search-selected.png +0 -0
  237. package/screenshots/truth/select/search-with-selected.png +0 -0
  238. package/screenshots/truth/select/searching.png +0 -0
  239. package/screenshots/truth/select/selected-multi-maxitems-reached.png +0 -0
  240. package/screenshots/truth/select/selected-multi.png +0 -0
  241. package/screenshots/truth/select/selected-single.png +0 -0
  242. package/screenshots/truth/select/selection-clearable.png +0 -0
  243. package/screenshots/truth/select/static-initial-value.png +0 -0
  244. package/screenshots/truth/select/static-initial-via-selected.png +0 -0
  245. package/screenshots/truth/select/truncated-selection.png +0 -0
  246. package/screenshots/truth/select/with-placeholder.png +0 -0
  247. package/screenshots/truth/select/without-placeholder.png +0 -0
  248. package/screenshots/truth/slider/update-slider-on-circle-dragged.png +0 -0
  249. package/screenshots/truth/templates/default.png +0 -0
  250. package/screenshots/truth/templates/unapproved.png +0 -0
  251. package/screenshots/truth/textinput/autogrow-initial.png +0 -0
  252. package/screenshots/truth/textinput/input-disabled.png +0 -0
  253. package/screenshots/truth/textinput/input-focused.png +0 -0
  254. package/screenshots/truth/textinput/input-form.png +0 -0
  255. package/screenshots/truth/textinput/input-inserted.png +0 -0
  256. package/screenshots/truth/textinput/input-placeholder.png +0 -0
  257. package/screenshots/truth/textinput/input-updated.png +0 -0
  258. package/screenshots/truth/textinput/input.png +0 -0
  259. package/screenshots/truth/textinput/textarea-focused.png +0 -0
  260. package/screenshots/truth/textinput/textarea.png +0 -0
  261. package/src/events.ts +4 -2
  262. package/src/flow/CanvasNode.ts +2 -39
  263. package/src/flow/Editor.ts +6 -3
  264. package/src/flow/NodeEditor.ts +54 -13
  265. package/src/flow/actions/add_contact_groups.ts +1 -1
  266. package/src/flow/actions/add_contact_urn.ts +1 -1
  267. package/src/flow/actions/add_input_labels.ts +1 -1
  268. package/src/flow/actions/remove_contact_groups.ts +1 -1
  269. package/src/flow/actions/send_email.ts +11 -1
  270. package/src/flow/actions/send_msg.ts +20 -11
  271. package/src/flow/actions/set_contact_channel.ts +28 -5
  272. package/src/flow/actions/set_contact_field.ts +56 -2
  273. package/src/flow/actions/set_contact_language.ts +74 -3
  274. package/src/flow/actions/set_contact_name.ts +31 -3
  275. package/src/flow/actions/set_contact_status.ts +36 -3
  276. package/src/flow/actions/set_run_result.ts +13 -15
  277. package/src/flow/actions/split_by_expression_example.ts +4 -4
  278. package/src/flow/config.ts +2 -8
  279. package/src/flow/forms/index.ts +1 -0
  280. package/src/flow/nodes/split_by_llm.ts +119 -0
  281. package/src/flow/nodes/split_by_llm_categorize.ts +13 -116
  282. package/src/flow/nodes/split_by_random.ts +148 -0
  283. package/src/flow/nodes/split_by_subflow.ts +153 -3
  284. package/src/flow/nodes/split_by_ticket.ts +134 -12
  285. package/src/flow/nodes/split_by_webhook.ts +185 -12
  286. package/src/flow/types.ts +2 -1
  287. package/src/form/ArrayEditor.ts +6 -20
  288. package/src/form/FieldRenderer.ts +6 -65
  289. package/src/form/select/Select.ts +34 -66
  290. package/src/live/ContactChat.ts +56 -58
  291. package/src/store/flow-definition.d.ts +8 -2
  292. package/src/utils.ts +196 -0
  293. package/static/api/fields.json +93 -1208
  294. package/static/api/workspace.json +23 -0
  295. package/test/nodes/split_by_llm.test.ts +232 -0
  296. package/test/nodes/split_by_random.test.ts +0 -7
  297. package/test/temba-field-renderer.test.ts +26 -13
  298. package/test/utils.test.ts +20 -0
  299. package/test-assets/style.css +36 -234
  300. package/web-dev-server.config.mjs +28 -0
  301. package/web-test-runner.config.mjs +38 -1
  302. package/out-tsc/src/flow/actions/call_llm.js +0 -64
  303. package/out-tsc/src/flow/actions/call_llm.js.map +0 -1
  304. package/out-tsc/src/flow/actions/call_webhook.js +0 -131
  305. package/out-tsc/src/flow/actions/call_webhook.js.map +0 -1
  306. package/out-tsc/src/flow/actions/enter_flow.js +0 -14
  307. package/out-tsc/src/flow/actions/enter_flow.js.map +0 -1
  308. package/out-tsc/src/flow/actions/open_ticket.js +0 -73
  309. package/out-tsc/src/flow/actions/open_ticket.js.map +0 -1
  310. package/out-tsc/test/actions/call_llm.test.js +0 -103
  311. package/out-tsc/test/actions/call_llm.test.js.map +0 -1
  312. package/src/flow/actions/call_llm.ts +0 -66
  313. package/src/flow/actions/call_webhook.ts +0 -143
  314. package/src/flow/actions/enter_flow.ts +0 -15
  315. package/src/flow/actions/open_ticket.ts +0 -83
  316. package/test/actions/call_llm.test.ts +0 -137
@@ -1,11 +1,82 @@
1
1
  import { html } from 'lit-html';
2
- import { ActionConfig, COLORS } from '../types';
2
+ import { ActionConfig, COLORS, ValidationResult } from '../types';
3
3
  import { Node, SetContactLanguage } from '../../store/flow-definition';
4
+ import { getStore } from '../../store/Store';
4
5
 
5
6
  export const set_contact_language: ActionConfig = {
6
- name: 'Update Contact Language',
7
+ name: 'Update Language',
7
8
  color: COLORS.update,
8
9
  render: (_node: Node, action: SetContactLanguage) => {
9
- return html`<div>Set contact language to <b>${action.language}</b></div>`;
10
+ const languageNames = new Intl.DisplayNames(['en'], {
11
+ type: 'language'
12
+ });
13
+ return html`<div>Set to <b>${languageNames.of(action.language)}</b></div>`;
14
+ },
15
+ form: {
16
+ language: {
17
+ type: 'select',
18
+ label: 'Language',
19
+ required: true,
20
+ searchable: true,
21
+ clearable: false,
22
+ valueKey: 'value',
23
+ nameKey: 'name',
24
+ helpText: 'Select the language to set for the contact',
25
+ getDynamicOptions: () => {
26
+ const store = getStore();
27
+ const workspace = store?.getState().workspace;
28
+ if (workspace?.languages && Array.isArray(workspace.languages)) {
29
+ const languageNames = new Intl.DisplayNames(['en'], {
30
+ type: 'language'
31
+ });
32
+ return workspace.languages.map((languageCode: string) => ({
33
+ value: languageCode,
34
+ name: languageNames.of(languageCode) || languageCode
35
+ }));
36
+ }
37
+ return [];
38
+ }
39
+ }
40
+ },
41
+ toFormData: (action: SetContactLanguage) => {
42
+ // Convert the language code back to the option object format expected by the form
43
+ if (action.language) {
44
+ const languageNames = new Intl.DisplayNames(['en'], {
45
+ type: 'language'
46
+ });
47
+ return {
48
+ language: [
49
+ {
50
+ value: action.language,
51
+ name: languageNames.of(action.language) || action.language
52
+ }
53
+ ],
54
+ uuid: action.uuid
55
+ };
56
+ }
57
+ return {
58
+ language: null,
59
+ uuid: action.uuid
60
+ };
61
+ },
62
+ fromFormData: (formData: any): SetContactLanguage => {
63
+ return {
64
+ uuid: formData.uuid,
65
+ type: 'set_contact_language',
66
+ language: formData.language[0].value
67
+ };
68
+ },
69
+
70
+ validate: (formData: any): ValidationResult => {
71
+ const errors: { [key: string]: string } = {};
72
+
73
+ if (!formData.language) {
74
+ errors.language = 'Language is required';
75
+ }
76
+
77
+ return {
78
+ valid: Object.keys(errors).length === 0,
79
+ errors
80
+ };
10
81
  }
11
82
  };
@@ -1,11 +1,39 @@
1
1
  import { html } from 'lit-html';
2
- import { ActionConfig, COLORS } from '../types';
2
+ import { ActionConfig, COLORS, ValidationResult } from '../types';
3
3
  import { Node, SetContactName } from '../../store/flow-definition';
4
4
 
5
5
  export const set_contact_name: ActionConfig = {
6
- name: 'Update Contact',
6
+ name: 'Update Name',
7
7
  color: COLORS.update,
8
8
  render: (_node: Node, action: SetContactName) => {
9
- return html`<div>Set contact name to <b>${action.name}</b></div>`;
9
+ return html`<div>Set to <b>${action.name}</b></div>`;
10
+ },
11
+ form: {
12
+ name: {
13
+ type: 'text',
14
+ label: 'Name',
15
+ placeholder: 'Enter contact name...',
16
+ required: true,
17
+ evaluated: true,
18
+ helpText:
19
+ 'The new name for the contact. You can use expressions like @contact.name'
20
+ }
21
+ },
22
+ validate: (formData: SetContactName): ValidationResult => {
23
+ const errors: { [key: string]: string } = {};
24
+
25
+ if (!formData.name || formData.name.trim() === '') {
26
+ errors.name = 'Name is required';
27
+ }
28
+
29
+ return {
30
+ valid: Object.keys(errors).length === 0,
31
+ errors
32
+ };
33
+ },
34
+ sanitize: (formData: SetContactName): void => {
35
+ if (formData.name && typeof formData.name === 'string') {
36
+ formData.name = formData.name.trim();
37
+ }
10
38
  }
11
39
  };
@@ -1,11 +1,44 @@
1
1
  import { html } from 'lit-html';
2
- import { ActionConfig, COLORS } from '../types';
2
+ import { ActionConfig, COLORS, ValidationResult } from '../types';
3
3
  import { Node, SetContactStatus } from '../../store/flow-definition';
4
+ import { titleCase } from '../../utils';
4
5
 
5
6
  export const set_contact_status: ActionConfig = {
6
- name: 'Update Contact Status',
7
+ name: 'Update Status',
7
8
  color: COLORS.update,
8
9
  render: (_node: Node, action: SetContactStatus) => {
9
- return html`<div>Set contact status to <b>${action.status}</b></div>`;
10
+ return html`<div>Set to <b>${titleCase(action.status)}</b></div>`;
11
+ },
12
+ form: {
13
+ status: {
14
+ type: 'select',
15
+ label: 'Status',
16
+ required: true,
17
+ searchable: false,
18
+ clearable: false,
19
+ options: [
20
+ { value: 'active', name: 'Active' },
21
+ { value: 'archived', name: 'Archived' },
22
+ { value: 'stopped', name: 'Stopped' },
23
+ { value: 'blocked', name: 'Blocked' }
24
+ ],
25
+ helpText: 'Select the status to set for the contact'
26
+ }
27
+ },
28
+ validate: (formData: SetContactStatus): ValidationResult => {
29
+ const errors: { [key: string]: string } = {};
30
+
31
+ if (!formData.status) {
32
+ errors.status = 'Status is required';
33
+ } else if (
34
+ !['active', 'archived', 'stopped', 'blocked'].includes(formData.status)
35
+ ) {
36
+ errors.status = 'Invalid status selected';
37
+ }
38
+
39
+ return {
40
+ valid: Object.keys(errors).length === 0,
41
+ errors
42
+ };
10
43
  }
11
44
  };
@@ -7,7 +7,9 @@ export const set_run_result: ActionConfig = {
7
7
  name: 'Save Flow Result',
8
8
  color: COLORS.save,
9
9
  render: (_node: Node, action: SetRunResult) => {
10
- return html`<div>Save ${action.value} as <b>${action.name}</b></div>`;
10
+ return html`<div>
11
+ Save <b>${action.value}</b> as <b>${action.name}</b>
12
+ </div>`;
11
13
  },
12
14
  form: {
13
15
  name: {
@@ -28,7 +30,15 @@ export const set_run_result: ActionConfig = {
28
30
  },
29
31
  searchable: true,
30
32
  clearable: false,
31
- options: []
33
+ getDynamicOptions: () => {
34
+ const store = getStore();
35
+ return store
36
+ ? store
37
+ .getState()
38
+ .getFlowResults()
39
+ .map((r) => ({ value: r.name, name: r.name }))
40
+ : [];
41
+ }
32
42
  },
33
43
  value: {
34
44
  type: 'text',
@@ -48,21 +58,9 @@ export const set_run_result: ActionConfig = {
48
58
  },
49
59
  layout: ['name', 'value', 'category'],
50
60
  toFormData: (action: SetRunResult) => {
51
- // Get existing flow results to populate the select options
52
- const store = getStore();
53
- const flowResults = store ? store.getState().getFlowResults() : [];
54
-
55
- // Update the form configuration with dynamic options
56
- const config = set_run_result;
57
- if (config.form && config.form.name && config.form.name.type === 'select') {
58
- (config.form.name as any).options = flowResults.map(
59
- (result) => result.name
60
- );
61
- }
62
-
63
61
  return {
64
62
  uuid: action.uuid,
65
- name: action.name || '',
63
+ name: action.name ? [{ name: action.name, value: action.name }] : [],
66
64
  value: action.value || '',
67
65
  category: action.category || ''
68
66
  };
@@ -36,10 +36,10 @@ export const split_by_expression: ActionConfig = {
36
36
  label: 'Operator',
37
37
  required: true,
38
38
  options: [
39
- { value: 'contains', label: 'contains' },
40
- { value: 'equals', label: 'equals' },
41
- { value: 'starts_with', label: 'starts with' },
42
- { value: 'regex', label: 'regex' }
39
+ { value: 'contains', name: 'contains' },
40
+ { value: 'equals', name: 'equals' },
41
+ { value: 'starts_with', name: 'starts with' },
42
+ { value: 'regex', name: 'regex' }
43
43
  ]
44
44
  },
45
45
  operand: {
@@ -14,11 +14,8 @@ import { set_run_result } from './actions/set_run_result';
14
14
  import { send_msg } from './actions/send_msg';
15
15
  import { send_email } from './actions/send_email';
16
16
  import { start_session } from './actions/start_session';
17
- import { open_ticket } from './actions/open_ticket';
18
- import { call_webhook } from './actions/call_webhook';
19
17
  import { call_classifier } from './actions/call_classifier';
20
18
  import { call_resthook } from './actions/call_resthook';
21
- import { call_llm } from './actions/call_llm';
22
19
  import { transfer_airtime } from './actions/transfer_airtime';
23
20
  import { set_contact_name } from './actions/set_contact_name';
24
21
  import { add_contact_groups } from './actions/add_contact_groups';
@@ -26,7 +23,6 @@ import { remove_contact_groups } from './actions/remove_contact_groups';
26
23
  import { request_optin } from './actions/request_optin';
27
24
  import { say_msg } from './actions/say_msg';
28
25
  import { play_audio } from './actions/play_audio';
29
- import { enter_flow } from './actions/enter_flow';
30
26
 
31
27
  // Import all node configurations
32
28
  import { execute_actions } from './nodes/execute_actions';
@@ -40,6 +36,7 @@ import { split_by_scheme } from './nodes/split_by_scheme';
40
36
  import { split_by_subflow } from './nodes/split_by_subflow';
41
37
  import { split_by_ticket } from './nodes/split_by_ticket';
42
38
  import { split_by_webhook } from './nodes/split_by_webhook';
39
+ import { split_by_llm } from './nodes/split_by_llm';
43
40
  import { split_by_llm_categorize } from './nodes/split_by_llm_categorize';
44
41
  import { wait_for_audio } from './nodes/wait_for_audio';
45
42
  import { wait_for_digits } from './nodes/wait_for_digits';
@@ -63,12 +60,8 @@ export const ACTION_CONFIG: {
63
60
  send_msg,
64
61
  send_email,
65
62
  start_session,
66
- open_ticket,
67
- call_webhook,
68
63
  call_classifier,
69
64
  call_resthook,
70
- call_llm,
71
- enter_flow,
72
65
  transfer_airtime,
73
66
  set_contact_name,
74
67
  add_contact_groups,
@@ -86,6 +79,7 @@ export const NODE_CONFIG: {
86
79
  split_by_contact_field,
87
80
  split_by_expression,
88
81
  split_by_groups,
82
+ split_by_llm,
89
83
  split_by_llm_categorize,
90
84
  split_by_random,
91
85
  split_by_run_result,
@@ -0,0 +1 @@
1
+ // No unified contact form exports - each action has its own form configuration
@@ -0,0 +1,119 @@
1
+ import { COLORS, NodeConfig } from '../types';
2
+ import { CallLLM, Node } from '../../store/flow-definition';
3
+ import { generateUUID, createSuccessFailureRouter } from '../../utils';
4
+ import { html } from 'lit';
5
+
6
+ export const split_by_llm: NodeConfig = {
7
+ type: 'split_by_llm',
8
+ name: 'Call AI',
9
+ color: COLORS.call,
10
+ form: {
11
+ llm: {
12
+ type: 'select',
13
+ label: 'LLM',
14
+ required: true,
15
+ options: [],
16
+ endpoint: '/test-assets/select/llms.json',
17
+ searchable: true,
18
+ valueKey: 'uuid',
19
+ nameKey: 'name',
20
+ placeholder: 'Select an LLM...'
21
+ },
22
+ input: {
23
+ type: 'text',
24
+ label: 'Input',
25
+ helpText: 'The input the AI will process',
26
+ required: true,
27
+ evaluated: true,
28
+ placeholder: '@input'
29
+ },
30
+ instructions: {
31
+ type: 'textarea',
32
+ label: 'Instructions',
33
+ helpText:
34
+ 'Tell the AI what to do with the input. The result can be referenced as **`@locals._llm_output`**',
35
+ required: true,
36
+ evaluated: true,
37
+ placeholder: 'Enter instructions for the AI model...',
38
+ minHeight: 130
39
+ }
40
+ },
41
+ layout: ['llm', 'input', 'instructions'],
42
+ render: (node: Node) => {
43
+ const callLlmAction = node.actions?.find(
44
+ (action) => action.type === 'call_llm'
45
+ ) as CallLLM;
46
+ return html`
47
+ <div
48
+ class="body"
49
+ style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; max-width: 180px; max-height: 90px; margin-bottom:10px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical;"
50
+ >
51
+ ${callLlmAction?.instructions || 'Configure AI instructions'}
52
+ </div>
53
+ `;
54
+ },
55
+ toFormData: (node: Node) => {
56
+ // Extract data from the existing node structure
57
+ const callLlmAction = node.actions?.find(
58
+ (action) => action.type === 'call_llm'
59
+ ) as CallLLM;
60
+
61
+ return {
62
+ uuid: node.uuid,
63
+ llm: callLlmAction?.llm
64
+ ? [{ value: callLlmAction.llm.uuid, name: callLlmAction.llm.name }]
65
+ : [],
66
+ input: callLlmAction?.input || '@input',
67
+ instructions: callLlmAction?.instructions || ''
68
+ };
69
+ },
70
+ fromFormData: (formData: any, originalNode: Node): Node => {
71
+ // Get LLM selection
72
+ const llmSelection =
73
+ Array.isArray(formData.llm) && formData.llm.length > 0
74
+ ? formData.llm[0]
75
+ : null;
76
+
77
+ // Find existing call_llm action to preserve its UUID
78
+ const existingCallLlmAction = originalNode.actions?.find(
79
+ (action) => action.type === 'call_llm'
80
+ );
81
+ const callLlmUuid = existingCallLlmAction?.uuid || generateUUID();
82
+
83
+ // Create call_llm action
84
+ const callLlmAction: CallLLM = {
85
+ type: 'call_llm',
86
+ uuid: callLlmUuid,
87
+ llm: llmSelection
88
+ ? { uuid: llmSelection.value, name: llmSelection.name }
89
+ : { uuid: '', name: '' },
90
+ input: formData.input || '@input',
91
+ instructions: formData.instructions || '',
92
+ output_local: '_llm_output'
93
+ };
94
+
95
+ // Create categories and exits for Success and Failure
96
+ const existingCategories = originalNode.router?.categories || [];
97
+ const existingExits = originalNode.exits || [];
98
+ const existingCases = originalNode.router?.cases || [];
99
+
100
+ const { router, exits } = createSuccessFailureRouter(
101
+ '@locals._llm_output',
102
+ {
103
+ type: 'has_text',
104
+ arguments: []
105
+ },
106
+ existingCategories,
107
+ existingExits,
108
+ existingCases
109
+ );
110
+
111
+ // Return the complete node
112
+ return {
113
+ uuid: originalNode.uuid,
114
+ actions: [callLlmAction],
115
+ router: router,
116
+ exits: exits
117
+ };
118
+ }
119
+ };
@@ -1,6 +1,6 @@
1
1
  import { COLORS, NodeConfig } from '../types';
2
- import { Node } from '../../store/flow-definition';
3
- import { generateUUID } from '../../utils';
2
+ import { CallLLM, Node } from '../../store/flow-definition';
3
+ import { generateUUID, createMultiCategoryRouter } from '../../utils';
4
4
  import { html } from 'lit';
5
5
 
6
6
  export const split_by_llm_categorize: NodeConfig = {
@@ -92,7 +92,7 @@ export const split_by_llm_categorize: NodeConfig = {
92
92
  render: (node: Node) => {
93
93
  const callLlmAction = node.actions?.find(
94
94
  (action) => action.type === 'call_llm'
95
- ) as any;
95
+ ) as CallLLM;
96
96
  return html`
97
97
  <div class="body">Categorize with ${callLlmAction.llm.name}</div>
98
98
  `;
@@ -135,7 +135,7 @@ export const split_by_llm_categorize: NodeConfig = {
135
135
  const callLlmUuid = existingCallLlmAction?.uuid || generateUUID();
136
136
 
137
137
  // Create call_llm action (using any type to match the example format)
138
- const callLlmAction: any = {
138
+ const callLlmAction: CallLLM = {
139
139
  type: 'call_llm',
140
140
  uuid: callLlmUuid,
141
141
  llm: llmSelection
@@ -147,124 +147,21 @@ export const split_by_llm_categorize: NodeConfig = {
147
147
  };
148
148
 
149
149
  // Create categories and exits
150
- const categories = [];
151
- const exits = [];
152
- const cases = [];
153
-
154
- // Get existing categories from original node for UUID preservation
155
150
  const existingCategories = originalNode.router?.categories || [];
156
151
  const existingExits = originalNode.exits || [];
157
152
  const existingCases = originalNode.router?.cases || [];
158
153
 
159
- // Add user categories
160
- userCategories.forEach((categoryName: string) => {
161
- // Check if this category already exists
162
- const existingCategory = existingCategories.find(
163
- (cat) => cat.name === categoryName
164
- );
165
- const existingExit = existingCategory
166
- ? existingExits.find((exit) => exit.uuid === existingCategory.exit_uuid)
167
- : null;
168
- const existingCase = existingCategory
169
- ? existingCases.find(
170
- (case_) => case_.category_uuid === existingCategory.uuid
171
- )
172
- : null;
173
-
174
- // Use existing UUIDs if category name hasn't changed, otherwise generate new ones
175
- const categoryUuid = existingCategory?.uuid || generateUUID();
176
- const exitUuid = existingExit?.uuid || generateUUID();
177
- const caseUuid = existingCase?.uuid || generateUUID();
178
-
179
- categories.push({
180
- uuid: categoryUuid,
181
- name: categoryName,
182
- exit_uuid: exitUuid
183
- });
184
-
185
- exits.push({
186
- uuid: exitUuid,
187
- destination_uuid: existingExit?.destination_uuid || null
188
- });
189
-
190
- cases.push({
191
- uuid: caseUuid,
154
+ const { router, exits } = createMultiCategoryRouter(
155
+ '@locals._llm_output',
156
+ userCategories,
157
+ (categoryName) => ({
192
158
  type: 'has_only_text',
193
- arguments: [categoryName],
194
- category_uuid: categoryUuid
195
- });
196
- });
197
-
198
- // Add "Other" category (default)
199
- const existingOtherCategory = existingCategories.find(
200
- (cat) => cat.name === 'Other'
201
- );
202
- const existingOtherExit = existingOtherCategory
203
- ? existingExits.find(
204
- (exit) => exit.uuid === existingOtherCategory.exit_uuid
205
- )
206
- : null;
207
-
208
- const otherCategoryUuid = existingOtherCategory?.uuid || generateUUID();
209
- const otherExitUuid = existingOtherExit?.uuid || generateUUID();
210
-
211
- categories.push({
212
- uuid: otherCategoryUuid,
213
- name: 'Other',
214
- exit_uuid: otherExitUuid
215
- });
216
- exits.push({
217
- uuid: otherExitUuid,
218
- destination_uuid: existingOtherExit?.destination_uuid || null
219
- });
220
-
221
- // Add "Failure" category
222
- const existingFailureCategory = existingCategories.find(
223
- (cat) => cat.name === 'Failure'
159
+ arguments: [categoryName]
160
+ }),
161
+ existingCategories,
162
+ existingExits,
163
+ existingCases
224
164
  );
225
- const existingFailureExit = existingFailureCategory
226
- ? existingExits.find(
227
- (exit) => exit.uuid === existingFailureCategory.exit_uuid
228
- )
229
- : null;
230
- const existingFailureCase = existingFailureCategory
231
- ? existingCases.find(
232
- (case_) =>
233
- case_.category_uuid === existingFailureCategory.uuid &&
234
- case_.arguments?.[0] === '<ERROR>'
235
- )
236
- : null;
237
-
238
- const failureCategoryUuid = existingFailureCategory?.uuid || generateUUID();
239
- const failureExitUuid = existingFailureExit?.uuid || generateUUID();
240
- const failureCaseUuid = existingFailureCase?.uuid || generateUUID();
241
-
242
- categories.push({
243
- uuid: failureCategoryUuid,
244
- name: 'Failure',
245
- exit_uuid: failureExitUuid
246
- });
247
- exits.push({
248
- uuid: failureExitUuid,
249
- destination_uuid: existingFailureExit?.destination_uuid || null
250
- });
251
-
252
- // Add failure case for <ERROR>
253
- cases.push({
254
- uuid: failureCaseUuid,
255
- type: 'has_only_text',
256
- arguments: ['<ERROR>'],
257
- category_uuid: failureCategoryUuid
258
- });
259
-
260
- // Create the router
261
- const router = {
262
- type: 'switch' as const,
263
- categories: categories,
264
- default_category_uuid: otherCategoryUuid,
265
- operand: '@locals._llm_output',
266
- cases: cases
267
- };
268
165
 
269
166
  // Return the complete node
270
167
  return {