@rimori/client 2.5.32 → 2.5.33-next.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.
Files changed (28) hide show
  1. package/dist/cli/scripts/init/dev-registration.js +118 -136
  2. package/dist/cli/scripts/init/main.js +116 -127
  3. package/dist/cli/scripts/init/package-setup.js +15 -2
  4. package/dist/cli/scripts/release/detect-translation-languages.js +24 -35
  5. package/dist/cli/scripts/release/release-config-upload.js +87 -100
  6. package/dist/cli/scripts/release/release-db-update.js +70 -81
  7. package/dist/cli/scripts/release/release-file-upload.js +75 -91
  8. package/dist/cli/scripts/release/release-prompts-upload.js +60 -72
  9. package/dist/cli/scripts/release/release.js +20 -31
  10. package/dist/controller/AccomplishmentController.js +12 -12
  11. package/dist/controller/AudioController.js +15 -33
  12. package/dist/controller/TranslationController.js +108 -118
  13. package/dist/fromRimori/EventBus.js +20 -31
  14. package/dist/plugin/CommunicationHandler.js +73 -81
  15. package/dist/plugin/Logger.js +71 -83
  16. package/dist/plugin/RimoriClient.js +31 -31
  17. package/dist/plugin/StandaloneClient.js +81 -98
  18. package/dist/plugin/TTS/ChunkedAudioPlayer.js +31 -41
  19. package/dist/plugin/TTS/MessageSender.js +28 -37
  20. package/dist/plugin/module/AIModule.js +215 -237
  21. package/dist/plugin/module/DbModule.js +22 -31
  22. package/dist/plugin/module/EventModule.js +23 -32
  23. package/dist/plugin/module/ExerciseModule.js +42 -56
  24. package/dist/plugin/module/PluginModule.js +97 -106
  25. package/dist/plugin/module/SharedContentController.js +170 -207
  26. package/dist/plugin/module/StorageModule.js +18 -29
  27. package/dist/worker/WorkerSetup.js +23 -34
  28. package/package.json +1 -1
@@ -1,12 +1,3 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  import fs from 'fs';
11
2
  import path from 'path';
12
3
  import ts from 'typescript';
@@ -16,73 +7,70 @@ import ts from 'typescript';
16
7
  * @param config - Configuration object
17
8
  * @param release_id - The release ID
18
9
  */
