@nyaruka/temba-components 0.130.0 → 0.130.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/CHANGELOG.md +34 -4
  2. package/DEV_DATA.md +89 -0
  3. package/demo/data/flows/food-order.json +4 -4
  4. package/demo/data/flows/sample-flow.json +132 -147
  5. package/dist/temba-components.js +764 -628
  6. package/dist/temba-components.js.map +1 -1
  7. package/out-tsc/src/display/Chat.js +5 -3
  8. package/out-tsc/src/display/Chat.js.map +1 -1
  9. package/out-tsc/src/events.js.map +1 -1
  10. package/out-tsc/src/flow/CanvasNode.js +83 -78
  11. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  12. package/out-tsc/src/flow/Editor.js +1 -0
  13. package/out-tsc/src/flow/Editor.js.map +1 -1
  14. package/out-tsc/src/flow/NodeEditor.js +47 -3
  15. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  16. package/out-tsc/src/flow/actions/add_contact_groups.js +13 -1
  17. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  18. package/out-tsc/src/flow/actions/add_contact_urn.js +1 -1
  19. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  20. package/out-tsc/src/flow/actions/add_input_labels.js +1 -0
  21. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  22. package/out-tsc/src/flow/actions/set_contact_channel.js +1 -1
  23. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  24. package/out-tsc/src/flow/actions/set_contact_field.js +2 -1
  25. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  26. package/out-tsc/src/flow/actions/set_contact_language.js +3 -1
  27. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  28. package/out-tsc/src/flow/actions/set_contact_name.js +1 -1
  29. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  30. package/out-tsc/src/flow/actions/set_contact_status.js +17 -14
  31. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  32. package/out-tsc/src/flow/actions/set_run_result.js +1 -1
  33. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  34. package/out-tsc/src/flow/nodes/split_by_llm.js +12 -12
  35. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  36. package/out-tsc/src/flow/nodes/wait_for_response.js +609 -6
  37. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  38. package/out-tsc/src/flow/operators.js +194 -0
  39. package/out-tsc/src/flow/operators.js.map +1 -0
  40. package/out-tsc/src/flow/types.js.map +1 -1
  41. package/out-tsc/src/form/ArrayEditor.js +84 -19
  42. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  43. package/out-tsc/src/form/Checkbox.js +12 -0
  44. package/out-tsc/src/form/Checkbox.js.map +1 -1
  45. package/out-tsc/src/form/FieldRenderer.js +13 -3
  46. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  47. package/out-tsc/src/form/TextInput.js +20 -1
  48. package/out-tsc/src/form/TextInput.js.map +1 -1
  49. package/out-tsc/src/form/select/Select.js +14 -1
  50. package/out-tsc/src/form/select/Select.js.map +1 -1
  51. package/out-tsc/src/interfaces.js.map +1 -1
  52. package/out-tsc/src/layout/Dialog.js +3 -4
  53. package/out-tsc/src/layout/Dialog.js.map +1 -1
  54. package/out-tsc/src/list/RunList.js +2 -2
  55. package/out-tsc/src/list/RunList.js.map +1 -1
  56. package/out-tsc/src/live/ContactChat.js +114 -34
  57. package/out-tsc/src/live/ContactChat.js.map +1 -1
  58. package/out-tsc/src/live/ContactDetails.js +7 -0
  59. package/out-tsc/src/live/ContactDetails.js.map +1 -1
  60. package/out-tsc/src/live/ContactNameFetch.js +1 -1
  61. package/out-tsc/src/live/ContactNameFetch.js.map +1 -1
  62. package/out-tsc/test/NodeHelper.js +25 -27
  63. package/out-tsc/test/NodeHelper.js.map +1 -1
  64. package/out-tsc/test/nodes/split_by_llm.test.js +12 -4
  65. package/out-tsc/test/nodes/split_by_llm.test.js.map +1 -1
  66. package/out-tsc/test/nodes/split_by_llm_categorize.test.js +101 -91
  67. package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -1
  68. package/out-tsc/test/nodes/split_by_random.test.js +120 -112
  69. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -1
  70. package/out-tsc/test/nodes/wait_for_digits.test.js +131 -111
  71. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  72. package/out-tsc/test/nodes/wait_for_response.test.js +549 -85
  73. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -1
  74. package/out-tsc/test/temba-checkbox.test.js +32 -32
  75. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  76. package/out-tsc/test/temba-contact-chat.test.js +2 -1
  77. package/out-tsc/test/temba-contact-chat.test.js.map +1 -1
  78. package/out-tsc/test/temba-dropdown.test.js +0 -4
  79. package/out-tsc/test/temba-dropdown.test.js.map +1 -1
  80. package/out-tsc/test/temba-flow-editor-node.test.js +9 -4
  81. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  82. package/out-tsc/test/temba-integration-markdown.test.js +13 -15
  83. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  84. package/out-tsc/test/temba-node-editor.test.js +5 -38
  85. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  86. package/out-tsc/test/temba-run-list.test.js +2 -2
  87. package/out-tsc/test/temba-run-list.test.js.map +1 -1
  88. package/out-tsc/test/utils.test.js +2 -1
  89. package/out-tsc/test/utils.test.js.map +1 -1
  90. package/package.json +6 -2
  91. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  92. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  93. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  94. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  95. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  96. package/screenshots/truth/actions/add_contact_groups/render/descriptive-group-names.png +0 -0
  97. package/screenshots/truth/actions/add_contact_groups/render/long-group-names.png +0 -0
  98. package/screenshots/truth/actions/add_contact_groups/render/many-groups.png +0 -0
  99. package/screenshots/truth/actions/add_contact_groups/render/multiple-groups.png +0 -0
  100. package/screenshots/truth/actions/add_contact_groups/render/single-group.png +0 -0
  101. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  102. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  103. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  104. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  105. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  106. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  107. package/screenshots/truth/actions/remove_contact_groups/render/cleanup-groups.png +0 -0
  108. package/screenshots/truth/actions/remove_contact_groups/render/long-descriptive-group-names.png +0 -0
  109. package/screenshots/truth/actions/remove_contact_groups/render/many-groups.png +0 -0
  110. package/screenshots/truth/actions/remove_contact_groups/render/multiple-groups.png +0 -0
  111. package/screenshots/truth/actions/remove_contact_groups/render/remove-from-all-groups.png +0 -0
  112. package/screenshots/truth/actions/remove_contact_groups/render/single-group.png +0 -0
  113. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  114. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  115. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  116. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  117. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  118. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  119. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  120. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  121. package/screenshots/truth/actions/send_email/render/complex-business-email.png +0 -0
  122. package/screenshots/truth/actions/send_email/render/empty-body.png +0 -0
  123. package/screenshots/truth/actions/send_email/render/empty-subject.png +0 -0
  124. package/screenshots/truth/actions/send_email/render/long-subject.png +0 -0
  125. package/screenshots/truth/actions/send_email/render/multiline-body.png +0 -0
  126. package/screenshots/truth/actions/send_email/render/multiple-recipients.png +0 -0
  127. package/screenshots/truth/actions/send_email/render/simple-email.png +0 -0
  128. package/screenshots/truth/actions/send_email/render/with-expressions.png +0 -0
  129. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  130. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  131. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  132. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  133. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  134. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  135. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  136. package/screenshots/truth/actions/send_msg/render/long-quick-replies.png +0 -0
  137. package/screenshots/truth/actions/send_msg/render/multiline-text-with-replies.png +0 -0
  138. package/screenshots/truth/actions/send_msg/render/simple-text.png +0 -0
  139. package/screenshots/truth/actions/send_msg/render/text-with-linebreaks.png +0 -0
  140. package/screenshots/truth/actions/send_msg/render/text-with-many-quick-replies.png +0 -0
  141. package/screenshots/truth/actions/send_msg/render/text-with-quick-replies.png +0 -0
  142. package/screenshots/truth/actions/send_msg/render/text-without-quick-replies.png +0 -0
  143. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  144. package/screenshots/truth/checkbox/checkbox-whitespace-label-no-background-hover.png +0 -0
  145. package/screenshots/truth/checkbox/checkbox-with-help-text.png +0 -0
  146. package/screenshots/truth/checkbox/checked.png +0 -0
  147. package/screenshots/truth/checkbox/default.png +0 -0
  148. package/screenshots/truth/editor/wait.png +0 -0
  149. package/screenshots/truth/integration/textinput-markdown-errors.png +0 -0
  150. package/screenshots/truth/lightbox/img-zoomed.png +0 -0
  151. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  152. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  153. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  154. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  155. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  156. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  157. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  158. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  159. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  160. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  161. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  162. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  163. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  164. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  165. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  166. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  167. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  168. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  169. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  170. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  171. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  172. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  173. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  174. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  175. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  176. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  177. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  178. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  179. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  180. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  181. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  182. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  183. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  184. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  185. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  186. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  187. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  188. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  189. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  190. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  191. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  192. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  193. package/screenshots/truth/run-list/basic.png +0 -0
  194. package/screenshots/truth/templates/default.png +0 -0
  195. package/screenshots/truth/wait-for-response/rules-editor.png +0 -0
  196. package/screenshots/truth/wait-for-response/timeout-editor-unchecked.png +0 -0
  197. package/screenshots/truth/wait-for-response/timeout-editor.png +0 -0
  198. package/scripts/dev-data-sync.mjs +182 -0
  199. package/src/display/Chat.ts +6 -4
  200. package/src/events.ts +6 -5
  201. package/src/flow/CanvasNode.ts +89 -79
  202. package/src/flow/Editor.ts +1 -0
  203. package/src/flow/NodeEditor.ts +55 -3
  204. package/src/flow/actions/add_contact_groups.ts +16 -1
  205. package/src/flow/actions/add_contact_urn.ts +1 -1
  206. package/src/flow/actions/add_input_labels.ts +1 -0
  207. package/src/flow/actions/set_contact_channel.ts +1 -1
  208. package/src/flow/actions/set_contact_field.ts +2 -1
  209. package/src/flow/actions/set_contact_language.ts +3 -1
  210. package/src/flow/actions/set_contact_name.ts +1 -1
  211. package/src/flow/actions/set_contact_status.ts +18 -18
  212. package/src/flow/actions/set_run_result.ts +1 -1
  213. package/src/flow/nodes/split_by_llm.ts +14 -13
  214. package/src/flow/nodes/wait_for_response.ts +717 -5
  215. package/src/flow/operators.ts +215 -0
  216. package/src/flow/types.ts +10 -2
  217. package/src/form/ArrayEditor.ts +117 -37
  218. package/src/form/Checkbox.ts +12 -0
  219. package/src/form/FieldRenderer.ts +24 -3
  220. package/src/form/TextInput.ts +19 -1
  221. package/src/form/select/Select.ts +15 -4
  222. package/src/interfaces.ts +1 -1
  223. package/src/layout/Dialog.ts +4 -4
  224. package/src/list/RunList.ts +2 -2
  225. package/src/live/ContactChat.ts +144 -58
  226. package/src/live/ContactDetails.ts +7 -0
  227. package/src/live/ContactNameFetch.ts +1 -1
  228. package/static/api/labels.json +6 -1
  229. package/test/NodeHelper.ts +38 -40
  230. package/test/nodes/split_by_llm.test.ts +43 -32
  231. package/test/nodes/split_by_llm_categorize.test.ts +130 -120
  232. package/test/nodes/split_by_random.test.ts +136 -128
  233. package/test/nodes/wait_for_digits.test.ts +147 -127
  234. package/test/nodes/wait_for_response.test.ts +657 -104
  235. package/test/temba-checkbox.test.ts +36 -32
  236. package/test/temba-contact-chat.test.ts +2 -1
  237. package/test/temba-dropdown.test.ts +0 -12
  238. package/test/temba-flow-editor-node.test.ts +11 -4
  239. package/test/temba-integration-markdown.test.ts +16 -17
  240. package/test/temba-node-editor.test.ts +5 -43
  241. package/test/temba-run-list.test.ts +2 -2
  242. package/test/utils.test.ts +2 -1
  243. package/test-assets/list/runs.json +8 -8
  244. package/web-dev-mock.mjs +86 -30
  245. package/web-dev-server.config.mjs +272 -31
  246. package/screenshots/truth/dropdown/bottom-edge-collision.png +0 -0
  247. package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
  248. package/screenshots/truth/editor/send_msg.png +0 -0
  249. package/screenshots/truth/editor/set_contact_language.png +0 -0
  250. package/screenshots/truth/editor/set_contact_name.png +0 -0
  251. package/screenshots/truth/editor/set_run_result.png +0 -0
  252. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
