@learnpack/learnpack 5.0.312 → 5.0.315

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.
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.processImage = exports.createLearnJson = void 0;
4
4
  const tslib_1 = require("tslib");
5
- require("newrelic");
6
5
  const command_1 = require("@oclif/command");
7
6
  const buffer_1 = require("buffer");
8
7
  const express = require("express");
@@ -34,6 +33,9 @@ const sidebarGenerator_1 = require("../utils/sidebarGenerator");
34
33
  const publish_1 = require("./publish");
35
34
  const export_1 = require("../utils/export");
36
35
  const frontMatter = require("front-matter");
36
+ if (process.env.NEW_RELIC_ENABLED === "true") {
37
+ require("newrelic");
38
+ }
37
39
  dotenv.config();
38
40
  const createLearnJson = (courseInfo) => {
39
41
  // console.log("courseInfo to create learn json", courseInfo)
@@ -75,18 +77,18 @@ async function fetchComponentsYml() {
75
77
  axios_1.default.get("https://raw.githubusercontent.com/learnpack/ide/refs/heads/master/docs/assessment_components.yml"),
76
78
  axios_1.default.get("https://raw.githubusercontent.com/learnpack/ide/refs/heads/master/docs/explanatory_components.yml"),
77
79
  ]);
78
- const combinedContent = `
79
- # ASSESSMENT COMPONENTS
80
- These components are designed for evaluation and knowledge assessment:
81
-
82
- ${assessmentResponse.data}
83
-
84
- ---
85
-
86
- # EXPLANATORY COMPONENTS
87
- These components are designed for explanation and learning support:
88
-
89
- ${explanatoryResponse.data}
80
+ const combinedContent = `
81
+ # ASSESSMENT COMPONENTS
82
+ These components are designed for evaluation and knowledge assessment:
83
+
84
+ ${assessmentResponse.data}
85
+
86
+ ---
87
+
88
+ # EXPLANATORY COMPONENTS
89
+ These components are designed for explanation and learning support:
90
+
91
+ ${explanatoryResponse.data}
90
92
  `;
91
93
  return combinedContent;
92
94
  }
@@ -120,10 +122,10 @@ const createInitialSidebar = async (slugs, initialLanguage = "en") => {
120
122
  return sidebar;
121
123
  };
122
124
  const uploadInitialReadme = async (bucket, exSlug, targetDir, packageContext) => {
123
- const isGeneratingText = `
124
- \`\`\`loader slug="${exSlug}"
125
- :rigo
126
- \`\`\`
125
+ const isGeneratingText = `
126
+ \`\`\`loader slug="${exSlug}"
127
+ :rigo
128
+ \`\`\`
127
129
  `;
128
130
  const readmeFilename = `README${(0, creatorUtilities_1.getReadmeExtension)(packageContext.language || "en")}`;
129
131
  await uploadFileToBucket(bucket, isGeneratingText, `${targetDir}/${readmeFilename}`);
@@ -163,29 +165,13 @@ const createMultiLangAsset = async (bucket, rigoToken, bcToken, courseSlug, cour
163
165
  all_translations.push(asset.slug);
164
166
  }
165
167
  };
