@aj-archipelago/cortex 1.3.28 → 1.3.29

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aj-archipelago/cortex",
3
- "version": "1.3.28",
3
+ "version": "1.3.29",
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": {
@@ -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
- // If we only have one translation for this caption, use it
22
- if (translations.length === 1) return translations[0];
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
- // For multiple translations, prefer the one from the middle of its chunk
25
- // This helps avoid edge effects in translation
26
- return translations.reduce((best, current) => {
27
- const currentDistance = Math.min(
28
- Math.abs(current.chunkStart - startIndex),
29
- Math.abs(current.chunkEnd - endIndex)
30
- );
31
- const bestDistance = Math.min(
32
- Math.abs(best.chunkStart - startIndex),
33
- Math.abs(best.chunkEnd - endIndex)
34
- );
35
- return currentDistance < bestDistance ? current : best;
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 = 'srt' } = args;
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
- if (!translationMap.has(caption.index)) {
105
- translationMap.set(caption.index, []);
129
+ const identifier = caption.identifier || caption.index;
130
+ if (!translationMap.has(identifier)) {
131
+ translationMap.set(identifier, []);
106
132
  }
107
- translationMap.get(caption.index).push(caption);
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 translations = translationMap.get(caption.index) || [caption];
113
- const bestTranslation = selectBestTranslation(translations, caption.index, caption.index);
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
  });
@@ -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
- if(wordTimestamped!=null) {
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;