@learnpack/learnpack 5.0.81 → 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.
- package/README.md +13 -13
- package/lib/creatorDist/assets/{index-WzdhAujs.js → index-D15TgYvM.js} +38777 -31507
- package/lib/creatorDist/assets/{index-BJ2JJzVC.css → index-ldEC0yWM.css} +148 -41
- package/lib/creatorDist/index.html +2 -2
- package/lib/utils/api.js +0 -8
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/creator/package-lock.json +1229 -61
- package/src/creator/package.json +1 -0
- package/src/creator/src/App.tsx +6 -6
- package/src/creator/src/assets/svgs.tsx +18 -0
- package/src/creator/src/components/FileCard.tsx +22 -0
- package/src/creator/src/components/LessonItem.tsx +20 -9
- package/src/creator/src/components/Login.tsx +10 -24
- package/src/creator/src/components/MarkdownRenderer.tsx +11 -0
- package/src/creator/src/components/Message.tsx +4 -2
- package/src/creator/src/components/syllabus/ContentIndex.tsx +53 -42
- package/src/creator/src/components/syllabus/Sidebar.tsx +22 -20
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +63 -30
- package/src/creator/src/index.css +47 -0
- package/src/creator/src/utils/creatorUtils.ts +5 -1
- package/src/creator/src/utils/lib.ts +20 -10
- package/src/creator/src/utils/store.ts +31 -22
- package/src/creatorDist/assets/{index-WzdhAujs.js → index-D15TgYvM.js} +38777 -31507
- package/src/creatorDist/assets/{index-BJ2JJzVC.css → index-ldEC0yWM.css} +148 -41
- package/src/creatorDist/index.html +2 -2
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +344 -344
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +0 -9
@@ -1,4 +1,4 @@
|
|
1
|
-
import 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
|
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
|
47
|
-
const { syllabus, setSyllabus, auth, setAuth } = useStore(
|
49
|
+
const { history, auth, setAuth, push, uploadedFiles } = useStore(
|
48
50
|
useShallow((state) => ({
|
49
|
-
|
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
|
-
|
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
|
-
|
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,23 +186,21 @@ 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 =
|
158
|
-
syllabus.courseInfo.title
|
159
|
-
)}
|
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"
|
165
|
-
icon={
|
166
|
-
<img src={"rigo-float.gif"} alt="rigo" className="w-20 h-20" />
|
167
|
-
}
|
203
|
+
icon={<img src={"rigo-float.gif"} alt="rigo" className="w-20 h-20" />}
|
168
204
|
initialBuffer="🚀 Starting course generation..."
|
169
205
|
text="Learnpack is setting up your tutorial.
|
170
206
|
It may take a moment..."
|
@@ -187,14 +223,11 @@ It may take a moment..."
|
|
187
223
|
handleSubmit={handleSubmit}
|
188
224
|
/>
|
189
225
|
|
190
|
-
<
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
isThinking={isThinking}
|
196
|
-
/>
|
197
|
-
</div>
|
226
|
+
<ContentIndex
|
227
|
+
handleSubmit={handleSubmit}
|
228
|
+
messages={messages}
|
229
|
+
isThinking={isThinking}
|
230
|
+
/>
|
198
231
|
</div>
|
199
232
|
)
|
200
233
|
}
|
@@ -141,3 +141,50 @@ h1 {
|
|
141
141
|
top: -10px;
|
142
142
|
left: 10px;
|
143
143
|
}
|
144
|
+
|
145
|
+
.fluid-svg {
|
146
|
+
> svg {
|
147
|
+
width: 100%;
|
148
|
+
height: 100%;
|
149
|
+
}
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
103
|
-
set((state) => ({ syllabus: { ...state.syllabus, ...syllabus } })),
|
112
|
+
|
104
113
|
setAuth: (auth: Auth) => set({ auth }),
|
105
114
|
}),
|
106
115
|
{
|