@nyaruka/temba-components 0.129.9 → 0.129.11

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 (323) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/demo/data/flows/sample-flow.json +96 -56
  3. package/demo/test-colorpicker.html +30 -0
  4. package/dist/temba-components.js +896 -934
  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/Editor.js +9 -6
  8. package/out-tsc/src/flow/Editor.js.map +1 -1
  9. package/out-tsc/src/flow/actions/set_contact_channel.js +1 -1
  10. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  11. package/out-tsc/src/flow/actions/set_contact_field.js +1 -1
  12. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  13. package/out-tsc/src/flow/actions/set_contact_language.js +1 -1
  14. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  15. package/out-tsc/src/flow/actions/set_contact_status.js +1 -1
  16. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  17. package/out-tsc/src/flow/config.js +2 -8
  18. package/out-tsc/src/flow/config.js.map +1 -1
  19. package/out-tsc/src/flow/nodes/split_by_llm.js +101 -0
  20. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -0
  21. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +4 -89
  22. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  23. package/out-tsc/src/flow/nodes/split_by_subflow.js +123 -3
  24. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  25. package/out-tsc/src/flow/nodes/split_by_ticket.js +115 -13
  26. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
  27. package/out-tsc/src/flow/nodes/split_by_webhook.js +160 -12
  28. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  29. package/out-tsc/src/form/ArrayEditor.js +45 -56
  30. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  31. package/out-tsc/src/form/BaseListEditor.js +4 -3
  32. package/out-tsc/src/form/BaseListEditor.js.map +1 -1
  33. package/out-tsc/src/form/Checkbox.js +77 -24
  34. package/out-tsc/src/form/Checkbox.js.map +1 -1
  35. package/out-tsc/src/form/ColorPicker.js +28 -40
  36. package/out-tsc/src/form/ColorPicker.js.map +1 -1
  37. package/out-tsc/src/form/Completion.js +44 -53
  38. package/out-tsc/src/form/Completion.js.map +1 -1
  39. package/out-tsc/src/form/Compose.js +7 -8
  40. package/out-tsc/src/form/Compose.js.map +1 -1
  41. package/out-tsc/src/form/ContactSearch.js +3 -4
  42. package/out-tsc/src/form/ContactSearch.js.map +1 -1
  43. package/out-tsc/src/form/DatePicker.js +29 -36
  44. package/out-tsc/src/form/DatePicker.js.map +1 -1
  45. package/out-tsc/src/form/{FormField.js → FieldElement.js} +78 -50
  46. package/out-tsc/src/form/FieldElement.js.map +1 -0
  47. package/out-tsc/src/form/FieldRenderer.js +2 -1
  48. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  49. package/out-tsc/src/form/ImagePicker.js +122 -126
  50. package/out-tsc/src/form/ImagePicker.js.map +1 -1
  51. package/out-tsc/src/form/KeyValueEditor.js +41 -37
  52. package/out-tsc/src/form/KeyValueEditor.js.map +1 -1
  53. package/out-tsc/src/form/MessageEditor.js +55 -63
  54. package/out-tsc/src/form/MessageEditor.js.map +1 -1
  55. package/out-tsc/src/form/TembaSlider.js +3 -3
  56. package/out-tsc/src/form/TembaSlider.js.map +1 -1
  57. package/out-tsc/src/form/TemplateEditor.js +3 -3
  58. package/out-tsc/src/form/TemplateEditor.js.map +1 -1
  59. package/out-tsc/src/form/TextInput.js +22 -26
  60. package/out-tsc/src/form/TextInput.js.map +1 -1
  61. package/out-tsc/src/form/select/Select.js +9 -15
  62. package/out-tsc/src/form/select/Select.js.map +1 -1
  63. package/out-tsc/src/form/select/UserSelect.js +8 -9
  64. package/out-tsc/src/form/select/UserSelect.js.map +1 -1
  65. package/out-tsc/src/form/select/WorkspaceSelect.js +7 -8
  66. package/out-tsc/src/form/select/WorkspaceSelect.js.map +1 -1
  67. package/out-tsc/src/live/ContactChat.js +73 -99
  68. package/out-tsc/src/live/ContactChat.js.map +1 -1
  69. package/out-tsc/src/live/ContactFieldEditor.js.map +1 -1
  70. package/out-tsc/src/utils.js +115 -0
  71. package/out-tsc/src/utils.js.map +1 -1
  72. package/out-tsc/temba-modules.js +3 -2
  73. package/out-tsc/temba-modules.js.map +1 -1
  74. package/out-tsc/test/nodes/split_by_llm.test.js +174 -0
  75. package/out-tsc/test/nodes/split_by_llm.test.js.map +1 -0
  76. package/out-tsc/test/temba-checkbox.test.js +16 -0
  77. package/out-tsc/test/temba-checkbox.test.js.map +1 -1
  78. package/out-tsc/test/temba-integration-markdown.test.js +2 -4
  79. package/out-tsc/test/temba-integration-markdown.test.js.map +1 -1
  80. package/out-tsc/test/temba-slider.test.js +0 -1
  81. package/out-tsc/test/temba-slider.test.js.map +1 -1
  82. package/package.json +1 -1
  83. package/screenshots/truth/actions/add_contact_groups/editor/descriptive-group-names.png +0 -0
  84. package/screenshots/truth/actions/add_contact_groups/editor/long-group-names.png +0 -0
  85. package/screenshots/truth/actions/add_contact_groups/editor/many-groups.png +0 -0
  86. package/screenshots/truth/actions/add_contact_groups/editor/multiple-groups.png +0 -0
  87. package/screenshots/truth/actions/add_contact_groups/editor/single-group.png +0 -0
  88. package/screenshots/truth/actions/call_llm/editor/information-extraction.png +0 -0
  89. package/screenshots/truth/actions/call_llm/editor/sentiment-analysis.png +0 -0
  90. package/screenshots/truth/actions/call_llm/editor/summarization.png +0 -0
  91. package/screenshots/truth/actions/call_llm/editor/translation-task.png +0 -0
  92. package/screenshots/truth/actions/remove_contact_groups/editor/cleanup-groups.png +0 -0
  93. package/screenshots/truth/actions/remove_contact_groups/editor/long-descriptive-group-names.png +0 -0
  94. package/screenshots/truth/actions/remove_contact_groups/editor/many-groups.png +0 -0
  95. package/screenshots/truth/actions/remove_contact_groups/editor/multiple-groups.png +0 -0
  96. package/screenshots/truth/actions/remove_contact_groups/editor/remove-from-all-groups.png +0 -0
  97. package/screenshots/truth/actions/remove_contact_groups/editor/single-group.png +0 -0
  98. package/screenshots/truth/actions/send_email/editor/complex-business-email.png +0 -0
  99. package/screenshots/truth/actions/send_email/editor/empty-body.png +0 -0
  100. package/screenshots/truth/actions/send_email/editor/empty-subject.png +0 -0
  101. package/screenshots/truth/actions/send_email/editor/long-subject.png +0 -0
  102. package/screenshots/truth/actions/send_email/editor/multiline-body.png +0 -0
  103. package/screenshots/truth/actions/send_email/editor/multiple-recipients.png +0 -0
  104. package/screenshots/truth/actions/send_email/editor/simple-email.png +0 -0
  105. package/screenshots/truth/actions/send_email/editor/with-expressions.png +0 -0
  106. package/screenshots/truth/actions/send_msg/editor/long-quick-replies.png +0 -0
  107. package/screenshots/truth/actions/send_msg/editor/multiline-text-with-replies.png +0 -0
  108. package/screenshots/truth/actions/send_msg/editor/simple-text.png +0 -0
  109. package/screenshots/truth/actions/send_msg/editor/text-with-linebreaks.png +0 -0
  110. package/screenshots/truth/actions/send_msg/editor/text-with-many-quick-replies.png +0 -0
  111. package/screenshots/truth/actions/send_msg/editor/text-with-quick-replies.png +0 -0
  112. package/screenshots/truth/actions/send_msg/editor/text-without-quick-replies.png +0 -0
  113. package/screenshots/truth/checkbox/checkbox-label-background-hover.png +0 -0
  114. package/screenshots/truth/checkbox/checkbox-no-label-no-background-hover.png +0 -0
  115. package/screenshots/truth/checkbox/checkbox-whitespace-label-no-background-hover.png +0 -0
  116. package/screenshots/truth/checkbox/checkbox-with-help-text.png +0 -0
  117. package/screenshots/truth/checkbox/checked.png +0 -0
  118. package/screenshots/truth/checkbox/default.png +0 -0
  119. package/screenshots/truth/colorpicker/default.png +0 -0
  120. package/screenshots/truth/colorpicker/focused.png +0 -0
  121. package/screenshots/truth/colorpicker/initialized.png +0 -0
  122. package/screenshots/truth/colorpicker/selected.png +0 -0
  123. package/screenshots/truth/compose/attachments-tab.png +0 -0
  124. package/screenshots/truth/compose/attachments-with-files.png +0 -0
  125. package/screenshots/truth/compose/intial-text.png +0 -0
  126. package/screenshots/truth/compose/no-counter.png +0 -0
  127. package/screenshots/truth/compose/wraps-text-and-spaces.png +0 -0
  128. package/screenshots/truth/compose/wraps-text-and-url.png +0 -0
  129. package/screenshots/truth/compose/wraps-text-no-spaces.png +0 -0
  130. package/screenshots/truth/contacts/chat-failure.png +0 -0
  131. package/screenshots/truth/contacts/chat-for-active-contact.png +0 -0
  132. package/screenshots/truth/contacts/chat-sends-attachments-only.png +0 -0
  133. package/screenshots/truth/contacts/chat-sends-text-and-attachments.png +0 -0
  134. package/screenshots/truth/contacts/chat-sends-text-only.png +0 -0
  135. package/screenshots/truth/counter/summary.png +0 -0
  136. package/screenshots/truth/counter/text.png +0 -0
  137. package/screenshots/truth/counter/unicode-variables.png +0 -0
  138. package/screenshots/truth/counter/unicode.png +0 -0
  139. package/screenshots/truth/counter/variable.png +0 -0
  140. package/screenshots/truth/datepicker/date-truncated-time.png +0 -0
  141. package/screenshots/truth/datepicker/date.png +0 -0
  142. package/screenshots/truth/datepicker/initial-timezone.png +0 -0
  143. package/screenshots/truth/datepicker/range-picker-editing-start.png +0 -0
  144. package/screenshots/truth/datepicker/updated-keyboard-date.png +0 -0
  145. package/screenshots/truth/dialog/focused.png +0 -0
  146. package/screenshots/truth/dropdown/right-edge-collision.png +0 -0
  147. package/screenshots/truth/editor/router.png +0 -0
  148. package/screenshots/truth/editor/send_msg.png +0 -0
  149. package/screenshots/truth/editor/set_contact_language.png +0 -0
  150. package/screenshots/truth/editor/set_contact_name.png +0 -0
  151. package/screenshots/truth/editor/set_run_result.png +0 -0
  152. package/screenshots/truth/editor/wait.png +0 -0
  153. package/screenshots/truth/field-renderer/checkbox-checked.png +0 -0
  154. package/screenshots/truth/field-renderer/checkbox-unchecked.png +0 -0
  155. package/screenshots/truth/field-renderer/checkbox-with-errors.png +0 -0
  156. package/screenshots/truth/field-renderer/context-comparison.png +0 -0
  157. package/screenshots/truth/field-renderer/key-value-with-label.png +0 -0
  158. package/screenshots/truth/field-renderer/message-editor-with-label.png +0 -0
  159. package/screenshots/truth/field-renderer/select-multi.png +0 -0
  160. package/screenshots/truth/field-renderer/select-no-label.png +0 -0
  161. package/screenshots/truth/field-renderer/select-with-label.png +0 -0
  162. package/screenshots/truth/field-renderer/text-evaluated.png +0 -0
  163. package/screenshots/truth/field-renderer/text-no-label.png +0 -0
  164. package/screenshots/truth/field-renderer/text-with-errors.png +0 -0
  165. package/screenshots/truth/field-renderer/text-with-label.png +0 -0
  166. package/screenshots/truth/field-renderer/textarea-evaluated.png +0 -0
  167. package/screenshots/truth/field-renderer/textarea-with-label.png +0 -0
  168. package/screenshots/truth/integration/checkbox-markdown-errors.png +0 -0
  169. package/screenshots/truth/list/fields-dragging.png +0 -0
  170. package/screenshots/truth/list/fields-filtered.png +0 -0
  171. package/screenshots/truth/list/fields-hovered.png +0 -0
  172. package/screenshots/truth/list/fields.png +0 -0
  173. package/screenshots/truth/list/items-selected.png +0 -0
  174. package/screenshots/truth/list/items-updated.png +0 -0
  175. package/screenshots/truth/list/items.png +0 -0
  176. package/screenshots/truth/menu/menu-focused-with items.png +0 -0
  177. package/screenshots/truth/menu/menu-refresh-1.png +0 -0
  178. package/screenshots/truth/menu/menu-refresh-2.png +0 -0
  179. package/screenshots/truth/menu/menu-submenu.png +0 -0
  180. package/screenshots/truth/menu/menu-tasks-nextup.png +0 -0
  181. package/screenshots/truth/menu/menu-tasks.png +0 -0
  182. package/screenshots/truth/message-editor/autogrow-initial-content.png +0 -0
  183. package/screenshots/truth/message-editor/default.png +0 -0
  184. package/screenshots/truth/message-editor/drag-highlight.png +0 -0
  185. package/screenshots/truth/message-editor/filtered-attachments.png +0 -0
  186. package/screenshots/truth/message-editor/with-completion.png +0 -0
  187. package/screenshots/truth/message-editor/with-properties.png +0 -0
  188. package/screenshots/truth/modax/form.png +0 -0
  189. package/screenshots/truth/modax/simple.png +0 -0
  190. package/screenshots/truth/nodes/split_by_llm/editor/information-extraction.png +0 -0
  191. package/screenshots/truth/nodes/split_by_llm/editor/sentiment-analysis.png +0 -0
  192. package/screenshots/truth/nodes/split_by_llm/editor/summarization.png +0 -0
  193. package/screenshots/truth/nodes/split_by_llm/editor/translation-task.png +0 -0
  194. package/screenshots/truth/nodes/split_by_llm/render/information-extraction.png +0 -0
  195. package/screenshots/truth/nodes/split_by_llm/render/sentiment-analysis.png +0 -0
  196. package/screenshots/truth/nodes/split_by_llm/render/summarization.png +0 -0
  197. package/screenshots/truth/nodes/split_by_llm/render/translation-task.png +0 -0
  198. package/screenshots/truth/nodes/split_by_llm_categorize/editor/basic-categorization.png +0 -0
  199. package/screenshots/truth/nodes/split_by_llm_categorize/editor/custom-input-and-result-name.png +0 -0
  200. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  201. package/screenshots/truth/nodes/split_by_llm_categorize/editor/many-categories.png +0 -0
  202. package/screenshots/truth/nodes/split_by_llm_categorize/editor/minimal-categories.png +0 -0
  203. package/screenshots/truth/nodes/split_by_random/editor/ab-test-multiple-variants.png +0 -0
  204. package/screenshots/truth/nodes/split_by_random/editor/sampling-split.png +0 -0
  205. package/screenshots/truth/nodes/split_by_random/editor/three-way-split.png +0 -0
  206. package/screenshots/truth/nodes/split_by_random/editor/two-bucket-split.png +0 -0
  207. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  208. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  209. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  210. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  211. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  212. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  213. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  214. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  215. package/screenshots/truth/omnibox/selected.png +0 -0
  216. package/screenshots/truth/options/block.png +0 -0
  217. package/screenshots/truth/run-list/basic.png +0 -0
  218. package/screenshots/truth/select/disabled-multi-selection.png +0 -0
  219. package/screenshots/truth/select/disabled-selection.png +0 -0
  220. package/screenshots/truth/select/disabled.png +0 -0
  221. package/screenshots/truth/select/embedded.png +0 -0
  222. package/screenshots/truth/select/empty-options.png +0 -0
  223. package/screenshots/truth/select/expression-selected.png +0 -0
  224. package/screenshots/truth/select/expressions.png +0 -0
  225. package/screenshots/truth/select/functions.png +0 -0
  226. package/screenshots/truth/select/local-options.png +0 -0
  227. package/screenshots/truth/select/multi-with-endpoint.png +0 -0
  228. package/screenshots/truth/select/multiple-initial-values.png +0 -0
  229. package/screenshots/truth/select/remote-options.png +0 -0
  230. package/screenshots/truth/select/search-enabled.png +0 -0
  231. package/screenshots/truth/select/search-multi-no-matches.png +0 -0
  232. package/screenshots/truth/select/search-selected-focus.png +0 -0
  233. package/screenshots/truth/select/search-selected.png +0 -0
  234. package/screenshots/truth/select/search-with-selected.png +0 -0
  235. package/screenshots/truth/select/searching.png +0 -0
  236. package/screenshots/truth/select/selected-multi-maxitems-reached.png +0 -0
  237. package/screenshots/truth/select/selected-multi.png +0 -0
  238. package/screenshots/truth/select/selected-single.png +0 -0
  239. package/screenshots/truth/select/selection-clearable.png +0 -0
  240. package/screenshots/truth/select/static-initial-value.png +0 -0
  241. package/screenshots/truth/select/static-initial-via-selected.png +0 -0
  242. package/screenshots/truth/select/truncated-selection.png +0 -0
  243. package/screenshots/truth/select/with-placeholder.png +0 -0
  244. package/screenshots/truth/select/without-placeholder.png +0 -0
  245. package/screenshots/truth/slider/update-slider-on-circle-dragged.png +0 -0
  246. package/screenshots/truth/templates/default.png +0 -0
  247. package/screenshots/truth/templates/unapproved.png +0 -0
  248. package/screenshots/truth/textinput/autogrow-initial.png +0 -0
  249. package/screenshots/truth/textinput/input-disabled.png +0 -0
  250. package/screenshots/truth/textinput/input-focused.png +0 -0
  251. package/screenshots/truth/textinput/input-form.png +0 -0
  252. package/screenshots/truth/textinput/input-inserted.png +0 -0
  253. package/screenshots/truth/textinput/input-placeholder.png +0 -0
  254. package/screenshots/truth/textinput/input-updated.png +0 -0
  255. package/screenshots/truth/textinput/input.png +0 -0
  256. package/screenshots/truth/textinput/textarea-focused.png +0 -0
  257. package/screenshots/truth/textinput/textarea.png +0 -0
  258. package/src/events.ts +9 -8
  259. package/src/flow/Editor.ts +6 -3
  260. package/src/flow/actions/set_contact_channel.ts +1 -1
  261. package/src/flow/actions/set_contact_field.ts +1 -1
  262. package/src/flow/actions/set_contact_language.ts +1 -1
  263. package/src/flow/actions/set_contact_status.ts +1 -1
  264. package/src/flow/config.ts +2 -8
  265. package/src/flow/nodes/split_by_llm.ts +119 -0
  266. package/src/flow/nodes/split_by_llm_categorize.ts +13 -116
  267. package/src/flow/nodes/split_by_subflow.ts +153 -3
  268. package/src/flow/nodes/split_by_ticket.ts +135 -12
  269. package/src/flow/nodes/split_by_webhook.ts +187 -12
  270. package/src/form/ArrayEditor.ts +45 -57
  271. package/src/form/BaseListEditor.ts +4 -4
  272. package/src/form/Checkbox.ts +81 -24
  273. package/src/form/ColorPicker.ts +31 -43
  274. package/src/form/Completion.ts +49 -56
  275. package/src/form/Compose.ts +8 -8
  276. package/src/form/ContactSearch.ts +3 -4
  277. package/src/form/DatePicker.ts +32 -38
  278. package/src/form/{FormField.ts → FieldElement.ts} +105 -52
  279. package/src/form/FieldRenderer.ts +2 -1
  280. package/src/form/ImagePicker.ts +107 -110
  281. package/src/form/KeyValueEditor.ts +43 -39
  282. package/src/form/MessageEditor.ts +61 -67
  283. package/src/form/TembaSlider.ts +3 -3
  284. package/src/form/TemplateEditor.ts +3 -3
  285. package/src/form/TextInput.ts +25 -28
  286. package/src/form/select/Select.ts +12 -17
  287. package/src/form/select/UserSelect.ts +10 -11
  288. package/src/form/select/WorkspaceSelect.ts +9 -10
  289. package/src/live/ContactChat.ts +81 -92
  290. package/src/live/ContactFieldEditor.ts +2 -2
  291. package/src/store/flow-definition.d.ts +2 -1
  292. package/src/utils.ts +192 -0
  293. package/temba-modules.ts +3 -2
  294. package/test/nodes/split_by_llm.test.ts +232 -0
  295. package/test/temba-checkbox.test.ts +26 -0
  296. package/test/temba-integration-markdown.test.ts +2 -4
  297. package/test/temba-slider.test.ts +0 -1
  298. package/test-assets/contacts/history.json +7 -20
  299. package/test-assets/style.css +36 -234
  300. package/web-dev-server.config.mjs +26 -0
  301. package/web-test-runner.config.mjs +1 -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/src/form/FormElement.js +0 -67
  311. package/out-tsc/src/form/FormElement.js.map +0 -1
  312. package/out-tsc/src/form/FormField.js.map +0 -1
  313. package/out-tsc/test/actions/call_llm.test.js +0 -103
  314. package/out-tsc/test/actions/call_llm.test.js.map +0 -1
  315. package/out-tsc/test/temba-formfield.test.js +0 -94
  316. package/out-tsc/test/temba-formfield.test.js.map +0 -1
  317. package/src/flow/actions/call_llm.ts +0 -66
  318. package/src/flow/actions/call_webhook.ts +0 -143
  319. package/src/flow/actions/enter_flow.ts +0 -15
  320. package/src/flow/actions/open_ticket.ts +0 -83
  321. package/src/form/FormElement.ts +0 -69
  322. package/test/actions/call_llm.test.ts +0 -137
  323. package/test/temba-formfield.test.ts +0 -121
