@nyaruka/temba-components 0.129.8 → 0.129.10

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 (282) hide show
  1. package/CHANGELOG.md +37 -3
  2. package/demo/data/flows/sample-flow.json +186 -96
  3. package/demo/test-colorpicker.html +30 -0
  4. package/dist/temba-components.js +1126 -1111
  5. package/dist/temba-components.js.map +1 -1
  6. package/out-tsc/src/events.js.map +1 -1
  7. package/out-tsc/src/excellent/helpers.js +2 -2
  8. package/out-tsc/src/excellent/helpers.js.map +1 -1
  9. package/out-tsc/src/flow/CanvasNode.js +25 -7
  10. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  11. package/out-tsc/src/flow/Editor.js +11 -1
  12. package/out-tsc/src/flow/Editor.js.map +1 -1
  13. package/out-tsc/src/flow/NodeEditor.js +133 -290
  14. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  15. package/out-tsc/src/flow/actions/add_input_labels.js +40 -0
  16. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  17. package/out-tsc/src/flow/actions/call_llm.js +56 -3
  18. package/out-tsc/src/flow/actions/call_llm.js.map +1 -1
  19. package/out-tsc/src/flow/actions/call_webhook.js +1 -1
  20. package/out-tsc/src/flow/actions/call_webhook.js.map +1 -1
  21. package/out-tsc/src/flow/actions/open_ticket.js +65 -3
  22. package/out-tsc/src/flow/actions/open_ticket.js.map +1 -1
  23. package/out-tsc/src/flow/actions/set_run_result.js +75 -0
  24. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  25. package/out-tsc/src/flow/config.js +4 -0
  26. package/out-tsc/src/flow/config.js.map +1 -1
  27. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +227 -0
  28. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -0
  29. package/out-tsc/src/flow/nodes/split_by_ticket.js +18 -0
  30. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -0
  31. package/out-tsc/src/flow/nodes/wait_for_response.js +27 -1
  32. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  33. package/out-tsc/src/flow/types.js +0 -65
  34. package/out-tsc/src/flow/types.js.map +1 -1
  35. package/out-tsc/src/form/ArrayEditor.js +63 -117
  36. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  37. package/out-tsc/src/form/BaseListEditor.js +4 -3
  38. package/out-tsc/src/form/BaseListEditor.js.map +1 -1
  39. package/out-tsc/src/form/Checkbox.js +77 -24
  40. package/out-tsc/src/form/Checkbox.js.map +1 -1
  41. package/out-tsc/src/form/ColorPicker.js +28 -40
  42. package/out-tsc/src/form/ColorPicker.js.map +1 -1
  43. package/out-tsc/src/form/Completion.js +44 -53
  44. package/out-tsc/src/form/Completion.js.map +1 -1
  45. package/out-tsc/src/form/Compose.js +7 -8
  46. package/out-tsc/src/form/Compose.js.map +1 -1
  47. package/out-tsc/src/form/ContactSearch.js +3 -4
  48. package/out-tsc/src/form/ContactSearch.js.map +1 -1
  49. package/out-tsc/src/form/DatePicker.js +29 -36
  50. package/out-tsc/src/form/DatePicker.js.map +1 -1
  51. package/out-tsc/src/form/{FormField.js → FieldElement.js} +81 -53
  52. package/out-tsc/src/form/FieldElement.js.map +1 -0
  53. package/out-tsc/src/form/FieldRenderer.js +306 -0
  54. package/out-tsc/src/form/FieldRenderer.js.map +1 -0
  55. package/out-tsc/src/form/ImagePicker.js +122 -126
  56. package/out-tsc/src/form/ImagePicker.js.map +1 -1
  57. package/out-tsc/src/form/KeyValueEditor.js +41 -37
  58. package/out-tsc/src/form/KeyValueEditor.js.map +1 -1
  59. package/out-tsc/src/form/MessageEditor.js +55 -63
  60. package/out-tsc/src/form/MessageEditor.js.map +1 -1
  61. package/out-tsc/src/form/TembaSlider.js +3 -3
  62. package/out-tsc/src/form/TembaSlider.js.map +1 -1
  63. package/out-tsc/src/form/TemplateEditor.js +3 -3
  64. package/out-tsc/src/form/TemplateEditor.js.map +1 -1
  65. package/out-tsc/src/form/TextInput.js +23 -27
  66. package/out-tsc/src/form/TextInput.js.map +1 -1
  67. package/out-tsc/src/form/select/Select.js +57 -35
  68. package/out-tsc/src/form/select/Select.js.map +1 -1
  69. package/out-tsc/src/form/select/UserSelect.js +8 -9
  70. package/out-tsc/src/form/select/UserSelect.js.map +1 -1
  71. package/out-tsc/src/form/select/WorkspaceSelect.js +7 -8
  72. package/out-tsc/src/form/select/WorkspaceSelect.js.map +1 -1
  73. package/out-tsc/src/live/ContactChat.js +62 -44
  74. package/out-tsc/src/live/ContactChat.js.map +1 -1
  75. package/out-tsc/src/live/ContactFieldEditor.js.map +1 -1
  76. package/out-tsc/src/markdown.js +13 -11
  77. package/out-tsc/src/markdown.js.map +1 -1
  78. package/out-tsc/temba-modules.js +3 -2
  79. package/out-tsc/temba-modules.js.map +1 -1
  80. package/out-tsc/test/ActionHelper.js +2 -0
  81. package/out-tsc/test/ActionHelper.js.map +1 -1
  82. package/out-tsc/test/NodeHelper.js +148 -0
  83. package/out-tsc/test/NodeHelper.js.map +1 -0
  84. package/out-tsc/test/actions/call_llm.test.js +103 -0
  85. package/out-tsc/test/actions/call_llm.test.js.map +1 -0
  86. package/out-tsc/test/nodes/split_by_llm_categorize.test.js +532 -0
  87. package/out-tsc/test/nodes/split_by_llm_categorize.test.js.map +1 -0
  88. package/out-tsc/test/nodes/split_by_random.test.js +150 -0
  89. package/out-tsc/test/nodes/split_by_random.test.js.map +1 -0
  90. package/out-tsc/test/nodes/wait_for_digits.test.js +150 -0
  91. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -0
  92. package/out-tsc/test/nodes/wait_for_response.test.js +171 -0
  93. package/out-tsc/test/nodes/wait_for_response.test.js.map +1 -0
  94. package/out-tsc/test/temba-add-input-labels.test.js +70 -0
  95. package/out-tsc/test/temba-add-input-labels.test.js.map +1 -0
  96. package/out-tsc/test/temba-checkbox.test.js +16 -0
  97. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  98. package/out-tsc/test/temba-field-renderer.test.js +296 -0
  99. package/out-tsc/test/temba-field-renderer.test.js.map +1 -0
  100. package/out-tsc/test/temba-integration-markdown.test.js +2 -4
  101. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  102. package/out-tsc/test/temba-markdown.test.js +1 -1
  103. package/out-tsc/test/temba-markdown.test.js.map +1 -1
  104. package/out-tsc/test/temba-node-editor.test.js +400 -0
  105. package/out-tsc/test/temba-node-editor.test.js.map +1 -1
  106. package/out-tsc/test/temba-select.test.js +6 -3
  107. package/out-tsc/test/temba-select.test.js.map +1 -1
  108. package/out-tsc/test/temba-slider.test.js +0 -1
  109. package/out-tsc/test/temba-slider.test.js.map +1 -1
  110. package/out-tsc/test/temba-webchat.test.js +1 -1
  111. package/out-tsc/test/temba-webchat.test.js.map +1 -1
  112. package/package.json +1 -1
  113. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  114. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  115. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  116. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  117. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  118. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  119. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  120. package/screenshots/truth/actions/call_llm/render/information-extraction.png +0 -0
  121. package/screenshots/truth/actions/call_llm/render/sentiment-analysis.png +0 -0
  122. package/screenshots/truth/actions/call_llm/render/summarization.png +0 -0
  123. package/screenshots/truth/actions/call_llm/render/translation-task.png +0 -0
  124. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  125. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  126. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  127. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  128. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  129. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  130. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  131. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  132. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  133. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  134. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  135. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  136. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  137. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  138. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  139. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  140. package/screenshots/truth/checkbox/checkbox-no-label-no-background-hover.png +0 -0
  141. package/screenshots/truth/checkbox/checkbox-with-help-text.png +0 -0
  142. package/screenshots/truth/checkbox/checked.png +0 -0
  143. package/screenshots/truth/checkbox/default.png +0 -0
  144. package/screenshots/truth/colorpicker/default.png +0 -0
  145. package/screenshots/truth/colorpicker/focused.png +0 -0
  146. package/screenshots/truth/colorpicker/initialized.png +0 -0
  147. package/screenshots/truth/colorpicker/selected.png +0 -0
  148. package/screenshots/truth/editor/router.png +0 -0
  149. package/screenshots/truth/editor/send_msg.png +0 -0
  150. package/screenshots/truth/editor/set_contact_language.png +0 -0
  151. package/screenshots/truth/editor/set_contact_name.png +0 -0
  152. package/screenshots/truth/editor/set_run_result.png +0 -0
  153. package/screenshots/truth/editor/wait.png +0 -0
  154. package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
  155. package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
  156. package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
  157. package/screenshots/truth/field-renderer/context-comparison.png +0 -0
  158. package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
  159. package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
  160. package/screenshots/truth/field-renderer/select-multi.png +0 -0
  161. package/screenshots/truth/field-renderer/select-no-label.png +0 -0
  162. package/screenshots/truth/field-renderer/select-with-label.png +0 -0
  163. package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
  164. package/screenshots/truth/field-renderer/text-no-label.png +0 -0
  165. package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
  166. package/screenshots/truth/field-renderer/text-with-label.png +0 -0
  167. package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
  168. package/screenshots/truth/field-renderer/textarea-with-label.png +0 -0
  169. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
  170. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  171. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  172. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  173. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  174. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  175. package/screenshots/truth/nodes/split_by_llm_categorize/render/basic-categorization.png +0 -0
  176. package/screenshots/truth/nodes/split_by_llm_categorize/render/custom-input-and-result-name.png +0 -0
  177. package/screenshots/truth/nodes/split_by_llm_categorize/render/feedback-categorization.png +0 -0
  178. package/screenshots/truth/nodes/split_by_llm_categorize/render/many-categories.png +0 -0
  179. package/screenshots/truth/nodes/split_by_llm_categorize/render/minimal-categories.png +0 -0
  180. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  181. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  182. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  183. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  184. package/screenshots/truth/nodes/split_by_random/render/ab-test-multiple-variants.png +0 -0
  185. package/screenshots/truth/nodes/split_by_random/render/sampling-split.png +0 -0
  186. package/screenshots/truth/nodes/split_by_random/render/three-way-split.png +0 -0
  187. package/screenshots/truth/nodes/split_by_random/render/two-bucket-split.png +0 -0
  188. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  189. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  190. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  191. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  192. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  193. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  194. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  195. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
  196. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  197. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  198. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  199. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  200. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  201. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  202. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  203. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  204. package/screenshots/truth/omnibox/selected.png +0 -0
  205. package/screenshots/truth/run-list/basic.png +0 -0
  206. package/screenshots/truth/select/functions.png +0 -0
  207. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  208. package/screenshots/truth/select/search-enabled.png +0 -0
  209. package/src/events.ts +12 -6
  210. package/src/excellent/helpers.ts +2 -2
  211. package/src/flow/CanvasNode.ts +22 -1
  212. package/src/flow/Editor.ts +12 -1
  213. package/src/flow/NodeEditor.ts +186 -374
  214. package/src/flow/actions/add_input_labels.ts +45 -0
  215. package/src/flow/actions/call_llm.ts +57 -3
  216. package/src/flow/actions/call_webhook.ts +1 -1
  217. package/src/flow/actions/open_ticket.ts +74 -3
  218. package/src/flow/actions/set_run_result.ts +83 -0
  219. package/src/flow/config.ts +4 -0
  220. package/src/flow/nodes/split_by_llm_categorize.ts +277 -0
  221. package/src/flow/nodes/split_by_ticket.ts +19 -0
  222. package/src/flow/nodes/wait_for_response.ts +28 -1
  223. package/src/flow/types.ts +26 -127
  224. package/src/form/ArrayEditor.ts +79 -139
  225. package/src/form/BaseListEditor.ts +4 -4
  226. package/src/form/Checkbox.ts +81 -24
  227. package/src/form/ColorPicker.ts +31 -43
  228. package/src/form/Completion.ts +49 -56
  229. package/src/form/Compose.ts +8 -8
  230. package/src/form/ContactSearch.ts +3 -4
  231. package/src/form/DatePicker.ts +32 -38
  232. package/src/form/{FormField.ts → FieldElement.ts} +108 -55
  233. package/src/form/FieldRenderer.ts +466 -0
  234. package/src/form/ImagePicker.ts +107 -110
  235. package/src/form/KeyValueEditor.ts +43 -39
  236. package/src/form/MessageEditor.ts +61 -67
  237. package/src/form/TembaSlider.ts +3 -3
  238. package/src/form/TemplateEditor.ts +3 -3
  239. package/src/form/TextInput.ts +26 -29
  240. package/src/form/select/Select.ts +63 -37
  241. package/src/form/select/UserSelect.ts +10 -11
  242. package/src/form/select/WorkspaceSelect.ts +9 -10
  243. package/src/live/ContactChat.ts +62 -47
  244. package/src/live/ContactFieldEditor.ts +2 -2
  245. package/src/markdown.ts +19 -11
  246. package/src/store/flow-definition.d.ts +5 -2
  247. package/static/api/labels.json +31 -0
  248. package/static/api/topics.json +24 -9
  249. package/static/api/users.json +35 -16
  250. package/static/css/temba-components.css +3 -3
  251. package/stress-test.js +18 -13
  252. package/temba-modules.ts +3 -2
  253. package/test/ActionHelper.ts +2 -0
  254. package/test/NodeHelper.ts +184 -0
  255. package/test/actions/call_llm.test.ts +137 -0
  256. package/test/nodes/README.md +78 -0
  257. package/test/nodes/split_by_llm_categorize.test.ts +698 -0
  258. package/test/nodes/split_by_random.test.ts +177 -0
  259. package/test/nodes/wait_for_digits.test.ts +176 -0
  260. package/test/nodes/wait_for_response.test.ts +206 -0
  261. package/test/temba-add-input-labels.test.ts +87 -0
  262. package/test/temba-checkbox.test.ts +26 -0
  263. package/test/temba-field-renderer.test.ts +482 -0
  264. package/test/temba-integration-markdown.test.ts +2 -4
  265. package/test/temba-markdown.test.ts +1 -1
  266. package/test/temba-node-editor.test.ts +496 -0
  267. package/test/temba-select.test.ts +6 -6
  268. package/test/temba-slider.test.ts +0 -1
  269. package/test/temba-webchat.test.ts +1 -1
  270. package/test-assets/contacts/history.json +7 -20
  271. package/test-assets/select/llms.json +18 -0
  272. package/web-dev-mock.mjs +96 -6
  273. package/web-dev-server.config.mjs +29 -7
  274. package/out-tsc/src/form/FormElement.js +0 -67
  275. package/out-tsc/src/form/FormElement.js.map +0 -1
  276. package/out-tsc/src/form/FormField.js.map +0 -1
  277. package/out-tsc/test/temba-formfield.test.js +0 -94
  278. package/out-tsc/test/temba-formfield.test.js.map +0 -1
  279. package/src/form/FormElement.ts +0 -69
  280. package/test/temba-flow-editor.test.ts.backup +0 -563
  281. package/test/temba-formfield.test.ts +0 -121
  282. package/test/temba-utils-index.test.ts.backup +0 -1737