166
- async function startExerciseGeneration(rigoToken, steps, packageContext, exercise, courseSlug, purposeSlug, lastLesson = "") {
167
- const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
168
- console.log("Starting generation of", exSlug);
169
- const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/exercise-processor/${exercise.id}/${rigoToken}`;
170
- const res = await (0, rigoActions_1.readmeCreator)(rigoToken.trim(), {
171
- title: `${exercise.id} - ${exercise.title}`,
172
- output_lang: packageContext.language || "en",
173
- list_of_exercises: JSON.stringify(steps.map(step => step.id + "-" + step.title)),
174
- tutorial_description: JSON.stringify(cleanFormState(packageContext)),
175
- lesson_description: exercise.description,
176
- kind: exercise.type.toLowerCase(),
177
- last_lesson: lastLesson,
178
- }, purposeSlug, webhookUrl);
179
- console.log("README CREATOR RES", res);
180
- return res.id;
181
- }
182
168
  const lessonCleaner = (lesson) => {
183
169
  return Object.assign(Object.assign({}, lesson), { duration: undefined, generated: undefined, status: undefined, translations: undefined, uid: undefined, initialContent: undefined, locked: undefined });
184
170
  };
185
171
  async function startInitialContentGeneration(rigoToken, steps, packageContext, exercise, courseSlug, purposeSlug, lastLesson = "") {
186
172
  const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
187
173
  console.log("Starting initial content generation for", exSlug);
188
- const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/initial-content-processor/${exercise.id}/${rigoToken}`;
174
+ const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/initial-content-processor/${exercise.uid}/${rigoToken}`;
189
175
  // Emit notification that initial content generation is starting
190
176
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
191
177
  lesson: exSlug,
@@ -211,7 +197,7 @@ async function startInitialContentGeneration(rigoToken, steps, packageContext, e
211
197
  async function startInteractivityGeneration(rigoToken, steps, packageContext, exercise, courseSlug, purposeSlug, bucket, lastLesson = "") {
212
198
  const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
213
199
  console.log("Starting interactivity generation for", exSlug);
214
- const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/interactivity-processor/${exercise.id}/${rigoToken}`;
200
+ const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/interactivity-processor/${exercise.uid}/${rigoToken}`;
215
201
  // Emit notification that interactivity generation is starting
216
202
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
217
203
  lesson: exSlug,
@@ -219,7 +205,6 @@ async function startInteractivityGeneration(rigoToken, steps, packageContext, ex
219
205
  log: `🔄 Starting interactivity generation for lesson: ${exercise.title}`,
220
206
  });
221
207
  const componentsYml = await fetchComponentsYml();
222
- // Get current syllabus to include used_components
223
208
  const currentSyllabus = await getSyllabus(courseSlug, bucket);
224
209
  const fullSyllabus = {
225
210
  steps: steps.map(lessonCleaner),
@@ -236,7 +221,6 @@ async function startInteractivityGeneration(rigoToken, steps, packageContext, ex
236
221
  output_language: packageContext.language || "en",
237
222
  current_syllabus: JSON.stringify(fullSyllabus),
238
223
  }, webhookUrl);
239
- console.log("INTERACTIVITY GENERATOR RES", res);
240
224
  return res.id;
241
225
  }
242
226
  async function createInitialReadme(tutorialInfo, tutorialSlug, rigoToken) {
@@ -252,6 +236,11 @@ async function createInitialReadme(tutorialInfo, tutorialSlug, rigoToken) {
252
236
  console.error("Error creating initial readme", error);
253
237
  }
254
238
  }
239
+ const getConfigJSON = async (bucket, courseSlug) => {
240
+ const configFile = await bucket.file(`courses/${courseSlug}/.learn/config.json`);
241
+ const [content] = await configFile.download();
242
+ return JSON.parse(content.toString());
243
+ };
255
244
  async function getSyllabus(courseSlug, bucket) {
256
245
  const syllabus = await bucket.file(`courses/${courseSlug}/.learn/initialSyllabus.json`);
257
246
  const [content] = await syllabus.download();
@@ -278,24 +267,25 @@ async function updateUsedComponents(courseSlug, usedComponents, bucket) {
278
267
  console.log("Updated component usage:", syllabus.used_components);
279
268
  await saveSyllabus(courseSlug, syllabus, bucket);
280
269
  }
281
- async function updateLessonWithInitialContent(courseSlug, lessonID, initialResponse, bucket) {
270
+ async function updateLessonWithInitialContent(courseSlug, lessonUID, initialResponse, bucket) {
282
271
  const syllabus = await getSyllabus(courseSlug, bucket);
283
- const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
272
+ const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUID);
284
273
  if (lessonIndex === -1) {
285
- console.error(`Lesson ${lessonID} not found in syllabus`);
286
- return;
274
+ console.error(`Lesson ${lessonUID} not found in syllabus`);
275
+ return null;
287
276
  }
288
277
  const lesson = syllabus.lessons[lessonIndex];
289
278
  // Update initial content
290
279
  lesson.initialContent = initialResponse.lesson_content;
291
280
  await saveSyllabus(courseSlug, syllabus, bucket);
281
+ return lesson;
292
282
  }
293
- async function updateLessonStatusToError(courseSlug, lessonID, bucket) {
283
+ async function updateLessonStatusToError(courseSlug, lessonUID, bucket) {
294
284
  try {
295
285
  const syllabus = await getSyllabus(courseSlug, bucket);
296
- const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
286
+ const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUID);
297
287
  if (lessonIndex === -1) {
298
- console.error(`Lesson ${lessonID} not found in syllabus when updating status to error`);
288
+ console.error(`Lesson ${lessonUID} not found in syllabus when updating status to error`);
299
289
  return;
300
290
  }
301
291
  const lesson = syllabus.lessons[lessonIndex];
@@ -316,10 +306,10 @@ async function updateLessonStatusToError(courseSlug, lessonID, bucket) {
316
306
  }
317
307
  lesson.translations = currentTranslations;
318
308
  await saveSyllabus(courseSlug, syllabus, bucket);
319
- console.log(`Updated lesson ${lessonID} status to ERROR in syllabus`);
309
+ console.log(`Updated lesson ${lessonUID} status to ERROR in syllabus`);
320
310
  }
321
311
  catch (error) {
322
- console.error(`Error updating lesson ${lessonID} status to ERROR:`, error);
312
+ console.error(`Error updating lesson ${lessonUID} status to ERROR:`, error);
323
313
  }
324
314
  }
325
315
  async function continueWithNextLesson(courseSlug, currentExerciseIndex, rigoToken, finalContent, bucket) {
@@ -687,18 +677,23 @@ class ServeCommand extends SessionCommand_1.default {
687
677
  res.status(500).json({ error: error.message });
688
678
  }
689
679
  });
690
- app.post("/actions/continue-generating/:courseSlug/:lessonId", async (req, res) => {
691
- const { courseSlug, lessonId } = req.params;
680
+ app.post("/actions/continue-generating/:courseSlug/:lessonUid", async (req, res) => {
681
+ const { courseSlug, lessonUid } = req.params;
692
682
  const { feedback, mode } = req.body;
693
683
  const rigoToken = req.header("x-rigo-token");
684
+ console.log("CONTINUE GENERATING REQUEST RECEIVED");
685
+ // console.log("COURSE SLUG", courseSlug);
686
+ console.log("LESSON UID", lessonUid);
687
+ // console.log("FEEDBACK", feedback);
688
+ // console.log("MODE", mode);
694
689
  if (!rigoToken) {
695
690
  return res.status(400).json({
696
691
  error: "Rigo token is required. x-rigo-token header is missing",
697
692
  });
698
693
  }
699
694
  const syllabus = await getSyllabus(courseSlug, bucket);
700
- const exercise = syllabus.lessons.find(lesson => lesson.id === lessonId);
701
- const exerciseIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonId);
695
+ const exercise = syllabus.lessons.find(lesson => lesson.uid === lessonUid);
696
+ const exerciseIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUid);
702
697
  // previous exercise
703
698
  let previousReadme = "---";
704
699
  const previousExercise = syllabus.lessons[exerciseIndex - 1];
@@ -817,189 +812,255 @@ class ServeCommand extends SessionCommand_1.default {
817
812
  });
818
813
  }
819
814
  });
820
- app.post("/webhooks/:courseSlug/exercise-processor/:lessonID/:rigoToken", async (req, res) => {
821
- // console.log("Receiving a webhook to exercise processor")
822
- const { courseSlug, lessonID, rigoToken } = req.params;
823
- const readme = req.body;
824
- const syllabus = await bucket.file(`courses/${courseSlug}/.learn/initialSyllabus.json`);
825
- const [content] = await syllabus.download();
826
- const syllabusJson = JSON.parse(content.toString());
827
- if (readme.status === "ERROR") {
828
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
829
- lesson: lessonID,
830
- status: "error",
831
- log: `❌ Error generating the lesson ${lessonID}`,
832
- });
833
- }
834
- const exerciseIndex = syllabusJson.lessons.findIndex(lesson => lesson.id === lessonID);
815
+ // app.post(
816
+ // "/webhooks/:courseSlug/exercise-processor/:lessonID/:rigoToken",
817
+ // async (req, res) => {
818
+ // // console.log("Receiving a webhook to exercise processor")
819
+ // const { courseSlug, lessonID, rigoToken } = req.params
820
+ // const readme = req.body
821
+ // const syllabus = await bucket.file(
822
+ // `courses/${courseSlug}/.learn/initialSyllabus.json`
823
+ // )
824
+ // const [content] = await syllabus.download()
825
+ // const syllabusJson: Syllabus = JSON.parse(content.toString())
826
+ // if (readme.status === "ERROR") {
827
+ // emitToCourse(courseSlug, "course-creation", {
828
+ // lesson: lessonID,
829
+ // status: "error",
830
+ // log: `❌ Error generating the lesson ${lessonID}`,
831
+ // })
832
+ // }
833
+ // const exerciseIndex = syllabusJson.lessons.findIndex(
834
+ // lesson => lesson.id === lessonID
835
+ // )
836
+ // if (exerciseIndex === -1) {
837
+ // console.log(
838
+ // "Exercise not found receiving webhook, this should not happen",
839
+ // lessonID
840
+ // )
841
+ // return res.json({ status: "ERROR", error: "Exercise not found" })
842
+ // }
843
+ // const exercise = syllabusJson.lessons[exerciseIndex]
844
+ // if (!exercise) {
845
+ // return res.json({
846
+ // status: "ERROR",
847
+ // error: "Exercise not found or is invalid",
848
+ // })
849
+ // }
850
+ // const nextExercise = syllabusJson.lessons[exerciseIndex + 1] || null
851
+ // const exSlug = slugify(exercise.id + "-" + exercise.title)
852
+ // const readability = checkReadability(
853
+ // readme.parsed.content,
854
+ // PARAMS.max_words,
855
+ // 3
856
+ // )
857
+ // emitToCourse(courseSlug, "course-creation", {
858
+ // lesson: exSlug,
859
+ // status: "generating",
860
+ // log: `🔄 The lesson ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
861
+ // })
862
+ // const exercisesDir = `courses/${courseSlug}/exercises`
863
+ // const targetDir = `${exercisesDir}/${exSlug}`
864
+ // const readmeFilename = `README${getReadmeExtension(
865
+ // readme.parsed.language_code
866
+ // )}`
867
+ // await uploadFileToBucket(
868
+ // bucket,
869
+ // readability.newMarkdown,
870
+ // `${targetDir}/${readmeFilename}`
871
+ // )
872
+ // if (
873
+ // exercise.type.toLowerCase() === "code" &&
874
+ // readme.parsed.codefile_content
875
+ // ) {
876
+ // emitToCourse(courseSlug, "course-creation", {
877
+ // lesson: exSlug,
878
+ // status: "generating",
879
+ // log: `🔄 Creating code file for ${exercise.title}`,
880
+ // })
881
+ // await uploadFileToBucket(
882
+ // bucket,
883
+ // readme.parsed.codefile_content,
884
+ // `${targetDir}/${readme.parsed.codefile_name.toLowerCase().trim()}`
885
+ // )
886
+ // emitToCourse(courseSlug, "course-creation", {
887
+ // lesson: exSlug,
888
+ // status: "generating",
889
+ // log: `✅ Code file created for ${exercise.title}`,
890
+ // })
891
+ // if (readme.parsed.solution_content) {
892
+ // const codeFileName = readme.parsed.codefile_name
893
+ // .toLowerCase()
894
+ // .trim()
895
+ // const solutionFileName = "solution.hide." + codeFileName
896
+ // await uploadFileToBucket(
897
+ // bucket,
898
+ // readme.parsed.solution_content,
899
+ // `${targetDir}/${solutionFileName}`
900
+ // )
901
+ // emitToCourse(courseSlug, "course-creation", {
902
+ // lesson: exSlug,
903
+ // status: "generating",
904
+ // log: `✅ Solution file created for ${exercise.title}`,
905
+ // })
906
+ // }
907
+ // }
908
+ // let nextCompletionId: number | null = null
909
+ // if (
910
+ // nextExercise &&
911
+ // (exerciseIndex === 0 ||
912
+ // !(exerciseIndex % 3 === 0) ||
913
+ // syllabusJson.generationMode === "continue-with-all")
914
+ // ) {
915
+ // let feedback = ""
916
+ // if (syllabusJson.feedback) {
917
+ // feedback = `\n\nThe user added the following feedback with relation to the previous generations: ${syllabusJson.feedback}`
918
+ // }
919
+ // nextCompletionId = await startExerciseGeneration(
920
+ // rigoToken,
921
+ // syllabusJson.lessons,
922
+ // syllabusJson.courseInfo,
923
+ // nextExercise,
924
+ // courseSlug,
925
+ // syllabusJson.courseInfo.purpose,
926
+ // readme.parsed.content + "\n\n" + feedback
927
+ // )
928
+ // } else {
929
+ // console.log(
930
+ // "Stopping generation process at",
931
+ // exerciseIndex,
932
+ // exercise.title,
933
+ // "because it's a multiple of 3"
934
+ // )
935
+ // }
936
+ // const newSyllabus = {
937
+ // ...syllabusJson,
938
+ // lessons: syllabusJson.lessons.map((lesson, index) => {
939
+ // if (index === exerciseIndex) {
940
+ // const currentTranslations = lesson.translations || {}
941
+ // let currentTranslation =
942
+ // currentTranslations[syllabusJson.courseInfo.language || "en"]
943
+ // if (currentTranslation) {
944
+ // currentTranslation.completedAt = Date.now()
945
+ // } else {
946
+ // currentTranslation = {
947
+ // completionId: readme.id,
948
+ // startedAt: Date.now(),
949
+ // completedAt: Date.now(),
950
+ // }
951
+ // }
952
+ // currentTranslations[syllabusJson.courseInfo.language || "en"] =
953
+ // currentTranslation
954
+ // return {
955
+ // ...lesson,
956
+ // generated: true,
957
+ // status: "DONE",
958
+ // translations: {
959
+ // [syllabusJson.courseInfo.language || "en"]: {
960
+ // completionId: nextCompletionId,
961
+ // completedAt: Date.now(),
962
+ // },
963
+ // },
964
+ // }
965
+ // }
966
+ // if (
967
+ // nextExercise &&
968
+ // nextExercise.id === lesson.id &&
969
+ // nextCompletionId
970
+ // ) {
971
+ // return {
972
+ // ...lesson,
973
+ // generated: false,
974
+ // status: "GENERATING",
975
+ // translations: {
976
+ // [syllabusJson.courseInfo.language || "en"]: {
977
+ // completionId: nextCompletionId,
978
+ // startedAt: Date.now(),
979
+ // },
980
+ // },
981
+ // }
982
+ // }
983
+ // return { ...lesson }
984
+ // }),
985
+ // }
986
+ // console.log("New syllabus", newSyllabus)
987
+ // await uploadFileToBucket(
988
+ // bucket,
989
+ // JSON.stringify(newSyllabus),
990
+ // `courses/${courseSlug}/.learn/initialSyllabus.json`
991
+ // )
992
+ // emitToCourse(courseSlug, "course-creation", {
993
+ // lesson: exSlug,
994
+ // status: "done",
995
+ // log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
996
+ // })
997
+ // res.json({ status: "SUCCESS" })
998
+ // }
999
+ // )
1000
+ // Phase 1: Initial content generation webhook
1001
+ app.post("/webhooks/:courseSlug/initial-content-processor/:lessonUID/:rigoToken", async (req, res) => {
1002
+ const { courseSlug, lessonUID, rigoToken } = req.params;
1003
+ const response = req.body;
1004
+ console.log("RECEIVING INITIAL CONTENT WEBHOOK", response);
1005
+ const syllabus = await getSyllabus(courseSlug, bucket);
1006
+ const exerciseIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUID);
835
1007
  if (exerciseIndex === -1) {
836
- console.log("Exercise not found receiving webhook, this should not happen", lessonID);
1008
+ console.error("Exercise not found receiving webhook:", lessonUID);
837
1009
  return res.json({ status: "ERROR", error: "Exercise not found" });
838
1010
  }
839
- const exercise = syllabusJson.lessons[exerciseIndex];
840
- if (!exercise) {
841
- return res.json({
842
- status: "ERROR",
843
- error: "Exercise not found or is invalid",
844
- });
845
- }
846
- const nextExercise = syllabusJson.lessons[exerciseIndex + 1] || null;
1011
+ let exercise = syllabus.lessons[exerciseIndex];
847
1012
  const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
848
- const readability = (0, creatorUtilities_2.checkReadability)(readme.parsed.content, PARAMS.max_words, 3);
849
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
850
- lesson: exSlug,
851
- status: "generating",
852
- log: `🔄 The lesson ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
853
- });
854
- const exercisesDir = `courses/${courseSlug}/exercises`;
855
- const targetDir = `${exercisesDir}/${exSlug}`;
856
- const readmeFilename = `README${(0, creatorUtilities_1.getReadmeExtension)(readme.parsed.language_code)}`;
857
- await uploadFileToBucket(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
858
- if (exercise.type.toLowerCase() === "code" &&
859
- readme.parsed.codefile_content) {
860
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
861
- lesson: exSlug,
862
- status: "generating",
863
- log: `🔄 Creating code file for ${exercise.title}`,
864
- });
865
- await uploadFileToBucket(bucket, readme.parsed.codefile_content, `${targetDir}/${readme.parsed.codefile_name.toLowerCase().trim()}`);
866
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
867
- lesson: exSlug,
868
- status: "generating",
869
- log: `✅ Code file created for ${exercise.title}`,
870
- });
871
- if (readme.parsed.solution_content) {
872
- const codeFileName = readme.parsed.codefile_name
873
- .toLowerCase()
874
- .trim();
875
- const solutionFileName = "solution.hide." + codeFileName;
876
- await uploadFileToBucket(bucket, readme.parsed.solution_content, `${targetDir}/${solutionFileName}`);
877
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
878
- lesson: exSlug,
879
- status: "generating",
880
- log: `✅ Solution file created for ${exercise.title}`,
881
- });
882
- }
883
- }
884
- let nextCompletionId = null;
885
- if (nextExercise &&
886
- (exerciseIndex === 0 ||
887
- !(exerciseIndex % 3 === 0) ||
888
- syllabusJson.generationMode === "continue-with-all")) {
889
- let feedback = "";
890
- if (syllabusJson.feedback) {
891
- feedback = `\n\nThe user added the following feedback with relation to the previous generations: ${syllabusJson.feedback}`;
892
- }
893
- nextCompletionId = await startExerciseGeneration(rigoToken, syllabusJson.lessons, syllabusJson.courseInfo, nextExercise, courseSlug, syllabusJson.courseInfo.purpose, readme.parsed.content + "\n\n" + feedback);
894
- }
895
- else {
896
- console.log("Stopping generation process at", exerciseIndex, exercise.title, "because it's a multiple of 3");
897
- }
898
- const newSyllabus = Object.assign(Object.assign({}, syllabusJson), { lessons: syllabusJson.lessons.map((lesson, index) => {
899
- if (index === exerciseIndex) {
900
- const currentTranslations = lesson.translations || {};
901
- let currentTranslation = currentTranslations[syllabusJson.courseInfo.language || "en"];
902
- if (currentTranslation) {
903
- currentTranslation.completedAt = Date.now();
904
- }
905
- else {
906
- currentTranslation = {
907
- completionId: readme.id,
908
- startedAt: Date.now(),
909
- completedAt: Date.now(),
910
- };
911
- }
912
- currentTranslations[syllabusJson.courseInfo.language || "en"] =
913
- currentTranslation;
914
- return Object.assign(Object.assign({}, lesson), { generated: true, status: "DONE", translations: {
915
- [syllabusJson.courseInfo.language || "en"]: {
916
- completionId: nextCompletionId,
917
- completedAt: Date.now(),
918
- },
919
- } });
920
- }
921
- if (nextExercise &&
922
- nextExercise.id === lesson.id &&
923
- nextCompletionId) {
924
- return Object.assign(Object.assign({}, lesson), { generated: false, status: "GENERATING", translations: {
925
- [syllabusJson.courseInfo.language || "en"]: {
926
- completionId: nextCompletionId,
927
- startedAt: Date.now(),
928
- },
929
- } });
930
- }
931
- return Object.assign({}, lesson);
932
- }) });
933
- console.log("New syllabus", newSyllabus);
934
- await uploadFileToBucket(bucket, JSON.stringify(newSyllabus), `courses/${courseSlug}/.learn/initialSyllabus.json`);
935
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
936
- lesson: exSlug,
937
- status: "done",
938
- log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
939
- });
940
- res.json({ status: "SUCCESS" });
941
- });
942
- // Phase 1: Initial content generation webhook
943
- app.post("/webhooks/:courseSlug/initial-content-processor/:lessonID/:rigoToken", async (req, res) => {
944
- const { courseSlug, lessonID, rigoToken } = req.params;
945
- const response = req.body;
946
- console.log("RECEIVING INITIAL CONTENT WEBHOOK", response);
947
1013
  // Handle errors
948
1014
  if (response.status === "ERROR") {
949
- await updateLessonStatusToError(courseSlug, lessonID, bucket);
1015
+ await updateLessonStatusToError(courseSlug, lessonUID, bucket);
950
1016
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
951
- lesson: lessonID,
1017
+ lesson: exSlug,
952
1018
  status: "error",
953
- log: `❌ Error generating initial content for lesson ${lessonID}`,
1019
+ log: `❌ Error generating initial content for lesson ${exSlug}`,
954
1020
  });
955
1021
  // Retry initial content generation
956
1022
  try {
957
- const syllabus = await getSyllabus(courseSlug, bucket);
958
- const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
959
- const exercise = syllabus.lessons[lessonIndex];
960
- if (exercise) {
961
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
962
- lesson: lessonID,
963
- status: "generating",
964
- log: `🔄 Retrying initial content generation for lesson ${lessonID}`,
965
- });
966
- const retryCompletionId = await startInitialContentGeneration(rigoToken, syllabus.lessons, syllabus.courseInfo, exercise, courseSlug, syllabus.courseInfo.purpose, "");
967
- // Update lesson status to show it's retrying
968
- exercise.status = "GENERATING";
969
- exercise.translations = {
970
- [syllabus.courseInfo.language || "en"]: {
971
- completionId: retryCompletionId,
972
- startedAt: Date.now(),
973
- completedAt: 0,
974
- },
975
- };
976
- await saveSyllabus(courseSlug, syllabus, bucket);
977
- }
1023
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1024
+ lesson: exSlug,
1025
+ status: "generating",
1026
+ log: `🔄 Retrying initial content generation for lesson ${exSlug}`,
1027
+ });
1028
+ const retryCompletionId = await startInitialContentGeneration(rigoToken, syllabus.lessons, syllabus.courseInfo, exercise, courseSlug, syllabus.courseInfo.purpose, "");
1029
+ // Update lesson status to show it's retrying
1030
+ exercise.status = "GENERATING";
1031
+ exercise.translations = {
1032
+ [syllabus.courseInfo.language || "en"]: {
1033
+ completionId: retryCompletionId,
1034
+ startedAt: Date.now(),
1035
+ completedAt: 0,
1036
+ },
1037
+ };
1038
+ await saveSyllabus(courseSlug, syllabus, bucket);
978
1039
  }
979
1040
  catch (retryError) {
980
1041
  console.error("Error retrying initial content generation:", retryError);
981
1042
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
982
- lesson: lessonID,
1043
+ lesson: lessonUID,
983
1044
  status: "error",
984
- log: `❌ Failed to retry initial content generation for lesson ${lessonID}`,
1045
+ log: `❌ Failed to retry initial content generation for lesson ${lessonUID}`,
985
1046
  });
986
1047
  }
987
1048
  return res.json({ status: "ERROR" });
988
1049
  }
989
1050
  try {
990
1051
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
991
- lesson: lessonID,
1052
+ lesson: exSlug,
992
1053
  status: "generating",
993
- log: `✅ Initial content generated for lesson ${lessonID}`,
1054
+ log: `✅ Initial content generated for lesson ${exSlug}`,
994
1055
  });
995
1056
  // Update lesson with initial content
996
- await updateLessonWithInitialContent(courseSlug, lessonID, response.parsed, bucket);
997
- // Start Phase 2: Add interactivity
998
- const syllabus = await getSyllabus(courseSlug, bucket);
999
- const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
1000
- const exercise = syllabus.lessons[lessonIndex];
1057
+ exercise = await updateLessonWithInitialContent(courseSlug, lessonUID, response.parsed, bucket);
1058
+ if (!exercise) {
1059
+ console.error("Exercise not found after updating initial content");
1060
+ return res.json({ status: "ERROR", error: "Exercise not found" });
1061
+ }
1001
1062
  let lastLesson = "";
1002
- const prevLessonIndex = lessonIndex - 1;
1063
+ const prevLessonIndex = exerciseIndex - 1;
1003
1064
  if (prevLessonIndex >= 0) {
1004
1065
  try {
1005
1066
  const prevLesson = syllabus.lessons[prevLessonIndex];
@@ -1013,38 +1074,36 @@ class ServeCommand extends SessionCommand_1.default {
1013
1074
  console.error("Error searching previous lesson content:", error);
1014
1075
  }
1015
1076
  }
1016
- if (exercise) {
1017
- const completionId = await startInteractivityGeneration(rigoToken, syllabus.lessons, syllabus.courseInfo, exercise, courseSlug, syllabus.courseInfo.purpose, bucket, lastLesson);
1018
- // Update lesson status to show it's in Phase 2
1019
- exercise.status = "GENERATING";
1020
- exercise.translations = {
1021
- [syllabus.courseInfo.language || "en"]: {
1022
- completionId,
1023
- startedAt: Date.now(),
1024
- completedAt: 0,
1025
- },
1026
- };
1027
- await saveSyllabus(courseSlug, syllabus, bucket);
1028
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1029
- lesson: lessonID,
1030
- status: "generating",
1031
- log: `🔄 Starting interactivity phase for lesson ${exercise.title}`,
1032
- });
1033
- }
1077
+ const completionId = await startInteractivityGeneration(rigoToken, syllabus.lessons, syllabus.courseInfo, exercise, courseSlug, syllabus.courseInfo.purpose, bucket, lastLesson);
1078
+ // Update lesson status to show it's in Phase 2
1079
+ exercise.status = "GENERATING";
1080
+ exercise.translations = {
1081
+ [syllabus.courseInfo.language || "en"]: {
1082
+ completionId,
1083
+ startedAt: Date.now(),
1084
+ completedAt: 0,
1085
+ },
1086
+ };
1087
+ await saveSyllabus(courseSlug, syllabus, bucket);
1034
1088
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1035
- lesson: lessonID,
1089
+ lesson: exSlug,
1090
+ status: "generating",
1091
+ log: `🔄 Starting interactivity phase for lesson ${exercise.title}`,
1092
+ });
1093
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1094
+ lesson: exSlug,
1036
1095
  status: "initial-content-complete",
1037
- log: `✅ Initial content generated for lesson ${lessonID}, starting interactivity phase`,
1096
+ log: `✅ Initial content generated for lesson ${exSlug}, starting interactivity phase`,
1038
1097
  });
1039
1098
  res.json({ status: "SUCCESS" });
1040
1099
  }
1041
1100
  catch (error) {
1042
1101
  console.error("Error processing initial content webhook:", error);
1043
- await updateLessonStatusToError(courseSlug, lessonID, bucket);
1102
+ await updateLessonStatusToError(courseSlug, lessonUID, bucket);
1044
1103
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1045
- lesson: lessonID,
1104
+ lesson: exSlug,
1046
1105
  status: "error",
1047
- log: `❌ Error processing initial content for lesson ${lessonID}`,
1106
+ log: `❌ Error processing initial content for lesson ${exSlug}`,
1048
1107
  });
1049
1108
  res
1050
1109
  .status(500)
@@ -1052,28 +1111,30 @@ class ServeCommand extends SessionCommand_1.default {
1052
1111
  }
1053
1112
  });
1054
1113
  // Phase 2: Interactivity generation webhook (replaces exercise-processor logic)
1055
- app.post("/webhooks/:courseSlug/interactivity-processor/:lessonID/:rigoToken", async (req, res) => {
1056
- const { courseSlug, lessonID, rigoToken } = req.params;
1114
+ app.post("/webhooks/:courseSlug/interactivity-processor/:lessonUID/:rigoToken", async (req, res) => {
1115
+ const { courseSlug, lessonUID, rigoToken } = req.params;
1057
1116
  const response = req.body;
1058
- console.log("RECEIVING INTERACTIVITY WEBHOOK", response);
1117
+ console.log("RECEIVING INTERACTIVITY WEBHOOK");
1118
+ // console.log("LESSON UID", lessonUID)
1119
+ // console.log("RESPONSE", response)
1059
1120
  // Handle errors
1060
1121
  if (response.status === "ERROR") {
1061
- await updateLessonStatusToError(courseSlug, lessonID, bucket);
1122
+ await updateLessonStatusToError(courseSlug, lessonUID, bucket);
1062
1123
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1063
- lesson: lessonID,
1124
+ lesson: lessonUID,
1064
1125
  status: "error",
1065
- log: `❌ Error adding interactivity to lesson ${lessonID}`,
1126
+ log: `❌ Error adding interactivity to lesson ${lessonUID}`,
1066
1127
  });
1067
1128
  // Retry interactivity generation
1068
1129
  try {
1069
1130
  const syllabus = await getSyllabus(courseSlug, bucket);
1070
- const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
1131
+ const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUID);
1071
1132
  const exercise = syllabus.lessons[lessonIndex];
1072
1133
  if (exercise && exercise.initialContent) {
1073
1134
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1074
- lesson: lessonID,
1135
+ lesson: lessonUID,
1075
1136
  status: "generating",
1076
- log: `🔄 Retrying interactivity generation for lesson ${lessonID}`,
1137
+ log: `🔄 Retrying interactivity generation for lesson ${lessonUID}`,
1077
1138
  });
1078
1139
  // Get previous lesson content for context
1079
1140
  let lastLesson = "";
@@ -1106,18 +1167,18 @@ class ServeCommand extends SessionCommand_1.default {
1106
1167
  catch (retryError) {
1107
1168
  console.error("Error retrying interactivity generation:", retryError);
1108
1169
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1109
- lesson: lessonID,
1170
+ lesson: lessonUID,
1110
1171
  status: "error",
1111
- log: `❌ Failed to retry interactivity generation for lesson ${lessonID}`,
1172
+ log: `❌ Failed to retry interactivity generation for lesson ${lessonUID}`,
1112
1173
  });
1113
1174
  }
1114
1175
  return res.json({ status: "ERROR" });
1115
1176
  }
1116
1177
  try {
1117
1178
  const syllabus = await getSyllabus(courseSlug, bucket);
1118
- const exerciseIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
1179
+ const exerciseIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUID);
1119
1180
  if (exerciseIndex === -1) {
1120
- console.error("Exercise not found receiving webhook:", lessonID);
1181
+ console.error("Exercise not found receiving webhook:", lessonUID);
1121
1182
  return res.json({ status: "ERROR", error: "Exercise not found" });
1122
1183
  }
1123
1184
  const exercise = syllabus.lessons[exerciseIndex];
@@ -1136,35 +1197,6 @@ class ServeCommand extends SessionCommand_1.default {
1136
1197
  syllabus.courseInfo.language ||
1137
1198
  "en")}`;
1138
1199
  await uploadFileToBucket(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
1139
- // Handle code files if it's a coding exercise
1140
- if (exercise.type.toLowerCase() === "code" &&
1141
- response.parsed.codefile_content) {
1142
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1143
- lesson: exSlug,
1144
- status: "generating",
1145
- log: `🔄 Creating code file for ${exercise.title}`,
1146
- });
1147
- await uploadFileToBucket(bucket, response.parsed.codefile_content, `${targetDir}/${response.parsed.codefile_name
1148
- .toLowerCase()
1149
- .trim()}`);
1150
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1151
- lesson: exSlug,
1152
- status: "generating",
1153
- log: `✅ Code file created for ${exercise.title}`,
1154
- });
1155
- if (response.parsed.solution_content) {
1156
- const codeFileName = response.parsed.codefile_name
1157
- .toLowerCase()
1158
- .trim();
1159
- const solutionFileName = "solution.hide." + codeFileName;
1160
- await uploadFileToBucket(bucket, response.parsed.solution_content, `${targetDir}/${solutionFileName}`);
1161
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1162
- lesson: exSlug,
1163
- status: "generating",
1164
- log: `✅ Solution file created for ${exercise.title}`,
1165
- });
1166
- }
1167
- }
1168
1200
  // Update used components if provided by the AI
