@learnpack/learnpack 5.0.322 → 5.0.323

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.
@@ -187,18 +187,32 @@ async function startInitialContentGeneration(rigoToken, steps, packageContext, e
187
187
  if (!exercise) {
188
188
  throw new errorHandler_1.ValidationError("Exercise is required but was not provided");
189
189
  }
190
+ console.log("EXERCISE", exercise);
190
191
  if (!exercise.id || !exercise.title) {
191
192
  throw new errorHandler_1.ValidationError(`Exercise is missing required properties: id=${exercise.id}, title=${exercise.title}`);
192
193
  }
193
194
  const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
194
195
  console.log("Starting initial content generation for", exSlug);
195
196
  const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/initial-content-processor/${exercise.uid}/${rigoToken}`;
196
- // Determine if this is the first lesson
197
197
  const exerciseIndex = steps.findIndex(lesson => lesson.uid === exercise.uid);
198
198
  const isFirstLesson = exerciseIndex === 0;
199
- const endpointSlug = isFirstLesson ?
200
- "initial-step-content-generator" :
201
- "generate-step-initial-content";
199
+ const isLastLesson = exerciseIndex === steps.length - 1;
200
+ let endpointSlug;
201
+ let passTopicDescription = false;
202
+ if (isFirstLesson) {
203
+ endpointSlug = "generate-learnpack-intro-content";
204
+ }
205
+ else if (isLastLesson) {
206
+ endpointSlug = "generate-learnpack-outro-content";
207
+ }
208
+ else if (exercise.id.endsWith(".0")) {
209
+ passTopicDescription = true;
210
+ endpointSlug = "generate-learnpack-topic-intro-content";
211
+ }
212
+ else {
213
+ passTopicDescription = true;
214
+ endpointSlug = "generate-learnpack-subtopic-content";
215
+ }
202
216
  // Emit notification that initial content generation is starting
203
217
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
204
218
  lesson: exSlug,
@@ -212,12 +226,16 @@ async function startInitialContentGeneration(rigoToken, steps, packageContext, e
212
226
  // Add random 6-digit number to avoid cache issues
213
227
  // eslint-disable-next-line no-mixed-operators
214
228
  const randomCacheEvict = Math.floor(100000 + Math.random() * 900000);
215
- const res = await (0, rigoActions_1.initialContentGenerator)(rigoToken.trim(), {
216
- // prev_lesson: lastLesson,
229
+ const inputs = {
217
230
  output_language: packageContext.language || "en",
218
231
  current_syllabus: JSON.stringify(fullSyllabus),
219
232
  lesson_description: JSON.stringify(lessonCleaner(exercise)) + `-${randomCacheEvict}`,
220
- }, webhookUrl, endpointSlug);
233
+ target_word_count: String(PARAMS.max_words),
234
+ topic_description: passTopicDescription ?
235
+ String(exercise.description) :
236
+ undefined,
237
+ };
238
+ const res = await (0, rigoActions_1.initialContentGenerator)(rigoToken.trim(), inputs, webhookUrl, endpointSlug);
221
239
  console.log("INITIAL CONTENT GENERATOR RES", res);
222
240
  return res.id;
223
241
  }
@@ -1272,6 +1290,12 @@ class ServeCommand extends SessionCommand_1.default {
1272
1290
  console.log("RECEIVING TRANSLATION WEBHOOK", body);
1273
1291
  const readmePath = `courses/${courseSlug}/exercises/${exSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(body.parsed.output_language_code)}`;
1274
1292
  await uploadFileToBucket(bucket, body.parsed.translation, readmePath);
1293
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-completed", {
1294
+ exercise: exSlug,
1295
+ language: body.parsed.output_language_code,
1296
+ status: "completed",
1297
+ message: `Translation completed for ${exSlug} to ${body.parsed.output_language_code}`,
1298
+ });
1275
1299
  res.json({ status: "SUCCESS" });
1276
1300
  });
1277
1301
  app.get("/check-preview-image/:slug", async (req, res) => {
@@ -1513,35 +1537,40 @@ class ServeCommand extends SessionCommand_1.default {
1513
1537
  }
1514
1538
  res.send({ message: "Files renamed" });
1515
1539
  });
1516
- app.post("/actions/translate", express.json(), async (req, res) => {
1517
- console.log("POST /actions/translate");
1518
- const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body;
1519
- const query = req.query;
1520
- const courseSlug = query.slug;
1521
- if (!rigoToken) {
1522
- return res.status(400).json({ error: "RigoToken not found" });
1523
- }
1524
- const languagesToTranslate = languages.split(",");
1525
- const languageCodesRes = await (0, rigoActions_1.getLanguageCodes)(rigoToken, {
1526
- raw_languages: languagesToTranslate.join(","),
1527
- });
1528
- const languageCodes = languageCodesRes.parsed.language_codes;
1540
+ async function processTranslationsAsync(courseSlug, exerciseSlugs, languageCodes, rigoToken, currentLanguage, bucket) {
1529
1541
  try {
1542
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-started", {
1543
+ languages: languageCodes,
1544
+ exercises: exerciseSlugs,
1545
+ status: "started",
1546
+ message: "Translation process started",
1547
+ });
1530
1548
  await Promise.all(exerciseSlugs.map(async (slug) => {
1531
1549
  const readmePath = `courses/${courseSlug}/exercises/${slug}/README${(0, creatorUtilities_1.getReadmeExtension)(currentLanguage)}`;
1532
1550
  const readme = await bucket.file(readmePath).download();
1533
1551
  await Promise.all(languageCodes.map(async (language) => {
1534
- // verify if the translation already exists
1535
1552
  const translationPath = `courses/${courseSlug}/exercises/${slug}/README${(0, creatorUtilities_1.getReadmeExtension)(language)}`;
1536
1553
  const [exists] = await bucket.file(translationPath).exists();
1537
1554
  if (exists) {
1538
1555
  console.log(`Translation in ${language} already exists for exercise ${slug}`);
1556
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-progress", {
1557
+ exercise: slug,
1558
+ language: language,
1559
+ status: "skipped",
1560
+ message: `Translation in ${language} already exists`,
1561
+ });
1539
1562
  return;
1540
1563
  }
1541
1564
  await (0, rigoActions_1.translateExercise)(rigoToken, {
1542
1565
  text_to_translate: readme.toString(),
1543
1566
  output_language: language,
1544
1567
  }, `${process.env.HOST}/webhooks/${courseSlug}/${slug}/save-translation`);
1568
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-progress", {
1569
+ exercise: slug,
1570
+ language: language,
1571
+ status: "initiated",
1572
+ message: `Translation job initiated for ${slug} to ${language}`,
1573
+ });
1545
1574
  }));
1546
1575
  }));
1547
1576
  const course = await bucket
@@ -1571,7 +1600,6 @@ class ServeCommand extends SessionCommand_1.default {
1571
1600
  await uploadFileToBucket(bucket, JSON.stringify(courseJson), `courses/${courseSlug}/learn.json`);
1572
1601
  currentLanguages = Object.keys(courseJson.title);
1573
1602
  }
1574
- // Check that all the READMEs exists
1575
1603
  const missingReadmeTranslations = [];
1576
1604
  let firstAvailable = "";
1577
1605
  for (const languageCode of currentLanguages) {
@@ -1595,7 +1623,49 @@ class ServeCommand extends SessionCommand_1.default {
1595
1623
  output_language: languageCode,
1596
1624
  }, `${process.env.HOST}/webhooks/${courseSlug}/initial-readme-processor`);
1597
1625
  }));
1598
- return res.status(200).json({ message: "Translated exercises" });
1626
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-initiated", {
1627
+ status: "completed",
1628
+ message: "All translation jobs have been initiated",
1629
+ languages: languageCodes,
1630
+ exercises: exerciseSlugs,
1631
+ });
1632
+ }
1633
+ catch (error) {
1634
+ console.error("Error processing translations:", error);
1635
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-error", {
1636
+ status: "error",
1637
+ error: error.message,
1638
+ message: "Error occurred during translation processing",
1639
+ });
1640
+ throw error;
1641
+ }
1642
+ }
1643
+ app.post("/actions/translate", express.json(), async (req, res) => {
1644
+ console.log("POST /actions/translate");
1645
+ const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body;
1646
+ const query = req.query;
1647
+ const courseSlug = typeof query.slug === "string" ? query.slug : undefined;
1648
+ if (!rigoToken) {
1649
+ return res.status(400).json({ error: "RigoToken not found" });
1650
+ }
1651
+ if (!courseSlug) {
1652
+ return res.status(400).json({ error: "Course slug not found" });
1653
+ }
1654
+ const languagesToTranslate = languages.split(",");
1655
+ try {
1656
+ const languageCodesRes = await (0, rigoActions_1.getLanguageCodes)(rigoToken, {
1657
+ raw_languages: languagesToTranslate.join(","),
1658
+ });
1659
+ const languageCodes = languageCodesRes.parsed.language_codes;
1660
+ res.status(200).json({
1661
+ message: "Translation started",
1662
+ languages: languageCodes,
1663
+ exercises: exerciseSlugs,
1664
+ status: "processing",
1665
+ });
1666
+ processTranslationsAsync(courseSlug, exerciseSlugs, languageCodes, rigoToken, currentLanguage, bucket).catch(error => {
1667
+ console.error("Error in background translation processing:", error);
1668
+ });
1599
1669
  }
1600
1670
  catch (error) {
1601
1671
  console.log(error, "ERROR");
@@ -95,6 +95,8 @@ type TInitialContentGeneratorInputs = {
95
95
  output_language: string;
96
96
  current_syllabus: string;
97
97
  lesson_description: string;
98
+ target_word_count: string;
99
+ topic_description?: string;
98
100
  };
99
101
  export declare const initialContentGenerator: (token: string, inputs: TInitialContentGeneratorInputs, webhookUrl?: string, endpointSlug?: string) => Promise<any>;
100
102
  type TAddInteractivityInputs = {
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.322",
4
+ "version": "5.0.323",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -340,6 +340,8 @@ async function startInitialContentGeneration(
340
340
  throw new ValidationError("Exercise is required but was not provided")
341
341
  }
342
342
 
343
+ console.log("EXERCISE", exercise)
344
+
343
345
  if (!exercise.id || !exercise.title) {
344
346
  throw new ValidationError(
345
347
  `Exercise is missing required properties: id=${exercise.id}, title=${exercise.title}`
@@ -351,14 +353,26 @@ async function startInitialContentGeneration(
351
353
 
352
354
  const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/initial-content-processor/${exercise.uid}/${rigoToken}`
353
355
 
354
- // Determine if this is the first lesson
355
356
  const exerciseIndex = steps.findIndex(
356
357
  lesson => lesson.uid === exercise.uid
357
358
  )
358
359
  const isFirstLesson = exerciseIndex === 0
359
- const endpointSlug = isFirstLesson ?
360
- "initial-step-content-generator" :
361
- "generate-step-initial-content"
360
+ const isLastLesson = exerciseIndex === steps.length - 1
361
+
362
+ let endpointSlug: string
363
+
364
+ let passTopicDescription = false
365
+ if (isFirstLesson) {
366
+ endpointSlug = "generate-learnpack-intro-content"
367
+ } else if (isLastLesson) {
368
+ endpointSlug = "generate-learnpack-outro-content"
369
+ } else if (exercise.id.endsWith(".0")) {
370
+ passTopicDescription = true
371
+ endpointSlug = "generate-learnpack-topic-intro-content"
372
+ } else {
373
+ passTopicDescription = true
374
+ endpointSlug = "generate-learnpack-subtopic-content"
375
+ }
362
376
 
363
377
  // Emit notification that initial content generation is starting
364
378
  emitToCourse(courseSlug, "course-creation", {
@@ -376,15 +390,20 @@ async function startInitialContentGeneration(
376
390
  // eslint-disable-next-line no-mixed-operators
377
391
  const randomCacheEvict = Math.floor(100_000 + Math.random() * 900_000)
378
392
 
393
+ const inputs = {
394
+ output_language: packageContext.language || "en",
395
+ current_syllabus: JSON.stringify(fullSyllabus),
396
+ lesson_description:
397
+ JSON.stringify(lessonCleaner(exercise)) + `-${randomCacheEvict}`,
398
+ target_word_count: String(PARAMS.max_words),
399
+ topic_description: passTopicDescription ?
400
+ String(exercise.description) :
401
+ undefined,
402
+ }
403
+
379
404
  const res = await initialContentGenerator(
380
405
  rigoToken.trim(),
381
- {
382
- // prev_lesson: lastLesson,
383
- output_language: packageContext.language || "en",
384
- current_syllabus: JSON.stringify(fullSyllabus),
385
- lesson_description:
386
- JSON.stringify(lessonCleaner(exercise)) + `-${randomCacheEvict}`,
387
- },
406
+ inputs,
388
407
  webhookUrl,
389
408
  endpointSlug
390
409
  )
@@ -1897,6 +1916,13 @@ export default class ServeCommand extends SessionCommand {
1897
1916
 
1898
1917
  await uploadFileToBucket(bucket, body.parsed.translation, readmePath)
1899
1918
 
1919
+ emitToCourse(courseSlug, "translation-completed", {
1920
+ exercise: exSlug,
1921
+ language: body.parsed.output_language_code,
1922
+ status: "completed",
1923
+ message: `Translation completed for ${exSlug} to ${body.parsed.output_language_code}`,
1924
+ })
1925
+
1900
1926
  res.json({ status: "SUCCESS" })
1901
1927
  }
1902
1928
  )
@@ -2245,23 +2271,22 @@ export default class ServeCommand extends SessionCommand {
2245
2271
  res.send({ message: "Files renamed" })
2246
2272
  })
2247
2273
 
2248
- app.post("/actions/translate", express.json(), async (req, res) => {
2249
- console.log("POST /actions/translate")
2250
- const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body
2251
- const query = req.query
2252
- const courseSlug = query.slug
2253
-
2254
- if (!rigoToken) {
2255
- return res.status(400).json({ error: "RigoToken not found" })
2256
- }
2257
-
2258
- const languagesToTranslate: string[] = languages.split(",")
2259
- const languageCodesRes = await getLanguageCodes(rigoToken, {
2260
- raw_languages: languagesToTranslate.join(","),
2261
- })
2262
- const languageCodes = languageCodesRes.parsed.language_codes
2263
-
2274
+ async function processTranslationsAsync(
2275
+ courseSlug: string,
2276
+ exerciseSlugs: string[],
2277
+ languageCodes: string[],
2278
+ rigoToken: string,
2279
+ currentLanguage: string,
2280
+ bucket: Bucket
2281
+ ) {
2264
2282
  try {
2283
+ emitToCourse(courseSlug, "translation-started", {
2284
+ languages: languageCodes,
2285
+ exercises: exerciseSlugs,
2286
+ status: "started",
2287
+ message: "Translation process started",
2288
+ })
2289
+
2265
2290
  await Promise.all(
2266
2291
  exerciseSlugs.map(async (slug: string) => {
2267
2292
  const readmePath = `courses/${courseSlug}/exercises/${slug}/README${getReadmeExtension(
@@ -2271,7 +2296,6 @@ export default class ServeCommand extends SessionCommand {
2271
2296
 
2272
2297
  await Promise.all(
2273
2298
  languageCodes.map(async (language: string) => {
2274
- // verify if the translation already exists
2275
2299
  const translationPath = `courses/${courseSlug}/exercises/${slug}/README${getReadmeExtension(
2276
2300
  language
2277
2301
  )}`
@@ -2281,6 +2305,12 @@ export default class ServeCommand extends SessionCommand {
2281
2305
  console.log(
2282
2306
  `Translation in ${language} already exists for exercise ${slug}`
2283
2307
  )
2308
+ emitToCourse(courseSlug, "translation-progress", {
2309
+ exercise: slug,
2310
+ language: language,
2311
+ status: "skipped",
2312
+ message: `Translation in ${language} already exists`,
2313
+ })
2284
2314
  return
2285
2315
  }
2286
2316
 
@@ -2292,6 +2322,13 @@ export default class ServeCommand extends SessionCommand {
2292
2322
  },
2293
2323
  `${process.env.HOST}/webhooks/${courseSlug}/${slug}/save-translation`
2294
2324
  )
2325
+
2326
+ emitToCourse(courseSlug, "translation-progress", {
2327
+ exercise: slug,
2328
+ language: language,
2329
+ status: "initiated",
2330
+ message: `Translation job initiated for ${slug} to ${language}`,
2331
+ })
2295
2332
  })
2296
2333
  )
2297
2334
  })
@@ -2339,8 +2376,6 @@ export default class ServeCommand extends SessionCommand {
2339
2376
  currentLanguages = Object.keys(courseJson.title)
2340
2377
  }
2341
2378
 
2342
- // Check that all the READMEs exists
2343
-
2344
2379
  const missingReadmeTranslations = []
2345
2380
  let firstAvailable = ""
2346
2381
  for (const languageCode of currentLanguages) {
@@ -2363,6 +2398,7 @@ export default class ServeCommand extends SessionCommand {
2363
2398
 
2364
2399
  await Promise.all(
2365
2400
  missingReadmeTranslations.map(async languageCode => {
2401
+
2366
2402
  await translateExercise(
2367
2403
  rigoToken,
2368
2404
  {
@@ -2374,7 +2410,63 @@ export default class ServeCommand extends SessionCommand {
2374
2410
  })
2375
2411
  )
2376
2412
 
2377
- return res.status(200).json({ message: "Translated exercises" })
2413
+ emitToCourse(courseSlug, "translation-initiated", {
2414
+ status: "completed",
2415
+ message: "All translation jobs have been initiated",
2416
+ languages: languageCodes,
2417
+ exercises: exerciseSlugs,
2418
+ })
2419
+ } catch (error) {
2420
+ console.error("Error processing translations:", error)
2421
+ emitToCourse(courseSlug, "translation-error", {
2422
+ status: "error",
2423
+ error: (error as Error).message,
2424
+ message: "Error occurred during translation processing",
2425
+ })
2426
+ throw error
2427
+ }
2428
+ }
2429
+
2430
+ app.post("/actions/translate", express.json(), async (req, res) => {
2431
+ console.log("POST /actions/translate")
2432
+ const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body
2433
+ const query = req.query
2434
+ const courseSlug =
2435
+ typeof query.slug === "string" ? query.slug : undefined
2436
+
2437
+ if (!rigoToken) {
2438
+ return res.status(400).json({ error: "RigoToken not found" })
2439
+ }
2440
+
2441
+ if (!courseSlug) {
2442
+ return res.status(400).json({ error: "Course slug not found" })
2443
+ }
2444
+
2445
+ const languagesToTranslate: string[] = languages.split(",")
2446
+
2447
+ try {
2448
+ const languageCodesRes = await getLanguageCodes(rigoToken, {
2449
+ raw_languages: languagesToTranslate.join(","),
2450
+ })
2451
+ const languageCodes = languageCodesRes.parsed.language_codes
2452
+
2453
+ res.status(200).json({
2454
+ message: "Translation started",
2455
+ languages: languageCodes,
2456
+ exercises: exerciseSlugs,
2457
+ status: "processing",
2458
+ })
2459
+
2460
+ processTranslationsAsync(
2461
+ courseSlug,
2462
+ exerciseSlugs,
2463
+ languageCodes,
2464
+ rigoToken,
2465
+ currentLanguage,
2466
+ bucket
2467
+ ).catch(error => {
2468
+ console.error("Error in background translation processing:", error)
2469
+ })
2378
2470
  } catch (error) {
2379
2471
  console.log(error, "ERROR")
2380
2472
  return res.status(400).json({ error: (error as Error).message })