@learnpack/learnpack 5.0.272 → 5.0.275

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.
@@ -44,7 +44,7 @@ import {
44
44
  // import { handleAssetCreation } from "./publish"
45
45
  import axios from "axios"
46
46
  import * as FormData from "form-data"
47
- import api, { RIGOBOT_HOST } from "../utils/api"
47
+ import api, { RIGOBOT_HOST, RIGOBOT_REALTIME_HOST } from "../utils/api"
48
48
  import { createUploadMiddleware, minutesToISO8601Duration } from "../utils/misc"
49
49
  import { buildConfig, ConfigResponse } from "../utils/configBuilder"
50
50
  import { checkReadability, slugify } from "../utils/creatorUtilities"
@@ -125,7 +125,6 @@ export const processImage = async (
125
125
  callbackUrl: webhookUrl,
126
126
  })
127
127
 
128
- // console.log("✅ Image", imagePath, "generated successfully!")
129
128
  return true
130
129
  } catch {
131
130
  return false
@@ -157,11 +156,9 @@ const uploadInitialReadme = async (
157
156
  :rigo
158
157
  \`\`\`
159
158
  `
160
- const readmeFilename = `README.${
161
- packageContext.language && packageContext.language !== "en" ?
162
- `${packageContext.language}.` :
163
- ""
164
- }md`
159
+ const readmeFilename = `README${getReadmeExtension(
160
+ packageContext.language || "en"
161
+ )}`
165
162
 
166
163
  await uploadFileToBucket(
167
164
  bucket,
@@ -229,16 +226,14 @@ const createMultiLangAsset = async (
229
226
  }
230
227
 
231
228
  async function startExerciseGeneration(
232
- bucket: Bucket,
233
229
  rigoToken: string,
234
230
  steps: Lesson[],
235
231
  packageContext: FormState,
236
232
  exercise: Lesson,
237
- tutorialDir: string,
238
233
  courseSlug: string,
239
234
  purposeSlug: string,
240
235
  lastLesson = ""
241
- ): Promise<void> {
236
+ ): Promise<number> {
242
237
  const exSlug = slugify(exercise.id + "-" + exercise.title)
243
238
  console.log("Starting generation of", exSlug)
244
239
 
@@ -246,7 +241,7 @@ async function startExerciseGeneration(
246
241
 
247
242
  console.log("WEBHOOK URL", webhookUrl)
248
243
 
249
- await readmeCreator(
244
+ const res = await readmeCreator(
250
245
  rigoToken,
251
246
  {
252
247
  title: `${exercise.id} - ${exercise.title}`,
@@ -262,6 +257,9 @@ async function startExerciseGeneration(
262
257
  purposeSlug,
263
258
  webhookUrl
264
259
  )
260
+
261
+ console.log("README CREATOR RES", res)
262
+ return res.id
265
263
  }
266
264
 
267
265
  async function createInitialReadme(
@@ -648,13 +646,11 @@ export default class ServeCommand extends SessionCommand {
648
646
  }
649
647
  }
650
648
 
651
- await startExerciseGeneration(
652
- bucket,
649
+ const completionId = await startExerciseGeneration(
653
650
  rigoToken,
654
651
  syllabusJson.lessons,
655
652
  syllabusJson.courseInfo,
656
653
  exercise,
657
- `courses/${courseSlug}`,
658
654
  courseSlug,
659
655
  syllabusJson.courseInfo.purpose,
660
656
  previousReadme +
@@ -663,6 +659,14 @@ export default class ServeCommand extends SessionCommand {
663
659
  )
664
660
 
665
661
  syllabusJson.lessons[parseInt(position)].status = "GENERATING"
662
+ syllabusJson.lessons[parseInt(position)].translations = {
663
+ [syllabusJson.courseInfo.language || "en"]: {
664
+ completionId,
665
+ startedAt: Date.now(),
666
+ completedAt: 0,
667
+ },
668
+ }
669
+ console.log("Lesson", syllabusJson.lessons[parseInt(position)])
666
670
  if (
667
671
  syllabusJson.feedback &&
668
672
  typeof syllabusJson.feedback === "string"
@@ -682,6 +686,27 @@ export default class ServeCommand extends SessionCommand {
682
686
  }
683
687
  )
684
688
 
689
+ app.post("/actions/generate-image/:courseSlug", async (req, res) => {
690
+ const rigoToken = req.header("x-rigo-token")
691
+ const { courseSlug } = req.params
692
+ const { image } = req.body
693
+
694
+ if (!image) {
695
+ return res.status(400).json({
696
+ error: "Image is required",
697
+ })
698
+ }
699
+
700
+ if (!rigoToken) {
701
+ return res.status(400).json({
702
+ error: "Rigo token is required. x-rigo-token header is missing",
703
+ })
704
+ }
705
+
706
+ await processImage(image.url, image.alt, rigoToken, courseSlug)
707
+ res.json({ status: "QUEUED" })
708
+ })
709
+
685
710
  app.post(
686
711
  "/webhooks/:courseSlug/exercise-processor/:lessonID/:rigoToken",
687
712
  async (req, res) => {
@@ -773,7 +798,7 @@ export default class ServeCommand extends SessionCommand {
773
798
  })
774
799
  }
775
800
 
776
- let nextStarted = false
801
+ let nextCompletionId: number | null = null
777
802
  if (
778
803
  nextExercise &&
779
804
  (exerciseIndex === 0 || !(exerciseIndex % 3 === 0))
@@ -783,18 +808,15 @@ export default class ServeCommand extends SessionCommand {
783
808
  feedback = `\n\nThe user added the following feedback with relation to the previous generations: ${syllabusJson.feedback}`
784
809
  }
785
810
 
786
- startExerciseGeneration(
787
- bucket,
811
+ nextCompletionId = await startExerciseGeneration(
788
812
  rigoToken,
789
813
  syllabusJson.lessons,
790
814
  syllabusJson.courseInfo,
791
815
  nextExercise,
792
- `courses/${courseSlug}`,
793
816
  courseSlug,
794
817
  syllabusJson.courseInfo.purpose,
795
818
  readme.parsed.content + "\n\n" + feedback
796
819
  )
797
- nextStarted = true
798
820
  } else {
799
821
  console.log(
800
822
  "Stopping generation process at",
@@ -808,32 +830,60 @@ export default class ServeCommand extends SessionCommand {
808
830
  readability.newMarkdown
809
831
  )
810
832
 
811
- if (imagesArray.length > 0) {
812
- emitToCourse(courseSlug, "course-creation", {
813
- lesson: exSlug,
814
- status: "pending",
815
- log: `🔄 Generating images for ${exercise.title}`,
816
- })
817
- for (const image of imagesArray) {
818
- // eslint-disable-next-line no-await-in-loop
819
- await processImage(image.url, image.alt, rigoToken, courseSlug)
820
- }
821
- }
822
-
823
833
  const newSyllabus = {
824
834
  ...syllabusJson,
825
835
  lessons: syllabusJson.lessons.map((lesson, index) => {
826
836
  if (index === exerciseIndex) {
827
- return { ...lesson, generated: true, status: "DONE" }
837
+ const currentTranslations = lesson.translations || {}
838
+ let currentTranslation =
839
+ currentTranslations[syllabusJson.courseInfo.language || "en"]
840
+ if (currentTranslation) {
841
+ currentTranslation.completedAt = Date.now()
842
+ } else {
843
+ currentTranslation = {
844
+ completionId: readme.id,
845
+ startedAt: Date.now(),
846
+ completedAt: Date.now(),
847
+ }
848
+ }
849
+
850
+ currentTranslations[syllabusJson.courseInfo.language || "en"] =
851
+ currentTranslation
852
+ return {
853
+ ...lesson,
854
+ generated: true,
855
+ status: "DONE",
856
+ translations: {
857
+ [syllabusJson.courseInfo.language || "en"]: {
858
+ completionId: nextCompletionId,
859
+ completedAt: Date.now(),
860
+ },
861
+ },
862
+ }
828
863
  }
829
864
 
830
- if (nextExercise && nextExercise.id === lesson.id && nextStarted) {
831
- return { ...lesson, generated: false, status: "GENERATING" }
865
+ if (
866
+ nextExercise &&
867
+ nextExercise.id === lesson.id &&
868
+ nextCompletionId
869
+ ) {
870
+ return {
871
+ ...lesson,
872
+ generated: false,
873
+ status: "GENERATING",
874
+ translations: {
875
+ [syllabusJson.courseInfo.language || "en"]: {
876
+ completionId: nextCompletionId,
877
+ startedAt: Date.now(),
878
+ },
879
+ },
880
+ }
832
881
  }
833
882
 
834
883
  return { ...lesson }
835
884
  }),
836
885
  }
886
+ console.log("New syllabus", newSyllabus)
837
887
  await uploadFileToBucket(
838
888
  bucket,
839
889
  JSON.stringify(newSyllabus),
@@ -1444,17 +1494,24 @@ export default class ServeCommand extends SessionCommand {
1444
1494
 
1445
1495
  const lastResult = "---"
1446
1496
 
1447
- await startExerciseGeneration(
1448
- bucket,
1497
+ const completionId = await startExerciseGeneration(
1449
1498
  rigoToken,
1450
1499
  syllabus.lessons,
1451
1500
  syllabus.courseInfo,
1452
1501
  firstLesson,
1453
- tutorialDir,
1454
1502
  courseSlug,
1455
1503
  syllabus.courseInfo.purpose,
1456
1504
  lastResult
1457
1505
  )
1506
+ if (firstLesson) {
1507
+ firstLesson.translations = {
1508
+ [syllabus.courseInfo.language || "en"]: {
1509
+ completionId,
1510
+ startedAt: Date.now(),
1511
+ completedAt: 0,
1512
+ },
1513
+ }
1514
+ }
1458
1515
 
1459
1516
  await createInitialReadme(
1460
1517
  JSON.stringify(syllabus.courseInfo),
@@ -1484,54 +1541,52 @@ export default class ServeCommand extends SessionCommand {
1484
1541
  }
1485
1542
  })
1486
1543
 
1487
- app.post("/actions/continue-course/:courseSlug", async (req, res) => {
1488
- console.log("POST /actions/continue-course/:courseSlug")
1489
- const { courseSlug } = req.params
1490
-
1491
- const { feedback }: { feedback: string } = req.body
1492
-
1493
- const rigoToken = req.header("x-rigo-token")
1494
- const bcToken = req.header("x-breathecode-token")
1495
- if (!rigoToken || !bcToken) {
1496
- return res.status(400).json({ error: "Missing tokens" })
1497
- }
1498
-
1499
- const syllabus = await bucket.file(
1500
- `courses/${courseSlug}/.learn/initialSyllabus.json`
1501
- )
1502
- const [content] = await syllabus.download()
1503
- const syllabusJson: Syllabus = JSON.parse(content.toString())
1504
- const notGeneratedLessons = syllabusJson.lessons.filter(
1505
- lesson => !lesson.generated
1506
- )
1507
-
1508
- const lastGeneratedLesson = findLast(
1509
- syllabusJson.lessons,
1510
- lesson => lesson.generated ?? false
1511
- )
1512
-
1513
- console.log("ABout to generate", notGeneratedLessons.length, "lessons")
1514
-
1515
- const firstLessonToGenerate = notGeneratedLessons[0]
1516
-
1517
- await startExerciseGeneration(
1518
- bucket,
1519
- rigoToken,
1520
- syllabusJson.lessons,
1521
- syllabusJson.courseInfo,
1522
- firstLessonToGenerate,
1523
- `courses/${courseSlug}`,
1524
- courseSlug,
1525
- syllabusJson.courseInfo.purpose,
1526
- JSON.stringify(lastGeneratedLesson) +
1527
- `\n\nThe user provided this feedback in relation to the course: ${feedback}`
1528
- )
1529
-
1530
- return res.json({
1531
- message: "Course continued",
1532
- slug: courseSlug,
1533
- })
1534
- })
1544
+ // app.post("/actions/continue-course/:courseSlug", async (req, res) => {
1545
+ // console.log("POST /actions/continue-course/:courseSlug")
1546
+ // const { courseSlug } = req.params
1547
+
1548
+ // const { feedback }: { feedback: string } = req.body
1549
+
1550
+ // const rigoToken = req.header("x-rigo-token")
1551
+ // const bcToken = req.header("x-breathecode-token")
1552
+ // if (!rigoToken || !bcToken) {
1553
+ // return res.status(400).json({ error: "Missing tokens" })
1554
+ // }
1555
+
1556
+ // const syllabus = await bucket.file(
1557
+ // `courses/${courseSlug}/.learn/initialSyllabus.json`
1558
+ // )
1559
+ // const [content] = await syllabus.download()
1560
+ // const syllabusJson: Syllabus = JSON.parse(content.toString())
1561
+ // const notGeneratedLessons = syllabusJson.lessons.filter(
1562
+ // lesson => !lesson.generated
1563
+ // )
1564
+
1565
+ // const lastGeneratedLesson = findLast(
1566
+ // syllabusJson.lessons,
1567
+ // lesson => lesson.generated ?? false
1568
+ // )
1569
+
1570
+ // console.log("ABout to generate", notGeneratedLessons.length, "lessons")
1571
+
1572
+ // const firstLessonToGenerate = notGeneratedLessons[0]
1573
+
1574
+ // const completionId = await startExerciseGeneration(
1575
+ // rigoToken,
1576
+ // syllabusJson.lessons,
1577
+ // syllabusJson.courseInfo,
1578
+ // firstLessonToGenerate,
1579
+ // courseSlug,
1580
+ // syllabusJson.courseInfo.purpose,
1581
+ // JSON.stringify(lastGeneratedLesson) +
1582
+ // `\n\nThe user provided this feedback in relation to the course: ${feedback}`
1583
+ // )
1584
+
1585
+ // return res.json({
1586
+ // message: "Course continued",
1587
+ // slug: courseSlug,
1588
+ // })
1589
+ // })
1535
1590
 
1536
1591
  app.get(
1537
1592
  "/courses/:courseSlug/exercises/:exerciseSlug/",
@@ -1753,9 +1808,9 @@ export default class ServeCommand extends SessionCommand {
1753
1808
  return res.send(content.toString("utf-8"))
1754
1809
  } catch (error) {
1755
1810
  console.error("❌ Error fetching file:", error)
1756
- return res
1757
- .status(500)
1758
- .json({ error: (error as Error).message || "Unable to fetch file" })
1811
+ return res.status(500).json({
1812
+ error: (error as Error).message || "Unable to fetch file",
1813
+ })
1759
1814
  }
1760
1815
  }
1761
1816
  )
@@ -1771,26 +1826,36 @@ export default class ServeCommand extends SessionCommand {
1771
1826
 
1772
1827
  if (ytMatch) {
1773
1828
  const videoId = ytMatch[1]
1774
- // fetch metadata
1775
- const items = await YoutubeTranscript.fetchTranscript(videoId)
1776
- const transcript = items.map(i => i.text).join(" ")
1777
-
1778
- const { data: meta } = await axios.get(
1779
- "https://www.youtube.com/oembed",
1780
- {
1781
- params: { url: decoded, format: "json" },
1782
- }
1829
+ const resFromRigo = await axios.get(
1830
+ `${RIGOBOT_REALTIME_HOST}/actions/youtube-transcript/${videoId}`
1783
1831
  )
1832
+ console.log("RES FROM RIGO", resFromRigo.data)
1833
+ const transcript = resFromRigo.data.transcript
1834
+
1835
+ // let meta: any = null
1836
+ // try {
1837
+ // const { data: meta } = await axios.get(
1838
+ // "https://www.youtube.com/oembed",
1839
+ // {
1840
+ // params: { url: decoded, format: "json" },
1841
+ // }
1842
+ // )
1843
+ // console.log("META", meta)
1844
+ // } catch (error) {
1845
+ // console.error("ERROR FETCHING META", error)
1846
+ // meta = null
1847
+ // }
1784
1848
 
1785
1849
  return res.json({
1786
1850
  url: decoded,
1787
- title: meta.title,
1788
- author: meta.author_name,
1789
- thumbnail: meta.thumbnail_url,
1851
+ title: resFromRigo.data.title || null,
1852
+ author: resFromRigo.data.author || null,
1790
1853
  transcript,
1791
1854
  })
1792
1855
  }
1793
1856
 
1857
+ console.log("NOT A YOUTUBE LINK", decoded)
1858
+
1794
1859
  const response = await axios.get(decoded, { responseType: "text" })
1795
1860
  const html = response.data as string
1796
1861
  const title = getTitleFromHTML(html)