1169
1201
  if (response.parsed.used_components &&
1170
1202
  Array.isArray(response.parsed.used_components)) {
@@ -1186,11 +1218,11 @@ class ServeCommand extends SessionCommand_1.default {
1186
1218
  }
1187
1219
  catch (error) {
1188
1220
  console.error("Error processing interactivity webhook:", error);
1189
- await updateLessonStatusToError(courseSlug, lessonID, bucket);
1221
+ await updateLessonStatusToError(courseSlug, lessonUID, bucket);
1190
1222
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1191
- lesson: lessonID,
1223
+ lesson: lessonUID,
1192
1224
  status: "error",
1193
- log: `❌ Error processing interactivity for lesson ${lessonID}`,
1225
+ log: `❌ Error processing interactivity for lesson ${lessonUID}`,
1194
1226
  });
1195
1227
  res
1196
1228
  .status(500)
@@ -1348,24 +1380,61 @@ class ServeCommand extends SessionCommand_1.default {
1348
1380
  created,
1349
1381
  });
1350
1382
  });
1351
- app.post("/exercise/:slug/create", async (req, res) => {
1352
- console.log("POST /exercise/:slug/create");
1353
- const query = req.query;
1354
- const { title, readme, language } = req.body;
1355
- if (!title || !readme) {
1356
- return res
1357
- .status(400)
1358
- .json({ error: "Missing title or readme content" });
1383
+ // Create a new step for a course
1384
+ app.post("/course/:slug/create-step", async (req, res) => {
1385
+ console.log("POST /course/:slug/create-step");
1386
+ const params = req.params;
1387
+ const rigoToken = req.header("x-rigo-token");
1388
+ if (!rigoToken) {
1389
+ return res.status(400).json({ error: "RigoToken not found" });
1359
1390
  }
1360
- const courseSlug = query.slug;
1361
- const fileName = `courses/${courseSlug}/exercises/${title}/README${(0, creatorUtilities_1.getReadmeExtension)(language)}`;
1362
- const file = bucket.file(fileName);
1363
- await file.save(readme);
1364
- const created = await file.exists();
1365
- res.send({
1366
- message: "File updated",
1367
- created,
1391
+ const { description, stepIndex } = req.body;
1392
+ if (!description) {
1393
+ return res.status(400).json({ error: "Missing description" });
1394
+ }
1395
+ const courseSlug = params.slug;
1396
+ const config = await getConfigJSON(bucket, courseSlug);
1397
+ const initialSyllabus = await getSyllabus(courseSlug, bucket);
1398
+ const stepSlugResponse = await (0, rigoActions_1.generateStepSlug)(rigoToken, {
1399
+ description,
1400
+ stepIndex,
1401
+ courseInfo: JSON.stringify(config),
1402
+ lang: initialSyllabus.courseInfo.language || "en",
1368
1403
  });
1404
+ if (stepSlugResponse.status !== "SUCCESS") {
1405
+ return res.status(400).json({ error: stepSlugResponse.status_text });
1406
+ }
1407
+ console.log("STEP SLUG GENERATED BY RIGO", stepSlugResponse);
1408
+ const stepSlug = stepSlugResponse.parsed.slug;
1409
+ // split the slug at the first -
1410
+ const stepId = stepSlug.split("-")[0].trim();
1411
+ const stepTitle = stepSlug.replace(`${stepId}-`, "").trim();
1412
+ const newLesson = {
1413
+ id: stepId,
1414
+ title: stepTitle,
1415
+ description: description,
1416
+ type: "READ",
1417
+ duration: 2,
1418
+ generated: false,
1419
+ status: "GENERATING",
1420
+ initialContent: "",
1421
+ translations: {},
1422
+ uid: stepSlug,
1423
+ };
1424
+ const newLessons = (0, creatorUtilities_1.insertStepInCorrectPosition)(initialSyllabus.lessons, newLesson);
1425
+ // Use new two-phase generation workflow
1426
+ const completionId = await startInitialContentGeneration(rigoToken, newLessons, initialSyllabus.courseInfo, newLesson, courseSlug, initialSyllabus.courseInfo.purpose, "lastResult");
1427
+ newLesson.translations = {
1428
+ [initialSyllabus.courseInfo.language || "en"]: {
1429
+ completionId,
1430
+ startedAt: Date.now(),
1431
+ completedAt: 0,
1432
+ },
1433
+ };
1434
+ await uploadFileToBucket(bucket, JSON.stringify(Object.assign(Object.assign({}, initialSyllabus), { lessons: newLessons })), `courses/${courseSlug}/.learn/initialSyllabus.json`);
1435
+ const targetDir = `courses/${courseSlug}/exercises/${stepSlug}`;
1436
+ await uploadInitialReadme(bucket, stepSlug, targetDir, initialSyllabus.courseInfo);
1437
+ res.json({ status: "SUCCESS", message: "Exercise generati on started!" });
1369
1438
  });
1370
1439
  app.put("/actions/rename", async (req, res) => {
1371
1440
  console.log("PUT /actions/rename");
@@ -1844,7 +1913,9 @@ class ServeCommand extends SessionCommand_1.default {
1844
1913
  }
1845
1914
  catch (error) {
1846
1915
  console.error("❌ /actions/fetch error:", error.message || error);
1847
- res.status(500).json({ error: error.message || "Failed to fetch link" });
1916
+ res
1917
+ .status(500)
1918
+ .json({ error: error.message || "Failed to fetch link" });
1848
1919
  }
1849
1920
  });
1850
1921
  app.delete("/packages/:slug", async (req, res) => {
@@ -2118,7 +2189,9 @@ class ServeCommand extends SessionCommand_1.default {
2118
2189
  }
2119
2190
  catch (error) {
2120
2191
  console.error("Export error:", error);
2121
- res.status(500).json({ error: "Export failed", details: error.message });
2192
+ res
2193
+ .status(500)
2194
+ .json({ error: "Export failed", details: error.message });
2122
2195
  }
2123
2196
  });
2124
2197
  server.listen(PORT, () => {