@learnpack/learnpack 5.0.322 → 5.0.324

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
  }
@@ -247,6 +265,7 @@ async function startInteractivityGeneration(rigoToken, steps, packageContext, ex
247
265
  initial_lesson: exercise.initialContent + `-${randomCacheEvict}`,
248
266
  output_language: packageContext.language || "en",
249
267
  current_syllabus: JSON.stringify(fullSyllabus),
268
+ lesson_info: JSON.stringify(lessonCleaner(exercise)),
250
269
  }, webhookUrl);
251
270
  return res.id;
252
271
  }
@@ -1272,6 +1291,12 @@ class ServeCommand extends SessionCommand_1.default {
1272
1291
  console.log("RECEIVING TRANSLATION WEBHOOK", body);
1273
1292
  const readmePath = `courses/${courseSlug}/exercises/${exSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(body.parsed.output_language_code)}`;
1274
1293
  await uploadFileToBucket(bucket, body.parsed.translation, readmePath);
1294
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-completed", {
1295
+ exercise: exSlug,
1296
+ language: body.parsed.output_language_code,
1297
+ status: "completed",
1298
+ message: `Translation completed for ${exSlug} to ${body.parsed.output_language_code}`,
1299
+ });
1275
1300
  res.json({ status: "SUCCESS" });
1276
1301
  });
1277
1302
  app.get("/check-preview-image/:slug", async (req, res) => {
@@ -1513,35 +1538,40 @@ class ServeCommand extends SessionCommand_1.default {
1513
1538
  }
1514
1539
  res.send({ message: "Files renamed" });
1515
1540
  });
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;
1541
+ async function processTranslationsAsync(courseSlug, exerciseSlugs, languageCodes, rigoToken, currentLanguage, bucket) {
1529
1542
  try {
1543
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-started", {
1544
+ languages: languageCodes,
1545
+ exercises: exerciseSlugs,
1546
+ status: "started",
1547
+ message: "Translation process started",
1548
+ });
1530
1549
  await Promise.all(exerciseSlugs.map(async (slug) => {
1531
1550
  const readmePath = `courses/${courseSlug}/exercises/${slug}/README${(0, creatorUtilities_1.getReadmeExtension)(currentLanguage)}`;
1532
1551
  const readme = await bucket.file(readmePath).download();
1533
1552
  await Promise.all(languageCodes.map(async (language) => {
1534
- // verify if the translation already exists
1535
1553
  const translationPath = `courses/${courseSlug}/exercises/${slug}/README${(0, creatorUtilities_1.getReadmeExtension)(language)}`;
1536
1554
  const [exists] = await bucket.file(translationPath).exists();
1537
1555
  if (exists) {
1538
1556
  console.log(`Translation in ${language} already exists for exercise ${slug}`);
1557
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-progress", {
1558
+ exercise: slug,
1559
+ language: language,
1560
+ status: "skipped",
1561
+ message: `Translation in ${language} already exists`,
1562
+ });
1539
1563
  return;
1540
1564
  }
1541
1565
  await (0, rigoActions_1.translateExercise)(rigoToken, {
1542
1566
  text_to_translate: readme.toString(),
1543
1567
  output_language: language,
1544
1568
  }, `${process.env.HOST}/webhooks/${courseSlug}/${slug}/save-translation`);
1569
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-progress", {
1570
+ exercise: slug,
1571
+ language: language,
1572
+ status: "initiated",
1573
+ message: `Translation job initiated for ${slug} to ${language}`,
1574
+ });
1545
1575
  }));
1546
1576
  }));
1547
1577
  const course = await bucket
@@ -1571,7 +1601,6 @@ class ServeCommand extends SessionCommand_1.default {
1571
1601
  await uploadFileToBucket(bucket, JSON.stringify(courseJson), `courses/${courseSlug}/learn.json`);
1572
1602
  currentLanguages = Object.keys(courseJson.title);
1573
1603
  }
1574
- // Check that all the READMEs exists
1575
1604
  const missingReadmeTranslations = [];
1576
1605
  let firstAvailable = "";
1577
1606
  for (const languageCode of currentLanguages) {
@@ -1595,7 +1624,49 @@ class ServeCommand extends SessionCommand_1.default {
1595
1624
  output_language: languageCode,
1596
1625
  }, `${process.env.HOST}/webhooks/${courseSlug}/initial-readme-processor`);
1597
1626
  }));
