@aj-archipelago/cortex 1.3.28 → 1.3.30
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/package.json +1 -1
- package/pathways/system/entity/sys_entity_continue.js +6 -1
- package/pathways/system/entity/sys_generator_error.js +5 -2
- package/pathways/translate_subtitle.js +51 -24
- package/server/plugins/gemini15ChatPlugin.js +1 -1
- package/server/plugins/gemini15VisionPlugin.js +1 -1
- package/server/plugins/openAiWhisperPlugin.js +3 -8
- package/tests/subchunk.srt +1459 -0
- package/tests/translate_srt.test.js +386 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aj-archipelago/cortex",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.30",
|
|
4
4
|
"description": "Cortex is a GraphQL API for AI. It provides a simple, extensible interface for using AI services from OpenAI, Azure and others.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -59,12 +59,17 @@ export default {
|
|
|
59
59
|
|
|
60
60
|
logger.debug(`Using generator pathway: ${generatorPathway}`);
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
let result = await callPathway(generatorPathway, newArgs, resolver);
|
|
63
63
|
|
|
64
64
|
if (!result && !args.stream) {
|
|
65
65
|
result = await callPathway('sys_generator_error', { ...args, text: `Tried to use a tool (${generatorPathway}), but no result was returned`, stream: false }, resolver);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
if (resolver.errors.length > 0) {
|
|
69
|
+
result = await callPathway('sys_generator_error', { ...args, text: resolver.errors.join('\n'), stream: false }, resolver);
|
|
70
|
+
resolver.errors = [];
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
return result;
|
|
69
74
|
|
|
70
75
|
} catch (e) {
|
|
@@ -4,8 +4,11 @@ export default {
|
|
|
4
4
|
prompt:
|
|
5
5
|
[
|
|
6
6
|
new Prompt({ messages: [
|
|
7
|
-
{"role": "system", "content": `{{renderTemplate AI_DIRECTIVES}}\n\n{{renderTemplate AI_COMMON_INSTRUCTIONS}}\n\n{{renderTemplate AI_EXPERTISE}}\n\
|
|
8
|
-
|
|
7
|
+
{"role": "system", "content": `{{renderTemplate AI_MEMORY}}\n\n{{renderTemplate AI_DIRECTIVES}}\n\n{{renderTemplate AI_COMMON_INSTRUCTIONS}}\n\n{{renderTemplate AI_EXPERTISE}}\n\n{{renderTemplate AI_CONVERSATION_HISTORY}}\n\nYou were trying to fulfill the user's last request in the above conversation, but ran into an error. You cannot resolve this error.\n{{renderTemplate AI_DATETIME}}`},
|
|
8
|
+
{
|
|
9
|
+
"role": "user",
|
|
10
|
+
"content": `The model that you were trying to use to fulfill the user's request returned the following error(s): {{{text}}}. Please let them know what happened. Your response should be concise, fit the rest of the conversation, include detail appropriate for the technical level of the user if you can determine it, and be appropriate for the context. You cannot resolve this error.`
|
|
11
|
+
},
|
|
9
12
|
]}),
|
|
10
13
|
],
|
|
11
14
|
inputParameters: {
|
|
@@ -2,7 +2,7 @@ import { parse, build } from "@aj-archipelago/subvibe";
|
|
|
2
2
|
import logger from "../lib/logger.js";
|
|
3
3
|
import { callPathway } from "../lib/pathwayTools.js";
|
|
4
4
|
|
|
5
|
-
function splitIntoOverlappingChunks(captions, chunkSize = 20, overlap = 3) {
|
|
5
|
+
export function splitIntoOverlappingChunks(captions, chunkSize = 20, overlap = 3) {
|
|
6
6
|
const chunks = [];
|
|
7
7
|
for (let i = 0; i < captions.length; i += (chunkSize - overlap)) {
|
|
8
8
|
const end = Math.min(i + chunkSize, captions.length);
|
|
@@ -17,26 +17,51 @@ function splitIntoOverlappingChunks(captions, chunkSize = 20, overlap = 3) {
|
|
|
17
17
|
return chunks;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function selectBestTranslation(translations, startIndex, endIndex) {
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
export function selectBestTranslation(translations, startIndex, endIndex) {
|
|
21
|
+
try {
|
|
22
|
+
if (!translations || !Array.isArray(translations)) {
|
|
23
|
+
logger.warn(`Invalid translations input: ${JSON.stringify(translations)}`);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (translations.length === 0) {
|
|
28
|
+
logger.warn(`No translations available for selection`);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If we only have one translation for this caption, use it
|
|
33
|
+
if (translations.length === 1) return translations[0];
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// Use the first translation as a starting point
|
|
36
|
+
const first = translations[0];
|
|
37
|
+
|
|
38
|
+
// For multiple translations, prefer the one whose identifier is closest to the middle
|
|
39
|
+
// of the requested range
|
|
40
|
+
const targetValue = (Number(startIndex) + Number(endIndex)) / 2;
|
|
41
|
+
|
|
42
|
+
return translations.reduce((best, current) => {
|
|
43
|
+
try {
|
|
44
|
+
// Use identifier for comparison if available, otherwise use index
|
|
45
|
+
const currentValue = Number(current.identifier !== undefined ? current.identifier : current.index || 0);
|
|
46
|
+
const bestValue = Number(best.identifier !== undefined ? best.identifier : best.index || 0);
|
|
47
|
+
|
|
48
|
+
const currentDistance = Math.abs(currentValue - targetValue);
|
|
49
|
+
const bestDistance = Math.abs(bestValue - targetValue);
|
|
50
|
+
|
|
51
|
+
return currentDistance < bestDistance ? current : best;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
logger.warn(`Error comparing translations: ${err.message}`);
|
|
54
|
+
return best; // Fallback to existing best on error
|
|
55
|
+
}
|
|
56
|
+
}, first);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
logger.error(`Error in selectBestTranslation: ${err.message}`);
|
|
59
|
+
// Return the first translation if available, otherwise null
|
|
60
|
+
return translations && translations.length ? translations[0] : null;
|
|
61
|
+
}
|
|
37
62
|
}
|
|
38
63
|
|
|
39
|
-
async function translateChunk(chunk, args, maxRetries = 3) {
|
|
64
|
+
export async function translateChunk(chunk, args, maxRetries = 3) {
|
|
40
65
|
const chunkText = build(chunk.captions, { format: args.format, preserveIndexes: true });
|
|
41
66
|
|
|
42
67
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
@@ -82,7 +107,7 @@ export default {
|
|
|
82
107
|
timeout: 3600,
|
|
83
108
|
executePathway: async ({args}) => {
|
|
84
109
|
try {
|
|
85
|
-
const { text, format = '
|
|
110
|
+
const { text, format = 'vtt' } = args;
|
|
86
111
|
const parsed = parse(text, { format, preserveIndexes: true });
|
|
87
112
|
const captions = parsed.cues;
|
|
88
113
|
|
|
@@ -101,16 +126,18 @@ export default {
|
|
|
101
126
|
// Create a map of caption index to all its translations
|
|
102
127
|
const translationMap = new Map();
|
|
103
128
|
translatedChunks.flat().forEach(caption => {
|
|
104
|
-
|
|
105
|
-
|
|
129
|
+
const identifier = caption.identifier || caption.index;
|
|
130
|
+
if (!translationMap.has(identifier)) {
|
|
131
|
+
translationMap.set(identifier, []);
|
|
106
132
|
}
|
|
107
|
-
translationMap.get(
|
|
133
|
+
translationMap.get(identifier).push(caption);
|
|
108
134
|
});
|
|
109
135
|
|
|
110
136
|
// Select best translation for each caption
|
|
111
137
|
const finalCaptions = captions.map(caption => {
|
|
112
|
-
const
|
|
113
|
-
const
|
|
138
|
+
const identifier = caption.identifier || caption.index;
|
|
139
|
+
const translations = translationMap.get(identifier) || [caption];
|
|
140
|
+
const bestTranslation = selectBestTranslation(translations, identifier, identifier);
|
|
114
141
|
const text = bestTranslation?.text || caption?.text;
|
|
115
142
|
return { ...caption, text };
|
|
116
143
|
});
|
|
@@ -140,7 +140,7 @@ class Gemini15ChatPlugin extends ModelPlugin {
|
|
|
140
140
|
dataToMerge = data.contents;
|
|
141
141
|
} else if (data && data.candidates && Array.isArray(data.candidates)) {
|
|
142
142
|
const { content, finishReason, safetyRatings } = data.candidates[0];
|
|
143
|
-
if (finishReason === 'STOP') {
|
|
143
|
+
if (finishReason === 'STOP' || finishReason === 'MAX_TOKENS') {
|
|
144
144
|
return content?.parts?.[0]?.text ?? '';
|
|
145
145
|
} else {
|
|
146
146
|
const returnString = `Response was not completed. Finish reason: ${finishReason}, Safety ratings: ${JSON.stringify(safetyRatings, null, 2)}`;
|
|
@@ -143,8 +143,8 @@ class Gemini15VisionPlugin extends Gemini15ChatPlugin {
|
|
|
143
143
|
if (data.error.code === 400 && data.error.message === 'Precondition check failed.') {
|
|
144
144
|
throw new Error('One or more of the included files is too large to process. Please try again with a smaller file.');
|
|
145
145
|
}
|
|
146
|
-
throw e;
|
|
147
146
|
}
|
|
147
|
+
throw e;
|
|
148
148
|
}
|
|
149
149
|
return result;
|
|
150
150
|
}
|
|
@@ -72,13 +72,7 @@ class OpenAIWhisperPlugin extends ModelPlugin {
|
|
|
72
72
|
if(maxLineWidth) tsparams.max_line_width = maxLineWidth;
|
|
73
73
|
if(maxLineCount) tsparams.max_line_count = maxLineCount;
|
|
74
74
|
if(maxWordsPerLine) tsparams.max_words_per_line = maxWordsPerLine;
|
|
75
|
-
|
|
76
|
-
if(!wordTimestamped) {
|
|
77
|
-
tsparams.word_timestamps = "False";
|
|
78
|
-
}else{
|
|
79
|
-
tsparams.word_timestamps = wordTimestamped;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
75
|
+
tsparams.word_timestamps = !wordTimestamped ? "False" : wordTimestamped;
|
|
82
76
|
|
|
83
77
|
const cortexRequest = new CortexRequest({ pathwayResolver });
|
|
84
78
|
cortexRequest.url = WHISPER_TS_API_URL;
|
|
@@ -157,7 +151,8 @@ async function processURI(uri) {
|
|
|
157
151
|
|
|
158
152
|
const intervalId = setInterval(() => sendProgress(true), 3000);
|
|
159
153
|
|
|
160
|
-
const useTS = WHISPER_TS_API_URL && (wordTimestamped || highlightWords);
|
|
154
|
+
//const useTS = WHISPER_TS_API_URL && (wordTimestamped || highlightWords); // use TS API only for word timestamped
|
|
155
|
+
const useTS = !!WHISPER_TS_API_URL; // use TS API always if URL is set
|
|
161
156
|
|
|
162
157
|
if (useTS) {
|
|
163
158
|
_promise = processTS;
|