@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.
@@ -165,29 +165,13 @@ const createMultiLangAsset = async (bucket, rigoToken, bcToken, courseSlug, cour
165
165
  all_translations.push(asset.slug);
166
166
  }
167
167
  };
168
- async function startExerciseGeneration(rigoToken, steps, packageContext, exercise, courseSlug, purposeSlug, lastLesson = "") {
169
- const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
170
- console.log("Starting generation of", exSlug);
171
- const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/exercise-processor/${exercise.id}/${rigoToken}`;
172
- const res = await (0, rigoActions_1.readmeCreator)(rigoToken.trim(), {
173
- title: `${exercise.id} - ${exercise.title}`,
174
- output_lang: packageContext.language || "en",
175
- list_of_exercises: JSON.stringify(steps.map(step => step.id + "-" + step.title)),
176
- tutorial_description: JSON.stringify(cleanFormState(packageContext)),
177
- lesson_description: exercise.description,
178
- kind: exercise.type.toLowerCase(),
179
- last_lesson: lastLesson,
180
- }, purposeSlug, webhookUrl);
181
- console.log("README CREATOR RES", res);
182
- return res.id;
183
- }
184
168
  const lessonCleaner = (lesson) => {
185
169
  return Object.assign(Object.assign({}, lesson), { duration: undefined, generated: undefined, status: undefined, translations: undefined, uid: undefined, initialContent: undefined, locked: undefined });
186
170
  };
187
171
  async function startInitialContentGeneration(rigoToken, steps, packageContext, exercise, courseSlug, purposeSlug, lastLesson = "") {
188
172
  const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
189
173
  console.log("Starting initial content generation for", exSlug);
190
- 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}`;
191
175
  // Emit notification that initial content generation is starting
192
176
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
193
177
  lesson: exSlug,
@@ -213,7 +197,7 @@ async function startInitialContentGeneration(rigoToken, steps, packageContext, e
213
197
  async function startInteractivityGeneration(rigoToken, steps, packageContext, exercise, courseSlug, purposeSlug, bucket, lastLesson = "") {
214
198
  const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
215
199
  console.log("Starting interactivity generation for", exSlug);
216
- 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}`;
217
201
  // Emit notification that interactivity generation is starting
218
202
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
219
203
  lesson: exSlug,
@@ -221,7 +205,6 @@ async function startInteractivityGeneration(rigoToken, steps, packageContext, ex
221
205
  log: `🔄 Starting interactivity generation for lesson: ${exercise.title}`,
222
206
  });
223
207
  const componentsYml = await fetchComponentsYml();
224
- // Get current syllabus to include used_components
225
208
  const currentSyllabus = await getSyllabus(courseSlug, bucket);
226
209
  const fullSyllabus = {
227
210
  steps: steps.map(lessonCleaner),
@@ -238,7 +221,6 @@ async function startInteractivityGeneration(rigoToken, steps, packageContext, ex
238
221
  output_language: packageContext.language || "en",
239
222
  current_syllabus: JSON.stringify(fullSyllabus),
240
223
  }, webhookUrl);
241
- console.log("INTERACTIVITY GENERATOR RES", res);
242
224
  return res.id;
243
225
  }
244
226
  async function createInitialReadme(tutorialInfo, tutorialSlug, rigoToken) {
@@ -254,6 +236,11 @@ async function createInitialReadme(tutorialInfo, tutorialSlug, rigoToken) {
254
236
  console.error("Error creating initial readme", error);
255
237
  }
256
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
+ };
257
244
  async function getSyllabus(courseSlug, bucket) {
258
245
  const syllabus = await bucket.file(`courses/${courseSlug}/.learn/initialSyllabus.json`);
259
246
  const [content] = await syllabus.download();
@@ -280,24 +267,25 @@ async function updateUsedComponents(courseSlug, usedComponents, bucket) {
280
267
  console.log("Updated component usage:", syllabus.used_components);
281
268
  await saveSyllabus(courseSlug, syllabus, bucket);
282
269
  }
283
- async function updateLessonWithInitialContent(courseSlug, lessonID, initialResponse, bucket) {
270
+ async function updateLessonWithInitialContent(courseSlug, lessonUID, initialResponse, bucket) {
284
271
  const syllabus = await getSyllabus(courseSlug, bucket);
285
- const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
272
+ const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUID);
286
273
  if (lessonIndex === -1) {
287
- console.error(`Lesson ${lessonID} not found in syllabus`);
288
- return;
274
+ console.error(`Lesson ${lessonUID} not found in syllabus`);
275
+ return null;
289
276
  }
290
277
  const lesson = syllabus.lessons[lessonIndex];
291
278
  // Update initial content
292
279
  lesson.initialContent = initialResponse.lesson_content;
293
280
  await saveSyllabus(courseSlug, syllabus, bucket);
281
+ return lesson;
294
282
  }
295
- async function updateLessonStatusToError(courseSlug, lessonID, bucket) {
283
+ async function updateLessonStatusToError(courseSlug, lessonUID, bucket) {
296
284
  try {
297
285
  const syllabus = await getSyllabus(courseSlug, bucket);
298
- const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
286
+ const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUID);
299
287
  if (lessonIndex === -1) {
300
- 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`);
301
289
  return;
302
290
  }
