@aj-archipelago/cortex 1.1.20 → 1.1.22
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/default.example.json +84 -0
- package/config.js +17 -4
- package/helper-apps/cortex-file-handler/blobHandler.js +144 -100
- package/helper-apps/cortex-file-handler/fileChunker.js +13 -8
- package/helper-apps/cortex-file-handler/index.js +56 -9
- package/lib/pathwayTools.js +7 -1
- package/lib/requestExecutor.js +4 -4
- package/lib/util.js +163 -1
- package/package.json +2 -1
- package/pathways/categorize.js +23 -0
- package/pathways/chat.js +1 -1
- package/pathways/chat_code.js +19 -0
- package/pathways/chat_context.js +19 -0
- package/pathways/chat_jarvis.js +19 -0
- package/pathways/chat_persist.js +23 -0
- package/pathways/code_review.js +17 -0
- package/pathways/cognitive_delete.js +2 -1
- package/pathways/cognitive_insert.js +1 -0
- package/pathways/cognitive_search.js +1 -0
- package/pathways/embeddings.js +1 -1
- package/pathways/expand_story.js +12 -0
- package/pathways/format_paragraph_turbo.js +16 -0
- package/pathways/format_summarization.js +21 -0
- package/pathways/gemini_15_vision.js +20 -0
- package/pathways/gemini_vision.js +20 -0
- package/pathways/grammar.js +30 -0
- package/pathways/hashtags.js +19 -0
- package/pathways/headline.js +43 -0
- package/pathways/headline_custom.js +169 -0
- package/pathways/highlights.js +22 -0
- package/pathways/image.js +2 -1
- package/pathways/index.js +109 -17
- package/pathways/jira_story.js +18 -0
- package/pathways/keywords.js +4 -0
- package/pathways/language.js +17 -6
- package/pathways/locations.js +93 -0
- package/pathways/quotes.js +19 -0
- package/pathways/rag.js +207 -0
- package/pathways/rag_jarvis.js +254 -0
- package/pathways/rag_search_helper.js +21 -0
- package/pathways/readme.js +18 -0
- package/pathways/release_notes.js +16 -0
- package/pathways/remove_content.js +31 -0
- package/pathways/retrieval.js +23 -0
- package/pathways/run_claude35_sonnet.js +21 -0
- package/pathways/run_claude3_haiku.js +20 -0
- package/pathways/run_gpt35turbo.js +20 -0
- package/pathways/run_gpt4.js +20 -0
- package/pathways/run_gpt4_32.js +20 -0
- package/pathways/select_extension.js +6 -0
- package/pathways/select_services.js +10 -0
- package/pathways/spelling.js +3 -0
- package/pathways/story_angles.js +13 -0
- package/pathways/styleguide/styleguide.js +221 -0
- package/pathways/styleguidemulti.js +127 -0
- package/pathways/subhead.js +48 -0
- package/pathways/summarize_turbo.js +98 -0
- package/pathways/summary.js +31 -12
- package/pathways/sys_claude_35_sonnet.js +19 -0
- package/pathways/sys_claude_3_haiku.js +19 -0
- package/pathways/sys_google_chat.js +19 -0
- package/pathways/sys_google_code_chat.js +19 -0
- package/pathways/sys_google_gemini_chat.js +23 -0
- package/pathways/sys_openai_chat.js +2 -2
- package/pathways/sys_openai_chat_16.js +19 -0
- package/pathways/sys_openai_chat_gpt4.js +19 -0
- package/pathways/sys_openai_chat_gpt4_32.js +19 -0
- package/pathways/sys_openai_chat_gpt4_turbo.js +19 -0
- package/pathways/tags.js +25 -0
- package/pathways/taxonomy.js +135 -0
- package/pathways/timeline.js +51 -0
- package/pathways/topics.js +25 -0
- package/pathways/topics_sentiment.js +20 -0
- package/pathways/transcribe.js +2 -4
- package/pathways/transcribe_neuralspace.js +18 -0
- package/pathways/translate.js +10 -12
- package/pathways/translate_azure.js +13 -0
- package/pathways/translate_context.js +21 -0
- package/pathways/translate_gpt4.js +19 -0
- package/pathways/translate_gpt4_turbo.js +19 -0
- package/pathways/translate_turbo.js +19 -0
- package/pathways/vision.js +9 -7
- package/server/modelExecutor.js +4 -0
- package/server/pathwayResolver.js +19 -1
- package/server/plugins/azureCognitivePlugin.js +10 -1
- package/server/plugins/claude3VertexPlugin.js +2 -1
- package/server/plugins/gemini15ChatPlugin.js +8 -3
- package/server/plugins/gemini15VisionPlugin.js +19 -3
- package/server/plugins/geminiChatPlugin.js +1 -1
- package/server/plugins/geminiVisionPlugin.js +2 -3
- package/server/plugins/neuralSpacePlugin.js +252 -0
- package/server/plugins/openAiVisionPlugin.js +32 -13
- package/server/plugins/openAiWhisperPlugin.js +5 -152
- package/server/plugins/palmChatPlugin.js +1 -1
- package/server/resolver.js +3 -4
- package/server/typeDef.js +1 -0
- package/tests/claude3VertexPlugin.test.js +214 -0
- package/tests/main.test.js +2 -2
- package/tests/mocks.js +2 -0
- package/tests/openAiChatPlugin.test.js +4 -0
- package/tests/vision.test.js +0 -34
|
@@ -12,6 +12,62 @@
|
|
|
12
12
|
"requestsPerSecond": 10,
|
|
13
13
|
"maxTokenLength": 2000
|
|
14
14
|
},
|
|
15
|
+
|
|
16
|
+
"gemini-pro-chat": {
|
|
17
|
+
"type": "GEMINI-CHAT",
|
|
18
|
+
"url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/google/models/gemini-pro:streamGenerateContent",
|
|
19
|
+
"headers": {
|
|
20
|
+
"Content-Type": "application/json"
|
|
21
|
+
},
|
|
22
|
+
"requestsPerSecond": 10,
|
|
23
|
+
"maxTokenLength": 32768,
|
|
24
|
+
"maxReturnTokens": 8192,
|
|
25
|
+
"supportsStreaming": true
|
|
26
|
+
},
|
|
27
|
+
"gemini-pro-vision": {
|
|
28
|
+
"type": "GEMINI-VISION",
|
|
29
|
+
"url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/google/models/gemini-pro-vision:streamGenerateContent",
|
|
30
|
+
"headers": {
|
|
31
|
+
"Content-Type": "application/json"
|
|
32
|
+
},
|
|
33
|
+
"requestsPerSecond": 10,
|
|
34
|
+
"maxTokenLength": 32768,
|
|
35
|
+
"maxReturnTokens": 2048,
|
|
36
|
+
"supportsStreaming": true
|
|
37
|
+
},
|
|
38
|
+
"gemini-pro-15-vision": {
|
|
39
|
+
"type": "GEMINI-VISION",
|
|
40
|
+
"url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/google/models/gemini-1.5-pro-preview-0215:streamGenerateContent",
|
|
41
|
+
"headers": {
|
|
42
|
+
"Content-Type": "application/json"
|
|
43
|
+
},
|
|
44
|
+
"requestsPerSecond": 10,
|
|
45
|
+
"maxTokenLength": 1048576,
|
|
46
|
+
"maxReturnTokens": 2048,
|
|
47
|
+
"supportsStreaming": true
|
|
48
|
+
},
|
|
49
|
+
"claude-3-haiku-vertex": {
|
|
50
|
+
"type": "CLAUDE-3-VERTEX",
|
|
51
|
+
"url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/anthropic/models/claude-3-haiku@20240307",
|
|
52
|
+
"headers": {
|
|
53
|
+
"Content-Type": "application/json"
|
|
54
|
+
},
|
|
55
|
+
"requestsPerSecond": 10,
|
|
56
|
+
"maxTokenLength": 200000,
|
|
57
|
+
"maxReturnTokens": 2048,
|
|
58
|
+
"supportsStreaming": true
|
|
59
|
+
},
|
|
60
|
+
"claude-35-sonnet-vertex": {
|
|
61
|
+
"type": "CLAUDE-3-VERTEX",
|
|
62
|
+
"url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/anthropic/models/claude-3-5-sonnet@20240229",
|
|
63
|
+
"headers": {
|
|
64
|
+
"Content-Type": "application/json"
|
|
65
|
+
},
|
|
66
|
+
"requestsPerSecond": 10,
|
|
67
|
+
"maxTokenLength": 200000,
|
|
68
|
+
"maxReturnTokens": 2048,
|
|
69
|
+
"supportsStreaming": true
|
|
70
|
+
},
|
|
15
71
|
"oai-gpturbo": {
|
|
16
72
|
"type": "OPENAI-CHAT",
|
|
17
73
|
"url": "https://api.openai.com/v1/chat/completions",
|
|
@@ -38,6 +94,34 @@
|
|
|
38
94
|
"requestsPerSecond": 10,
|
|
39
95
|
"maxTokenLength": 8192
|
|
40
96
|
},
|
|
97
|
+
"oai-gpt4-32": {
|
|
98
|
+
"type": "OPENAI-CHAT",
|
|
99
|
+
"url": "https://api.openai.com/v1/chat/completions",
|
|
100
|
+
"headers": {
|
|
101
|
+
"Authorization": "Bearer {{OPENAI_API_KEY}}",
|
|
102
|
+
"Content-Type": "application/json"
|
|
103
|
+
},
|
|
104
|
+
"params": {
|
|
105
|
+
"model": "gpt-4-32"
|
|
106
|
+
},
|
|
107
|
+
"requestsPerSecond": 10,
|
|
108
|
+
"maxTokenLength": 32768
|
|
109
|
+
},
|
|
110
|
+
"oai-gpt4o": {
|
|
111
|
+
"type": "OPENAI-VISION",
|
|
112
|
+
"url": "https://api.openai.com/v1/chat/completions",
|
|
113
|
+
"headers": {
|
|
114
|
+
"Authorization": "Bearer {{OPENAI_API_KEY}}",
|
|
115
|
+
"Content-Type": "application/json"
|
|
116
|
+
},
|
|
117
|
+
"params": {
|
|
118
|
+
"model": "gpt-4o"
|
|
119
|
+
},
|
|
120
|
+
"requestsPerSecond": 10,
|
|
121
|
+
"maxTokenLength": 131072,
|
|
122
|
+
"maxReturnTokens": 4096,
|
|
123
|
+
"supportsStreaming": true
|
|
124
|
+
},
|
|
41
125
|
"palm-text": {
|
|
42
126
|
"type": "PALM-COMPLETION",
|
|
43
127
|
"url": "https://us-central1-aiplatform.googleapis.com/v1/projects/project-id/locations/us-central1/publishers/google/models/text-bison@001:predict",
|
package/config.js
CHANGED
|
@@ -111,6 +111,13 @@ var config = convict({
|
|
|
111
111
|
"model": "whisper-1"
|
|
112
112
|
},
|
|
113
113
|
},
|
|
114
|
+
"neuralspace": {
|
|
115
|
+
"type": "NEURALSPACE",
|
|
116
|
+
"url": "https://voice.neuralspace.ai/api/v2/jobs",
|
|
117
|
+
"headers": {
|
|
118
|
+
"Authorization": "{{NEURALSPACE_API_KEY}}",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
114
121
|
"azure-cognitive": {
|
|
115
122
|
"type": "AZURE-COGNITIVE",
|
|
116
123
|
"url": "{{{AZURE_COGNITIVE_API_URL}}}",
|
|
@@ -132,7 +139,7 @@ var config = convict({
|
|
|
132
139
|
},
|
|
133
140
|
"maxTokenLength": 8192,
|
|
134
141
|
},
|
|
135
|
-
"oai-
|
|
142
|
+
"oai-gpt4o": {
|
|
136
143
|
"type": "OPENAI-VISION",
|
|
137
144
|
"url": "https://api.openai.com/v1/chat/completions",
|
|
138
145
|
"headers": {
|
|
@@ -140,10 +147,11 @@ var config = convict({
|
|
|
140
147
|
"Content-Type": "application/json"
|
|
141
148
|
},
|
|
142
149
|
"params": {
|
|
143
|
-
"model": "gpt-
|
|
150
|
+
"model": "gpt-4o"
|
|
144
151
|
},
|
|
145
|
-
"requestsPerSecond":
|
|
146
|
-
"maxTokenLength":
|
|
152
|
+
"requestsPerSecond": 50,
|
|
153
|
+
"maxTokenLength": 131072,
|
|
154
|
+
"maxReturnTokens": 4096,
|
|
147
155
|
"supportsStreaming": true
|
|
148
156
|
},
|
|
149
157
|
"azure-bing": {
|
|
@@ -222,6 +230,11 @@ var config = convict({
|
|
|
222
230
|
default: 0,
|
|
223
231
|
env: 'SUBSCRIPTION_KEEP_ALIVE'
|
|
224
232
|
},
|
|
233
|
+
neuralSpaceApiKey: {
|
|
234
|
+
format: String,
|
|
235
|
+
default: null,
|
|
236
|
+
env: 'NEURALSPACE_API_KEY'
|
|
237
|
+
},
|
|
225
238
|
});
|
|
226
239
|
|
|
227
240
|
// Read in environment variables and set up service configuration
|
|
@@ -36,6 +36,15 @@ const VIDEO_EXTENSIONS = [
|
|
|
36
36
|
".mkv",
|
|
37
37
|
];
|
|
38
38
|
|
|
39
|
+
const AUDIO_EXTENSIONS = [
|
|
40
|
+
".mp3",
|
|
41
|
+
".wav",
|
|
42
|
+
".ogg",
|
|
43
|
+
".flac",
|
|
44
|
+
".aac",
|
|
45
|
+
".aiff",
|
|
46
|
+
];
|
|
47
|
+
|
|
39
48
|
function isBase64(str) {
|
|
40
49
|
try {
|
|
41
50
|
return btoa(atob(str)) == str;
|
|
@@ -76,6 +85,33 @@ if (!GCP_PROJECT_ID || !GCP_SERVICE_ACCOUNT) {
|
|
|
76
85
|
|
|
77
86
|
const GCS_BUCKETNAME = process.env.GCS_BUCKETNAME || "cortextempfiles";
|
|
78
87
|
|
|
88
|
+
|
|
89
|
+
async function gcsUrlExists(url, defaultReturn = true) {
|
|
90
|
+
try {
|
|
91
|
+
if(!url) {
|
|
92
|
+
return defaultReturn; // Cannot check return
|
|
93
|
+
}
|
|
94
|
+
if (!gcs) {
|
|
95
|
+
console.warn('GCS environment variables are not set. Unable to check if URL exists in GCS.');
|
|
96
|
+
return defaultReturn; // Cannot check return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const urlParts = url.replace('gs://', '').split('/');
|
|
100
|
+
const bucketName = urlParts[0];
|
|
101
|
+
const fileName = urlParts.slice(1).join('/');
|
|
102
|
+
|
|
103
|
+
const bucket = gcs.bucket(bucketName);
|
|
104
|
+
const file = bucket.file(fileName);
|
|
105
|
+
|
|
106
|
+
const [exists] = await file.exists();
|
|
107
|
+
|
|
108
|
+
return exists;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('Error checking if GCS URL exists:', error);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
79
115
|
const getBlobClient = async () => {
|
|
80
116
|
const connectionString = process.env.AZURE_STORAGE_CONNECTION_STRING;
|
|
81
117
|
const containerName = process.env.AZURE_STORAGE_CONTAINER_NAME;
|
|
@@ -101,7 +137,7 @@ const getBlobClient = async () => {
|
|
|
101
137
|
async function saveFileToBlob(chunkPath, requestId) {
|
|
102
138
|
const { containerClient } = await getBlobClient();
|
|
103
139
|
// Use the filename with a UUID as the blob name
|
|
104
|
-
const blobName = `${requestId}/${uuidv4()}_${path.basename(chunkPath)}`;
|
|
140
|
+
const blobName = `${requestId}/${uuidv4()}_${encodeURIComponent(path.basename(chunkPath))}`;
|
|
105
141
|
|
|
106
142
|
// Create a read stream for the chunk file
|
|
107
143
|
const fileStream = fs.createReadStream(chunkPath);
|
|
@@ -135,131 +171,139 @@ async function deleteBlob(requestId) {
|
|
|
135
171
|
return result;
|
|
136
172
|
}
|
|
137
173
|
|
|
138
|
-
async function uploadBlob(
|
|
139
|
-
context,
|
|
140
|
-
req,
|
|
141
|
-
saveToLocal = false,
|
|
142
|
-
useGoogle = false
|
|
143
|
-
) {
|
|
174
|
+
async function uploadBlob(context, req, saveToLocal = false, useGoogle = false, filePath=null) {
|
|
144
175
|
return new Promise((resolve, reject) => {
|
|
145
176
|
try {
|
|
146
|
-
const busboy = Busboy({ headers: req.headers });
|
|
147
177
|
let requestId = uuidv4();
|
|
148
178
|
let body = {};
|
|
149
179
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
// If filePath is given, we are dealing with local file and not form-data
|
|
181
|
+
if (filePath) {
|
|
182
|
+
const file = fs.createReadStream(filePath);
|
|
183
|
+
const filename = path.basename(filePath);
|
|
184
|
+
uploadFile(context, requestId, body, saveToLocal, useGoogle, file, filename, resolve)
|
|
185
|
+
} else {
|
|
186
|
+
// Otherwise, continue working with form-data
|
|
187
|
+
const busboy = Busboy({ headers: req.headers });
|
|
188
|
+
|
|
189
|
+
busboy.on("field", (fieldname, value) => {
|
|
190
|
+
if (fieldname === "requestId") {
|
|
191
|
+
requestId = value;
|
|
192
|
+
} else if (fieldname === "useGoogle") {
|
|
193
|
+
useGoogle = value;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
busboy.on("file", async (fieldname, file, filename) => {
|
|
198
|
+
uploadFile(context, requestId, body, saveToLocal, useGoogle, file, filename?.filename || filename, resolve)
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
busboy.on("error", (error) => {
|
|
202
|
+
context.log.error("Error processing file upload:", error);
|
|
203
|
+
context.res = {
|
|
204
|
+
status: 500,
|
|
205
|
+
body: "Error processing file upload.",
|
|
206
|
+
};
|
|
207
|
+
reject(error); // Reject the promise
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
req.pipe(busboy);
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
context.log.error("Error processing file upload:", error);
|
|
214
|
+
context.res = {
|
|
215
|
+
status: 500,
|
|
216
|
+
body: "Error processing file upload.",
|
|
217
|
+
};
|
|
218
|
+
reject(error); // Reject the promise
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
165
222
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
223
|
+
async function uploadFile(context, requestId, body, saveToLocal, useGoogle, file, filename, resolve) {
|
|
224
|
+
// do not use Google if the file is not an image or video
|
|
225
|
+
const ext = path.extname(filename).toLowerCase();
|
|
226
|
+
const canUseGoogle = IMAGE_EXTENSIONS.includes(ext) || VIDEO_EXTENSIONS.includes(ext) || AUDIO_EXTENSIONS.includes(ext);
|
|
227
|
+
if (!canUseGoogle) {
|
|
228
|
+
useGoogle = false;
|
|
229
|
+
}
|
|
171
230
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
231
|
+
// check if useGoogle is set but no gcs and warn
|
|
232
|
+
if (useGoogle && useGoogle !== "false" && !gcs) {
|
|
233
|
+
context.log.warn("Google Cloud Storage is not initialized reverting google upload ");
|
|
234
|
+
useGoogle = false;
|
|
235
|
+
}
|
|
176
236
|
|
|
177
|
-
|
|
178
|
-
const destinationPath = `${localPath}/${filename}`;
|
|
237
|
+
const encodedFilename = encodeURIComponent(`${requestId || uuidv4()}_${filename}`);
|
|
179
238
|
|
|
180
|
-
await pipeline(file, fs.createWriteStream(destinationPath));
|
|
181
239
|
|
|
182
|
-
|
|
183
|
-
|
|
240
|
+
if (saveToLocal) {
|
|
241
|
+
// create the target folder if it doesn't exist
|
|
242
|
+
const localPath = join(publicFolder, requestId);
|
|
243
|
+
fs.mkdirSync(localPath, { recursive: true });
|
|
184
244
|
|
|
185
|
-
|
|
245
|
+
const destinationPath = `${localPath}/${encodedFilename}`;
|
|
186
246
|
|
|
187
|
-
|
|
247
|
+
await pipeline(file, fs.createWriteStream(destinationPath));
|
|
188
248
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const filename = `${requestId}/${uuidv4()}_${info.filename}`;
|
|
192
|
-
const { containerClient } = await getBlobClient();
|
|
249
|
+
const message = `File '${encodedFilename}' saved to folder successfully.`;
|
|
250
|
+
context.log(message);
|
|
193
251
|
|
|
194
|
-
|
|
195
|
-
const options = {};
|
|
196
|
-
if (contentType) {
|
|
197
|
-
options.blobHTTPHeaders = { blobContentType: contentType };
|
|
198
|
-
}
|
|
252
|
+
const url = `http://${ipAddress}:${port}/files/${requestId}/${encodedFilename}`;
|
|
199
253
|
|
|
200
|
-
|
|
254
|
+
body = { message, url };
|
|
201
255
|
|
|
202
|
-
|
|
203
|
-
|
|
256
|
+
resolve(body); // Resolve the promise
|
|
257
|
+
} else {
|
|
258
|
+
const { containerClient } = await getBlobClient();
|
|
204
259
|
|
|
205
|
-
|
|
260
|
+
const contentType = mime.lookup(encodedFilename); // content type based on file extension
|
|
261
|
+
const options = {};
|
|
262
|
+
if (contentType) {
|
|
263
|
+
options.blobHTTPHeaders = { blobContentType: contentType };
|
|
264
|
+
}
|
|
206
265
|
|
|
207
|
-
|
|
208
|
-
const url = blockBlobClient.url;
|
|
209
|
-
context.log(message);
|
|
210
|
-
body = { message, url };
|
|
211
|
-
}
|
|
266
|
+
const blockBlobClient = containerClient.getBlockBlobClient(encodedFilename);
|
|
212
267
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
body,
|
|
216
|
-
};
|
|
268
|
+
const passThroughStream = new PassThrough();
|
|
269
|
+
file.pipe(passThroughStream);
|
|
217
270
|
|
|
218
|
-
|
|
219
|
-
const { url } = body;
|
|
220
|
-
const filename = `${requestId}/${uuidv4()}_${info.filename}`;
|
|
221
|
-
const gcsFile = gcs.bucket(GCS_BUCKETNAME).file(filename);
|
|
222
|
-
const writeStream = gcsFile.createWriteStream();
|
|
271
|
+
await blockBlobClient.uploadStream(passThroughStream, undefined, undefined, options);
|
|
223
272
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
273
|
+
const message = `File '${encodedFilename}' uploaded successfully.`;
|
|
274
|
+
const url = blockBlobClient.url;
|
|
275
|
+
context.log(message);
|
|
276
|
+
body = { message, url };
|
|
277
|
+
}
|
|
229
278
|
|
|
230
|
-
|
|
231
|
-
|
|
279
|
+
context.res = {
|
|
280
|
+
status: 200,
|
|
281
|
+
body,
|
|
282
|
+
};
|
|
232
283
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
284
|
+
if (useGoogle && useGoogle !== "false") {
|
|
285
|
+
const { url } = body;
|
|
286
|
+
const gcsFile = gcs.bucket(GCS_BUCKETNAME).file(encodedFilename);
|
|
287
|
+
const writeStream = gcsFile.createWriteStream();
|
|
237
288
|
|
|
238
|
-
|
|
239
|
-
|
|
289
|
+
const response = await axios({
|
|
290
|
+
method: "get",
|
|
291
|
+
url: url,
|
|
292
|
+
responseType: "stream",
|
|
293
|
+
});
|
|
240
294
|
|
|
241
|
-
|
|
242
|
-
|
|
295
|
+
// pipe the Axios response stream directly into the GCS Write Stream
|
|
296
|
+
response.data.pipe(writeStream);
|
|
243
297
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
body: "Error processing file upload.",
|
|
249
|
-
};
|
|
250
|
-
reject(error); // Reject the promise
|
|
251
|
-
});
|
|
298
|
+
await new Promise((resolve, reject) => {
|
|
299
|
+
writeStream.on("finish", resolve);
|
|
300
|
+
writeStream.on("error", reject);
|
|
301
|
+
});
|
|
252
302
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
status: 500,
|
|
258
|
-
body: "Error processing file upload.",
|
|
259
|
-
};
|
|
260
|
-
reject(error); // Reject the promise
|
|
261
|
-
}
|
|
262
|
-
});
|
|
303
|
+
body.gcs = `gs://${GCS_BUCKETNAME}/${encodedFilename}`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
resolve(body); // Resolve the promise
|
|
263
307
|
}
|
|
264
308
|
|
|
265
309
|
// Function to delete files that haven't been used in more than a month
|
|
@@ -348,4 +392,4 @@ async function cleanupGCS(urls=null) {
|
|
|
348
392
|
return cleanedURLs;
|
|
349
393
|
}
|
|
350
394
|
|
|
351
|
-
export { saveFileToBlob, deleteBlob, uploadBlob, cleanup, cleanupGCS };
|
|
395
|
+
export { saveFileToBlob, deleteBlob, uploadBlob, cleanup, cleanupGCS, gcsUrlExists };
|
|
@@ -112,16 +112,20 @@ async function splitMediaFile(inputPath, chunkDurationInSeconds = 500) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
const ytdlDownload = async (url, filename) => {
|
|
115
|
+
const ytdlDownload = async (url, filename, video = false) => {
|
|
116
116
|
return new Promise((resolve, reject) => {
|
|
117
|
-
const
|
|
117
|
+
const videoOptions = video
|
|
118
|
+
? { filter: 'audioandvideo' } // audio and video
|
|
119
|
+
: { quality: 'highestaudio' }; // audio only
|
|
120
|
+
|
|
121
|
+
const videoStream = ytdl(url, videoOptions);
|
|
118
122
|
let lastLoggedTime = Date.now();
|
|
119
123
|
|
|
120
|
-
|
|
124
|
+
videoStream.on('error', (error) => {
|
|
121
125
|
reject(error);
|
|
122
126
|
});
|
|
123
127
|
|
|
124
|
-
|
|
128
|
+
videoStream.on('progress', (chunkLength, downloaded, total) => {
|
|
125
129
|
const currentTime = Date.now();
|
|
126
130
|
if (currentTime - lastLoggedTime >= 2000) { // Log every 2 seconds
|
|
127
131
|
const percent = downloaded / total;
|
|
@@ -130,7 +134,7 @@ const ytdlDownload = async (url, filename) => {
|
|
|
130
134
|
}
|
|
131
135
|
});
|
|
132
136
|
|
|
133
|
-
|
|
137
|
+
videoStream.pipe(fs.createWriteStream(filename))
|
|
134
138
|
.on('finish', () => {
|
|
135
139
|
resolve();
|
|
136
140
|
})
|
|
@@ -140,10 +144,11 @@ const ytdlDownload = async (url, filename) => {
|
|
|
140
144
|
});
|
|
141
145
|
};
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
async function processYoutubeUrl(url, video=false) {
|
|
144
148
|
try {
|
|
145
|
-
const
|
|
146
|
-
|
|
149
|
+
const outputFormat = video ? '.mp4' : '.mp3';
|
|
150
|
+
const outputFileName = path.join(os.tmpdir(), `${uuidv4()}${outputFormat}`);
|
|
151
|
+
await ytdlDownload(url, outputFileName, video);
|
|
147
152
|
return outputFileName;
|
|
148
153
|
} catch (e) {
|
|
149
154
|
console.log(e);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { downloadFile, processYoutubeUrl, splitMediaFile } from './fileChunker.js';
|
|
2
|
-
import { saveFileToBlob, deleteBlob, uploadBlob, cleanup, cleanupGCS } from './blobHandler.js';
|
|
2
|
+
import { saveFileToBlob, deleteBlob, uploadBlob, cleanup, cleanupGCS, gcsUrlExists } from './blobHandler.js';
|
|
3
3
|
import { cleanupRedisFileStoreMap, getFileStoreMap, publishRequestProgress, removeFromFileStoreMap, setFileStoreMap } from './redis.js';
|
|
4
4
|
import { deleteTempPath, ensureEncoded, isValidYoutubeUrl } from './helper.js';
|
|
5
5
|
import { moveFileToPublicFolder, deleteFolder, cleanupLocal } from './localFileHandler.js';
|
|
@@ -10,6 +10,10 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
10
10
|
import fs from 'fs';
|
|
11
11
|
import http from 'http';
|
|
12
12
|
import https from 'https';
|
|
13
|
+
import axios from "axios";
|
|
14
|
+
import { pipeline } from "stream";
|
|
15
|
+
import { promisify } from "util";
|
|
16
|
+
const pipelineUtility = promisify(pipeline); // To pipe streams using async/await
|
|
13
17
|
|
|
14
18
|
const DOC_EXTENSIONS = [".txt", ".json", ".csv", ".md", ".xml", ".js", ".html", ".css", '.pdf', '.docx', '.xlsx', '.csv'];
|
|
15
19
|
|
|
@@ -116,20 +120,62 @@ async function main(context, req) {
|
|
|
116
120
|
return;
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
const { uri, requestId, save, hash, checkHash } = req.body?.params || req.query;
|
|
123
|
+
const { uri, requestId, save, hash, checkHash, fetch, load, restore } = req.body?.params || req.query;
|
|
124
|
+
|
|
125
|
+
const filepond = fetch || restore || load;
|
|
126
|
+
if (req.method.toLowerCase() === `get` && filepond) {
|
|
127
|
+
context.log(`Remote file: ${filepond}`);
|
|
128
|
+
// Check if file already exists (using hash as the key)
|
|
129
|
+
const exists = await getFileStoreMap(filepond);
|
|
130
|
+
if(exists){
|
|
131
|
+
context.res = {
|
|
132
|
+
status: 200,
|
|
133
|
+
body: exists // existing file URL
|
|
134
|
+
};
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check if it's a youtube url
|
|
139
|
+
let youtubeDownloadedFile = null;
|
|
140
|
+
if(isValidYoutubeUrl(filepond)){
|
|
141
|
+
youtubeDownloadedFile = await processYoutubeUrl(filepond, true);
|
|
142
|
+
}
|
|
143
|
+
const filename = path.join(os.tmpdir(), path.basename(youtubeDownloadedFile || filepond));
|
|
144
|
+
// Download the remote file to a local/temporary location keep name & ext
|
|
145
|
+
if(!youtubeDownloadedFile){
|
|
146
|
+
const response = await axios.get(filepond, { responseType: "stream" });
|
|
147
|
+
await pipelineUtility(response.data, fs.createWriteStream(filename));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
const res = await uploadBlob(context, null, !useAzure, true, filename);
|
|
152
|
+
context.log(`File uploaded: ${JSON.stringify(res)}`);
|
|
153
|
+
|
|
154
|
+
//Update Redis (using hash as the key)
|
|
155
|
+
await setFileStoreMap(filepond, res);
|
|
156
|
+
|
|
157
|
+
// Return the file URL
|
|
158
|
+
context.res = {
|
|
159
|
+
status: 200,
|
|
160
|
+
body: res,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
120
165
|
|
|
121
166
|
if(hash && checkHash){ //check if hash exists
|
|
122
167
|
context.log(`Checking hash: ${hash}`);
|
|
123
168
|
const result = await getFileStoreMap(hash);
|
|
124
169
|
|
|
125
|
-
|
|
170
|
+
if(result){
|
|
171
|
+
const exists = await urlExists(result?.url);
|
|
172
|
+
const gcsExists = await gcsUrlExists(result?.gcs);
|
|
126
173
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
174
|
+
if(!exists || !gcsExists){
|
|
175
|
+
await removeFromFileStoreMap(hash);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
131
178
|
|
|
132
|
-
if(result){
|
|
133
179
|
context.log(`Hash exists: ${hash}`);
|
|
134
180
|
//update redis timestamp with current time
|
|
135
181
|
await setFileStoreMap(hash, result);
|
|
@@ -228,7 +274,8 @@ async function main(context, req) {
|
|
|
228
274
|
|
|
229
275
|
if (isYoutubeUrl) {
|
|
230
276
|
// totalCount += 1; // extra 1 step for youtube download
|
|
231
|
-
|
|
277
|
+
const processAsVideo = req.body?.params?.processAsVideo || req.query?.processAsVideo;
|
|
278
|
+
file = await processYoutubeUrl(file, processAsVideo);
|
|
232
279
|
}
|
|
233
280
|
|
|
234
281
|
const { chunkPromises, chunkOffsets, uniqueOutputPath } = await splitMediaFile(file);
|
package/lib/pathwayTools.js
CHANGED
|
@@ -3,7 +3,7 @@ import { encode, decode } from '../lib/encodeCache.js';
|
|
|
3
3
|
import { config } from '../config.js';
|
|
4
4
|
|
|
5
5
|
// callPathway - call a pathway from another pathway
|
|
6
|
-
const callPathway = async (pathwayName, inArgs) => {
|
|
6
|
+
const callPathway = async (pathwayName, inArgs, pathwayResolver) => {
|
|
7
7
|
|
|
8
8
|
// Clone the args object to avoid modifying the original
|
|
9
9
|
const args = JSON.parse(JSON.stringify(inArgs));
|
|
@@ -15,6 +15,12 @@ const callPathway = async (pathwayName, inArgs) => {
|
|
|
15
15
|
const requestState = {};
|
|
16
16
|
const parent = {};
|
|
17
17
|
const data = await pathway.rootResolver(parent, args, { config, pathway, requestState } );
|
|
18
|
+
|
|
19
|
+
// Merge the results into the pathwayResolver if it was provided
|
|
20
|
+
if (pathwayResolver) {
|
|
21
|
+
pathwayResolver.mergeResults(data);
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
return data?.result;
|
|
19
25
|
};
|
|
20
26
|
|
package/lib/requestExecutor.js
CHANGED
|
@@ -184,6 +184,7 @@ setInterval(() => {
|
|
|
184
184
|
}, 30000); // Log rates every 30 seconds
|
|
185
185
|
|
|
186
186
|
const requestWithMonitor = async (endpoint, url, data, axiosConfigObj) => {
|
|
187
|
+
//logger.warn(`Requesting ${url} with data: ${JSON.stringify(data)}`);
|
|
187
188
|
const callId = endpoint?.monitor?.startCall();
|
|
188
189
|
let response;
|
|
189
190
|
try {
|
|
@@ -356,14 +357,13 @@ const executeRequest = async (cortexRequest) => {
|
|
|
356
357
|
if (cached) {
|
|
357
358
|
logger.info(`<<< [${requestId}] served with cached response.`);
|
|
358
359
|
}
|
|
359
|
-
if (error
|
|
360
|
-
|
|
361
|
-
return { error: lastError.toJSON() ?? lastError ?? error };
|
|
360
|
+
if (error) {
|
|
361
|
+
throw { error: error.toJSON() ?? error };
|
|
362
362
|
}
|
|
363
363
|
return { data, duration };
|
|
364
364
|
} catch (error) {
|
|
365
365
|
logger.error(`Error in request: ${error.message || error}`);
|
|
366
|
-
|
|
366
|
+
throw error;
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
|