@aj-archipelago/cortex 1.3.10 → 1.3.12

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 (37) hide show
  1. package/config.js +15 -0
  2. package/helper-apps/cortex-file-handler/.env.test +7 -0
  3. package/helper-apps/cortex-file-handler/.env.test.azure +6 -0
  4. package/helper-apps/cortex-file-handler/.env.test.gcs +9 -0
  5. package/helper-apps/cortex-file-handler/blobHandler.js +263 -179
  6. package/helper-apps/cortex-file-handler/constants.js +107 -0
  7. package/helper-apps/cortex-file-handler/docHelper.js +4 -1
  8. package/helper-apps/cortex-file-handler/fileChunker.js +171 -109
  9. package/helper-apps/cortex-file-handler/helper.js +39 -17
  10. package/helper-apps/cortex-file-handler/index.js +230 -138
  11. package/helper-apps/cortex-file-handler/localFileHandler.js +21 -3
  12. package/helper-apps/cortex-file-handler/package-lock.json +2622 -51
  13. package/helper-apps/cortex-file-handler/package.json +24 -4
  14. package/helper-apps/cortex-file-handler/redis.js +9 -18
  15. package/helper-apps/cortex-file-handler/scripts/setup-azure-container.js +22 -0
  16. package/helper-apps/cortex-file-handler/scripts/setup-test-containers.js +49 -0
  17. package/helper-apps/cortex-file-handler/scripts/test-azure.sh +34 -0
  18. package/helper-apps/cortex-file-handler/scripts/test-gcs.sh +49 -0
  19. package/helper-apps/cortex-file-handler/start.js +26 -4
  20. package/helper-apps/cortex-file-handler/tests/docHelper.test.js +148 -0
  21. package/helper-apps/cortex-file-handler/tests/fileChunker.test.js +322 -0
  22. package/helper-apps/cortex-file-handler/tests/start.test.js +928 -0
  23. package/helper-apps/cortex-realtime-voice-server/client/src/chat/components/ScreenshotCapture.tsx +57 -9
  24. package/helper-apps/cortex-realtime-voice-server/src/SocketServer.ts +35 -22
  25. package/helper-apps/cortex-realtime-voice-server/src/Tools.ts +65 -14
  26. package/helper-apps/cortex-realtime-voice-server/src/realtime/client.ts +10 -10
  27. package/helper-apps/cortex-realtime-voice-server/src/realtime/socket.ts +2 -1
  28. package/package.json +1 -1
  29. package/pathways/system/entity/sys_entity_continue.js +1 -1
  30. package/pathways/system/entity/sys_entity_start.js +1 -0
  31. package/pathways/system/entity/sys_generator_reasoning.js +1 -1
  32. package/pathways/system/entity/sys_generator_video_vision.js +2 -1
  33. package/pathways/system/entity/sys_router_tool.js +6 -4
  34. package/pathways/system/rest_streaming/sys_openai_chat_o1.js +19 -0
  35. package/pathways/system/rest_streaming/sys_openai_chat_o1_mini.js +19 -0
  36. package/server/plugins/openAiReasoningPlugin.js +11 -2
  37. package/server/plugins/openAiWhisperPlugin.js +9 -13