@@ -3,24 +3,43 @@
3
3
  "previous": null,
4
4
  "results": [
5
5
  {
6
- "uuid": "user-1",
7
- "email": "admin@example.com",
8
- "first_name": "Admin",
9
- "last_name": "User",
10
- "username": "admin",
11
- "is_active": true,
12
- "is_staff": true,
13
- "date_joined": "2024-01-01T00:00:00Z"
6
+ "uuid": "c0f5b431-35e9-429c-9d57-5fee2fac46a3",
7
+ "email": "eric+marion+berry@textit.com",
8
+ "first_name": "Marion",
9
+ "last_name": "Berry",
10
+ "name": "Marion Berry",
11
+ "role": "agent",
12
+ "team": {
13
+ "uuid": "15236c1e-9375-4f84-bb48-ec64283d1eb9",
14
+ "name": "All Topics"
15
+ },
16
+ "created_on": "2023-04-05T21:11:31.909765Z",
17
+ "avatar": null
14
18
  },
15
19
  {
16
- "uuid": "user-2",
17
- "email": "editor@example.com",
18
- "first_name": "Editor",
19
- "last_name": "User",
20
- "username": "editor",
21
- "is_active": true,
22
- "is_staff": false,
23
- "date_joined": "2024-01-01T00:00:00Z"
20
+ "uuid": "ae79dd5b-8c34-4602-a2c1-1e4db2419f0f",
21
+ "email": "eric@textit.com",
22
+ "first_name": "Eric",
23
+ "last_name": "Newcomer",
24
+ "name": "Eric Newcomer",
25
+ "role": "administrator",
26
+ "team": null,
27
+ "created_on": "2013-02-26T21:19:44Z",
28
+ "avatar": "https://dl-textit.s3.amazonaws.com/avatars/4/b6d756224c61435bb36b57ae03b83359.jpg"
29
+ },
30
+ {
31
+ "uuid": "f8e2a1b5-9c7d-4e6f-8a3b-1c5e9f2d4a6b",
32
+ "email": "sarah.smith@textit.com",
33
+ "first_name": "Sarah",
34
+ "last_name": "Smith",
35
+ "name": "Sarah Smith",
36
+ "role": "agent",
37
+ "team": {
38
+ "uuid": "15236c1e-9375-4f84-bb48-ec64283d1eb9",
39
+ "name": "All Topics"
40
+ },
41
+ "created_on": "2023-06-15T14:22:18.456789Z",
42
+ "avatar": null
24
43
  }
25
44
  ]