303
291
  const lesson = syllabus.lessons[lessonIndex];
@@ -318,10 +306,10 @@ async function updateLessonStatusToError(courseSlug, lessonID, bucket) {
318
306
  }
319
307
  lesson.translations = currentTranslations;
320
308
  await saveSyllabus(courseSlug, syllabus, bucket);
321
- console.log(`Updated lesson ${lessonID} status to ERROR in syllabus`);
309
+ console.log(`Updated lesson ${lessonUID} status to ERROR in syllabus`);
322
310
  }
323
311
  catch (error) {
324
- console.error(`Error updating lesson ${lessonID} status to ERROR:`, error);
312
+ console.error(`Error updating lesson ${lessonUID} status to ERROR:`, error);
325
313
  }
326
314
  }
327
315
  async function continueWithNextLesson(courseSlug, currentExerciseIndex, rigoToken, finalContent, bucket) {
@@ -689,18 +677,23 @@ class ServeCommand extends SessionCommand_1.default {
689
677
  res.status(500).json({ error: error.message });
690
678
  }
691
679
  });
692
- app.post("/actions/continue-generating/:courseSlug/:lessonId", async (req, res) => {
693
- const { courseSlug, lessonId } = req.params;
680
+ app.post("/actions/continue-generating/:courseSlug/:lessonUid", async (req, res) => {
681
+ const { courseSlug, lessonUid } = req.params;
694
682
  const { feedback, mode } = req.body;
695
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);
696
689
  if (!rigoToken) {
697
690
  return res.status(400).json({
698
691
  error: "Rigo token is required. x-rigo-token header is missing",
699
692
  });
700
693
  }
701
694
  const syllabus = await getSyllabus(courseSlug, bucket);
702
- const exercise = syllabus.lessons.find(lesson => lesson.id === lessonId);
703
- 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);
704
697
  // previous exercise
705
698
  let previousReadme = "---";
706
699
  const previousExercise = syllabus.lessons[exerciseIndex - 1];
@@ -819,189 +812,255 @@ class ServeCommand extends SessionCommand_1.default {
819
812
  });
820
813
  }
821
814
  });
822
- app.post("/webhooks/:courseSlug/exercise-processor/:lessonID/:rigoToken", async (req, res) => {
823
- // console.log("Receiving a webhook to exercise processor")
824
- const { courseSlug, lessonID, rigoToken } = req.params;
825
- const readme = req.body;
826
- const syllabus = await bucket.file(`courses/${courseSlug}/.learn/initialSyllabus.json`);
827
- const [content] = await syllabus.download();
828
- const syllabusJson = JSON.parse(content.toString());
829
- if (readme.status === "ERROR") {
830
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
831
- lesson: lessonID,
832
- status: "error",
833
- log: `❌ Error generating the lesson ${lessonID}`,
834
- });
835
- }
836
- 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);
837
1007
  if (exerciseIndex === -1) {
838
- console.log("Exercise not found receiving webhook, this should not happen", lessonID);
1008
+ console.error("Exercise not found receiving webhook:", lessonUID);
839
1009
  return res.json({ status: "ERROR", error: "Exercise not found" });
840
1010
  }
