@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
@@ -7,7 +7,7 @@ import { fromStore, zustand } from '../store/AppState';
7
7
  import { RapidElement } from '../RapidElement';
8
8
  import { repeat } from 'lit-html/directives/repeat.js';
9
9
  import { CustomEventType } from '../interfaces';
10
- import { generateUUID } from '../utils';
10
+ import { generateUUID, postJSON } from '../utils';
11
11
  import { ACTION_CONFIG, NODE_CONFIG } from './config';
12
12
  import { ACTION_GROUP_METADATA } from './types';
13
13
  import { Plumber } from './Plumber';
@@ -27,6 +27,7 @@ export function findNodeForExit(definition, exitUuid) {
27
27
  }
28
28
  const SAVE_QUIET_TIME = 500;
29
29
  const DRAG_THRESHOLD = 5;
30
+ const AUTO_TRANSLATE_MODELS_ENDPOINT = '/api/internal/llms.json';
30
31
  // Offset for positioning dropped action node relative to mouse cursor
31
32
  // Keep small to make drop location close to cursor position
32
33
  const DROP_PREVIEW_OFFSET_X = 20;
@@ -40,6 +41,18 @@ export class Editor extends RapidElement {
40
41
  get dragging() {
41
42
  return this.isDragging;
42
43
  }
44
+ getAvailableLanguages() {
45
+ var _b, _c;
46
+ // Use languages from flow definition if available, otherwise use defaults
47
+ if (((_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.languages) &&
48
+ this.definition._ui.languages.length > 0) {
49
+ return this.definition._ui.languages.map((lang) => ({
50
+ code: typeof lang === 'string' ? lang : lang.iso || lang.code,
51
+ name: typeof lang === 'string' ? lang : lang.name
52
+ }));
53
+ }
54
+ return this.DEFAULT_LANGUAGES;
55
+ }
43
56
  static get styles() {
44
57
  return css `
45
58
  #editor {
@@ -183,12 +196,193 @@ export class Editor extends RapidElement {
183
196
  .jtk-floating-endpoint {
184
197
  pointer-events: none;
185
198
  }
199
+
200
+ .localization-window-content {
201
+ display: flex;
202
+ flex-direction: column;
203
+ gap: 16px;
204
+ height: 100%;
205
+ }
206
+
207
+ .localization-header {
208
+ font-size: 13px;
209
+ color: #4b5563;
210
+ line-height: 1.4;
211
+ }
212
+
213
+ .localization-language-select {
214
+ --color-widget-border: #d1d5db;
215
+ --color-widget-background: #fff;
216
+ }
217
+
218
+ .localization-language-row {
219
+ display: flex;
220
+ align-items: flex-end;
221
+ gap: 12px;
222
+ }
223
+
224
+ .localization-language-row temba-select {
225
+ flex: 1;
226
+ }
227
+
228
+ .localization-progress {
229
+ margin-top: auto;
230
+ display: flex;
231
+ flex-direction: column;
232
+ gap: 8px;
233
+ }
234
+
235
+ .localization-progress-bar-row {
236
+ display: flex;
237
+ align-items: center;
238
+ gap: 8px;
239
+ }
240
+
241
+ .localization-progress-trigger {
242
+ flex: 1;
243
+ border-radius: 6px;
244
+ cursor: pointer;
245
+ display: flex;
246
+ align-items: center;
247
+ }
248
+
249
+ .localization-progress-trigger:focus-visible {
250
+ outline: 2px solid #94a3b8;
251
+ outline-offset: 2px;
252
+ }
253
+
254
+ .localization-progress-trigger temba-progress {
255
+ flex: 1;
256
+ }
257
+
258
+ .localization-progress h5 {
259
+ margin: 0;
260
+ font-size: 13px;
261
+ font-weight: 600;
262
+ color: #374151;
263
+ }
264
+
265
+ .localization-progress-summary {
266
+ font-size: 12px;
267
+ color: #6b7280;
268
+ display: flex;
269
+ align-items: center;
270
+ gap: 6px;
271
+ min-height: 20px;
272
+ }
273
+
274
+ .translation-settings-toggle {
275
+ display: inline-flex;
276
+ align-items: center;
277
+ gap: 6px;
278
+ background: transparent;
279
+ border: none;
280
+ color: #6b7280;
281
+ font-size: 12px;
282
+ font-weight: 600;
283
+ cursor: pointer;
284
+ padding: 4px;
285
+ border-radius: 4px;
286
+ }
287
+
288
+ .translation-settings-label {
289
+ font-size: 12px;
290
+ color: #6b7280;
291
+ }
292
+
293
+ .translation-settings-toggle:focus-visible {
294
+ outline: 2px solid #94a3b8;
295
+ outline-offset: 2px;
296
+ }
297
+
298
+ .translation-settings-arrow {
299
+ width: 8px;
300
+ height: 8px;
301
+ border-right: 2px solid currentColor;
302
+ border-bottom: 2px solid currentColor;
303
+ transform: rotate(-45deg);
304
+ transition: transform 0.2s ease;
305
+ margin-left: 2px;
306
+ }
307
+
308
+ .translation-settings-arrow.expanded {
309
+ transform: rotate(45deg);
310
+ }
311
+
312
+ .translation-settings {
313
+ }
314
+
315
+ .translation-settings-row {
316
+ display: flex;
317
+ align-items: center;
318
+ justify-content: space-between;
319
+ }
320
+
321
+ .translation-settings-row temba-checkbox {
322
+ width: 100%;
323
+ }
324
+
325
+ .auto-translate-button {
326
+ background: var(--color-primary-dark);
327
+ border: none;
328
+ color: #fff;
329
+ padding: 10px 12px;
330
+ border-radius: var(--curvature);
331
+ font-size: 12px;
332
+ font-weight: 600;
333
+ cursor: pointer;
334
+ transition: opacity 0.2s ease;
335
+ }
336
+
337
+ .auto-translate-button[disabled] {
338
+ opacity: 0.5;
339
+ cursor: not-allowed;
340
+ }
341
+
342
+ .auto-translate-error {
343
+ font-size: 12px;
344
+ color: #b91c1c;
345
+ }
346
+
347
+ .auto-translate-dialog-content {
348
+ padding: 20px;
349
+ display: flex;
350
+ flex-direction: column;
351
+ gap: 12px;
352
+ font-size: 14px;
353
+ color: #374151;
354
+ }
355
+
356
+ .auto-translate-dialog-content p {
357
+ margin: 0;
358
+ }
359
+
360
+ .auto-translate-loading {
361
+ display: flex;
362
+ align-items: center;
363
+ gap: 8px;
364
+ font-size: 13px;
365
+ color: #6b7280;
366
+ }
367
+
368
+ .auto-translate-empty {
369
+ font-size: 13px;
370
+ color: #6b7280;
371
+ }
372
+
373
+ .localization-empty {
374
+ font-size: 13px;
375
+ color: #9ca3af;
376
+ white-space: nowrap;
377
+ }
186
378
  `;
187
379
  }
188
380
  constructor() {
189
381
  super();
190
382
  // timer for debounced saving
191
383
  this.saveTimer = null;
384
+ this.flowType = 'message';
385
+ this.features = [];
192
386
  // Drag state
193
387
  this.isDragging = false;
194
388
  this.isMouseDown = false;
@@ -203,6 +397,16 @@ export class Editor extends RapidElement {
203
397
  this.sourceId = null;
204
398
  this.dragFromNodeId = null;
205
399
  this.isValidTarget = true;
400
+ this.localizationWindowHidden = true;
401
+ this.translationFilters = {
402
+ categories: false
403
+ };
404
+ this.translationSettingsExpanded = false;
405
+ this.autoTranslateDialogOpen = false;
406
+ this.autoTranslating = false;
407
+ this.autoTranslateModel = null;
408
+ this.autoTranslateError = null;
409
+ this.translationCache = new Map();
206
410
  // NodeEditor state - handles both node and action editing
207
411
  this.editingNode = null;
208
412
  this.editingNodeUI = null;
@@ -217,6 +421,12 @@ export class Editor extends RapidElement {
217
421
  // Track previous target node to clear placeholder when moving between nodes
218
422
  this.previousActionDragTargetNodeUuid = null;
219
423
  this.canvasMouseDown = false;
424
+ // Default languages if not specified in flow definition
425
+ this.DEFAULT_LANGUAGES = [
426
+ { code: 'eng', name: 'English' },
427
+ { code: 'fra', name: 'French' },
428
+ { code: 'esp', name: 'Spanish' }
429
+ ];
220
430
  // Bound event handlers to maintain proper 'this' context
221
431
  this.boundMouseMove = this.handleMouseMove.bind(this);
222
432
  this.boundMouseUp = this.handleMouseUp.bind(this);
@@ -264,18 +474,54 @@ export class Editor extends RapidElement {
264
474
  this.isValidTarget = true;
265
475
  }
266
476
  updated(changes) {
477
+ var _b, _c, _d;
267
478
  super.updated(changes);
268
479
  if (changes.has('canvasSize')) {
269
480
  // console.log('Setting canvas size', this.canvasSize);
270
481
  }
271
482
  if (changes.has('definition')) {
272
483
  this.updateCanvasSize();
484
+ // Set flowType from the loaded definition
485
+ if ((_b = this.definition) === null || _b === void 0 ? void 0 : _b.type) {
486
+ this.flowType = this.getFlowTypeFromDefinition(this.definition.type);
487
+ }
488
+ const filters = ((_d = (_c = this.definition) === null || _c === void 0 ? void 0 : _c._ui) === null || _d === void 0 ? void 0 : _d.translation_filters) || {
489
+ categories: false
490
+ };
491
+ const normalizedFilters = {
492
+ categories: !!filters.categories
493
+ };
494
+ if (this.translationFilters.categories !== normalizedFilters.categories) {
495
+ this.translationFilters = normalizedFilters;
496
+ }
497
+ this.translationCache.clear();
273
498
  }
274
499
  if (changes.has('dirtyDate')) {
275
500
  if (this.dirtyDate) {
276
501
  this.debouncedSave();
277
502
  }
278
503
  }
504
+ if (changes.has('languageCode')) {
505
+ this.translationCache.clear();
506
+ }
507
+ }
508
+ /**
509
+ * Map FlowDefinition type to Editor flowType
510
+ * FlowDefinition uses: 'messaging', 'messaging_background', 'messaging_offline', 'voice'
511
+ * Editor uses: 'message', 'voice', 'background'
512
+ */
513
+ getFlowTypeFromDefinition(definitionType) {
514
+ if (definitionType === 'voice') {
515
+ return 'voice';
516
+ }
517
+ else if (definitionType === 'messaging_background' ||
518
+ definitionType === 'messaging_offline') {
519
+ return 'background';
520
+ }
521
+ else {
522
+ // 'messaging' or any other messaging type defaults to 'message'
523
+ return 'message';
524
+ }
279
525
  }
280
526
  debouncedSave() {
281
527
  // Clear any existing timer
@@ -309,6 +555,15 @@ export class Editor extends RapidElement {
309
555
  });
310
556
  getStore().getState().setDirtyDate(null);
311
557
  }
558
+ handleLanguageChange(languageCode) {
559
+ zustand.getState().setLanguageCode(languageCode);
560
+ // Repaint connections after language change since node sizes can change
561
+ if (this.plumber) {
562
+ requestAnimationFrame(() => {
563
+ this.plumber.repaintEverything();
564
+ });
565
+ }
566
+ }
312
567
  disconnectedCallback() {
313
568
  super.disconnectedCallback();
314
569
  if (this.saveTimer !== null) {
@@ -1312,6 +1567,567 @@ export class Editor extends RapidElement {
1312
1567
  });
1313
1568
  }
1314
1569
  }
1570
+ getLocalizationLanguages() {
1571
+ if (!this.definition) {
1572
+ return [];
1573
+ }
1574
+ const baseLanguage = this.definition.language;
1575
+ return this.getAvailableLanguages().filter((lang) => lang.code !== baseLanguage);
1576
+ }
1577
+ getLocalizationProgress(languageCode) {
1578
+ if (!this.definition ||
1579
+ !languageCode ||
1580
+ languageCode === this.definition.language) {
1581
+ return { total: 0, localized: 0 };
1582
+ }
1583
+ const bundles = this.buildTranslationBundles(this.translationFilters.categories, languageCode);
1584
+ return this.getTranslationCounts(bundles);
1585
+ }
1586
+ getLanguageLocalization(languageCode) {
1587
+ var _b;
1588
+ if (!((_b = this.definition) === null || _b === void 0 ? void 0 : _b.localization)) {
1589
+ return {};
1590
+ }
1591
+ return this.definition.localization[languageCode] || {};
1592
+ }
1593
+ buildTranslationBundles(includeCategories, languageCode = this.languageCode) {
1594
+ if (!this.definition ||
1595
+ !languageCode ||
1596
+ languageCode === this.definition.language) {
1597
+ return [];
1598
+ }
1599
+ const languageLocalization = this.getLanguageLocalization(languageCode);
1600
+ const bundles = [];
1601
+ this.definition.nodes.forEach((node) => {
1602
+ var _b, _c, _d, _e;
1603
+ node.actions.forEach((action) => {
1604
+ const config = ACTION_CONFIG[action.type];
1605
+ if (!(config === null || config === void 0 ? void 0 : config.localizable) || config.localizable.length === 0) {
1606
+ return;
1607
+ }
1608
+ // For send_msg actions, only count 'text' for progress tracking
1609
+ // (quick_replies and attachments are still localizable but don't count toward progress)
1610
+ const localizableKeys = action.type === 'send_msg'
1611
+ ? config.localizable.filter((key) => key === 'text')
1612
+ : config.localizable;
1613
+ const translations = this.findTranslations('property', action.uuid, localizableKeys, action, languageLocalization);
1614
+ if (translations.length > 0) {
1615
+ bundles.push({
1616
+ nodeUuid: node.uuid,
1617
+ actionUuid: action.uuid,
1618
+ translations
1619
+ });
1620
+ }
1621
+ });
1622
+ if (!includeCategories) {
1623
+ return;
1624
+ }
1625
+ const nodeUI = (_c = (_b = this.definition._ui) === null || _b === void 0 ? void 0 : _b.nodes) === null || _c === void 0 ? void 0 : _c[node.uuid];
1626
+ const nodeType = nodeUI === null || nodeUI === void 0 ? void 0 : nodeUI.type;
1627
+ if (!nodeType) {
1628
+ return;
1629
+ }
1630
+ const nodeConfig = NODE_CONFIG[nodeType];
1631
+ if ((nodeConfig === null || nodeConfig === void 0 ? void 0 : nodeConfig.localizable) === 'categories' &&
1632
+ ((_e = (_d = node.router) === null || _d === void 0 ? void 0 : _d.categories) === null || _e === void 0 ? void 0 : _e.length)) {
1633
+ const categoryTranslations = node.router.categories.flatMap((category) => this.findTranslations('category', category.uuid, ['name'], category, languageLocalization));
1634
+ if (categoryTranslations.length > 0) {
1635
+ bundles.push({
1636
+ nodeUuid: node.uuid,
1637
+ translations: categoryTranslations
1638
+ });
1639
+ }
1640
+ }
1641
+ });
1642
+ return bundles;
1643
+ }
1644
+ findTranslations(type, uuid, localizeableKeys, source, localization) {
1645
+ const translations = [];
1646
+ localizeableKeys.forEach((attribute) => {
1647
+ if (attribute === 'quick_replies') {
1648
+ return;
1649
+ }
1650
+ const pathSegments = attribute.split('.');
1651
+ let from = source;
1652
+ let to = [];
1653
+ while (pathSegments.length > 0 && from) {
1654
+ if (from.uuid) {
1655
+ to = localization[from.uuid];
1656
+ }
1657
+ const path = pathSegments.shift();
1658
+ if (!path) {
1659
+ break;
1660
+ }
1661
+ if (to) {
1662
+ to = to[path];
1663
+ }
1664
+ from = from[path];
1665
+ }
1666
+ if (!from) {
1667
+ return;
1668
+ }
1669
+ const fromValue = this.formatTranslationValue(from);
1670
+ if (!fromValue) {
1671
+ return;
1672
+ }
1673
+ const toValue = to ? this.formatTranslationValue(to) : null;
1674
+ translations.push({
1675
+ uuid,
1676
+ type,
1677
+ attribute,
1678
+ from: fromValue,
1679
+ to: toValue
1680
+ });
1681
+ });
1682
+ return translations;
1683
+ }
1684
+ formatTranslationValue(value) {
1685
+ if (value === null || value === undefined) {
1686
+ return null;
1687
+ }
1688
+ if (Array.isArray(value)) {
1689
+ const normalized = value
1690
+ .map((entry) => this.formatTranslationValue(entry))
1691
+ .filter((entry) => !!entry);
1692
+ return normalized.length > 0 ? normalized.join(', ') : null;
1693
+ }
1694
+ if (typeof value === 'object') {
1695
+ if ('name' in value && value.name) {
1696
+ return String(value.name);
1697
+ }
1698
+ if ('arguments' in value && Array.isArray(value.arguments)) {
1699
+ return value.arguments.join(' ');
1700
+ }
1701
+ return null;
1702
+ }
1703
+ if (typeof value === 'number') {
1704
+ return value.toString();
1705
+ }
1706
+ if (typeof value === 'string') {
1707
+ const trimmed = value.trim();
1708
+ return trimmed.length > 0 ? trimmed : null;
1709
+ }
1710
+ return null;
1711
+ }
1712
+ getTranslationCounts(bundles) {
1713
+ return bundles.reduce((counts, bundle) => {
1714
+ bundle.translations.forEach((translation) => {
1715
+ counts.total += 1;
1716
+ if (translation.to && translation.to.trim().length > 0) {
1717
+ counts.localized += 1;
1718
+ }
1719
+ });
1720
+ return counts;
1721
+ }, { total: 0, localized: 0 });
1722
+ }
1723
+ handleLocalizationTabClick() {
1724
+ const languages = this.getLocalizationLanguages();
1725
+ if (!languages.length) {
1726
+ return;
1727
+ }
1728
+ this.localizationWindowHidden = false;
1729
+ const alreadySelected = languages.some((lang) => lang.code === this.languageCode);
1730
+ if (!alreadySelected) {
1731
+ this.handleLanguageChange(languages[0].code);
1732
+ }
1733
+ }
1734
+ handleLocalizationLanguageSelect(languageCode) {
1735
+ if (languageCode === this.languageCode) {
1736
+ return;
1737
+ }
1738
+ this.handleLanguageChange(languageCode);
1739
+ }
1740
+ handleLocalizationLanguageSelectChange(event) {
1741
+ var _b, _c;
1742
+ const select = event.target;
1743
+ const nextValue = (_c = (_b = select === null || select === void 0 ? void 0 : select.values) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.value;
1744
+ if (nextValue) {
1745
+ this.handleLocalizationLanguageSelect(nextValue);
1746
+ }
1747
+ }
1748
+ handleLocalizationWindowClosed() {
1749
+ var _b;
1750
+ this.localizationWindowHidden = true;
1751
+ const baseLanguage = (_b = this.definition) === null || _b === void 0 ? void 0 : _b.language;
1752
+ if (baseLanguage && this.languageCode !== baseLanguage) {
1753
+ this.handleLanguageChange(baseLanguage);
1754
+ }
1755
+ }
1756
+ toggleTranslationSettings() {
1757
+ this.translationSettingsExpanded = !this.translationSettingsExpanded;
1758
+ }
1759
+ handleLocalizationProgressToggleClick(event) {
1760
+ const target = event.target;
1761
+ if (target.closest('.translation-settings-toggle')) {
1762
+ return;
1763
+ }
1764
+ this.toggleTranslationSettings();
1765
+ }
1766
+ handleLocalizationProgressToggleKeydown(event) {
1767
+ if (event.key === 'Enter' || event.key === ' ') {
1768
+ event.preventDefault();
1769
+ this.toggleTranslationSettings();
1770
+ }
1771
+ }
1772
+ handleIncludeCategoriesChange(event) {
1773
+ var _b, _c;
1774
+ const checkbox = event.target;
1775
+ const categories = (_b = checkbox === null || checkbox === void 0 ? void 0 : checkbox.checked) !== null && _b !== void 0 ? _b : false;
1776
+ this.translationFilters = { categories };
1777
+ (_c = getStore()) === null || _c === void 0 ? void 0 : _c.getState().setTranslationFilters({ categories });
1778
+ this.requestUpdate();
1779
+ }
1780
+ async handleAutoTranslateClick(event) {
1781
+ event.preventDefault();
1782
+ event.stopPropagation();
1783
+ if (this.autoTranslating) {
1784
+ this.autoTranslating = false;
1785
+ return;
1786
+ }
1787
+ this.autoTranslateDialogOpen = true;
1788
+ }
1789
+ handleAutoTranslateDialogButton(event) {
1790
+ var _b;
1791
+ const button = (_b = event.detail) === null || _b === void 0 ? void 0 : _b.button;
1792
+ if (!button) {
1793
+ return;
1794
+ }
1795
+ if (button.name === 'Translate') {
1796
+ if (!this.autoTranslateModel) {
1797
+ return;
1798
+ }
1799
+ this.autoTranslateDialogOpen = false;
1800
+ this.autoTranslateError = null;
1801
+ this.autoTranslating = true;
1802
+ this.runAutoTranslation().catch((error) => {
1803
+ console.error('Auto translation failed', error);
1804
+ this.autoTranslateError = 'Auto translation failed. Please try again.';
1805
+ this.autoTranslating = false;
1806
+ });
1807
+ }
1808
+ else if (button.name === 'Cancel' || button.name === 'Close') {
1809
+ this.autoTranslateDialogOpen = false;
1810
+ }
1811
+ }
1812
+ handleAutoTranslateModelChange(event) {
1813
+ var _b;
1814
+ const select = event.target;
1815
+ const nextModel = ((_b = select === null || select === void 0 ? void 0 : select.values) === null || _b === void 0 ? void 0 : _b[0]) || null;
1816
+ this.autoTranslateModel = nextModel;
1817
+ }
1818
+ shouldTranslateValue(text) {
1819
+ if (!text) {
1820
+ return false;
1821
+ }
1822
+ const trimmed = text.trim();
1823
+ if (trimmed.length <= 1) {
1824
+ return false;
1825
+ }
1826
+ if (/^\d+$/.test(trimmed)) {
1827
+ return false;
1828
+ }
1829
+ return true;
1830
+ }
1831
+ async requestAutoTranslation(text) {
1832
+ var _b, _c;
1833
+ if (!this.autoTranslateModel || !this.definition) {
1834
+ return null;
1835
+ }
1836
+ const payload = {
1837
+ text,
1838
+ lang: {
1839
+ from: this.definition.language,
1840
+ to: this.languageCode
1841
+ }
1842
+ };
1843
+ const response = await postJSON(`/llm/translate/${this.autoTranslateModel.uuid}/`, payload);
1844
+ if ((response === null || response === void 0 ? void 0 : response.status) === 200) {
1845
+ const result = ((_b = response.json) === null || _b === void 0 ? void 0 : _b.result) || ((_c = response.json) === null || _c === void 0 ? void 0 : _c.text);
1846
+ return result ? String(result) : null;
1847
+ }
1848
+ throw new Error('Auto translation request failed');
1849
+ }
1850
+ applyLocalizationUpdates(updates, autoTranslated = false) {
1851
+ if (!updates.length || !this.definition) {
1852
+ return;
1853
+ }
1854
+ const store = getStore();
1855
+ if (!store) {
1856
+ return;
1857
+ }
1858
+ updates.forEach(({ uuid, translations }) => {
1859
+ var _b, _c;
1860
+ const normalized = Object.entries(translations).reduce((acc, [key, value]) => {
1861
+ if (!value) {
1862
+ return acc;
1863
+ }
1864
+ acc[key] = Array.isArray(value) ? value : [value];
1865
+ return acc;
1866
+ }, {});
1867
+ const existing = ((_c = (_b = this.definition.localization) === null || _b === void 0 ? void 0 : _b[this.languageCode]) === null || _c === void 0 ? void 0 : _c[uuid]) || {};
1868
+ const merged = { ...existing, ...normalized };
1869
+ store.getState().updateLocalization(this.languageCode, uuid, merged);
1870
+ if (autoTranslated) {
1871
+ zustand
1872
+ .getState()
1873
+ .markAutoTranslated(this.languageCode, uuid, Object.keys(translations));
1874
+ }
1875
+ });
1876
+ }
1877
+ async runAutoTranslation() {
1878
+ if (!this.definition ||
1879
+ this.languageCode === this.definition.language ||
1880
+ !this.autoTranslateModel) {
1881
+ this.autoTranslating = false;
1882
+ return;
1883
+ }
1884
+ const bundles = this.buildTranslationBundles(this.translationFilters.categories);
1885
+ for (const bundle of bundles) {
1886
+ if (!this.autoTranslating) {
1887
+ break;
1888
+ }
1889
+ const untranslated = bundle.translations.filter((translation) => !translation.to || translation.to.trim().length === 0);
1890
+ if (untranslated.length === 0) {
1891
+ continue;
1892
+ }
1893
+ const updates = [];
1894
+ for (const translation of untranslated) {
1895
+ if (!this.autoTranslating) {
1896
+ break;
1897
+ }
1898
+ if (!this.shouldTranslateValue(translation.from)) {
1899
+ continue;
1900
+ }
1901
+ const cached = this.translationCache.get(translation.from);
1902
+ if (cached) {
1903
+ updates.push({
1904
+ uuid: translation.uuid,
1905
+ translations: { [translation.attribute]: cached }
1906
+ });
1907
+ continue;
1908
+ }
1909
+ try {
1910
+ const result = await this.requestAutoTranslation(translation.from);
1911
+ if (result) {
1912
+ this.translationCache.set(translation.from, result);
1913
+ updates.push({
1914
+ uuid: translation.uuid,
1915
+ translations: { [translation.attribute]: result }
1916
+ });
1917
+ }
1918
+ }
1919
+ catch (error) {
1920
+ console.error('Auto translation request failed', error);
1921
+ this.autoTranslateError =
1922
+ 'Auto translation failed. Please try again.';
1923
+ this.autoTranslating = false;
1924
+ break;
1925
+ }
1926
+ }
1927
+ if (updates.length > 0) {
1928
+ this.applyLocalizationUpdates(updates, true);
1929
+ }
1930
+ if (!this.autoTranslating) {
1931
+ break;
1932
+ }
1933
+ }
1934
+ this.autoTranslating = false;
1935
+ }
1936
+ renderLocalizationWindow() {
1937
+ var _b, _c, _d;
1938
+ const languages = this.getLocalizationLanguages();
1939
+ if (!languages.length) {
1940
+ return html ``;
1941
+ }
1942
+ const baseLanguage = (_b = this.definition) === null || _b === void 0 ? void 0 : _b.language;
1943
+ const availableLanguages = this.getAvailableLanguages();
1944
+ const baseName = ((_c = availableLanguages.find((lang) => lang.code === baseLanguage)) === null || _c === void 0 ? void 0 : _c.name) ||
1945
+ 'Base Language';
1946
+ const activeLanguageCode = languages.some((lang) => lang.code === this.languageCode)
1947
+ ? this.languageCode
1948
+ : (_d = languages[0]) === null || _d === void 0 ? void 0 : _d.code;
1949
+ const activeLanguage = activeLanguageCode
1950
+ ? languages.find((lang) => lang.code === activeLanguageCode)
1951
+ : null;
1952
+ const progress = this.getLocalizationProgress(activeLanguageCode || '');
1953
+ const includeCategories = this.translationFilters.categories;
1954
+ const settingsPanelId = 'translation-settings-panel';
1955
+ const remainingTranslations = Math.max(progress.total - progress.localized, 0);
1956
+ const hasTranslations = progress.total > 0;
1957
+ const hasPendingTranslations = remainingTranslations > 0;
1958
+ const autoTranslateButtonLabel = this.autoTranslating
1959
+ ? 'Stop Auto Translate'
1960
+ : 'Auto Translate';
1961
+ const autoTranslateButtonDisabled = !this.autoTranslating && !hasTranslations;
1962
+ return html `
1963
+ <temba-floating-window
1964
+ id="localization-window"
1965
+ header="Translations"
1966
+ .width=${360}
1967
+ .maxHeight=${600}
1968
+ .top=${20}
1969
+ color="#6b7280"
1970
+ .hidden=${this.localizationWindowHidden}
1971
+ @temba-dialog-hidden=${this.handleLocalizationWindowClosed}
1972
+ >
1973
+ <div class="localization-window-content">
1974
+ <div class="localization-header">
1975
+ Translate from <strong>${baseName}</strong> to the languages below.
1976
+ Closing this window returns you to editing in ${baseName}.
1977
+ </div>
1978
+ <div class="localization-language-row">
1979
+ <temba-select
1980
+ flavor="small"
1981
+ class="localization-language-select"
1982
+ .values=${activeLanguage
1983
+ ? [{ name: activeLanguage.name, value: activeLanguage.code }]
1984
+ : []}
1985
+ @change=${this.handleLocalizationLanguageSelectChange}
1986
+ >
1987
+ ${languages.map((lang) => html `<temba-option
1988
+ value="${lang.code}"
1989
+ name="${lang.name}"
1990
+ ></temba-option>`)}
1991
+ </temba-select>
1992
+ <button
1993
+ class="auto-translate-button"
1994
+ type="button"
1995
+ ?disabled=${autoTranslateButtonDisabled}
1996
+ @click=${this.handleAutoTranslateClick}
1997
+ >
1998
+ ${autoTranslateButtonLabel}
1999
+ </button>
2000
+ </div>
2001
+ <div class="localization-progress">
2002
+ <div class="localization-progress-summary">
2003
+ ${this.autoTranslating
2004
+ ? html `<temba-loading units="3" size="8"></temba-loading>
2005
+ <span>Auto translating remaining text…</span>`
2006
+ : !hasTranslations
2007
+ ? html `<span>
2008
+ Add content or enable more options to start translating.
2009
+ </span>`
2010
+ : hasPendingTranslations
2011
+ ? html `<span>
2012
+ ${progress.localized} of ${progress.total} items translated
2013
+ </span>`
2014
+ : html `<span>All items are translated.</span>`}
2015
+ </div>
2016
+ ${this.autoTranslateError
2017
+ ? html `<div class="auto-translate-error">
2018
+ ${this.autoTranslateError}
2019
+ </div>`
2020
+ : ''}
2021
+ <div class="localization-progress-bar-row">
2022
+ <div
2023
+ class="localization-progress-trigger"
2024
+ role="button"
2025
+ tabindex="0"
2026
+ aria-expanded="${this.translationSettingsExpanded}"
2027
+ aria-controls="${settingsPanelId}"
2028
+ @click=${this.handleLocalizationProgressToggleClick}
2029
+ @keydown=${this.handleLocalizationProgressToggleKeydown}
2030
+ >
2031
+ <temba-progress
2032
+ .current=${progress.localized}
2033
+ .total=${Math.max(progress.total, 1)}
2034
+ .animated=${false}
2035
+ ></temba-progress>
2036
+ </div>
2037
+ <button
2038
+ class="translation-settings-toggle"
2039
+ type="button"
2040
+ @click=${this.toggleTranslationSettings}
2041
+ aria-expanded="${this.translationSettingsExpanded}"
2042
+ aria-controls="${settingsPanelId}"
2043
+ >
2044
+ <span
2045
+ class="translation-settings-arrow ${this
2046
+ .translationSettingsExpanded
2047
+ ? 'expanded'
2048
+ : ''}"
2049
+ ></span>
2050
+ </button>
2051
+ </div>
2052
+ ${this.translationSettingsExpanded
2053
+ ? html `<div id="${settingsPanelId}" class="translation-settings">
2054
+ <div class="translation-settings-row">
2055
+ <temba-checkbox
2056
+ name="include-categories"
2057
+ label="Include categories"
2058
+ ?checked=${includeCategories}
2059
+ style="--checkbox-padding:5px; border-radius:var(--curvature);"
2060
+ @change=${this.handleIncludeCategoriesChange}
2061
+ ></temba-checkbox>
2062
+ </div>
2063
+ </div>`
2064
+ : ''}
2065
+ </div>
2066
+ </div>
2067
+ </temba-floating-window>
2068
+ `;
2069
+ }
2070
+ renderAutoTranslateDialog() {
2071
+ if (!this.autoTranslateDialogOpen) {
2072
+ return html ``;
2073
+ }
2074
+ const selectedModel = this.autoTranslateModel
2075
+ ? [this.autoTranslateModel]
2076
+ : [];
2077
+ const disableTranslate = !this.autoTranslateModel;
2078
+ return html `
2079
+ <temba-dialog
2080
+ header="Auto translate"
2081
+ .open=${this.autoTranslateDialogOpen}
2082
+ primaryButtonName="Translate"
2083
+ cancelButtonName="Cancel"
2084
+ size="small"
2085
+ .disabled=${disableTranslate}
2086
+ @temba-button-clicked=${this.handleAutoTranslateDialogButton}
2087
+ >
2088
+ <div class="auto-translate-dialog-content">
2089
+ <p>
2090
+ We'll send any untranslated text to the selected AI model and save
2091
+ the responses automatically.
2092
+ </p>
2093
+ <div class="auto-translate-models">
2094
+ <temba-select
2095
+ class="auto-translate-model-select"
2096
+ endpoint="${AUTO_TRANSLATE_MODELS_ENDPOINT}"
2097
+ .valueKey=${'uuid'}
2098
+ .values=${selectedModel}
2099
+ ?searchable=${true}
2100
+ ?clearable=${true}
2101
+ placeholder="Select an AI model"
2102
+ @change=${this.handleAutoTranslateModelChange}
2103
+ ></temba-select>
2104
+ </div>
2105
+ <p>Only text without translations will be sent.</p>
2106
+ ${this.autoTranslateError
2107
+ ? html `<div class="auto-translate-error">
2108
+ ${this.autoTranslateError}
2109
+ </div>`
2110
+ : ''}
2111
+ </div>
2112
+ </temba-dialog>
2113
+ `;
2114
+ }
2115
+ renderLocalizationTab() {
2116
+ const languages = this.getLocalizationLanguages();
2117
+ if (!languages.length) {
2118
+ return html ``;
2119
+ }
2120
+ return html `
2121
+ <temba-floating-tab
2122
+ id="localization-tab"
2123
+ icon="language"
2124
+ label="Translate Flow"
2125
+ color="#6b7280"
2126
+ .hidden=${!this.localizationWindowHidden}
2127
+ @temba-button-clicked=${this.handleLocalizationTabClick}
2128
+ ></temba-floating-tab>
2129
+ `;
2130
+ }
1315
2131
  render() {
1316
2132
  var _b, _c;
1317
2133
  // we have to embed our own style since we are in light DOM
@@ -1320,7 +2136,8 @@ export class Editor extends RapidElement {
1320
2136
  ${unsafeCSS(CanvasNode.styles.cssText)}
1321
2137
  </style>`;
1322
2138
  const stickies = ((_c = (_b = this.definition) === null || _b === void 0 ? void 0 : _b._ui) === null || _c === void 0 ? void 0 : _c.stickies) || {};
1323
- return html `${style}
2139
+ return html `${style} ${this.renderLocalizationWindow()}
2140
+ ${this.renderAutoTranslateDialog()}
1324
2141
  <div id="editor">
1325
2142
  <div
1326
2143
  id="grid"
@@ -1390,7 +2207,11 @@ export class Editor extends RapidElement {
1390
2207
  : ''}
1391
2208
 
1392
2209
  <temba-canvas-menu></temba-canvas-menu>
1393
- <temba-node-type-selector></temba-node-type-selector> `;
2210
+ <temba-node-type-selector
2211
+ .flowType=${this.flowType}
2212
+ .features=${this.features}
2213
+ ></temba-node-type-selector>
2214
+ ${this.renderLocalizationTab()} `;
1394
2215
  }
1395
2216
  }
1396
2217
  __decorate([
@@ -1399,6 +2220,12 @@ __decorate([
1399
2220
  __decorate([
1400
2221
  property({ type: String })
1401
2222
  ], Editor.prototype, "version", void 0);
2223
+ __decorate([
2224
+ property({ type: String })
2225
+ ], Editor.prototype, "flowType", void 0);
2226
+ __decorate([
2227
+ property({ type: Array })
2228
+ ], Editor.prototype, "features", void 0);
1402
2229
  __decorate([
1403
2230
  fromStore(zustand, (state) => state.flowDefinition)
1404
2231
  ], Editor.prototype, "definition", void 0);
@@ -1408,6 +2235,12 @@ __decorate([
1408
2235
  __decorate([
1409
2236
  fromStore(zustand, (state) => state.dirtyDate)
1410
2237
  ], Editor.prototype, "dirtyDate", void 0);
2238
+ __decorate([
2239
+ fromStore(zustand, (state) => state.languageCode)
2240
+ ], Editor.prototype, "languageCode", void 0);
2241
+ __decorate([
2242
+ fromStore(zustand, (state) => state.isTranslating)
2243
+ ], Editor.prototype, "isTranslating", void 0);
1411
2244
  __decorate([
1412
2245
  state()
1413
2246
  ], Editor.prototype, "isDragging", void 0);
@@ -1435,6 +2268,27 @@ __decorate([
1435
2268
  __decorate([
1436
2269
  state()
1437
2270
  ], Editor.prototype, "isValidTarget", void 0);
2271
+ __decorate([
2272
+ state()
2273
+ ], Editor.prototype, "localizationWindowHidden", void 0);
2274
+ __decorate([
2275
+ state()
2276
+ ], Editor.prototype, "translationFilters", void 0);
2277
+ __decorate([
2278
+ state()
2279
+ ], Editor.prototype, "translationSettingsExpanded", void 0);
2280
+ __decorate([
2281
+ state()
2282
+ ], Editor.prototype, "autoTranslateDialogOpen", void 0);
2283
+ __decorate([
2284
+ state()
2285
+ ], Editor.prototype, "autoTranslating", void 0);
2286
+ __decorate([
2287
+ state()
2288
+ ], Editor.prototype, "autoTranslateModel", void 0);
2289
+ __decorate([
2290
+ state()
2291
+ ], Editor.prototype, "autoTranslateError", void 0);
1438
2292
  __decorate([
1439
2293
  state()
1440
2294
  ], Editor.prototype, "editingNode", void 0);