gptrans 1.8.4 → 1.8.6

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.
@@ -0,0 +1,156 @@
1
+ import GPTrans from '../index.js';
2
+ import dotenv from 'dotenv';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+
6
+ // Cargar .env desde la carpeta demo
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ dotenv.config({ path: join(__dirname, '.env') });
10
+
11
+ console.log('🚀 Prueba de Paralelismo en GPTrans');
12
+ console.log('⚠️ Múltiples instancias con MISMO NOMBRE y MISMO PAR DE IDIOMAS\n');
13
+ console.log('=' .repeat(70));
14
+
15
+ // Test: Múltiples instancias con el MISMO NOMBRE y MISMO PAR DE IDIOMAS
16
+ async function testParallelTranslations() {
17
+ const instanceName = 'parallel_test'; // UN SOLO NOMBRE
18
+ const sourceLang = 'en'; // UN SOLO IDIOMA ORIGEN
19
+ const targetLang = 'es'; // UN SOLO IDIOMA DESTINO
20
+ const instanceCount = 20; // MUCHAS INSTANCIAS
21
+
22
+ console.log(`📋 Configuración de la prueba:`);
23
+ console.log(` Nombre de instancia: "${instanceName}"`);
24
+ console.log(` Par de idiomas: ${sourceLang} → ${targetLang}`);
25
+ console.log(` Número de instancias: ${instanceCount}`);
26
+ console.log(` Modo: Promise.all() - Todas simultáneas`);
27
+ console.log('-'.repeat(70));
28
+
29
+ // Generar textos únicos para cada instancia
30
+ const texts = Array.from({ length: instanceCount }, (_, i) =>
31
+ `Parallel translation test number ${i + 1}`
32
+ );
33
+
34
+ console.log(`\n🚀 Iniciando ${instanceCount} traducciones simultáneas...\n`);
35
+
36
+ const startTime = Date.now();
37
+
38
+ // TODAS las instancias se crean y solicitan traducciones SIMULTÁNEAMENTE
39
+ const results = await Promise.all(
40
+ texts.map(async (text, index) => {
41
+ // Todas las instancias comparten el MISMO NOMBRE
42
+ const gptrans = new GPTrans({
43
+ from: sourceLang,
44
+ target: targetLang,
45
+ model: 'sonnet45',
46
+ name: instanceName // ⚠️ MISMO NOMBRE = MISMA DB
47
+ });
48
+
49
+ // Mostrar solo las primeras y últimas para no saturar consola
50
+ if (index < 5 || index >= instanceCount - 2) {
51
+ console.log(` [${index + 1}/${instanceCount}] Instancia creada, traduciendo: "${text}"`);
52
+ } else if (index === 5) {
53
+ console.log(` ... (${instanceCount - 7} instancias más) ...`);
54
+ }
55
+
56
+ // Primera lectura (inmediata - probablemente sin traducir)
57
+ const immediate = await gptrans.t(text);
58
+
59
+ // Esperar para que se procesen las traducciones
60
+ await new Promise(resolve => setTimeout(resolve, 3000));
61
+
62
+ // Segunda lectura (después del procesamiento)
63
+ const final = await gptrans.t(text);
64
+
65
+ return {
66
+ index: index + 1,
67
+ original: text,
68
+ immediate: immediate,
69
+ final: final
70
+ };
71
+ })
72
+ );
73
+
74
+ const duration = (Date.now() - startTime) / 1000;
75
+
76
+ console.log('\n' + '='.repeat(70));
77
+ console.log('📊 RESULTADOS');
78
+ console.log('='.repeat(70));
79
+
80
+ // Mostrar solo algunos resultados para no saturar
81
+ console.log('\n📋 Traducciones (primeras 5):');
82
+ results.slice(0, 5).forEach(({ index, original, immediate, final }) => {
83
+ console.log(`\n [${index}] Original: "${original}"`);
84
+ console.log(` Inmediato: "${immediate}"`);
85
+ console.log(` Final: "${final}"`);
86
+ });
87
+
88
+ if (instanceCount > 5) {
89
+ console.log(`\n ... (${instanceCount - 5} traducciones más) ...`);
90
+ }
91
+
92
+ console.log(`\n⏱️ Tiempo total: ${duration.toFixed(2)}s`);
93
+ console.log(`⚡ Velocidad promedio: ${(duration / instanceCount).toFixed(3)}s por traducción`);
94
+
95
+ // Análisis de integridad
96
+ console.log('\n' + '='.repeat(70));
97
+ console.log('🔍 ANÁLISIS DE INTEGRIDAD');
98
+ console.log('='.repeat(70));
99
+
100
+ const allTranslated = results.every(r => r.final !== r.original);
101
+ const someImmediate = results.filter(r => r.immediate !== r.original).length;
102
+ const uniqueFinals = new Set(results.map(r => r.final));
103
+ const uniqueOriginals = new Set(results.map(r => r.original));
104
+
105
+ console.log(`\n✓ Textos originales únicos: ${uniqueOriginals.size}/${instanceCount}`);
106
+ console.log(`✓ Traducciones inmediatas: ${someImmediate}/${instanceCount}`);
107
+ console.log(`✓ Traducciones finales completadas: ${results.filter(r => r.final !== r.original).length}/${instanceCount}`);
108
+ console.log(`✓ Traducciones finales únicas: ${uniqueFinals.size}/${instanceCount}`);
109
+
110
+ console.log('\n📈 Estado:');
111
+ console.log(` Todas traducidas correctamente: ${allTranslated ? '✅ SÍ' : '❌ NO'}`);
112
+ console.log(` Sin duplicados: ${uniqueFinals.size === instanceCount ? '✅ SÍ' : '❌ NO'}`);
113
+
114
+ // Detectar problemas
115
+ const hasRaceCondition = uniqueFinals.size !== instanceCount;
116
+ const hasUntranslated = !allTranslated;
117
+
118
+ if (hasRaceCondition) {
119
+ console.log(`\n⚠️ RACE CONDITION DETECTADA:`);
120
+ console.log(` Se esperaban ${instanceCount} traducciones únicas`);
121
+ console.log(` Se obtuvieron ${uniqueFinals.size} traducciones únicas`);
122
+ console.log(` Duplicados: ${instanceCount - uniqueFinals.size}`);
123
+ }
124
+
125
+ if (hasUntranslated) {
126
+ const untranslated = results.filter(r => r.final === r.original);
127
+ console.log(`\n⚠️ TRADUCCIONES INCOMPLETAS:`);
128
+ console.log(` ${untranslated.length} textos no fueron traducidos`);
129
+ untranslated.slice(0, 3).forEach(({ index, original }) => {
130
+ console.log(` [${index}] "${original}"`);
131
+ });
132
+ }
133
+
134
+ if (!hasRaceCondition && !hasUntranslated) {
135
+ console.log(`\n✅ PRUEBA EXITOSA:`);
136
+ console.log(` Todas las traducciones se completaron correctamente`);
137
+ console.log(` No se detectaron race conditions`);
138
+ console.log(` No se detectaron duplicados`);
139
+ }
140
+
141
+ console.log('\n' + '='.repeat(70));
142
+ console.log('💡 CONCLUSIÓN');
143
+ console.log('='.repeat(70));
144
+ console.log(`\n${instanceCount} instancias con:`);
145
+ console.log(` • MISMO nombre: "${instanceName}"`);
146
+ console.log(` • MISMO par de idiomas: ${sourceLang} → ${targetLang}`);
147
+ console.log(` • Solicitudes SIMULTÁNEAS con Promise.all()`);
148
+ console.log(` • Resultado: ${hasRaceCondition || hasUntranslated ? '🔴 PROBLEMAS DETECTADOS' : '✅ EXITOSO'}`);
149
+ console.log('');
150
+ }
151
+
152
+ // Ejecutar prueba
153
+ testParallelTranslations().catch(error => {
154
+ console.error('\n❌ Error durante la prueba:', error.message);
155
+ console.error(error.stack);
156
+ });
package/index.js CHANGED
@@ -9,6 +9,7 @@ import path from 'path';
9
9
 