@@ -1,9 +1,328 @@
1
1
  import { COLORS } from '../types';
2
+ import { generateUUID } from '../../utils';
3
+ import { getWaitForResponseOperators, operatorsToSelectOptions, getOperatorConfig } from '../operators';
4
+ const TIMEOUT_OPTIONS = [
5
+ { value: '60', name: '1 minute' },
6
+ { value: '120', name: '2 minutes' },
7
+ { value: '180', name: '3 minutes' },
8
+ { value: '240', name: '4 minutes' },
9
+ { value: '300', name: '5 minutes' },
10
+ { value: '600', name: '10 minutes' },
11
+ { value: '900', name: '15 minutes' },
12
+ { value: '1800', name: '30 minutes' },
13
+ { value: '3600', name: '1 hour' },
14
+ { value: '7200', name: '2 hours' },
15
+ { value: '10800', name: '3 hours' },
16
+ { value: '21600', name: '6 hours' },
17
+ { value: '43200', name: '12 hours' },
18
+ { value: '64800', name: '18 hours' },
19
+ { value: '86400', name: '1 day' },
20
+ { value: '172800', name: '2 days' },
21
+ { value: '259200', name: '3 days' },
22
+ { value: '604800', name: '1 week' }
23
+ ];
24
+ // Helper function to create a wait_for_response router with user rules
25
+ const createWaitForResponseRouter = (userRules, existingCategories = [], existingExits = [], existingCases = []) => {
26
+ var _a;
27
+ const categories = [];
28
+ const exits = [];
29
+ const cases = [];
30
+ // Filter existing categories to get only user-defined rules (exclude system categories)
31
+ const existingUserCategories = existingCategories.filter((cat) => cat.name !== 'No Response' &&
32
+ cat.name !== 'Other' &&
33
+ cat.name !== 'Timeout');
34
+ // Group rules by category name (case-insensitive) to merge them
35
+ const rulesByCategory = new Map();
36
+ userRules.forEach((rule) => {
37
+ const categoryKey = rule.category.trim().toLowerCase();
38
+ if (!rulesByCategory.has(categoryKey)) {
39
+ rulesByCategory.set(categoryKey, []);
40
+ }
41
+ rulesByCategory.get(categoryKey).push(rule);
42
+ });
43
+ // Track category creation order to preserve UUID mapping
44
+ const categoryOrder = [];
45
+ userRules.forEach((rule) => {
46
+ const categoryKey = rule.category.trim().toLowerCase();
47
+ if (!categoryOrder.includes(categoryKey)) {
48
+ categoryOrder.push(categoryKey);
49
+ }
50
+ });
51
+ // Create categories, exits, and cases for each unique category
52
+ categoryOrder.forEach((categoryKey, categoryIndex) => {
53
+ const rulesForCategory = rulesByCategory.get(categoryKey);
54
+ const categoryName = rulesForCategory[0].category.trim(); // Use the first occurrence's casing
55
+ // Try to find existing category by position/index to preserve UUIDs when names change
56
+ const existingCategory = existingUserCategories[categoryIndex];
57
+ const existingExit = existingCategory
58
+ ? existingExits.find((exit) => exit.uuid === existingCategory.exit_uuid)
59
+ : null;
60
+ const exitUuid = (existingExit === null || existingExit === void 0 ? void 0 : existingExit.uuid) || generateUUID();
61
+ const categoryUuid = (existingCategory === null || existingCategory === void 0 ? void 0 : existingCategory.uuid) || generateUUID();
62
+ // Create single category for all rules with this category name
63
+ categories.push({
64
+ uuid: categoryUuid,
65
+ name: categoryName,
66
+ exit_uuid: exitUuid
67
+ });
68
+ exits.push({
69
+ uuid: exitUuid,
70
+ destination_uuid: (existingExit === null || existingExit === void 0 ? void 0 : existingExit.destination_uuid) || null
71
+ });
72
+ // Create a case for each rule in this category
73
+ rulesForCategory.forEach((rule) => {
74
+ // Try to find existing case for this rule by looking at the original rule order
75
+ const originalRuleIndex = userRules.findIndex((r) => r === rule);
76
+ const existingCase = existingCases[originalRuleIndex];
77
+ const caseUuid = (existingCase === null || existingCase === void 0 ? void 0 : existingCase.uuid) || generateUUID();
78
+ // Parse rule value based on operator configuration
79
+ const operatorConfig = getOperatorConfig(rule.operator);
80
+ let arguments_ = [];
81
+ if (operatorConfig) {
82
+ if (operatorConfig.operands === 0) {
83
+ // No operands needed
84
+ arguments_ = [];
85
+ }
86
+ else if (operatorConfig.operands === 2) {
87
+ // Split value for two operands (e.g., "1 10" for between)
88
+ arguments_ = rule.value
89
+ .split(' ')
90
+ .filter((arg) => arg.trim());
91
+ }
92
+ else {
93
+ // Single operand - but split words for operators that expect multiple words
94
+ if (rule.value && rule.value.trim()) {
95
+ // Split on spaces and filter out empty strings
96
+ arguments_ = rule.value
97
+ .trim()
98
+ .split(/\s+/)
99
+ .filter((arg) => arg.length > 0);
100
+ }
101
+ else {
102
+ arguments_ = [];
103
+ }
104
+ }
105
+ }
106
+ else {
107
+ // Fallback for unknown operators - split on spaces if value exists
108
+ if (rule.value && rule.value.trim()) {
109
+ arguments_ = rule.value
110
+ .trim()
111
+ .split(/\s+/)
112
+ .filter((arg) => arg.length > 0);
113
+ }
114
+ else {
115
+ arguments_ = [];
116
+ }
117
+ }
118
+ cases.push({
119
+ uuid: caseUuid,
120
+ type: rule.operator,
121
+ arguments: arguments_,
122
+ category_uuid: categoryUuid
123
+ });
124
+ });
125
+ });
126
+ // Preserve existing timeout categories like "No Response"
127
+ existingCategories.forEach((category) => {
128
+ if (category.name === 'No Response' || category.name === 'Timeout') {
129
+ const existingExit = existingExits.find((exit) => exit.uuid === category.exit_uuid);
130
+ if (existingExit) {
131
+ categories.push(category);
132
+ exits.push(existingExit);
133
+ }
134
+ }
135
+ });
136
+ // Add "Other" category (default) only if there are user rules
137
+ if (userRules.length > 0) {
138
+ const existingOtherCategory = existingCategories.find((cat) => cat.name === 'Other');
139
+ const existingOtherExit = existingOtherCategory
140
+ ? existingExits.find((exit) => exit.uuid === existingOtherCategory.exit_uuid)
141
+ : null;
142
+ const otherExitUuid = (existingOtherExit === null || existingOtherExit === void 0 ? void 0 : existingOtherExit.uuid) || generateUUID();
143
+ const otherCategoryUuid = (existingOtherCategory === null || existingOtherCategory === void 0 ? void 0 : existingOtherCategory.uuid) || generateUUID();
144
+ categories.push({
145
+ uuid: otherCategoryUuid,
146
+ name: 'Other',
147
+ exit_uuid: otherExitUuid
148
+ });
149
+ exits.push({
150
+ uuid: otherExitUuid,
151
+ destination_uuid: (existingOtherExit === null || existingOtherExit === void 0 ? void 0 : existingOtherExit.destination_uuid) || null
152
+ });
153
+ }
154
+ return {
155
+ router: {
156
+ type: 'switch',
157
+ categories: categories,
158
+ default_category_uuid: (_a = categories.find((cat) => cat.name === 'Other')) === null || _a === void 0 ? void 0 : _a.uuid,
159
+ operand: '@input.text',
160
+ cases: cases
161
+ },
162
+ exits: exits
163
+ };
164
+ };
2
165
  export const wait_for_response = {
3
166
  type: 'wait_for_response',
4
167
  name: 'Wait for Response',
5
168
  color: COLORS.wait,
169
+ dialogSize: 'large',
6
170
  form: {
171
+ rules: {
172
+ type: 'array',
173
+ helpText: 'Define rules to categorize responses',
174
+ itemLabel: 'Rule',
175
+ minItems: 0,
176
+ maxItems: 100,
177
+ maintainEmptyItem: true, // Explicitly enable empty item maintenance
178
+ isEmptyItem: (item) => {
179
+ // Helper function to get operator value from various formats
180
+ const getOperatorValue = (operator) => {
181
+ if (typeof operator === 'string') {
182
+ return operator.trim();
183
+ }
184
+ else if (Array.isArray(operator) && operator.length > 0) {
185
+ // Handle array format: [{value: "has_any_word", name: "..."}]
186
+ const firstOperator = operator[0];
187
+ if (firstOperator &&
188
+ typeof firstOperator === 'object' &&
189
+ firstOperator.value) {
190
+ return firstOperator.value.trim();
191
+ }
192
+ }
193
+ else if (operator &&
194
+ typeof operator === 'object' &&
195
+ operator.value) {
196
+ // Handle object format: {value: "has_any_word", name: "..."}
197
+ return operator.value.trim();
198
+ }
199
+ return '';
200
+ };
201
+ // Check if operator and category are provided
202
+ const operatorValue = getOperatorValue(item.operator);
203
+ if (!operatorValue || !item.category || item.category.trim() === '') {
204
+ return true;
205
+ }
206
+ // Check if value is required based on operator configuration
207
+ const operatorConfig = getOperatorConfig(operatorValue);
208
+ if (operatorConfig && operatorConfig.operands === 1) {
209
+ // value1 is required for this operator
210
+ return !item.value1 || item.value1.trim() === '';
211
+ }
212
+ else if (operatorConfig && operatorConfig.operands === 2) {
213
+ // Both value1 and value2 are required for this operator
214
+ return (!item.value1 ||
215
+ item.value1.trim() === '' ||
216
+ !item.value2 ||
217
+ item.value2.trim() === '');
218
+ }
219
+ // No value required for this operator
220
+ return false;
221
+ },
222
+ itemConfig: {
223
+ operator: {
224
+ type: 'select',
225
+ required: true,
226
+ multi: false, // Explicitly set as single-select
227
+ options: operatorsToSelectOptions(getWaitForResponseOperators()),
228
+ flavor: 'xsmall',
229
+ width: '200px'
230
+ },
231
+ value1: {
232
+ type: 'text',
233
+ flavor: 'xsmall',
234
+ conditions: {
235
+ visible: (formData) => {
236
+ // Helper function to get operator value from various formats
237
+ const getOperatorValue = (operator) => {
238
+ if (typeof operator === 'string') {
239
+ return operator.trim();
240
+ }
241
+ else if (Array.isArray(operator) && operator.length > 0) {
242
+ const firstOperator = operator[0];
243
+ if (firstOperator &&
244
+ typeof firstOperator === 'object' &&
245
+ firstOperator.value) {
246
+ return firstOperator.value.trim();
247
+ }
248
+ }
249
+ else if (operator &&
250
+ typeof operator === 'object' &&
251
+ operator.value) {
252
+ return operator.value.trim();
253
+ }
254
+ return '';
255
+ };
256
+ // Show value1 field for operators that require 1 or 2 operands
257
+ const operatorValue = getOperatorValue(formData.operator);
258
+ const operatorConfig = getOperatorConfig(operatorValue);
259
+ return operatorConfig ? operatorConfig.operands >= 1 : true;
260
+ }
261
+ }
262
+ },
263
+ value2: {
264
+ type: 'text',
265
+ flavor: 'xsmall',
266
+ conditions: {
267
+ visible: (formData) => {
268
+ // Helper function to get operator value from various formats
269
+ const getOperatorValue = (operator) => {
270
+ if (typeof operator === 'string') {
271
+ return operator.trim();
272
+ }
273
+ else if (Array.isArray(operator) && operator.length > 0) {
274
+ const firstOperator = operator[0];
275
+ if (firstOperator &&
276
+ typeof firstOperator === 'object' &&
277
+ firstOperator.value) {
278
+ return firstOperator.value.trim();
279
+ }
280
+ }
281
+ else if (operator &&
282
+ typeof operator === 'object' &&
283
+ operator.value) {
284
+ return operator.value.trim();
285
+ }
286
+ return '';
287
+ };
288
+ // Show value2 field only if operator requires exactly 2 operands
289
+ const operatorValue = getOperatorValue(formData.operator);
290
+ const operatorConfig = getOperatorConfig(operatorValue);
291
+ return operatorConfig ? operatorConfig.operands === 2 : false;
292
+ }
293
+ }
294
+ },
295
+ category: {
296
+ type: 'text',
297
+ placeholder: 'Category',
298
+ required: true,
299
+ maxWidth: '120px',
300
+ flavor: 'xsmall'
301
+ }
302
+ }
303
+ },
304
+ timeout_enabled: {
305
+ type: 'checkbox',
306
+ label: (formData) => {
307
+ return formData.timeout_enabled
308
+ ? 'Continue when there is no response for'
309
+ : 'Continue when there is no response..';
310
+ },
311
+ labelPadding: '4px 8px'
312
+ },
313
+ timeout_duration: {
314
+ type: 'select',
315
+ placeholder: '5 minutes',
316
+ multi: false,
317
+ maxWidth: '150px',
318
+ flavor: 'xsmall',
319
+ options: TIMEOUT_OPTIONS,
320
+ conditions: {
321
+ visible: (formData) => {
322
+ return formData.timeout_enabled === true;
323
+ }
324
+ }
325
+ },
7
326
  result_name: {
8
327
  type: 'text',
9
328
  label: 'Result Name',
@@ -11,22 +330,306 @@ export const wait_for_response = {
11
330
  placeholder: 'response'
12
331
  }
13
332
  },
14
- layout: ['timeout', 'result_name'],
333
+ layout: ['rules', 'result_name'],
334
+ gutter: [
335
+ {
336
+ type: 'row',
337
+ items: ['timeout_enabled', 'timeout_duration'],
338
+ gap: '0.5rem'
339
+ }
340
+ ],
341
+ validate: (_formData) => {
342
+ const errors = {};
343
+ // No validation needed - allow multiple rules to use same category name
344
+ // Rules with the same category name will be merged to use the same exit
345
+ return {
346
+ valid: Object.keys(errors).length === 0,
347
+ errors
348
+ };
349
+ },
15
350
  toFormData: (node) => {
16
- var _a;
351
+ var _a, _b, _c, _d, _e, _f;
352
+ // Extract rules from router cases
353
+ const rules = [];
354
+ if (((_a = node.router) === null || _a === void 0 ? void 0 : _a.cases) && ((_b = node.router) === null || _b === void 0 ? void 0 : _b.categories)) {
355
+ node.router.cases.forEach((case_) => {
356
+ // Find the category for this case
357
+ const category = node.router.categories.find((cat) => cat.uuid === case_.category_uuid);
358
+ // Skip timeout/system categories like "No Response"
359
+ if (category &&
360
+ category.name !== 'No Response' &&
361
+ category.name !== 'Other') {
362
+ // Handle different operator types
363
+ const operatorConfig = getOperatorConfig(case_.type);
364
+ const operatorDisplayName = operatorConfig
365
+ ? operatorConfig.name
366
+ : case_.type;
367
+ let value1 = '';
368
+ let value2 = '';
369
+ if (operatorConfig && operatorConfig.operands === 0) {
370
+ // No value needed for operators like has_text, has_number
371
+ value1 = '';
372
+ value2 = '';
373
+ }
374
+ else if (operatorConfig && operatorConfig.operands === 1) {
375
+ // Single value for operators like has_number_lt - use value1
376
+ value1 = case_.arguments.join(' ');
377
+ value2 = '';
378
+ }
379
+ else if (operatorConfig && operatorConfig.operands === 2) {
380
+ // Two separate values for operators like has_number_between
381
+ value1 = case_.arguments[0] || '';
382
+ value2 = case_.arguments[1] || '';
383
+ }
384
+ else {
385
+ // Fallback: use first argument for unknown operators
386
+ value1 = case_.arguments.join(' ');
387
+ value2 = '';
388
+ }
389
+ rules.push({
390
+ operator: { value: case_.type, name: operatorDisplayName },
391
+ value1: value1,
392
+ value2: value2,
393
+ category: category.name
394
+ });
395
+ }
396
+ });
397
+ }
398
+ // Extract timeout configuration
399
+ const timeoutSeconds = (_e = (_d = (_c = node.router) === null || _c === void 0 ? void 0 : _c.wait) === null || _d === void 0 ? void 0 : _d.timeout) === null || _e === void 0 ? void 0 : _e.seconds;
400
+ let timeoutOption = TIMEOUT_OPTIONS.find((opt) => opt.value === String(timeoutSeconds));
401
+ if (!timeoutOption) {
402
+ timeoutOption = { value: '300', name: '5 minutes' };
403
+ }
17
404
  return {
18
405
  uuid: node.uuid,
19
- result_name: ((_a = node.router) === null || _a === void 0 ? void 0 : _a.result_name) || 'response'
406
+ rules: rules,
407
+ timeout_enabled: !!timeoutSeconds,
408
+ timeout_duration: timeoutOption,
409
+ result_name: ((_f = node.router) === null || _f === void 0 ? void 0 : _f.result_name) || 'response'
20
410
  };
21
411
  },
22
412
  fromFormData: (formData, originalNode) => {
23
- const router = {
24
- ...originalNode.router,
413
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
414
+ // Helper function to get operator value from various formats
415
+ const getOperatorValue = (operator) => {
416
+ if (typeof operator === 'string') {
417
+ return operator.trim();
418
+ }
419
+ else if (Array.isArray(operator) && operator.length > 0) {
420
+ // Handle array format: [{value: "has_any_word", name: "..."}]
421
+ const firstOperator = operator[0];
422
+ if (firstOperator &&
423
+ typeof firstOperator === 'object' &&
424
+ firstOperator.value) {
425
+ return firstOperator.value.trim();
426
+ }
427
+ }
428
+ else if (operator && typeof operator === 'object' && operator.value) {
429
+ // Handle object format: {value: "has_any_word", name: "..."}
430
+ return operator.value.trim();
431
+ }
432
+ return '';
433
+ };
434
+ // Get user rules
435
+ const userRules = (formData.rules || [])
436
+ .filter((rule) => {
437
+ // Always need operator and category
438
+ const operatorValue = getOperatorValue(rule === null || rule === void 0 ? void 0 : rule.operator);
439
+ if (!operatorValue ||
440
+ !(rule === null || rule === void 0 ? void 0 : rule.category) ||
441
+ operatorValue === '' ||
442
+ rule.category.trim() === '') {
443
+ return false;
444
+ }
445
+ // Check if value is required based on operator
446
+ const operatorConfig = getOperatorConfig(operatorValue);
447
+ if (operatorConfig && operatorConfig.operands === 1) {
448
+ // value1 is required for this operator
449
+ return (rule === null || rule === void 0 ? void 0 : rule.value1) && rule.value1.trim() !== '';
450
+ }
451
+ else if (operatorConfig && operatorConfig.operands === 2) {
452
+ // Both value1 and value2 are required for this operator
453
+ return ((rule === null || rule === void 0 ? void 0 : rule.value1) &&
454
+ rule.value1.trim() !== '' &&
455
+ (rule === null || rule === void 0 ? void 0 : rule.value2) &&
456
+ rule.value2.trim() !== '');
457
+ }
458
+ // No value required for this operator
459
+ return true;
460
+ })
461
+ .map((rule) => {
462
+ const operatorValue = getOperatorValue(rule.operator);
463
+ const operatorConfig = getOperatorConfig(operatorValue);
464
+ let value = '';
465
+ if (operatorConfig && operatorConfig.operands === 1) {
466
+ // Single value from value1
467
+ value = rule.value1 ? rule.value1.trim() : '';
468
+ }
469
+ else if (operatorConfig && operatorConfig.operands === 2) {
470
+ // Two values - combine them with space
471
+ const val1 = rule.value1 ? rule.value1.trim() : '';
472
+ const val2 = rule.value2 ? rule.value2.trim() : '';
473
+ value = `${val1} ${val2}`.trim();
474
+ }
475
+ else {
476
+ // No value needed for 0-operand operators
477
+ value = '';
478
+ }
479
+ return {
480
+ operator: operatorValue,
481
+ value: value,
482
+ category: rule.category.trim()
483
+ };
484
+ });
485
+ // If no user rules, clear cases but preserve other router config
486
+ if (userRules.length === 0) {
487
+ const router = {
488
+ ...originalNode.router,
489
+ result_name: formData.result_name || 'response'
490
+ };
491
+ // Only set cases to empty if the original node had cases
492
+ if (((_a = originalNode.router) === null || _a === void 0 ? void 0 : _a.cases) !== undefined) {
493
+ router.cases = []; // Clear all cases when no rules
494
+ }
495
+ // Build wait configuration based on form data
496
+ const waitConfig = {
497
+ type: 'msg'
498
+ };
499
+ // Add timeout if enabled
500
+ if (formData.timeout_enabled) {
501
+ // Extract timeout value (handle both string and object formats)
502
+ let timeoutSeconds;
503
+ if (formData.timeout_duration) {
504
+ if (Array.isArray(formData.timeout_duration) &&
505
+ formData.timeout_duration.length > 0) {
506
+ // Handle array of selected options (multi-select behavior)
507
+ timeoutSeconds = parseInt(formData.timeout_duration[0].value, 10);
508
+ }
509
+ else if (typeof formData.timeout_duration === 'string') {
510
+ timeoutSeconds = parseInt(formData.timeout_duration, 10);
511
+ }
512
+ else if (formData.timeout_duration &&
513
+ typeof formData.timeout_duration === 'object' &&
514
+ formData.timeout_duration.value) {
515
+ timeoutSeconds = parseInt(formData.timeout_duration.value, 10);
516
+ }
517
+ else {
518
+ timeoutSeconds = 300; // Default to 5 minutes
519
+ }
520
+ }
521
+ else {
522
+ // No duration selected, use default
523
+ timeoutSeconds = 300; // Default to 5 minutes
524
+ }
525
+ // Validate that we got a valid number
526
+ if (isNaN(timeoutSeconds) || timeoutSeconds <= 0) {
527
+ timeoutSeconds = 300; // Default to 5 minutes
528
+ }
529
+ // Find or create the "No Response" category
530
+ let noResponseCategory = (_c = (_b = originalNode.router) === null || _b === void 0 ? void 0 : _b.categories) === null || _c === void 0 ? void 0 : _c.find((cat) => cat.name === 'No Response');
531
+ if (!noResponseCategory) {
532
+ noResponseCategory = {
533
+ uuid: generateUUID(),
534
+ name: 'No Response',
535
+ exit_uuid: generateUUID()
536
+ };
537
+ // Add to router categories
538
+ router.categories = router.categories || [];
539
+ router.categories.push(noResponseCategory);
540
+ }
541
+ waitConfig.timeout = {
542
+ seconds: timeoutSeconds,
543
+ category_uuid: noResponseCategory.uuid
544
+ };
545
+ }
546
+ router.wait = waitConfig;
547
+ return {
548
+ ...originalNode,
549
+ router
550
+ };
551
+ }
552
+ // Get existing router data for preservation
553
+ const existingCategories = ((_d = originalNode.router) === null || _d === void 0 ? void 0 : _d.categories) || [];
554
+ const existingExits = originalNode.exits || [];
555
+ const existingCases = ((_e = originalNode.router) === null || _e === void 0 ? void 0 : _e.cases) || [];
556
+ // Create router and exits using existing data when possible
557
+ const { router, exits } = createWaitForResponseRouter(userRules, existingCategories, existingExits, existingCases);
558
+ // Build final router with wait configuration and result_name
559
+ const finalRouter = {
560
+ ...router,
25
561
  result_name: formData.result_name || 'response'
26
562
  };
563
+ // Build wait configuration based on form data
564
+ const waitConfig = {
565
+ type: 'msg'
566
+ };
567
+ try {
568
+ // Handle timeout configuration
569
+ if (formData.timeout_enabled) {
570
+ // Extract timeout value (handle both string and object formats)
571
+ let timeoutSeconds;
572
+ if (formData.timeout_duration) {
573
+ try {
574
+ timeoutSeconds = parseInt(formData.timeout_duration[0].value, 10);
575
+ }
576
+ catch (e) {
577
+ timeoutSeconds = 300; // Default to 5 minutes
578
+ }
579
+ }
580
+ // Find or create the "No Response" category
581
+ const existingNoResponseCategory = (_g = (_f = originalNode.router) === null || _f === void 0 ? void 0 : _f.categories) === null || _g === void 0 ? void 0 : _g.find((cat) => cat.name === 'No Response');
582
+ const noResponseCategory = existingNoResponseCategory || {
583
+ uuid: generateUUID(),
584
+ name: 'No Response',
585
+ exit_uuid: generateUUID()
586
+ };
587
+ waitConfig.timeout = {
588
+ seconds: timeoutSeconds,
589
+ category_uuid: noResponseCategory.uuid
590
+ };
591
+ // Ensure No Response category and exit exist
592
+ if (!((_h = router.categories) === null || _h === void 0 ? void 0 : _h.some((cat) => cat.name === 'No Response'))) {
593
+ router.categories = router.categories || [];
594
+ router.categories.push(noResponseCategory);
595
+ // Add corresponding exit if it doesn't exist
596
+ if (!exits.some((exit) => exit.uuid === noResponseCategory.exit_uuid)) {
597
+ const noResponseExit = {
598
+ uuid: noResponseCategory.exit_uuid,
599
+ destination_uuid: (existingNoResponseCategory === null || existingNoResponseCategory === void 0 ? void 0 : existingNoResponseCategory.exit_uuid)
600
+ ? ((_k = (_j = originalNode.exits) === null || _j === void 0 ? void 0 : _j.find((exit) => exit.uuid === existingNoResponseCategory.exit_uuid)) === null || _k === void 0 ? void 0 : _k.destination_uuid) || null
601
+ : null
602
+ };
603
+ exits.push(noResponseExit);
604
+ }
605
+ }
606
+ }
607
+ else {
608
+ // Remove "No Response" category if timeout is disabled
609
+ if (router.categories) {
610
+ const noResponseCategoryIndex = router.categories.findIndex((cat) => cat.name === 'No Response');
611
+ if (noResponseCategoryIndex !== -1) {
612
+ const noResponseCategory = router.categories[noResponseCategoryIndex];
613
+ // Remove the category
614
+ router.categories.splice(noResponseCategoryIndex, 1);
615
+ // Remove corresponding exit
616
+ const exitIndex = exits.findIndex((exit) => exit.uuid === noResponseCategory.exit_uuid);
617
+ if (exitIndex !== -1) {
618
+ exits.splice(exitIndex, 1);
619
+ }
620
+ }
621
+ }
622
+ }
623
+ }
624
+ catch (error) {
625
+ console.error('Error processing timeout configuration:', error);
626
+ // Continue without timeout in case of error
627
+ }
628
+ finalRouter.wait = waitConfig;
27
629
  return {
28
630
  ...originalNode,
29
- router
631
+ router: finalRouter,
632
+ exits: exits
30
633
  };
31
634
  }
32
635
  };