@nyaruka/temba-components 0.131.2 → 0.131.3

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 (223) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/demo/components/floating-tabs/example.html +400 -0
  3. package/demo/components/flow/index.html +1 -1
  4. package/demo/data/flows/sample-flow.json +41 -2
  5. package/demo/data/flows/voicemail.json +613 -0
  6. package/demo/index.html +6 -0
  7. package/dist/locales/es.js +5 -5
  8. package/dist/locales/es.js.map +1 -1
  9. package/dist/locales/fr.js +5 -5
  10. package/dist/locales/fr.js.map +1 -1
  11. package/dist/locales/locale-codes.js +11 -2
  12. package/dist/locales/locale-codes.js.map +1 -1
  13. package/dist/locales/pt.js +5 -5
  14. package/dist/locales/pt.js.map +1 -1
  15. package/dist/temba-components.js +1109 -535
  16. package/dist/temba-components.js.map +1 -1
  17. package/out-tsc/src/display/FloatingTab.js +167 -0
  18. package/out-tsc/src/display/FloatingTab.js.map +1 -0
  19. package/out-tsc/src/display/ProgressBar.js +22 -2
  20. package/out-tsc/src/display/ProgressBar.js.map +1 -1
  21. package/out-tsc/src/events.js.map +1 -1
  22. package/out-tsc/src/flow/CanvasNode.js +165 -31
  23. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  24. package/out-tsc/src/flow/Editor.js +857 -3
  25. package/out-tsc/src/flow/Editor.js.map +1 -1
  26. package/out-tsc/src/flow/NodeEditor.js +239 -19
  27. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  28. package/out-tsc/src/flow/NodeTypeSelector.js +44 -3
  29. package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -1
  30. package/out-tsc/src/flow/StickyNote.js +12 -3
  31. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  32. package/out-tsc/src/flow/actions/add_contact_groups.js +2 -1
  33. package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
  34. package/out-tsc/src/flow/actions/add_contact_urn.js +2 -1
  35. package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
  36. package/out-tsc/src/flow/actions/add_input_labels.js +2 -1
  37. package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
  38. package/out-tsc/src/flow/actions/play_audio.js +2 -1
  39. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  40. package/out-tsc/src/flow/actions/remove_contact_groups.js +2 -1
  41. package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
  42. package/out-tsc/src/flow/actions/request_optin.js +1 -0
  43. package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
  44. package/out-tsc/src/flow/actions/say_msg.js +2 -1
  45. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  46. package/out-tsc/src/flow/actions/send_broadcast.js +2 -1
  47. package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
  48. package/out-tsc/src/flow/actions/send_email.js +2 -1
  49. package/out-tsc/src/flow/actions/send_email.js.map +1 -1
  50. package/out-tsc/src/flow/actions/send_msg.js +93 -3
  51. package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
  52. package/out-tsc/src/flow/actions/set_contact_channel.js +2 -1
  53. package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
  54. package/out-tsc/src/flow/actions/set_contact_field.js +2 -1
  55. package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
  56. package/out-tsc/src/flow/actions/set_contact_language.js +2 -1
  57. package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
  58. package/out-tsc/src/flow/actions/set_contact_name.js +2 -1
  59. package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
  60. package/out-tsc/src/flow/actions/set_contact_status.js +2 -1
  61. package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
  62. package/out-tsc/src/flow/actions/set_run_result.js +2 -1
  63. package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
  64. package/out-tsc/src/flow/actions/start_session.js +2 -1
  65. package/out-tsc/src/flow/actions/start_session.js.map +1 -1
  66. package/out-tsc/src/flow/config.js +2 -10
  67. package/out-tsc/src/flow/config.js.map +1 -1
  68. package/out-tsc/src/flow/nodes/shared.js +54 -0
  69. package/out-tsc/src/flow/nodes/shared.js.map +1 -1
  70. package/out-tsc/src/flow/nodes/split_by_airtime.js +9 -3
  71. package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
  72. package/out-tsc/src/flow/nodes/split_by_contact_field.js +8 -3
  73. package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
  74. package/out-tsc/src/flow/nodes/split_by_expression.js +8 -3
  75. package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
  76. package/out-tsc/src/flow/nodes/split_by_groups.js +8 -3
  77. package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
  78. package/out-tsc/src/flow/nodes/split_by_intent.js +3 -2
  79. package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -1
  80. package/out-tsc/src/flow/nodes/split_by_llm.js +9 -2
  81. package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
  82. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +9 -2
  83. package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
  84. package/out-tsc/src/flow/nodes/split_by_random.js +8 -2
  85. package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
  86. package/out-tsc/src/flow/nodes/split_by_resthook.js +8 -3
  87. package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -1
  88. package/out-tsc/src/flow/nodes/split_by_run_result.js +8 -3
  89. package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
  90. package/out-tsc/src/flow/nodes/split_by_scheme.js +8 -3
  91. package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
  92. package/out-tsc/src/flow/nodes/split_by_subflow.js +8 -2
  93. package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
  94. package/out-tsc/src/flow/nodes/split_by_ticket.js +8 -2
  95. package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
  96. package/out-tsc/src/flow/nodes/split_by_webhook.js +8 -2
  97. package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
  98. package/out-tsc/src/flow/nodes/wait_for_digits.js +3 -2
  99. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  100. package/out-tsc/src/flow/nodes/wait_for_menu.js +3 -2
  101. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  102. package/out-tsc/src/flow/nodes/wait_for_response.js +8 -3
  103. package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
  104. package/out-tsc/src/flow/types.js +15 -0
  105. package/out-tsc/src/flow/types.js.map +1 -1
  106. package/out-tsc/src/layout/FloatingWindow.js +346 -0
  107. package/out-tsc/src/layout/FloatingWindow.js.map +1 -0
  108. package/out-tsc/src/live/ContactChat.js +3 -19
  109. package/out-tsc/src/live/ContactChat.js.map +1 -1
  110. package/out-tsc/src/locales/es.js +5 -5
  111. package/out-tsc/src/locales/es.js.map +1 -1
  112. package/out-tsc/src/locales/fr.js +5 -5
  113. package/out-tsc/src/locales/fr.js.map +1 -1
  114. package/out-tsc/src/locales/locale-codes.js +11 -2
  115. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  116. package/out-tsc/src/locales/pt.js +5 -5
  117. package/out-tsc/src/locales/pt.js.map +1 -1
  118. package/out-tsc/src/store/AppState.js +67 -0
  119. package/out-tsc/src/store/AppState.js.map +1 -1
  120. package/out-tsc/temba-modules.js +4 -0
  121. package/out-tsc/temba-modules.js.map +1 -1
  122. package/out-tsc/test/temba-floating-tab.test.js +91 -0
  123. package/out-tsc/test/temba-floating-tab.test.js.map +1 -0
  124. package/out-tsc/test/temba-floating-window.test.js +301 -0
  125. package/out-tsc/test/temba-floating-window.test.js.map +1 -0
  126. package/out-tsc/test/temba-flow-editor-node.test.js +117 -0
  127. package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
  128. package/out-tsc/test/temba-localization.test.js +471 -0
  129. package/out-tsc/test/temba-localization.test.js.map +1 -0
  130. package/out-tsc/test/temba-node-type-selector.test.js +150 -0
  131. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
  132. package/out-tsc/test/utils.test.js +18 -0
  133. package/out-tsc/test/utils.test.js.map +1 -1
  134. package/package.json +1 -1
  135. package/screenshots/truth/floating-tab/default.png +0 -0
  136. package/screenshots/truth/floating-tab/gray.png +0 -0
  137. package/screenshots/truth/floating-tab/green.png +0 -0
  138. package/screenshots/truth/floating-tab/hidden.png +0 -0
  139. package/screenshots/truth/floating-tab/hover.png +0 -0
  140. package/screenshots/truth/floating-tab/purple.png +0 -0
  141. package/screenshots/truth/floating-window/chromeless.png +0 -0
  142. package/screenshots/truth/floating-window/custom-size.png +0 -0
  143. package/screenshots/truth/floating-window/default.png +0 -0
  144. package/screenshots/truth/floating-window/with-header.png +0 -0
  145. package/screenshots/truth/node-type-selector/action-mode.png +0 -0
  146. package/screenshots/truth/node-type-selector/split-mode.png +0 -0
  147. package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
  148. package/src/display/FloatingTab.ts +174 -0
  149. package/src/display/ProgressBar.ts +22 -2
  150. package/src/events.ts +2 -4
  151. package/src/flow/CanvasNode.ts +190 -32
  152. package/src/flow/Editor.ts +1040 -3
  153. package/src/flow/NodeEditor.ts +317 -19
  154. package/src/flow/NodeTypeSelector.ts +47 -3
  155. package/src/flow/StickyNote.ts +12 -3
  156. package/src/flow/actions/add_contact_groups.ts +2 -1
  157. package/src/flow/actions/add_contact_urn.ts +3 -1
  158. package/src/flow/actions/add_input_labels.ts +2 -1
  159. package/src/flow/actions/play_audio.ts +2 -1
  160. package/src/flow/actions/remove_contact_groups.ts +3 -1
  161. package/src/flow/actions/request_optin.ts +1 -0
  162. package/src/flow/actions/say_msg.ts +2 -1
  163. package/src/flow/actions/send_broadcast.ts +2 -1
  164. package/src/flow/actions/send_email.ts +3 -1
  165. package/src/flow/actions/send_msg.ts +134 -3
  166. package/src/flow/actions/set_contact_channel.ts +2 -1
  167. package/src/flow/actions/set_contact_field.ts +2 -1
  168. package/src/flow/actions/set_contact_language.ts +3 -1
  169. package/src/flow/actions/set_contact_name.ts +2 -1
  170. package/src/flow/actions/set_contact_status.ts +2 -1
  171. package/src/flow/actions/set_run_result.ts +2 -1
  172. package/src/flow/actions/start_session.ts +3 -1
  173. package/src/flow/config.ts +2 -12
  174. package/src/flow/nodes/shared.ts +70 -1
  175. package/src/flow/nodes/split_by_airtime.ts +20 -3
  176. package/src/flow/nodes/split_by_contact_field.ts +13 -3
  177. package/src/flow/nodes/split_by_expression.ts +13 -3
  178. package/src/flow/nodes/split_by_groups.ts +13 -3
  179. package/src/flow/nodes/split_by_intent.ts +3 -2
  180. package/src/flow/nodes/split_by_llm.ts +19 -2
  181. package/src/flow/nodes/split_by_llm_categorize.ts +19 -2
  182. package/src/flow/nodes/split_by_random.ts +12 -2
  183. package/src/flow/nodes/split_by_resthook.ts +13 -3
  184. package/src/flow/nodes/split_by_run_result.ts +13 -3
  185. package/src/flow/nodes/split_by_scheme.ts +13 -3
  186. package/src/flow/nodes/split_by_subflow.ts +12 -2
  187. package/src/flow/nodes/split_by_ticket.ts +12 -2
  188. package/src/flow/nodes/split_by_webhook.ts +12 -2
  189. package/src/flow/nodes/wait_for_digits.ts +3 -2
  190. package/src/flow/nodes/wait_for_menu.ts +3 -2
  191. package/src/flow/nodes/wait_for_response.ts +13 -3
  192. package/src/flow/types.ts +47 -0
  193. package/src/layout/FloatingWindow.ts +386 -0
  194. package/src/live/ContactChat.ts +4 -19
  195. package/src/locales/es.ts +18 -13
  196. package/src/locales/fr.ts +18 -13
  197. package/src/locales/locale-codes.ts +11 -2
  198. package/src/locales/pt.ts +18 -13
  199. package/src/store/AppState.ts +104 -0
  200. package/static/api/llms.json +18 -0
  201. package/temba-modules.ts +4 -0
  202. package/test/temba-floating-tab.test.ts +110 -0
  203. package/test/temba-floating-window.test.ts +477 -0
  204. package/test/temba-flow-editor-node.test.ts +144 -0
  205. package/test/temba-localization.test.ts +611 -0
  206. package/test/temba-node-type-selector.test.ts +203 -0
  207. package/test/utils.test.ts +20 -0
  208. package/test-assets/contacts/history.json +5 -6
  209. package/test-assets/select/llms.json +2 -2
  210. package/web-dev-server.config.mjs +47 -1
  211. package/web-test-runner.config.mjs +0 -1
  212. package/out-tsc/src/flow/nodes/wait_for_audio.js +0 -7
  213. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +0 -1
  214. package/out-tsc/src/flow/nodes/wait_for_image.js +0 -7
  215. package/out-tsc/src/flow/nodes/wait_for_image.js.map +0 -1
  216. package/out-tsc/src/flow/nodes/wait_for_location.js +0 -7
  217. package/out-tsc/src/flow/nodes/wait_for_location.js.map +0 -1
  218. package/out-tsc/src/flow/nodes/wait_for_video.js +0 -7
  219. package/out-tsc/src/flow/nodes/wait_for_video.js.map +0 -1
  220. package/src/flow/nodes/wait_for_audio.ts +0 -7
  221. package/src/flow/nodes/wait_for_image.ts +0 -7
  222. package/src/flow/nodes/wait_for_location.ts +0 -7
  223. package/src/flow/nodes/wait_for_video.ts +0 -7
