@learnpack/learnpack 5.0.262 → 5.0.266
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/lib/commands/serve.js +137 -75
- package/lib/commands/translate.js +1 -2
- package/lib/creatorDist/assets/{index-6e9E-1qG.js → index-BXp9oelr.js} +2681 -2672
- package/lib/creatorDist/index.html +1 -1
- package/lib/managers/server/routes.js +1 -2
- package/lib/utils/rigoActions.d.ts +6 -3
- package/lib/utils/rigoActions.js +15 -4
- package/package.json +1 -1
- package/src/commands/serve.ts +174 -95
- package/src/commands/translate.ts +8 -5
- package/src/creator/src/components/Message.tsx +6 -1
- package/src/creator/src/components/syllabus/ContentIndex.tsx +1 -1
- package/src/creator/src/components/syllabus/Sidebar.tsx +1 -1
- package/src/creator/src/locales/en.json +4 -2
- package/src/creator/src/locales/es.json +4 -2
- package/src/creator/src/utils/rigo.ts +2 -0
- package/src/creatorDist/assets/{index-6e9E-1qG.js → index-BXp9oelr.js} +2681 -2672
- package/src/creatorDist/index.html +1 -1
- package/src/managers/server/routes.ts +9 -7
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +398 -384
- package/src/ui/_app/sw.js +9 -4
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/creatorSocket.ts +99 -99
- package/src/utils/rigoActions.ts +27 -5
package/lib/commands/serve.js
CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.processImage = exports.createLearnJson = void 0;
|
4
4
|
const tslib_1 = require("tslib");
|
5
5
|
const command_1 = require("@oclif/command");
|
6
|
+
const buffer_1 = require("buffer");
|
6
7
|
const youtube_transcript_1 = require("youtube-transcript");
|
7
8
|
const express = require("express");
|
8
9
|
const cors = require("cors");
|
@@ -64,7 +65,7 @@ const createLearnJson = (courseInfo) => {
|
|
64
65
|
exports.createLearnJson = createLearnJson;
|
65
66
|
const uploadFileToBucket = async (bucket, file, path) => {
|
66
67
|
const fileRef = bucket.file(path);
|
67
|
-
await fileRef.save(Buffer.from(file, "utf8"));
|
68
|
+
await fileRef.save(buffer_1.Buffer.from(file, "utf8"));
|
68
69
|
};
|
69
70
|
const PARAMS = {
|
70
71
|
expected_grade_level: "8",
|
@@ -123,7 +124,7 @@ const createMultiLangAsset = async (bucket, rigoToken, bcToken, courseSlug, cour
|
|
123
124
|
// eslint-disable-next-line no-await-in-loop
|
124
125
|
const [indexReadmeContent] = await indexReadme.download();
|
125
126
|
const indexReadmeString = indexReadmeContent.toString();
|
126
|
-
const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64");
|
127
|
+
const b64IndexReadme = buffer_1.Buffer.from(indexReadmeString).toString("base64");
|
127
128
|
// eslint-disable-next-line no-await-in-loop
|
128
129
|
const asset = await (0, publish_1.handleAssetCreation)({ token: bcToken, rigobotToken: rigoToken.trim() }, courseJson, lang, deployUrl, b64IndexReadme, all_translations);
|
129
130
|
if (!asset) {
|
@@ -236,7 +237,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
236
237
|
if (!bucket) {
|
237
238
|
return res.status(500).send("Upload failed");
|
238
239
|
}
|
239
|
-
const buffer = Buffer.from(content, "utf-8");
|
240
|
+
const buffer = buffer_1.Buffer.from(content, "utf-8");
|
240
241
|
const file = bucket.file(destination);
|
241
242
|
const stream = file.createWriteStream({
|
242
243
|
resumable: false,
|
@@ -330,39 +331,77 @@ class ServeCommand extends SessionCommand_1.default {
|
|
330
331
|
app.post("/webhooks/:courseSlug/initial-readme-processor", async (req, res) => {
|
331
332
|
const { courseSlug } = req.params;
|
332
333
|
const body = req.body;
|
333
|
-
|
334
|
-
const
|
335
|
-
|
334
|
+
console.log("RECEIVING INITIAL README WEBHOOK", body);
|
335
|
+
const langCode = body.parsed.language_code || body.parsed.output_language_code || "";
|
336
|
+
const content = body.parsed.content || body.parsed.translation || "";
|
337
|
+
if (!content || !langCode) {
|
338
|
+
console.log("No content or language code to save", body);
|
339
|
+
return res.status(400).json({
|
340
|
+
status: "ERROR",
|
341
|
+
message: "No content to save",
|
342
|
+
});
|
343
|
+
}
|
344
|
+
const filePath = `courses/${courseSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(langCode)}`;
|
345
|
+
await uploadFileToBucket(bucket, content, filePath);
|
336
346
|
res.json({ status: "SUCCESS" });
|
337
347
|
});
|
338
348
|
app.post("/webhooks/:courseSlug/images/:imageId", async (req, res) => {
|
339
349
|
const { courseSlug, imageId } = req.params;
|
340
350
|
const body = req.body;
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
const imageFile = bucket.file(imagePath);
|
345
|
-
const [exists] = await imageFile.exists();
|
346
|
-
if (!exists) {
|
347
|
-
// Descargar la imagen
|
348
|
-
const response = await fetch(imageUrl);
|
349
|
-
if (!response.ok) {
|
350
|
-
return res.status(400).json({
|
351
|
+
try {
|
352
|
+
if (body.error) {
|
353
|
+
(0, creatorSocket_1.emitToNotification)(imageId, {
|
351
354
|
status: "ERROR",
|
352
|
-
message: "
|
355
|
+
message: "Error generating image",
|
353
356
|
});
|
357
|
+
return res.json({ status: "ERROR" });
|
358
|
+
}
|
359
|
+
fs.writeFileSync(`image-${imageId}.json`, JSON.stringify(body, null, 2));
|
360
|
+
const imageUrl = body.image_url;
|
361
|
+
const format = body.format;
|
362
|
+
const imagePath = `courses/${courseSlug}/.learn/assets/${imageId}`;
|
363
|
+
const imageFile = bucket.file(imagePath);
|
364
|
+
const [exists] = await imageFile.exists();
|
365
|
+
if (!exists) {
|
366
|
+
let buffer;
|
367
|
+
if (format === "b64" || imageUrl.startsWith("data:image/")) {
|
368
|
+
const base64Data = imageUrl.includes(",") ?
|
369
|
+
imageUrl.split(",")[1] :
|
370
|
+
imageUrl;
|
371
|
+
buffer = buffer_1.Buffer.from(base64Data, "base64");
|
372
|
+
}
|
373
|
+
else if (imageUrl.startsWith("http")) {
|
374
|
+
const response = await fetch(imageUrl);
|
375
|
+
if (!response.ok) {
|
376
|
+
return res.status(400).json({
|
377
|
+
status: "ERROR",
|
378
|
+
message: "Could not download the image",
|
379
|
+
});
|
380
|
+
}
|
381
|
+
buffer = new Uint8Array(await response.arrayBuffer());
|
382
|
+
}
|
383
|
+
else {
|
384
|
+
return res.status(400).json({
|
385
|
+
status: "ERROR",
|
386
|
+
message: "Image_url format not supported",
|
387
|
+
});
|
388
|
+
}
|
389
|
+
await imageFile.save(buffer, { contentType: "image/png" });
|
354
390
|
}
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
contentType: "image/png",
|
391
|
+
(0, creatorSocket_1.emitToNotification)(imageId, {
|
392
|
+
status: "SUCCESS",
|
393
|
+
message: "Image generated successfully",
|
359
394
|
});
|
395
|
+
res.json({ status: "SUCCESS" });
|
396
|
+
}
|
397
|
+
catch (error) {
|
398
|
+
(0, creatorSocket_1.emitToNotification)(imageId, {
|
399
|
+
status: "ERROR",
|
400
|
+
message: "Error receiving image webhook",
|
401
|
+
});
|
402
|
+
console.error("❌ Error receiving image webhook:", error);
|
403
|
+
res.status(500).json({ error: error.message });
|
360
404
|
}
|
361
|
-
(0, creatorSocket_1.emitToNotification)(imageId, {
|
362
|
-
status: "SUCCESS",
|
363
|
-
message: "Image generated successfully",
|
364
|
-
});
|
365
|
-
res.json({ status: "SUCCESS" });
|
366
405
|
});
|
367
406
|
app.post("/webhooks/:courseSlug/exercise-processor/:lessonID/:rigoToken", async (req, res) => {
|
368
407
|
// console.log("Receiving a webhook to exercise processor")
|
@@ -438,6 +477,15 @@ class ServeCommand extends SessionCommand_1.default {
|
|
438
477
|
await uploadFileToBucket(bucket, JSON.stringify(newSyllabus), `courses/${courseSlug}/.learn/initialSyllabus.json`);
|
439
478
|
res.json({ status: "SUCCESS" });
|
440
479
|
});
|
480
|
+
// The following endpoint is used to store an incoming translation where it supposed to be
|
481
|
+
app.post("/webhooks/:courseSlug/:exSlug/save-translation", async (req, res) => {
|
482
|
+
const { courseSlug, exSlug } = req.params;
|
483
|
+
const body = req.body;
|
484
|
+
console.log("RECEIVING TRANSLATION WEBHOOK", body);
|
485
|
+
const readmePath = `courses/${courseSlug}/exercises/${exSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(body.parsed.output_language_code)}`;
|
486
|
+
await uploadFileToBucket(bucket, body.parsed.translation, readmePath);
|
487
|
+
res.json({ status: "SUCCESS" });
|
488
|
+
});
|
441
489
|
app.get("/check-preview-image/:slug", async (req, res) => {
|
442
490
|
const { slug } = req.params;
|
443
491
|
const file = bucket.file(`courses/${slug}/preview.png`);
|
@@ -623,65 +671,79 @@ class ServeCommand extends SessionCommand_1.default {
|
|
623
671
|
return res.status(400).json({ error: "RigoToken not found" });
|
624
672
|
}
|
625
673
|
const languagesToTranslate = languages.split(",");
|
626
|
-
const
|
627
|
-
.
|
628
|
-
|
629
|
-
const
|
630
|
-
const languageCodes = new Set();
|
674
|
+
const languageCodesRes = await (0, rigoActions_1.getLanguageCodes)(rigoToken, {
|
675
|
+
raw_languages: languagesToTranslate.join(","),
|
676
|
+
});
|
677
|
+
const languageCodes = languageCodesRes.parsed.language_codes;
|
631
678
|
try {
|
632
679
|
await Promise.all(exerciseSlugs.map(async (slug) => {
|
633
680
|
const readmePath = `courses/${courseSlug}/exercises/${slug}/README${(0, creatorUtilities_1.getReadmeExtension)(currentLanguage)}`;
|
634
681
|
const readme = await bucket.file(readmePath).download();
|
635
|
-
await Promise.all(
|
636
|
-
|
682
|
+
await Promise.all(languageCodes.map(async (language) => {
|
683
|
+
// verify if the translation already exists
|
684
|
+
const translationPath = `courses/${courseSlug}/exercises/${slug}/README${(0, creatorUtilities_1.getReadmeExtension)(language)}`;
|
685
|
+
const [exists] = await bucket.file(translationPath).exists();
|
686
|
+
if (exists) {
|
687
|
+
console.log(`Translation in ${language} already exists for exercise ${slug}`);
|
688
|
+
return;
|
689
|
+
}
|
690
|
+
await (0, rigoActions_1.translateExercise)(rigoToken, {
|
637
691
|
text_to_translate: readme.toString(),
|
638
692
|
output_language: language,
|
639
|
-
|
640
|
-
});
|
641
|
-
const translatedReadme = await bucket.file(`courses/${courseSlug}/exercises/${slug}/README${(0, creatorUtilities_1.getReadmeExtension)(response.parsed.output_language_code)}`);
|
642
|
-
await translatedReadme.save(response.parsed.translation);
|
643
|
-
languageCodes.add(response.parsed.output_language_code);
|
693
|
+
}, `${process.env.HOST}/webhooks/${courseSlug}/${slug}/save-translation`);
|
644
694
|
}));
|
645
695
|
}));
|
646
|
-
const
|
647
|
-
|
648
|
-
|
649
|
-
|
696
|
+
const course = await bucket
|
697
|
+
.file(`courses/${courseSlug}/learn.json`)
|
698
|
+
.download();
|
699
|
+
const courseJson = JSON.parse(course.toString());
|
700
|
+
const neededLanguages = new Set();
|
701
|
+
let currentLanguages = Object.keys(courseJson.title);
|
702
|
+
for (const languageCode of languageCodes) {
|
703
|
+
if (!currentLanguages.includes(languageCode)) {
|
704
|
+
neededLanguages.add(languageCode);
|
650
705
|
}
|
651
706
|
}
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
for (const metadata of translatedCourseMetadata) {
|
666
|
-
courseJson.title[metadata.languageCode] = metadata.title;
|
667
|
-
courseJson.description[metadata.languageCode] =
|
668
|
-
metadata.description;
|
669
|
-
}
|
707
|
+
const neededLanguagesList = [...neededLanguages]
|
708
|
+
.map((lang) => lang.toLowerCase())
|
709
|
+
.sort();
|
710
|
+
if (neededLanguagesList.length > 0) {
|
711
|
+
const result = await (0, rigoActions_1.translateCourseMetadata)(rigoToken, {
|
712
|
+
title: JSON.stringify(courseJson.title),
|
713
|
+
description: JSON.stringify(courseJson.description),
|
714
|
+
new_languages: neededLanguagesList.join(","),
|
715
|
+
});
|
716
|
+
const translateTitle = JSON.parse(result.parsed.title);
|
717
|
+
const translateDescription = JSON.parse(result.parsed.description);
|
718
|
+
courseJson.title = translateTitle;
|
719
|
+
courseJson.description = translateDescription;
|
670
720
|
await uploadFileToBucket(bucket, JSON.stringify(courseJson), `courses/${courseSlug}/learn.json`);
|
671
|
-
|
672
|
-
const [previewReadmeContent] = await previewReadme.download();
|
673
|
-
const previewReadmeContentString = previewReadmeContent.toString();
|
674
|
-
await Promise.all([...languageCodes].map(async (languageCode) => {
|
675
|
-
const translatedPreviewReadme = await (0, rigoActions_1.translateExercise)(rigoToken, {
|
676
|
-
text_to_translate: previewReadmeContentString,
|
677
|
-
output_language: languageCode,
|
678
|
-
exercise_slug: "preview-readme",
|
679
|
-
});
|
680
|
-
await bucket
|
681
|
-
.file(`courses/${courseSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(languageCode)}`)
|
682
|
-
.save(translatedPreviewReadme.parsed.translation);
|
683
|
-
}));
|
721
|
+
currentLanguages = Object.keys(courseJson.title);
|
684
722
|
}
|
723
|
+
// Check that all the READMEs exists
|
724
|
+
const missingReadmeTranslations = [];
|
725
|
+
let firstAvailable = "";
|
726
|
+
for (const languageCode of currentLanguages) {
|
727
|
+
// eslint-disable-next-line no-await-in-loop
|
728
|
+
const previewReadme = await bucket.file(`courses/${courseSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(languageCode)}`);
|
729
|
+
// eslint-disable-next-line no-await-in-loop
|
730
|
+
const [exists] = await previewReadme.exists();
|
731
|
+
if (!exists) {
|
732
|
+
missingReadmeTranslations.push(languageCode);
|
733
|
+
}
|
734
|
+
else {
|
735
|
+
// eslint-disable-next-line no-await-in-loop
|
736
|
+
const [previewReadmeContent] = await previewReadme.download();
|
737
|
+
const previewReadmeContentString = previewReadmeContent.toString();
|
738
|
+
firstAvailable = previewReadmeContentString;
|
739
|
+
}
|
740
|
+
}
|
741
|
+
await Promise.all(missingReadmeTranslations.map(async (languageCode) => {
|
742
|
+
await (0, rigoActions_1.translateExercise)(rigoToken, {
|
743
|
+
text_to_translate: firstAvailable,
|
744
|
+
output_language: languageCode,
|
745
|
+
}, `${process.env.HOST}/webhooks/${courseSlug}/initial-readme-processor`);
|
746
|
+
}));
|
685
747
|
return res.status(200).json({ message: "Translated exercises" });
|
686
748
|
}
|
687
749
|
catch (error) {
|
@@ -1019,7 +1081,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
1019
1081
|
const { link } = req.params;
|
1020
1082
|
try {
|
1021
1083
|
// 1) Decode the URL
|
1022
|
-
const decoded = Buffer.from(link, "base64url").toString("utf-8");
|
1084
|
+
const decoded = buffer_1.Buffer.from(link, "base64url").toString("utf-8");
|
1023
1085
|
const ytMatch = decoded.match(YT_REGEX);
|
1024
1086
|
if (ytMatch) {
|
1025
1087
|
const videoId = ytMatch[1];
|
@@ -1087,7 +1149,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
1087
1149
|
return res.status(400).json({ error: "URL is required" });
|
1088
1150
|
}
|
1089
1151
|
try {
|
1090
|
-
const decodedUrl = Buffer.from(url, "base64url").toString("utf-8");
|
1152
|
+
const decodedUrl = buffer_1.Buffer.from(url, "base64url").toString("utf-8");
|
1091
1153
|
const response = await axios_1.default.get(decodedUrl, {
|
1092
1154
|
responseType: "arraybuffer",
|
1093
1155
|
});
|
@@ -78,8 +78,7 @@ class BuildCommand extends SessionCommand_1.default {
|
|
78
78
|
const response = await (0, rigoActions_1.translateExercise)(rigoToken, {
|
79
79
|
text_to_translate: readme,
|
80
80
|
output_language: language,
|
81
|
-
|
82
|
-
});
|
81
|
+
}, `${process.env.HOST}/webhooks/translate-exercise`);
|
83
82
|
await (0, creatorUtilities_1.saveTranslatedReadme)(exercise, response.parsed.output_language_code, response.parsed.translation);
|
84
83
|
console_1.default.success(`Translated ${exercise} to ${language} successfully`);
|
85
84
|
}));
|