@learnpack/learnpack 5.0.313 → 5.0.316
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 +341 -274
- package/lib/creatorDist/assets/{index-BI7U47zy.js → index-DxgQXudf.js} +1 -1
- package/lib/creatorDist/index.html +1 -1
- package/lib/utils/creatorUtilities.d.ts +2 -0
- package/lib/utils/creatorUtilities.js +37 -14
- package/lib/utils/rigoActions.d.ts +7 -0
- package/lib/utils/rigoActions.js +17 -1
- package/package.json +1 -1
- package/src/commands/serve.ts +422 -413
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +1 -1
- package/src/creatorDist/assets/{index-BI7U47zy.js → index-DxgQXudf.js} +1 -1
- package/src/creatorDist/index.html +1 -1
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +2414 -2414
- package/src/ui/_app/learnpack.svg +7 -7
- package/src/ui/_app/sw.js +59 -59
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/creatorUtilities.ts +536 -504
- package/src/utils/rigoActions.ts +29 -0
package/src/commands/serve.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
initialContentGenerator,
|
|
37
37
|
addInteractivity,
|
|
38
38
|
generateCodeChallenge,
|
|
39
|
+
generateStepSlug,
|
|
39
40
|
} from "../utils/rigoActions"
|
|
40
41
|
import * as dotenv from "dotenv"
|
|
41
42
|
|
|
@@ -43,6 +44,7 @@ import * as dotenv from "dotenv"
|
|
|
43
44
|
import {
|
|
44
45
|
getFilenameFromUrl,
|
|
45
46
|
getReadmeExtension,
|
|
47
|
+
insertStepInCorrectPosition,
|
|
46
48
|
} from "../utils/creatorUtilities"
|
|
47
49
|
// import { handleAssetCreation } from "./publish"
|
|
48
50
|
import axios from "axios"
|
|
@@ -281,41 +283,6 @@ const createMultiLangAsset = async (
|
|
|
281
283
|
}
|
|
282
284
|
}
|
|
283
285
|
|
|
284
|
-
async function startExerciseGeneration(
|
|
285
|
-
rigoToken: string,
|
|
286
|
-
steps: Lesson[],
|
|
287
|
-
packageContext: FormState,
|
|
288
|
-
exercise: Lesson,
|
|
289
|
-
courseSlug: string,
|
|
290
|
-
purposeSlug: string,
|
|
291
|
-
lastLesson = ""
|
|
292
|
-
): Promise<number> {
|
|
293
|
-
const exSlug = slugify(exercise.id + "-" + exercise.title)
|
|
294
|
-
console.log("Starting generation of", exSlug)
|
|
295
|
-
|
|
296
|
-
const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/exercise-processor/${exercise.id}/${rigoToken}`
|
|
297
|
-
|
|
298
|
-
const res = await readmeCreator(
|
|
299
|
-
rigoToken.trim(),
|
|
300
|
-
{
|
|
301
|
-
title: `${exercise.id} - ${exercise.title}`,
|
|
302
|
-
output_lang: packageContext.language || "en",
|
|
303
|
-
list_of_exercises: JSON.stringify(
|
|
304
|
-
steps.map(step => step.id + "-" + step.title)
|
|
305
|
-
),
|
|
306
|
-
tutorial_description: JSON.stringify(cleanFormState(packageContext)),
|
|
307
|
-
lesson_description: exercise.description,
|
|
308
|
-
kind: exercise.type.toLowerCase(),
|
|
309
|
-
last_lesson: lastLesson,
|
|
310
|
-
},
|
|
311
|
-
purposeSlug,
|
|
312
|
-
webhookUrl
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
console.log("README CREATOR RES", res)
|
|
316
|
-
return res.id
|
|
317
|
-
}
|
|
318
|
-
|
|
319
286
|
const lessonCleaner = (lesson: Lesson) => {
|
|
320
287
|
return {
|
|
321
288
|
...lesson,
|
|
@@ -341,7 +308,7 @@ async function startInitialContentGeneration(
|
|
|
341
308
|
const exSlug = slugify(exercise.id + "-" + exercise.title)
|
|
342
309
|
console.log("Starting initial content generation for", exSlug)
|
|
343
310
|
|
|
344
|
-
const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/initial-content-processor/${exercise.
|
|
311
|
+
const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/initial-content-processor/${exercise.uid}/${rigoToken}`
|
|
345
312
|
|
|
346
313
|
// Emit notification that initial content generation is starting
|
|
347
314
|
emitToCourse(courseSlug, "course-creation", {
|
|
@@ -388,7 +355,7 @@ async function startInteractivityGeneration(
|
|
|
388
355
|
const exSlug = slugify(exercise.id + "-" + exercise.title)
|
|
389
356
|
console.log("Starting interactivity generation for", exSlug)
|
|
390
357
|
|
|
391
|
-
const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/interactivity-processor/${exercise.
|
|
358
|
+
const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/interactivity-processor/${exercise.uid}/${rigoToken}`
|
|
392
359
|
|
|
393
360
|
// Emit notification that interactivity generation is starting
|
|
394
361
|
emitToCourse(courseSlug, "course-creation", {
|
|
@@ -399,7 +366,6 @@ async function startInteractivityGeneration(
|
|
|
399
366
|
|
|
400
367
|
const componentsYml = await fetchComponentsYml()
|
|
401
368
|
|
|
402
|
-
// Get current syllabus to include used_components
|
|
403
369
|
const currentSyllabus = await getSyllabus(courseSlug, bucket)
|
|
404
370
|
|
|
405
371
|
const fullSyllabus = {
|
|
@@ -424,7 +390,6 @@ async function startInteractivityGeneration(
|
|
|
424
390
|
webhookUrl
|
|
425
391
|
)
|
|
426
392
|
|
|
427
|
-
console.log("INTERACTIVITY GENERATOR RES", res)
|
|
428
393
|
return res.id
|
|
429
394
|
}
|
|
430
395
|
|
|
@@ -449,6 +414,14 @@ async function createInitialReadme(
|
|
|
449
414
|
}
|
|
450
415
|
}
|
|
451
416
|
|
|
417
|
+
const getConfigJSON = async (bucket: Bucket, courseSlug: string) => {
|
|
418
|
+
const configFile = await bucket.file(
|
|
419
|
+
`courses/${courseSlug}/.learn/config.json`
|
|
420
|
+
)
|
|
421
|
+
const [content] = await configFile.download()
|
|
422
|
+
return JSON.parse(content.toString())
|
|
423
|
+
}
|
|
424
|
+
|
|
452
425
|
async function getSyllabus(
|
|
453
426
|
courseSlug: string,
|
|
454
427
|
bucket: Bucket
|
|
@@ -499,18 +472,18 @@ async function updateUsedComponents(
|
|
|
499
472
|
|
|
500
473
|
async function updateLessonWithInitialContent(
|
|
501
474
|
courseSlug: string,
|
|
502
|
-
|
|
475
|
+
lessonUID: string,
|
|
503
476
|
initialResponse: any,
|
|
504
477
|
bucket: Bucket
|
|
505
|
-
): Promise<
|
|
478
|
+
): Promise<Lesson | null> {
|
|
506
479
|
const syllabus = await getSyllabus(courseSlug, bucket)
|
|
507
480
|
const lessonIndex = syllabus.lessons.findIndex(
|
|
508
|
-
lesson => lesson.
|
|
481
|
+
lesson => lesson.uid === lessonUID
|
|
509
482
|
)
|
|
510
483
|
|
|
511
484
|
if (lessonIndex === -1) {
|
|
512
|
-
console.error(`Lesson ${
|
|
513
|
-
return
|
|
485
|
+
console.error(`Lesson ${lessonUID} not found in syllabus`)
|
|
486
|
+
return null
|
|
514
487
|
}
|
|
515
488
|
|
|
516
489
|
const lesson = syllabus.lessons[lessonIndex]
|
|
@@ -519,22 +492,23 @@ async function updateLessonWithInitialContent(
|
|
|
519
492
|
lesson.initialContent = initialResponse.lesson_content
|
|
520
493
|
|
|
521
494
|
await saveSyllabus(courseSlug, syllabus, bucket)
|
|
495
|
+
return lesson
|
|
522
496
|
}
|
|
523
497
|
|
|
524
498
|
async function updateLessonStatusToError(
|
|
525
499
|
courseSlug: string,
|
|
526
|
-
|
|
500
|
+
lessonUID: string,
|
|
527
501
|
bucket: Bucket
|
|
528
502
|
): Promise<void> {
|
|
529
503
|
try {
|
|
530
504
|
const syllabus = await getSyllabus(courseSlug, bucket)
|
|
531
505
|
const lessonIndex = syllabus.lessons.findIndex(
|
|
532
|
-
lesson => lesson.
|
|
506
|
+
lesson => lesson.uid === lessonUID
|
|
533
507
|
)
|
|
534
508
|
|
|
535
509
|
if (lessonIndex === -1) {
|
|
536
510
|
console.error(
|
|
537
|
-
`Lesson ${
|
|
511
|
+
`Lesson ${lessonUID} not found in syllabus when updating status to error`
|
|
538
512
|
)
|
|
539
513
|
return
|
|
540
514
|
}
|
|
@@ -561,9 +535,9 @@ async function updateLessonStatusToError(
|
|
|
561
535
|
lesson.translations = currentTranslations
|
|
562
536
|
|
|
563
537
|
await saveSyllabus(courseSlug, syllabus, bucket)
|
|
564
|
-
console.log(`Updated lesson ${
|
|
538
|
+
console.log(`Updated lesson ${lessonUID} status to ERROR in syllabus`)
|
|
565
539
|
} catch (error) {
|
|
566
|
-
console.error(`Error updating lesson ${
|
|
540
|
+
console.error(`Error updating lesson ${lessonUID} status to ERROR:`, error)
|
|
567
541
|
}
|
|
568
542
|
}
|
|
569
543
|
|
|
@@ -1096,12 +1070,18 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1096
1070
|
)
|
|
1097
1071
|
|
|
1098
1072
|
app.post(
|
|
1099
|
-
"/actions/continue-generating/:courseSlug/:
|
|
1073
|
+
"/actions/continue-generating/:courseSlug/:lessonUid",
|
|
1100
1074
|
async (req, res) => {
|
|
1101
|
-
const { courseSlug,
|
|
1075
|
+
const { courseSlug, lessonUid } = req.params
|
|
1102
1076
|
const { feedback, mode } = req.body
|
|
1103
1077
|
const rigoToken = req.header("x-rigo-token")
|
|
1104
1078
|
|
|
1079
|
+
console.log("CONTINUE GENERATING REQUEST RECEIVED")
|
|
1080
|
+
// console.log("COURSE SLUG", courseSlug);
|
|
1081
|
+
console.log("LESSON UID", lessonUid)
|
|
1082
|
+
// console.log("FEEDBACK", feedback);
|
|
1083
|
+
// console.log("MODE", mode);
|
|
1084
|
+
|
|
1105
1085
|
if (!rigoToken) {
|
|
1106
1086
|
return res.status(400).json({
|
|
1107
1087
|
error: "Rigo token is required. x-rigo-token header is missing",
|
|
@@ -1111,10 +1091,10 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1111
1091
|
const syllabus = await getSyllabus(courseSlug, bucket)
|
|
1112
1092
|
|
|
1113
1093
|
const exercise = syllabus.lessons.find(
|
|
1114
|
-
lesson => lesson.
|
|
1094
|
+
lesson => lesson.uid === lessonUid
|
|
1115
1095
|
)
|
|
1116
1096
|
const exerciseIndex = syllabus.lessons.findIndex(
|
|
1117
|
-
lesson => lesson.
|
|
1097
|
+
lesson => lesson.uid === lessonUid
|
|
1118
1098
|
)
|
|
1119
1099
|
|
|
1120
1100
|
// previous exercise
|
|
@@ -1270,276 +1250,281 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1270
1250
|
}
|
|
1271
1251
|
})
|
|
1272
1252
|
|
|
1253
|
+
// app.post(
|
|
1254
|
+
// "/webhooks/:courseSlug/exercise-processor/:lessonID/:rigoToken",
|
|
1255
|
+
// async (req, res) => {
|
|
1256
|
+
// // console.log("Receiving a webhook to exercise processor")
|
|
1257
|
+
// const { courseSlug, lessonID, rigoToken } = req.params
|
|
1258
|
+
// const readme = req.body
|
|
1259
|
+
|
|
1260
|
+
// const syllabus = await bucket.file(
|
|
1261
|
+
// `courses/${courseSlug}/.learn/initialSyllabus.json`
|
|
1262
|
+
// )
|
|
1263
|
+
// const [content] = await syllabus.download()
|
|
1264
|
+
// const syllabusJson: Syllabus = JSON.parse(content.toString())
|
|
1265
|
+
|
|
1266
|
+
// if (readme.status === "ERROR") {
|
|
1267
|
+
// emitToCourse(courseSlug, "course-creation", {
|
|
1268
|
+
// lesson: lessonID,
|
|
1269
|
+
// status: "error",
|
|
1270
|
+
// log: `❌ Error generating the lesson ${lessonID}`,
|
|
1271
|
+
// })
|
|
1272
|
+
// }
|
|
1273
|
+
|
|
1274
|
+
// const exerciseIndex = syllabusJson.lessons.findIndex(
|
|
1275
|
+
// lesson => lesson.id === lessonID
|
|
1276
|
+
// )
|
|
1277
|
+
// if (exerciseIndex === -1) {
|
|
1278
|
+
// console.log(
|
|
1279
|
+
// "Exercise not found receiving webhook, this should not happen",
|
|
1280
|
+
// lessonID
|
|
1281
|
+
// )
|
|
1282
|
+
// return res.json({ status: "ERROR", error: "Exercise not found" })
|
|
1283
|
+
// }
|
|
1284
|
+
|
|
1285
|
+
// const exercise = syllabusJson.lessons[exerciseIndex]
|
|
1286
|
+
// if (!exercise) {
|
|
1287
|
+
// return res.json({
|
|
1288
|
+
// status: "ERROR",
|
|
1289
|
+
// error: "Exercise not found or is invalid",
|
|
1290
|
+
// })
|
|
1291
|
+
// }
|
|
1292
|
+
|
|
1293
|
+
// const nextExercise = syllabusJson.lessons[exerciseIndex + 1] || null
|
|
1294
|
+
|
|
1295
|
+
// const exSlug = slugify(exercise.id + "-" + exercise.title)
|
|
1296
|
+
|
|
1297
|
+
// const readability = checkReadability(
|
|
1298
|
+
// readme.parsed.content,
|
|
1299
|
+
// PARAMS.max_words,
|
|
1300
|
+
// 3
|
|
1301
|
+
// )
|
|
1302
|
+
|
|
1303
|
+
// emitToCourse(courseSlug, "course-creation", {
|
|
1304
|
+
// lesson: exSlug,
|
|
1305
|
+
// status: "generating",
|
|
1306
|
+
// log: `🔄 The lesson ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
|
|
1307
|
+
// })
|
|
1308
|
+
|
|
1309
|
+
// const exercisesDir = `courses/${courseSlug}/exercises`
|
|
1310
|
+
// const targetDir = `${exercisesDir}/${exSlug}`
|
|
1311
|
+
|
|
1312
|
+
// const readmeFilename = `README${getReadmeExtension(
|
|
1313
|
+
// readme.parsed.language_code
|
|
1314
|
+
// )}`
|
|
1315
|
+
|
|
1316
|
+
// await uploadFileToBucket(
|
|
1317
|
+
// bucket,
|
|
1318
|
+
// readability.newMarkdown,
|
|
1319
|
+
// `${targetDir}/${readmeFilename}`
|
|
1320
|
+
// )
|
|
1321
|
+
|
|
1322
|
+
// if (
|
|
1323
|
+
// exercise.type.toLowerCase() === "code" &&
|
|
1324
|
+
// readme.parsed.codefile_content
|
|
1325
|
+
// ) {
|
|
1326
|
+
// emitToCourse(courseSlug, "course-creation", {
|
|
1327
|
+
// lesson: exSlug,
|
|
1328
|
+
// status: "generating",
|
|
1329
|
+
// log: `🔄 Creating code file for ${exercise.title}`,
|
|
1330
|
+
// })
|
|
1331
|
+
|
|
1332
|
+
// await uploadFileToBucket(
|
|
1333
|
+
// bucket,
|
|
1334
|
+
// readme.parsed.codefile_content,
|
|
1335
|
+
// `${targetDir}/${readme.parsed.codefile_name.toLowerCase().trim()}`
|
|
1336
|
+
// )
|
|
1337
|
+
// emitToCourse(courseSlug, "course-creation", {
|
|
1338
|
+
// lesson: exSlug,
|
|
1339
|
+
// status: "generating",
|
|
1340
|
+
// log: `✅ Code file created for ${exercise.title}`,
|
|
1341
|
+
// })
|
|
1342
|
+
|
|
1343
|
+
// if (readme.parsed.solution_content) {
|
|
1344
|
+
// const codeFileName = readme.parsed.codefile_name
|
|
1345
|
+
// .toLowerCase()
|
|
1346
|
+
// .trim()
|
|
1347
|
+
// const solutionFileName = "solution.hide." + codeFileName
|
|
1348
|
+
// await uploadFileToBucket(
|
|
1349
|
+
// bucket,
|
|
1350
|
+
// readme.parsed.solution_content,
|
|
1351
|
+
// `${targetDir}/${solutionFileName}`
|
|
1352
|
+
// )
|
|
1353
|
+
// emitToCourse(courseSlug, "course-creation", {
|
|
1354
|
+
// lesson: exSlug,
|
|
1355
|
+
// status: "generating",
|
|
1356
|
+
// log: `✅ Solution file created for ${exercise.title}`,
|
|
1357
|
+
// })
|
|
1358
|
+
// }
|
|
1359
|
+
// }
|
|
1360
|
+
|
|
1361
|
+
// let nextCompletionId: number | null = null
|
|
1362
|
+
// if (
|
|
1363
|
+
// nextExercise &&
|
|
1364
|
+
// (exerciseIndex === 0 ||
|
|
1365
|
+
// !(exerciseIndex % 3 === 0) ||
|
|
1366
|
+
// syllabusJson.generationMode === "continue-with-all")
|
|
1367
|
+
// ) {
|
|
1368
|
+
// let feedback = ""
|
|
1369
|
+
// if (syllabusJson.feedback) {
|
|
1370
|
+
// feedback = `\n\nThe user added the following feedback with relation to the previous generations: ${syllabusJson.feedback}`
|
|
1371
|
+
// }
|
|
1372
|
+
|
|
1373
|
+
// nextCompletionId = await startExerciseGeneration(
|
|
1374
|
+
// rigoToken,
|
|
1375
|
+
// syllabusJson.lessons,
|
|
1376
|
+
// syllabusJson.courseInfo,
|
|
1377
|
+
// nextExercise,
|
|
1378
|
+
// courseSlug,
|
|
1379
|
+
// syllabusJson.courseInfo.purpose,
|
|
1380
|
+
// readme.parsed.content + "\n\n" + feedback
|
|
1381
|
+
// )
|
|
1382
|
+
// } else {
|
|
1383
|
+
// console.log(
|
|
1384
|
+
// "Stopping generation process at",
|
|
1385
|
+
// exerciseIndex,
|
|
1386
|
+
// exercise.title,
|
|
1387
|
+
// "because it's a multiple of 3"
|
|
1388
|
+
// )
|
|
1389
|
+
// }
|
|
1390
|
+
|
|
1391
|
+
// const newSyllabus = {
|
|
1392
|
+
// ...syllabusJson,
|
|
1393
|
+
// lessons: syllabusJson.lessons.map((lesson, index) => {
|
|
1394
|
+
// if (index === exerciseIndex) {
|
|
1395
|
+
// const currentTranslations = lesson.translations || {}
|
|
1396
|
+
// let currentTranslation =
|
|
1397
|
+
// currentTranslations[syllabusJson.courseInfo.language || "en"]
|
|
1398
|
+
// if (currentTranslation) {
|
|
1399
|
+
// currentTranslation.completedAt = Date.now()
|
|
1400
|
+
// } else {
|
|
1401
|
+
// currentTranslation = {
|
|
1402
|
+
// completionId: readme.id,
|
|
1403
|
+
// startedAt: Date.now(),
|
|
1404
|
+
// completedAt: Date.now(),
|
|
1405
|
+
// }
|
|
1406
|
+
// }
|
|
1407
|
+
|
|
1408
|
+
// currentTranslations[syllabusJson.courseInfo.language || "en"] =
|
|
1409
|
+
// currentTranslation
|
|
1410
|
+
// return {
|
|
1411
|
+
// ...lesson,
|
|
1412
|
+
// generated: true,
|
|
1413
|
+
// status: "DONE",
|
|
1414
|
+
// translations: {
|
|
1415
|
+
// [syllabusJson.courseInfo.language || "en"]: {
|
|
1416
|
+
// completionId: nextCompletionId,
|
|
1417
|
+
// completedAt: Date.now(),
|
|
1418
|
+
// },
|
|
1419
|
+
// },
|
|
1420
|
+
// }
|
|
1421
|
+
// }
|
|
1422
|
+
|
|
1423
|
+
// if (
|
|
1424
|
+
// nextExercise &&
|
|
1425
|
+
// nextExercise.id === lesson.id &&
|
|
1426
|
+
// nextCompletionId
|
|
1427
|
+
// ) {
|
|
1428
|
+
// return {
|
|
1429
|
+
// ...lesson,
|
|
1430
|
+
// generated: false,
|
|
1431
|
+
// status: "GENERATING",
|
|
1432
|
+
// translations: {
|
|
1433
|
+
// [syllabusJson.courseInfo.language || "en"]: {
|
|
1434
|
+
// completionId: nextCompletionId,
|
|
1435
|
+
// startedAt: Date.now(),
|
|
1436
|
+
// },
|
|
1437
|
+
// },
|
|
1438
|
+
// }
|
|
1439
|
+
// }
|
|
1440
|
+
|
|
1441
|
+
// return { ...lesson }
|
|
1442
|
+
// }),
|
|
1443
|
+
// }
|
|
1444
|
+
// console.log("New syllabus", newSyllabus)
|
|
1445
|
+
// await uploadFileToBucket(
|
|
1446
|
+
// bucket,
|
|
1447
|
+
// JSON.stringify(newSyllabus),
|
|
1448
|
+
// `courses/${courseSlug}/.learn/initialSyllabus.json`
|
|
1449
|
+
// )
|
|
1450
|
+
|
|
1451
|
+
// emitToCourse(courseSlug, "course-creation", {
|
|
1452
|
+
// lesson: exSlug,
|
|
1453
|
+
// status: "done",
|
|
1454
|
+
// log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
|
|
1455
|
+
// })
|
|
1456
|
+
// res.json({ status: "SUCCESS" })
|
|
1457
|
+
// }
|
|
1458
|
+
// )
|
|
1459
|
+
|
|
1460
|
+
// Phase 1: Initial content generation webhook
|
|
1273
1461
|
app.post(
|
|
1274
|
-
"/webhooks/:courseSlug/
|
|
1462
|
+
"/webhooks/:courseSlug/initial-content-processor/:lessonUID/:rigoToken",
|
|
1275
1463
|
async (req, res) => {
|
|
1276
|
-
|
|
1277
|
-
const
|
|
1278
|
-
const readme = req.body
|
|
1464
|
+
const { courseSlug, lessonUID, rigoToken } = req.params
|
|
1465
|
+
const response = req.body
|
|
1279
1466
|
|
|
1280
|
-
|
|
1281
|
-
`courses/${courseSlug}/.learn/initialSyllabus.json`
|
|
1282
|
-
)
|
|
1283
|
-
const [content] = await syllabus.download()
|
|
1284
|
-
const syllabusJson: Syllabus = JSON.parse(content.toString())
|
|
1467
|
+
console.log("RECEIVING INITIAL CONTENT WEBHOOK", response)
|
|
1285
1468
|
|
|
1286
|
-
|
|
1287
|
-
emitToCourse(courseSlug, "course-creation", {
|
|
1288
|
-
lesson: lessonID,
|
|
1289
|
-
status: "error",
|
|
1290
|
-
log: `❌ Error generating the lesson ${lessonID}`,
|
|
1291
|
-
})
|
|
1292
|
-
}
|
|
1469
|
+
const syllabus = await getSyllabus(courseSlug, bucket)
|
|
1293
1470
|
|
|
1294
|
-
const exerciseIndex =
|
|
1295
|
-
lesson => lesson.
|
|
1471
|
+
const exerciseIndex = syllabus.lessons.findIndex(
|
|
1472
|
+
lesson => lesson.uid === lessonUID
|
|
1296
1473
|
)
|
|
1474
|
+
|
|
1297
1475
|
if (exerciseIndex === -1) {
|
|
1298
|
-
console.
|
|
1299
|
-
"Exercise not found receiving webhook, this should not happen",
|
|
1300
|
-
lessonID
|
|
1301
|
-
)
|
|
1476
|
+
console.error("Exercise not found receiving webhook:", lessonUID)
|
|
1302
1477
|
return res.json({ status: "ERROR", error: "Exercise not found" })
|
|
1303
1478
|
}
|
|
1304
1479
|
|
|
1305
|
-
|
|
1306
|
-
if (!exercise) {
|
|
1307
|
-
return res.json({
|
|
1308
|
-
status: "ERROR",
|
|
1309
|
-
error: "Exercise not found or is invalid",
|
|
1310
|
-
})
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
const nextExercise = syllabusJson.lessons[exerciseIndex + 1] || null
|
|
1314
|
-
|
|
1480
|
+
let exercise: Lesson | null = syllabus.lessons[exerciseIndex]
|
|
1315
1481
|
const exSlug = slugify(exercise.id + "-" + exercise.title)
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
PARAMS.max_words,
|
|
1320
|
-
3
|
|
1321
|
-
)
|
|
1322
|
-
|
|
1323
|
-
emitToCourse(courseSlug, "course-creation", {
|
|
1324
|
-
lesson: exSlug,
|
|
1325
|
-
status: "generating",
|
|
1326
|
-
log: `🔄 The lesson ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
|
|
1327
|
-
})
|
|
1328
|
-
|
|
1329
|
-
const exercisesDir = `courses/${courseSlug}/exercises`
|
|
1330
|
-
const targetDir = `${exercisesDir}/${exSlug}`
|
|
1331
|
-
|
|
1332
|
-
const readmeFilename = `README${getReadmeExtension(
|
|
1333
|
-
readme.parsed.language_code
|
|
1334
|
-
)}`
|
|
1335
|
-
|
|
1336
|
-
await uploadFileToBucket(
|
|
1337
|
-
bucket,
|
|
1338
|
-
readability.newMarkdown,
|
|
1339
|
-
`${targetDir}/${readmeFilename}`
|
|
1340
|
-
)
|
|
1341
|
-
|
|
1342
|
-
if (
|
|
1343
|
-
exercise.type.toLowerCase() === "code" &&
|
|
1344
|
-
readme.parsed.codefile_content
|
|
1345
|
-
) {
|
|
1346
|
-
emitToCourse(courseSlug, "course-creation", {
|
|
1347
|
-
lesson: exSlug,
|
|
1348
|
-
status: "generating",
|
|
1349
|
-
log: `🔄 Creating code file for ${exercise.title}`,
|
|
1350
|
-
})
|
|
1351
|
-
|
|
1352
|
-
await uploadFileToBucket(
|
|
1353
|
-
bucket,
|
|
1354
|
-
readme.parsed.codefile_content,
|
|
1355
|
-
`${targetDir}/${readme.parsed.codefile_name.toLowerCase().trim()}`
|
|
1356
|
-
)
|
|
1482
|
+
// Handle errors
|
|
1483
|
+
if (response.status === "ERROR") {
|
|
1484
|
+
await updateLessonStatusToError(courseSlug, lessonUID, bucket)
|
|
1357
1485
|
emitToCourse(courseSlug, "course-creation", {
|
|
1358
1486
|
lesson: exSlug,
|
|
1359
|
-
status: "
|
|
1360
|
-
log:
|
|
1487
|
+
status: "error",
|
|
1488
|
+
log: `❌ Error generating initial content for lesson ${exSlug}`,
|
|
1361
1489
|
})
|
|
1362
1490
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
.toLowerCase()
|
|
1366
|
-
.trim()
|
|
1367
|
-
const solutionFileName = "solution.hide." + codeFileName
|
|
1368
|
-
await uploadFileToBucket(
|
|
1369
|
-
bucket,
|
|
1370
|
-
readme.parsed.solution_content,
|
|
1371
|
-
`${targetDir}/${solutionFileName}`
|
|
1372
|
-
)
|
|
1491
|
+
// Retry initial content generation
|
|
1492
|
+
try {
|
|
1373
1493
|
emitToCourse(courseSlug, "course-creation", {
|
|
1374
1494
|
lesson: exSlug,
|
|
1375
1495
|
status: "generating",
|
|
1376
|
-
log:
|
|
1496
|
+
log: `🔄 Retrying initial content generation for lesson ${exSlug}`,
|
|
1377
1497
|
})
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
let nextCompletionId: number | null = null
|
|
1382
|
-
if (
|
|
1383
|
-
nextExercise &&
|
|
1384
|
-
(exerciseIndex === 0 ||
|
|
1385
|
-
!(exerciseIndex % 3 === 0) ||
|
|
1386
|
-
syllabusJson.generationMode === "continue-with-all")
|
|
1387
|
-
) {
|
|
1388
|
-
let feedback = ""
|
|
1389
|
-
if (syllabusJson.feedback) {
|
|
1390
|
-
feedback = `\n\nThe user added the following feedback with relation to the previous generations: ${syllabusJson.feedback}`
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
nextCompletionId = await startExerciseGeneration(
|
|
1394
|
-
rigoToken,
|
|
1395
|
-
syllabusJson.lessons,
|
|
1396
|
-
syllabusJson.courseInfo,
|
|
1397
|
-
nextExercise,
|
|
1398
|
-
courseSlug,
|
|
1399
|
-
syllabusJson.courseInfo.purpose,
|
|
1400
|
-
readme.parsed.content + "\n\n" + feedback
|
|
1401
|
-
)
|
|
1402
|
-
} else {
|
|
1403
|
-
console.log(
|
|
1404
|
-
"Stopping generation process at",
|
|
1405
|
-
exerciseIndex,
|
|
1406
|
-
exercise.title,
|
|
1407
|
-
"because it's a multiple of 3"
|
|
1408
|
-
)
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
const newSyllabus = {
|
|
1412
|
-
...syllabusJson,
|
|
1413
|
-
lessons: syllabusJson.lessons.map((lesson, index) => {
|
|
1414
|
-
if (index === exerciseIndex) {
|
|
1415
|
-
const currentTranslations = lesson.translations || {}
|
|
1416
|
-
let currentTranslation =
|
|
1417
|
-
currentTranslations[syllabusJson.courseInfo.language || "en"]
|
|
1418
|
-
if (currentTranslation) {
|
|
1419
|
-
currentTranslation.completedAt = Date.now()
|
|
1420
|
-
} else {
|
|
1421
|
-
currentTranslation = {
|
|
1422
|
-
completionId: readme.id,
|
|
1423
|
-
startedAt: Date.now(),
|
|
1424
|
-
completedAt: Date.now(),
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
currentTranslations[syllabusJson.courseInfo.language || "en"] =
|
|
1429
|
-
currentTranslation
|
|
1430
|
-
return {
|
|
1431
|
-
...lesson,
|
|
1432
|
-
generated: true,
|
|
1433
|
-
status: "DONE",
|
|
1434
|
-
translations: {
|
|
1435
|
-
[syllabusJson.courseInfo.language || "en"]: {
|
|
1436
|
-
completionId: nextCompletionId,
|
|
1437
|
-
completedAt: Date.now(),
|
|
1438
|
-
},
|
|
1439
|
-
},
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
if (
|
|
1444
|
-
nextExercise &&
|
|
1445
|
-
nextExercise.id === lesson.id &&
|
|
1446
|
-
nextCompletionId
|
|
1447
|
-
) {
|
|
1448
|
-
return {
|
|
1449
|
-
...lesson,
|
|
1450
|
-
generated: false,
|
|
1451
|
-
status: "GENERATING",
|
|
1452
|
-
translations: {
|
|
1453
|
-
[syllabusJson.courseInfo.language || "en"]: {
|
|
1454
|
-
completionId: nextCompletionId,
|
|
1455
|
-
startedAt: Date.now(),
|
|
1456
|
-
},
|
|
1457
|
-
},
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
return { ...lesson }
|
|
1462
|
-
}),
|
|
1463
|
-
}
|
|
1464
|
-
console.log("New syllabus", newSyllabus)
|
|
1465
|
-
await uploadFileToBucket(
|
|
1466
|
-
bucket,
|
|
1467
|
-
JSON.stringify(newSyllabus),
|
|
1468
|
-
`courses/${courseSlug}/.learn/initialSyllabus.json`
|
|
1469
|
-
)
|
|
1470
|
-
|
|
1471
|
-
emitToCourse(courseSlug, "course-creation", {
|
|
1472
|
-
lesson: exSlug,
|
|
1473
|
-
status: "done",
|
|
1474
|
-
log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
|
|
1475
|
-
})
|
|
1476
|
-
res.json({ status: "SUCCESS" })
|
|
1477
|
-
}
|
|
1478
|
-
)
|
|
1479
1498
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
// Handle errors
|
|
1490
|
-
if (response.status === "ERROR") {
|
|
1491
|
-
await updateLessonStatusToError(courseSlug, lessonID, bucket)
|
|
1492
|
-
emitToCourse(courseSlug, "course-creation", {
|
|
1493
|
-
lesson: lessonID,
|
|
1494
|
-
status: "error",
|
|
1495
|
-
log: `❌ Error generating initial content for lesson ${lessonID}`,
|
|
1496
|
-
})
|
|
1497
|
-
|
|
1498
|
-
// Retry initial content generation
|
|
1499
|
-
try {
|
|
1500
|
-
const syllabus = await getSyllabus(courseSlug, bucket)
|
|
1501
|
-
const lessonIndex = syllabus.lessons.findIndex(
|
|
1502
|
-
lesson => lesson.id === lessonID
|
|
1499
|
+
const retryCompletionId = await startInitialContentGeneration(
|
|
1500
|
+
rigoToken,
|
|
1501
|
+
syllabus.lessons,
|
|
1502
|
+
syllabus.courseInfo,
|
|
1503
|
+
exercise,
|
|
1504
|
+
courseSlug,
|
|
1505
|
+
syllabus.courseInfo.purpose,
|
|
1506
|
+
""
|
|
1503
1507
|
)
|
|
1504
|
-
const exercise = syllabus.lessons[lessonIndex]
|
|
1505
|
-
|
|
1506
|
-
if (exercise) {
|
|
1507
|
-
emitToCourse(courseSlug, "course-creation", {
|
|
1508
|
-
lesson: lessonID,
|
|
1509
|
-
status: "generating",
|
|
1510
|
-
log: `🔄 Retrying initial content generation for lesson ${lessonID}`,
|
|
1511
|
-
})
|
|
1512
|
-
|
|
1513
|
-
const retryCompletionId = await startInitialContentGeneration(
|
|
1514
|
-
rigoToken,
|
|
1515
|
-
syllabus.lessons,
|
|
1516
|
-
syllabus.courseInfo,
|
|
1517
|
-
exercise,
|
|
1518
|
-
courseSlug,
|
|
1519
|
-
syllabus.courseInfo.purpose,
|
|
1520
|
-
""
|
|
1521
|
-
)
|
|
1522
1508
|
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
}
|
|
1532
|
-
await saveSyllabus(courseSlug, syllabus, bucket)
|
|
1509
|
+
// Update lesson status to show it's retrying
|
|
1510
|
+
exercise.status = "GENERATING"
|
|
1511
|
+
exercise.translations = {
|
|
1512
|
+
[syllabus.courseInfo.language || "en"]: {
|
|
1513
|
+
completionId: retryCompletionId,
|
|
1514
|
+
startedAt: Date.now(),
|
|
1515
|
+
completedAt: 0,
|
|
1516
|
+
},
|
|
1533
1517
|
}
|
|
1518
|
+
await saveSyllabus(courseSlug, syllabus, bucket)
|
|
1534
1519
|
} catch (retryError) {
|
|
1535
1520
|
console.error(
|
|
1536
1521
|
"Error retrying initial content generation:",
|
|
1537
1522
|
retryError
|
|
1538
1523
|
)
|
|
1539
1524
|
emitToCourse(courseSlug, "course-creation", {
|
|
1540
|
-
lesson:
|
|
1525
|
+
lesson: lessonUID,
|
|
1541
1526
|
status: "error",
|
|
1542
|
-
log: `❌ Failed to retry initial content generation for lesson ${
|
|
1527
|
+
log: `❌ Failed to retry initial content generation for lesson ${lessonUID}`,
|
|
1543
1528
|
})
|
|
1544
1529
|
}
|
|
1545
1530
|
|
|
@@ -1548,29 +1533,27 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1548
1533
|
|
|
1549
1534
|
try {
|
|
1550
1535
|
emitToCourse(courseSlug, "course-creation", {
|
|
1551
|
-
lesson:
|
|
1536
|
+
lesson: exSlug,
|
|
1552
1537
|
status: "generating",
|
|
1553
|
-
log: `✅ Initial content generated for lesson ${
|
|
1538
|
+
log: `✅ Initial content generated for lesson ${exSlug}`,
|
|
1554
1539
|
})
|
|
1555
1540
|
|
|
1556
1541
|
// Update lesson with initial content
|
|
1557
|
-
await updateLessonWithInitialContent(
|
|
1542
|
+
exercise = await updateLessonWithInitialContent(
|
|
1558
1543
|
courseSlug,
|
|
1559
|
-
|
|
1544
|
+
lessonUID,
|
|
1560
1545
|
response.parsed,
|
|
1561
1546
|
bucket
|
|
1562
1547
|
)
|
|
1563
1548
|
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
)
|
|
1569
|
-
const exercise = syllabus.lessons[lessonIndex]
|
|
1549
|
+
if (!exercise) {
|
|
1550
|
+
console.error("Exercise not found after updating initial content")
|
|
1551
|
+
return res.json({ status: "ERROR", error: "Exercise not found" })
|
|
1552
|
+
}
|
|
1570
1553
|
|
|
1571
1554
|
let lastLesson = ""
|
|
1572
1555
|
|
|
1573
|
-
const prevLessonIndex =
|
|
1556
|
+
const prevLessonIndex = exerciseIndex - 1
|
|
1574
1557
|
if (prevLessonIndex >= 0) {
|
|
1575
1558
|
try {
|
|
1576
1559
|
const prevLesson = syllabus.lessons[prevLessonIndex]
|
|
@@ -1590,50 +1573,48 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1590
1573
|
}
|
|
1591
1574
|
}
|
|
1592
1575
|
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
)
|
|
1604
|
-
|
|
1605
|
-
// Update lesson status to show it's in Phase 2
|
|
1606
|
-
exercise.status = "GENERATING"
|
|
1607
|
-
exercise.translations = {
|
|
1608
|
-
[syllabus.courseInfo.language || "en"]: {
|
|
1609
|
-
completionId,
|
|
1610
|
-
startedAt: Date.now(),
|
|
1611
|
-
completedAt: 0,
|
|
1612
|
-
},
|
|
1613
|
-
}
|
|
1614
|
-
await saveSyllabus(courseSlug, syllabus, bucket)
|
|
1576
|
+
const completionId = await startInteractivityGeneration(
|
|
1577
|
+
rigoToken,
|
|
1578
|
+
syllabus.lessons,
|
|
1579
|
+
syllabus.courseInfo,
|
|
1580
|
+
exercise,
|
|
1581
|
+
courseSlug,
|
|
1582
|
+
syllabus.courseInfo.purpose,
|
|
1583
|
+
bucket,
|
|
1584
|
+
lastLesson
|
|
1585
|
+
)
|
|
1615
1586
|
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1587
|
+
// Update lesson status to show it's in Phase 2
|
|
1588
|
+
exercise.status = "GENERATING"
|
|
1589
|
+
exercise.translations = {
|
|
1590
|
+
[syllabus.courseInfo.language || "en"]: {
|
|
1591
|
+
completionId,
|
|
1592
|
+
startedAt: Date.now(),
|
|
1593
|
+
completedAt: 0,
|
|
1594
|
+
},
|
|
1621
1595
|
}
|
|
1596
|
+
await saveSyllabus(courseSlug, syllabus, bucket)
|
|
1622
1597
|
|
|
1623
1598
|
emitToCourse(courseSlug, "course-creation", {
|
|
1624
|
-
lesson:
|
|
1599
|
+
lesson: exSlug,
|
|
1600
|
+
status: "generating",
|
|
1601
|
+
log: `🔄 Starting interactivity phase for lesson ${exercise.title}`,
|
|
1602
|
+
})
|
|
1603
|
+
|
|
1604
|
+
emitToCourse(courseSlug, "course-creation", {
|
|
1605
|
+
lesson: exSlug,
|
|
1625
1606
|
status: "initial-content-complete",
|
|
1626
|
-
log: `✅ Initial content generated for lesson ${
|
|
1607
|
+
log: `✅ Initial content generated for lesson ${exSlug}, starting interactivity phase`,
|
|
1627
1608
|
})
|
|
1628
1609
|
|
|
1629
1610
|
res.json({ status: "SUCCESS" })
|
|
1630
1611
|
} catch (error) {
|
|
1631
1612
|
console.error("Error processing initial content webhook:", error)
|
|
1632
|
-
await updateLessonStatusToError(courseSlug,
|
|
1613
|
+
await updateLessonStatusToError(courseSlug, lessonUID, bucket)
|
|
1633
1614
|
emitToCourse(courseSlug, "course-creation", {
|
|
1634
|
-
lesson:
|
|
1615
|
+
lesson: exSlug,
|
|
1635
1616
|
status: "error",
|
|
1636
|
-
log: `❌ Error processing initial content for lesson ${
|
|
1617
|
+
log: `❌ Error processing initial content for lesson ${exSlug}`,
|
|
1637
1618
|
})
|
|
1638
1619
|
res
|
|
1639
1620
|
.status(500)
|
|
@@ -1644,35 +1625,37 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1644
1625
|
|
|
1645
1626
|
// Phase 2: Interactivity generation webhook (replaces exercise-processor logic)
|
|
1646
1627
|
app.post(
|
|
1647
|
-
"/webhooks/:courseSlug/interactivity-processor/:
|
|
1628
|
+
"/webhooks/:courseSlug/interactivity-processor/:lessonUID/:rigoToken",
|
|
1648
1629
|
async (req, res) => {
|
|
1649
|
-
const { courseSlug,
|
|
1630
|
+
const { courseSlug, lessonUID, rigoToken } = req.params
|
|
1650
1631
|
const response = req.body
|
|
1651
1632
|
|
|
1652
|
-
console.log("RECEIVING INTERACTIVITY WEBHOOK"
|
|
1633
|
+
console.log("RECEIVING INTERACTIVITY WEBHOOK")
|
|
1634
|
+
// console.log("LESSON UID", lessonUID)
|
|
1635
|
+
// console.log("RESPONSE", response)
|
|
1653
1636
|
|
|
1654
1637
|
// Handle errors
|
|
1655
1638
|
if (response.status === "ERROR") {
|
|
1656
|
-
await updateLessonStatusToError(courseSlug,
|
|
1639
|
+
await updateLessonStatusToError(courseSlug, lessonUID, bucket)
|
|
1657
1640
|
emitToCourse(courseSlug, "course-creation", {
|
|
1658
|
-
lesson:
|
|
1641
|
+
lesson: lessonUID,
|
|
1659
1642
|
status: "error",
|
|
1660
|
-
log: `❌ Error adding interactivity to lesson ${
|
|
1643
|
+
log: `❌ Error adding interactivity to lesson ${lessonUID}`,
|
|
1661
1644
|
})
|
|
1662
1645
|
|
|
1663
1646
|
// Retry interactivity generation
|
|
1664
1647
|
try {
|
|
1665
1648
|
const syllabus = await getSyllabus(courseSlug, bucket)
|
|
1666
1649
|
const lessonIndex = syllabus.lessons.findIndex(
|
|
1667
|
-
lesson => lesson.
|
|
1650
|
+
lesson => lesson.uid === lessonUID
|
|
1668
1651
|
)
|
|
1669
1652
|
const exercise = syllabus.lessons[lessonIndex]
|
|
1670
1653
|
|
|
1671
1654
|
if (exercise && exercise.initialContent) {
|
|
1672
1655
|
emitToCourse(courseSlug, "course-creation", {
|
|
1673
|
-
lesson:
|
|
1656
|
+
lesson: lessonUID,
|
|
1674
1657
|
status: "generating",
|
|
1675
|
-
log: `🔄 Retrying interactivity generation for lesson ${
|
|
1658
|
+
log: `🔄 Retrying interactivity generation for lesson ${lessonUID}`,
|
|
1676
1659
|
})
|
|
1677
1660
|
|
|
1678
1661
|
// Get previous lesson content for context
|
|
@@ -1727,9 +1710,9 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1727
1710
|
retryError
|
|
1728
1711
|
)
|
|
1729
1712
|
emitToCourse(courseSlug, "course-creation", {
|
|
1730
|
-
lesson:
|
|
1713
|
+
lesson: lessonUID,
|
|
1731
1714
|
status: "error",
|
|
1732
|
-
log: `❌ Failed to retry interactivity generation for lesson ${
|
|
1715
|
+
log: `❌ Failed to retry interactivity generation for lesson ${lessonUID}`,
|
|
1733
1716
|
})
|
|
1734
1717
|
}
|
|
1735
1718
|
|
|
@@ -1739,11 +1722,11 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1739
1722
|
try {
|
|
1740
1723
|
const syllabus = await getSyllabus(courseSlug, bucket)
|
|
1741
1724
|
const exerciseIndex = syllabus.lessons.findIndex(
|
|
1742
|
-
lesson => lesson.
|
|
1725
|
+
lesson => lesson.uid === lessonUID
|
|
1743
1726
|
)
|
|
1744
1727
|
|
|
1745
1728
|
if (exerciseIndex === -1) {
|
|
1746
|
-
console.error("Exercise not found receiving webhook:",
|
|
1729
|
+
console.error("Exercise not found receiving webhook:", lessonUID)
|
|
1747
1730
|
return res.json({ status: "ERROR", error: "Exercise not found" })
|
|
1748
1731
|
}
|
|
1749
1732
|
|
|
@@ -1778,49 +1761,6 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1778
1761
|
`${targetDir}/${readmeFilename}`
|
|
1779
1762
|
)
|
|
1780
1763
|
|
|
1781
|
-
// Handle code files if it's a coding exercise
|
|
1782
|
-
if (
|
|
1783
|
-
exercise.type.toLowerCase() === "code" &&
|
|
1784
|
-
response.parsed.codefile_content
|
|
1785
|
-
) {
|
|
1786
|
-
emitToCourse(courseSlug, "course-creation", {
|
|
1787
|
-
lesson: exSlug,
|
|
1788
|
-
status: "generating",
|
|
1789
|
-
log: `🔄 Creating code file for ${exercise.title}`,
|
|
1790
|
-
})
|
|
1791
|
-
|
|
1792
|
-
await uploadFileToBucket(
|
|
1793
|
-
bucket,
|
|
1794
|
-
response.parsed.codefile_content,
|
|
1795
|
-
`${targetDir}/${response.parsed.codefile_name
|
|
1796
|
-
.toLowerCase()
|
|
1797
|
-
.trim()}`
|
|
1798
|
-
)
|
|
1799
|
-
|
|
1800
|
-
emitToCourse(courseSlug, "course-creation", {
|
|
1801
|
-
lesson: exSlug,
|
|
1802
|
-
status: "generating",
|
|
1803
|
-
log: `✅ Code file created for ${exercise.title}`,
|
|
1804
|
-
})
|
|
1805
|
-
|
|
1806
|
-
if (response.parsed.solution_content) {
|
|
1807
|
-
const codeFileName = response.parsed.codefile_name
|
|
1808
|
-
.toLowerCase()
|
|
1809
|
-
.trim()
|
|
1810
|
-
const solutionFileName = "solution.hide." + codeFileName
|
|
1811
|
-
await uploadFileToBucket(
|
|
1812
|
-
bucket,
|
|
1813
|
-
response.parsed.solution_content,
|
|
1814
|
-
`${targetDir}/${solutionFileName}`
|
|
1815
|
-
)
|
|
1816
|
-
emitToCourse(courseSlug, "course-creation", {
|
|
1817
|
-
lesson: exSlug,
|
|
1818
|
-
status: "generating",
|
|
1819
|
-
log: `✅ Solution file created for ${exercise.title}`,
|
|
1820
|
-
})
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
|
|
1824
1764
|
// Update used components if provided by the AI
|
|
1825
1765
|
if (
|
|
1826
1766
|
response.parsed.used_components &&
|
|
@@ -1858,11 +1798,11 @@ export default class ServeCommand extends SessionCommand {
|
|
|
1858
1798
|
res.json({ status: "SUCCESS" })
|
|
1859
1799
|
} catch (error) {
|
|
1860
1800
|
console.error("Error processing interactivity webhook:", error)
|
|
1861
|
-
await updateLessonStatusToError(courseSlug,
|
|
1801
|
+
await updateLessonStatusToError(courseSlug, lessonUID, bucket)
|
|
1862
1802
|
emitToCourse(courseSlug, "course-creation", {
|
|
1863
|
-
lesson:
|
|
1803
|
+
lesson: lessonUID,
|
|
1864
1804
|
status: "error",
|
|
1865
|
-
log: `❌ Error processing interactivity for lesson ${
|
|
1805
|
+
log: `❌ Error processing interactivity for lesson ${lessonUID}`,
|
|
1866
1806
|
})
|
|
1867
1807
|
res
|
|
1868
1808
|
.status(500)
|
|
@@ -2082,29 +2022,98 @@ export default class ServeCommand extends SessionCommand {
|
|
|
2082
2022
|
})
|
|
2083
2023
|
}
|
|
2084
2024
|
)
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
const
|
|
2025
|
+
// Create a new step for a course
|
|
2026
|
+
app.post("/course/:slug/create-step", async (req, res) => {
|
|
2027
|
+
console.log("POST /course/:slug/create-step")
|
|
2028
|
+
const params = req.params
|
|
2029
|
+
const rigoToken = req.header("x-rigo-token")
|
|
2089
2030
|
|
|
2090
|
-
if (!
|
|
2091
|
-
return res
|
|
2092
|
-
.status(400)
|
|
2093
|
-
.json({ error: "Missing title or readme content" })
|
|
2031
|
+
if (!rigoToken) {
|
|
2032
|
+
return res.status(400).json({ error: "RigoToken not found" })
|
|
2094
2033
|
}
|
|
2095
2034
|
|
|
2096
|
-
const
|
|
2035
|
+
const { description, stepIndex } = req.body
|
|
2036
|
+
|
|
2037
|
+
if (!description) {
|
|
2038
|
+
return res.status(400).json({ error: "Missing description" })
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
const courseSlug = params.slug
|
|
2042
|
+
|
|
2043
|
+
const config = await getConfigJSON(bucket, courseSlug)
|
|
2044
|
+
const initialSyllabus = await getSyllabus(courseSlug, bucket)
|
|
2097
2045
|
|
|
2098
|
-
const
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
const created = await file.exists()
|
|
2104
|
-
res.send({
|
|
2105
|
-
message: "File updated",
|
|
2106
|
-
created,
|
|
2046
|
+
const stepSlugResponse = await generateStepSlug(rigoToken, {
|
|
2047
|
+
description,
|
|
2048
|
+
stepIndex,
|
|
2049
|
+
courseInfo: JSON.stringify(config),
|
|
2050
|
+
lang: initialSyllabus.courseInfo.language || "en",
|
|
2107
2051
|
})
|
|
2052
|
+
|
|
2053
|
+
if (stepSlugResponse.status !== "SUCCESS") {
|
|
2054
|
+
return res.status(400).json({ error: stepSlugResponse.status_text })
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
console.log("STEP SLUG GENERATED BY RIGO", stepSlugResponse)
|
|
2058
|
+
|
|
2059
|
+
const stepSlug = stepSlugResponse.parsed.slug
|
|
2060
|
+
|
|
2061
|
+
// split the slug at the first -
|
|
2062
|
+
const stepId = stepSlug.split("-")[0].trim()
|
|
2063
|
+
|
|
2064
|
+
const stepTitle = stepSlug.replace(`${stepId}-`, "").trim()
|
|
2065
|
+
|
|
2066
|
+
const newLesson: Lesson = {
|
|
2067
|
+
id: stepId,
|
|
2068
|
+
title: stepTitle,
|
|
2069
|
+
description: description,
|
|
2070
|
+
type: "READ",
|
|
2071
|
+
duration: 2,
|
|
2072
|
+
generated: false,
|
|
2073
|
+
status: "GENERATING",
|
|
2074
|
+
initialContent: "",
|
|
2075
|
+
translations: {},
|
|
2076
|
+
uid: stepSlug,
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
const newLessons = insertStepInCorrectPosition(
|
|
2080
|
+
initialSyllabus.lessons,
|
|
2081
|
+
newLesson
|
|
2082
|
+
)
|
|
2083
|
+
// Use new two-phase generation workflow
|
|
2084
|
+
const completionId = await startInitialContentGeneration(
|
|
2085
|
+
rigoToken,
|
|
2086
|
+
newLessons,
|
|
2087
|
+
initialSyllabus.courseInfo,
|
|
2088
|
+
newLesson,
|
|
2089
|
+
courseSlug,
|
|
2090
|
+
initialSyllabus.courseInfo.purpose,
|
|
2091
|
+
"lastResult"
|
|
2092
|
+
)
|
|
2093
|
+
newLesson.translations = {
|
|
2094
|
+
[initialSyllabus.courseInfo.language || "en"]: {
|
|
2095
|
+
completionId,
|
|
2096
|
+
startedAt: Date.now(),
|
|
2097
|
+
completedAt: 0,
|
|
2098
|
+
},
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
await uploadFileToBucket(
|
|
2102
|
+
bucket,
|
|
2103
|
+
JSON.stringify({ ...initialSyllabus, lessons: newLessons }),
|
|
2104
|
+
`courses/${courseSlug}/.learn/initialSyllabus.json`
|
|
2105
|
+
)
|
|
2106
|
+
|
|
2107
|
+
const targetDir = `courses/${courseSlug}/exercises/${stepSlug}`
|
|
2108
|
+
|
|
2109
|
+
await uploadInitialReadme(
|
|
2110
|
+
bucket,
|
|
2111
|
+
stepSlug,
|
|
2112
|
+
targetDir,
|
|
2113
|
+
initialSyllabus.courseInfo
|
|
2114
|
+
)
|
|
2115
|
+
|
|
2116
|
+
res.json({ status: "SUCCESS", message: "Exercise generati on started!" })
|
|
2108
2117
|
})
|
|
2109
2118
|
|
|
2110
2119
|
app.put("/actions/rename", async (req, res) => {
|