@learnpack/learnpack 5.0.234 → 5.0.238

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.
@@ -10,7 +10,7 @@
10
10
  />
11
11
 
12
12
  <title>Learnpack Creator: Craft tutorials in seconds!</title>
13
- <script type="module" crossorigin src="/creator/assets/index-CFK5bQP2.js"></script>
13
+ <script type="module" crossorigin src="/creator/assets/index-x_kA-1DY.js"></script>
14
14
  <link rel="stylesheet" crossorigin href="/creator/assets/index-DmpsXknz.css">
15
15
  </head>
16
16
  <body>
@@ -0,0 +1 @@
1
+ {"version":"5.0.236","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false}},"args":[]},"breakToken":{"id":"breakToken","description":"Break the token","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false},"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"serve":{"id":"serve","description":"Runs a small server to build tutorials","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[]}}}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@learnpack/learnpack",
3
3
  "description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
4
- "version": "5.0.234",
4
+ "version": "5.0.238",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -157,7 +157,7 @@
157
157
  "copy-assets": "npx cpy src/creatorDist/**/* lib/creatorDist --parents --verbose",
158
158
  "tsc": "tsc -b",
159
159
  "postpack": "rm -f oclif.manifest.json && eslint . --ext .ts --config .eslintrc",
160
- "prepack": "rm -rf lib && tsc -b && npm run copy-assets ",
160
+ "prepack": "rm -rf lib && tsc -b && npm run copy-assets",
161
161
  "pre": "node ./test/precommit/index.ts",
162
162
  "test": "NODE_ENV=test nyc --extension .ts mocha --forbid-only \"test/**/*.test.ts\"",
163
163
  "version": "oclif-dev readme && git add README.md",