19
- export default function promptsUpload(config, release_id) {
20
- return __awaiter(this, void 0, void 0, function* () {
21
- var _a, _b;
22
- const promptsConfigPath = path.resolve('./rimori/prompts.config.ts');
23
- // Check if prompts config file exists — optional, skip if not present
10
+ export default async function promptsUpload(config, release_id) {
11
+ const promptsConfigPath = path.resolve('./rimori/prompts.config.ts');
12
+ // Check if prompts config file exists — optional, skip if not present
13
+ try {
14
+ await fs.promises.access(promptsConfigPath);
15
+ }
16
+ catch (e) {
17
+ return; // No prompts.config.ts — silently skip
18
+ }
19
+ try {
20
+ // Use TypeScript compiler to transpile and load
21
+ const promptsContent = await fs.promises.readFile(promptsConfigPath, 'utf8');
22
+ // Transpile TypeScript to JavaScript
23
+ const result = ts.transpile(promptsContent, {
24
+ target: ts.ScriptTarget.ES2020,
25
+ module: ts.ModuleKind.ES2020,
26
+ });
27
+ // Create a temporary file to import the transpiled code
28
+ const tempFile = path.join(process.cwd(), 'temp_prompts_config.js');
29
+ await fs.promises.writeFile(tempFile, result);
30
+ let prompts;
24
31
  try {
25
- yield fs.promises.access(promptsConfigPath);
32
+ const promptsModule = await import(`file://${tempFile}`);
33
+ // Collect all named exports as individual prompt definitions
34
+ prompts = Object.values(promptsModule);
35
+ await fs.promises.unlink(tempFile);
26
36
  }
27
- catch (e) {
28
- return; // No prompts.config.ts — silently skip
29
- }
30
- try {
31
- // Use TypeScript compiler to transpile and load
32
- const promptsContent = yield fs.promises.readFile(promptsConfigPath, 'utf8');
33
- // Transpile TypeScript to JavaScript
34
- const result = ts.transpile(promptsContent, {
35
- target: ts.ScriptTarget.ES2020,
36
- module: ts.ModuleKind.ES2020,
37
- });
38
- // Create a temporary file to import the transpiled code
39
- const tempFile = path.join(process.cwd(), 'temp_prompts_config.js');
40
- yield fs.promises.writeFile(tempFile, result);
41
- let prompts;
37
+ catch (error) {
42
38
  try {
43
- const promptsModule = yield import(`file://${tempFile}`);
44
- // Collect all named exports as individual prompt definitions
45
- prompts = Object.values(promptsModule);
46
- yield fs.promises.unlink(tempFile);
47
- }
48
- catch (error) {
49
- try {
50
- yield fs.promises.unlink(tempFile);
51
- }
52
- catch (e) { }
53
- throw error;
54
- }
55
- if (!Array.isArray(prompts) || prompts.length === 0) {
56
- console.warn('⚠️ prompts.config.ts has no exports. Skipping.');
57
- return;
58
- }
59
- console.log(`📝 Sending ${prompts.length} prompt definitions...`);
60
- const requestBody = {
61
- prompts,
62
- version: config.version,
63
- release_channel: config.release_channel,
64
- plugin_id: config.plugin_id,
65
- };
66
- const response = yield fetch(`${config.domain}/release/${release_id}/prompts`, {
67
- method: 'POST',
68
- headers: {
69
- 'Content-Type': 'application/json',
70
- Authorization: `Bearer ${config.token}`,
71
- },
72
- body: JSON.stringify(requestBody),
73
- });
74
- if (response.ok) {
75
- const data = yield response.json();
76
- console.log(`✅ Prompts uploaded: ${(_b = (_a = data.prompt_names) === null || _a === void 0 ? void 0 : _a.join(', ')) !== null && _b !== void 0 ? _b : 'ok'}`);
39
+ await fs.promises.unlink(tempFile);
77
40
  }
78
- else {
79
- const text = yield response.text().catch(() => 'unknown error');
80
- throw new Error(`Failed to upload prompts: ${text}`);
81
- }
82
- }
83
- catch (error) {
84
- console.error('❌ Error uploading prompts:', error.message);
41
+ catch (e) { }
85
42
  throw error;
86
43
  }
87
- });
44
+ if (!Array.isArray(prompts) || prompts.length === 0) {
45
+ console.warn('⚠️ prompts.config.ts has no exports. Skipping.');
46
+ return;
47
+ }
48
+ console.log(`📝 Sending ${prompts.length} prompt definitions...`);
49
+ const requestBody = {
50
+ prompts,
51
+ version: config.version,
52
+ release_channel: config.release_channel,
53
+ plugin_id: config.plugin_id,
54
+ };
55
+ const response = await fetch(`${config.domain}/release/${release_id}/prompts`, {
56
+ method: 'POST',
57
+ headers: {
58
+ 'Content-Type': 'application/json',
59
+ Authorization: `Bearer ${config.token}`,
60
+ },
61
+ body: JSON.stringify(requestBody),
62
+ });
63
+ if (response.ok) {
64
+ const data = await response.json();
65
+ console.log(`✅ Prompts uploaded: ${data.prompt_names?.join(', ') ?? 'ok'}`);
66
+ }
67
+ else {
68
+ const text = await response.text().catch(() => 'unknown error');
69
+ throw new Error(`Failed to upload prompts: ${text}`);
70
+ }
71
+ }
72
+ catch (error) {
73
+ console.error('❌ Error uploading prompts:', error.message);
74
+ throw error;
75
+ }
88
76
  }
@@ -10,15 +10,6 @@
10
10
  * Make sure to install dependencies:
11
11
  * npm install node-fetch form-data ts-node typescript
12
12
  */
13
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
- return new (P || (P = Promise))(function (resolve, reject) {
16
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
- step((generator = generator.apply(thisArg, _arguments || [])).next());
20
- });
21
- };
22
13
  import 'dotenv/config';
23
14
  import fs from 'fs';
24
15
  import path from 'path';
@@ -54,27 +45,25 @@ const config = {
54
45
  /**
55
46
  * Main release process
56
47
  */
57
- function releaseProcess() {
58
- return __awaiter(this, void 0, void 0, function* () {
59
- try {
60
- console.log(`🚀 Releasing ${config.plugin_id} to ${config.release_channel}...`);
61
- console.log(`📡 Deploying to: ${config.domain}`);
62
- // First send the configuration
63
- const release_id = yield sendConfiguration(config);
64
- // Upload prompts (if prompts.config.ts exists)
65
- yield promptsUpload(config, release_id);
66
- yield dbUpdate(config, release_id);
67
- // Then upload the files
68
- yield uploadDirectory(config, release_id);
69
- // Then release the plugin
70
- yield releasePlugin(config, release_id);
71
- // Inform user about translation processing
72
- console.log('🌐 Hint: The plugin is released but it might take some time until all translations are being processed.');
73
- }
74
- catch (error) {
75
- console.log('❌ Error:', error.message);
76
- process.exit(1);
77
- }
78
- });
48
+ async function releaseProcess() {
49
+ try {
50
+ console.log(`🚀 Releasing ${config.plugin_id} to ${config.release_channel}...`);
51
+ console.log(`📡 Deploying to: ${config.domain}`);
52
+ // First send the configuration
53
+ const release_id = await sendConfiguration(config);
54
+ // Upload prompts (if prompts.config.ts exists)
55
+ await promptsUpload(config, release_id);
56
+ await dbUpdate(config, release_id);
57
+ // Then upload the files
58
+ await uploadDirectory(config, release_id);
59
+ // Then release the plugin
60
+ await releasePlugin(config, release_id);
61
+ // Inform user about translation processing
62
+ console.log('🌐 Hint: The plugin is released but it might take some time until all translations are being processed.');
63
+ }
64
+ catch (error) {
65
+ console.log('❌ Error:', error.message);
66
+ process.exit(1);
67
+ }
79
68
  }
80
69
  releaseProcess();
@@ -1,12 +1,17 @@
1
1
  import { EventBus } from '../fromRimori/EventBus';
2
2
  export const skillCategories = ['reading', 'listening', 'speaking', 'writing', 'learning', 'community'];
3
3
  export class AccomplishmentController {
4
+ pluginId;
5
+ eventBus;
4
6
  constructor(pluginId, eventBus) {
5
7
  this.pluginId = pluginId;
6
- this.eventBus = eventBus !== null && eventBus !== void 0 ? eventBus : EventBus;
8
+ this.eventBus = eventBus ?? EventBus;
7
9
  }
8
10
  emitAccomplishment(payload) {
9
- const accomplishmentPayload = Object.assign(Object.assign({}, payload), { type: 'durationMinutes' in payload ? 'macro' : 'micro' });
11
+ const accomplishmentPayload = {
12
+ ...payload,
13
+ type: 'durationMinutes' in payload ? 'macro' : 'micro',
14
+ };
10
15
  if (!this.validateAccomplishment(accomplishmentPayload)) {
11
16
  return;
12
17
  }
@@ -40,22 +45,17 @@ export class AccomplishmentController {
40
45
  if (payload.type === 'macro' && (payload.errorRatio < 0 || payload.errorRatio > 1)) {
41
46
  throw new Error('The error ratio must be between 0 and 1');
42
47
  }
43
- //regex check meta data key
44
- if (payload.meta) {
45
- payload.meta.forEach((meta) => {
46
- if (!/^[a-z_]+$/.test(meta.key)) {
47
- throw new Error('Invalid meta data key ' + meta.key + ', only lowercase letters and underscores are allowed');
48
- }
49
- });
50
- }
51
48
  return true;
52
49
  }
53
50
  sanitizeAccomplishment(payload) {
54
- var _a;
55
51
  payload.description = payload.description.replace(/[^\x20-\x7E]/g, '');
56
- (_a = payload.meta) === null || _a === void 0 ? void 0 : _a.forEach((meta) => {
52
+ payload.meta?.forEach((meta) => {
57
53
  meta.description = meta.description.replace(/[^\x20-\x7E]/g, '');
58
54
  });
55
+ //convert meta keys to snakecase
56
+ payload.meta?.forEach((meta) => {
57
+ meta.key = meta.key.replace(/([A-Z])/g, '_$1').toLowerCase();
58
+ });
59
59
  return payload;
60
60
  }
61
61
  getDecoupledTopic(topic) {
@@ -1,12 +1,3 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  import { EventBus } from '../fromRimori/EventBus';
11
2
  /**
12
3
  * AudioController is a class that provides methods to record audio. It is a wrapper around the Capacitor Voice Recorder plugin. For more information, see https://github.com/tchvu3/capacitor-voice-recorder.
@@ -16,6 +7,7 @@ import { EventBus } from '../fromRimori/EventBus';
16
7
  * await audioController.startRecording();
17
8
  */
18
9
  export class AudioController {
10
+ pluginId;
19
11
  constructor(pluginId) {
20
12
  this.pluginId = pluginId;
21
13
  }
@@ -27,10 +19,8 @@ export class AudioController {
27
19
  * await audioController.startRecording();
28
20
  * @returns void
29
21
  */
30
- startRecording() {
31
- return __awaiter(this, void 0, void 0, function* () {
32
- EventBus.emit(this.pluginId, 'global.microphone.triggerStartRecording');
33
- });
22
+ async startRecording() {
23
+ EventBus.emit(this.pluginId, 'global.microphone.triggerStartRecording');
34
24
  }
35
25
  /**
36
26
  * Stop the recording and return the audio data.
@@ -41,28 +31,20 @@ export class AudioController {
41
31
  * audioRef.oncanplaythrough = () => audioRef.play()
42
32
  * audioRef.load()
43
33
  */
44
- stopRecording() {
45
- return __awaiter(this, void 0, void 0, function* () {
46
- const result = yield EventBus.request(this.pluginId, 'global.microphone.triggerStopRecording');
47
- return result.data;
48
- });
34
+ async stopRecording() {
35
+ const result = await EventBus.request(this.pluginId, 'global.microphone.triggerStopRecording');
36
+ return result.data;
49
37
  }
50
- pauseRecording() {
51
- return __awaiter(this, void 0, void 0, function* () {
52
- const result = yield EventBus.request(this.pluginId, 'global.microphone.triggerPauseRecording');
53
- return result.data;
54
- });
38
+ async pauseRecording() {
39
+ const result = await EventBus.request(this.pluginId, 'global.microphone.triggerPauseRecording');
40
+ return result.data;
55
41
  }
56
- resumeRecording() {
57
- return __awaiter(this, void 0, void 0, function* () {
58
- const result = yield EventBus.request(this.pluginId, 'global.microphone.triggerResumeRecording');
59
- return result.data;
60
- });
42
+ async resumeRecording() {
43
+ const result = await EventBus.request(this.pluginId, 'global.microphone.triggerResumeRecording');
44
+ return result.data;
61
45
  }
62
- getCurrentStatus() {
63
- return __awaiter(this, void 0, void 0, function* () {
64
- const result = yield EventBus.request(this.pluginId, 'global.microphone.triggerGetCurrentStatus');
65
- return result.data;
66
- });
46
+ async getCurrentStatus() {
47
+ const result = await EventBus.request(this.pluginId, 'global.microphone.triggerGetCurrentStatus');
48
+ return result.data;
67
49
  }
68
50
  }
@@ -1,20 +1,17 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  import { createInstance } from 'i18next';
11
2
  /**
12
3
  * Translator class for handling internationalization
13
4
  */
14
5
  export class Translator {
6
+ currentLanguage;
7
+ initializationState;
8
+ initializationPromise;
9
+ i18n;
10
+ translationUrl;
11
+ ai;
12
+ aiTranslationCache = new Map();
13
+ aiTranslationPending = new Map();
15
14
  constructor(initialLanguage, translationUrl, ai) {
16
- this.aiTranslationCache = new Map();
17
- this.aiTranslationPending = new Map();
18
15
  this.currentLanguage = initialLanguage;
19
16
  this.initializationState = 'not-inited';
20
17
  this.initializationPromise = null;
@@ -25,59 +22,56 @@ export class Translator {
25
22
  * Initialize translator with user's language
26
23
  * @param userLanguage - Language code from user info
27
24
  */
28
- initialize() {
29
- return __awaiter(this, void 0, void 0, function* () {
30
- // If already finished, return immediately
31
- if (this.initializationState === 'finished') {
32
- return;
25
+ async initialize() {
26
+ // If already finished, return immediately
27
+ if (this.initializationState === 'finished') {
28
+ return;
29
+ }
30
+ // If currently initializing, wait for the existing initialization to complete
31
+ if (this.initializationState === 'initing' && this.initializationPromise) {
32
+ return this.initializationPromise;
33
+ }
34
+ // Start initialization
35
+ this.initializationState = 'initing';
36
+ // Create a promise that will be resolved when initialization completes
37
+ this.initializationPromise = (async () => {
38
+ try {
39
+ const translations = await this.fetchTranslations(this.currentLanguage);
40
+ const instance = createInstance({
41
+ lng: this.currentLanguage,
42
+ resources: {
43
+ [this.currentLanguage]: {
44
+ translation: translations,
45
+ },
46
+ },
47
+ debug: false,
48
+ showSupportNotice: false,
49
+ parseMissingKeyHandler: (key, defaultValue) => {
50
+ if (!key.trim())
51
+ return '';
52
+ if (this.isTranslationKey(key)) {
53
+ console.warn(`Translation key not found: ${key}`);
54
+ return defaultValue ?? '';
55
+ }
56
+ void this.fetchTranslation(key).then((translation) => {
57
+ this.i18n?.addResource(this.currentLanguage, 'translation', key, translation);
58
+ this.i18n?.emit('languageChanged'); // triggers re-render
59
+ });
60
+ return key;
61
+ },
62
+ });
63
+ await instance.init();
64
+ this.i18n = instance;
65
+ this.initializationState = 'finished';
33
66
  }
34
- // If currently initializing, wait for the existing initialization to complete
35
- if (this.initializationState === 'initing' && this.initializationPromise) {
36
- return this.initializationPromise;
67
+ catch (error) {
68
+ // Reset state on error so it can be retried
69
+ this.initializationState = 'not-inited';
70
+ this.initializationPromise = null;
71
+ throw error;
37
72
  }
38
- // Start initialization
39
- this.initializationState = 'initing';
40
- // Create a promise that will be resolved when initialization completes
41
- this.initializationPromise = (() => __awaiter(this, void 0, void 0, function* () {
42
- try {
43
- const translations = yield this.fetchTranslations(this.currentLanguage);
44
- const instance = createInstance({
45
- lng: this.currentLanguage,
46
- resources: {
47
- [this.currentLanguage]: {
48
- translation: translations,
49
- },
50
- },
51
- debug: false,
52
- showSupportNotice: false,
53
- parseMissingKeyHandler: (key, defaultValue) => {
54
- if (!key.trim())
55
- return '';
56
- if (this.isTranslationKey(key)) {
57
- console.warn(`Translation key not found: ${key}`);
58
- return defaultValue !== null && defaultValue !== void 0 ? defaultValue : '';
59
- }
60
- void this.fetchTranslation(key).then((translation) => {
61
- var _a, _b;
62
- (_a = this.i18n) === null || _a === void 0 ? void 0 : _a.addResource(this.currentLanguage, 'translation', key, translation);
63
- (_b = this.i18n) === null || _b === void 0 ? void 0 : _b.emit('languageChanged'); // triggers re-render
64
- });
65
- return key;
66
- },
67
- });
68
- yield instance.init();
69
- this.i18n = instance;
70
- this.initializationState = 'finished';
71
- }
72
- catch (error) {
73
- // Reset state on error so it can be retried
74
- this.initializationState = 'not-inited';
75
- this.initializationPromise = null;
76
- throw error;
77
- }
78
- }))();
79
- return this.initializationPromise;
80
- });
73
+ })();
74
+ return this.initializationPromise;
81
75
  }
82
76
  getTranslationUrl(language) {
83
77
  const baseUrl = this.translationUrl || window.location.origin;
@@ -105,26 +99,24 @@ export class Translator {
105
99
  * @param language - Language code to fetch
106
100
  * @returns Promise with translation data
107
101
  */
108
- fetchTranslations(language) {
109
- return __awaiter(this, void 0, void 0, function* () {
110
- try {
111
- const response = yield fetch(this.getTranslationUrl(language));
112
- if (!response.ok) {
113
- throw new Error(`Failed to fetch translations for ${language}`);
114
- }
115
- return (yield response.json());
102
+ async fetchTranslations(language) {
103
+ try {
104
+ const response = await fetch(this.getTranslationUrl(language));
105
+ if (!response.ok) {
106
+ throw new Error(`Failed to fetch translations for ${language}`);
116
107
  }
117
- catch (error) {
118
- console.warn(`Failed to fetch translations for ${language}:`, error);
119
- if (language === 'en')
120
- return {};
121
- // Fallback to English
122
- return this.fetchTranslations('en').catch((error) => {
123
- console.error('Failed to fetch fallback translations:', error);
124
- return {};
125
- });
126
- }
127
- });
108
+ return (await response.json());
109
+ }
110
+ catch (error) {
111
+ console.warn(`Failed to fetch translations for ${language}:`, error);
112
+ if (language === 'en')
113
+ return {};
114
+ // Fallback to English
115
+ return this.fetchTranslations('en').catch((error) => {
116
+ console.error('Failed to fetch fallback translations:', error);
117
+ return {};
118
+ });
119
+ }
128
120
  }
129
121
  /**
130
122
  * Get translation for a key or freeform text. If the key is not a valid translation key, the freeform text is translated using AI and cached.
@@ -154,43 +146,41 @@ export class Translator {
154
146
  isTranslationKey(key) {
155
147
  return /^[^\s.]+(\.[^\s.]+)+$/.test(key);
156
148
  }
157
- fetchTranslation(text, additionalInstructions) {
158
- return __awaiter(this, void 0, void 0, function* () {
159
- const cached = this.aiTranslationCache.get(text);
160
- if (cached)
161
- return cached;
162
- const pending = this.aiTranslationPending.get(text);
163
- if (pending)
164
- return pending;
165
- if (!this.ai || this.currentLanguage === 'en')
166
- return text;
167
- const promise = (() => __awaiter(this, void 0, void 0, function* () {
168
- try {
169
- const response = yield this.ai.getObject({
170
- prompt: 'global.translator.translate',
171
- variables: {
172
- additionalInstructions: additionalInstructions !== null && additionalInstructions !== void 0 ? additionalInstructions : '',
173
- language: this.currentLanguage,
174
- text,
175
- },
176
- cache: true,
177
- });
178
- const translation = response === null || response === void 0 ? void 0 : response.translation;
179
- if (translation) {
180
- this.aiTranslationCache.set(text, translation);
181
- return translation;
182
- }
183
- }
184
- catch (error) {
185
- console.warn('Failed to translate freeform text:', { text, error });
186
- }
187
- finally {
188
- this.aiTranslationPending.delete(text);
149
+ async fetchTranslation(text, additionalInstructions) {
150
+ const cached = this.aiTranslationCache.get(text);
151
+ if (cached)
152
+ return cached;
153
+ const pending = this.aiTranslationPending.get(text);
154
+ if (pending)
155
+ return pending;
156
+ if (!this.ai || this.currentLanguage === 'en')
157
+ return text;
158
+ const promise = (async () => {
159
+ try {
160
+ const response = await this.ai.getObject({
161
+ prompt: 'global.translator.translate',
162
+ variables: {
163
+ additionalInstructions: additionalInstructions ?? '',
164
+ language: this.currentLanguage,
165
+ text,
166
+ },
167
+ cache: true,
168
+ });
169
+ const translation = response?.translation;
170
+ if (translation) {
171
+ this.aiTranslationCache.set(text, translation);
172
+ return translation;
189
173
  }
190
- return text;
191
- }))();
192
- this.aiTranslationPending.set(text, promise);
193
- return promise;
194
- });
174
+ }
175
+ catch (error) {
176
+ console.warn('Failed to translate freeform text:', { text, error });
177
+ }
178
+ finally {
179
+ this.aiTranslationPending.delete(text);
180
+ }
181
+ return text;
182
+ })();
183
+ this.aiTranslationPending.set(text, promise);
184
+ return promise;
195
185
  }
196
186
  }