@@ -0,0 +1,107 @@
1
+ export const DOC_EXTENSIONS = [
2
+ ".txt",
3
+ ".json",
4
+ ".csv",
5
+ ".md",
6
+ ".xml",
7
+ ".js",
8
+ ".html",
9
+ ".css",
10
+ ".doc",
11
+ ".docx",
12
+ ".xls",
13
+ ".xlsx"
14
+ ];
15
+
16
+ export const IMAGE_EXTENSIONS = [
17
+ ".jpg",
18
+ ".jpeg",
19
+ ".png",
20
+ ".webp",
21
+ ".heic",
22
+ ".heif",
23
+ ".pdf"
24
+ ];
25
+
26
+ export const VIDEO_EXTENSIONS = [
27
+ ".mp4",
28
+ ".mpeg",
29
+ ".mov",
30
+ ".avi",
31
+ ".flv",
32
+ ".mpg",
33
+ ".webm",
34
+ ".wmv",
35
+ ".3gp"
36
+ ];
37
+
38
+ export const AUDIO_EXTENSIONS = [
39
+ ".wav",
40
+ ".mp3",
41
+ ".aac",
42
+ ".ogg",
43
+ ".flac"
44
+ ];
45
+
46
+ export const ACCEPTED_MIME_TYPES = {
47
+ // Document types
48
+ 'text/plain': ['.txt'],
49
+ 'application/json': ['.json'],
50
+ 'text/csv': ['.csv'],
51
+ 'text/markdown': ['.md'],
52
+ 'application/xml': ['.xml'],
53
+ 'text/javascript': ['.js'],
54
+ 'text/html': ['.html'],
55
+ 'text/css': ['.css'],
56
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
57
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'],
58
+ 'application/msword': ['.doc'],
59
+ 'application/vnd.ms-excel': ['.xls'],
60
+ 'application/vnd.ms-word.document.macroEnabled.12': ['.docm'],
61
+ 'application/vnd.ms-excel.sheet.macroEnabled.12': ['.xlsm'],
62
+ 'application/vnd.ms-word.template.macroEnabled.12': ['.dotm'],
63
+ 'application/vnd.ms-excel.template.macroEnabled.12': ['.xltm'],
64
+
65
+ // Image types
66
+ 'image/jpeg': ['.jpg', '.jpeg'],
67
+ 'image/png': ['.png'],
68
+ 'image/webp': ['.webp'],
69
+ 'image/heic': ['.heic'],
70
+ 'image/heif': ['.heif'],
71
+ 'application/pdf': ['.pdf'],
72
+
73
+ // Audio types
74
+ 'audio/wav': ['.wav'],
75
+ 'audio/mpeg': ['.mp3'],
76
+ 'audio/aac': ['.aac'],
77
+ 'audio/ogg': ['.ogg'],
78
+ 'audio/flac': ['.flac'],
79
+
80
+ // Video types
81
+ 'video/mp4': ['.mp4'],
82
+ 'video/mpeg': ['.mpeg', '.mpg'],
83
+ 'video/quicktime': ['.mov'],
84
+ 'video/x-msvideo': ['.avi'],
85
+ 'video/x-flv': ['.flv'],
86
+ 'video/webm': ['.webm'],
87
+ 'video/x-ms-wmv': ['.wmv'],
88
+ 'video/3gpp': ['.3gp']
89
+ };
90
+
91
+ // Helper function to check if a mime type is accepted
92
+ export function isAcceptedMimeType(mimeType) {
93
+ return mimeType in ACCEPTED_MIME_TYPES;
94
+ }
95
+
96
+ // Helper function to get accepted extensions for a mime type
97
+ export function getExtensionsForMimeType(mimeType) {
98
+ return ACCEPTED_MIME_TYPES[mimeType] || [];
99
+ }
100
+
101
+ // Helper function to check if an extension is accepted
102
+ export function isAcceptedExtension(extension) {
103
+ return DOC_EXTENSIONS.includes(extension) ||
104
+ IMAGE_EXTENSIONS.includes(extension) ||
105
+ VIDEO_EXTENSIONS.includes(extension) ||
106
+ AUDIO_EXTENSIONS.includes(extension);
107
+ }
@@ -1,4 +1,4 @@
1
- import pdfjsLib from 'pdfjs-dist';
1
+ import * as pdfjsLib from 'pdfjs-dist';
2
2
  import fs from 'fs/promises';
3
3
  import mammoth from 'mammoth';
4
4
  import XLSX from 'xlsx';