841
- const exercise = syllabusJson.lessons[exerciseIndex];
842
- if (!exercise) {
843
- return res.json({
844
- status: "ERROR",
845
- error: "Exercise not found or is invalid",
846
- });
847
- }
848
- const nextExercise = syllabusJson.lessons[exerciseIndex + 1] || null;
1011
+ let exercise = syllabus.lessons[exerciseIndex];
849
1012
  const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
850
- const readability = (0, creatorUtilities_2.checkReadability)(readme.parsed.content, PARAMS.max_words, 3);
851
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
852
- lesson: exSlug,
853
- status: "generating",
854
- log: `🔄 The lesson ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
855
- });
856
- const exercisesDir = `courses/${courseSlug}/exercises`;
857
- const targetDir = `${exercisesDir}/${exSlug}`;
858
- const readmeFilename = `README${(0, creatorUtilities_1.getReadmeExtension)(readme.parsed.language_code)}`;
859
- await uploadFileToBucket(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
860
- if (exercise.type.toLowerCase() === "code" &&
861
- readme.parsed.codefile_content) {
862
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
863
- lesson: exSlug,
864
- status: "generating",
865
- log: `🔄 Creating code file for ${exercise.title}`,
866
- });
867
- await uploadFileToBucket(bucket, readme.parsed.codefile_content, `${targetDir}/${readme.parsed.codefile_name.toLowerCase().trim()}`);
868
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
869
- lesson: exSlug,
870
- status: "generating",
871
- log: `✅ Code file created for ${exercise.title}`,
872
- });
873
- if (readme.parsed.solution_content) {
874
- const codeFileName = readme.parsed.codefile_name
875
- .toLowerCase()
876
- .trim();
877
- const solutionFileName = "solution.hide." + codeFileName;
878
- await uploadFileToBucket(bucket, readme.parsed.solution_content, `${targetDir}/${solutionFileName}`);
879
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
880
- lesson: exSlug,
881
- status: "generating",
882
- log: `✅ Solution file created for ${exercise.title}`,
883
- });
884
- }
885
- }
886
- let nextCompletionId = null;
887
- if (nextExercise &&
888
- (exerciseIndex === 0 ||
889
- !(exerciseIndex % 3 === 0) ||
890
- syllabusJson.generationMode === "continue-with-all")) {
891
- let feedback = "";
892
- if (syllabusJson.feedback) {
893
- feedback = `\n\nThe user added the following feedback with relation to the previous generations: ${syllabusJson.feedback}`;
894
- }
895
- nextCompletionId = await startExerciseGeneration(rigoToken, syllabusJson.lessons, syllabusJson.courseInfo, nextExercise, courseSlug, syllabusJson.courseInfo.purpose, readme.parsed.content + "\n\n" + feedback);
896
- }
897
- else {
898
- console.log("Stopping generation process at", exerciseIndex, exercise.title, "because it's a multiple of 3");
899
- }
900
- const newSyllabus = Object.assign(Object.assign({}, syllabusJson), { lessons: syllabusJson.lessons.map((lesson, index) => {
901
- if (index === exerciseIndex) {
902
- const currentTranslations = lesson.translations || {};
903
- let currentTranslation = currentTranslations[syllabusJson.courseInfo.language || "en"];
904
- if (currentTranslation) {
905
- currentTranslation.completedAt = Date.now();
906
- }
907
- else {
908
- currentTranslation = {
909
- completionId: readme.id,
910
- startedAt: Date.now(),
911
- completedAt: Date.now(),
912
- };
913
- }
914
- currentTranslations[syllabusJson.courseInfo.language || "en"] =
915
- currentTranslation;
916
- return Object.assign(Object.assign({}, lesson), { generated: true, status: "DONE", translations: {
917
- [syllabusJson.courseInfo.language || "en"]: {
918
- completionId: nextCompletionId,
919
- completedAt: Date.now(),
920
- },
921
- } });
922
- }
923
- if (nextExercise &&
924
- nextExercise.id === lesson.id &&
925
- nextCompletionId) {
926
- return Object.assign(Object.assign({}, lesson), { generated: false, status: "GENERATING", translations: {
927
- [syllabusJson.courseInfo.language || "en"]: {
928
- completionId: nextCompletionId,
929
- startedAt: Date.now(),
930
- },
931
- } });
932
- }
933
- return Object.assign({}, lesson);
934
- }) });
935
- console.log("New syllabus", newSyllabus);
936
- await uploadFileToBucket(bucket, JSON.stringify(newSyllabus), `courses/${courseSlug}/.learn/initialSyllabus.json`);
937
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
938
- lesson: exSlug,
939
- status: "done",
940
- log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
941
- });
942
- res.json({ status: "SUCCESS" });
943
- });
944
- // Phase 1: Initial content generation webhook
945
- app.post("/webhooks/:courseSlug/initial-content-processor/:lessonID/:rigoToken", async (req, res) => {
946
- const { courseSlug, lessonID, rigoToken } = req.params;
947
- const response = req.body;
948
- console.log("RECEIVING INITIAL CONTENT WEBHOOK", response);
949
1013
  // Handle errors
950
1014
  if (response.status === "ERROR") {
951
- await updateLessonStatusToError(courseSlug, lessonID, bucket);
1015
+ await updateLessonStatusToError(courseSlug, lessonUID, bucket);
952
1016
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
953
- lesson: lessonID,
1017
+ lesson: exSlug,
954
1018
  status: "error",
955
- log: `❌ Error generating initial content for lesson ${lessonID}`,
1019
+ log: `❌ Error generating initial content for lesson ${exSlug}`,
956
1020
  });
957
1021
  // Retry initial content generation
958
1022
  try {
959
- const syllabus = await getSyllabus(courseSlug, bucket);
960
- const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
961
- const exercise = syllabus.lessons[lessonIndex];
962
- if (exercise) {
963
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
964
- lesson: lessonID,
965
- status: "generating",
966
- log: `🔄 Retrying initial content generation for lesson ${lessonID}`,
967
- });
968
- const retryCompletionId = await startInitialContentGeneration(rigoToken, syllabus.lessons, syllabus.courseInfo, exercise, courseSlug, syllabus.courseInfo.purpose, "");
969
- // Update lesson status to show it's retrying
970
- exercise.status = "GENERATING";
971
- exercise.translations = {
972
- [syllabus.courseInfo.language || "en"]: {
973
- completionId: retryCompletionId,
974
- startedAt: Date.now(),
975
- completedAt: 0,
976
- },
977
- };
978
- await saveSyllabus(courseSlug, syllabus, bucket);
979
- }
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);
980
1039
  }
981
1040
  catch (retryError) {
982
1041
  console.error("Error retrying initial content generation:", retryError);
983
1042
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
984
- lesson: lessonID,
1043
+ lesson: lessonUID,
985
1044
  status: "error",
986
- log: `❌ Failed to retry initial content generation for lesson ${lessonID}`,
1045
+ log: `❌ Failed to retry initial content generation for lesson ${lessonUID}`,
987
1046
  });
988
1047
  }
989
1048
  return res.json({ status: "ERROR" });
990
1049
  }
991
1050
  try {
992
1051
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
993
- lesson: lessonID,
1052
+ lesson: exSlug,
994
1053
  status: "generating",
995
- log: `✅ Initial content generated for lesson ${lessonID}`,
1054
+ log: `✅ Initial content generated for lesson ${exSlug}`,
996
1055
  });
997
1056
  // Update lesson with initial content
998
- await updateLessonWithInitialContent(courseSlug, lessonID, response.parsed, bucket);
999
- // Start Phase 2: Add interactivity
1000
- const syllabus = await getSyllabus(courseSlug, bucket);
1001
- const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
1002
- 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
+ }
1003
1062
  let lastLesson = "";
1004
- const prevLessonIndex = lessonIndex - 1;
1063
+ const prevLessonIndex = exerciseIndex - 1;
1005
1064
  if (prevLessonIndex >= 0) {
1006
1065
  try {
1007
1066
  const prevLesson = syllabus.lessons[prevLessonIndex];
@@ -1015,38 +1074,36 @@ class ServeCommand extends SessionCommand_1.default {
1015
1074
  console.error("Error searching previous lesson content:", error);
1016
1075
  }
1017
1076
  }
1018
- if (exercise) {
1019
- const completionId = await startInteractivityGeneration(rigoToken, syllabus.lessons, syllabus.courseInfo, exercise, courseSlug, syllabus.courseInfo.purpose, bucket, lastLesson);
1020
- // Update lesson status to show it's in Phase 2
1021
- exercise.status = "GENERATING";
1022
- exercise.translations = {
1023
- [syllabus.courseInfo.language || "en"]: {
1024
- completionId,
1025
- startedAt: Date.now(),
1026
- completedAt: 0,
1027
- },
1028
- };
1029
- await saveSyllabus(courseSlug, syllabus, bucket);
1030
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1031
- lesson: lessonID,
1032
- status: "generating",
1033
- log: `🔄 Starting interactivity phase for lesson ${exercise.title}`,
1034
- });
1035
- }
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);
1088
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1089
+ lesson: exSlug,
1090
+ status: "generating",
1091
+ log: `🔄 Starting interactivity phase for lesson ${exercise.title}`,
1092
+ });
1036
1093
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1037
- lesson: lessonID,
1094
+ lesson: exSlug,
1038
1095
  status: "initial-content-complete",
1039
- log: `✅ Initial content generated for lesson ${lessonID}, starting interactivity phase`,
1096
+ log: `✅ Initial content generated for lesson ${exSlug}, starting interactivity phase`,
1040
1097
  });
1041
1098
  res.json({ status: "SUCCESS" });
1042
1099
  }
1043
1100
  catch (error) {
1044
1101
  console.error("Error processing initial content webhook:", error);
1045
- await updateLessonStatusToError(courseSlug, lessonID, bucket);
1102
+ await updateLessonStatusToError(courseSlug, lessonUID, bucket);
1046
1103
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1047
- lesson: lessonID,
1104
+ lesson: exSlug,
1048
1105
  status: "error",
1049
- log: `❌ Error processing initial content for lesson ${lessonID}`,
1106
+ log: `❌ Error processing initial content for lesson ${exSlug}`,
1050
1107
  });
1051
1108
  res
1052
1109
  .status(500)
@@ -1054,28 +1111,30 @@ class ServeCommand extends SessionCommand_1.default {
1054
1111
  }
1055
1112
  });
1056
1113
  // Phase 2: Interactivity generation webhook (replaces exercise-processor logic)
1057
- app.post("/webhooks/:courseSlug/interactivity-processor/:lessonID/:rigoToken", async (req, res) => {
1058
- 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;
1059
1116
  const response = req.body;
1060
- console.log("RECEIVING INTERACTIVITY WEBHOOK", response);
1117
+ console.log("RECEIVING INTERACTIVITY WEBHOOK");
1118
+ // console.log("LESSON UID", lessonUID)
1119
+ // console.log("RESPONSE", response)
1061
1120
  // Handle errors
1062
1121
  if (response.status === "ERROR") {
1063
- await updateLessonStatusToError(courseSlug, lessonID, bucket);
1122
+ await updateLessonStatusToError(courseSlug, lessonUID, bucket);
1064
1123
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1065
- lesson: lessonID,
1124
+ lesson: lessonUID,
1066
1125
  status: "error",
1067
- log: `❌ Error adding interactivity to lesson ${lessonID}`,
1126
+ log: `❌ Error adding interactivity to lesson ${lessonUID}`,
1068
1127
  });
1069
1128
  // Retry interactivity generation
1070
1129
  try {
1071
1130
  const syllabus = await getSyllabus(courseSlug, bucket);
1072
- const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
1131
+ const lessonIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUID);
1073
1132
  const exercise = syllabus.lessons[lessonIndex];
1074
1133
  if (exercise && exercise.initialContent) {
1075
1134
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1076
- lesson: lessonID,
1135
+ lesson: lessonUID,
1077
1136
  status: "generating",
1078
- log: `🔄 Retrying interactivity generation for lesson ${lessonID}`,
1137
+ log: `🔄 Retrying interactivity generation for lesson ${lessonUID}`,
1079
1138
  });
1080
1139
  // Get previous lesson content for context
1081
1140
  let lastLesson = "";
@@ -1108,18 +1167,18 @@ class ServeCommand extends SessionCommand_1.default {
1108
1167
  catch (retryError) {
1109
1168
  console.error("Error retrying interactivity generation:", retryError);
1110
1169
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1111
- lesson: lessonID,
1170
+ lesson: lessonUID,
1112
1171
  status: "error",
1113
- log: `❌ Failed to retry interactivity generation for lesson ${lessonID}`,
1172
+ log: `❌ Failed to retry interactivity generation for lesson ${lessonUID}`,
1114
1173
  });
1115
1174
  }
1116
1175
  return res.json({ status: "ERROR" });
1117
1176
  }
1118
1177
  try {
1119
1178
  const syllabus = await getSyllabus(courseSlug, bucket);
1120
- const exerciseIndex = syllabus.lessons.findIndex(lesson => lesson.id === lessonID);
1179
+ const exerciseIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUID);
1121
1180
  if (exerciseIndex === -1) {
1122
- console.error("Exercise not found receiving webhook:", lessonID);
1181
+ console.error("Exercise not found receiving webhook:", lessonUID);
1123
1182
  return res.json({ status: "ERROR", error: "Exercise not found" });
1124
1183
  }
1125
1184
  const exercise = syllabus.lessons[exerciseIndex];
@@ -1138,35 +1197,6 @@ class ServeCommand extends SessionCommand_1.default {
1138
1197
  syllabus.courseInfo.language ||
1139
1198
  "en")}`;
