@learnpack/learnpack 5.0.68 → 5.0.70
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-B01XTAAq.js → index-Chx6V3zd.js} +15989 -15785
- package/{src/creatorDist/assets/index-t6ma_gVm.css → lib/creatorDist/assets/index-Dqo9u2iR.css} +233 -35
- package/lib/creatorDist/index.html +2 -2
- package/lib/creatorDist/rigo-float.gif +0 -0
- package/lib/utils/api.js +2 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/creator/public/rigo-float.gif +0 -0
- package/src/creator/src/App.tsx +202 -163
- package/src/creator/src/assets/svgs.tsx +41 -1
- package/src/creator/src/components/LessonItem.tsx +10 -3
- package/src/creator/src/components/Message.tsx +1 -1
- package/src/creator/src/components/StepWizard.tsx +32 -18
- package/src/creator/src/components/syllabus/ContentIndex.tsx +202 -0
- package/src/creator/src/components/syllabus/Sidebar.tsx +123 -0
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +134 -0
- package/src/creator/src/index.css +2 -6
- package/src/creator/src/main.tsx +1 -1
- package/src/creator/src/utils/store.ts +13 -4
- package/src/creatorDist/assets/{index-B01XTAAq.js → index-Chx6V3zd.js} +15989 -15785
- package/{lib/creatorDist/assets/index-t6ma_gVm.css → src/creatorDist/assets/index-Dqo9u2iR.css} +233 -35
- package/src/creatorDist/index.html +2 -2
- package/src/creatorDist/rigo-float.gif +0 -0
- package/src/utils/api.ts +2 -2
- package/src/creator/src/components/SyllabusEditor.tsx +0 -300
@@ -1,300 +0,0 @@
|
|
1
|
-
import React, { useRef, useState } from "react"
|
2
|
-
import { SVGS } from "../assets/svgs"
|
3
|
-
import { useShallow } from "zustand/react/shallow"
|
4
|
-
import useStore from "../utils/store"
|
5
|
-
import { interactiveCreation } from "../utils/rigo"
|
6
|
-
import {
|
7
|
-
parseLesson,
|
8
|
-
uploadFileToBucket,
|
9
|
-
useConsumableCall,
|
10
|
-
} from "../utils/lib"
|
11
|
-
import {
|
12
|
-
createLearnJson,
|
13
|
-
processExercise,
|
14
|
-
slugify,
|
15
|
-
randomUUID,
|
16
|
-
} from "../utils/creatorUtils"
|
17
|
-
|
18
|
-
import Loader from "./Loader"
|
19
|
-
import { Message, TMessage } from "./Message"
|
20
|
-
import { LessonItem, Lesson } from "./LessonItem"
|
21
|
-
import FileUploader from "./FileUploader"
|
22
|
-
import { ConsumablesManager } from "./ConsumablesManager"
|
23
|
-
import toast from "react-hot-toast"
|
24
|
-
|
25
|
-
const GenerateButton = ({ handleSubmit }: { handleSubmit: () => void }) => {
|
26
|
-
const auth = useStore((state) => state.auth)
|
27
|
-
return (
|
28
|
-
<div className="flex justify-end mt-6">
|
29
|
-
<button
|
30
|
-
onClick={async () => {
|
31
|
-
const success = await useConsumableCall(auth.bcToken, "ai-generation")
|
32
|
-
if (success) {
|
33
|
-
handleSubmit()
|
34
|
-
} else {
|
35
|
-
toast.error("You don't have enough credits to generate a course.")
|
36
|
-
}
|
37
|
-
}}
|
38
|
-
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 cursor-pointer flex items-center gap-2"
|
39
|
-
>
|
40
|
-
<span>I'm ready. Create the course for me!</span>
|
41
|
-
{SVGS.rigoSoftBlue}
|
42
|
-
</button>
|
43
|
-
</div>
|
44
|
-
)
|
45
|
-
}
|
46
|
-
|
47
|
-
const SyllabusEditor: React.FC = () => {
|
48
|
-
const inputRef = useRef<HTMLTextAreaElement>(null)
|
49
|
-
// const navigate = useNavigate()
|
50
|
-
const [messages, setMessages] = useState<TMessage[]>([])
|
51
|
-
const [isGenerating, setIsGenerating] = useState(false)
|
52
|
-
const prevLessons = useRef<Lesson[]>([])
|
53
|
-
const { syllabus, setSyllabus, auth } = useStore(
|
54
|
-
useShallow((state) => ({
|
55
|
-
syllabus: state.syllabus,
|
56
|
-
setSyllabus: state.setSyllabus,
|
57
|
-
auth: state.auth,
|
58
|
-
}))
|
59
|
-
)
|
60
|
-
|
61
|
-
const handleRemove = (id: string) => {
|
62
|
-
setSyllabus({
|
63
|
-
...syllabus,
|
64
|
-
lessons: syllabus.lessons.filter((lesson) => lesson.id !== id),
|
65
|
-
})
|
66
|
-
}
|
67
|
-
|
68
|
-
const handleChange = (id: string, newTitle: string) => {
|
69
|
-
setSyllabus({
|
70
|
-
...syllabus,
|
71
|
-
lessons: syllabus.lessons.map((lesson) =>
|
72
|
-
lesson.id === id ? { ...lesson, title: newTitle } : lesson
|
73
|
-
),
|
74
|
-
})
|
75
|
-
}
|
76
|
-
|
77
|
-
const addLessonAfter = (index: number, id: string) => {
|
78
|
-
const newLesson: Lesson = {
|
79
|
-
id: (parseFloat(id) + 0.1).toString(),
|
80
|
-
title: "Hello World",
|
81
|
-
type: "READ",
|
82
|
-
duration: 2,
|
83
|
-
description: "Hello World",
|
84
|
-
}
|
85
|
-
const updated = [...syllabus.lessons]
|
86
|
-
updated.splice(index + 1, 0, newLesson)
|
87
|
-
|
88
|
-
setSyllabus({
|
89
|
-
lessons: updated,
|
90
|
-
})
|
91
|
-
}
|
92
|
-
|
93
|
-
const sendPrompt = async (prompt: string) => {
|
94
|
-
setMessages([
|
95
|
-
...messages,
|
96
|
-
{ type: "user", content: prompt },
|
97
|
-
{ type: "assistant", content: "" },
|
98
|
-
])
|
99
|
-
prevLessons.current = syllabus.lessons
|
100
|
-
const res = await interactiveCreation(auth.rigoToken, {
|
101
|
-
courseInfo: JSON.stringify(syllabus),
|
102
|
-
prevInteractions:
|
103
|
-
messages
|
104
|
-
.map((message) => `${message.type}: ${message.content}`)
|
105
|
-
.join("\n") + `\nUSER: ${prompt}`,
|
106
|
-
})
|
107
|
-
console.log(res, "RES")
|
108
|
-
const lessons: Lesson[] = res.parsed.listOfSteps.map((step: any) =>
|
109
|
-
parseLesson(step)
|
110
|
-
)
|
111
|
-
setSyllabus({
|
112
|
-
...syllabus,
|
113
|
-
lessons: lessons,
|
114
|
-
courseInfo: {
|
115
|
-
...syllabus.courseInfo,
|
116
|
-
title: res.parsed.title || syllabus.courseInfo.title,
|
117
|
-
},
|
118
|
-
})
|
119
|
-
setMessages((prev) => {
|
120
|
-
const newMessages = [...prev]
|
121
|
-
newMessages[newMessages.length - 1].content = res.parsed.aiMessage
|
122
|
-
return newMessages
|
123
|
-
})
|
124
|
-
}
|
125
|
-
|
126
|
-
const handleSubmit = async () => {
|
127
|
-
setIsGenerating(true)
|
128
|
-
|
129
|
-
const lessonsPromises = syllabus.lessons.map((lesson) =>
|
130
|
-
processExercise(
|
131
|
-
auth.rigoToken,
|
132
|
-
syllabus.lessons,
|
133
|
-
JSON.stringify(syllabus.courseInfo),
|
134
|
-
lesson,
|
135
|
-
"courses/" +
|
136
|
-
slugify(syllabus.courseInfo.title || randomUUID()) +
|
137
|
-
"/exercises"
|
138
|
-
)
|
139
|
-
)
|
140
|
-
await Promise.all(lessonsPromises)
|
141
|
-
|
142
|
-
const learnJson = createLearnJson(syllabus.courseInfo)
|
143
|
-
await uploadFileToBucket(
|
144
|
-
JSON.stringify(learnJson),
|
145
|
-
"courses/" +
|
146
|
-
slugify(syllabus.courseInfo.title || randomUUID()) +
|
147
|
-
"/learn.json"
|
148
|
-
)
|
149
|
-
setIsGenerating(false)
|
150
|
-
|
151
|
-
window.location.href = `/?slug=${slugify(
|
152
|
-
syllabus.courseInfo.title || "exercises"
|
153
|
-
)}&token=${auth.bcToken}`
|
154
|
-
}
|
155
|
-
|
156
|
-
return isGenerating ? (
|
157
|
-
<Loader
|
158
|
-
listeningTo="course-generation"
|
159
|
-
icon={SVGS.rigoSoftBlue}
|
160
|
-
initialBuffer="🚀 Starting course generation..."
|
161
|
-
text="Learnpack is setting up your tutorial.
|
162
|
-
It may take a moment..."
|
163
|
-
/>
|
164
|
-
) : (
|
165
|
-
<div className="flex w-full bg-white rounded-md shadow-md overflow-hidden h-screen ">
|
166
|
-
<ConsumablesManager />
|
167
|
-
{/* Sidebar */}
|
168
|
-
<div className="w-1/3 p-6 text-sm text-gray-700 border-r bg-learnpack-blue h-screen overflow-y-auto scrollbar-hide relative">
|
169
|
-
<div className="p-4 rounded-md bg-white shadow-sm">
|
170
|
-
<p>We generated this syllabus based on your answers.</p>
|
171
|
-
<p className="mt-2">If you're satisfied, type "OK" in the chat.</p>
|
172
|
-
<p className="mt-2">If not, use the chat to give more context.</p>
|
173
|
-
</div>
|
174
|
-
|
175
|
-
<div className="mt-10 space-y-2 pb-16">
|
176
|
-
{messages.map((message, index) => (
|
177
|
-
<Message
|
178
|
-
key={index}
|
179
|
-
type={message.type}
|
180
|
-
content={message.content}
|
181
|
-
/>
|
182
|
-
))}
|
183
|
-
</div>
|
184
|
-
|
185
|
-
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 w-[90%] border rounded-md bg-white text-gray-400 resize-none h-24 ">
|
186
|
-
<textarea
|
187
|
-
ref={inputRef}
|
188
|
-
style={{ resize: "none" }}
|
189
|
-
className="w-full h-full p-2"
|
190
|
-
placeholder="How can Learnpack help you?"
|
191
|
-
autoFocus
|
192
|
-
onKeyUp={(e) => {
|
193
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
194
|
-
e.preventDefault()
|
195
|
-
sendPrompt(inputRef.current?.value || "")
|
196
|
-
inputRef.current!.value = ""
|
197
|
-
}
|
198
|
-
}}
|
199
|
-
/>
|
200
|
-
<div className="absolute bottom-2 right-2 flex gap-1 items-center">
|
201
|
-
<div className="relative inline-block">
|
202
|
-
{syllabus.uploadedFiles?.length > 0 && (
|
203
|
-
<span
|
204
|
-
className="absolute -top-1 right-0 inline-flex items-center justify-center w-3 h-3 text-[10px] text-white bg-blue-200 rounded-full hover:bg-red-300 cursor-pointer"
|
205
|
-
title="Remove uploaded files"
|
206
|
-
onClick={() => {
|
207
|
-
setSyllabus({
|
208
|
-
...syllabus,
|
209
|
-
uploadedFiles: [],
|
210
|
-
})
|
211
|
-
}}
|
212
|
-
>
|
213
|
-
{syllabus.uploadedFiles?.length}
|
214
|
-
</span>
|
215
|
-
)}
|
216
|
-
<FileUploader
|
217
|
-
onResult={(res) => {
|
218
|
-
setSyllabus({
|
219
|
-
...syllabus,
|
220
|
-
uploadedFiles: [...syllabus.uploadedFiles, ...res],
|
221
|
-
})
|
222
|
-
}}
|
223
|
-
/>
|
224
|
-
</div>
|
225
|
-
|
226
|
-
<button
|
227
|
-
className="cursor-pointer blue-on-hover flex items-center justify-center w-6 h-6"
|
228
|
-
onClick={() => sendPrompt(inputRef.current?.value || "")}
|
229
|
-
>
|
230
|
-
{SVGS.send}
|
231
|
-
</button>
|
232
|
-
</div>
|
233
|
-
</div>
|
234
|
-
</div>
|
235
|
-
|
236
|
-
{/* Editor */}
|
237
|
-
<div className="w-2/3 p-8 space-y-6">
|
238
|
-
<div>
|
239
|
-
<h2 className="text-lg font-semibold">
|
240
|
-
{messages.filter(
|
241
|
-
(m) => m.type === "assistant" && m.content.length > 0
|
242
|
-
).length === 0
|
243
|
-
? "I've created a detailed structure for your course."
|
244
|
-
: "I've updated the structure based on your feedback."}
|
245
|
-
</h2>
|
246
|
-
<p className="text-sm text-gray-600">
|
247
|
-
{messages.filter(
|
248
|
-
(m) => m.type === "assistant" && m.content.length > 0
|
249
|
-
).length === 0
|
250
|
-
? `It includes a mix of reading, coding exercises, and quizzes. Give
|
251
|
-
it a look and let me know if it aligns with your expectations or if
|
252
|
-
there are any changes you'd like to make.`
|
253
|
-
: "Based on your input, here is the new syllabus, updates are highlighted in yellow"}
|
254
|
-
</p>
|
255
|
-
<h3 className="text-sm text-gray-600 mt-2 font-bold">
|
256
|
-
{syllabus.courseInfo.title}
|
257
|
-
</h3>
|
258
|
-
</div>
|
259
|
-
|
260
|
-
{/* Lessons */}
|
261
|
-
<div className="space-y-3 overflow-y-auto max-h-[60vh] pr-2 scrollbar-hide">
|
262
|
-
{syllabus.lessons.map((lesson, index) => (
|
263
|
-
<div key={lesson.id}>
|
264
|
-
<LessonItem
|
265
|
-
key={lesson.id}
|
266
|
-
index={lesson.id}
|
267
|
-
lesson={lesson}
|
268
|
-
onChange={handleChange}
|
269
|
-
onRemove={handleRemove}
|
270
|
-
isNew={
|
271
|
-
prevLessons.current.length > 0 &&
|
272
|
-
!prevLessons.current.some(
|
273
|
-
(l) =>
|
274
|
-
l.id === lesson.id &&
|
275
|
-
l.title === lesson.title &&
|
276
|
-
l.type === lesson.type
|
277
|
-
)
|
278
|
-
}
|
279
|
-
/>
|
280
|
-
<div className="relative h-6">
|
281
|
-
<div className="absolute left-1/2 -translate-x-1/2 -top-3">
|
282
|
-
<button
|
283
|
-
onClick={() => addLessonAfter(index, lesson.id)}
|
284
|
-
className="w-6 h-6 flex items-center justify-center bg-blue-100 text-blue-600 rounded hover:bg-blue-200 shadow-sm text-sm font-semibold cursor-pointer"
|
285
|
-
>
|
286
|
-
+
|
287
|
-
</button>
|
288
|
-
</div>
|
289
|
-
</div>
|
290
|
-
</div>
|
291
|
-
))}
|
292
|
-
</div>
|
293
|
-
|
294
|
-
<GenerateButton handleSubmit={handleSubmit} />
|
295
|
-
</div>
|
296
|
-
</div>
|
297
|
-
)
|
298
|
-
}
|
299
|
-
|
300
|
-
export default SyllabusEditor
|