@@ -149,4 +149,207 @@ describe('temba-node-type-selector', () => {
149
149
  expect(selectionDetail.position).to.deep.equal({ x: 100, y: 100 });
150
150
  expect(selector.open).to.be.false;
151
151
  });
152
+
153
+ it('filters actions by flow type - voice flow should show voice-only actions', async () => {
154
+ const selector = await createSelector();
155
+ selector.flowType = 'voice';
156
+ await selector.updateComplete;
157
+ selector.show('action', { x: 100, y: 100 });
158
+ await selector.updateComplete;
159
+
160
+ // get all node item titles
161
+ const nodeItems = selector.shadowRoot?.querySelectorAll('.node-item-title');
162
+ const titles = Array.from(nodeItems || []).map((item) =>
163
+ item.textContent?.trim()
164
+ );
165
+
166
+ // voice flow should have Say Message and Play Audio
167
+ expect(titles).to.include('Say Message');
168
+ expect(titles).to.include('Play Audio');
169
+ });
170
+
171
+ it('filters actions by flow type - message flow should not show voice-only actions', async () => {
172
+ const selector = await createSelector();
173
+ selector.flowType = 'message';
174
+ await selector.updateComplete;
175
+ selector.show('action', { x: 100, y: 100 });
176
+ await selector.updateComplete;
177
+
178
+ // get all node item titles
179
+ const nodeItems = selector.shadowRoot?.querySelectorAll('.node-item-title');
180
+ const titles = Array.from(nodeItems || []).map((item) =>
181
+ item.textContent?.trim()
182
+ );
183
+
184
+ // message flow should not have Say Message or Play Audio
185
+ expect(titles).to.not.include('Say Message');
186
+ expect(titles).to.not.include('Play Audio');
187
+ });
188
+
189
+ it('filters splits by flow type - message flow should show wait for response', async () => {
190
+ const selector = await createSelector();
191
+ selector.flowType = 'message';
192
+ await selector.updateComplete;
193
+ selector.show('split', { x: 100, y: 100 });
194
+ await selector.updateComplete;
195
+
196
+ // get all node item titles
197
+ const nodeItems = selector.shadowRoot?.querySelectorAll('.node-item-title');
198
+ const titles = Array.from(nodeItems || []).map((item) =>
199
+ item.textContent?.trim()
200
+ );
201
+
202
+ // message flow should have Wait for Response
203
+ expect(titles).to.include('Wait for Response');
204
+ });
205
+
206
+ it('filters splits by flow type - voice flow should not show wait for response', async () => {
207
+ const selector = await createSelector();
208
+ selector.flowType = 'voice';
209
+ await selector.updateComplete;
210
+ selector.show('split', { x: 100, y: 100 });
211
+ await selector.updateComplete;
212
+
213
+ // get all node item titles
214
+ const nodeItems = selector.shadowRoot?.querySelectorAll('.node-item-title');
215
+ const titles = Array.from(nodeItems || []).map((item) =>
216
+ item.textContent?.trim()
217
+ );
218
+
219
+ // voice flow should not have Wait for Response
220
+ expect(titles).to.not.include('Wait for Response');
221
+
222
+ // but should have Wait for Digits and Wait for Menu Selection
223
+ expect(titles).to.include('Wait for Digits');
224
+ expect(titles).to.include('Wait for Menu Selection');
225
+ });
226
+
227
+ it('filters by features - AI feature enables AI splits', async () => {
228
+ const selector = await createSelector();
229
+ selector.flowType = 'message';
230
+ selector.features = ['ai'];
231
+ await selector.updateComplete;
232
+ selector.show('split', { x: 100, y: 100 });
233
+ await selector.updateComplete;
234
+
235
+ // get all node item titles
236
+ const nodeItems = selector.shadowRoot?.querySelectorAll('.node-item-title');
237
+ const titles = Array.from(nodeItems || []).map((item) =>
238
+ item.textContent?.trim()
239
+ );
240
+
241
+ // with ai feature, should have Split by AI
242
+ expect(titles).to.include('Split by AI');
243
+ });
244
+
245
+ it('filters by features - without AI feature, AI splits are hidden', async () => {
246
+ const selector = await createSelector();
247
+ selector.flowType = 'message';
248
+ selector.features = [];
249
+ await selector.updateComplete;
250
+ selector.show('split', { x: 100, y: 100 });
251
+ await selector.updateComplete;
252
+
253
+ // get all node item titles
254
+ const nodeItems = selector.shadowRoot?.querySelectorAll('.node-item-title');
255
+ const titles = Array.from(nodeItems || []).map((item) =>
256
+ item.textContent?.trim()
257
+ );
258
+
259
+ // without ai feature, should not have Split by AI
260
+ expect(titles).to.not.include('Split by AI');
261
+ });
262
+
263
+ it('filters by features - airtime feature enables airtime actions', async () => {
264
+ const selector = await createSelector();
265
+ selector.flowType = 'message';
266
+ selector.features = ['airtime'];
267
+ await selector.updateComplete;
268
+ selector.show('action', { x: 100, y: 100 });
269
+ await selector.updateComplete;
270
+
271
+ // get all node item titles
272
+ const nodeItems = selector.shadowRoot?.querySelectorAll('.node-item-title');
273
+ const titles = Array.from(nodeItems || []).map((item) =>
274
+ item.textContent?.trim()
275
+ );
276
+
277
+ // with airtime feature, should have Send Airtime
278
+ expect(titles).to.include('Send Airtime');
279
+ });
280
+
281
+ it('filters by features - without airtime feature, airtime actions are hidden', async () => {
282
+ const selector = await createSelector();
283
+ selector.flowType = 'message';
284
+ selector.features = [];
285
+ await selector.updateComplete;
286
+ selector.show('action', { x: 100, y: 100 });
287
+ await selector.updateComplete;
288
+
289
+ // get all node item titles
290
+ const nodeItems = selector.shadowRoot?.querySelectorAll('.node-item-title');
291
+ const titles = Array.from(nodeItems || []).map((item) =>
292
+ item.textContent?.trim()
293
+ );
294
+
295
+ // without airtime feature, should not have Send Airtime
296
+ expect(titles).to.not.include('Send Airtime');
297
+ });
298
+
299
+ it('hides actions/nodes with empty flowTypes array from selector', async () => {
300
+ const selector = await createSelector();
301
+
302
+ // test that isConfigAvailable returns false for empty flowTypes array
303
+ const configWithEmptyFlowTypes = {
304
+ name: 'Test Action',
305
+ type: 'test_action',
306
+ flowTypes: [] // empty array should hide from all flow types
307
+ };
308
+
309
+ selector.flowType = 'message';
310
+ await selector.updateComplete;
311
+
312
+ // call private method via any to test behavior
313
+ const isAvailable = (selector as any).isConfigAvailable(
314
+ configWithEmptyFlowTypes
315
+ );
316
+ expect(isAvailable).to.be.false;
317
+
318
+ // test with different flow types - should still be false
319
+ selector.flowType = 'voice';
320
+ await selector.updateComplete;
321
+
322
+ const isAvailableVoice = (selector as any).isConfigAvailable(
323
+ configWithEmptyFlowTypes
324
+ );
325
+ expect(isAvailableVoice).to.be.false;
326
+ });
327
+
328
+ it('shows actions/nodes with undefined flowTypes for all flow types', async () => {
329
+ const selector = await createSelector();
330
+
331
+ // test that isConfigAvailable returns true for undefined flowTypes
332
+ const configWithUndefinedFlowTypes = {
333
+ name: 'Test Action',
334
+ type: 'test_action'
335
+ // flowTypes is undefined - should be available for all
336
+ };
337
+
338
+ selector.flowType = 'message';
339
+ await selector.updateComplete;
340
+
341
+ const isAvailable = (selector as any).isConfigAvailable(
342
+ configWithUndefinedFlowTypes
343
+ );
344
+ expect(isAvailable).to.be.true;
345
+
346
+ // test with different flow types - should still be true
347
+ selector.flowType = 'voice';
348
+ await selector.updateComplete;
349
+
350
+ const isAvailableVoice = (selector as any).isConfigAvailable(
351
+ configWithUndefinedFlowTypes
352
+ );
353
+ expect(isAvailableVoice).to.be.true;
354
+ });
152
355
  });
