@learnpack/learnpack 5.0.178 → 5.0.180
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/commands/serve.js +32 -18
- package/lib/creatorDist/assets/{index-CrWESWmj.css → index-Bnq3eZ3T.css} +13 -3
- package/lib/creatorDist/assets/index-hhajeHFt.js +35366 -0
- package/lib/creatorDist/index.html +2 -2
- package/lib/utils/creatorSocket.d.ts +1 -0
- package/lib/utils/creatorSocket.js +24 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/serve.ts +48 -26
- package/src/creator/package-lock.json +137 -0
- package/src/creator/package.json +1 -0
- package/src/creator/src/App.tsx +77 -50
- package/src/creator/src/components/ConsumablesManager.tsx +1 -3
- package/src/creator/src/components/FileUploader.tsx +136 -44
- package/src/creator/src/components/PurposeSelector.tsx +70 -0
- package/src/creator/src/components/SelectableCard.tsx +5 -3
- package/src/creator/src/components/Uploader.tsx +12 -1
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +6 -12
- package/src/creator/src/utils/constants.ts +10 -4
- package/src/creator/src/utils/lib.ts +0 -2
- package/src/creator/src/utils/rigo.ts +9 -5
- package/src/creator/src/utils/socket.ts +61 -0
- package/src/creator/src/utils/store.ts +7 -1
- package/src/creatorDist/assets/{index-CrWESWmj.css → index-Bnq3eZ3T.css} +13 -3
- package/src/creatorDist/assets/index-hhajeHFt.js +35366 -0
- package/src/creatorDist/index.html +2 -2
- package/src/ui/_app/app.js +19 -8
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/creatorSocket.ts +30 -0
- package/lib/creatorDist/assets/index-BvrB0WCf.js +0 -32991
- package/src/creatorDist/assets/index-BvrB0WCf.js +0 -32991
package/src/creator/src/App.tsx
CHANGED
@@ -12,10 +12,11 @@ import { checkParams, loginWithToken, parseLesson } from "./utils/lib"
|
|
12
12
|
import { Uploader } from "./components/Uploader"
|
13
13
|
import toast from "react-hot-toast"
|
14
14
|
import { ParamsChecker } from "./components/ParamsChecker"
|
15
|
-
import {
|
15
|
+
import { RIGO_FLOAT_GIF } from "./utils/constants"
|
16
16
|
import TurnstileChallenge from "./components/TurnstileChallenge"
|
17
17
|
// import TurnstileChallenge from "./components/TurnstileChallenge"
|
18
18
|
import ResumeCourseModal from "./components/ResumeCourseModal"
|
19
|
+
import { possiblePurposes, PurposeSelector } from "./components/PurposeSelector"
|
19
20
|
|
20
21
|
function App() {
|
21
22
|
const navigate = useNavigate()
|
@@ -72,10 +73,11 @@ function App() {
|
|
72
73
|
}
|
73
74
|
|
74
75
|
const checkDescription = () => {
|
75
|
-
const { description, duration, plan } = checkParams([
|
76
|
+
const { description, duration, plan, purpose } = checkParams([
|
76
77
|
"description",
|
77
78
|
"duration",
|
78
79
|
"plan",
|
80
|
+
"purpose",
|
79
81
|
])
|
80
82
|
if (description) {
|
81
83
|
console.log("description", description)
|
@@ -102,6 +104,13 @@ function App() {
|
|
102
104
|
} else {
|
103
105
|
console.debug("No plan received in params")
|
104
106
|
}
|
107
|
+
|
108
|
+
if (purpose && purpose.length > 0 && possiblePurposes.includes(purpose)) {
|
109
|
+
setFormState({
|
110
|
+
purpose: purpose,
|
111
|
+
currentStep: "hasContentIndex",
|
112
|
+
})
|
113
|
+
}
|
105
114
|
}
|
106
115
|
|
107
116
|
const handleCreateTutorial = async () => {
|
@@ -110,22 +119,17 @@ function App() {
|
|
110
119
|
|
111
120
|
const res = await publicInteractiveCreation(
|
112
121
|
{
|
113
|
-
courseInfo: `${JSON.stringify(formState)}
|
114
|
-
${
|
115
|
-
uploadedFiles.length > 0
|
116
|
-
? `These files where uploaded by the user: \n\n${JSON.stringify(
|
117
|
-
uploadedFiles
|
118
|
-
)}`
|
119
|
-
: ""
|
120
|
-
}`,
|
122
|
+
courseInfo: `${JSON.stringify(formState)} `,
|
121
123
|
prevInteractions: "",
|
122
124
|
},
|
123
|
-
auth.publicToken
|
125
|
+
auth.publicToken,
|
126
|
+
formState.purpose
|
124
127
|
)
|
125
128
|
const lessons = res.parsed.listOfSteps.map((lesson: any) => {
|
126
129
|
return parseLesson(lesson, [])
|
127
130
|
})
|
128
131
|
|
132
|
+
console.log("RES FROM RIGO", res)
|
129
133
|
push({
|
130
134
|
lessons,
|
131
135
|
courseInfo: {
|
@@ -133,7 +137,13 @@ ${
|
|
133
137
|
title: res.parsed.title,
|
134
138
|
description: res.parsed.description,
|
135
139
|
},
|
136
|
-
|
140
|
+
messages: [
|
141
|
+
{
|
142
|
+
type: "assistant",
|
143
|
+
content: res.parsed.aiMessage,
|
144
|
+
},
|
145
|
+
],
|
146
|
+
// sources: uploadedFiles,
|
137
147
|
})
|
138
148
|
navigate("/creator/syllabus")
|
139
149
|
setFormState({
|
@@ -185,7 +195,7 @@ ${
|
|
185
195
|
onClick={() => {
|
186
196
|
setFormState({
|
187
197
|
duration: 30,
|
188
|
-
currentStep: "
|
198
|
+
currentStep: "purpose",
|
189
199
|
})
|
190
200
|
}}
|
191
201
|
selected={formState.duration === 30}
|
@@ -196,7 +206,7 @@ ${
|
|
196
206
|
onClick={() => {
|
197
207
|
setFormState({
|
198
208
|
duration: 60,
|
199
|
-
currentStep: "
|
209
|
+
currentStep: "purpose",
|
200
210
|
})
|
201
211
|
}}
|
202
212
|
selected={formState.duration === 60}
|
@@ -207,7 +217,7 @@ ${
|
|
207
217
|
onClick={() => {
|
208
218
|
setFormState({
|
209
219
|
duration: 120,
|
210
|
-
currentStep: "
|
220
|
+
currentStep: "purpose",
|
211
221
|
})
|
212
222
|
}}
|
213
223
|
selected={formState.duration === 120}
|
@@ -215,6 +225,53 @@ ${
|
|
215
225
|
</div>
|
216
226
|
),
|
217
227
|
},
|
228
|
+
{
|
229
|
+
title: "How would you like to use learnpack?",
|
230
|
+
slug: "purpose",
|
231
|
+
isCompleted: formState?.purpose?.length > 0,
|
232
|
+
required: true,
|
233
|
+
content: (
|
234
|
+
<PurposeSelector
|
235
|
+
onFinish={(purpose) => {
|
236
|
+
setFormState({
|
237
|
+
purpose: purpose,
|
238
|
+
currentStep: "verifyHuman",
|
239
|
+
})
|
240
|
+
}}
|
241
|
+
/>
|
242
|
+
),
|
243
|
+
},
|
244
|
+
{
|
245
|
+
title: "Please verify you are a human",
|
246
|
+
slug: "verifyHuman",
|
247
|
+
isCompleted: false,
|
248
|
+
content: (
|
249
|
+
<TurnstileChallenge
|
250
|
+
// siteKey={"0x4AAAAAABeKMBYYinMU4Ib0"}
|
251
|
+
siteKey={"0x4AAAAAABeZ9tjEevGBsJFU"} // This is the learnpack one
|
252
|
+
onSuccess={async (token) => {
|
253
|
+
const { human, message, token: jwtToken } = await isHuman(token)
|
254
|
+
if (human) {
|
255
|
+
toast.success("You are a human! 👌🏻")
|
256
|
+
|
257
|
+
console.log("JWT TOKEN received", jwtToken)
|
258
|
+
setAuth({
|
259
|
+
...auth,
|
260
|
+
publicToken: jwtToken,
|
261
|
+
})
|
262
|
+
setFormState({
|
263
|
+
currentStep: "hasContentIndex",
|
264
|
+
})
|
265
|
+
} else {
|
266
|
+
toast.error(message)
|
267
|
+
setFormState({
|
268
|
+
currentStep: "purpose",
|
269
|
+
})
|
270
|
+
}
|
271
|
+
}}
|
272
|
+
/>
|
273
|
+
),
|
274
|
+
},
|
218
275
|
{
|
219
276
|
title: "Any materials to get this course started?",
|
220
277
|
slug: "hasContentIndex",
|
@@ -227,16 +284,15 @@ ${
|
|
227
284
|
onClick={() => {
|
228
285
|
setFormState({
|
229
286
|
hasContentIndex: false,
|
230
|
-
currentStep: "verifyHuman",
|
231
287
|
variables: [
|
232
288
|
...formState.variables.filter(
|
233
289
|
(v) => v !== "contentIndex"
|
234
290
|
),
|
235
291
|
],
|
236
|
-
|
292
|
+
isCompleted: true,
|
237
293
|
})
|
238
294
|
}}
|
239
|
-
selected={false}
|
295
|
+
selected={formState.hasContentIndex === false}
|
240
296
|
/>
|
241
297
|
<SelectableCard
|
242
298
|
title="✅ Yes"
|
@@ -247,7 +303,7 @@ ${
|
|
247
303
|
variables: [...formState.variables, "contentIndex"],
|
248
304
|
})
|
249
305
|
}}
|
250
|
-
selected={
|
306
|
+
selected={formState.hasContentIndex === true}
|
251
307
|
/>
|
252
308
|
</div>
|
253
309
|
</>
|
@@ -266,41 +322,12 @@ ${
|
|
266
322
|
onFinish={(text) => {
|
267
323
|
setFormState({
|
268
324
|
contentIndex: text,
|
269
|
-
|
270
|
-
// isCompleted: true,
|
325
|
+
isCompleted: true,
|
271
326
|
})
|
272
327
|
}}
|
273
328
|
/>
|
274
329
|
),
|
275
330
|
},
|
276
|
-
{
|
277
|
-
title: "Please verify you are a human",
|
278
|
-
slug: "verifyHuman",
|
279
|
-
isCompleted: false,
|
280
|
-
content: (
|
281
|
-
<TurnstileChallenge
|
282
|
-
// siteKey={"0x4AAAAAABeKMBYYinMU4Ib0"}
|
283
|
-
siteKey={"0x4AAAAAABeZ9tjEevGBsJFU"}
|
284
|
-
onSuccess={async (token) => {
|
285
|
-
const { human, message, token: jwtToken } = await isHuman(token)
|
286
|
-
if (human) {
|
287
|
-
toast.success("You are a human! 👌🏻")
|
288
|
-
|
289
|
-
setAuth({
|
290
|
-
...auth,
|
291
|
-
publicToken: jwtToken,
|
292
|
-
})
|
293
|
-
setFormState({ isCompleted: true })
|
294
|
-
} else {
|
295
|
-
toast.error(message)
|
296
|
-
setFormState({
|
297
|
-
currentStep: "hasContentIndex",
|
298
|
-
})
|
299
|
-
}
|
300
|
-
}}
|
301
|
-
/>
|
302
|
-
),
|
303
|
-
},
|
304
331
|
]
|
305
332
|
|
306
333
|
return steps.filter(
|
@@ -316,7 +343,7 @@ ${
|
|
316
343
|
{formState.isCompleted && history.length === 0 ? (
|
317
344
|
<Loader
|
318
345
|
text="Learnpack is setting up your tutorial. It may take a moment..."
|
319
|
-
icon={<img src={
|
346
|
+
icon={<img src={RIGO_FLOAT_GIF} alt="rigo" className="w-20 h-20" />}
|
320
347
|
/>
|
321
348
|
) : (
|
322
349
|
<>
|
@@ -1,8 +1,11 @@
|
|
1
|
-
import React, { useRef, useState } from "react"
|
1
|
+
import React, { useEffect, useRef, useState } from "react"
|
2
2
|
import { SVGS } from "../assets/svgs"
|
3
3
|
import { ContentCard } from "./ContentCard"
|
4
4
|
import useStore from "../utils/store"
|
5
5
|
import toast from "react-hot-toast"
|
6
|
+
import CreatorSocket from "../utils/socket"
|
7
|
+
|
8
|
+
const socketClient = new CreatorSocket("")
|
6
9
|
|
7
10
|
const allowedTypes = [
|
8
11
|
"application/pdf",
|
@@ -14,14 +17,100 @@ const allowedTypes = [
|
|
14
17
|
export interface ParsedFile {
|
15
18
|
name: string
|
16
19
|
text: string
|
20
|
+
status: "PROCESSING" | "SUCCESS" | "ERROR"
|
21
|
+
notificationId: string
|
17
22
|
}
|
18
23
|
|
19
24
|
interface FileUploaderProps {
|
20
25
|
styledAs?: "button" | "card"
|
26
|
+
onFinish?: (files: ParsedFile[]) => void
|
27
|
+
}
|
28
|
+
|
29
|
+
const UploadedFileCard = ({ idx, file }: { idx: number; file: ParsedFile }) => {
|
30
|
+
const setUploadedFiles = useStore((state) => state.setUploadedFiles)
|
31
|
+
const uploadedFiles = useStore((state) => state.uploadedFiles)
|
32
|
+
|
33
|
+
const handleUpdate = (data: any) => {
|
34
|
+
if (data.text) {
|
35
|
+
setUploadedFiles(
|
36
|
+
uploadedFiles.map((f, i) =>
|
37
|
+
i === idx ? { ...f, status: "SUCCESS", text: data.text } : f
|
38
|
+
)
|
39
|
+
)
|
40
|
+
} else {
|
41
|
+
setUploadedFiles(
|
42
|
+
uploadedFiles.map((f, i) => (i === idx ? { ...f, status: "ERROR" } : f))
|
43
|
+
)
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
useEffect(() => {
|
48
|
+
if (file.status === "SUCCESS") return
|
49
|
+
|
50
|
+
console.log("CONNECTING TO SOCKET", file.notificationId)
|
51
|
+
socketClient.connect()
|
52
|
+
socketClient.on(file.notificationId, handleUpdate)
|
53
|
+
|
54
|
+
socketClient.emit("registerNotification", {
|
55
|
+
notificationId: file.notificationId,
|
56
|
+
})
|
57
|
+
|
58
|
+
return () => {
|
59
|
+
socketClient.off(file.notificationId, handleUpdate)
|
60
|
+
socketClient.disconnect()
|
61
|
+
}
|
62
|
+
}, [])
|
63
|
+
|
64
|
+
return (
|
65
|
+
<div
|
66
|
+
className={
|
67
|
+
"p-3 rounded-md shadow-sm text-sm text-gray-800 text-left flex items-center gap-2" +
|
68
|
+
(file.status === "PROCESSING"
|
69
|
+
? " bg-gray-100"
|
70
|
+
: file.status === "ERROR"
|
71
|
+
? " bg-red-100"
|
72
|
+
: " bg-white")
|
73
|
+
}
|
74
|
+
title={file.name}
|
75
|
+
>
|
76
|
+
{file.status === "PROCESSING" && (
|
77
|
+
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
78
|
+
)}
|
79
|
+
{file.status === "ERROR" && (
|
80
|
+
<span className="text-red-500 font-bold">Error!</span>
|
81
|
+
)}
|
82
|
+
|
83
|
+
<strong className="truncate">{file.name.slice(0, 20)}...</strong>
|
84
|
+
|
85
|
+
<button
|
86
|
+
className={
|
87
|
+
"ml-auto cursor-pointer transition-colors " +
|
88
|
+
(file.status === "PROCESSING"
|
89
|
+
? "text-gray-400 cursor-not-allowed"
|
90
|
+
: "text-gray-600 hover:text-red-500")
|
91
|
+
}
|
92
|
+
onClick={
|
93
|
+
() => setUploadedFiles(uploadedFiles.filter((_, i) => i !== idx))
|
94
|
+
// Set the file as error
|
95
|
+
// setUploadedFiles(
|
96
|
+
// uploadedFiles.map((f, i) =>
|
97
|
+
// i === idx ? { ...f, status: "ERROR" } : f
|
98
|
+
// )
|
99
|
+
// )
|
100
|
+
}
|
101
|
+
>
|
102
|
+
{SVGS.trash}
|
103
|
+
</button>
|
104
|
+
</div>
|
105
|
+
)
|
21
106
|
}
|
22
107
|
|
23
|
-
const FileUploader: React.FC<FileUploaderProps> = ({
|
24
|
-
|
108
|
+
const FileUploader: React.FC<FileUploaderProps> = ({
|
109
|
+
styledAs = "button",
|
110
|
+
onFinish,
|
111
|
+
}) => {
|
112
|
+
// const rigoToken = useStore((state) => state.auth.rigoToken)
|
113
|
+
const publicToken = useStore((state) => state.auth.publicToken)
|
25
114
|
const inputRef = useRef<HTMLInputElement>(null)
|
26
115
|
const uploadedFiles = useStore((state) => state.uploadedFiles)
|
27
116
|
const setUploadedFiles = useStore((state) => state.setUploadedFiles)
|
@@ -33,7 +122,7 @@ const FileUploader: React.FC<FileUploaderProps> = ({ styledAs = "button" }) => {
|
|
33
122
|
|
34
123
|
if (type === "text/plain" || type === "text/markdown") {
|
35
124
|
const text = await file.text()
|
36
|
-
return { name, text }
|
125
|
+
return { name, text, status: "SUCCESS", notificationId: "" }
|
37
126
|
}
|
38
127
|
|
39
128
|
const formData = new FormData()
|
@@ -41,24 +130,28 @@ const FileUploader: React.FC<FileUploaderProps> = ({ styledAs = "button" }) => {
|
|
41
130
|
|
42
131
|
const loadingToast = toast.loading(`Processing ${file.name}...`)
|
43
132
|
try {
|
44
|
-
const res = await fetch("
|
133
|
+
const res = await fetch("/read-document", {
|
45
134
|
method: "POST",
|
46
135
|
headers: {
|
47
|
-
"x-
|
136
|
+
"x-public-token": publicToken,
|
48
137
|
},
|
49
138
|
body: formData,
|
50
139
|
})
|
51
140
|
|
52
141
|
if (!res.ok) throw new Error(`Failed to read ${file.name}`)
|
53
142
|
const data = await res.json()
|
54
|
-
|
55
|
-
|
56
|
-
|
143
|
+
toast.success(`Processing ${file.name}`, { id: loadingToast })
|
144
|
+
return {
|
145
|
+
name,
|
146
|
+
text: "",
|
147
|
+
status: data.status,
|
148
|
+
notificationId: data.notificationId,
|
149
|
+
}
|
57
150
|
} catch (err: any) {
|
58
151
|
toast.error(`❌ ${file.name} failed: ${err.message}`, {
|
59
152
|
id: loadingToast,
|
60
153
|
})
|
61
|
-
return { name, text: "" }
|
154
|
+
return { name, text: "", status: "ERROR", notificationId: "" }
|
62
155
|
}
|
63
156
|
}
|
64
157
|
|
@@ -73,7 +166,9 @@ const FileUploader: React.FC<FileUploaderProps> = ({ styledAs = "button" }) => {
|
|
73
166
|
|
74
167
|
setIsLoading(true)
|
75
168
|
const parsed = await Promise.all(validFiles.map(extractTextFromFile))
|
76
|
-
|
169
|
+
const newFiles = [...uploadedFiles, ...parsed]
|
170
|
+
|
171
|
+
setUploadedFiles(newFiles.filter((file) => file.status !== "ERROR"))
|
77
172
|
setIsLoading(false)
|
78
173
|
}
|
79
174
|
|
@@ -95,21 +190,7 @@ const FileUploader: React.FC<FileUploaderProps> = ({ styledAs = "button" }) => {
|
|
95
190
|
{uploadedFiles.length > 0 && styledAs === "card" && (
|
96
191
|
<div className="w-full flex flex-row gap-2 flex-wrap justify-center items-center">
|
97
192
|
{uploadedFiles.map((file, idx) => (
|
98
|
-
<
|
99
|
-
key={idx}
|
100
|
-
className="p-3 rounded-md bg-white shadow-sm text-sm text-gray-800 text-left"
|
101
|
-
title={file.name}
|
102
|
-
>
|
103
|
-
<strong>{file.name.slice(0, 20)}...</strong>
|
104
|
-
<button
|
105
|
-
className="text-gray-600 mt-1 float-right cursor-pointer"
|
106
|
-
onClick={() =>
|
107
|
-
setUploadedFiles(uploadedFiles.filter((_, i) => i !== idx))
|
108
|
-
}
|
109
|
-
>
|
110
|
-
{SVGS.trash}
|
111
|
-
</button>
|
112
|
-
</div>
|
193
|
+
<UploadedFileCard key={idx} idx={idx} file={file} />
|
113
194
|
))}
|
114
195
|
</div>
|
115
196
|
)}
|
@@ -131,24 +212,35 @@ const FileUploader: React.FC<FileUploaderProps> = ({ styledAs = "button" }) => {
|
|
131
212
|
)}
|
132
213
|
|
133
214
|
{styledAs === "card" && (
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
e
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
215
|
+
<>
|
216
|
+
<ContentCard
|
217
|
+
description={
|
218
|
+
isDragging
|
219
|
+
? "Drop it here"
|
220
|
+
: isLoading
|
221
|
+
? "Processing..."
|
222
|
+
: "Upload a PDF or DOCX file or drag it here"
|
223
|
+
}
|
224
|
+
icon={isDragging ? SVGS.clip : SVGS.pdf}
|
225
|
+
onClick={() => inputRef.current?.click()}
|
226
|
+
onDragOver={(e) => {
|
227
|
+
e.preventDefault()
|
228
|
+
setIsDragging(true)
|
229
|
+
}}
|
230
|
+
onDragLeave={() => setIsDragging(false)}
|
231
|
+
onDrop={handleDrop}
|
232
|
+
className={isDragging ? "border-blue-600 bg-blue-50" : ""}
|
233
|
+
/>
|
234
|
+
<button
|
235
|
+
disabled={uploadedFiles.some((file) => file.status !== "SUCCESS")}
|
236
|
+
className="bg-blue-500 text-white px-4 py-2 rounded-md cursor-pointer disabled:opacity-50"
|
237
|
+
onClick={() => {
|
238
|
+
onFinish?.(uploadedFiles)
|
239
|
+
}}
|
240
|
+
>
|
241
|
+
🚀 Finish
|
242
|
+
</button>
|
243
|
+
</>
|
152
244
|
)}
|
153
245
|
|
154
246
|
<input
|
@@ -0,0 +1,70 @@
|
|
1
|
+
import React from "react"
|
2
|
+
import SelectableCard from "./SelectableCard"
|
3
|
+
import useStore from "../utils/store"
|
4
|
+
|
5
|
+
export const possiblePurposes = [
|
6
|
+
"learnpack-lesson-writer",
|
7
|
+
"homework-and-exam-preparation-aid",
|
8
|
+
"skill-building-facilitator",
|
9
|
+
"certification-preparation-specialist",
|
10
|
+
]
|
11
|
+
|
12
|
+
type PurposeSlug =
|
13
|
+
| "learnpack-lesson-writer"
|
14
|
+
| "homework-and-exam-preparation-aid"
|
15
|
+
| "skill-building-facilitator"
|
16
|
+
| "certification-preparation-specialist"
|
17
|
+
|
18
|
+
const PURPOSES: { slug: PurposeSlug; label: string; description: string }[] = [
|
19
|
+
{
|
20
|
+
slug: "learnpack-lesson-writer",
|
21
|
+
label: "Understand a new topic",
|
22
|
+
description:
|
23
|
+
"Learn about a new concept (e.g., ISO 27001 or exponential decay).",
|
24
|
+
},
|
25
|
+
{
|
26
|
+
slug: "homework-and-exam-preparation-aid",
|
27
|
+
label: "Practice for an exam or homework",
|
28
|
+
description: "Solve math problems or certification questions.",
|
29
|
+
},
|
30
|
+
{
|
31
|
+
slug: "skill-building-facilitator",
|
32
|
+
label: "Build real-world skills",
|
33
|
+
description:
|
34
|
+
"Apply concepts to projects, data science, or security audits.",
|
35
|
+
},
|
36
|
+
{
|
37
|
+
slug: "certification-preparation-specialist",
|
38
|
+
label: "Prepare for a certification",
|
39
|
+
description: "Get ready for exams like ISO 27001 Lead Auditor.",
|
40
|
+
},
|
41
|
+
]
|
42
|
+
|
43
|
+
interface PurposeSelectorProps {
|
44
|
+
onFinish: (purpose: PurposeSlug) => void
|
45
|
+
// Optionally allow passing an initial purpose, for testability or server-side rendering
|
46
|
+
initialPurpose?: PurposeSlug
|
47
|
+
}
|
48
|
+
|
49
|
+
export const PurposeSelector: React.FC<PurposeSelectorProps> = ({
|
50
|
+
onFinish,
|
51
|
+
}) => {
|
52
|
+
const formState = useStore((state) => state.formState)
|
53
|
+
|
54
|
+
return (
|
55
|
+
<div className="w-full">
|
56
|
+
<ul className="flex flex-row gap-4 flex-wrap">
|
57
|
+
{PURPOSES.map((p) => (
|
58
|
+
<SelectableCard
|
59
|
+
key={p.slug}
|
60
|
+
title={p.label}
|
61
|
+
selected={formState.purpose === p.slug}
|
62
|
+
subtitle={p.description}
|
63
|
+
onClick={() => onFinish(p.slug)}
|
64
|
+
className="w-full"
|
65
|
+
/>
|
66
|
+
))}
|
67
|
+
</ul>
|
68
|
+
</div>
|
69
|
+
)
|
70
|
+
}
|
@@ -5,6 +5,7 @@ interface SelectableCardProps {
|
|
5
5
|
subtitle?: string
|
6
6
|
selected?: boolean
|
7
7
|
onClick: () => void
|
8
|
+
className?: string
|
8
9
|
}
|
9
10
|
|
10
11
|
const SelectableCard: React.FC<SelectableCardProps> = ({
|
@@ -12,13 +13,14 @@ const SelectableCard: React.FC<SelectableCardProps> = ({
|
|
12
13
|
selected = false,
|
13
14
|
subtitle = "",
|
14
15
|
onClick,
|
16
|
+
className = "",
|
15
17
|
}) => {
|
16
18
|
return (
|
17
19
|
<div
|
18
20
|
className={`cursor-pointer bg-white rounded-lg shadow-md p-6 text-center transition-all
|
19
|
-
hover:shadow-lg border-2 ${
|
20
|
-
|
21
|
-
|
21
|
+
hover:shadow-lg border-2 ${className} ${
|
22
|
+
selected ? "border-blue-500" : "border-transparent"
|
23
|
+
}`}
|
22
24
|
onClick={onClick}
|
23
25
|
>
|
24
26
|
<p className="text-md font-medium">{title}</p>
|
@@ -61,7 +61,18 @@ export const Uploader = ({
|
|
61
61
|
|
62
62
|
{selectedOption === "text" && <TextUploader onFinish={onFinish} />}
|
63
63
|
|
64
|
-
{selectedOption === "files" &&
|
64
|
+
{selectedOption === "files" && (
|
65
|
+
<FileUploader
|
66
|
+
styledAs="card"
|
67
|
+
onFinish={(files) => {
|
68
|
+
const allFilesText = files
|
69
|
+
.filter((f) => f.status === "SUCCESS")
|
70
|
+
.map((f) => `<FILE name="${f.name}">${f.text}</FILE>`)
|
71
|
+
.join("\n")
|
72
|
+
onFinish(allFilesText)
|
73
|
+
}}
|
74
|
+
/>
|
75
|
+
)}
|
65
76
|
|
66
77
|
{selectedOption === "youtube" && (
|
67
78
|
<LinkUploader
|
@@ -24,7 +24,7 @@ import Login from "../Login"
|
|
24
24
|
import { useNavigate } from "react-router"
|
25
25
|
import { ParamsChecker } from "../ParamsChecker"
|
26
26
|
import { slugify } from "../../utils/creatorUtils"
|
27
|
-
import {
|
27
|
+
import { RIGO_FLOAT_GIF } from "../../utils/constants"
|
28
28
|
|
29
29
|
const SyllabusEditor: React.FC = () => {
|
30
30
|
const navigate = useNavigate()
|
@@ -69,7 +69,6 @@ const SyllabusEditor: React.FC = () => {
|
|
69
69
|
useEffect(() => {
|
70
70
|
if (!syllabus) {
|
71
71
|
navigate("/creator", { replace: true })
|
72
|
-
} else {
|
73
72
|
}
|
74
73
|
}, [syllabus, navigate])
|
75
74
|
|
@@ -141,19 +140,14 @@ const SyllabusEditor: React.FC = () => {
|
|
141
140
|
|
142
141
|
const res = await publicInteractiveCreation(
|
143
142
|
{
|
144
|
-
courseInfo:
|
145
|
-
${
|
146
|
-
uploadedFiles.length > 0 &&
|
147
|
-
"The user has uploaded files, take them in consideration while generating the course structure." +
|
148
|
-
JSON.stringify(uploadedFiles)
|
149
|
-
}
|
150
|
-
`,
|
143
|
+
courseInfo: JSON.stringify(syllabus.courseInfo),
|
151
144
|
prevInteractions:
|
152
145
|
messages
|
153
146
|
.map((message) => `${message.type}: ${message.content}`)
|
154
147
|
.join("\n") + `\nUSER: ${prompt}`,
|
155
148
|
},
|
156
|
-
auth.publicToken
|
149
|
+
auth.publicToken,
|
150
|
+
syllabus.courseInfo.purpose
|
157
151
|
)
|
158
152
|
|
159
153
|
const lessons: Lesson[] = res.parsed.listOfSteps.map((step: any) =>
|
@@ -208,7 +202,7 @@ const SyllabusEditor: React.FC = () => {
|
|
208
202
|
// toast.error("You don't have enough credits to generate a course!")
|
209
203
|
// return
|
210
204
|
// }
|
211
|
-
syllabus.sources = uploadedFiles
|
205
|
+
// syllabus.sources = uploadedFiles
|
212
206
|
console.log(syllabus)
|
213
207
|
|
214
208
|
setIsGenerating(true)
|
@@ -229,7 +223,7 @@ const SyllabusEditor: React.FC = () => {
|
|
229
223
|
<>
|
230
224
|
<Loader
|
231
225
|
listeningTo="course-generation"
|
232
|
-
icon={<img src={
|
226
|
+
icon={<img src={RIGO_FLOAT_GIF} alt="rigo" className="w-20 h-20" />}
|
233
227
|
initialBuffer="🚀 Starting course generation..."
|
234
228
|
text="Learnpack is setting up your tutorial.
|
235
229
|
It may take a moment..."
|
@@ -1,6 +1,12 @@
|
|
1
|
-
export const
|
2
|
-
// export const RIGOBOT_HOST = "https://rigobot-test-cca7d841c9d8.herokuapp.com"
|
3
|
-
export const BREATHECODE_HOST = "https://breathecode.herokuapp.com"
|
1
|
+
export const DEV_MODE = false
|
4
2
|
|
5
|
-
export const
|
3
|
+
export const RIGOBOT_HOST = DEV_MODE
|
4
|
+
? "https://rigobot-test-cca7d841c9d8.herokuapp.com"
|
5
|
+
: "https://rigobot.herokuapp.com"
|
6
|
+
|
7
|
+
export const BREATHECODE_HOST = DEV_MODE
|
8
|
+
? "https://breathecode-test.herokuapp.com"
|
9
|
+
: "https://breathecode.herokuapp.com"
|
10
|
+
|
11
|
+
export const RIGO_FLOAT_GIF =
|
6
12
|
"https://raw.githubusercontent.com/learnpack/ide/20ed3f4c3ead9b33d5d6acb20154dcd93a0ec4af/public/rigo-float.gif"
|