@aj-archipelago/cortex 1.1.37 → 1.2.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/config.js +60 -0
- package/package.json +1 -1
- package/pathways/flux_image.js +2 -1
- package/pathways/index.js +6 -1
- package/pathways/sys_parse_numbered_object_list.js +19 -0
- package/pathways/sys_repair_json.js +17 -0
- package/server/chunker.js +156 -113
- package/server/modelExecutor.js +9 -1
- package/server/parser.js +18 -36
- package/server/pathwayResolver.js +1 -1
- package/server/pathwayResponseParser.js +3 -3
- package/server/plugins/azureCognitivePlugin.js +1 -1
- package/server/plugins/azureVideoTranslatePlugin.js +163 -0
- package/server/plugins/openAiVisionPlugin.js +0 -3
- package/server/plugins/{runwareAIPlugin.js → runwareAiPlugin.js} +1 -1
- package/tests/chunkfunction.test.js +270 -4
- package/tests/main.test.js +0 -55
- package/tests/parser.test.js +255 -0
- package/tests/translate_srt.test.js +82 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// AzureVideoTranslatePlugin.js
|
|
2
|
+
import ModelPlugin from "./modelPlugin.js";
|
|
3
|
+
import logger from "../../lib/logger.js";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import { publishRequestProgress } from "../../lib/redisSubscription.js";
|
|
6
|
+
import { config } from "../../config.js";
|
|
7
|
+
|
|
8
|
+
function isValidJSON(str) {
|
|
9
|
+
try {
|
|
10
|
+
JSON.parse(str);
|
|
11
|
+
return true;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class AzureVideoTranslatePlugin extends ModelPlugin {
|
|
18
|
+
constructor(pathway, model) {
|
|
19
|
+
super(pathway, model);
|
|
20
|
+
this.apiUrl = config.get("azureVideoTranslationApiUrl");
|
|
21
|
+
this.eventSource = null;
|
|
22
|
+
this.jsonBuffer = '';
|
|
23
|
+
this.jsonDepth = 0;
|
|
24
|
+
this.currentStep = 0;
|
|
25
|
+
this.totalNumOfSteps = 30;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getRequestParameters(_, parameters, __) {
|
|
29
|
+
const excludedParameters = [
|
|
30
|
+
'text', 'parameters', 'prompt', 'promptParameters', 'previousResult', 'stream'
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
return Object.fromEntries(
|
|
34
|
+
Object.entries(parameters).filter(([key, value]) =>
|
|
35
|
+
!excludedParameters.includes(key) &&
|
|
36
|
+
value !== '' &&
|
|
37
|
+
typeof value !== 'undefined'
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
handleStream(stream, onData, onEnd, onError) {
|
|
43
|
+
const timeout = setTimeout(() => {
|
|
44
|
+
onError(new Error('Stream timeout'));
|
|
45
|
+
}, 300000); // timeout
|
|
46
|
+
|
|
47
|
+
stream.on('data', (chunk) => {
|
|
48
|
+
clearTimeout(timeout);
|
|
49
|
+
const lines = chunk.toString().split('\n\n');
|
|
50
|
+
lines.forEach(line => {
|
|
51
|
+
if (line.startsWith('data: ')) {
|
|
52
|
+
const eventData = line.slice(6);
|
|
53
|
+
try {
|
|
54
|
+
this.handleEvent({ data: eventData }, onData);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
onError(error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
stream.on('end', () => {
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
this.cleanup();
|
|
64
|
+
onEnd();
|
|
65
|
+
});
|
|
66
|
+
stream.on('error', (error) => {
|
|
67
|
+
clearTimeout(timeout);
|
|
68
|
+
console.error('Stream error:', error);
|
|
69
|
+
this.cleanup();
|
|
70
|
+
onError(error);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
handleEvent(event, onData) {
|
|
75
|
+
const data = event.data;
|
|
76
|
+
this.jsonBuffer += data;
|
|
77
|
+
this.jsonDepth += (data.match(/{/g) || []).length - (data.match(/}/g) || []).length;
|
|
78
|
+
|
|
79
|
+
if (this.jsonDepth === 0 && this.jsonBuffer.trim()) {
|
|
80
|
+
console.log(this.jsonBuffer);
|
|
81
|
+
if (this.jsonBuffer.includes('Failed to run with exception')) {
|
|
82
|
+
this.cleanup();
|
|
83
|
+
throw new Error(this.jsonBuffer);
|
|
84
|
+
}
|
|
85
|
+
onData(this.jsonBuffer);
|
|
86
|
+
this.jsonBuffer = '';
|
|
87
|
+
this.jsonDepth = 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async execute(text, parameters, prompt, cortexRequest) {
|
|
92
|
+
if (!this.apiUrl) {
|
|
93
|
+
throw new Error("API URL is not set");
|
|
94
|
+
}
|
|
95
|
+
this.requestId = cortexRequest.requestId;
|
|
96
|
+
const requestParameters = this.getRequestParameters(text, parameters, prompt);
|
|
97
|
+
try {
|
|
98
|
+
const response = await axios.post(this.apiUrl, requestParameters, {
|
|
99
|
+
responseType: 'stream',
|
|
100
|
+
headers: {
|
|
101
|
+
'Cache-Control': 'no-cache',
|
|
102
|
+
'Pragma': 'no-cache',
|
|
103
|
+
'Expires': '0',
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
let finalJson = '';
|
|
109
|
+
this.handleStream(response.data,
|
|
110
|
+
(data) => {
|
|
111
|
+
this.currentStep++;
|
|
112
|
+
publishRequestProgress({
|
|
113
|
+
requestId: this.requestId,
|
|
114
|
+
progress: this.currentStep / this.totalNumOfSteps,
|
|
115
|
+
// data: this.jsonBuffer,
|
|
116
|
+
info: data
|
|
117
|
+
});
|
|
118
|
+
if (isValidJSON(data)) {
|
|
119
|
+
finalJson = data;
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
() => {
|
|
123
|
+
// console.log('Full data:', fullData);
|
|
124
|
+
resolve(finalJson)
|
|
125
|
+
},
|
|
126
|
+
(error) => reject(error)
|
|
127
|
+
);
|
|
128
|
+
}).finally(() => this.cleanup());
|
|
129
|
+
|
|
130
|
+
} catch (error) {
|
|
131
|
+
this.cleanup();
|
|
132
|
+
return error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
parseResponse(data) {
|
|
137
|
+
const response = typeof data === 'object' ? JSON.stringify(data) : data;
|
|
138
|
+
publishRequestProgress({
|
|
139
|
+
requestId: this.requestId,
|
|
140
|
+
progress: 1,
|
|
141
|
+
data: response,
|
|
142
|
+
});
|
|
143
|
+
return response;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
logRequestData(data, responseData, prompt) {
|
|
147
|
+
logger.verbose(`Request: ${JSON.stringify(data)}`);
|
|
148
|
+
logger.verbose(`Response: ${this.parseResponse(responseData)}`);
|
|
149
|
+
if (prompt?.debugInfo) {
|
|
150
|
+
prompt.debugInfo += `\nRequest: ${JSON.stringify(data)}`;
|
|
151
|
+
prompt.debugInfo += `\nResponse: ${this.parseResponse(responseData)}`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
cleanup() {
|
|
156
|
+
if (this.eventSource) {
|
|
157
|
+
this.eventSource.close();
|
|
158
|
+
this.eventSource = null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export default AzureVideoTranslatePlugin;
|
|
@@ -22,9 +22,6 @@ class OpenAIVisionPlugin extends OpenAIChatPlugin {
|
|
|
22
22
|
if (message.role === "tool") {
|
|
23
23
|
return message;
|
|
24
24
|
}
|
|
25
|
-
if (typeof message.content === 'string') {
|
|
26
|
-
message.content = safeJsonParse(message.content);
|
|
27
|
-
}
|
|
28
25
|
if (Array.isArray(message.content)) {
|
|
29
26
|
message.content = message.content.map(item => {
|
|
30
27
|
const parsedItem = safeJsonParse(item);
|
|
@@ -57,7 +57,7 @@ class RunwareAiPlugin extends ModelPlugin {
|
|
|
57
57
|
return this.executeRequest(cortexRequest);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// Parse the response from the
|
|
60
|
+
// Parse the response from the Runware API
|
|
61
61
|
parseResponse(data) {
|
|
62
62
|
if (data.data) {
|
|
63
63
|
return JSON.stringify(data.data);
|
|
@@ -87,9 +87,9 @@ test('should chunk text between html elements if needed', async t => {
|
|
|
87
87
|
|
|
88
88
|
t.is(chunks.length, 4);
|
|
89
89
|
t.is(chunks[0], htmlChunkTwo);
|
|
90
|
-
t.is(chunks[1], 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
|
|
91
|
-
t.
|
|
92
|
-
t.is(chunks[2], '; Fusce at dignissim quam.');
|
|
90
|
+
t.is(chunks[1], 'Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia ');
|
|
91
|
+
t.true(encode(chunks[1]).length < chunkSize);
|
|
92
|
+
t.is(chunks[2], 'curae; Fusce at dignissim quam.');
|
|
93
93
|
t.is(chunks[3], htmlChunkTwo);
|
|
94
94
|
});
|
|
95
95
|
|
|
@@ -221,4 +221,270 @@ test('should correctly split text into single token chunks', t => {
|
|
|
221
221
|
|
|
222
222
|
// Check specific tokens (this may need adjustment based on your tokenizer)
|
|
223
223
|
t.deepEqual(chunks, ['Hello', ',', ' world', '!']);
|
|
224
|
-
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('should respect sentence boundaries when possible', t => {
|
|
227
|
+
const text = 'First sentence. Second sentence. Third sentence.';
|
|
228
|
+
const maxChunkToken = encode('First sentence. Second').length;
|
|
229
|
+
const chunks = getSemanticChunks(text, maxChunkToken);
|
|
230
|
+
|
|
231
|
+
t.is(chunks[0], 'First sentence.');
|
|
232
|
+
t.is(chunks[1], ' Second sentence.');
|
|
233
|
+
t.is(chunks[2], ' Third sentence.');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('should respect paragraph boundaries', t => {
|
|
237
|
+
const text = 'First paragraph.\n\nSecond paragraph.\n\nThird paragraph.';
|
|
238
|
+
const maxChunkToken = encode('First paragraph.\n\nSecond').length;
|
|
239
|
+
const chunks = getSemanticChunks(text, maxChunkToken);
|
|
240
|
+
|
|
241
|
+
t.is(chunks[0], 'First paragraph.\n\n');
|
|
242
|
+
t.is(chunks[1], 'Second paragraph.\n\n');
|
|
243
|
+
t.is(chunks[2], 'Third paragraph.');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('should handle lists appropriately', t => {
|
|
247
|
+
const text = '1. First item\n2. Second item\n3. Third item';
|
|
248
|
+
const maxChunkToken = encode('1. First item\n2.').length;
|
|
249
|
+
const chunks = getSemanticChunks(text, maxChunkToken);
|
|
250
|
+
|
|
251
|
+
t.is(chunks[0], '1. First item\n');
|
|
252
|
+
t.is(chunks[1], '2. Second item\n');
|
|
253
|
+
t.is(chunks[2], '3. Third item');
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('should keep related punctuation together', t => {
|
|
257
|
+
const text = 'Question? Answer! Ellipsis... Done.';
|
|
258
|
+
const maxChunkToken = 5; // Small chunk size to force splits
|
|
259
|
+
const chunks = getSemanticChunks(text, maxChunkToken);
|
|
260
|
+
|
|
261
|
+
// Ensure question mark stays with "Question"
|
|
262
|
+
t.true(chunks.some(chunk => chunk.includes('Question?')));
|
|
263
|
+
// Ensure exclamation mark stays with "Answer"
|
|
264
|
+
t.true(chunks.some(chunk => chunk.includes('Answer!')));
|
|
265
|
+
// Ensure ellipsis stays together
|
|
266
|
+
t.true(chunks.some(chunk => chunk.includes('...')));
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('should handle empty strings appropriately', t => {
|
|
270
|
+
const chunks = getSemanticChunks('', 100);
|
|
271
|
+
t.deepEqual(chunks, []);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('should handle strings with only whitespace', t => {
|
|
275
|
+
const text = ' \n\n \t \n ';
|
|
276
|
+
const chunks = getSemanticChunks(text, 100);
|
|
277
|
+
t.is(chunks.join(''), text);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('should handle special characters and emoji correctly', t => {
|
|
281
|
+
const text = '👋 Hello! Special chars: §±@#$%^&* and more 🌟';
|
|
282
|
+
const maxChunkToken = 10;
|
|
283
|
+
const chunks = getSemanticChunks(text, maxChunkToken);
|
|
284
|
+
t.true(chunks.length > 0);
|
|
285
|
+
t.is(chunks.join(''), text);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('should handle code-like content appropriately', t => {
|
|
289
|
+
const text = 'const x = 42;\nfunction test() {\n return x;\n}';
|
|
290
|
+
const maxChunkToken = 20;
|
|
291
|
+
const chunks = getSemanticChunks(text, maxChunkToken);
|
|
292
|
+
|
|
293
|
+
// Code blocks should preferably break at logical points
|
|
294
|
+
t.true(chunks.some(chunk => chunk.includes('const x = 42;')));
|
|
295
|
+
t.true(chunks.join('').includes('function test() {'));
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('should handle extremely large token sizes gracefully', t => {
|
|
299
|
+
const maxChunkToken = Number.MAX_SAFE_INTEGER;
|
|
300
|
+
const chunks = getSemanticChunks(testText, maxChunkToken);
|
|
301
|
+
t.is(chunks.length, 1);
|
|
302
|
+
t.is(chunks[0], testText);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('should throw error for invalid maxChunkToken values', t => {
|
|
306
|
+
t.throws(() => getSemanticChunks(testText, 0), { message: /invalid/i });
|
|
307
|
+
t.throws(() => getSemanticChunks(testText, -1), { message: /invalid/i });
|
|
308
|
+
t.throws(() => getSemanticChunks(testText, NaN), { message: /invalid/i });
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test('should handle Arabic text correctly', t => {
|
|
312
|
+
const arabicText = 'مرحبا بالعالم. هذه جملة عربية. وهذه جملة أخرى!';
|
|
313
|
+
const maxChunkToken = encode('مرحبا بالعالم. هذه !').length;
|
|
314
|
+
const chunks = getSemanticChunks(arabicText, maxChunkToken);
|
|
315
|
+
|
|
316
|
+
// Check that chunks respect Arabic sentence boundaries
|
|
317
|
+
t.true(chunks[0].endsWith('.'));
|
|
318
|
+
t.is(chunks.join(''), arabicText);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('should handle mixed RTL and LTR text', t => {
|
|
322
|
+
const mixedText = 'Hello مرحبا World عالم! Testing اختبار.';
|
|
323
|
+
const maxChunkToken = 10;
|
|
324
|
+
const chunks = getSemanticChunks(mixedText, maxChunkToken);
|
|
325
|
+
|
|
326
|
+
t.true(chunks.length > 0);
|
|
327
|
+
t.is(chunks.join(''), mixedText);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test('should handle Chinese text correctly', t => {
|
|
331
|
+
const chineseText = '你好世界。这是一个测试。我们在测试中文分段。';
|
|
332
|
+
const maxChunkToken = encode('你好世界。').length;
|
|
333
|
+
const chunks = getSemanticChunks(chineseText, maxChunkToken);
|
|
334
|
+
|
|
335
|
+
// Check that chunks respect Chinese sentence boundaries
|
|
336
|
+
t.true(chunks[0].endsWith('。'));
|
|
337
|
+
t.is(chunks.join(''), chineseText);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test('should handle mixed scripts appropriately', t => {
|
|
341
|
+
const mixedText = 'Hello World! مرحبا بالعالم! 你好世界! Bonjour le monde!';
|
|
342
|
+
const maxChunkToken = 15;
|
|
343
|
+
const chunks = getSemanticChunks(mixedText, maxChunkToken);
|
|
344
|
+
|
|
345
|
+
t.true(chunks.length > 0);
|
|
346
|
+
t.true(chunks.every(chunk => encode(chunk).length <= maxChunkToken));
|
|
347
|
+
t.is(chunks.join(''), mixedText);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('should handle text with combining diacritical marks', t => {
|
|
351
|
+
const textWithDiacritics = 'é è ê ë ā ă ą ḥ ḫ ṭ ﻋَﺮَﺑِﻲ';
|
|
352
|
+
const maxChunkToken = 5;
|
|
353
|
+
const chunks = getSemanticChunks(textWithDiacritics, maxChunkToken);
|
|
354
|
+
|
|
355
|
+
t.true(chunks.length > 0);
|
|
356
|
+
t.is(chunks.join(''), textWithDiacritics);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test('should handle Arabic text with various sentence structures', t => {
|
|
360
|
+
const arabicText = `السَّلامُ عَلَيْكُمْ وَرَحْمَةُ اللهِ وَبَرَكَاتُهُ!
|
|
361
|
+
|
|
362
|
+
هَذَا نَصٌّ طَوِيلٌ لِاخْتِبَارِ التَّقْسِيمِ. يَحْتَوِي عَلَى عِدَّةِ جُمَلٍ؟ وَيَشْمَلُ عَلَامَاتِ التَّرْقِيمِ!
|
|
363
|
+
|
|
364
|
+
نَصٌّ مَعَ أَرْقَامٍ: 123 و ٤٥٦ و ٧٨٩.`;
|
|
365
|
+
|
|
366
|
+
const maxChunkToken = 20;
|
|
367
|
+
const chunks = getSemanticChunks(arabicText, maxChunkToken);
|
|
368
|
+
|
|
369
|
+
t.true(chunks.length > 1);
|
|
370
|
+
t.true(chunks.every(chunk => encode(chunk).length <= maxChunkToken));
|
|
371
|
+
t.is(chunks.join(''), arabicText);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
test('should handle Arabic text with Quranic diacritics', t => {
|
|
375
|
+
const quranText = 'بِسْمِ ٱللَّهِ ٱلرَّحْمَٰنِ ٱلرَّحِيمِ ٱلْحَمْدُ لِلَّهِ رَبِّ ٱلْعَٰلَمِينَ';
|
|
376
|
+
const maxChunkToken = 15;
|
|
377
|
+
const chunks = getSemanticChunks(quranText, maxChunkToken);
|
|
378
|
+
|
|
379
|
+
t.true(chunks.every(chunk => encode(chunk).length <= maxChunkToken));
|
|
380
|
+
t.is(chunks.join(''), quranText);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test('should handle Arabic text with mixed numbers and punctuation', t => {
|
|
384
|
+
const mixedArabicText = 'العام الدراسي 2023-2024م. سيبدأ في ١٥ سبتمبر! (إن شاء الله)';
|
|
385
|
+
const maxChunkToken = 10;
|
|
386
|
+
const chunks = getSemanticChunks(mixedArabicText, maxChunkToken);
|
|
387
|
+
|
|
388
|
+
t.true(chunks.length > 1);
|
|
389
|
+
t.true(chunks.every(chunk => encode(chunk).length <= maxChunkToken));
|
|
390
|
+
t.is(chunks.join(''), mixedArabicText);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test('should handle Arabic text with HTML', t => {
|
|
394
|
+
const arabicHtml = '<p>مرحباً <strong>بالعالم</strong> العربي!</p>';
|
|
395
|
+
const maxChunkToken = encode(arabicHtml).length;
|
|
396
|
+
const chunks = getSemanticChunks(arabicHtml, maxChunkToken, 'html');
|
|
397
|
+
|
|
398
|
+
t.is(chunks.length, 1);
|
|
399
|
+
t.is(chunks[0], arabicHtml);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test('should respect Arabic paragraph breaks', t => {
|
|
403
|
+
const arabicParagraphs = `الفقرة الأولى تحتوي على معلومات مهمة.
|
|
404
|
+
|
|
405
|
+
الفقرة الثانية تكمل الموضوع.
|
|
406
|
+
|
|
407
|
+
الفقرة الثالثة تختم الكلام.`;
|
|
408
|
+
|
|
409
|
+
const maxChunkToken = encode('الفقرة الأولى تحتوي على معلومات مهمة.').length;
|
|
410
|
+
const chunks = getSemanticChunks(arabicParagraphs, maxChunkToken);
|
|
411
|
+
|
|
412
|
+
t.true(chunks.some(chunk => chunk.includes('الفقرة الأولى')));
|
|
413
|
+
t.true(chunks.some(chunk => chunk.includes('الفقرة الثانية')));
|
|
414
|
+
t.true(chunks.some(chunk => chunk.includes('الفقرة الثالثة')));
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('should handle very large text (50x) efficiently', async t => {
|
|
418
|
+
const largeText = Array(50).fill(testText).join('\n');
|
|
419
|
+
t.log('Size of very large text:', largeText.length, 'bytes');
|
|
420
|
+
|
|
421
|
+
const startTime = performance.now();
|
|
422
|
+
|
|
423
|
+
const maxChunkToken = 1000;
|
|
424
|
+
const chunks = getSemanticChunks(largeText, maxChunkToken);
|
|
425
|
+
|
|
426
|
+
const endTime = performance.now();
|
|
427
|
+
const processingTime = endTime - startTime;
|
|
428
|
+
|
|
429
|
+
t.true(chunks.length > 0);
|
|
430
|
+
t.true(chunks.every(chunk => encode(chunk).length <= maxChunkToken));
|
|
431
|
+
t.is(chunks.join(''), largeText);
|
|
432
|
+
|
|
433
|
+
// Processing should take less than 1 second for this size
|
|
434
|
+
t.true(processingTime < 1000, `Processing took ${processingTime}ms`);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test('should handle extremely large text (500x) efficiently', async t => {
|
|
438
|
+
const largeText = Array(500).fill(testText).join('\n');
|
|
439
|
+
t.log('Size of extremely large text:', largeText.length, 'bytes');
|
|
440
|
+
|
|
441
|
+
const startTime = performance.now();
|
|
442
|
+
|
|
443
|
+
const maxChunkToken = 1000;
|
|
444
|
+
const chunks = getSemanticChunks(largeText, maxChunkToken);
|
|
445
|
+
|
|
446
|
+
const endTime = performance.now();
|
|
447
|
+
const processingTime = endTime - startTime;
|
|
448
|
+
|
|
449
|
+
t.true(chunks.length > 0);
|
|
450
|
+
t.true(chunks.every(chunk => encode(chunk).length <= maxChunkToken));
|
|
451
|
+
t.is(chunks.join(''), largeText);
|
|
452
|
+
|
|
453
|
+
// Processing should take less than 5 seconds for this size
|
|
454
|
+
t.true(processingTime < 5000, `Processing took ${processingTime}ms`);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test('should handle massive text (5000x) efficiently', async t => {
|
|
458
|
+
const largeText = Array(5000).fill(testText).join('\n');
|
|
459
|
+
t.log('Size of massive text:', largeText.length, 'bytes');
|
|
460
|
+
|
|
461
|
+
const startTime = performance.now();
|
|
462
|
+
|
|
463
|
+
const maxChunkToken = 1000;
|
|
464
|
+
const chunks = getSemanticChunks(largeText, maxChunkToken);
|
|
465
|
+
|
|
466
|
+
const endTime = performance.now();
|
|
467
|
+
const processingTime = endTime - startTime;
|
|
468
|
+
|
|
469
|
+
t.true(chunks.length > 0);
|
|
470
|
+
t.true(chunks.every(chunk => encode(chunk).length <= maxChunkToken));
|
|
471
|
+
t.is(chunks.join(''), largeText);
|
|
472
|
+
|
|
473
|
+
// Processing should take less than 30 seconds for this size
|
|
474
|
+
t.true(processingTime < 30000, `Processing took ${processingTime}ms`);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test('should maintain memory efficiency with huge texts', async t => {
|
|
478
|
+
const initialMemory = process.memoryUsage().heapUsed;
|
|
479
|
+
|
|
480
|
+
const largeText = Array(1000).fill(testText).join('\n');
|
|
481
|
+
const maxChunkToken = 1000;
|
|
482
|
+
const chunks = getSemanticChunks(largeText, maxChunkToken);
|
|
483
|
+
|
|
484
|
+
const finalMemory = process.memoryUsage().heapUsed;
|
|
485
|
+
const memoryIncrease = (finalMemory - initialMemory) / 1024 / 1024; // Convert to MB
|
|
486
|
+
|
|
487
|
+
t.true(chunks.length > 0);
|
|
488
|
+
// Memory increase should be reasonable (less than 100MB for this test)
|
|
489
|
+
t.true(memoryIncrease < 100, `Memory increase was ${memoryIncrease.toFixed(2)}MB`);
|
|
490
|
+
});
|
package/tests/main.test.js
CHANGED
|
@@ -357,61 +357,6 @@ test('test translate endpoint with huge arabic text english translation and chec
|
|
|
357
357
|
});
|
|
358
358
|
|
|
359
359
|
|
|
360
|
-
async function testTranslateSrt(t, text, language='English') {
|
|
361
|
-
const response = await testServer.executeOperation({
|
|
362
|
-
query: 'query translate_subtitle($text: String!, $to:String) { translate_subtitle(text: $text, to:$to) { result } }',
|
|
363
|
-
variables: {
|
|
364
|
-
to: language,
|
|
365
|
-
text
|
|
366
|
-
},
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
t.falsy(response.body?.singleResult?.errors);
|
|
370
|
-
|
|
371
|
-
const result = response.body?.singleResult?.data?.translate_subtitle?.result;
|
|
372
|
-
t.true(result?.length > text.length*0.5);
|
|
373
|
-
|
|
374
|
-
//check all timestamps are still there and not translated
|
|
375
|
-
const originalTimestamps = text.match(/\d{2}:\d{2}:\d{2},\d{3} --> \d{2}:\d{2}:\d{2},\d{3}/g);
|
|
376
|
-
const translatedTimestamps = result.match(/\d{2}:\d{2}:\d{2},\d{3} --> \d{2}:\d{2}:\d{2},\d{3}/g);
|
|
377
|
-
|
|
378
|
-
t.deepEqual(originalTimestamps, translatedTimestamps, 'All timestamps should be present and unchanged');
|
|
379
|
-
|
|
380
|
-
const originalLineCount = text.split('\n').length;
|
|
381
|
-
const translatedLineCount = result.split('\n').length;
|
|
382
|
-
|
|
383
|
-
t.is(originalLineCount, translatedLineCount, 'Total number of lines should be the same');
|
|
384
|
-
}
|
|
385
360
|
|
|
386
|
-
test('test translate_srt endpoint with simple srt', async t => {
|
|
387
|
-
const text = `1
|
|
388
|
-
00:00:03,069 --> 00:00:04,771
|
|
389
|
-
Who’s that?
|
|
390
361
|
|
|
391
|
-
2
|
|
392
|
-
00:00:04,771 --> 00:00:06,039
|
|
393
|
-
Aseel.
|
|
394
362
|
|
|
395
|
-
3
|
|
396
|
-
00:00:06,039 --> 00:00:07,474
|
|
397
|
-
Who is Aseel a mom to?
|
|
398
|
-
|
|
399
|
-
4
|
|
400
|
-
00:00:07,474 --> 00:00:09,376
|
|
401
|
-
Aseel is mommy
|
|
402
|
-
`;
|
|
403
|
-
|
|
404
|
-
await testTranslateSrt(t, text, 'Spanish');
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
test('test translate_srt endpoint with long srt file', async t => {
|
|
408
|
-
t.timeout(400000);
|
|
409
|
-
const text = fs.readFileSync(path.join(__dirname, 'sublong.srt'), 'utf8');
|
|
410
|
-
await testTranslateSrt(t, text, 'English');
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
test('test translate_srt endpoint with horizontal srt file', async t => {
|
|
414
|
-
t.timeout(400000);
|
|
415
|
-
const text = fs.readFileSync(path.join(__dirname, 'subhorizontal.srt'), 'utf8');
|
|
416
|
-
await testTranslateSrt(t, text, 'Turkish');
|
|
417
|
-
});
|