@@ -95,10 +95,13 @@ export async function documentToText(filePath) {
95
95
  case 'pdf':
96
96
  return pdfToText(filePath);
97
97
  case 'txt':
98
+ case 'html':
98
99
  return txtToText(filePath);
99
100
  case 'docx':
101
+ case 'doc':
100
102
  return docxToText(filePath);
101
103
  case 'xlsx':
104
+ case 'xls':
102
105
  return xlsxToText(filePath);
103
106
  case 'csv':
104
107
  return csvToText(filePath);
@@ -6,170 +6,232 @@ import os from 'os';
6
6
  import { promisify } from 'util';
7
7
  import axios from 'axios';
8
8
  import { ensureEncoded } from './helper.js';
9
- import ytdl from '@distube/ytdl-core';
10
-
9
+ import http from 'http';
10
+ import https from 'https';
11
+ import { pipeline } from 'stream/promises';
11
12
 
12
13
  const ffmpegProbe = promisify(ffmpeg.ffprobe);
13
14
 
15
+ // Temp file management
16
+ const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
17
+ const tempDirectories = new Map(); // dir -> { createdAt, requestId }
18
+
19
+ // Temp directory cleanup
20
+ async function cleanupTempDirectories() {
21
+ const tempDir = os.tmpdir();
22
+
23
+ for (const [dir, info] of tempDirectories) {
24
+ try {
25
+ // Cleanup directories older than 1 hour
26
+ if (Date.now() - info.createdAt > 60 * 60 * 1000) {
27
+ await fs.promises.rm(dir, { recursive: true, force: true });
28
+ tempDirectories.delete(dir);
29
+ console.log(`Cleaned up old temp directory: ${dir}`);
30
+ }
31
+ } catch (err) {
32
+ // Directory might be gone
33
+ tempDirectories.delete(dir);
34
+ }
35
+ }
36
+ }
37
+
38
+ // Setup periodic cleanup
39
+ setInterval(async () => {
40
+ try {
41
+ await cleanupTempDirectories();
42
+ } catch (err) {
43
+ console.error('Error during periodic cleanup:', err);
44
+ }
45
+ }, CLEANUP_INTERVAL_MS);
14
46
 
47
+ // Process a single chunk with streaming
15
48
  async function processChunk(inputPath, outputFileName, start, duration) {
16
49
  return new Promise((resolve, reject) => {
17
- ffmpeg(inputPath)
50
+ const command = ffmpeg(inputPath)
18
51
  .seekInput(start)
19
52
  .duration(duration)
20
53
  .format('mp3')
21
54
  .audioCodec('libmp3lame')
22
55
  .audioBitrate(128)
23
- .on('start', (cmd) => {
24
- console.log(`Started FFmpeg with command: ${cmd}`);
56
+ .on('start', () => {
57
+ console.log(`Processing chunk: ${start}s -> ${start + duration}s`);
25
58
  })
26
- .on('error', (err) => {
27
- console.error(`Error occurred while processing chunk:`, err);
59
+ .on('progress', (progress) => {
60
+ if (progress.percent) {
61
+ console.log(`Chunk progress: ${progress.percent}%`);
62
+ }
63
+ })
64
+ .on('error', (err, stdout, stderr) => {
65
+ console.error('FFmpeg error:', err.message);
66
+ if (stdout) console.log('FFmpeg stdout:', stdout);
67
+ if (stderr) console.error('FFmpeg stderr:', stderr);
28
68
  reject(err);
29
69
  })
30
70
  .on('end', () => {
31
- console.log(`Finished processing chunk`);
71
+ console.log(`Chunk complete: ${outputFileName}`);
32
72
  resolve(outputFileName);
33
- })
34
- .save(outputFileName);
73
+ });
74
+
75
+ // Use pipe() to handle streaming
76
+ command.pipe(fs.createWriteStream(outputFileName), { end: true });
35
77
  });
36
78
  }
37
79
 
38
80
  const generateUniqueFolderName = () => {
39
81
  const uniqueFolderName = uuidv4();
40
- const tempFolderPath = os.tmpdir(); // Get the system's temporary folder
41
- const uniqueOutputPath = path.join(tempFolderPath, uniqueFolderName);
42
- return uniqueOutputPath;
82
+ const tempFolderPath = os.tmpdir();
83
+ return path.join(tempFolderPath, uniqueFolderName);
43
84
  }
44
85
 
45
86
  async function downloadFile(url, outputPath) {
46
87
  try {
47
88
  let response;
48
89
  try {
49
- response = await axios.get(decodeURIComponent(url), { responseType: 'stream' });
90
+ response = await axios.get(decodeURIComponent(url), {
91
+ responseType: 'stream',
92
+ // Add timeout and maxContentLength
93
+ timeout: 30000,
94
+ maxContentLength: Infinity,
95
+ // Enable streaming download
96
+ decompress: true,
97
+ // Use a smaller chunk size for better memory usage
98
+ httpAgent: new http.Agent({ keepAlive: true }),
99
+ httpsAgent: new https.Agent({ keepAlive: true })
100
+ });
50
101
  } catch (error) {
51
- response = await axios.get(url, { responseType: 'stream' });
102
+ response = await axios.get(url, {
103
+ responseType: 'stream',
104
+ timeout: 30000,
105
+ maxContentLength: Infinity,
106
+ decompress: true,
107
+ httpAgent: new http.Agent({ keepAlive: true }),
108
+ httpsAgent: new https.Agent({ keepAlive: true })
109
+ });
52
110
  }
53
111
 
54
- // Make an HTTP request for the file
55
-
56
- // Create a writable file stream to save the file
57
- const fileStream = fs.createWriteStream(outputPath);
58
-
59
- // Pipe the response data into the file stream
60
- response.data.pipe(fileStream);
61
-
62
- // Wait for the file stream to finish writing
63
- await new Promise((resolve, reject) => {
64
- fileStream.on('finish', resolve);
65
- fileStream.on('error', reject);
66
- });
112
+ const writer = fs.createWriteStream(outputPath);
113
+
114
+ // Use pipeline for better error handling and memory management
115
+ await pipeline(response.data, writer);
67
116
 
68
- console.log(`Downloaded file saved to: ${outputPath}`);
117
+ if (!fs.existsSync(outputPath) || fs.statSync(outputPath).size === 0) {
118
+ throw new Error('Download failed or file is empty');
119
+ }
69
120
  } catch (error) {
70
- console.error(`Error downloading file from ${url}:`, error);
121
+ if (fs.existsSync(outputPath)) {
122
+ fs.unlinkSync(outputPath);
123
+ }
71
124
  throw error;
72
125
  }
73
126
  }
74
127
 
75
- // Split a media file into chunks of max 500 seconds
76
- async function splitMediaFile(inputPath, chunkDurationInSeconds = 500) {
128
+ async function splitMediaFile(inputPath, chunkDurationInSeconds = 500, requestId = uuidv4()) {
129
+ let tempPath = null;
130
+ let uniqueOutputPath = null;
131
+ let inputStream = null;
132
+
77
133
  try {
78
- // Create unique folder
79
- const uniqueOutputPath = generateUniqueFolderName();
134
+ uniqueOutputPath = generateUniqueFolderName();
80
135
  fs.mkdirSync(uniqueOutputPath, { recursive: true });
136
+
137
+ tempDirectories.set(uniqueOutputPath, {
138
+ createdAt: Date.now(),
139
+ requestId
140
+ });
81
141
 
82
- // Download the file if it's not a local file
142
+ // Handle URL downloads with streaming
83
143
  const isUrl = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i.test(inputPath);
84
144
  if (isUrl) {
85
- inputPath = ensureEncoded(inputPath);
86
- // Extract the original file name from the URL
87
- const urlObj = new URL(inputPath);
88
- const originalFileName = path.basename(urlObj.pathname);
89
- const maxLength = 200; // Set the maximum length for the filename
90
- let truncatedFileName = originalFileName;
91
- if (originalFileName.length > maxLength) {
92
- const extension = path.extname(originalFileName); // Preserve the file extension
93
- const basename = path.basename(originalFileName, extension); // Get the filename without the extension
94
- truncatedFileName = basename.substring(0, maxLength) + extension; // Truncate the filename and append the extension
95
- }
145
+ const urlObj = new URL(ensureEncoded(inputPath));
146
+ const originalFileName = path.basename(urlObj.pathname) || 'downloaded_file';
147
+ tempPath = path.join(uniqueOutputPath, originalFileName);
148
+ console.log('Downloading file to:', tempPath);
149
+ await downloadFile(inputPath, tempPath);
150
+ inputPath = tempPath;
151
+ }
96
152
 
97
- // Use the original-truncated file name when saving the downloaded file
98
- const downloadPath = path.join(uniqueOutputPath, truncatedFileName);
99
- await downloadFile(inputPath, downloadPath);
100
- inputPath = downloadPath;
153
+ inputPath = path.resolve(inputPath);
154
+ if (!fs.existsSync(inputPath)) {
155
+ throw new Error(`Input file not found: ${inputPath}`);
101
156
  }
102
157
 
158
+ // Use a larger chunk size for better throughput while still managing memory
159
+ inputStream = fs.createReadStream(inputPath, {
160
+ highWaterMark: 4 * 1024 * 1024, // 4MB chunks
161
+ autoClose: true
162
+ });
163
+
164
+ console.log('Probing file:', inputPath);
103
165
  const metadata = await ffmpegProbe(inputPath);
166
+ if (!metadata?.format?.duration) {
167
+ throw new Error('Invalid media file or unable to determine duration');
168
+ }
169
+
104
170
  const duration = metadata.format.duration;
105
171
  const numChunks = Math.ceil((duration - 1) / chunkDurationInSeconds);
106
-
107
- const chunkPromises = [];
108
- const chunkOffsets = [];
109
-
110
- for (let i = 0; i < numChunks; i++) {
111
- const outputFileName = path.join(uniqueOutputPath, `chunk-${i + 1}-${path.parse(inputPath).name}.mp3`);
112
- const offset = i * chunkDurationInSeconds;
113
-
114
- const chunkPromise = processChunk(inputPath, outputFileName, offset, chunkDurationInSeconds);
172
+ console.log(`Processing ${numChunks} chunks of ${chunkDurationInSeconds} seconds each`);
173
+
174
+ const chunkResults = new Array(numChunks); // Pre-allocate array to maintain order
175
+ const chunkOffsets = new Array(numChunks); // Pre-allocate offsets array
176
+
177
+ // Process chunks in parallel with a concurrency limit
178
+ const CONCURRENT_CHUNKS = 3; // Process 3 chunks at a time
179
+ for (let i = 0; i < numChunks; i += CONCURRENT_CHUNKS) {
180
+ const chunkBatch = [];
181
+ for (let j = 0; j < CONCURRENT_CHUNKS && i + j < numChunks; j++) {
182
+ const chunkIndex = i + j;
183
+ const outputFileName = path.join(uniqueOutputPath, `chunk-${chunkIndex + 1}-${path.parse(inputPath).name}.mp3`);
184
+ const offset = chunkIndex * chunkDurationInSeconds;
185
+
186
+ chunkBatch.push(processChunk(inputPath, outputFileName, offset, chunkDurationInSeconds)
187
+ .then(result => {
188
+ chunkResults[chunkIndex] = result; // Store in correct position
189
+ chunkOffsets[chunkIndex] = offset; // Store offset in correct position
190
+ console.log(`Completed chunk ${chunkIndex + 1}/${numChunks}`);
191
+ return result;
192
+ })
193
+ .catch(error => {
194
+ console.error(`Failed to process chunk ${chunkIndex + 1}:`, error);
195
+ return null;
196
+ }));
197
+ }
115
198
 
116
- chunkPromises.push(chunkPromise);
117
- chunkOffsets.push(offset);
199
+ // Wait for the current batch to complete before starting the next
200
+ await Promise.all(chunkBatch);
118
201
  }
119
202
 
120
- return { chunkPromises, chunkOffsets, uniqueOutputPath };
121
- } catch (err) {
122
- const msg = `Error processing media file, check if the file is a valid media file or is accessible`;
123
- console.error(msg, err);
124
- throw new Error(msg);
125
- }
126
- }
127
-
128
- const ytdlDownload = async (url, filename, video = false) => {
129
- return new Promise((resolve, reject) => {
130
- const videoOptions = video
131
- ? { filter: 'audioandvideo' } // audio and video
132
- : { quality: 'highestaudio' }; // audio only
133
-
134
- const encodedUrl = encodeURI(url);
135
- const videoStream = ytdl(encodedUrl, videoOptions);
136
- let lastLoggedTime = Date.now();
203
+ // Filter out any failed chunks
204
+ const validChunks = chunkResults.filter(Boolean);
205
+ const validOffsets = chunkOffsets.filter((_, index) => chunkResults[index]);
137
206
 
138
- videoStream.on('error', (error) => {
139
- reject(error);
140
- });
207
+ if (validChunks.length === 0) {
208
+ throw new Error('No chunks were successfully processed');
209
+ }
141
210
 
142
- videoStream.on('progress', (chunkLength, downloaded, total) => {
143
- const currentTime = Date.now();
144
- if (currentTime - lastLoggedTime >= 2000) { // Log every 2 seconds
145
- const percent = downloaded / total;
146
- console.log(`${(percent * 100).toFixed(2)}% downloaded ${url}`);
147
- lastLoggedTime = currentTime;
211
+ return { chunkPromises: validChunks, chunkOffsets: validOffsets, uniqueOutputPath };
212
+ } catch (err) {
213
+ if (uniqueOutputPath && fs.existsSync(uniqueOutputPath)) {
214
+ try {
215
+ fs.rmSync(uniqueOutputPath, { recursive: true, force: true });
216
+ tempDirectories.delete(uniqueOutputPath);
217
+ } catch (cleanupErr) {
218
+ console.error('Error during cleanup:', cleanupErr);
148
219
  }
149
- });
150
-
151
- videoStream.pipe(fs.createWriteStream(filename))
152
- .on('finish', () => {
153
- resolve();
154
- })
155
- .on('error', (error) => {
156
- reject(error);
157
- });
158
- });
159
- };
160
-
161
- async function processYoutubeUrl(url, video=false) {
162
- try {
163
- const outputFormat = video ? '.mp4' : '.mp3';
164
- const outputFileName = path.join(os.tmpdir(), `${uuidv4()}${outputFormat}`);
165
- await ytdlDownload(url, outputFileName, video);
166
- return outputFileName;
167
- } catch (e) {
168
- console.log(e);
169
- throw new Error(`Error processing YouTube video, YouTube downloader might be outdated or blocked. ${e.message}`);
220
+ }
221
+ console.error('Error in splitMediaFile:', err);
222
+ throw new Error(`Error processing media file: ${err.message}`);
223
+ } finally {
224
+ if (inputStream) {
225
+ try {
226
+ inputStream.destroy();
227
+ } catch (err) {
228
+ console.error('Error closing input stream:', err);
229
+ }
230
+ }
170
231
  }
171
232
  }
172
233
 
173
234
  export {
174
- splitMediaFile, processYoutubeUrl, downloadFile
235
+ splitMediaFile,
236
+ downloadFile
175
237
  };
@@ -1,11 +1,8 @@
1
1
  import fs from 'fs';
2
+ import { ACCEPTED_MIME_TYPES } from './constants.js';
3
+ import path from 'path';
2
4
 
3
- function isValidYoutubeUrl(url) {
4
- const regex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.?be)\/.+$/;
5
- return regex.test(url);
6
- }
7
-
8
- async function deleteTempPath(path) {
5
+ export async function deleteTempPath(path) {
9
6
  try {
10
7
  if (!path) {
11
8
  console.log('Temporary path is not defined.');
@@ -28,18 +25,43 @@ async function deleteTempPath(path) {
28
25
  }
29
26
  }
30
27
 
31
- function ensureEncoded(url) {
28
+ // Get the first extension for a given mime type
29
+ export function getExtensionForMimeType(mimeType) {
30
+ if (!mimeType) return '';
31
+ const cleanMimeType = mimeType.split(';')[0].trim();
32
+ const extensions = ACCEPTED_MIME_TYPES[cleanMimeType];
33
+ return extensions ? extensions[0] : '';
34
+ }
35
+
36
+ // Ensure a filename has the correct extension based on its mime type
37
+ export function ensureFileExtension(filename, mimeType) {
38
+ if (!mimeType) return filename;
39
+
40
+ const extension = getExtensionForMimeType(mimeType);
41
+ if (!extension) return filename;
42
+
43
+ // If filename already has this extension, return as is
44
+ if (filename.toLowerCase().endsWith(extension)) {
45
+ return filename;
46
+ }
47
+
48
+ // Get the current extension if any
49
+ const currentExt = path.extname(filename);
50
+
51
+ // If there's no current extension, just append the new one
52
+ if (!currentExt) {
53
+ return `${filename}${extension}`;
54
+ }
55
+
56
+ // Replace the current extension with the new one
57
+ return filename.slice(0, -currentExt.length) + extension;
58
+ }
59
+
60
+ export function ensureEncoded(url) {
32
61
  try {
33
- const decodedUrl = decodeURI(url);
34
- if (decodedUrl === url) {
35
- return encodeURI(url);
36
- }
37
- return url;
38
- } catch (e) {
62
+ return decodeURIComponent(url) !== url ? url : encodeURI(url);
63
+ } catch (error) {
64
+ console.error('Error encoding URL:', error);
39
65
  return url;
40
66
  }
41
67
  }
42
-
43
- export {
44
- isValidYoutubeUrl, deleteTempPath, ensureEncoded
45
- }