@@ -1,94 +0,0 @@
1
- import { html, fixture, expect } from '@open-wc/testing';
2
- import { assertScreenshot, getClip } from './utils.test';
3
- describe('temba-field', () => {
4
- it('renders field with plain text errors', async () => {
5
- const formField = await fixture(html `
6
- <temba-field
7
- label="Test Field"
8
- name="test"
9
- .errors=${['This is a plain text error', 'Another error message']}
10
- >
11
- <input type="text" />
12
- </temba-field>
13
- `);
14
- await formField.updateComplete;
15
- // Check that errors are rendered
16
- const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
17
- expect(errorElements.length).to.equal(2);
18
- expect(errorElements[0].textContent.trim()).to.equal('This is a plain text error');
19
- expect(errorElements[1].textContent.trim()).to.equal('Another error message');
20
- await assertScreenshot('formfield/plain-text-errors', getClip(formField));
21
- });
22
- it('renders field with markdown errors', async () => {
23
- const formField = await fixture(html `
24
- <temba-field
25
- label="Test Field"
26
- name="test"
27
- .errors=${[
28
- 'This is **bold** text',
29
- 'This has a [link](https://example.com)',
30
- 'This is *italic* and **bold** with a [link](https://example.com)'
31
- ]}
32
- >
33
- <input type="text" />
34
- </temba-field>
35
- `);
36
- await formField.updateComplete;
37
- // Check that errors are rendered
38
- const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
39
- expect(errorElements.length).to.equal(3);
40
- // First error should have bold text
41
- const firstError = errorElements[0];
42
- const boldElement = firstError.querySelector('strong');
43
- expect(boldElement).to.not.be.null;
44
- expect(boldElement.textContent).to.equal('bold');
45
- // Second error should have a link
46
- const secondError = errorElements[1];
47
- const linkElement = secondError.querySelector('a');
48
- expect(linkElement).to.not.be.null;
49
- expect(linkElement.getAttribute('href')).to.equal('https://example.com');
50
- expect(linkElement.textContent).to.equal('link');
51
- // Third error should have both bold, italic, and link
52
- const thirdError = errorElements[2];
53
- const thirdBoldElement = thirdError.querySelector('strong');
54
- const thirdItalicElement = thirdError.querySelector('em');
55
- const thirdLinkElement = thirdError.querySelector('a');
56
- expect(thirdBoldElement).to.not.be.null;
57
- expect(thirdItalicElement).to.not.be.null;
58
- expect(thirdLinkElement).to.not.be.null;
59
- await assertScreenshot('formfield/markdown-errors', getClip(formField));
60
- });
61
- it('renders field without errors', async () => {
62
- const formField = await fixture(html `
63
- <temba-field label="Test Field" name="test">
64
- <input type="text" />
65
- </temba-field>
66
- `);
67
- await formField.updateComplete;
68
- // Check that no errors are rendered
69
- const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
70
- expect(errorElements.length).to.equal(0);
71
- await assertScreenshot('formfield/no-errors', getClip(formField));
72
- });
73
- it('renders in widget-only mode with errors', async () => {
74
- const formField = await fixture(html `
75
- <temba-field
76
- widget_only
77
- .errors=${['Widget only **error** with [link](https://example.com)']}
78
- >
79
- <input type="text" />
80
- </temba-field>
81
- `);
82
- await formField.updateComplete;
83
- // Check that error is rendered in widget-only mode
84
- const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');
85
- expect(errorElements.length).to.equal(1);
86
- const errorElement = errorElements[0];
87
- const boldElement = errorElement.querySelector('strong');
88
- const linkElement = errorElement.querySelector('a');
89
- expect(boldElement).to.not.be.null;
90
- expect(linkElement).to.not.be.null;
91
- await assertScreenshot('formfield/widget-only-markdown-errors', getClip(formField));
92
- });
93
- });
94
- //# sourceMappingURL=temba-formfield.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"temba-formfield.test.js","sourceRoot":"","sources":["../../test/temba-formfield.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, fixture, expect } from '@open-wc/testing';\nimport { FormField } from '../src/form/FormField';\nimport { assertScreenshot, getClip } from './utils.test';\n\ndescribe('temba-field', () => {\n it('renders field with plain text errors', async () => {\n const formField: FormField = await fixture(html`\n <temba-field\n label=\"Test Field\"\n name=\"test\"\n .errors=${['This is a plain text error', 'Another error message']}\n >\n <input type=\"text\" />\n </temba-field>\n `);\n\n await formField.updateComplete;\n\n // Check that errors are rendered\n const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');\n expect(errorElements.length).to.equal(2);\n expect(errorElements[0].textContent.trim()).to.equal(\n 'This is a plain text error'\n );\n expect(errorElements[1].textContent.trim()).to.equal(\n 'Another error message'\n );\n\n await assertScreenshot('formfield/plain-text-errors', getClip(formField));\n });\n\n it('renders field with markdown errors', async () => {\n const formField: FormField = await fixture(html`\n <temba-field\n label=\"Test Field\"\n name=\"test\"\n .errors=${[\n 'This is **bold** text',\n 'This has a [link](https://example.com)',\n 'This is *italic* and **bold** with a [link](https://example.com)'\n ]}\n >\n <input type=\"text\" />\n </temba-field>\n `);\n\n await formField.updateComplete;\n\n // Check that errors are rendered\n const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');\n expect(errorElements.length).to.equal(3);\n\n // First error should have bold text\n const firstError = errorElements[0];\n const boldElement = firstError.querySelector('strong');\n expect(boldElement).to.not.be.null;\n expect(boldElement.textContent).to.equal('bold');\n\n // Second error should have a link\n const secondError = errorElements[1];\n const linkElement = secondError.querySelector('a');\n expect(linkElement).to.not.be.null;\n expect(linkElement.getAttribute('href')).to.equal('https://example.com');\n expect(linkElement.textContent).to.equal('link');\n\n // Third error should have both bold, italic, and link\n const thirdError = errorElements[2];\n const thirdBoldElement = thirdError.querySelector('strong');\n const thirdItalicElement = thirdError.querySelector('em');\n const thirdLinkElement = thirdError.querySelector('a');\n expect(thirdBoldElement).to.not.be.null;\n expect(thirdItalicElement).to.not.be.null;\n expect(thirdLinkElement).to.not.be.null;\n\n await assertScreenshot('formfield/markdown-errors', getClip(formField));\n });\n\n it('renders field without errors', async () => {\n const formField: FormField = await fixture(html`\n <temba-field label=\"Test Field\" name=\"test\">\n <input type=\"text\" />\n </temba-field>\n `);\n\n await formField.updateComplete;\n\n // Check that no errors are rendered\n const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');\n expect(errorElements.length).to.equal(0);\n\n await assertScreenshot('formfield/no-errors', getClip(formField));\n });\n\n it('renders in widget-only mode with errors', async () => {\n const formField: FormField = await fixture(html`\n <temba-field\n widget_only\n .errors=${['Widget only **error** with [link](https://example.com)']}\n >\n <input type=\"text\" />\n </temba-field>\n `);\n\n await formField.updateComplete;\n\n // Check that error is rendered in widget-only mode\n const errorElements = formField.shadowRoot.querySelectorAll('.alert-error');\n expect(errorElements.length).to.equal(1);\n\n const errorElement = errorElements[0];\n const boldElement = errorElement.querySelector('strong');\n const linkElement = errorElement.querySelector('a');\n expect(boldElement).to.not.be.null;\n expect(linkElement).to.not.be.null;\n\n await assertScreenshot(\n 'formfield/widget-only-markdown-errors',\n getClip(formField)\n );\n });\n});\n"]}
@@ -1,66 +0,0 @@
1
- import { html } from 'lit-html';
2
- import { ActionConfig, COLORS } from '../types';
3
- import { Node, CallLLM } from '../../store/flow-definition';
4
-
5
- export const call_llm: ActionConfig = {
6
- name: 'Call AI',
7
- color: COLORS.call,
8
- render: (_node: Node, action: CallLLM) => {
9
- return html`<div
10
- style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; max-width: 180px; max-height: 100px; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical;"
11
- >
12
- ${action.instructions}
13
- </div>`;
14
- },
15
- form: {
16
- llm: {
17
- type: 'select',
18
- required: true,
19
- options: [],
20
- endpoint: '/test-assets/select/llms.json',
21
- searchable: true,
22
- valueKey: 'uuid',
23
- nameKey: 'name'
24
- },
25
- input: {
26
- type: 'text',
27
- required: true,
28
- label: 'The input the AI will process',
29
- evaluated: true,
30
- placeholder: '@input'
31
- },
32
- instructions: {
33
- type: 'textarea',
34
- required: true,
35
- label: 'Tell the AI what to do with the input',
36
- evaluated: true,
37
- placeholder: 'Enter instructions for the AI model...',
38
- minHeight: 130,
39
- helpText: 'The result can be referenced as **`@locals._llm_output`**'
40
- }
41
- },
42
- layout: ['llm', 'input', 'instructions'],
43
- toFormData: (action: CallLLM) => {
44
- return {
45
- uuid: action.uuid,
46
- llm: action.llm
47
- ? [{ value: action.llm.uuid, name: action.llm.name }]
48
- : [],
49
- input: action.input || '@input',
50
- instructions: action.instructions || ''
51
- };
52
- },
53
- fromFormData: (data: Record<string, any>) => {
54
- const llmSelection =
55
- Array.isArray(data.llm) && data.llm.length > 0 ? data.llm[0] : null;
56
- return {
57
- uuid: data.uuid,
58
- type: 'call_llm',
59
- input: data.input || '@input',
60
- llm: llmSelection
61
- ? { uuid: llmSelection.value, name: llmSelection.name }
62
- : { uuid: '', name: '' },
63
- instructions: data.instructions || ''
64
- } as CallLLM;
65
- }
66
- };
@@ -1,143 +0,0 @@
1
- import { html } from 'lit-html';
2
- import { ActionConfig, COLORS } from '../types';
3
- import { Node, CallWebhook } from '../../store/flow-definition';
4
-
5
- const defaultPost = `@(json(object(
6
- "contact", object(
7
- "uuid", contact.uuid,
8
- "name", contact.name,
9
- "urn", contact.urn
10
- ),
11
- "flow", object(
12
- "uuid", run.flow.uuid,
13
- "name", run.flow.name
14
- ),
15
- "results", foreach_value(results, extract_object, "value", "category")
16
- )))`;
17
-
18
- export const call_webhook: ActionConfig = {
19
- name: 'Call Webhook',
20
- color: COLORS.call,
21
- render: (_node: Node, action: CallWebhook) => {
22
- return html`<div
23
- style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto;"
24
- >
25
- ${action.url}
26
- </div>`;
27
- },
28
- evaluated: ['url', 'headers', 'body'], // keep for backward compatibility
29
- form: {
30
- method: {
31
- type: 'select',
32
- required: true,
33
- options: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH'],
34
- maxWidth: '120px',
35
- searchable: false
36
- },
37
- url: {
38
- type: 'text',
39
- required: true,
40
- evaluated: true,
41
- placeholder: 'https://example.com/webhook'
42
- },
43
- headers: {
44
- type: 'key-value',
45
- sortable: true,
46
- keyPlaceholder: 'Header name',
47
- valuePlaceholder: 'Header value',
48
- minRows: 0
49
- },
50
- body: {
51
- type: 'textarea',
52
- evaluated: true,
53
- placeholder: 'Request body content (JSON, XML, etc.)',
54
- minHeight: 200,
55
- dependsOn: ['method'],
56
- computeValue: (
57
- values: Record<string, any>,
58
- currentValue: any,
59
- originalValues?: Record<string, any>
60
- ) => {
61
- // Check if method is POST (handle both string and select object formats)
62
- const method =
63
- Array.isArray(values.method) && values.method.length > 0
64
- ? values.method[0].value || values.method[0].name
65
- : values.method;
66
-
67
- if (method === 'POST') {
68
- // For POST, provide the template if body is empty or was never set by user
69
- if (!currentValue || currentValue.trim() === '') {
70
- return defaultPost;
71
- }
72
- } else {
73
- // For non-POST methods, clear the body if it was auto-generated or empty
74
- // Check if the original body was empty (user never specified a body)
75
- const originalBody = originalValues?.body || '';
76
- const isOriginallyEmpty = !originalBody || originalBody.trim() === '';
77
-
78
- // Clear if: originally empty, contains default template, or is currently empty
79
- if (
80
- isOriginallyEmpty ||
81
- !currentValue ||
82
- currentValue.trim() === '' ||
83
- currentValue.trim() === defaultPost.trim()
84
- ) {
85
- return '';
86
- }
87
- }
88
-
89
- return currentValue; // Keep existing value if user has customized it
90
- }
91
- }
92
- },
93
- layout: [
94
- // Row with method and URL side by side
95
- { type: 'row', items: ['method', 'url'] },
96
- // Advanced group with nested layouts
97
- {
98
- type: 'group',
99
- label: 'Headers',
100
- items: ['headers'],
101
- collapsible: true,
102
- collapsed: true,
103
- helpText: 'Configure authentication or custom headers',
104
- getGroupValueCount: (formData: any) => {
105
- return formData.headers?.length || 0;
106
- }
107
- },
108
- {
109
- type: 'group',
110
- label: 'Body',
111
- items: ['body'],
112
- collapsible: true,
113
- collapsed: true,
114
- helpText: 'Configure the request payload',
115
- getGroupValueCount: (formData: any) => {
116
- return !!(
117
- formData.body &&
118
- formData.body.trim() !== '' &&
119
- formData.body !== defaultPost
120
- );
121
- }
122
- }
123
- ],
124
- toFormData: (action: CallWebhook) => {
125
- return {
126
- uuid: action.uuid,
127
- url: action.url || '',
128
- method: [{ value: action.method, name: action.method }],
129
- headers: action.headers || [],
130
- body: action.body || ''
131
- };
132
- },
133
- fromFormData: (data: Record<string, any>) => {
134
- return {
135
- uuid: data.uuid,
136
- type: 'call_webhook',
137
- url: data.url,
138
- method: data.method[0].value,
139
- headers: data.headers || [],
140
- body: data.body || ''
141
- } as CallWebhook;
142
- }
143
- };
@@ -1,15 +0,0 @@
1
- import { html } from 'lit-html';
2
- import { ActionConfig, COLORS } from '../types';
3
- import { Node, EnterFlow } from '../../store/flow-definition';
4
-
5
- export const enter_flow: ActionConfig = {
6
- name: 'Enter a Flow',
7
- color: COLORS.execute,
8
- render: (_node: Node, action: EnterFlow) => {
9
- return html`<div
10
- style="word-wrap: break-word; overflow-wrap: break-word; hyphens: auto;"
11
- >
12
- Enter <b>${action.flow.name}</b>
13
- </div>`;
14
- }
15
- };
@@ -1,83 +0,0 @@
1
- import { html } from 'lit-html';
2
- import { ActionConfig, COLORS } from '../types';
3
- import { Node, OpenTicket } from '../../store/flow-definition';
4
-
5
- export const open_ticket: ActionConfig = {
6
- name: 'Open Ticket',
7
- color: COLORS.create,
8
- render: (_node: Node, action: OpenTicket) => {
9
- return html`<div>${action.topic.name}</div>`;
10
- },
11
- form: {
12
- topic: {
13
- type: 'select',
14
- required: true,
15
- placeholder: 'Select a topic',
16
- options: [],
17
- endpoint: '/api/v2/topics.json',
18
- valueKey: 'uuid',
19
- nameKey: 'name',
20
- maxWidth: '200px'
21
- },
22
- assignee: {
23
- type: 'select',
24
- required: false,
25
- placeholder: 'Select an agent (optional)',
26
- options: [],
27
- endpoint: '/api/v2/users.json',
28
- valueKey: 'uuid',
29
- getName: (item: {
30
- first_name?: string;
31
- last_name?: string;
32
- name?: string;
33
- }) => {
34
- return item.name || [item.first_name, item.last_name].join(' ');
35
- },
36
- clearable: true
37
- },
38
- note: {
39
- type: 'textarea',
40
- required: false,
41
- placeholder: 'Enter a note for the ticket (optional)',
42
- minHeight: 100
43
- }
44
- },
45
- layout: [{ type: 'row', items: ['topic', 'assignee'] }, 'note'],
46
- toFormData: (action: OpenTicket) => {
47
- return {
48
- uuid: action.uuid,
49
- topic: action.topic
50
- ? [{ uuid: action.topic.uuid, name: action.topic.name }]
51
- : [],
52
- assignee: action.assignee
53
- ? [{ uuid: action.assignee.uuid, name: action.assignee.name }]
54
- : [],
55
- note: action.note || ''
56
- };
57
- },
58
- fromFormData: (data: Record<string, any>) => {
59
- return {
60
- uuid: data.uuid,
61
- type: 'open_ticket',
62
- topic:
63
- data.topic && data.topic.length > 0
64
- ? {
65
- uuid: data.topic[0].uuid,
66
- name: data.topic[0].name
67
- }
68
- : undefined,
69
- assignee:
70
- data.assignee && data.assignee.length > 0
71
- ? {
72
- uuid: data.assignee[0].uuid,
73
- name:
74
- data.assignee[0].name ||
75
- [data.assignee[0].first_name, data.assignee[0].last_name].join(
76
- ' '
77
- )
78
- }
79
- : undefined,
80
- note: data.note || ''
81
- } as OpenTicket;
82
- }
83
- };
@@ -1,69 +0,0 @@
1
- import { RapidElement } from '../RapidElement';
2
- import { property } from 'lit/decorators.js';
3
-
4
- /**
5
- * FormElement is a component that appends a hidden input (outside of
6
- * its own shadow) with its value to be included in forms.
7
- */
8
- export class FormElement extends RapidElement {
9
- @property({ type: String })
10
- name = '';
11
-
12
- @property({ type: String, attribute: 'help_text' })
13
- helpText: string;
14
-
15
- @property({ type: Boolean, attribute: 'help_always' })
16
- helpAlways: boolean;
17
-
18
- @property({ type: Boolean, attribute: 'widget_only' })
19
- widgetOnly: boolean;
20
-
21
- @property({ type: Boolean, attribute: 'hide_label' })
22
- hideLabel: boolean;
23
-
24
- @property({ type: String })
25
- label: string;
26
-
27
- @property({ type: Array })
28
- errors: string[];
29
-
30
- @property({ type: String })
31
- value = null;
32
-
33
- @property({ attribute: false })
34
- inputRoot: HTMLElement = this;
35
-
36
- @property({ type: Boolean })
37
- disabled = false;
38
- static formAssociated = true;
39
-
40
- protected internals: ElementInternals;
41
-
42
- constructor() {
43
- super();
44
- this.internals = this.attachInternals();
45
- }
46
-
47
- public updated(changedProperties: Map<string, any>) {
48
- super.updated(changedProperties);
49
- if (changedProperties.has('value')) {
50
- this.internals.setFormValue(this.value);
51
- }
52
- }
53
-
54
- get form() {
55
- return this.internals.form;
56
- }
57
-
58
- public setValue(value: any) {
59
- this.value = this.serializeValue(value);
60
- }
61
-
62
- public getDeserializedValue(): any {
63
- return JSON.parse(this.value);
64
- }
65
-
66
- public serializeValue(value: any): string {
67
- return JSON.stringify(value);
68
- }
69
- }
@@ -1,137 +0,0 @@
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
- });