10
10
  class GPTrans {
11
11
  static #mmixInstances = new Map();
12
+ static #translationLocks = new Map();
12
13
 
13
14
  static mmix(models = 'sonnet45') {
14
15
  const key = Array.isArray(models) ? models.join(',') : models;
@@ -44,6 +45,24 @@ class GPTrans {
44
45
  return this.#mmixInstances.get(key);
45
46
  }
46
47
 
48
+ static async #acquireTranslationLock(modelKey) {
49
+ if (!this.#translationLocks.has(modelKey)) {
50
+ this.#translationLocks.set(modelKey, Promise.resolve());
51
+ }
52
+
53
+ const previousLock = this.#translationLocks.get(modelKey);
54
+ let releaseLock;
55
+
56
+ const currentLock = new Promise(resolve => {
57
+ releaseLock = resolve;
58
+ });
59
+
60
+ this.#translationLocks.set(modelKey, previousLock.then(() => currentLock));
61
+
62
+ await previousLock;
63
+ return releaseLock;
64
+ }
65
+
47
66
  static isLanguageAvailable(langCode) {
48
67
  return isLanguageAvailable(langCode);
49
68
  }
@@ -100,7 +119,8 @@ class GPTrans {
100
119
  setContext(context = '') {
101
120
  if (this.context !== context && this.pendingTranslations.size > 0) {
102
121
  clearTimeout(this.debounceTimer);
103
- this._processBatch(this.context);
122
+ const capturedContext = this.context;
123
+ this._processBatch(capturedContext);
104
124
  }
105
125
  this.context = context;
106
126
  return this;
@@ -150,10 +170,11 @@ class GPTrans {
150
170
  clearTimeout(this.debounceTimer);
151
171
  }
152
172
 
153
- // Set new timer
173
+ // Set new timer - capture context at scheduling time
174
+ const capturedContext = this.context;
154
175
  this.debounceTimer = setTimeout(() => {
155
176
  if (this.pendingTranslations.size > 0) {
156
- this._processBatch(this.context);
177
+ this._processBatch(capturedContext);
157
178
  }
158
179
  }, this.debounceTimeout);
159
180
 
@@ -203,24 +224,31 @@ class GPTrans {
203
224
  }
204
225
 
205
226
  async _translate(text) {
227
+ // Acquire lock to ensure atomic model configuration and translation
228
+ const releaseLock = await GPTrans.#acquireTranslationLock(this.modelKey);
229
+
230
+ try {
231
+ const model = GPTrans.mmix(this.modelKey);
206
232
 
207
- const model = GPTrans.mmix(this.modelKey);
208
-
209
- model.setSystem("You are an expert translator specialized in literary translation between FROM_LANG and TARGET_DENONYM TARGET_LANG.");
233
+ model.setSystem("You are an expert translator specialized in literary translation between FROM_LANG and TARGET_DENONYM TARGET_LANG.");
210
234
 
211
- model.addTextFromFile(this.promptFile);
235
+ model.addTextFromFile(this.promptFile);
212
236
 
213
- model.replace({ INPUT: text, CONTEXT: this.context });
214
- model.replace(this.replaceTarget);
215
- model.replace(this.replaceFrom);
237
+ model.replace({ INPUT: text, CONTEXT: this.context });
238
+ model.replace(this.replaceTarget);
239
+ model.replace(this.replaceFrom);
216
240
 
217
- const response = await model.message();
241
+ const response = await model.message();
218
242
 
219
- const codeBlockRegex = /```(?:\w*\n)?([\s\S]*?)```/;
220
- const match = response.match(codeBlockRegex);
221
- const translatedText = match ? match[1].trim() : response;
243
+ const codeBlockRegex = /```(?:\w*\n)?([\s\S]*?)```/;
244
+ const match = response.match(codeBlockRegex);
245
+ const translatedText = match ? match[1].trim() : response;
222
246
 
223
- return translatedText;
247
+ return translatedText;
248
+ } finally {
249
+ // Always release the lock
250
+ releaseLock();
251
+ }
224
252
  }
225
253
 
226
254
  _textToKey(text, tokens = 5, maxlen = 6) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gptrans",
3
3
  "type": "module",
4
- "version": "1.8.4",
4
+ "version": "1.8.6",
5
5
  "description": "🚆 GPTrans - The smarter AI-powered way to translate.",
6
6
  "keywords": [
7
7
  "translate",