@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.
- package/lib/commands/serve.js +133 -61
- package/lib/creatorDist/assets/{index-C1pv1wUb.js → index-BfLyIQVh.js} +7 -2
- package/lib/creatorDist/assets/{index-B4khtb0r.css → index-C39zeF3W.css} +3 -3
- package/lib/creatorDist/index.html +2 -2
- package/lib/models/creator.d.ts +7 -0
- package/lib/utils/api.d.ts +1 -0
- package/lib/utils/api.js +3 -1
- package/package.json +1 -1
- package/src/commands/serve.ts +165 -100
- package/src/creator/src/components/syllabus/ContentIndex.tsx +312 -312
- package/src/creator/src/locales/en.json +126 -125
- package/src/creator/src/locales/es.json +126 -125
- package/src/creator/src/utils/rigo.ts +26 -26
- package/src/creatorDist/assets/{index-C1pv1wUb.js → index-BfLyIQVh.js} +7 -2
- package/src/creatorDist/assets/{index-B4khtb0r.css → index-C39zeF3W.css} +3 -3
- package/src/creatorDist/index.html +2 -2
- package/src/models/creator.ts +7 -0
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +435 -414
- package/src/ui/_app/learnpack.svg +7 -7
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +2 -0
package/src/commands/serve.ts
CHANGED
@@ -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
|
162
|
-
|
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<
|
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
|
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
|
-
|
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 (
|
831
|
-
|
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
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
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
|
-
|
1758
|
-
|
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
|
-
|
1775
|
-
|
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:
|
1788
|
-
author:
|
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)
|