@learnpack/learnpack 5.0.240 → 5.0.244
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/publish.d.ts +1 -1
- package/lib/commands/publish.js +19 -14
- package/lib/commands/serve.js +91 -98
- package/lib/creatorDist/assets/{index-DJn8b8wj.js → index-BWHp9KF3.js} +3043 -3038
- package/lib/creatorDist/index.html +1 -1
- package/lib/models/creator.d.ts +1 -0
- package/lib/utils/api.d.ts +1 -0
- package/lib/utils/creatorSocket.d.ts +1 -1
- package/lib/utils/creatorSocket.js +10 -3
- package/lib/utils/rigoActions.d.ts +5 -0
- package/lib/utils/rigoActions.js +11 -1
- package/package.json +1 -1
- package/src/commands/publish.ts +26 -24
- package/src/commands/serve.ts +164 -137
- package/src/creator/src/App.tsx +3 -1
- package/src/creator/src/components/NotificationListener.tsx +1 -1
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +11 -13
- package/src/creator/src/utils/store.ts +4 -1
- package/src/creatorDist/assets/{index-DJn8b8wj.js → index-BWHp9KF3.js} +3043 -3038
- package/src/creatorDist/index.html +1 -1
- package/src/models/creator.ts +1 -0
- package/src/ui/_app/app.js +189 -189
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +1 -0
- package/src/utils/creatorSocket.ts +22 -16
- package/src/utils/rigoActions.ts +22 -0
@@ -10,7 +10,7 @@
|
|
10
10
|
/>
|
11
11
|
|
12
12
|
<title>Learnpack Creator: Craft tutorials in seconds!</title>
|
13
|
-
<script type="module" crossorigin src="/creator/assets/index-
|
13
|
+
<script type="module" crossorigin src="/creator/assets/index-BWHp9KF3.js"></script>
|
14
14
|
<link rel="stylesheet" crossorigin href="/creator/assets/index-DmpsXknz.css">
|
15
15
|
</head>
|
16
16
|
<body>
|
package/lib/models/creator.d.ts
CHANGED
package/lib/utils/api.d.ts
CHANGED
@@ -23,6 +23,7 @@ type TAssetMissing = {
|
|
23
23
|
author: number;
|
24
24
|
preview: string;
|
25
25
|
readme_raw: string;
|
26
|
+
all_translations: string[];
|
26
27
|
};
|
27
28
|
export declare const createAsset: (token: string, asset: TAssetMissing) => Promise<any>;
|
28
29
|
export declare const doesAssetExists: (token: string, assetSlug: string) => Promise<{
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Server as SocketIOServer } from "socket.io";
|
2
2
|
export declare function initSocketIO(server: any): SocketIOServer<import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, any>;
|
3
3
|
export declare function emitToCourse(courseSlug: string, event: string, payload: any): void;
|
4
|
-
export declare function emitToNotification(notificationId: string, payload: any): void;
|
4
|
+
export declare function emitToNotification(notificationId: string, payload: any, retry?: number): void;
|
5
5
|
export declare function getSocketIO(): SocketIOServer<import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, any>;
|
@@ -62,11 +62,18 @@ function emitToCourse(courseSlug, event, payload) {
|
|
62
62
|
socket.emit(event, payload);
|
63
63
|
}
|
64
64
|
}
|
65
|
-
function emitToNotification(notificationId, payload) {
|
66
|
-
console.log("Emitting to notification", notificationId, payload);
|
65
|
+
function emitToNotification(notificationId, payload, retry = 0) {
|
67
66
|
const socketIds = notificationSocketMap.get(notificationId);
|
68
|
-
if (!socketIds || socketIds.size === 0)
|
67
|
+
if (!socketIds || socketIds.size === 0) {
|
68
|
+
if (retry > 3) {
|
69
|
+
console.log("❌ Notification", notificationId, "not found");
|
70
|
+
return;
|
71
|
+
}
|
72
|
+
setTimeout(() => {
|
73
|
+
emitToNotification(notificationId, payload, retry + 1);
|
74
|
+
}, 3000);
|
69
75
|
return;
|
76
|
+
}
|
70
77
|
for (const id of socketIds) {
|
71
78
|
const socket = socketStore.get(id);
|
72
79
|
if (socket)
|
@@ -60,6 +60,11 @@ type TCreateStructuredPreviewReadmeInputs = {
|
|
60
60
|
tutorial_info: string;
|
61
61
|
};
|
62
62
|
export declare const createStructuredPreviewReadme: (token: string, inputs: TCreateStructuredPreviewReadmeInputs, webhookUrl?: string) => Promise<any>;
|
63
|
+
export declare const translateCourseMetadata: (token: string, inputs: {
|
64
|
+
title: string;
|
65
|
+
description: string;
|
66
|
+
destination_lang_code: string;
|
67
|
+
}) => Promise<any>;
|
63
68
|
export declare function createPreviewReadme(tutorialDir: string, packageInfo: PackageInfo, rigoToken: string, readmeContents: string[]): Promise<void>;
|
64
69
|
type TReduceReadmeInputs = {
|
65
70
|
lesson: string;
|
package/lib/utils/rigoActions.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.isPackageAuthor = exports.fillSidebarJSON = exports.generateCourseShortName = exports.isValidRigoToken = exports.createStructuredPreviewReadme = exports.readmeCreator = exports.createCodingReadme = exports.createCodeFile = exports.interactiveCreation = exports.generateCourseIntroduction = exports.translateExercise = exports.generateImage = exports.hasCreatorPermission = exports.createReadme = void 0;
|
3
|
+
exports.isPackageAuthor = exports.fillSidebarJSON = exports.generateCourseShortName = exports.isValidRigoToken = exports.translateCourseMetadata = exports.createStructuredPreviewReadme = exports.readmeCreator = exports.createCodingReadme = exports.createCodeFile = exports.interactiveCreation = exports.generateCourseIntroduction = exports.translateExercise = exports.generateImage = exports.hasCreatorPermission = exports.createReadme = void 0;
|
4
4
|
exports.downloadImage = downloadImage;
|
5
5
|
exports.createPreviewReadme = createPreviewReadme;
|
6
6
|
exports.makeReadmeReadable = makeReadmeReadable;
|
@@ -191,6 +191,16 @@ const createStructuredPreviewReadme = async (token, inputs, webhookUrl) => {
|
|
191
191
|
return response.data;
|
192
192
|
};
|
193
193
|
exports.createStructuredPreviewReadme = createStructuredPreviewReadme;
|
194
|
+
const translateCourseMetadata = async (token, inputs) => {
|
195
|
+
const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/translate-course-metadata/`, { inputs, include_purpose_objective: false, execute_async: false }, {
|
196
|
+
headers: {
|
197
|
+
"Content-Type": "application/json",
|
198
|
+
Authorization: "Token " + token,
|
199
|
+
},
|
200
|
+
});
|
201
|
+
return response.data;
|
202
|
+
};
|
203
|
+
exports.translateCourseMetadata = translateCourseMetadata;
|
194
204
|
async function createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeContents) {
|
195
205
|
const readmeFilename = `README.md`;
|
196
206
|
const readmeContent = await (0, exports.generateCourseIntroduction)(rigoToken, {
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@learnpack/learnpack",
|
3
3
|
"description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
|
4
|
-
"version": "5.0.
|
4
|
+
"version": "5.0.244",
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
6
6
|
"contributors": [
|
7
7
|
{
|
package/src/commands/publish.ts
CHANGED
@@ -19,6 +19,7 @@ import api, { getConsumable, RIGOBOT_HOST, TAcademy } from "../utils/api"
|
|
19
19
|
import * as prompts from "prompts"
|
20
20
|
import { isValidRigoToken } from "../utils/rigoActions"
|
21
21
|
import { minutesToISO8601Duration } from "../utils/misc"
|
22
|
+
import { slugify } from "../utils/creatorUtilities"
|
22
23
|
|
23
24
|
const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload"
|
24
25
|
|
@@ -27,7 +28,8 @@ export const handleAssetCreation = async (
|
|
27
28
|
learnJson: any,
|
28
29
|
selectedLang: string,
|
29
30
|
learnpackDeployUrl: string,
|
30
|
-
b64IndexReadme: string
|
31
|
+
b64IndexReadme: string,
|
32
|
+
all_translations: string[] = []
|
31
33
|
) => {
|
32
34
|
const categories: Record<string, number> = {
|
33
35
|
us: 9,
|
@@ -43,15 +45,13 @@ export const handleAssetCreation = async (
|
|
43
45
|
try {
|
44
46
|
const user = await api.validateToken(sessionPayload.token)
|
45
47
|
|
46
|
-
const
|
47
|
-
|
48
|
-
learnJson.slug
|
49
|
-
)
|
48
|
+
const slug = slugify(learnJson.title[selectedLang]).slice(0, 50)
|
49
|
+
const { exists } = await api.doesAssetExists(sessionPayload.token, slug)
|
50
50
|
|
51
51
|
if (!exists) {
|
52
52
|
Console.info("Asset does not exist in this academy, creating it")
|
53
53
|
const asset = await api.createAsset(sessionPayload.token, {
|
54
|
-
slug:
|
54
|
+
slug: slug,
|
55
55
|
title: learnJson.title[selectedLang],
|
56
56
|
lang: selectedLang,
|
57
57
|
description: learnJson.description[selectedLang],
|
@@ -63,6 +63,7 @@ export const handleAssetCreation = async (
|
|
63
63
|
author: user.id,
|
64
64
|
preview: learnJson.preview,
|
65
65
|
readme_raw: b64IndexReadme,
|
66
|
+
all_translations,
|
66
67
|
})
|
67
68
|
await api.updateRigoAssetID(
|
68
69
|
sessionPayload.token,
|
@@ -70,26 +71,26 @@ export const handleAssetCreation = async (
|
|
70
71
|
asset.id
|
71
72
|
)
|
72
73
|
Console.info("Asset created with id", asset.id)
|
73
|
-
|
74
|
-
Console.info("Asset exists, updating it")
|
75
|
-
const asset = await api.updateAsset(
|
76
|
-
sessionPayload.token,
|
77
|
-
learnJson.slug,
|
78
|
-
{
|
79
|
-
learnpack_deploy_url: learnpackDeployUrl,
|
80
|
-
title: learnJson.title[selectedLang],
|
81
|
-
description: learnJson.description[selectedLang],
|
82
|
-
}
|
83
|
-
)
|
84
|
-
await api.updateRigoAssetID(
|
85
|
-
sessionPayload.rigobotToken,
|
86
|
-
learnJson.slug,
|
87
|
-
asset.id
|
88
|
-
)
|
89
|
-
Console.info("Asset updated with id", asset.id)
|
74
|
+
return asset
|
90
75
|
}
|
76
|
+
|
77
|
+
Console.info("Asset exists, updating it")
|
78
|
+
const asset = await api.updateAsset(sessionPayload.token, slug, {
|
79
|
+
learnpack_deploy_url: learnpackDeployUrl,
|
80
|
+
title: learnJson.title[selectedLang],
|
81
|
+
description: learnJson.description[selectedLang],
|
82
|
+
all_translations,
|
83
|
+
})
|
84
|
+
await api.updateRigoAssetID(
|
85
|
+
sessionPayload.rigobotToken.trim(),
|
86
|
+
learnJson.slug,
|
87
|
+
asset.id
|
88
|
+
)
|
89
|
+
Console.info("Asset updated with id", asset.id)
|
90
|
+
return asset
|
91
91
|
} catch (error) {
|
92
92
|
Console.error("Error updating or creating asset:", error)
|
93
|
+
return null
|
93
94
|
}
|
94
95
|
}
|
95
96
|
|
@@ -442,7 +443,8 @@ class BuildCommand extends SessionCommand {
|
|
442
443
|
learnJson,
|
443
444
|
"us",
|
444
445
|
res.data.url,
|
445
|
-
""
|
446
|
+
"",
|
447
|
+
[]
|
446
448
|
)
|
447
449
|
} catch (error) {
|
448
450
|
if (axios.isAxiosError(error)) {
|
package/src/commands/serve.ts
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
import { flags } from "@oclif/command"
|
2
2
|
|
3
|
-
// import { readDocument } from "../utils/readDocuments"
|
4
3
|
import { YoutubeTranscript } from "youtube-transcript"
|
5
4
|
import * as express from "express"
|
6
5
|
import * as cors from "cors"
|
@@ -31,6 +30,7 @@ import {
|
|
31
30
|
generateImage,
|
32
31
|
isPackageAuthor,
|
33
32
|
createStructuredPreviewReadme,
|
33
|
+
translateCourseMetadata,
|
34
34
|
} from "../utils/rigoActions"
|
35
35
|
import * as dotenv from "dotenv"
|
36
36
|
import {
|
@@ -65,15 +65,13 @@ function findLast<T>(
|
|
65
65
|
export const createLearnJson = (courseInfo: FormState) => {
|
66
66
|
// console.log("courseInfo to create learn json", courseInfo)
|
67
67
|
|
68
|
-
const expectedPreviewUrl = `https://${
|
69
|
-
courseInfo.title as string
|
70
|
-
)}.learn-pack.com/preview.png`
|
68
|
+
const expectedPreviewUrl = `https://${courseInfo.slug}.learn-pack.com/preview.png`
|
71
69
|
console.log("Preview url in generated learn.json", expectedPreviewUrl)
|
72
70
|
|
73
71
|
const language = courseInfo.language || "en"
|
74
72
|
|
75
73
|
const learnJson = {
|
76
|
-
slug:
|
74
|
+
slug: courseInfo.slug,
|
77
75
|
title:
|
78
76
|
language === "en" || language === "us" ?
|
79
77
|
{
|
@@ -106,27 +104,6 @@ const uploadFileToBucket = async (
|
|
106
104
|
await fileRef.save(Buffer.from(file, "utf8"))
|
107
105
|
}
|
108
106
|
|
109
|
-
const uploadImageToBucket = async (
|
110
|
-
bucket: Bucket,
|
111
|
-
url: string,
|
112
|
-
path: string
|
113
|
-
) => {
|
114
|
-
const response = await fetch(url)
|
115
|
-
if (!response.ok) {
|
116
|
-
throw new Error(`Failed to download image: ${response.statusText}`)
|
117
|
-
}
|
118
|
-
|
119
|
-
const contentType =
|
120
|
-
response.headers.get("content-type") || "application/octet-stream"
|
121
|
-
const buffer = await response.arrayBuffer()
|
122
|
-
|
123
|
-
const fileRef = bucket.file(path)
|
124
|
-
await fileRef.save(Buffer.from(buffer), {
|
125
|
-
resumable: false,
|
126
|
-
contentType,
|
127
|
-
})
|
128
|
-
}
|
129
|
-
|
130
107
|
const PARAMS = {
|
131
108
|
expected_grade_level: "8",
|
132
109
|
max_fkgl: 10,
|
@@ -135,41 +112,6 @@ const PARAMS = {
|
|
135
112
|
max_title_length: 50,
|
136
113
|
}
|
137
114
|
|
138
|
-
// app.post("/webhooks/:courseSlug/images/:imageId", async (req, res) => {
|
139
|
-
// const { courseSlug, imageId } = req.params
|
140
|
-
// const body = req.body
|
141
|
-
// console.log("RECEIVING IMAGE WEBHOOK", body)
|
142
|
-
|
143
|
-
// const imageUrl = body.image_url
|
144
|
-
// const imagePath = `courses/${courseSlug}/.learn/assets/${imageId}`
|
145
|
-
|
146
|
-
// const imageFile = bucket.file(imagePath)
|
147
|
-
// const [exists] = await imageFile.exists()
|
148
|
-
// if (!exists) {
|
149
|
-
// // Descargar la imagen
|
150
|
-
// const response = await fetch(imageUrl)
|
151
|
-
// if (!response.ok) {
|
152
|
-
// return res.status(400).json({
|
153
|
-
// status: "ERROR",
|
154
|
-
// message: "No se pudo descargar la imagen",
|
155
|
-
// })
|
156
|
-
// }
|
157
|
-
|
158
|
-
// const buffer = await response.arrayBuffer()
|
159
|
-
|
160
|
-
// // Guardar la imagen en el bucket
|
161
|
-
// await imageFile.save(new Uint8Array(buffer), {
|
162
|
-
// contentType: "image/png",
|
163
|
-
// })
|
164
|
-
// }
|
165
|
-
|
166
|
-
// emitToNotification(imageId, {
|
167
|
-
// status: "SUCCESS",
|
168
|
-
// message: "Image generated successfully",
|
169
|
-
// })
|
170
|
-
// res.json({ status: "SUCCESS" })
|
171
|
-
// })
|
172
|
-
|
173
115
|
export const processImage = async (
|
174
116
|
url: string,
|
175
117
|
description: string,
|
@@ -186,7 +128,6 @@ export const processImage = async (
|
|
186
128
|
prompt: description,
|
187
129
|
callbackUrl: webhookUrl,
|
188
130
|
})
|
189
|
-
// await uploadImageToBucket(bucket, res.image_url, imagePath)
|
190
131
|
|
191
132
|
// console.log("✅ Image", imagePath, "generated successfully!")
|
192
133
|
return true
|
@@ -236,7 +177,6 @@ const uploadInitialReadme = async (
|
|
236
177
|
}
|
237
178
|
|
238
179
|
const cleanFormState = (formState: FormState) => {
|
239
|
-
// keysToDelete: description, technologies, purpose
|
240
180
|
const {
|
241
181
|
description,
|
242
182
|
technologies,
|
@@ -252,6 +192,51 @@ const cleanFormState = (formState: FormState) => {
|
|
252
192
|
return rest
|
253
193
|
}
|
254
194
|
|
195
|
+
const createMultiLangAsset = async (
|
196
|
+
bucket: Bucket,
|
197
|
+
rigoToken: string,
|
198
|
+
bcToken: string,
|
199
|
+
courseSlug: string,
|
200
|
+
courseJson: any,
|
201
|
+
deployUrl: string
|
202
|
+
) => {
|
203
|
+
const availableLangs = Object.keys(courseJson.title)
|
204
|
+
console.log("AVAILABLE LANGUAGES to upload asset", availableLangs)
|
205
|
+
|
206
|
+
const all_translations: string[] = []
|
207
|
+
|
208
|
+
for (const lang of availableLangs) {
|
209
|
+
// eslint-disable-next-line no-await-in-loop
|
210
|
+
const indexReadme = await bucket.file(
|
211
|
+
`courses/${courseSlug}/README.${
|
212
|
+
lang === "us" || lang === "en" ? "md" : `${lang}.md`
|
213
|
+
}`
|
214
|
+
)
|
215
|
+
// eslint-disable-next-line no-await-in-loop
|
216
|
+
const [indexReadmeContent] = await indexReadme.download()
|
217
|
+
const indexReadmeString = indexReadmeContent.toString()
|
218
|
+
const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64")
|
219
|
+
|
220
|
+
// eslint-disable-next-line no-await-in-loop
|
221
|
+
const asset = await handleAssetCreation(
|
222
|
+
{ token: bcToken, rigobotToken: rigoToken },
|
223
|
+
courseJson,
|
224
|
+
lang,
|
225
|
+
deployUrl,
|
226
|
+
b64IndexReadme,
|
227
|
+
all_translations
|
228
|
+
)
|
229
|
+
|
230
|
+
all_translations.push(asset.slug)
|
231
|
+
}
|
232
|
+
// const languageCodes = new Set(languageCodes)
|
233
|
+
// const asset = await api.createAsset(rigoToken, {
|
234
|
+
// slug: courseSlug,
|
235
|
+
// title: courseJson.title,
|
236
|
+
// description: courseJson.description,
|
237
|
+
// })
|
238
|
+
}
|
239
|
+
|
255
240
|
async function startExerciseGeneration(
|
256
241
|
bucket: Bucket,
|
257
242
|
rigoToken: string,
|
@@ -544,7 +529,6 @@ export default class ServeCommand extends SessionCommand {
|
|
544
529
|
const { courseSlug } = req.params
|
545
530
|
const body = req.body
|
546
531
|
|
547
|
-
console.log("RECEIVING INITIAL README WEBHOOK", body)
|
548
532
|
// Save the file as courses/courseSlug/README.md
|
549
533
|
const filePath = `courses/${courseSlug}/README.${
|
550
534
|
body.parsed.language_code === "us" ||
|
@@ -935,25 +919,34 @@ export default class ServeCommand extends SessionCommand {
|
|
935
919
|
|
936
920
|
app.post("/actions/translate", express.json(), async (req, res) => {
|
937
921
|
console.log("POST /actions/translate")
|
938
|
-
const { exerciseSlugs, languages, rigoToken } = req.body
|
922
|
+
const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body
|
939
923
|
const query = req.query
|
940
924
|
const courseSlug = query.slug
|
941
925
|
|
942
|
-
console.log("EXERCISE SLUGS", exerciseSlugs)
|
943
|
-
console.log("LANGUAGES", languages)
|
944
|
-
console.log("RIGO TOKEN", rigoToken)
|
945
|
-
|
946
926
|
if (!rigoToken) {
|
947
927
|
return res.status(400).json({ error: "RigoToken not found" })
|
948
928
|
}
|
949
929
|
|
950
930
|
const languagesToTranslate: string[] = languages.split(",")
|
951
931
|
|
932
|
+
const course = await bucket
|
933
|
+
.file(`courses/${courseSlug}/learn.json`)
|
934
|
+
.download()
|
935
|
+
|
936
|
+
const courseJson = JSON.parse(course.toString())
|
937
|
+
const languageCodes = new Set()
|
938
|
+
|
952
939
|
try {
|
953
940
|
await Promise.all(
|
954
941
|
exerciseSlugs.map(async (slug: string) => {
|
955
|
-
const readmePath = `courses/${courseSlug}/exercises/${slug}/README
|
942
|
+
const readmePath = `courses/${courseSlug}/exercises/${slug}/README${
|
943
|
+
currentLanguage === "us" || currentLanguage === "en" ?
|
944
|
+
"" :
|
945
|
+
`.${currentLanguage}`
|
946
|
+
}.md`
|
947
|
+
|
956
948
|
const readme = await bucket.file(readmePath).download()
|
949
|
+
|
957
950
|
await Promise.all(
|
958
951
|
languagesToTranslate.map(async (language: string) => {
|
959
952
|
const response = await translateExercise(rigoToken, {
|
@@ -963,14 +956,98 @@ export default class ServeCommand extends SessionCommand {
|
|
963
956
|
})
|
964
957
|
|
965
958
|
const translatedReadme = await bucket.file(
|
966
|
-
`courses/${courseSlug}/exercises/${slug}/README
|
959
|
+
`courses/${courseSlug}/exercises/${slug}/README${
|
960
|
+
response.parsed.output_language_code === "us" ||
|
961
|
+
response.parsed.output_language_code === "en" ?
|
962
|
+
"" :
|
963
|
+
`.${response.parsed.output_language_code}`
|
964
|
+
}.md`
|
967
965
|
)
|
968
966
|
await translatedReadme.save(response.parsed.translation)
|
967
|
+
|
968
|
+
languageCodes.add(response.parsed.output_language_code)
|
969
969
|
})
|
970
970
|
)
|
971
971
|
})
|
972
972
|
)
|
973
973
|
|
974
|
+
if (languageCodes.has("en")) {
|
975
|
+
languageCodes.delete("en")
|
976
|
+
languageCodes.add("us")
|
977
|
+
}
|
978
|
+
|
979
|
+
const currentLanguages = Object.keys(courseJson.title)
|
980
|
+
for (const languageCode of currentLanguages) {
|
981
|
+
if (languageCodes.has(languageCode)) {
|
982
|
+
languageCodes.delete(languageCode)
|
983
|
+
}
|
984
|
+
}
|
985
|
+
|
986
|
+
if ([...languageCodes].length > 0) {
|
987
|
+
console.log("TRANSLATING COURSE METADATA", languageCodes)
|
988
|
+
|
989
|
+
const translatedCourseMetadata = await Promise.all(
|
990
|
+
[...languageCodes].map(async languageCode => {
|
991
|
+
const result = await translateCourseMetadata(rigoToken, {
|
992
|
+
title: courseJson.title[currentLanguage],
|
993
|
+
description: courseJson.description[currentLanguage],
|
994
|
+
destination_lang_code: languageCode as string,
|
995
|
+
})
|
996
|
+
|
997
|
+
return {
|
998
|
+
languageCode,
|
999
|
+
title: result.parsed.title,
|
1000
|
+
description: result.parsed.description,
|
1001
|
+
}
|
1002
|
+
})
|
1003
|
+
)
|
1004
|
+
|
1005
|
+
for (const metadata of translatedCourseMetadata) {
|
1006
|
+
courseJson.title[metadata.languageCode as string] = metadata.title
|
1007
|
+
courseJson.description[metadata.languageCode as string] =
|
1008
|
+
metadata.description
|
1009
|
+
}
|
1010
|
+
|
1011
|
+
await uploadFileToBucket(
|
1012
|
+
bucket,
|
1013
|
+
JSON.stringify(courseJson),
|
1014
|
+
`courses/${courseSlug}/learn.json`
|
1015
|
+
)
|
1016
|
+
|
1017
|
+
const previewReadme = await bucket.file(
|
1018
|
+
`courses/${courseSlug}/README${
|
1019
|
+
currentLanguage === "us" || currentLanguage === "en" ?
|
1020
|
+
"" :
|
1021
|
+
`.${currentLanguage}`
|
1022
|
+
}.md`
|
1023
|
+
)
|
1024
|
+
const [previewReadmeContent] = await previewReadme.download()
|
1025
|
+
const previewReadmeContentString = previewReadmeContent.toString()
|
1026
|
+
|
1027
|
+
await Promise.all(
|
1028
|
+
[...languageCodes].map(async languageCode => {
|
1029
|
+
const translatedPreviewReadme = await translateExercise(
|
1030
|
+
rigoToken,
|
1031
|
+
{
|
1032
|
+
text_to_translate: previewReadmeContentString,
|
1033
|
+
output_language: languageCode as string,
|
1034
|
+
exercise_slug: "preview-readme",
|
1035
|
+
}
|
1036
|
+
)
|
1037
|
+
|
1038
|
+
await bucket
|
1039
|
+
.file(
|
1040
|
+
`courses/${courseSlug}/README${
|
1041
|
+
languageCode === "us" || languageCode === "en" ?
|
1042
|
+
"" :
|
1043
|
+
`.${languageCode}`
|
1044
|
+
}.md`
|
1045
|
+
)
|
1046
|
+
.save(translatedPreviewReadme.parsed.translation)
|
1047
|
+
})
|
1048
|
+
)
|
1049
|
+
}
|
1050
|
+
|
974
1051
|
return res.status(200).json({ message: "Translated exercises" })
|
975
1052
|
} catch (error) {
|
976
1053
|
console.log(error, "ERROR")
|
@@ -979,6 +1056,8 @@ export default class ServeCommand extends SessionCommand {
|
|
979
1056
|
})
|
980
1057
|
|
981
1058
|
app.delete("/exercise/:slug/delete", async (req, res) => {
|
1059
|
+
console.log("DELETE /exercise/:slug/delete")
|
1060
|
+
|
982
1061
|
const { slug } = req.params
|
983
1062
|
const query = req.query
|
984
1063
|
const courseSlug = query.slug
|
@@ -1032,13 +1111,17 @@ export default class ServeCommand extends SessionCommand {
|
|
1032
1111
|
res.json(sidebar)
|
1033
1112
|
} catch (error: any) {
|
1034
1113
|
if (error.code === 404) {
|
1035
|
-
console.log("SIDEBAR FILE NOT FOUND", courseSlug)
|
1036
|
-
|
1037
1114
|
const { exercises } = await buildConfig(bucket, courseSlug)
|
1038
1115
|
|
1039
1116
|
const exerciseSlugsArray = exercises.map(exercise => exercise.slug)
|
1040
1117
|
const sidebar = await createInitialSidebar(exerciseSlugsArray)
|
1041
1118
|
|
1119
|
+
await uploadFileToBucket(
|
1120
|
+
bucket,
|
1121
|
+
JSON.stringify(sidebar),
|
1122
|
+
`courses/${courseSlug}/.learn/sidebar.json`
|
1123
|
+
)
|
1124
|
+
|
1042
1125
|
if (rigoToken) {
|
1043
1126
|
const { fixedSidebar } = await checkAndFixSidebarPure(
|
1044
1127
|
sidebar,
|
@@ -1053,20 +1136,9 @@ export default class ServeCommand extends SessionCommand {
|
|
1053
1136
|
)
|
1054
1137
|
}
|
1055
1138
|
|
1056
|
-
await uploadFileToBucket(
|
1057
|
-
bucket,
|
1058
|
-
JSON.stringify(sidebar),
|
1059
|
-
`courses/${courseSlug}/.learn/sidebar.json`
|
1060
|
-
)
|
1061
1139
|
return res.status(200).json(fixedSidebar)
|
1062
1140
|
}
|
1063
1141
|
|
1064
|
-
await uploadFileToBucket(
|
1065
|
-
bucket,
|
1066
|
-
JSON.stringify(sidebar),
|
1067
|
-
`courses/${courseSlug}/.learn/sidebar.json`
|
1068
|
-
)
|
1069
|
-
|
1070
1142
|
return res.status(200).json(sidebar)
|
1071
1143
|
}
|
1072
1144
|
|
@@ -1075,35 +1147,6 @@ export default class ServeCommand extends SessionCommand {
|
|
1075
1147
|
}
|
1076
1148
|
})
|
1077
1149
|
|
1078
|
-
// app.get("/test/:slug", (req, res) => {
|
1079
|
-
// emitToCourse(req.params.slug, "course-creation", {
|
1080
|
-
// lesson: "000-welcome-to-bird-domestication",
|
1081
|
-
// status: "generating",
|
1082
|
-
// log: "Hello",
|
1083
|
-
// })
|
1084
|
-
// emitToCourse(req.params.slug, "course-creation", {
|
1085
|
-
// lesson: "000-welcome-to-bird-domestication",
|
1086
|
-
// status: "generating",
|
1087
|
-
// log: "Hello",
|
1088
|
-
// })
|
1089
|
-
// emitToCourse(req.params.slug, "course-creation", {
|
1090
|
-
// lesson: "000-welcome-to-bird-domestication",
|
1091
|
-
// status: "generating",
|
1092
|
-
// log: "Hello broder",
|
1093
|
-
// })
|
1094
|
-
// emitToCourse(req.params.slug, "course-creation", {
|
1095
|
-
// lesson: "000-welcome-to-bird-domestication",
|
1096
|
-
// status: "done",
|
1097
|
-
// log: "Hello broder",
|
1098
|
-
// })
|
1099
|
-
// emitToCourse(req.params.slug, "course-creation", {
|
1100
|
-
// lesson: "other",
|
1101
|
-
// status: "generating",
|
1102
|
-
// log: "Hello",
|
1103
|
-
// })
|
1104
|
-
// res.send({ message: "Course creation started" })
|
1105
|
-
// })
|
1106
|
-
|
1107
1150
|
app.get("/technologies", async (req, res) => {
|
1108
1151
|
console.log("GET /technologies")
|
1109
1152
|
|
@@ -1143,7 +1186,7 @@ export default class ServeCommand extends SessionCommand {
|
|
1143
1186
|
return res.status(400).json({ error: "Missing tokens" })
|
1144
1187
|
}
|
1145
1188
|
|
1146
|
-
const courseSlug =
|
1189
|
+
const courseSlug = syllabus.courseInfo.slug
|
1147
1190
|
|
1148
1191
|
const tutorialDir = `courses/${courseSlug}`
|
1149
1192
|
const learnJson = createLearnJson(syllabus.courseInfo)
|
@@ -1241,7 +1284,7 @@ export default class ServeCommand extends SessionCommand {
|
|
1241
1284
|
|
1242
1285
|
return res.json({
|
1243
1286
|
message: "Course created",
|
1244
|
-
slug:
|
1287
|
+
slug: syllabus.courseInfo.slug,
|
1245
1288
|
})
|
1246
1289
|
})
|
1247
1290
|
|
@@ -1409,11 +1452,6 @@ export default class ServeCommand extends SessionCommand {
|
|
1409
1452
|
selectedLang = availableLangs[0]
|
1410
1453
|
}
|
1411
1454
|
|
1412
|
-
// console.log(availableLangs, "AVAILABLE LANGs")
|
1413
|
-
// console.log(selectedLang, "SELECTED LANG")
|
1414
|
-
// console.log(title, "TITLE")
|
1415
|
-
|
1416
|
-
// 5) Inyectar placeholders en index.html
|
1417
1455
|
const idxTpl = fs.readFileSync(path.join(uiSrc, "index.html"), "utf-8")
|
1418
1456
|
const idxHtml = idxTpl
|
1419
1457
|
.replace(/{{title}}/g, title)
|
@@ -1485,24 +1523,13 @@ export default class ServeCommand extends SessionCommand {
|
|
1485
1523
|
}
|
1486
1524
|
)
|
1487
1525
|
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
}`
|
1494
|
-
)
|
1495
|
-
const [indexReadmeContent] = await indexReadme.download()
|
1496
|
-
const indexReadmeString = indexReadmeContent.toString()
|
1497
|
-
const b64IndexReadme =
|
1498
|
-
Buffer.from(indexReadmeString).toString("base64")
|
1499
|
-
|
1500
|
-
await handleAssetCreation(
|
1501
|
-
{ token: bcToken, rigobotToken: rigoToken },
|
1526
|
+
await createMultiLangAsset(
|
1527
|
+
bucket,
|
1528
|
+
rigoToken,
|
1529
|
+
bcToken,
|
1530
|
+
slug,
|
1502
1531
|
fullConfig.config,
|
1503
|
-
|
1504
|
-
rigoRes.data.url,
|
1505
|
-
b64IndexReadme
|
1532
|
+
rigoRes.data.url
|
1506
1533
|
)
|
1507
1534
|
|
1508
1535
|
rimraf.sync(tmpRoot)
|
package/src/creator/src/App.tsx
CHANGED
@@ -26,6 +26,7 @@ import ResumeCourseModal from "./components/ResumeCourseModal"
|
|
26
26
|
import { possiblePurposes, PurposeSelector } from "./components/PurposeSelector"
|
27
27
|
import { useTranslation } from "react-i18next"
|
28
28
|
import NotificationListener from "./components/NotificationListener"
|
29
|
+
import { slugify } from "./utils/creatorUtils"
|
29
30
|
|
30
31
|
function App() {
|
31
32
|
const navigate = useNavigate()
|
@@ -410,7 +411,8 @@ function App() {
|
|
410
411
|
lessons,
|
411
412
|
courseInfo: {
|
412
413
|
...formState,
|
413
|
-
title:
|
414
|
+
title: res.parsed.title,
|
415
|
+
slug: slugify(fixTitleLength(res.parsed.title)),
|
414
416
|
description: res.parsed.description,
|
415
417
|
language:
|
416
418
|
res.parsed.languageCode || formState.language || "en",
|