@@ -8,6 +8,26 @@ interface Clip {
8
8
  }
9
9
 
10
10
  import { expect, fixture, html, assert } from '@open-wc/testing';
11
+
12
+ // disable transitions for all tests to prevent flaky screenshot tests
13
+ const style = document.createElement('style');
14
+ style.textContent = `
15
+ * {
16
+ --transition-duration: 0ms !important;
17
+ }
18
+ `;
19
+ document.head.appendChild(style);
20
+
21
+ // prevent resize event listeners from being added during tests
22
+ // this prevents flaky positioning in components that adjust on resize
23
+ const originalAddEventListener = window.addEventListener;
24
+ window.addEventListener = function (type, listener, options) {
25
+ if (type === 'resize') {
26
+ // skip adding resize listeners during tests
27
+ return;
28
+ }
29
+ return originalAddEventListener.call(this, type, listener, options);
30
+ } as typeof window.addEventListener;
11
31
  import MouseHelper from './MouseHelper';
12
32
  import { Store } from '../src/store/Store';
13
33
  import { stub } from 'sinon';
@@ -3,7 +3,6 @@
3
3
  "recent_only": false,
4
4
  "next_before": 1617135091567814,
5
5
  "next_after": 1609359091567814,
6
- "start_date": "2019-09-22",
7
6
  "events": [
8
7
  {
9
8
  "uuid": "01997d74-bf67-749a-8440-688a41c3b275",
@@ -55,7 +54,7 @@
55
54
  "name": "SMS Channel"
56
55
  }
57
56
  },