@@ -142,8 +142,10 @@ export const processImage = async (
142
142
  rigoToken: string
143
143
  ) => {
144
144
  try {
145
+ // TODO: MAKE THIS ASYNC
145
146
  const filename = getFilenameFromUrl(url)
146
147
 
148
+ console.log("🖼️ IMAGE FILENAME", filename)
147
149
  const imagePath = `${tutorialDir}/.learn/assets/${filename}`
148
150
 
149
151
  console.log("🖼️ Generating image", imagePath)
@@ -249,131 +251,6 @@ async function startExerciseGeneration(
249
251
  purposeSlug,
250
252
  webhookUrl
251
253
  )
252
- // console.log("res processing in background", res)
253
- }
254
-
255
- export async function processExercise(
256
- bucket: Bucket,
257
- rigoToken: string,
258
- steps: Lesson[],
259
- packageContext: FormState,
260
- exercise: Lesson,
261
- tutorialDir: string,
262
- courseSlug: string,
263
- purposeSlug: string,
264
- lastLesson = ""
265
- ): Promise<string> {
266
- const exercisesDir = `${tutorialDir}/exercises`
267
- // const tid = toast.loading("Generating lesson...")
268
- const exSlug = slugify(exercise.id + "-" + exercise.title)
269
- console.log("exSlug", exSlug)
270
-
271
- const readmeFilename = `README.${
272
- packageContext.language && packageContext.language !== "en" ?
273
- `${packageContext.language}.` :
274
- ""
275
- }md`
276
- const targetDir = `${exercisesDir}/${exSlug}`
277
-
278
- console.log("✍🏻 Generating lesson", exercise.id, exercise.title)
279
-
280
- const readme = await readmeCreator(
281
- rigoToken,
282
- {
283
- title: `${exercise.id} - ${exercise.title}`,
284
- output_lang: packageContext.language || "en",
285
- list_of_exercises: JSON.stringify(
286
- steps.map(step => step.id + "-" + step.title)
287
- ),
288
- tutorial_description: JSON.stringify(cleanFormState(packageContext)),
289
- lesson_description: exercise.description,
290
- kind: exercise.type.toLowerCase(),
291
- last_lesson: lastLesson,
292
- },
293
- purposeSlug
294
- )
295
-
296
- const duration = exercise.duration
297
- // let attempts = 0
298
- const readability = checkReadability(
299
- readme.parsed.content,
300
- PARAMS.max_words,
301
- duration || 3
302
- )
303
-
304
- emitToCourse(courseSlug, "course-creation", {
305
- lesson: exSlug,
306
- status: "generating",
307
- log: `🔄 The lesson ${exercise.id} - ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
308
- })
309
- // while (
310
- // readability.fkglResult.fkgl > PARAMS.max_fkgl &&
311
- // attempts < PARAMS.max_rewrite_attempts
312
- // ) {
313
-
314
- // // eslint-disable-next-line
315
- // const reducedReadme = await makeReadmeReadable(
316
- // rigoToken,
317
- // {
318
- // lesson: readability.body,
319
- // number_of_words: readability.minutes.toString(),
320
- // expected_number_words: PARAMS.max_words.toString(),
321
- // fkgl_results: JSON.stringify(readability.fkglResult),
322
- // expected_grade_level: PARAMS.expected_grade_level,
323
- // },
324
- // purposeSlug
325
- // )
326
-
327
- // if (!reducedReadme) break
328
-
329
- // readability = checkReadability(
330
- // reducedReadme.parsed.content,
331
- // PARAMS.max_words,
332
- // duration || 3
333
- // )
334
-
335
- // attempts++
336
- // }
337
-
338
- await uploadFileToBucket(
339
- bucket,
340
- readability.newMarkdown,
341
- `${targetDir}/${readmeFilename}`
342
- )
343
-
344
- if (
345
- exercise.type.toLowerCase() === "code" &&
346
- readme.parsed.codefile_content
347
- ) {
348
- console.log("🔍 Creating code file for", exercise.title)
349
-
350
- await uploadFileToBucket(
351
- bucket,
352
- readme.parsed.codefile_content,
353
- `${targetDir}/${readme.parsed.codefile_name.toLowerCase().trim()}`
354
- )
355
- }
356
-
357
- const imagesArray: any[] = extractImagesFromMarkdown(readability.newMarkdown)
358
-
359
- if (imagesArray.length > 0) {
360
- emitToCourse(courseSlug, "course-creation", {
361
- lesson: exSlug,
362
- status: "done",
363
- log: `🔄 Generating images for ${exercise.title}`,
364
- })
365
- for (const image of imagesArray) {
366
- // eslint-disable-next-line no-await-in-loop
367
- await processImage(bucket, tutorialDir, image.url, image.alt, rigoToken)
368
- }
369
- }
370
-
371
- emitToCourse(courseSlug, "course-creation", {
372
- lesson: exSlug,
373
- status: "done",
374
- log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
375
- })
376
- return readability.newMarkdown
377
254
  }
378
255
 
379
256
  const fixPreviewUrl = (slug: string, previewUrl: string) => {
@@ -719,6 +596,11 @@ export default class ServeCommand extends SessionCommand {
719
596
  readme.parsed.codefile_content,
720
597
  `${targetDir}/${readme.parsed.codefile_name.toLowerCase().trim()}`
721
598
  )
599
+ emitToCourse(courseSlug, "course-creation", {
600
+ lesson: exSlug,
601
+ status: "done",
602
+ log: `✅ Code file created for ${exercise.title}`,
603
+ })
722
604
  }
723
605
 
724
606
  if (nextExercise) {
@@ -740,10 +622,6 @@ export default class ServeCommand extends SessionCommand {
740
622
  )
741
623
 
742
624
  if (imagesArray.length > 0) {
743
- console.log(
744
- "This course requires images and I don't have the token :)"
745
- )
746
-
747
625
  emitToCourse(courseSlug, "course-creation", {
748
626
  lesson: exSlug,
749
627
  status: "pending",
@@ -1271,7 +1149,7 @@ export default class ServeCommand extends SessionCommand {
1271
1149
 
1272
1150
  const lastResult = "Nothing"
1273
1151
 
1274
- startExerciseGeneration(
1152
+ await startExerciseGeneration(
1275
1153
  bucket,
1276
1154
  rigoToken,
1277
1155
  syllabus.lessons,
@@ -1289,41 +1167,6 @@ export default class ServeCommand extends SessionCommand {
1289
1167
  })
1290
1168
  })
1291
1169
 
1292
- // app.post(
1293
- // "/check-latex/:courseSlug/:exerciseSlug/:lang",
1294
- // async (req, res) => {
1295
- // const { courseSlug, exerciseSlug, lang } = req.params
1296
-
1297
- // const rigoToken = req.header("x-rigo-token")
1298
-
1299
- // if (!rigoToken) {
1300
- // return res.status(400).json({ error: "Missing tokens" })
1301
- // }
1302
-
1303
- // const exercise = await bucket.file(
1304
- // `courses/${courseSlug}/exercises/${exerciseSlug}/README.${lang}.md`
1305
- // )
1306
- // const [content] = await exercise.download()
1307
- // const headers = {
1308
- // Authorization: `Token ${rigoToken}`,
1309
- // }
1310
- // const response = await axios.get(
1311
- // `${RIGOBOT_HOST}/v1/prompting/completion/60865/`,
1312
- // {
1313
- // headers,
1314
- // }
1315
- // )
1316
-
1317
- // console.log(response.data.parsed.content, "RESPONSE from Rigobot")
1318
-
1319
- // res.json({
1320
- // message: "Exercise downloaded",
1321
- // completion: response.data,
1322
- // exercise: content.toString(),
1323
- // })
1324
- // }
1325
- // )
1326
-
1327
1170
  app.get("/courses/:courseSlug/syllabus", async (req, res) => {
1328
1171
  try {
1329
1172
  console.log("GET /courses/:courseSlug/syllabus")
@@ -1,4 +1,4 @@
1
- import { useEffect } from "react"
1
+ import { useEffect, useState } from "react"
2
2
  import StepWizard from "./components/StepWizard"
3
3
  import SelectableCard from "./components/SelectableCard"
4
4
  import Loader from "./components/Loader"
@@ -25,6 +25,7 @@ import TurnstileChallenge from "./components/TurnstileChallenge"
25
25
  import ResumeCourseModal from "./components/ResumeCourseModal"
26
26
  import { possiblePurposes, PurposeSelector } from "./components/PurposeSelector"
27
27
  import { useTranslation } from "react-i18next"
28
+ import NotificationListener from "./components/NotificationListener"
28
29
 
29
30
  function App() {
30
31
  const navigate = useNavigate()
@@ -64,6 +65,8 @@ function App() {
64
65
  }))
65
66
  )
66
67
 
68
+ const [notificationId, setNotificationId] = useState<string>("")
69
+
67
70
  useEffect(() => {
68
71
  if (formState.isCompleted) {
69
72
  handleCreateTutorial()
@@ -111,7 +114,6 @@ function App() {
111
114
  cleanAll()
112
115
  }
113
116
 
114
-
115
117
  if (description) {
116
118
  console.log("description", description)
117
119
  setFormState({
@@ -187,51 +189,9 @@ function App() {
187
189
  formState.purpose || "learnpack-lesson-writer",
188
190
  auth.rigoToken && isAuthenticated ? false : true
189
191
  )
190
- const lessons = res.parsed.listOfSteps.map((lesson: any) => {
191
- return parseLesson(lesson, [])
192
- })
193
-
194
- push({
195
- lessons,
196
- courseInfo: {
197
- ...formState,
198
- title: fixTitleLength(res.parsed.title),
199
- description: res.parsed.description,
200
- language: res.parsed.languageCode || formState.language || "en",
201
- technologies:
202
- res.parsed.technologies.length > 0
203
- ? res.parsed.technologies
204
- : ["education", "quizzes"],
205
- },
206
- })
192
+ console.log("RES", res)
207
193
 
208
- if (res.parsed.languageCode) {
209
- i18n.changeLanguage(res.parsed.languageCode)
210
- }
211
-
212
- navigate("/creator/syllabus")
213
- setFormState({
214
- isCompleted: false,
215
- currentStep: "description",
216
- })
217
- setMessages([
218
- {
219
- type: "user",
220
- content: formState.description,
221
- },
222
- {
223
- type: "assistant",
224
- content: res.parsed.aiMessage,
225
- },
226
- {
227
- type: "assistant",
228
- content: t("contentIndex.okMessage"),
229
- },
230
- {
231
- type: "assistant",
232
- content: t("contentIndex.instructionsMessage"),
233
- },
234
- ])
194
+ setNotificationId(res.notificationId)
235
195
  } catch (error) {
236
196
  console.error(error, "ERROR CREATING TUTORIAL")
237
197
  toast.error("Something went wrong. Please try again.")
@@ -432,24 +392,69 @@ function App() {
432
392
  <>
433
393
  <ParamsChecker />
434
394
  {formState.isCompleted && history.length === 0 ? (
435
- <Loader
436
- text={t("loader.text")}
437
- icon={<img src={RIGO_FLOAT_GIF} alt="rigo" className="w-20 h-20" />}
438
- />
395
+ <>
396
+ <Loader
397
+ text={t("loader.text")}
398
+ icon={<img src={RIGO_FLOAT_GIF} alt="rigo" className="w-20 h-20" />}
399
+ />
400
+ {notificationId && (
401
+ <NotificationListener
402
+ onNotification={(res) => {
403
+ console.log("Async response", res)
404
+ toast.success("Initial course created successfully")
405
+ const lessons = res.parsed.listOfSteps.map((lesson: any) => {
406
+ return parseLesson(lesson, [])
407
+ })
408
+
409
+ push({
410
+ lessons,
411
+ courseInfo: {
412
+ ...formState,
413
+ title: fixTitleLength(res.parsed.title),
414
+ description: res.parsed.description,
415
+ language:
416
+ res.parsed.languageCode || formState.language || "en",
417
+ technologies:
418
+ res.parsed.technologies.length > 0
419
+ ? res.parsed.technologies
420
+ : ["education", "quizzes"],
421
+ },
422
+ })
423
+
424
+ if (res.parsed.languageCode) {
425
+ i18n.changeLanguage(res.parsed.languageCode)
426
+ }
427
+
428
+ setMessages([
429
+ {
430
+ type: "user",
431
+ content: formState.description,
432
+ },
433
+ {
434
+ type: "assistant",
435
+ content: res.parsed.aiMessage,
436
+ },
437
+ {
438
+ type: "assistant",
439
+ content: t("contentIndex.okMessage"),
440
+ },
441
+ {
442
+ type: "assistant",
443
+ content: t("contentIndex.instructionsMessage"),
444
+ },
445
+ ])
446
+ navigate("/creator/syllabus")
447
+ setFormState({
448
+ isCompleted: false,
449
+ currentStep: "description",
450
+ })
451
+ }}
452
+ notificationId={notificationId}
453
+ />
454
+ )}
455
+ </>
439
456
  ) : (
440
457
  <>
441
- {/* {formState.language && (
442
- <div className="flex flex-col items-center justify-center">
443
- <p>
444
- <span className="font-bold">Language:</span>{" "}
445
- {formState.language}
446
- </p>
447
- <p>
448
- <span className="font-bold">Technologies:</span>{" "}
449
- {formState.technologies?.join(", ")}
450
- </p>
451
- </div>
452
- )} */}
453
458
  {history.length > 0 && (
454
459
  <ResumeCourseModal
455
460
  onContinue={() => {
@@ -15,6 +15,12 @@ export interface Lesson {
15
15
  duration?: number
16
16
  }
17
17
 
18
+ const typeToEmoji: Record<string, string> = {
19
+ read: "📖",
20
+ code: "💻",
21
+ quiz: "🧠",
22
+ }
23
+
18
24
  interface LessonItemProps {
19
25
  lesson: Lesson
20
26
  isNew: boolean
@@ -58,12 +64,12 @@ export const LessonItem: React.FC<LessonItemProps> = ({
58
64
  {mode === "teacher" && (
59
65
  <>
60
66
  <span className="index-circle">{cleanFloatString(lesson.id)}</span>
61
- <span className="text-gray-500 text-sm">
62
- {lesson.type[0] + lesson.type.slice(1).toLowerCase()} ●
63
- </span>
64
67
  </>
65
68
  )}
66
69
 
70
+ <span className="text-gray-500 text-sm">
71
+ {typeToEmoji[lesson.type.toLowerCase()]}
72
+ </span>
67
73
  {isEditing ? (
68
74
  <input
69
75
  defaultValue={lesson.title}
@@ -0,0 +1,32 @@
1
+ import { useEffect } from "react"
2
+ import CreatorSocket from "../utils/socket"
3
+
4
+ interface NotificationListenerProps {
5
+ notificationId: string
6
+ onNotification: (data: any) => void
7
+ }
8
+
9
+ const socketClient = new CreatorSocket("")
10
+
11
+ const NotificationListener: React.FC<NotificationListenerProps> = ({
12
+ notificationId,
13
+ onNotification,
14
+ }) => {
15
+ useEffect(() => {
16
+ console.log("NOTIFICATION ID listening", notificationId)
17
+ if (!notificationId) return
18
+
19
+ socketClient.connect()
20
+ socketClient.on(notificationId, onNotification)
21
+ socketClient.emit("registerNotification", { notificationId })
22
+
23
+ return () => {
24
+ socketClient.off(notificationId, onNotification)
25
+ socketClient.disconnect()
26
+ }
27
+ }, [notificationId, onNotification])
28
+
29
+ return null
30
+ }
31
+
32
+ export default NotificationListener
@@ -29,6 +29,7 @@ import { ParamsChecker } from "../ParamsChecker"
29
29
  import { randomUUID, slugify } from "../../utils/creatorUtils"
30
30
  import { RIGO_FLOAT_GIF } from "../../utils/constants"
31
31
  import { useTranslation } from "react-i18next"
32
+ import NotificationListener from "../NotificationListener"
32
33
 
33
34
  const SyllabusEditor: React.FC = () => {
34
35
  const navigate = useNavigate()
@@ -60,6 +61,7 @@ const SyllabusEditor: React.FC = () => {
60
61
  const [isGenerating, setIsGenerating] = useState(false)
61
62
  const [showLoginModal, setShowLoginModal] = useState(false)
62
63
  const [isThinking, setIsThinking] = useState(false)
64
+ const [notificationId, setNotificationId] = useState<string>("")
63
65
 
64
66
  const syllabus = history[history.length - 1]
65
67
 
@@ -89,8 +91,6 @@ const SyllabusEditor: React.FC = () => {
89
91
  }, [])
90
92
 
91
93
  const checkSlug = async () => {
92
- console.log("Checking slug")
93
-
94
94
  if (!syllabus.courseInfo.title) {
95
95
  toast.error("Please provide a title for the course")
96
96
  return
@@ -109,7 +109,6 @@ const SyllabusEditor: React.FC = () => {
109
109
  slug = slugify(newTitle)
110
110
  isAvailable = await isSlugAvailable(slug)
111
111
  }
112
- console.log("Slug is available", slug)
113
112
 
114
113
  push({
115
114
  ...syllabus,
@@ -162,44 +161,9 @@ const SyllabusEditor: React.FC = () => {
162
161
  auth.rigoToken && isAuthenticated ? false : true
163
162
  )
164
163
 
165
- const lessons: Lesson[] = res.parsed.listOfSteps.map((step: any) =>
166
- parseLesson(step, syllabus.lessons)
167
- )
168
- push({
169
- ...syllabus,
170
- lessons: lessons,
171
- courseInfo: {
172
- ...syllabus.courseInfo,
173
- title: fixTitleLength(res.parsed.title) || syllabus.courseInfo.title,
174
- description:
175
- res.parsed.description || syllabus.courseInfo.description,
176
- language:
177
- res.parsed.languageCode || syllabus.courseInfo.language || "en",
178
- technologies:
179
- res.parsed.technologies || syllabus.courseInfo.technologies || [],
180
- },
181
- })
182
-
183
- if (res.parsed.languageCode) {
184
- i18n.changeLanguage(res.parsed.languageCode)
185
- }
186
-
187
- const newMessages: TMessage[] = [
188
- ...messages,
189
- {
190
- type: "user",
191
- content: prompt,
192
- },
193
- {
194
- type: "assistant",
195
- content: res.parsed.aiMessage,
196
- },
197
- ]
198
- setMessages(newMessages)
199
- setIsThinking(false)
164
+ setNotificationId(res.notificationId)
200
165
  } catch (error) {
201
166
  console.error(error)
202
- // Remove the last message
203
167
  const newMessages = [...messages]
204
168
  newMessages.pop()
205
169
  setMessages(newMessages as TMessage[])
@@ -248,7 +212,6 @@ const SyllabusEditor: React.FC = () => {
248
212
  }
249
213
 
250
214
  setIsGenerating(true)
251
- console.log("Timeout set")
252
215
 
253
216
  const timeout = setTimeout(() => {
254
217
  cleanAll()
@@ -257,10 +220,8 @@ const SyllabusEditor: React.FC = () => {
257
220
  )}?token=${auth.bcToken}&language=${syllabus.courseInfo.language}`
258
221
  }, 8000)
259
222
  try {
260
- console.log("Creating course")
261
223
  await createCourse(syllabus, tokenToUse, auth.bcToken)
262
224
  } catch (error) {
263
- console.log("Failed to create course, clearing timeout")
264
225
  console.error("Failed to create course:", error)
265
226
  clearTimeout(timeout)
266
227
  setIsGenerating(false)
@@ -269,8 +230,6 @@ const SyllabusEditor: React.FC = () => {
269
230
 
270
231
  if (!syllabus) return null
271
232
 
272
- console.log(syllabus, "SYLLABUS")
273
-
274
233
  return isGenerating ? (
275
234
  <>
276
235
  <Loader
@@ -284,6 +243,45 @@ It may take a moment..."
284
243
  ) : (
285
244
  <div className="flex w-full bg-white rounded-md shadow-md overflow-hidden h-screen ">
286
245
  <ParamsChecker />
246
+ <NotificationListener
247
+ onNotification={(res) => {
248
+ const lessons: Lesson[] = res.parsed.listOfSteps.map((step: any) =>
249
+ parseLesson(step, syllabus.lessons)
250
+ )
251
+ push({
252
+ ...syllabus,
253
+ lessons: lessons,
254
+ courseInfo: {
255
+ ...syllabus.courseInfo,
256
+ title:
257
+ fixTitleLength(res.parsed.title) || syllabus.courseInfo.title,
258
+ description:
259
+ res.parsed.description || syllabus.courseInfo.description,
260
+ language:
261
+ res.parsed.languageCode || syllabus.courseInfo.language || "en",
262
+ technologies:
263
+ res.parsed.technologies.length > 0
264
+ ? res.parsed.technologies
265
+ : syllabus.courseInfo.technologies || [],
266
+ },
267
+ })
268
+
269
+ if (res.parsed.languageCode) {
270
+ i18n.changeLanguage(res.parsed.languageCode)
271
+ }
272
+
273
+ setMessages((prev) => [
274
+ ...prev.filter((m) => Boolean(m.content)),
275
+ {
276
+ type: "assistant",
277
+ content: res.parsed.aiMessage,
278
+ },
279
+ ])
280
+ setIsThinking(false)
281
+ setNotificationId("")
282
+ }}
283
+ notificationId={notificationId}
284
+ />
287
285
  {showLoginModal && (
288
286
  <Login
289
287
  onFinish={() => {
@@ -14,8 +14,8 @@ export const slugify = (text: string) => {
14
14
  .replace(/^-+|-+$/g, "") // Trim hyphens from start/end
15
15
  }
16
16
 
17
- export const randomUUID = () => {
18
- return Math.random().toString(36).substring(2, 15)
17
+ export const randomUUID = (length = 15) => {
18
+ return Math.random().toString(36).substring(2, length)
19
19
  }
20
20
 
21
21
  export const processImage = async (