1140
1199
  await uploadFileToBucket(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
1141
- // Handle code files if it's a coding exercise
1142
- if (exercise.type.toLowerCase() === "code" &&
1143
- response.parsed.codefile_content) {
1144
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1145
- lesson: exSlug,
1146
- status: "generating",
1147
- log: `🔄 Creating code file for ${exercise.title}`,
1148
- });
1149
- await uploadFileToBucket(bucket, response.parsed.codefile_content, `${targetDir}/${response.parsed.codefile_name
1150
- .toLowerCase()
1151
- .trim()}`);
1152
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1153
- lesson: exSlug,
1154
- status: "generating",
1155
- log: `✅ Code file created for ${exercise.title}`,
1156
- });
1157
- if (response.parsed.solution_content) {
1158
- const codeFileName = response.parsed.codefile_name
1159
- .toLowerCase()
1160
- .trim();
1161
- const solutionFileName = "solution.hide." + codeFileName;
1162
- await uploadFileToBucket(bucket, response.parsed.solution_content, `${targetDir}/${solutionFileName}`);
1163
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1164
- lesson: exSlug,
1165
- status: "generating",
1166
- log: `✅ Solution file created for ${exercise.title}`,
1167
- });
1168
- }
1169
- }
1170
1200
  // Update used components if provided by the AI