58
- "_status": {"status": "failed", "reason": "error_limit"},
57
+ "_status": {"created_on": "2025-09-24T20:40:28.239437+00:00", "status": "failed", "reason": "error_limit"},
59
58
  "_logs_url": null
60
59
  },
61
60
  {
@@ -90,7 +89,7 @@
90
89
  "name": "SMS Channel"
91
90
  }
92
91
  },
93
- "_status": "W",
92
+ "_status": {"created_on": "2025-09-23T20:40:28.239434+00:00", "status": "wired"},
94
93
  "_logs_url": "/channels/channellog/read/1478/"
95
94
  },
96
95
  {
@@ -119,7 +118,7 @@
119
118
  "name": "SMS Channel"
120
119
  }
121
120
  },
122
- "_status": "W",
121
+ "_status": {"created_on": "2025-09-23T20:40:28.239434+00:00", "status": "wired"},
123
122
  "_logs_url": "/channels/channellog/read/1476/"
124
123
  },
125
124
  {
@@ -148,7 +147,7 @@
148
147
  "name": "SMS Channel"
149
148
  }
150
149
  },
151
- "_status": "W",
150
+ "_status": {"created_on": "2025-09-23T20:40:28.239434+00:00", "status": "wired"},
152
151
  "_logs_url": "/channels/channellog/read/1474/"
