@procore/ai-translations 0.8.0 → 0.9.0
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/README.md +39 -5
- package/dist/legacy/index.d.mts +28 -3
- package/dist/legacy/index.d.ts +28 -3
- package/dist/legacy/index.js +346 -74
- package/dist/legacy/index.mjs +346 -74
- package/dist/modern/index.d.mts +28 -3
- package/dist/modern/index.d.ts +28 -3
- package/dist/modern/index.js +339 -73
- package/dist/modern/index.mjs +339 -73
- package/package.json +1 -1
package/dist/legacy/index.js
CHANGED
|
@@ -678,6 +678,7 @@ var TRANSLATION_COMPLETE_EVENT_NAME = "ai-translation-completed";
|
|
|
678
678
|
var RERENDER_EVENT_NAME = "ai-translations-component-rerender";
|
|
679
679
|
var TRANSLATION_PROGRESS_EVENT_NAME = "ai-translations-progress";
|
|
680
680
|
var MODEL_DOWNLOAD_PROGRESS_EVENT_NAME = "ai-translations-model-download-progress";
|
|
681
|
+
var MODEL_CONSENT_REQUIRED_EVENT_NAME = "ai-translations-model-consent-required";
|
|
681
682
|
|
|
682
683
|
// src/utils/eventHandler.ts
|
|
683
684
|
var EventHandler = class {
|
|
@@ -758,24 +759,51 @@ var EventHandler = class {
|
|
|
758
759
|
* Not tool-scoped — a Chrome language model is shared across the entire
|
|
759
760
|
* browser session, so all Provider instances receive it.
|
|
760
761
|
*/
|
|
761
|
-
publishModelDownloadProgressEvent(loaded, total, progress) {
|
|
762
|
+
publishModelDownloadProgressEvent(loaded, total, progress, sourceLanguage, targetLanguage) {
|
|
762
763
|
this.aiTranslationEvents.publish(MODEL_DOWNLOAD_PROGRESS_EVENT_NAME, {
|
|
763
764
|
loaded,
|
|
764
765
|
total,
|
|
765
766
|
progress,
|
|
766
|
-
isComplete: loaded === total
|
|
767
|
+
isComplete: loaded === total,
|
|
768
|
+
sourceLanguage,
|
|
769
|
+
targetLanguage
|
|
767
770
|
});
|
|
768
771
|
}
|
|
769
772
|
subscribeToModelDownloadProgressEvent(callback) {
|
|
770
773
|
return this.aiTranslationEvents.subscribe(
|
|
771
774
|
MODEL_DOWNLOAD_PROGRESS_EVENT_NAME,
|
|
772
775
|
(detail) => {
|
|
773
|
-
var _a, _b, _c, _d;
|
|
776
|
+
var _a, _b, _c, _d, _e, _f;
|
|
774
777
|
return callback({
|
|
775
778
|
loaded: ((_a = detail.data) == null ? void 0 : _a.loaded) ?? 0,
|
|
776
779
|
total: ((_b = detail.data) == null ? void 0 : _b.total) ?? 0,
|
|
777
780
|
progress: ((_c = detail.data) == null ? void 0 : _c.progress) ?? 0,
|
|
778
|
-
isComplete: ((_d = detail.data) == null ? void 0 : _d.isComplete) ?? false
|
|
781
|
+
isComplete: ((_d = detail.data) == null ? void 0 : _d.isComplete) ?? false,
|
|
782
|
+
sourceLanguage: ((_e = detail.data) == null ? void 0 : _e.sourceLanguage) ?? "",
|
|
783
|
+
targetLanguage: ((_f = detail.data) == null ? void 0 : _f.targetLanguage) ?? ""
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Publishes a request for the user to consent to downloading a specific
|
|
790
|
+
* source -> target language model. Like the download-progress event this is
|
|
791
|
+
* NOT tool-scoped — a language model is shared across the whole session.
|
|
792
|
+
*/
|
|
793
|
+
publishModelConsentRequiredEvent(sourceLanguage, targetLanguage) {
|
|
794
|
+
this.aiTranslationEvents.publish(MODEL_CONSENT_REQUIRED_EVENT_NAME, {
|
|
795
|
+
sourceLanguage,
|
|
796
|
+
targetLanguage
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
subscribeToModelConsentRequiredEvent(callback) {
|
|
800
|
+
return this.aiTranslationEvents.subscribe(
|
|
801
|
+
MODEL_CONSENT_REQUIRED_EVENT_NAME,
|
|
802
|
+
(detail) => {
|
|
803
|
+
var _a, _b;
|
|
804
|
+
return callback({
|
|
805
|
+
sourceLanguage: ((_a = detail.data) == null ? void 0 : _a.sourceLanguage) ?? "",
|
|
806
|
+
targetLanguage: ((_b = detail.data) == null ? void 0 : _b.targetLanguage) ?? ""
|
|
779
807
|
});
|
|
780
808
|
}
|
|
781
809
|
);
|
|
@@ -785,6 +813,113 @@ var EventHandler = class {
|
|
|
785
813
|
}
|
|
786
814
|
};
|
|
787
815
|
|
|
816
|
+
// src/utils/modelDownloadGate.ts
|
|
817
|
+
var ModelDownloadGateImpl = class _ModelDownloadGateImpl {
|
|
818
|
+
constructor() {
|
|
819
|
+
__publicField(this, "eventHandler", null);
|
|
820
|
+
__publicField(this, "ready", /* @__PURE__ */ new Set());
|
|
821
|
+
__publicField(this, "pending", /* @__PURE__ */ new Map());
|
|
822
|
+
__publicField(this, "resolvers", /* @__PURE__ */ new Map());
|
|
823
|
+
__publicField(this, "rejecters", /* @__PURE__ */ new Map());
|
|
824
|
+
}
|
|
825
|
+
getEventHandler() {
|
|
826
|
+
if (!this.eventHandler) {
|
|
827
|
+
this.eventHandler = new EventHandler("__chrome_model__");
|
|
828
|
+
}
|
|
829
|
+
return this.eventHandler;
|
|
830
|
+
}
|
|
831
|
+
static key(sourceLanguage, targetLanguage) {
|
|
832
|
+
return `${sourceLanguage}->${targetLanguage}`;
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Resolves once the model for `sourceLanguage -> targetLanguage` is downloaded
|
|
836
|
+
* and ready. Pauses (stays pending) until the user confirms the download and
|
|
837
|
+
* it completes. Re-prompts are deduped per model.
|
|
838
|
+
*/
|
|
839
|
+
ensureModelReady(sourceLanguage, targetLanguage) {
|
|
840
|
+
const key = _ModelDownloadGateImpl.key(sourceLanguage, targetLanguage);
|
|
841
|
+
if (this.ready.has(key)) {
|
|
842
|
+
return Promise.resolve();
|
|
843
|
+
}
|
|
844
|
+
const existing = this.pending.get(key);
|
|
845
|
+
if (existing) {
|
|
846
|
+
return existing;
|
|
847
|
+
}
|
|
848
|
+
const promise = new Promise((resolve, reject) => {
|
|
849
|
+
this.resolvers.set(key, resolve);
|
|
850
|
+
this.rejecters.set(key, reject);
|
|
851
|
+
});
|
|
852
|
+
this.pending.set(key, promise);
|
|
853
|
+
this.getEventHandler().publishModelConsentRequiredEvent(
|
|
854
|
+
sourceLanguage,
|
|
855
|
+
targetLanguage
|
|
856
|
+
);
|
|
857
|
+
return promise;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Marks a model as ready and resolves any waiter. Resolved exclusively when
|
|
861
|
+
* `downloadModel` resolves (wired in the Provider). This guarantees Chrome's
|
|
862
|
+
* `Translator.create()` has returned and the translator instance is cached in
|
|
863
|
+
* `chromeTranslatorRegistry`, so the resumed translation reuses it instead of
|
|
864
|
+
* calling `Translator.create()` again outside the user gesture (which throws
|
|
865
|
+
* `NotAllowedError`). It is deliberately NOT resolved on the intermediate
|
|
866
|
+
* `MODEL_DOWNLOAD_PROGRESS` `isComplete` event, which fires before
|
|
867
|
+
* `Translator.create()` returns.
|
|
868
|
+
*/
|
|
869
|
+
markReady(sourceLanguage, targetLanguage) {
|
|
870
|
+
const key = _ModelDownloadGateImpl.key(sourceLanguage, targetLanguage);
|
|
871
|
+
this.ready.add(key);
|
|
872
|
+
const resolve = this.resolvers.get(key);
|
|
873
|
+
if (resolve) {
|
|
874
|
+
resolve();
|
|
875
|
+
}
|
|
876
|
+
this.clearPending(key);
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Marks a model download as failed and rejects any waiter so the paused
|
|
880
|
+
* translation run can move past this text instead of hanging forever. Called
|
|
881
|
+
* when `downloadModel` rejects (e.g. Chrome returns 404 for a pair it
|
|
882
|
+
* reported as downloadable). The pair is NOT cached as ready, so it can be
|
|
883
|
+
* re-attempted later.
|
|
884
|
+
*/
|
|
885
|
+
markFailed(sourceLanguage, targetLanguage, error) {
|
|
886
|
+
const key = _ModelDownloadGateImpl.key(sourceLanguage, targetLanguage);
|
|
887
|
+
const reject = this.rejecters.get(key);
|
|
888
|
+
if (reject) {
|
|
889
|
+
reject(
|
|
890
|
+
error instanceof Error ? error : new Error(`Model download failed for ${key}`)
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
this.clearPending(key);
|
|
894
|
+
}
|
|
895
|
+
isReady(sourceLanguage, targetLanguage) {
|
|
896
|
+
return this.ready.has(
|
|
897
|
+
_ModelDownloadGateImpl.key(sourceLanguage, targetLanguage)
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Clears all gate state. Intended for test teardown so cached "ready" models
|
|
902
|
+
* do not leak between cases.
|
|
903
|
+
*/
|
|
904
|
+
reset() {
|
|
905
|
+
this.ready.clear();
|
|
906
|
+
this.pending.clear();
|
|
907
|
+
this.resolvers.clear();
|
|
908
|
+
this.rejecters.clear();
|
|
909
|
+
}
|
|
910
|
+
clearPending(key) {
|
|
911
|
+
this.resolvers.delete(key);
|
|
912
|
+
this.rejecters.delete(key);
|
|
913
|
+
this.pending.delete(key);
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
var modelDownloadGate = new ModelDownloadGateImpl();
|
|
917
|
+
|
|
918
|
+
// src/utils/language.ts
|
|
919
|
+
function normalizeLanguage(language) {
|
|
920
|
+
return language.split("-")[0] ?? language;
|
|
921
|
+
}
|
|
922
|
+
|
|
788
923
|
// src/utils/renderVersionManager.ts
|
|
789
924
|
var RenderVersionManager = class {
|
|
790
925
|
constructor() {
|
|
@@ -843,27 +978,30 @@ var TranslationManager = class {
|
|
|
843
978
|
this.translationProgress.total = queueManager.queueSize(
|
|
844
979
|
this.currentTranslatorStrategy
|
|
845
980
|
);
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
981
|
+
try {
|
|
982
|
+
do {
|
|
983
|
+
for (const batch of queueManager.generateBatches(
|
|
984
|
+
this.translator.getConfig()
|
|
985
|
+
)) {
|
|
986
|
+
batch.forEach((entry) => this.mfeToBeNotified.add(entry.tool));
|
|
987
|
+
const result = await this.translator.processTranslations(
|
|
988
|
+
batch.map(
|
|
989
|
+
(entry) => this.convertTranslationQueueEntryToTranslationRequest(entry)
|
|
990
|
+
)
|
|
991
|
+
);
|
|
992
|
+
this.setTranslationProgress(batch.length);
|
|
993
|
+
await this.updateDatabaseWithTranslations(result);
|
|
994
|
+
await this.notifyTranslationCompleted();
|
|
995
|
+
}
|
|
996
|
+
} while (queueManager.queueSize(this.currentTranslatorStrategy) > 0);
|
|
997
|
+
this.resetTranslationProgress();
|
|
998
|
+
this.publishTranslationProgress();
|
|
999
|
+
} finally {
|
|
1000
|
+
if (this.currentTranslatorStrategy === "frontend_translations") {
|
|
1001
|
+
globalThis._FRONTEND_AI_TRANSLATION_IN_PROGRESS_ = false;
|
|
1002
|
+
} else {
|
|
1003
|
+
globalThis._BACKEND_AI_TRANSLATION_IN_PROGRESS_ = false;
|
|
859
1004
|
}
|
|
860
|
-
} while (queueManager.queueSize(this.currentTranslatorStrategy) > 0);
|
|
861
|
-
this.resetTranslationProgress();
|
|
862
|
-
this.publishTranslationProgress();
|
|
863
|
-
if (this.currentTranslatorStrategy === "frontend_translations") {
|
|
864
|
-
globalThis._FRONTEND_AI_TRANSLATION_IN_PROGRESS_ = false;
|
|
865
|
-
} else {
|
|
866
|
-
globalThis._BACKEND_AI_TRANSLATION_IN_PROGRESS_ = false;
|
|
867
1005
|
}
|
|
868
1006
|
}
|
|
869
1007
|
async updateDatabaseWithTranslations(translationResult) {
|
|
@@ -1033,6 +1171,16 @@ var Client = class {
|
|
|
1033
1171
|
return this.createTranslationErrorResponse(requests, errorMessage);
|
|
1034
1172
|
}
|
|
1035
1173
|
}
|
|
1174
|
+
/**
|
|
1175
|
+
* No-op: backend translation does not manage on-device models, so there is
|
|
1176
|
+
* nothing to download client-side. The parameters are kept to match the
|
|
1177
|
+
* {@link TranslatorClientAdapter.downloadModel} signature and make the no-op
|
|
1178
|
+
* intent explicit to callers.
|
|
1179
|
+
*/
|
|
1180
|
+
async downloadModel(sourceLanguage, targetLanguage) {
|
|
1181
|
+
void sourceLanguage;
|
|
1182
|
+
void targetLanguage;
|
|
1183
|
+
}
|
|
1036
1184
|
createTranslationErrorResponse(requests, errorMessage) {
|
|
1037
1185
|
return {
|
|
1038
1186
|
responseBody: requests.map((req) => ({
|
|
@@ -1152,6 +1300,7 @@ var _ChromeTranslator = class _ChromeTranslator {
|
|
|
1152
1300
|
}
|
|
1153
1301
|
}
|
|
1154
1302
|
static async init(sourceLanguage, targetLanguage) {
|
|
1303
|
+
this.ensureRegistry();
|
|
1155
1304
|
if (globalThis.chromeTranslatorRegistry.has(
|
|
1156
1305
|
this.generateKey(sourceLanguage, targetLanguage)
|
|
1157
1306
|
)) {
|
|
@@ -1166,7 +1315,30 @@ var _ChromeTranslator = class _ChromeTranslator {
|
|
|
1166
1315
|
new _ChromeTranslator(translator)
|
|
1167
1316
|
);
|
|
1168
1317
|
}
|
|
1169
|
-
|
|
1318
|
+
/**
|
|
1319
|
+
* Downloads the on-device model for a source -> target language pair. Must be
|
|
1320
|
+
* invoked from a user gesture (e.g. a click handler), since Chrome requires an
|
|
1321
|
+
* active gesture to start a model download.
|
|
1322
|
+
*/
|
|
1323
|
+
static async downloadModel(sourceLanguage, targetLanguage) {
|
|
1324
|
+
await this.init(
|
|
1325
|
+
normalizeLanguage(sourceLanguage),
|
|
1326
|
+
normalizeLanguage(targetLanguage)
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Creates a Chrome translator instance for the pair.
|
|
1331
|
+
*
|
|
1332
|
+
* @param publishProgress - When true, `downloadprogress` events are forwarded
|
|
1333
|
+
* as `MODEL_DOWNLOAD_PROGRESS` events (driving the download-progress popup).
|
|
1334
|
+
* This should only be true for an explicit, consent-driven download
|
|
1335
|
+
* (`init`/`downloadModel`). When creating an already-downloaded
|
|
1336
|
+
* (`available`) model in the translate loop, Chrome still emits
|
|
1337
|
+
* `downloadprogress` while loading the pack from disk into memory; forwarding
|
|
1338
|
+
* those would wrongly show a "downloading" popup for a model that is already
|
|
1339
|
+
* downloaded, so the translate-loop path passes `false`.
|
|
1340
|
+
*/
|
|
1341
|
+
static async createTranslator(source, target, publishProgress = true) {
|
|
1170
1342
|
const key = this.generateKey(source, target);
|
|
1171
1343
|
let resolveReady;
|
|
1172
1344
|
const readyPromise = new Promise((resolve) => {
|
|
@@ -1183,11 +1355,15 @@ var _ChromeTranslator = class _ChromeTranslator {
|
|
|
1183
1355
|
const loaded = e.loaded ?? 0;
|
|
1184
1356
|
const total = e.total ?? 0;
|
|
1185
1357
|
const progress = total > 0 ? Math.round(loaded / total * 100) : 0;
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1358
|
+
if (publishProgress) {
|
|
1359
|
+
getModelDownloadEventHandler().publishModelDownloadProgressEvent(
|
|
1360
|
+
loaded,
|
|
1361
|
+
total,
|
|
1362
|
+
progress,
|
|
1363
|
+
source,
|
|
1364
|
+
target
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1191
1367
|
if (total > 0 && loaded >= total) {
|
|
1192
1368
|
resolveReady();
|
|
1193
1369
|
}
|
|
@@ -1205,26 +1381,27 @@ var _ChromeTranslator = class _ChromeTranslator {
|
|
|
1205
1381
|
static async translate(text, targetLanguage) {
|
|
1206
1382
|
var _a;
|
|
1207
1383
|
this.ensureRegistry();
|
|
1384
|
+
const normalizedTarget = normalizeLanguage(targetLanguage);
|
|
1385
|
+
let sourceLanguage = normalizedTarget;
|
|
1208
1386
|
try {
|
|
1209
1387
|
const detector = await ChromeLanguageDetector.getInstance();
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
if (!this.isSupportedLanguage(sourceLanguage) || !this.isSupportedLanguage(targetLanguage)) {
|
|
1388
|
+
sourceLanguage = normalizeLanguage(await detector.detectLanguage(text));
|
|
1389
|
+
if (!this.isSupportedLanguage(sourceLanguage) || !this.isSupportedLanguage(normalizedTarget)) {
|
|
1213
1390
|
return {
|
|
1214
1391
|
translation: text,
|
|
1215
1392
|
sourceLanguage,
|
|
1216
1393
|
success: false,
|
|
1217
1394
|
retryable: false,
|
|
1218
|
-
errorMessage: `Unsupported language pair: ${sourceLanguage} \u2192 ${
|
|
1395
|
+
errorMessage: `Unsupported language pair: ${sourceLanguage} \u2192 ${normalizedTarget}`
|
|
1219
1396
|
};
|
|
1220
1397
|
}
|
|
1221
|
-
const
|
|
1222
|
-
if (!
|
|
1223
|
-
const
|
|
1398
|
+
const key = this.generateKey(sourceLanguage, normalizedTarget);
|
|
1399
|
+
if (!globalThis.chromeTranslatorRegistry.has(key)) {
|
|
1400
|
+
const availability = await Translator.availability({
|
|
1224
1401
|
sourceLanguage,
|
|
1225
|
-
targetLanguage
|
|
1402
|
+
targetLanguage: normalizedTarget
|
|
1226
1403
|
});
|
|
1227
|
-
if (
|
|
1404
|
+
if (availability === "unavailable") {
|
|
1228
1405
|
return {
|
|
1229
1406
|
translation: text,
|
|
1230
1407
|
sourceLanguage,
|
|
@@ -1233,47 +1410,95 @@ var _ChromeTranslator = class _ChromeTranslator {
|
|
|
1233
1410
|
errorMessage: "Translation not supported"
|
|
1234
1411
|
};
|
|
1235
1412
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1413
|
+
if (availability !== "available") {
|
|
1414
|
+
const isUserActivationActive = typeof navigator !== "undefined" && (((_a = navigator.userActivation) == null ? void 0 : _a.isActive) ?? false);
|
|
1415
|
+
if (isUserActivationActive) {
|
|
1416
|
+
await _ChromeTranslator.downloadModel(
|
|
1417
|
+
sourceLanguage,
|
|
1418
|
+
normalizedTarget
|
|
1419
|
+
);
|
|
1420
|
+
modelDownloadGate.markReady(sourceLanguage, normalizedTarget);
|
|
1421
|
+
} else {
|
|
1422
|
+
await modelDownloadGate.ensureModelReady(
|
|
1423
|
+
sourceLanguage,
|
|
1424
|
+
normalizedTarget
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1245
1427
|
}
|
|
1246
1428
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
this.translatorReadyPromises.delete(key);
|
|
1250
|
-
const translation = await ((_a = instance.translator) == null ? void 0 : _a.translate(text));
|
|
1251
|
-
if (!translation) {
|
|
1252
|
-
return {
|
|
1253
|
-
translation: text,
|
|
1254
|
-
sourceLanguage,
|
|
1255
|
-
success: false,
|
|
1256
|
-
errorMessage: "Translation failed",
|
|
1257
|
-
retryable: false
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
return {
|
|
1261
|
-
translation,
|
|
1429
|
+
return await this.createInstanceAndTranslate(
|
|
1430
|
+
text,
|
|
1262
1431
|
sourceLanguage,
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
};
|
|
1432
|
+
normalizedTarget
|
|
1433
|
+
);
|
|
1266
1434
|
} catch (error) {
|
|
1435
|
+
const isGestureRequired = error instanceof DOMException && error.name === "NotAllowedError";
|
|
1436
|
+
if (isGestureRequired) {
|
|
1437
|
+
try {
|
|
1438
|
+
await modelDownloadGate.ensureModelReady(
|
|
1439
|
+
sourceLanguage,
|
|
1440
|
+
normalizedTarget
|
|
1441
|
+
);
|
|
1442
|
+
return await this.createInstanceAndTranslate(
|
|
1443
|
+
text,
|
|
1444
|
+
sourceLanguage,
|
|
1445
|
+
normalizedTarget
|
|
1446
|
+
);
|
|
1447
|
+
} catch (retryError) {
|
|
1448
|
+
void retryError;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1267
1451
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1268
1452
|
return {
|
|
1269
1453
|
translation: text,
|
|
1270
|
-
sourceLanguage
|
|
1454
|
+
sourceLanguage,
|
|
1271
1455
|
success: false,
|
|
1272
1456
|
errorMessage: message,
|
|
1273
|
-
retryable:
|
|
1457
|
+
retryable: false,
|
|
1458
|
+
requiresGesture: isGestureRequired
|
|
1274
1459
|
};
|
|
1275
1460
|
}
|
|
1276
1461
|
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Creates the translator instance for the pair if it is not already cached,
|
|
1464
|
+
* waits for the model to be ready, and translates the text. The model is
|
|
1465
|
+
* expected to already be downloaded (or downloadable without a gesture) by
|
|
1466
|
+
* the time this is called.
|
|
1467
|
+
*/
|
|
1468
|
+
static async createInstanceAndTranslate(text, sourceLanguage, targetLanguage) {
|
|
1469
|
+
var _a;
|
|
1470
|
+
const key = this.generateKey(sourceLanguage, targetLanguage);
|
|
1471
|
+
if (!globalThis.chromeTranslatorRegistry.has(key)) {
|
|
1472
|
+
const chromeTranslator = await this.createTranslator(
|
|
1473
|
+
sourceLanguage,
|
|
1474
|
+
targetLanguage,
|
|
1475
|
+
false
|
|
1476
|
+
);
|
|
1477
|
+
globalThis.chromeTranslatorRegistry.set(
|
|
1478
|
+
key,
|
|
1479
|
+
new _ChromeTranslator(chromeTranslator)
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
const instance = globalThis.chromeTranslatorRegistry.get(key);
|
|
1483
|
+
await this.waitForReady(key);
|
|
1484
|
+
this.translatorReadyPromises.delete(key);
|
|
1485
|
+
const translation = await ((_a = instance.translator) == null ? void 0 : _a.translate(text));
|
|
1486
|
+
if (!translation) {
|
|
1487
|
+
return {
|
|
1488
|
+
translation: text,
|
|
1489
|
+
sourceLanguage,
|
|
1490
|
+
success: false,
|
|
1491
|
+
errorMessage: "Translation failed",
|
|
1492
|
+
retryable: false
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
return {
|
|
1496
|
+
translation,
|
|
1497
|
+
sourceLanguage,
|
|
1498
|
+
success: true,
|
|
1499
|
+
retryable: true
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1277
1502
|
static generateKey(sourceLanguage, targetLanguage) {
|
|
1278
1503
|
return `${sourceLanguage.toUpperCase()}-${targetLanguage.toUpperCase()}`;
|
|
1279
1504
|
}
|
|
@@ -1317,7 +1542,8 @@ var Client2 = class {
|
|
|
1317
1542
|
translation: result.translation,
|
|
1318
1543
|
sourceLanguage: result.sourceLanguage,
|
|
1319
1544
|
isTranslated: result.success,
|
|
1320
|
-
retryable: result.retryable
|
|
1545
|
+
retryable: result.retryable,
|
|
1546
|
+
requiresGesture: result.requiresGesture ?? false
|
|
1321
1547
|
}
|
|
1322
1548
|
],
|
|
1323
1549
|
targetLanguage: request.targetLanguage,
|
|
@@ -1332,6 +1558,9 @@ var Client2 = class {
|
|
|
1332
1558
|
errorMessage
|
|
1333
1559
|
};
|
|
1334
1560
|
}
|
|
1561
|
+
async downloadModel(sourceLanguage, targetLanguage) {
|
|
1562
|
+
await ChromeTranslator.downloadModel(sourceLanguage, targetLanguage);
|
|
1563
|
+
}
|
|
1335
1564
|
getConfig() {
|
|
1336
1565
|
return this.config;
|
|
1337
1566
|
}
|
|
@@ -1372,6 +1601,10 @@ var Client3 = class {
|
|
|
1372
1601
|
const translator = this.getFrontendTranslator();
|
|
1373
1602
|
return await translator.translateBatch(...args);
|
|
1374
1603
|
}
|
|
1604
|
+
async downloadModel(sourceLanguage, targetLanguage) {
|
|
1605
|
+
const translator = this.getFrontendTranslator();
|
|
1606
|
+
await translator.downloadModel(sourceLanguage, targetLanguage);
|
|
1607
|
+
}
|
|
1375
1608
|
};
|
|
1376
1609
|
|
|
1377
1610
|
// src/translators/translator.ts
|
|
@@ -1386,6 +1619,17 @@ var Translator2 = class {
|
|
|
1386
1619
|
const translatorClient = this.getTranslatorClient();
|
|
1387
1620
|
return await translatorClient.translateBatch(translationRequests);
|
|
1388
1621
|
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Downloads the on-device model for a source -> target language pair via the
|
|
1624
|
+
* active translator client. Must be invoked from a user gesture for clients
|
|
1625
|
+
* (e.g. Chrome) that require one to start a model download.
|
|
1626
|
+
*/
|
|
1627
|
+
async downloadModel(sourceLanguage, targetLanguage) {
|
|
1628
|
+
await this.getTranslatorClient().downloadModel(
|
|
1629
|
+
sourceLanguage,
|
|
1630
|
+
targetLanguage
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1389
1633
|
setTranslatorClient(config2) {
|
|
1390
1634
|
if (config2.getToolConfig().strategy === "frontend_translations") {
|
|
1391
1635
|
this.translatorClient = new Client3(config2);
|
|
@@ -1504,6 +1748,9 @@ function AITranslationInnerProvider(props) {
|
|
|
1504
1748
|
} = props;
|
|
1505
1749
|
const [translationProgress, setTranslationProgress] = (0, import_react.useState)(null);
|
|
1506
1750
|
const [modelDownloadProgress, setModelDownloadProgress] = (0, import_react.useState)(null);
|
|
1751
|
+
const [modelConsent, setModelConsent] = (0, import_react.useState)(
|
|
1752
|
+
null
|
|
1753
|
+
);
|
|
1507
1754
|
const [config2, setConfig] = (0, import_react.useState)(new Config());
|
|
1508
1755
|
const translator = (0, import_react.useRef)(new Translator2(config2));
|
|
1509
1756
|
const eventHandler = (0, import_react.useRef)(new EventHandler(tool));
|
|
@@ -1555,6 +1802,14 @@ function AITranslationInnerProvider(props) {
|
|
|
1555
1802
|
);
|
|
1556
1803
|
return () => unsubscribe();
|
|
1557
1804
|
}, []);
|
|
1805
|
+
(0, import_react.useEffect)(() => {
|
|
1806
|
+
const unsubscribe = eventHandler.current.subscribeToModelConsentRequiredEvent(
|
|
1807
|
+
(request) => {
|
|
1808
|
+
setModelConsent(request);
|
|
1809
|
+
}
|
|
1810
|
+
);
|
|
1811
|
+
return () => unsubscribe();
|
|
1812
|
+
}, []);
|
|
1558
1813
|
(0, import_react.useEffect)(() => {
|
|
1559
1814
|
if (!isFetched || !remoteConfig) return;
|
|
1560
1815
|
const toolConfig = remoteConfig[tool] ?? remoteConfig;
|
|
@@ -1571,9 +1826,23 @@ function AITranslationInnerProvider(props) {
|
|
|
1571
1826
|
config2.setCompanyId(companyId);
|
|
1572
1827
|
config2.setProjectId(projectId);
|
|
1573
1828
|
const ait = (0, import_react.useCallback)(
|
|
1574
|
-
async (text) =>
|
|
1829
|
+
async (text) => {
|
|
1830
|
+
if (!enableAIT) {
|
|
1831
|
+
return text;
|
|
1832
|
+
}
|
|
1833
|
+
return aitFunction(text, translationRegistry.current, config2, tool);
|
|
1834
|
+
},
|
|
1575
1835
|
[tool, config2, enableAIT]
|
|
1576
1836
|
);
|
|
1837
|
+
const confirmModelDownload = (0, import_react.useCallback)(() => {
|
|
1838
|
+
if (!modelConsent) return;
|
|
1839
|
+
if (config2.getToolConfig().strategy !== "frontend_translations") return;
|
|
1840
|
+
const { sourceLanguage, targetLanguage } = modelConsent;
|
|
1841
|
+
setModelConsent(null);
|
|
1842
|
+
translator.current.downloadModel(sourceLanguage, targetLanguage).then(() => modelDownloadGate.markReady(sourceLanguage, targetLanguage)).catch(
|
|
1843
|
+
(error) => modelDownloadGate.markFailed(sourceLanguage, targetLanguage, error)
|
|
1844
|
+
);
|
|
1845
|
+
}, [modelConsent, config2]);
|
|
1577
1846
|
const contextValue = {
|
|
1578
1847
|
ait,
|
|
1579
1848
|
locale,
|
|
@@ -1582,6 +1851,8 @@ function AITranslationInnerProvider(props) {
|
|
|
1582
1851
|
renderVersion: renderVersionManager.getVersion(),
|
|
1583
1852
|
translationProgress,
|
|
1584
1853
|
modelDownloadProgress,
|
|
1854
|
+
modelConsent,
|
|
1855
|
+
confirmModelDownload,
|
|
1585
1856
|
tool,
|
|
1586
1857
|
onTrackAnalyticsEvent,
|
|
1587
1858
|
page,
|
|
@@ -1616,8 +1887,8 @@ var BUTTON_TYPE = {
|
|
|
1616
1887
|
var ACTION = {
|
|
1617
1888
|
CLICKED: "clicked"
|
|
1618
1889
|
};
|
|
1619
|
-
function buildObject(
|
|
1620
|
-
return `${
|
|
1890
|
+
function buildObject(buttonType) {
|
|
1891
|
+
return `${buttonType}_button`;
|
|
1621
1892
|
}
|
|
1622
1893
|
function buildEventKey(parts) {
|
|
1623
1894
|
const { scope, tool, object, action } = parts;
|
|
@@ -1634,7 +1905,7 @@ function buildAnalyticEvent(params) {
|
|
|
1634
1905
|
additionalProperties
|
|
1635
1906
|
} = params;
|
|
1636
1907
|
const resolvedAction = action ?? ACTION.CLICKED;
|
|
1637
|
-
const object = buildObject(
|
|
1908
|
+
const object = buildObject(buttonType);
|
|
1638
1909
|
const key = buildEventKey({
|
|
1639
1910
|
scope,
|
|
1640
1911
|
tool,
|
|
@@ -1643,6 +1914,7 @@ function buildAnalyticEvent(params) {
|
|
|
1643
1914
|
});
|
|
1644
1915
|
const properties = {
|
|
1645
1916
|
...baseProperties,
|
|
1917
|
+
[`${buttonType}_button_location`]: pageContext,
|
|
1646
1918
|
...additionalProperties
|
|
1647
1919
|
};
|
|
1648
1920
|
return { key, properties };
|
|
@@ -1922,7 +2194,7 @@ var CustomizableAITranslateText = ({
|
|
|
1922
2194
|
|
|
1923
2195
|
// src/utils/featureFlag.ts
|
|
1924
2196
|
var import_web_sdk_mfe_utils = require("@procore/web-sdk-mfe-utils");
|
|
1925
|
-
var AI_TRANSLATION_FEATURE_FLAG_KEY = "
|
|
2197
|
+
var AI_TRANSLATION_FEATURE_FLAG_KEY = "ai-translation";
|
|
1926
2198
|
var getAITranslationLDId = (domain) => {
|
|
1927
2199
|
const { environment, zone } = (0, import_web_sdk_mfe_utils.getProcoreZone)(domain);
|
|
1928
2200
|
if ((0, import_web_sdk_mfe_utils.isFederalZone)(zone)) {
|