@learnpack/learnpack 5.0.83 → 5.0.85

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.
Files changed (29) hide show
  1. package/README.md +13 -13
  2. package/lib/creatorDist/assets/{index-CwPh6b6M.js → index-D15TgYvM.js} +38846 -31631
  3. package/lib/creatorDist/assets/{index-DSLkFVbA.css → index-ldEC0yWM.css} +125 -46
  4. package/lib/creatorDist/index.html +2 -2
  5. package/lib/utils/api.js +0 -8
  6. package/oclif.manifest.json +1 -1
  7. package/package.json +1 -1
  8. package/src/creator/package-lock.json +1229 -61
  9. package/src/creator/package.json +1 -0
  10. package/src/creator/src/App.tsx +12 -6
  11. package/src/creator/src/components/FileCard.tsx +22 -0
  12. package/src/creator/src/components/LessonItem.tsx +20 -9
  13. package/src/creator/src/components/Login.tsx +6 -24
  14. package/src/creator/src/components/MarkdownRenderer.tsx +11 -0
  15. package/src/creator/src/components/Message.tsx +4 -2
  16. package/src/creator/src/components/syllabus/ContentIndex.tsx +45 -64
  17. package/src/creator/src/components/syllabus/Sidebar.tsx +21 -19
  18. package/src/creator/src/components/syllabus/SyllabusEditor.tsx +62 -33
  19. package/src/creator/src/index.css +40 -0
  20. package/src/creator/src/utils/creatorUtils.ts +5 -1
  21. package/src/creator/src/utils/lib.ts +20 -10
  22. package/src/creator/src/utils/store.ts +31 -22
  23. package/src/creatorDist/assets/{index-CwPh6b6M.js → index-D15TgYvM.js} +38846 -31631
  24. package/src/creatorDist/assets/{index-DSLkFVbA.css → index-ldEC0yWM.css} +125 -46
  25. package/src/creatorDist/index.html +2 -2
  26. package/src/ui/_app/app.css +1 -1
  27. package/src/ui/_app/app.js +344 -344
  28. package/src/ui/app.tar.gz +0 -0
  29. package/src/utils/api.ts +0 -9
@@ -1,4 +1,4 @@
1
- import React, { useRef, useState } from "react"
1
+ import React, { useState, useEffect } from "react"
2
2
  import { useShallow } from "zustand/react/shallow"
3
3
  import useStore from "../../utils/store"
4
4
  import { interactiveCreation } from "../../utils/rigo"
@@ -8,12 +8,13 @@ import {
8
8
  useConsumableCall,
9
9
  validateTokens,
10
10
  extractImagesFromMarkdown,
11
+ checkParams,
12
+ loginWithToken,
11
13
  } from "../../utils/lib"
12
14
  import {
13
15
  createLearnJson,
14
16
  processExercise,
15
17
  slugify,
16
- randomUUID,
17
18
  processImage,
18
19
  } from "../../utils/creatorUtils"
19
20
 
@@ -26,8 +27,10 @@ import { ContentIndex } from "./ContentIndex"
26
27
  import { Sidebar } from "./Sidebar"
27
28
  import Login from "../Login"
28
29
  import { eventBus } from "../../utils/eventBus"
30
+ import { useNavigate } from "react-router"
29
31
 
