@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.
- package/CHANGELOG.md +14 -0
- package/demo/components/floating-tabs/example.html +400 -0
- package/demo/components/flow/index.html +1 -1
- package/demo/data/flows/sample-flow.json +41 -2
- package/demo/data/flows/voicemail.json +613 -0
- package/demo/index.html +6 -0
- package/dist/locales/es.js +5 -5
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +5 -5
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/locale-codes.js +11 -2
- package/dist/locales/locale-codes.js.map +1 -1
- package/dist/locales/pt.js +5 -5
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +1109 -535
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +167 -0
- package/out-tsc/src/display/FloatingTab.js.map +1 -0
- package/out-tsc/src/display/ProgressBar.js +22 -2
- package/out-tsc/src/display/ProgressBar.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +165 -31
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +857 -3
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +239 -19
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/NodeTypeSelector.js +44 -3
- package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -1
- package/out-tsc/src/flow/StickyNote.js +12 -3
- package/out-tsc/src/flow/StickyNote.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js +2 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_urn.js +2 -1
- package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +2 -1
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/play_audio.js +2 -1
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js +2 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/request_optin.js +1 -0
- package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +2 -1
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/send_broadcast.js +2 -1
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
- package/out-tsc/src/flow/actions/send_email.js +2 -1
- package/out-tsc/src/flow/actions/send_email.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +93 -3
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_field.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_language.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_name.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_status.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
- package/out-tsc/src/flow/actions/set_run_result.js +2 -1
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/actions/start_session.js +2 -1
- package/out-tsc/src/flow/actions/start_session.js.map +1 -1
- package/out-tsc/src/flow/config.js +2 -10
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/shared.js +54 -0
- package/out-tsc/src/flow/nodes/shared.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_airtime.js +9 -3
- package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_contact_field.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_expression.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_groups.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_intent.js +3 -2
- package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm.js +9 -2
- package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +9 -2
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_random.js +8 -2
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_resthook.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_run_result.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_scheme.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_subflow.js +8 -2
- package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_ticket.js +8 -2
- package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_webhook.js +8 -2
- package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js +3 -2
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +3 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +8 -3
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/types.js +15 -0
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/layout/FloatingWindow.js +346 -0
- package/out-tsc/src/layout/FloatingWindow.js.map +1 -0
- package/out-tsc/src/live/ContactChat.js +3 -19
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/locales/es.js +5 -5
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +5 -5
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/locale-codes.js +11 -2
- package/out-tsc/src/locales/locale-codes.js.map +1 -1
- package/out-tsc/src/locales/pt.js +5 -5
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/store/AppState.js +67 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/temba-modules.js +4 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-floating-tab.test.js +91 -0
- package/out-tsc/test/temba-floating-tab.test.js.map +1 -0
- package/out-tsc/test/temba-floating-window.test.js +301 -0
- package/out-tsc/test/temba-floating-window.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +117 -0
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-localization.test.js +471 -0
- package/out-tsc/test/temba-localization.test.js.map +1 -0
- package/out-tsc/test/temba-node-type-selector.test.js +150 -0
- package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +18 -0
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/floating-tab/default.png +0 -0
- package/screenshots/truth/floating-tab/gray.png +0 -0
- package/screenshots/truth/floating-tab/green.png +0 -0
- package/screenshots/truth/floating-tab/hidden.png +0 -0
- package/screenshots/truth/floating-tab/hover.png +0 -0
- package/screenshots/truth/floating-tab/purple.png +0 -0
- package/screenshots/truth/floating-window/chromeless.png +0 -0
- package/screenshots/truth/floating-window/custom-size.png +0 -0
- package/screenshots/truth/floating-window/default.png +0 -0
- package/screenshots/truth/floating-window/with-header.png +0 -0
- package/screenshots/truth/node-type-selector/action-mode.png +0 -0
- package/screenshots/truth/node-type-selector/split-mode.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/src/display/FloatingTab.ts +174 -0
- package/src/display/ProgressBar.ts +22 -2
- package/src/events.ts +2 -4
- package/src/flow/CanvasNode.ts +190 -32
- package/src/flow/Editor.ts +1040 -3
- package/src/flow/NodeEditor.ts +317 -19
- package/src/flow/NodeTypeSelector.ts +47 -3
- package/src/flow/StickyNote.ts +12 -3
- package/src/flow/actions/add_contact_groups.ts +2 -1
- package/src/flow/actions/add_contact_urn.ts +3 -1
- package/src/flow/actions/add_input_labels.ts +2 -1
- package/src/flow/actions/play_audio.ts +2 -1
- package/src/flow/actions/remove_contact_groups.ts +3 -1
- package/src/flow/actions/request_optin.ts +1 -0
- package/src/flow/actions/say_msg.ts +2 -1
- package/src/flow/actions/send_broadcast.ts +2 -1
- package/src/flow/actions/send_email.ts +3 -1
- package/src/flow/actions/send_msg.ts +134 -3
- package/src/flow/actions/set_contact_channel.ts +2 -1
- package/src/flow/actions/set_contact_field.ts +2 -1
- package/src/flow/actions/set_contact_language.ts +3 -1
- package/src/flow/actions/set_contact_name.ts +2 -1
- package/src/flow/actions/set_contact_status.ts +2 -1
- package/src/flow/actions/set_run_result.ts +2 -1
- package/src/flow/actions/start_session.ts +3 -1
- package/src/flow/config.ts +2 -12
- package/src/flow/nodes/shared.ts +70 -1
- package/src/flow/nodes/split_by_airtime.ts +20 -3
- package/src/flow/nodes/split_by_contact_field.ts +13 -3
- package/src/flow/nodes/split_by_expression.ts +13 -3
- package/src/flow/nodes/split_by_groups.ts +13 -3
- package/src/flow/nodes/split_by_intent.ts +3 -2
- package/src/flow/nodes/split_by_llm.ts +19 -2
- package/src/flow/nodes/split_by_llm_categorize.ts +19 -2
- package/src/flow/nodes/split_by_random.ts +12 -2
- package/src/flow/nodes/split_by_resthook.ts +13 -3
- package/src/flow/nodes/split_by_run_result.ts +13 -3
- package/src/flow/nodes/split_by_scheme.ts +13 -3
- package/src/flow/nodes/split_by_subflow.ts +12 -2
- package/src/flow/nodes/split_by_ticket.ts +12 -2
- package/src/flow/nodes/split_by_webhook.ts +12 -2
- package/src/flow/nodes/wait_for_digits.ts +3 -2
- package/src/flow/nodes/wait_for_menu.ts +3 -2
- package/src/flow/nodes/wait_for_response.ts +13 -3
- package/src/flow/types.ts +47 -0
- package/src/layout/FloatingWindow.ts +386 -0
- package/src/live/ContactChat.ts +4 -19
- package/src/locales/es.ts +18 -13
- package/src/locales/fr.ts +18 -13
- package/src/locales/locale-codes.ts +11 -2
- package/src/locales/pt.ts +18 -13
- package/src/store/AppState.ts +104 -0
- package/static/api/llms.json +18 -0
- package/temba-modules.ts +4 -0
- package/test/temba-floating-tab.test.ts +110 -0
- package/test/temba-floating-window.test.ts +477 -0
- package/test/temba-flow-editor-node.test.ts +144 -0
- package/test/temba-localization.test.ts +611 -0
- package/test/temba-node-type-selector.test.ts +203 -0
- package/test/utils.test.ts +20 -0
- package/test-assets/contacts/history.json +5 -6
- package/test-assets/select/llms.json +2 -2
- package/web-dev-server.config.mjs +47 -1
- package/web-test-runner.config.mjs +0 -1
- package/out-tsc/src/flow/nodes/wait_for_audio.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_image.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_image.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_location.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_location.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_video.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_video.js.map +0 -1
- package/src/flow/nodes/wait_for_audio.ts +0 -7
- package/src/flow/nodes/wait_for_image.ts +0 -7
- package/src/flow/nodes/wait_for_location.ts +0 -7
- 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
|
|
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);
|