153
152
  },
154
153
  {
@@ -199,7 +198,7 @@
199
198
  "name": "SMS Channel"
200
199
  }
201
200
  },
202
- "_status": "W",
201
+ "_status": {"created_on": "2025-09-23T20:40:28.239434+00:00", "status": "wired"},
203
202
  "_logs_url": "/channels/channellog/read/1472/"
204
203
  },
205
204
  {
@@ -9,10 +9,10 @@
9
9
  "type": "openai"
10
10
  },
11
11
  {
12
- "uuid": "4399e7d6-fcdf-4e47-a835-f3bdb7f80938",
12
+ "uuid": "4399e7d6-fcdf-4e47-a835-f3bdb7f80938",
13
13
  "name": "GPT 5",
14
14
  "value": "4399e7d6-fcdf-4e47-a835-f3bdb7f80938",
15
15
  "type": "openai"
16
16
  }
17
17
  ]
18
- }
18
+ }
@@ -255,7 +255,8 @@ export default {
255
255
  '/api/v2/workspace.json': 'workspace.json',
256
256
  '/api/internal/locations.json': 'locations.json',
257
257
  '/api/internal/orgs.json': 'orgs.json',
258
- '/api/v2/channels.json': 'channels.json'
258
+ '/api/v2/channels.json': 'channels.json',
259
+ '/api/internal/llms.json': 'llms.json'
259
260
  };