30
32
  const SyllabusEditor: React.FC = () => {
33
+ const navigate = useNavigate()
31
34
  const [messages, setMessages] = useState<TMessage[]>([
32
35
  {
33
36
  type: "assistant",
@@ -36,23 +39,48 @@ const SyllabusEditor: React.FC = () => {
36
39
  {
37
40
  type: "assistant",
38
41
  content:
39
- "If not, what would you like me to change? You can sat things like: 'Add more exercises', 'Make it more difficult', 'Remove step 1.1 and replace it with a new step that explains the concept of X'",
42
+ "If not, what would you like me to change? You can sat things like:\n - Add more exercises\n - Make it more difficult\n - Remove step 1.1 and replace it with a new step that explains the concept of X ",
40
43
  },
41
44
  ])
42
45
  const [isGenerating, setIsGenerating] = useState(false)
43
46
  const [showLoginModal, setShowLoginModal] = useState(false)
44
47
  const [isThinking, setIsThinking] = useState(false)
45
48
 
46
- const prevLessons = useRef<Lesson[]>([])
47
- const { syllabus, setSyllabus, auth, setAuth } = useStore(
49
+ const { history, auth, setAuth, push, uploadedFiles } = useStore(
48
50
  useShallow((state) => ({
49
- syllabus: state.syllabus,
50
- setSyllabus: state.setSyllabus,
51
+ history: state.history,
51
52
  auth: state.auth,
52
53
  setAuth: state.setAuth,
54
+ push: state.push,
55
+ uploadedFiles: state.uploadedFiles,
53
56
  }))
54
57
  )
55
58
 
59
+ const syllabus = history[history.length - 1]
60
+
61
+ useEffect(() => {
62
+ if (!syllabus) {
63
+ navigate("/creator", { replace: true })
64
+ }
65
+ }, [syllabus, navigate])
66
+
67
+ useEffect(() => {
68
+ ;(async () => {
69
+ const { token } = checkParams()
70
+ if (token) {
71
+ const user = await loginWithToken(token)
72
+ if (user) {
73
+ setAuth({
74
+ bcToken: token,
75
+ userId: user.id,
76
+ rigoToken: user.rigobot.key,
77
+ user,
78
+ })
79
+ }
80
+ }
81
+ })()
82
+ }, [])
83
+
56
84
  const sendPrompt = async (prompt: string) => {
57
85
  setIsThinking(true)
58
86
 
@@ -61,9 +89,15 @@ const SyllabusEditor: React.FC = () => {
61
89
  { type: "user", content: prompt },
62
90
  { type: "assistant", content: "" },
63
91
  ])
64
- prevLessons.current = syllabus.lessons
92
+
65
93
  const res = await interactiveCreation({
66
- courseInfo: JSON.stringify(syllabus),
94
+ courseInfo: `${JSON.stringify(syllabus.courseInfo)}
95
+ ${
96
+ uploadedFiles.length > 0 &&
97
+ "The user has uploaded files, take them in consideration while generating the course structure." +
98
+ JSON.stringify(uploadedFiles)
99
+ }
100
+ `,
67
101
  prevInteractions:
68
102
  messages
69
103
  .map((message) => `${message.type}: ${message.content}`)
@@ -71,9 +105,9 @@ const SyllabusEditor: React.FC = () => {
71
105
  })
72
106
 
73
107
  const lessons: Lesson[] = res.parsed.listOfSteps.map((step: any) =>
74
- parseLesson(step)
108
+ parseLesson(step, syllabus.lessons)
75
109
  )
76
- setSyllabus({
110
+ push({
77
111
  ...syllabus,
78
112
  lessons: lessons,
79
113
  courseInfo: {
@@ -95,6 +129,11 @@ const SyllabusEditor: React.FC = () => {
95
129
  return
96
130
  }
97
131
 
132
+ if (!syllabus.courseInfo.title) {
133
+ toast.error("Please provide a title for the course")
134
+ return
135
+ }
136
+
98
137
  let tokenToUse = auth.rigoToken
99
138
  const onValidRigoToken = (rigotoken: string) => {
100
139
  setAuth({
@@ -115,8 +154,7 @@ const SyllabusEditor: React.FC = () => {
115
154
  }
116
155
  setIsGenerating(true)
117
156
 
118
- const tutorialDir =
119
- "courses/" + slugify(syllabus.courseInfo.title || randomUUID())
157
+ const tutorialDir = "courses/" + slugify(syllabus.courseInfo.title)
120
158
  const lessonsPromises = syllabus.lessons.map((lesson) =>
121
159
  processExercise(
122
160
  tokenToUse,
@@ -148,17 +186,17 @@ const SyllabusEditor: React.FC = () => {
148
186
  const learnJson = createLearnJson(syllabus.courseInfo)
149
187
  await uploadFileToBucket(
150
188
  JSON.stringify(learnJson),
151
- "courses/" +
152
- slugify(syllabus.courseInfo.title || randomUUID()) +
153
- "/learn.json"
189
+ "courses/" + slugify(syllabus.courseInfo.title) + "/learn.json"
154
190
  )
155
191
  setIsGenerating(false)
156
192
 
157
- window.location.href = `/?slug=${slugify(
158
- syllabus.courseInfo.title || "exercises"
159
- )}&token=${auth.bcToken}`
193
+ window.location.href = `preview/${slugify(
194
+ syllabus.courseInfo.title
195
+ )}?token=${auth.rigoToken}`
160
196
  }
161
197
 
198
+ if (!syllabus) return null
199
+
162
200
  return isGenerating ? (
163
201
  <Loader
164
202
  listeningTo="course-generation"
@@ -185,20 +223,11 @@ It may take a moment..."
185
223
  handleSubmit={handleSubmit}
186
224
  />
187
225
 
188
- <div className="w-full p-8 space-y-6">
189
- <ContentIndex
190
- prevLessons={prevLessons.current}
191
- handleSubmit={handleSubmit}
192
- messages={messages}
193
- handleUndo={() => {
194
- setSyllabus({
195
- ...syllabus,
196
- lessons: prevLessons.current,
197
- })
198
- }}
199
- isThinking={isThinking}
200
- />
201
- </div>
226
+ <ContentIndex
227
+ handleSubmit={handleSubmit}
228
+ messages={messages}
229
+ isThinking={isThinking}
230
+ />
202
231
  </div>
203
232
  )
204
233
  }
@@ -148,3 +148,43 @@ h1 {
148
148
  height: 100%;
149
149
  }
150
150
  }
151
+
152
+ .outline-blue:focus {
153
+ outline: 1px solid #51b3e5;
154
+ }
155
+
156
+ .border-C8DBFC {
157
+ border-color: #c8dbfc;
158
+ }
159
+
160
+ .markdown-renderer {
161
+ & ul {
162
+ padding-left: 0;
163
+ list-style-position: inside;
164
+ list-style-type: disc;
165
+ }
166
+
167
+ & ol {
168
+ padding-left: 0;
169
+ list-style-position: inside;
170
+ list-style-type: decimal;
171
+ }
172
+ }
173
+
174
+ .appear {
175
+ animation: appear 0.5s ease-in-out forwards;
176
+ transform-origin: top center; /* o donde quieras que crezca */
177
+ opacity: 0;
178
+ transform: scaleY(0) translateY(20px);
179
+ }
180
+
181
+ @keyframes appear {
182
+ from {
183
+ opacity: 0;
184
+ transform: scaleY(0) translateY(20px);
185
+ }
186
+ to {
187
+ opacity: 1;
188
+ transform: scaleY(1) translateY(0);
189
+ }
190
+ }
@@ -18,14 +18,18 @@ export const slugify = (text: string) => {
18
18
 
19
19
  export const createLearnJson = (courseInfo: FormState) => {
20
20
  const learnJson = {
21
- slug: slugify(courseInfo.title || randomUUID()),
21
+ slug: slugify(courseInfo.title as string),
22
22
  title: {
23
23
  us: courseInfo.title,
24
24
  },
25
+ difficulty: "beginner",
25
26
  description: {
26
27
  us: courseInfo.description,
27
28
  },
28
29
  grading: "isolated",
30
+ telemetry: {
31
+ batch: "https://breathecode.herokuapp.com/v1/assignment/me/telemetry",
32
+ },
29
33
  }
30
34
  return learnJson
31
35
  }
@@ -1,15 +1,9 @@
1
1
  import axios from "axios"
2
2
  import { BREATHECODE_HOST, RIGOBOT_HOST } from "./constants"
3
+ import { Lesson } from "../components/LessonItem"
4
+ import { randomUUID } from "./creatorUtils"
3
5
 
4
- type ParsedLesson = {
5
- id: string
6
- title: string
7
- type: string
8
- description: string
9
- duration?: number
10
- }
11
-
12
- export function parseLesson(input: string): ParsedLesson | null {
6
+ export function parseLesson(input: string, previous: Lesson[]): Lesson | null {
13
7
  const pattern = /^([\d.]+)\s*-\s*(.*?)\s*\[(\w+):\s*(.+)\]$/
14
8
  const match = input.match(pattern)
15
9
 
@@ -17,12 +11,28 @@ export function parseLesson(input: string): ParsedLesson | null {
17
11
 
18
12
  const [, index, title, type, description] = match
19
13
 
14
+ const alreadyExistsIndex = previous.findIndex(
15
+ (lesson) => lesson.id === index && lesson.title === title
16
+ )
17
+
18
+ if (alreadyExistsIndex !== -1) {
19
+ return {
20
+ id: index,
21
+ uid: previous[alreadyExistsIndex].uid,
22
+ title: title.trim(),
23
+ type: type.trim().toUpperCase() as "READ" | "CODE" | "QUIZ",
24
+ description: description.trim(),
25
+ duration: previous[alreadyExistsIndex].duration,
26
+ }
27
+ }
28
+
20
29
  return {
21
30
  id: index,
22
31
  title: title.trim(),
23
- type: type.trim().toUpperCase(),
32
+ type: type.trim().toUpperCase() as "READ" | "CODE" | "QUIZ",
24
33
  description: description.trim(),
25
34
  duration: 2,
35
+ uid: randomUUID(),
26
36
  }
27
37
  }
28
38
 
@@ -23,22 +23,28 @@ type Auth = {
23
23
  export type Syllabus = {
24
24
  lessons: Lesson[]
25
25
  courseInfo: FormState
26
- uploadedFiles: {
27
- name: string
28
- text: string
29
- }[]
30
26
  }
31
27
 
32
28
  type Consumables = {
33
29
  [key: string]: number
34
30
  }
35
31
 
32
+ export type UploadedFile = {
33
+ name: string
34
+ text: string
35
+ }
36
36
  type Store = {
37
37
  auth: Auth
38
38
  formState: FormState
39
39
  setAuth: (auth: Auth) => void
40
- syllabus: Syllabus
41
- setSyllabus: (syllabus: Partial<Syllabus>) => void
40
+ // syllabus: Syllabus
41
+ uploadedFiles: UploadedFile[]
42
+ setUploadedFiles: (uploadedFiles: UploadedFile[]) => void
43
+
44
+ history: Syllabus[]
45
+ undo: () => void
46
+ push: (syllabus: Syllabus) => void
47
+ // setSyllabus: (syllabus: Partial<Syllabus>) => void
42
48
  setFormState: (formState: Partial<FormState>) => void
43
49
  consumables: Consumables
44
50
  setConsumables: (consumables: Partial<Consumables>) => void
@@ -71,20 +77,24 @@ const useStore = create<Store>()(
71
77
  setFormState: (formState: Partial<FormState>) =>
72
78
  set((state) => ({ formState: { ...state.formState, ...formState } })),
73
79
 
74
- syllabus: {
75
- lessons: [],
76
- courseInfo: {
77
- description: "",
78
- duration: 0,
79
- targetAudience: "",
80
- hasContentIndex: false,
81
- contentIndex: "",
82
- isCompleted: false,
83
- currentStep: "description",
84
- title: "",
85
- variables: [],
86
- },
87
- uploadedFiles: [],
80
+ setUploadedFiles: (uploadedFiles) =>
81
+ set(() => ({
82
+ uploadedFiles: [...uploadedFiles],
83
+ })),
84
+ uploadedFiles: [],
85
+ history: [],
86
+ push: (syllabus: Syllabus) => {
87
+ set((state) => ({
88
+ history: [...state.history, syllabus],
89
+ }))
90
+ },
91
+
92
+ undo: () => {
93
+ set((state) => {
94
+ return {
95
+ history: state.history.slice(0, -1),
96
+ }
97
+ })
88
98
  },
89
99
  consumables: {},
90
100
  setConsumables: (consumables: Partial<Consumables>) =>
@@ -99,8 +109,7 @@ const useStore = create<Store>()(
99
109
  },
100
110
  }
101
111
  }),
102
- setSyllabus: (syllabus: Partial<Syllabus>) =>
103
- set((state) => ({ syllabus: { ...state.syllabus, ...syllabus } })),
112
+
104
113
  setAuth: (auth: Auth) => set({ auth }),
105
114
  }),
106
115
  {