1598
- return res.status(200).json({ message: "Translated exercises" });
1627
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-initiated", {
1628
+ status: "completed",
1629
+ message: "All translation jobs have been initiated",
1630
+ languages: languageCodes,
1631
+ exercises: exerciseSlugs,
1632
+ });
1633
+ }
1634
+ catch (error) {
1635
+ console.error("Error processing translations:", error);
1636
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-error", {
1637
+ status: "error",
1638
+ error: error.message,
1639
+ message: "Error occurred during translation processing",
1640
+ });
1641
+ throw error;
1642
+ }
1643
+ }
1644
+ app.post("/actions/translate", express.json(), async (req, res) => {
1645
+ console.log("POST /actions/translate");
1646
+ const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body;
1647
+ const query = req.query;
1648
+ const courseSlug = typeof query.slug === "string" ? query.slug : undefined;
1649
+ if (!rigoToken) {
1650
+ return res.status(400).json({ error: "RigoToken not found" });
1651
+ }
1652
+ if (!courseSlug) {
1653
+ return res.status(400).json({ error: "Course slug not found" });
1654
+ }
1655
+ const languagesToTranslate = languages.split(",");
1656
+ try {
1657
+ const languageCodesRes = await (0, rigoActions_1.getLanguageCodes)(rigoToken, {
1658
+ raw_languages: languagesToTranslate.join(","),
1659
+ });
1660
+ const languageCodes = languageCodesRes.parsed.language_codes;
1661
+ res.status(200).json({
1662
+ message: "Translation started",
1663
+ languages: languageCodes,
1664
+ exercises: exerciseSlugs,
1665
+ status: "processing",
1666
+ });
1667
+ processTranslationsAsync(courseSlug, exerciseSlugs, languageCodes, rigoToken, currentLanguage, bucket).catch(error => {
1668
+ console.error("Error in background translation processing:", error);
1669
+ });
1599
1670
  }
1600
1671
  catch (error) {
1601
1672
  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 = {
@@ -102,6 +104,7 @@ type TAddInteractivityInputs = {
102
104
  prev_lesson: string;
103
105
  initial_lesson: string;
104
106
  output_language: string;
107
+ lesson_info: string;
105
108
  current_syllabus: string;
106
109
  };
107
110
  export declare const addInteractivity: (token: string, inputs: TAddInteractivityInputs, webhookUrl?: string) => Promise<any>;
@@ -306,7 +306,7 @@ const initialContentGenerator = async (token, inputs, webhookUrl, endpointSlug =
306
306
  exports.initialContentGenerator = initialContentGenerator;
307
307
  const addInteractivity = async (token, inputs, webhookUrl) => {
308
308
  try {
309
- const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/lesson-refiner/`, {
309
+ const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/add-lesson-interactivy/`, {
310
310
  inputs,
311
311
  include_purpose_objective: false,
312
312
  execute_async: !!webhookUrl,
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.324",
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
  )
@@ -437,6 +456,7 @@ async function startInteractivityGeneration(
437
456
  initial_lesson: exercise.initialContent + `-${randomCacheEvict}`,
438
457
  output_language: packageContext.language || "en",
439
458
  current_syllabus: JSON.stringify(fullSyllabus),
459
+ lesson_info: JSON.stringify(lessonCleaner(exercise)),
440
460
  },
441
461
  webhookUrl
442
462
  )
@@ -1897,6 +1917,13 @@ export default class ServeCommand extends SessionCommand {
1897
1917
 
1898
1918
  await uploadFileToBucket(bucket, body.parsed.translation, readmePath)
1899
1919
 
1920
+ emitToCourse(courseSlug, "translation-completed", {
1921
+ exercise: exSlug,
1922
+ language: body.parsed.output_language_code,
1923
+ status: "completed",
1924
+ message: `Translation completed for ${exSlug} to ${body.parsed.output_language_code}`,
1925
+ })
1926
+
1900
1927
  res.json({ status: "SUCCESS" })
1901
1928
  }
1902
1929
  )
@@ -2245,23 +2272,22 @@ export default class ServeCommand extends SessionCommand {
2245
2272
  res.send({ message: "Files renamed" })
2246
2273
  })
2247
2274
 
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
-
2275
+ async function processTranslationsAsync(
2276
+ courseSlug: string,
2277
+ exerciseSlugs: string[],
2278
+ languageCodes: string[],
2279
+ rigoToken: string,
2280
+ currentLanguage: string,
2281
+ bucket: Bucket
2282
+ ) {
2264
2283
  try {
2284
+ emitToCourse(courseSlug, "translation-started", {
2285
+ languages: languageCodes,
2286
+ exercises: exerciseSlugs,
2287
+ status: "started",
2288
+ message: "Translation process started",
2289
+ })
2290
+
2265
2291
  await Promise.all(
2266
2292
  exerciseSlugs.map(async (slug: string) => {
2267
2293
  const readmePath = `courses/${courseSlug}/exercises/${slug}/README${getReadmeExtension(
@@ -2271,7 +2297,6 @@ export default class ServeCommand extends SessionCommand {
2271
2297
 
2272
2298
  await Promise.all(
2273
2299
  languageCodes.map(async (language: string) => {
2274
- // verify if the translation already exists
2275
2300
  const translationPath = `courses/${courseSlug}/exercises/${slug}/README${getReadmeExtension(
2276
2301
  language
2277
2302
  )}`
@@ -2281,6 +2306,12 @@ export default class ServeCommand extends SessionCommand {
2281
2306
  console.log(
2282
2307
  `Translation in ${language} already exists for exercise ${slug}`
2283
2308
  )
2309
+ emitToCourse(courseSlug, "translation-progress", {
2310
+ exercise: slug,
2311
+ language: language,
2312
+ status: "skipped",
2313
+ message: `Translation in ${language} already exists`,
2314
+ })
2284
2315
  return
2285
2316
  }
2286
2317
 
@@ -2292,6 +2323,13 @@ export default class ServeCommand extends SessionCommand {
2292
2323
  },
2293
2324
  `${process.env.HOST}/webhooks/${courseSlug}/${slug}/save-translation`
2294
2325
  )
2326
+
2327
+ emitToCourse(courseSlug, "translation-progress", {
2328
+ exercise: slug,
2329
+ language: language,
2330
+ status: "initiated",
2331
+ message: `Translation job initiated for ${slug} to ${language}`,
2332
+ })
2295
2333
  })
2296
2334
  )
2297
2335
  })
@@ -2339,8 +2377,6 @@ export default class ServeCommand extends SessionCommand {
2339
2377
  currentLanguages = Object.keys(courseJson.title)
2340
2378
  }
2341
2379
 
2342
- // Check that all the READMEs exists
2343
-
2344
2380
  const missingReadmeTranslations = []
2345
2381
  let firstAvailable = ""
2346
2382
  for (const languageCode of currentLanguages) {
@@ -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 })