1171
1201
  if (response.parsed.used_components &&
1172
1202
  Array.isArray(response.parsed.used_components)) {
@@ -1188,11 +1218,11 @@ class ServeCommand extends SessionCommand_1.default {
1188
1218
  }
1189
1219
  catch (error) {
1190
1220
  console.error("Error processing interactivity webhook:", error);
1191
- await updateLessonStatusToError(courseSlug, lessonID, bucket);
1221
+ await updateLessonStatusToError(courseSlug, lessonUID, bucket);
1192
1222
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
1193
- lesson: lessonID,
1223
+ lesson: lessonUID,
1194
1224
  status: "error",
1195
- log: `❌ Error processing interactivity for lesson ${lessonID}`,
1225
+ log: `❌ Error processing interactivity for lesson ${lessonUID}`,
1196
1226
  });
1197
1227
  res
1198
1228
  .status(500)
@@ -1350,24 +1380,61 @@ class ServeCommand extends SessionCommand_1.default {
1350
1380
  created,
1351
1381
  });
1352
1382
  });
1353
- app.post("/exercise/:slug/create", async (req, res) => {
1354
- console.log("POST /exercise/:slug/create");
1355
- const query = req.query;
1356
- const { title, readme, language } = req.body;
1357
- if (!title || !readme) {
1358
- return res
1359
- .status(400)
1360
- .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" });
1361
1390
  }
1362
- const courseSlug = query.slug;
1363
- const fileName = `courses/${courseSlug}/exercises/${title}/README${(0, creatorUtilities_1.getReadmeExtension)(language)}`;
1364
- const file = bucket.file(fileName);
1365
- await file.save(readme);
1366
- const created = await file.exists();
1367
- res.send({
1368
- message: "File updated",
1369
- 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",
1370
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!" });
1371
1438
  });
1372
1439
  app.put("/actions/rename", async (req, res) => {
1373
1440
  console.log("PUT /actions/rename");