26
45
  }
@@ -61,7 +61,7 @@
61
61
  --color-text-light: rgba(255, 255, 255, 1);
62
62
  --color-text-dark: rgba(0, 0, 0, 0.8);
63
63
  --color-text-dark-secondary: rgba(0, 0, 0, 0.25);
64
- --color-text-help: rgba(0, 0, 0, 0.35);
64
+ --color-text-help: rgb(120, 120, 120);
65
65
  --color-tertiary: rgb(var(--tertiary-rgb));
66
66
 
67
67
  --help-text-size: 0.85em;
@@ -125,8 +125,8 @@
125
125
  --header-bg: var(--color-primary-dark);
126
126
  --header-text: var(--color-text-light);
127
127
 
128
- --temba-textinput-padding: 9px;
129
- --temba-textinput-font-size: 13px;
128
+ --temba-textinput-padding: 9px 14px;
129
+ --temba-textinput-font-size: 14px;
130
130
 
131
131
  --options-block-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.03);
132
132
  --options-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
package/stress-test.js CHANGED
@@ -11,7 +11,7 @@ let maxRuns = 10;
11
11
  // Parse arguments
12
12
  for (let i = 0; i < args.length; i++) {
13
13
  const arg = args[i];
14
-
14
+
15
15
  if (arg.startsWith('--runs=')) {
16
16
  maxRuns = parseInt(arg.split('=')[1]);
17
17
  if (isNaN(maxRuns) || maxRuns <= 0) {
@@ -28,12 +28,16 @@ for (let i = 0; i < args.length; i++) {
28
28
  // Validate test file
29
29
  if (!testFile) {
30
30
  console.error('❌ Usage: yarn stress-test <test-file> [--runs=N]');
31
- console.error(' Example: yarn stress-test test/temba-webchat.test.ts --runs=100');
31
+ console.error(
32
+ ' Example: yarn stress-test test/temba-webchat.test.ts --runs=100'
33
+ );
32
34
  process.exit(1);
33
35
  }
34
36
 
35
37
  if (!testFile.startsWith('test/') || !testFile.endsWith('.test.ts')) {
36
- console.error('❌ Test file must be in the test/ directory and end with .test.ts');
38
+ console.error(
39
+ '❌ Test file must be in the test/ directory and end with .test.ts'
40
+ );
37
41
  process.exit(1);
38
42
  }
39
43
 
@@ -51,21 +55,21 @@ const startTime = performance.now();
51
55
  try {
52
56
  while (run <= maxRuns) {
53
57
  const runStartTime = performance.now();
54
-
58
+
55
59
  process.stdout.write(`Run ${run.toString().padStart(3)}/${maxRuns}: `);
56
-
60
+
57
61
  try {
58
62
  // Run the test with minimal output
59
- const result = execSync(`yarn test ${testFile}`, {
63
+ const result = execSync(`yarn test ${testFile}`, {
60
64
  encoding: 'utf-8',
61
65
  stdio: ['pipe', 'pipe', 'pipe']
62
66
  });
63
-
67
+
64
68
  const runEndTime = performance.now();
65
69
  const runTime = runEndTime - runStartTime;
66
70
  runTimes.push(runTime);
67
71
  totalTime += runTime;
68
-
72
+
69
73
  // Check if the test actually passed by looking for success indicators
70
74
  if (result.includes('all tests passed') || result.includes('0 failed')) {
71
75
  console.log(`✅ PASS (${(runTime / 1000).toFixed(2)}s)`);
@@ -75,11 +79,10 @@ try {
75
79
  failures++;
76
80
  break;
77
81
  }
78
-
79
82
  } catch (error) {
80
83
  const runEndTime = performance.now();
81
84
  const runTime = runEndTime - runStartTime;
82
-
85
+
83
86
  console.log(`❌ FAIL (${(runTime / 1000).toFixed(2)}s)`);
84
87
  console.log('');
85
88
  console.log('💥 Test failed on run', run);
@@ -94,7 +97,7 @@ try {
94
97
  failures++;
95
98
  break;
96
99
  }
97
-
100
+
98
101
  run++;
99
102
  }
100
103
  } catch (error) {
@@ -112,14 +115,16 @@ console.log('==================');
112
115
  console.log(`Test file: ${testFile}`);
113
116
  console.log(`Completed runs: ${run - 1}/${maxRuns}`);
114
117
  console.log(`Failures: ${failures}`);
115
- console.log(`Success rate: ${(((run - 1 - failures) / (run - 1)) * 100).toFixed(1)}%`);
118
+ console.log(
119
+ `Success rate: ${(((run - 1 - failures) / (run - 1)) * 100).toFixed(1)}%`
120
+ );
116
121
  console.log('');
117
122
 
118
123
  if (runTimes.length > 0) {
119
124
  const avgTime = runTimes.reduce((a, b) => a + b, 0) / runTimes.length;
120
125
  const minTime = Math.min(...runTimes);
121
126
  const maxTime = Math.max(...runTimes);
122
-
127
+
123
128
  console.log('⏱️ Timing Statistics');
124
129
  console.log('=====================');
125
130
  console.log(`Total time: ${(totalTestTime / 1000).toFixed(2)}s`);
package/temba-modules.ts CHANGED
@@ -6,7 +6,7 @@ import { Completion } from './src/form/Completion';
6
6
  import { Modax } from './src/layout/Modax';
7
7
  import { Dialog } from './src/layout/Dialog';
8
8
  import { Button } from './src/display/Button';
9
- import { FormField } from './src/form/FormField';
9
+ import { FieldElement } from './src/form/FieldElement';
10
10
  import { Loading } from './src/display/Loading';
11
11
  import { CharCount } from './src/display/CharCount';
12
12
  import { Options } from './src/display/Options';
@@ -103,7 +103,8 @@ addCustomElement('temba-field-manager', FieldManager);
103
103
  addCustomElement('temba-urn', ContactUrn);
104
104
  addCustomElement('temba-content-menu', ContentMenu);
105
105
 
106
- addCustomElement('temba-field', FormField);
106
+ // Note: FieldElement is a base class and not directly instantiated as a custom element
107
+ export { FieldElement };
107
108
  addCustomElement('temba-dialog', Dialog);
108
109
  addCustomElement('temba-modax', Modax);
109
110
  addCustomElement('temba-charcount', CharCount);
@@ -8,6 +8,8 @@ import '../temba-modules';
8
8
  /**
9
9
  * Generic action test framework
10
10
  * Tests the complete action lifecycle: render → edit → save → validate
11
+ *
12
+ * For node configuration testing, see NodeHelper.ts
11
13
  */
12
14
  export class ActionTest<T extends Action> {
13
15
  constructor(private actionConfig: any, private actionName: string) {}
@@ -0,0 +1,184 @@
1
+ import { fixture, expect } from '@open-wc/testing';
2
+ import { html } from 'lit';
3
+ import { Node } from '../src/store/flow-definition';
4
+ import { assertScreenshot, getClip } from './utils.test';
5
+ import { Editor } from '../src/flow/Editor';
6
+ import '../temba-modules';
7
+
8
+ /**
9
+ * Generic node test framework
10
+ * Tests the complete node lifecycle: render → edit → save → validate
11
+ *
12
+ * This is the node configuration equivalent of ActionHelper.ts for action configurations.
13
+ * It provides uniform testing for all types of nodes: simple wait nodes, router-based
14
+ * split nodes, and complex form-configured nodes.
15
+ */
16
+ export class NodeTest<T extends Node> {
17
+ constructor(private nodeConfig: any, private nodeName: string) {}
18
+
19
+ /**
20
+ * Renders a node in the flow editor and returns the flow node
21
+ */
22
+ private async renderNode(node: T, nodeUI: any): Promise<HTMLElement> {
23
+ const mockDefinition = {
24
+ nodes: [node],
25
+ _ui: {
26
+ nodes: {
27
+ [node.uuid]: {
28
+ type: nodeUI.type,
29
+ position: { left: 50, top: 50 },
30
+ ...nodeUI
31
+ }
32
+ }
33
+ }
34
+ };
35
+
36
+ const editor = (await fixture(html`
37
+ <temba-flow-editor>
38
+ <div id="canvas"></div>
39
+ </temba-flow-editor>
40
+ `)) as Editor;
41
+
42
+ (editor as any).definition = mockDefinition;
43
+ (editor as any).canvasSize = { width: 400, height: 300 };
44
+ await editor.updateComplete;
45
+
46
+ const flowNode = editor.querySelector('temba-flow-node') as HTMLElement;
47
+ expect(flowNode).to.exist;
48
+
49
+ return flowNode;
50
+ }
51
+
52
+ /**
53
+ * Opens the node editor for a node and returns the editor element
54
+ */
55
+ private async openNodeEditor(node: T, nodeUI: any): Promise<HTMLElement> {
56
+ const nodeEditor = (await fixture(html`
57
+ <temba-node-editor
58
+ .node=${node}
59
+ .nodeUI=${nodeUI}
60
+ .isOpen=${true}
61
+ ></temba-node-editor>
62
+ `)) as HTMLElement;
63
+
64
+ await (nodeEditor as any).updateComplete;
65
+
66
+ // Wait for form data initialization if needed
67
+ await new Promise((resolve) => setTimeout(resolve, 200));
68
+ await (nodeEditor as any).updateComplete;
69
+
70
+ expect(nodeEditor).to.exist;
71
+
72
+ return nodeEditor;
73
+ }
74
+
75
+ /**
76
+ * Takes a screenshot of the dialog container within a node editor
77
+ */
78
+ private async assertDialogScreenshot(
79
+ el: HTMLElement,
80
+ screenshotName: string
81
+ ) {
82
+ const dialog = el.shadowRoot
83
+ ?.querySelector('temba-dialog')
84
+ ?.shadowRoot?.querySelector('.dialog-container') as HTMLElement;
85
+ await assertScreenshot(screenshotName, getClip(dialog));
86
+ }
87
+
88
+ /**
89
+ * Complete test for a node configuration
90
+ * 1. Renders the node in a flow node (with screenshot)
91
+ * 2. Opens the node editor (with screenshot)
92
+ * 3. Simulates save and validates round-trip conversion
93
+ */
94
+ async testNode(node: T, nodeUI: any, testName: string) {
95
+ it(`${testName}`, async () => {
96
+ // Step 1: Render node in flow node
97
+ const flowNode = await this.renderNode(node, nodeUI);
98
+
99
+ // For execute_actions nodes, check for .body, for router nodes check for .router or .categories
100
+ const hasContent =
101
+ flowNode.querySelector('.body') ||
102
+ flowNode.querySelector('.router') ||
103
+ flowNode.querySelector('.categories') ||
104
+ flowNode.querySelector('.action') ||
105
+ flowNode.textContent?.trim();
106
+
107
+ expect(hasContent).to.exist;
108
+ await assertScreenshot(
109
+ `nodes/${this.nodeName}/render/${testName}`,
110
+ getClip(flowNode)
111
+ );
112
+
113
+ // Step 2: Open node editor
114
+ const nodeEditor = await this.openNodeEditor(node, nodeUI);
115
+ await this.assertDialogScreenshot(
116
+ nodeEditor,
117
+ `nodes/${this.nodeName}/editor/${testName}`
118
+ );
119
+
120
+ // Step 3: Test round-trip conversion (simulates save workflow)
121
+ if (this.nodeConfig.toFormData && this.nodeConfig.fromFormData) {
122
+ const formData = this.nodeConfig.toFormData(node);
123
+ const convertedNode = this.nodeConfig.fromFormData(formData, node) as T;
124
+
125
+ // Validate the round trip worked
126
+ expect(convertedNode.uuid).to.equal(node.uuid);
127
+
128
+ // Validate the converted node has expected structure
129
+ expect(convertedNode).to.have.property('actions');
130
+ expect(convertedNode).to.have.property('exits');
131
+
132
+ expect(convertedNode).to.deep.equal(node);
133
+ }
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Run basic property tests
139
+ */
140
+ testBasicProperties() {
141
+ it('has correct basic properties', () => {
142
+ expect(this.nodeConfig.type).to.be.a('string');
143
+
144
+ // Name is optional - only some node configs have it
145
+ if (this.nodeConfig.name) {
146
+ expect(this.nodeConfig.name).to.be.a('string');
147
+ }
148
+
149
+ // Color is optional
150
+ if (this.nodeConfig.color) {
151
+ expect(this.nodeConfig.color).to.be.a('string');
152
+ }
153
+
154
+ // toFormData and fromFormData are optional - only needed for complex data transformations
155
+ if (this.nodeConfig.toFormData) {
156
+ expect(this.nodeConfig.toFormData).to.be.a('function');
157
+ }
158
+ if (this.nodeConfig.fromFormData) {
159
+ expect(this.nodeConfig.fromFormData).to.be.a('function');
160
+ }
161
+
162
+ // Form configuration is optional
163
+ if (this.nodeConfig.form) {
164
+ expect(this.nodeConfig.form).to.be.an('object');
165
+ }
166
+
167
+ // Layout is optional
168
+ if (this.nodeConfig.layout) {
169
+ expect(this.nodeConfig.layout).to.be.an('array');
170
+ }
171
+
172
+ // Router config is optional
173
+ if (this.nodeConfig.router) {
174
+ expect(this.nodeConfig.router).to.be.an('object');
175
+ expect(this.nodeConfig.router.type).to.exist;
176
+ }
177
+
178
+ // Render function is optional
179
+ if (this.nodeConfig.render) {
180
+ expect(this.nodeConfig.render).to.be.a('function');
181
+ }
182
+ });
183
+ }
184
+ }
@@ -0,0 +1,137 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { call_llm } from '../../src/flow/actions/call_llm';
3
+ import { CallLLM } from '../../src/store/flow-definition';
4
+ import { ActionTest } from '../ActionHelper';
5
+
6
+ /**
7
+ * Test suite for the call_llm action configuration.
8
+ */
9
+ describe('call_llm action config', () => {
10
+ const helper = new ActionTest(call_llm, 'call_llm');
11
+
12
+ describe('basic properties', () => {
13
+ helper.testBasicProperties();
14
+
15
+ it('has correct name', () => {
16
+ expect(call_llm.name).to.equal('Call AI');
17
+ });
18
+
19
+ it('has form configuration', () => {
20
+ expect(call_llm.form).to.exist;
21
+ expect(call_llm.form.llm).to.exist;
22
+ expect(call_llm.form.instructions).to.exist;
23
+ expect(call_llm.form.input).to.exist;
24
+ });
25
+
26
+ it('has layout configuration', () => {
27
+ expect(call_llm.layout).to.exist;
28
+ expect(call_llm.layout).to.deep.equal(['llm', 'input', 'instructions']);
29
+ });
30
+
31
+ it('has data transformation functions', () => {
32
+ expect(call_llm.toFormData).to.be.a('function');
33
+ expect(call_llm.fromFormData).to.be.a('function');
34
+ });
35
+ });
36
+
37
+ describe('data transformations', () => {
38
+ it('converts action to form data correctly', () => {
39
+ const action: CallLLM = {
40
+ uuid: 'test-llm-1',
41
+ type: 'call_llm',
42
+ input: '@input',
43
+ llm: { uuid: 'gpt-4', name: 'GPT 4.1' },
44
+ instructions: 'Translate to French',
45
+ result_name: 'translated_text'
46
+ };
47
+
48
+ const formData = call_llm.toFormData(action);
49
+
50
+ expect(formData.uuid).to.equal('test-llm-1');
51
+ expect(formData.llm).to.deep.equal([{ value: 'gpt-4', name: 'GPT 4.1' }]);
52
+ expect(formData.instructions).to.equal('Translate to French');
53
+ expect(formData.input).to.equal('@input');
54
+ });
55
+
56
+ it('converts form data to action correctly', () => {
57
+ const formData = {
58
+ uuid: 'test-llm-2',
59
+ llm: [{ value: 'gpt-5', name: 'GPT 5' }],
60
+ instructions: 'Summarize the following text',
61
+ input: '@input'
62
+ };
63
+
64
+ const action = call_llm.fromFormData(formData) as CallLLM;
65
+
66
+ expect(action.uuid).to.equal('test-llm-2');
67
+ expect(action.type).to.equal('call_llm');
68
+ expect(action.llm).to.deep.equal({ uuid: 'gpt-5', name: 'GPT 5' });
69
+ expect(action.instructions).to.equal('Summarize the following text');
70
+ expect(action.input).to.equal('@input');
71
+ });
72
+
73
+ it('handles empty form data', () => {
74
+ const formData = {
75
+ uuid: 'test-llm-3',
76
+ llm: [],
77
+ instructions: '',
78
+ input: ''
79
+ };
80
+
81
+ const action = call_llm.fromFormData(formData) as CallLLM;
82
+
83
+ expect(action.llm).to.deep.equal({ uuid: '', name: '' });
84
+ expect(action.instructions).to.equal('');
85
+ expect(action.input).to.equal('@input');
86
+ });
87
+ });
88
+
89
+ describe('action scenarios', () => {
90
+ helper.testAction(
91
+ {
92
+ uuid: 'test-action-1',
93
+ type: 'call_llm',
94
+ llm: { uuid: 'gpt-4', name: 'GPT 4.1' },
95
+ instructions: 'Translate to French',
96
+ input: '@input'
97
+ } as CallLLM,
98
+ 'translation-task'
99
+ );
100
+
101
+ helper.testAction(
102
+ {
103
+ uuid: 'test-action-2',
104
+ type: 'call_llm',
105
+ llm: { uuid: 'gpt-5', name: 'GPT 5' },
106
+ instructions:
107
+ 'Analyze the sentiment of the following message and classify it as positive, negative, or neutral. Provide a brief explanation for your classification.',
108
+ input: '@input'
109
+ } as CallLLM,
110
+ 'sentiment-analysis'
111
+ );
112
+
113
+ helper.testAction(
114
+ {
115
+ uuid: 'test-action-3',
116
+ type: 'call_llm',
117
+ llm: { uuid: 'gpt-4', name: 'GPT 4.1' },
118
+ instructions:
119
+ 'Summarize the key points from the conversation above in bullet format.',
120
+ input: '@input'
121
+ } as CallLLM,
122
+ 'summarization'
123
+ );
124
+
125
+ helper.testAction(
126
+ {
127
+ uuid: 'test-action-4',
128
+ type: 'call_llm',
129
+ llm: { uuid: 'gpt-5', name: 'GPT 5' },
130
+ instructions:
131
+ 'Extract any contact information (phone numbers, email addresses) from the text and format them as a JSON object.',
132
+ input: '@input'
133
+ } as CallLLM,
134
+ 'information-extraction'
135
+ );
136
+ });
137
+ });
@@ -0,0 +1,78 @@
1
+ # Node Configuration Tests
2
+
3
+ This directory contains tests for node configurations using the `NodeHelper` testing framework.
4
+
5
+ ## Overview
6
+
7
+ Similar to how the `ActionHelper` provides a uniform testing strategy for action configurations, the `NodeHelper` class provides a structured approach to testing node configurations. It handles:
8
+
9
+ 1. **Rendering Tests**: Verifies nodes render correctly in the flow editor
10
+ 2. **Editor Tests**: Verifies the node editor opens and displays correctly
11
+ 3. **Round-trip Conversion**: Tests form data conversion for nodes with `toFormData`/`fromFormData`
12
+ 4. **Screenshot Testing**: Captures visual screenshots for regression testing
13
+
14
+ ## Test Structure
15
+
16
+ Each node test file follows this pattern:
17
+
18
+ ```typescript
19
+ import { expect } from '@open-wc/testing';
20
+ import { node_config } from '../../src/flow/nodes/node_config';
21
+ import { Node } from '../../src/store/flow-definition';
22
+ import { NodeTest } from '../NodeHelper';
23
+
24
+ describe('node_config node config', () => {
25
+ const helper = new NodeTest(node_config, 'node_config');
26
+
27
+ describe('basic properties', () => {
28
+ helper.testBasicProperties();
29
+
30
+ // Additional property tests...
31
+ });
32
+
33
+ describe('node scenarios', () => {
34
+ helper.testNode(nodeData, nodeUI, 'test-name');
35
+
36
+ // More scenarios...
37
+ });
38
+
39
+ // Optional: data transformation tests for nodes with form configuration
40
+ describe('data transformation', () => {
41
+ // Round-trip conversion tests...
42
+ });
43
+ });
44
+ ```
45
+
46
+ ## Node Types Covered
47
+
48
+ ### Simple Nodes
49
+
50
+ - `wait_for_digits`: Basic node with no form configuration
51
+ - `wait_for_audio`, `wait_for_image`, etc.: Similar wait nodes
52
+
53
+ ### Form-Configured Nodes
54
+
55
+ - `wait_for_response`: Node with form fields and data transformation
56
+ - `split_by_llm_categorize`: Complex node with form configuration
57
+
58
+ ### Router-Based Nodes
59
+
60
+ - `split_by_random`: Random distribution node
61
+ - Other split nodes with router configurations
62
+
63
+ ## Screenshots
64
+
65
+ Screenshots are automatically generated and stored in:
66
+
67
+ - `screenshots/nodes/{node_name}/render/{test_name}.png` - Flow editor rendering
68
+ - `screenshots/nodes/{node_name}/editor/{test_name}.png` - Node editor dialog
69
+
70
+ ## Running Tests
71
+
72
+ ```bash
73
+ # Run all node tests
74
+ yarn test test/nodes/*.test.ts --no-watch
75
+
76
+ # Run specific node test
77
+ yarn test test/nodes/wait_for_response.test.ts --no-watch
78
+ ```