@learnpack/learnpack 5.0.234 → 5.0.240

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.
@@ -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 (
@@ -1,6 +1,7 @@
1
1
  import toast from "react-hot-toast"
2
2
  import { RIGOBOT_HOST, DEV_MODE } from "./constants"
3
3
  import axios, { AxiosError } from "axios"
4
+ import { randomUUID } from "./creatorUtils"
4
5
 
5
6
  type TInteractiveCreationInputs = {
6
7
  courseInfo: string
@@ -14,6 +15,14 @@ export const publicInteractiveCreation = async (
14
15
  publicRequest: boolean = true
15
16
  ): Promise<any | null> => {
16
17
  try {
18
+ const randomUID = randomUUID(15)
19
+ const webhookUrl = `${
20
+ DEV_MODE
21
+ ? "https://9cw5zmww-3000.use2.devtunnels.ms"
22
+ : window.location.origin
23
+ // "https://9cw5zmww-3000.use2.devtunnels.ms"
24
+ }/notifications/${randomUID}`
25
+
17
26
  const response = await axios.post(
18
27
  `${RIGOBOT_HOST}/v1/prompting${
19
28
  publicRequest ? "/public" : ""
@@ -21,8 +30,8 @@ export const publicInteractiveCreation = async (
21
30
  {
22
31
  inputs: inputs,
23
32
  include_purpose_objective: true,
24
- execute_async: false,
25
33
  purpose_slug: purposeSlug,
34
+ webhook_url: webhookUrl,
26
35
  },
27
36
  {
28
37
  headers: {
@@ -32,10 +41,10 @@ export const publicInteractiveCreation = async (
32
41
  }
33
42
  )
34
43
 
35
- return response.data
44
+ return { res: response.data, notificationId: randomUID }
36
45
  } catch (error: unknown) {
37
46
  const err = error as AxiosError
38
- console.log("error", err)
47
+ console.log("error trying to create course", err)
39
48
 
40
49
  if (err.response?.status === 403) {
41
50
  toast.error("You've reached the limit. Please log in to continue.")
@@ -53,7 +53,9 @@ type Store = {
53
53
  uploadedFiles: ParsedFile[]
54
54
  setUploadedFiles: (uploadedFiles: ParsedFile[]) => void
55
55
  messages: TMessage[]
56
- setMessages: (messages: TMessage[]) => void
56
+ setMessages: (
57
+ messages: TMessage[] | ((prev: TMessage[]) => TMessage[])
58
+ ) => void
57
59
  cleanHistory: () => void
58
60
  history: Syllabus[]
59
61
  technologies: TTechnology[]
@@ -102,7 +104,16 @@ const useStore = create<Store>()(
102
104
  messages: [],
103
105
  technologies: [],
104
106
  setTechnologies: (technologies: TTechnology[]) => set({ technologies }),
105
- setMessages: (messages: TMessage[]) => set({ messages }),
107
+ setMessages: (
108
+ messages: TMessage[] | ((prev: TMessage[]) => TMessage[])
109
+ ) => {
110
+ set((state) => ({
111
+ messages:
112
+ typeof messages === "function"
113
+ ? messages(state.messages)
114
+ : messages,
115
+ }))
116
+ },
106
117
  setFormState: (formState: Partial<FormState>) =>
107
118
  set((state) => ({ formState: { ...state.formState, ...formState } })),
108
119
  resetFormState: () =>