260
261
 
261
262
  // Handle base path without query parameters
@@ -328,6 +329,50 @@ export default {
328
329
  if (context.request.method === 'POST' && context.path === '/api/v2/groups.json') {
329
330
  return handleDevEntityCreation('groups', context);
330
331
  }
332
+
333
+ // Handle mock LLM translations
334
+ if (
335
+ context.request.method === 'POST' &&
336
+ context.path.startsWith('/llm/translate/')
337
+ ) {
338
+ return new Promise((resolve) => {
339
+ let body = '';
340
+ context.req.on('data', (chunk) => {
341
+ body += chunk.toString();
342
+ });
343
+ context.req.on('end', () => {
344
+ let payload = {};
345
+ try {
346
+ payload = body ? JSON.parse(body) : {};
347
+ } catch (error) {
348
+ console.warn('Invalid LLM translate payload', error);
349
+ }
350
+
351
+ const sourceText =
352
+ typeof payload.text === 'string' ? payload.text : '';
353
+ const targetLang = payload.lang?.to || 'eng';
354
+ const modelUuid = context.path.split('/')[3] || 'mock-model';
355
+
356
+ const translated = sourceText
357
+ ? `${sourceText} (${targetLang.toUpperCase()})`
358
+ : `Translation for ${targetLang.toUpperCase()}`;
359
+
360
+ context.contentType = 'application/json';
361
+ context.status = 200;
362
+ context.body = JSON.stringify({
363
+ status: 'success',
364
+ model: modelUuid,
365
+ text: translated,
366
+ result: translated,
367
+ lang: {
368
+ from: payload.lang?.from || 'eng',
369
+ to: targetLang
370
+ }
371
+ });
372
+ resolve();
373
+ });
374
+ });
375
+ }
331
376
  }
332
377
  },
333
378
  {
@@ -345,6 +390,7 @@ export default {
345
390
 
346
391
  if (fs.existsSync(flowsDir)) {
347
392
  const files = fs.readdirSync(flowsDir).filter(file => file.endsWith('.json'));
393
+ console.log(`Listing ${files.length} flow files from ${flowsDir}...`);
348
394
 
349
395
  // Return JSON array of filenames
350
396
  context.contentType = 'application/json';
@@ -309,7 +309,6 @@ export default {
309
309
  rootDir: './',
310
310
  files: '**/test/**/*.test.ts',
311
311
  nodeResolve: true,
312
- setupFiles: ['./test-setup.js'],
313
312
  concurrency: 4,
314
313
  testFramework: {
315
314
  config: {
@@ -1,7 +0,0 @@
1
- import { SPLIT_GROUPS } from '../types';
2
- export const wait_for_audio = {
3
- type: 'wait_for_audio',
4
- name: 'Wait for Audio',
5
- group: SPLIT_GROUPS.wait
6
- };
7
- //# sourceMappingURL=wait_for_audio.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wait_for_audio.js","sourceRoot":"","sources":["../../../../src/flow/nodes/wait_for_audio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAc,MAAM,UAAU,CAAC;AAEpD,MAAM,CAAC,MAAM,cAAc,GAAe;IACxC,IAAI,EAAE,gBAAgB;IACtB,IAAI,EAAE,gBAAgB;IACtB,KAAK,EAAE,YAAY,CAAC,IAAI;CACzB,CAAC","sourcesContent":["import { SPLIT_GROUPS, NodeConfig } from '../types';\n\nexport const wait_for_audio: NodeConfig = {\n type: 'wait_for_audio',\n name: 'Wait for Audio',\n group: SPLIT_GROUPS.wait\n};\n"]}
@@ -1,7 +0,0 @@
1
- import { SPLIT_GROUPS } from '../types';
2
- export const wait_for_image = {
3
- type: 'wait_for_image',
4
- name: 'Wait for Image',
5
- group: SPLIT_GROUPS.wait
6
- };
7
- //# sourceMappingURL=wait_for_image.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wait_for_image.js","sourceRoot":"","sources":["../../../../src/flow/nodes/wait_for_image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAc,MAAM,UAAU,CAAC;AAEpD,MAAM,CAAC,MAAM,cAAc,GAAe;IACxC,IAAI,EAAE,gBAAgB;IACtB,IAAI,EAAE,gBAAgB;IACtB,KAAK,EAAE,YAAY,CAAC,IAAI;CACzB,CAAC","sourcesContent":["import { SPLIT_GROUPS, NodeConfig } from '../types';\n\nexport const wait_for_image: NodeConfig = {\n type: 'wait_for_image',\n name: 'Wait for Image',\n group: SPLIT_GROUPS.wait\n};\n"]}
@@ -1,7 +0,0 @@
1
- import { SPLIT_GROUPS } from '../types';
2
- export const wait_for_location = {
3
- type: 'wait_for_location',
4
- name: 'Wait for Location',
5
- group: SPLIT_GROUPS.wait
6
- };
7
- //# sourceMappingURL=wait_for_location.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wait_for_location.js","sourceRoot":"","sources":["../../../../src/flow/nodes/wait_for_location.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAc,MAAM,UAAU,CAAC;AAEpD,MAAM,CAAC,MAAM,iBAAiB,GAAe;IAC3C,IAAI,EAAE,mBAAmB;IACzB,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,YAAY,CAAC,IAAI;CACzB,CAAC","sourcesContent":["import { SPLIT_GROUPS, NodeConfig } from '../types';\n\nexport const wait_for_location: NodeConfig = {\n type: 'wait_for_location',\n name: 'Wait for Location',\n group: SPLIT_GROUPS.wait\n};\n"]}
@@ -1,7 +0,0 @@
1
- import { SPLIT_GROUPS } from '../types';
2
- export const wait_for_video = {
3
- type: 'wait_for_video',
4
- name: 'Wait for Video',
5
- group: SPLIT_GROUPS.wait
6
- };
7
- //# sourceMappingURL=wait_for_video.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wait_for_video.js","sourceRoot":"","sources":["../../../../src/flow/nodes/wait_for_video.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAc,MAAM,UAAU,CAAC;AAEpD,MAAM,CAAC,MAAM,cAAc,GAAe;IACxC,IAAI,EAAE,gBAAgB;IACtB,IAAI,EAAE,gBAAgB;IACtB,KAAK,EAAE,YAAY,CAAC,IAAI;CACzB,CAAC","sourcesContent":["import { SPLIT_GROUPS, NodeConfig } from '../types';\n\nexport const wait_for_video: NodeConfig = {\n type: 'wait_for_video',\n name: 'Wait for Video',\n group: SPLIT_GROUPS.wait\n};\n"]}
@@ -1,7 +0,0 @@
1
- import { SPLIT_GROUPS, NodeConfig } from '../types';
2
-
3
- export const wait_for_audio: NodeConfig = {
4
- type: 'wait_for_audio',
5
- name: 'Wait for Audio',
6
- group: SPLIT_GROUPS.wait
7
- };
@@ -1,7 +0,0 @@
1
- import { SPLIT_GROUPS, NodeConfig } from '../types';
2
-
3
- export const wait_for_image: NodeConfig = {
4
- type: 'wait_for_image',
5
- name: 'Wait for Image',
6
- group: SPLIT_GROUPS.wait
7
- };
@@ -1,7 +0,0 @@
1
- import { SPLIT_GROUPS, NodeConfig } from '../types';
2
-
3
- export const wait_for_location: NodeConfig = {
4
- type: 'wait_for_location',
5
- name: 'Wait for Location',
6
- group: SPLIT_GROUPS.wait
7
- };
@@ -1,7 +0,0 @@
1
- import { SPLIT_GROUPS, NodeConfig } from '../types';
2
-
3
- export const wait_for_video: NodeConfig = {
4
- type: 'wait_for_video',
5
- name: 'Wait for Video',
6
- group: SPLIT_GROUPS.wait
7
- };