@learnpack/learnpack 5.0.54 → 5.0.58

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 (57) hide show
  1. package/README.md +30 -12
  2. package/lib/commands/serve.d.ts +7 -0
  3. package/lib/commands/serve.js +277 -0
  4. package/lib/managers/server/routes.js +2 -0
  5. package/lib/utils/api.js +1 -1
  6. package/lib/utils/cloudStorage.d.ts +8 -0
  7. package/lib/utils/cloudStorage.js +17 -0
  8. package/oclif.manifest.json +1 -1
  9. package/package.json +4 -1
  10. package/src/commands/serve.ts +371 -0
  11. package/src/creator/README.md +54 -0
  12. package/src/creator/eslint.config.js +28 -0
  13. package/src/creator/index.html +13 -0
  14. package/src/creator/package-lock.json +4659 -0
  15. package/src/creator/package.json +41 -0
  16. package/src/creator/public/vite.svg +1 -0
  17. package/src/creator/src/App.css +42 -0
  18. package/src/creator/src/App.tsx +221 -0
  19. package/src/creator/src/assets/react.svg +1 -0
  20. package/src/creator/src/assets/svgs.tsx +88 -0
  21. package/src/creator/src/components/Loader.tsx +28 -0
  22. package/src/creator/src/components/Login.tsx +263 -0
  23. package/src/creator/src/components/SelectableCard.tsx +30 -0
  24. package/src/creator/src/components/StepWizard.tsx +77 -0
  25. package/src/creator/src/components/SyllabusEditor.tsx +431 -0
  26. package/src/creator/src/index.css +68 -0
  27. package/src/creator/src/main.tsx +19 -0
  28. package/src/creator/src/utils/configTypes.ts +122 -0
  29. package/src/creator/src/utils/constants.ts +2 -0
  30. package/src/creator/src/utils/lib.ts +36 -0
  31. package/src/creator/src/utils/rigo.ts +391 -0
  32. package/src/creator/src/utils/store.ts +78 -0
  33. package/src/creator/src/vite-env.d.ts +1 -0
  34. package/src/creator/tsconfig.app.json +26 -0
  35. package/src/creator/tsconfig.json +7 -0
  36. package/src/creator/tsconfig.node.json +24 -0
  37. package/src/creator/vite.config.ts +13 -0
  38. package/src/creatorDist/assets/index-D92OoEoU.js +23719 -0
  39. package/src/creatorDist/assets/index-tt9JBVY0.css +987 -0
  40. package/src/creatorDist/index.html +14 -0
  41. package/src/creatorDist/vite.svg +1 -0
  42. package/src/managers/server/routes.ts +3 -0
  43. package/src/ui/_app/app.css +1 -0
  44. package/src/ui/_app/app.js +3025 -0
  45. package/src/ui/_app/favicon.ico +0 -0
  46. package/src/ui/_app/index.html +109 -0
  47. package/src/ui/_app/index.html.backup +91 -0
  48. package/src/ui/_app/learnpack.svg +7 -0
  49. package/src/ui/_app/logo-192.png +0 -0
  50. package/src/ui/_app/logo-512.png +0 -0
  51. package/src/ui/_app/logo.png +0 -0
  52. package/src/ui/_app/manifest.webmanifest +21 -0
  53. package/src/ui/_app/sw.js +30 -0
  54. package/src/ui/app.tar.gz +0 -0
  55. package/src/utils/api.ts +1 -1
  56. package/src/utils/cloudStorage.ts +24 -0
  57. package/src/utils/convertCreds.js +30 -0
@@ -0,0 +1,431 @@
1
+ import React, { useRef, useState } from "react"
2
+ import { SVGS } from "../assets/svgs"
3
+ import { useShallow } from "zustand/react/shallow"
4
+ import useStore, { FormState } from "../utils/store"
5
+ import {
6
+ checkReadability,
7
+ interactiveCreation,
8
+ makeReadmeReadable,
9
+ readmeCreator,
10
+ } from "../utils/rigo"
11
+ import { CREATOR_API_URL, parseLesson, uploadFileToBucket } from "../utils/lib"
12
+
13
+ import Loader from "./Loader"
14
+
15
+ const slugify = (text: string) => {
16
+ return text
17
+ .toLowerCase()
18
+ .replace(/ /g, "-")
19
+ .replace(/[^\w.-]+/g, "")
20
+ }
21
+
22
+ const createLearnJson = (courseInfo: FormState) => {
23
+ const learnJson = {
24
+ slug: slugify(courseInfo.title || randomUUID()),
25
+ title: {
26
+ us: courseInfo.title,
27
+ },
28
+ description: {
29
+ us: courseInfo.description,
30
+ },
31
+ grading: "isolated",
32
+ }
33
+ return learnJson
34
+ }
35
+
36
+ const PARAMS = {
37
+ expected_grade_level: "6",
38
+ max_fkgl: 8,
39
+ max_words: 200,
40
+ max_rewrite_attempts: 3,
41
+ max_title_length: 50,
42
+ }
43
+ async function processExercise(
44
+ rigoToken: string,
45
+ steps: Lesson[],
46
+ packageContext: string,
47
+ exercise: Lesson,
48
+ exercisesDir: string
49
+ ): Promise<string> {
50
+ // const tid = toast.loading("Generating lesson...")
51
+ const readme = await readmeCreator(rigoToken, {
52
+ title: `${exercise.id} - ${exercise.title}`,
53
+ output_lang: "en",
54
+ list_of_exercises: JSON.stringify(steps),
55
+ tutorial_description: packageContext,
56
+ lesson_description: exercise.description,
57
+ kind: exercise.type.toLowerCase(),
58
+ })
59
+
60
+ console.log(exercise.id, "ID")
61
+
62
+ const duration = exercise.duration
63
+ let attempts = 0
64
+ let readability = checkReadability(readme.parsed.content, 200, duration || 1)
65
+
66
+ while (
67
+ readability.fkglResult.fkgl > PARAMS.max_fkgl &&
68
+ attempts < PARAMS.max_rewrite_attempts
69
+ ) {
70
+ // Console.warning(
71
+ // `The lesson ${exTitle} has as readability score of ${
72
+ // readability.fkglResult.fkgl
73
+ // } . It exceeds the maximum of words per minute. Rewriting it... (Attempt ${
74
+ // attempts + 1
75
+ // })`
76
+ // )
77
+
78
+ // eslint-disable-next-line
79
+ const reducedReadme = await makeReadmeReadable(rigoToken, {
80
+ lesson: readability.body,
81
+ number_of_words: readability.minutes.toString(),
82
+ expected_number_words: PARAMS.max_words.toString(),
83
+ fkgl_results: JSON.stringify(readability.fkglResult),
84
+ expected_grade_level: PARAMS.expected_grade_level,
85
+ })
86
+
87
+ // console.log("REDUCED README START", reducedReadme, "REDUCED README END")
88
+
89
+ if (!reducedReadme) break
90
+
91
+ readability = checkReadability(
92
+ reducedReadme.parsed.content,
93
+ PARAMS.max_words,
94
+ duration || 1
95
+ )
96
+
97
+ attempts++
98
+ }
99
+
100
+ // Console.success(
101
+ // `After ${attempts} attempts, the lesson ${exTitle} has a readability score of ${
102
+ // readability.fkglResult.fkgl
103
+ // } using FKGL. And it has ${readability.minutes.toFixed(
104
+ // 2
105
+ // )} minutes of reading time.`
106
+ // )
107
+
108
+ const readmeFilename = "README.md"
109
+ await uploadFileToBucket(
110
+ readability.newMarkdown,
111
+ `${exercisesDir}/${slugify(
112
+ exercise.id + "-" + exercise.title
113
+ )}/${readmeFilename}`
114
+ )
115
+
116
+ if (exercise.type.toLowerCase() === "code") {
117
+ // const codeFile = await createCodeFile(rigoToken, {
118
+ // readme: readability.newMarkdown,
119
+ // tutorial_info: packageContext,
120
+ // })
121
+ // fs.writeFileSync(
122
+ // path.join(
123
+ // exerciseDir,
124
+ // `app.${codeFile.parsed.extension.replace(".", "")}`
125
+ // ),
126
+ // codeFile.parsed.content
127
+ // )
128
+ }
129
+
130
+ // toast.success("Lesson generated successfully", { id: tid })
131
+ return readability.newMarkdown
132
+ }
133
+
134
+ const randomUUID = () => {
135
+ return Math.random().toString(36).substring(2, 15)
136
+ }
137
+
138
+ // types.ts
139
+ export interface Lesson {
140
+ id: string
141
+ title: string
142
+ type: "READ" | "CODE" | "QUIZ"
143
+ description: string
144
+ duration?: number
145
+ }
146
+
147
+ type TMessage = {
148
+ type: "user" | "assistant"
149
+ content: string
150
+ }
151
+
152
+ const SyllabusEditor: React.FC = () => {
153
+ const inputRef = useRef<HTMLInputElement>(null)
154
+ // const navigate = useNavigate()
155
+ const [messages, setMessages] = useState<TMessage[]>([])
156
+ const [isGenerating, setIsGenerating] = useState(false)
157
+ const { syllabus, setSyllabus, auth } = useStore(
158
+ useShallow((state) => ({
159
+ syllabus: state.syllabus,
160
+ setSyllabus: state.setSyllabus,
161
+ auth: state.auth,
162
+ }))
163
+ )
164
+
165
+ const handleRemove = (id: string) => {
166
+ setSyllabus({
167
+ ...syllabus,
168
+ lessons: syllabus.lessons.filter((lesson) => lesson.id !== id),
169
+ })
170
+ }
171
+
172
+ const handleChange = (id: string, newTitle: string) => {
173
+ setSyllabus({
174
+ ...syllabus,
175
+ lessons: syllabus.lessons.map((lesson) =>
176
+ lesson.id === id ? { ...lesson, title: newTitle } : lesson
177
+ ),
178
+ })
179
+ }
180
+
181
+ const addLessonAfter = (index: number, id: string) => {
182
+ const newLesson: Lesson = {
183
+ id: (parseFloat(id) + 0.1).toString(),
184
+ title: "Hello World",
185
+ type: "READ",
186
+ duration: 2,
187
+ description: "Hello World",
188
+ }
189
+ const updated = [...syllabus.lessons]
190
+ updated.splice(index + 1, 0, newLesson)
191
+
192
+ setSyllabus({
193
+ lessons: updated,
194
+ })
195
+ }
196
+
197
+ const sendPrompt = async (prompt: string) => {
198
+ setMessages([...messages, { type: "user", content: prompt }])
199
+ const res = await interactiveCreation(auth.rigoToken, {
200
+ courseInfo: JSON.stringify(syllabus),
201
+ prevInteractions:
202
+ messages
203
+ .map((message) => `${message.type}: ${message.content}`)
204
+ .join("\n") + `\nUSER: ${prompt}`,
205
+ })
206
+ console.log(res, "RES")
207
+ const lessons = res.parsed.listOfSteps.map((step: any) => parseLesson(step))
208
+ setSyllabus({
209
+ ...syllabus,
210
+ lessons: lessons,
211
+ courseInfo: {
212
+ ...syllabus.courseInfo,
213
+ title: res.parsed.title || syllabus.courseInfo.title,
214
+ },
215
+ })
216
+ setMessages((prev) => [
217
+ ...prev,
218
+ { type: "assistant", content: res.parsed.aiMessage },
219
+ ])
220
+ }
221
+
222
+ const handleSubmit = async () => {
223
+ setIsGenerating(true)
224
+ const lessonsPromises = syllabus.lessons.map((lesson) =>
225
+ processExercise(
226
+ auth.rigoToken,
227
+ syllabus.lessons,
228
+ JSON.stringify(syllabus.courseInfo),
229
+ lesson,
230
+ "courses/" +
231
+ slugify(syllabus.courseInfo.title || randomUUID()) +
232
+ "/exercises"
233
+ )
234
+ )
235
+ await Promise.all(lessonsPromises)
236
+
237
+ const learnJson = createLearnJson(syllabus.courseInfo)
238
+ await uploadFileToBucket(
239
+ JSON.stringify(learnJson),
240
+ "courses/" +
241
+ slugify(syllabus.courseInfo.title || randomUUID()) +
242
+ "/learn.json"
243
+ )
244
+ setIsGenerating(false)
245
+
246
+ window.location.href = `${CREATOR_API_URL}?slug=${slugify(
247
+ syllabus.courseInfo.title || "exercises"
248
+ )}`
249
+ }
250
+
251
+ return isGenerating ? (
252
+ <Loader
253
+ icon={SVGS.aiStars}
254
+ text="Learnpack is setting up your tutorial.
255
+ It may take a moment..."
256
+ />
257
+ ) : (
258
+ <div className="flex w-full bg-white rounded-md shadow-md overflow-hidden h-screen ">
259
+ {/* Sidebar */}
260
+ <div className="w-1/3 p-6 text-sm text-gray-700 border-r bg-learnpack-blue h-screen overflow-y-auto scrollbar-hide">
261
+ <div className="p-4 border rounded-md bg-white shadow-sm">
262
+ <p>We generated this syllabus based on your answers.</p>
263
+ <p className="mt-2">If you're satisfied, type "OK" in the chat.</p>
264
+ <p className="mt-2">If not, use the chat to give more context.</p>
265
+ <p className="mt-2 text-gray-500">We recommend:</p>
266
+ <ul className="mt-2 space-y-1 text-blue-500">
267
+ <li>
268
+ <a href="#">Make it have more readings</a>
269
+ </li>
270
+ <li>
271
+ <a href="#">Make it shorter</a>
272
+ </li>
273
+ </ul>
274
+ </div>
275
+ <div className="mt-10 space-y-2">
276
+ {messages.map((message, index) => (
277
+ <Message
278
+ key={index}
279
+ type={message.type}
280
+ content={message.content}
281
+ />
282
+ ))}
283
+ </div>
284
+ <input
285
+ ref={inputRef}
286
+ className="mt-10 w-full p-2 border rounded-md text-gray-400 bg-white"
287
+ placeholder="How can Learnpack help you?"
288
+ onKeyUp={(e) => {
289
+ if (e.key === "Enter") {
290
+ sendPrompt(inputRef.current?.value || "")
291
+ inputRef.current!.value = ""
292
+ }
293
+ }}
294
+ />
295
+ </div>
296
+
297
+ {/* Editor */}
298
+ <div className="w-2/3 p-8 space-y-6">
299
+ <div>
300
+ <h2 className="text-lg font-semibold">
301
+ I've created a detailed structure for your course.
302
+ </h2>
303
+ <p className="text-sm text-gray-600">
304
+ It includes a mix of reading, coding exercises, and quizzes. Give it
305
+ a look and let me know if it aligns with your expectations or if
306
+ there are any changes you'd like to make.
307
+ </p>
308
+ <h3 className="text-sm text-gray-600 mt-2 font-bold">
309
+ {syllabus.courseInfo.title}
310
+ </h3>
311
+ </div>
312
+
313
+ {/* Lessons */}
314
+ <div className="space-y-3 overflow-y-auto max-h-[60vh] pr-2 scrollbar-hide">
315
+ {syllabus.lessons.map((lesson, index) => (
316
+ <div key={lesson.id}>
317
+ <LessonItem
318
+ key={lesson.id}
319
+ index={lesson.id}
320
+ lesson={lesson}
321
+ onChange={handleChange}
322
+ onRemove={handleRemove}
323
+ />
324
+ <div className="relative h-6">
325
+ <div className="absolute left-1/2 -translate-x-1/2 -top-3">
326
+ <button
327
+ onClick={() => addLessonAfter(index, lesson.id)}
328
+ 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"
329
+ >
330
+ +
331
+ </button>
332
+ </div>
333
+ </div>
334
+ </div>
335
+ ))}
336
+ </div>
337
+
338
+ <div className="flex justify-between mt-6">
339
+ <button className="text-blue-600 hover:underline text-sm">
340
+ Skip
341
+ </button>
342
+ <button
343
+ onClick={handleSubmit}
344
+ className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700"
345
+ >
346
+ Next
347
+ </button>
348
+ </div>
349
+ </div>
350
+ </div>
351
+ )
352
+ }
353
+
354
+ export default SyllabusEditor
355
+
356
+ interface LessonItemProps {
357
+ lesson: Lesson
358
+ index: string
359
+ onChange: (id: string, newTitle: string) => void
360
+ onRemove: (id: string) => void
361
+ }
362
+
363
+ const LessonItem: React.FC<LessonItemProps> = ({
364
+ lesson,
365
+ index,
366
+ onChange,
367
+ onRemove,
368
+ }) => {
369
+ const [isEditing, setIsEditing] = useState(false)
370
+
371
+ return (
372
+ <div className="flex items-center space-x-2 border border-gray-200 rounded-md p-3">
373
+ <span className="index-circle">{index}</span>
374
+ <span className="text-gray-500 text-sm">
375
+ {lesson.type[0] + lesson.type.slice(1).toLowerCase()} ●
376
+ </span>
377
+
378
+ {isEditing ? (
379
+ <input
380
+ value={lesson.title}
381
+ onChange={(e) => onChange(lesson.id, e.target.value)}
382
+ onBlur={() => setIsEditing(false)}
383
+ autoFocus
384
+ className="flex-1 bg-white border border-gray-300 rounded-md px-2 py-1 text-sm"
385
+ />
386
+ ) : (
387
+ <span className="flex-1 text-sm text-gray-800">{lesson.title}</span>
388
+ )}
389
+
390
+ <span className="text-sm text-gray-600 bg-blue-100 px-2 py-1 rounded-full">
391
+ {lesson.duration} min
392
+ </span>
393
+
394
+ <button
395
+ onClick={() => setIsEditing(!isEditing)}
396
+ className="text-gray-500 hover:text-blue-500"
397
+ >
398
+ {SVGS.pen}
399
+ </button>
400
+ <button
401
+ onClick={() => onRemove(lesson.id)}
402
+ className="text-red-500 hover:text-red-700"
403
+ >
404
+ {SVGS.trash}
405
+ </button>
406
+ </div>
407
+ )
408
+ }
409
+
410
+ const Message: React.FC<TMessage> = ({ type, content }) => {
411
+ const isAI = type === "assistant"
412
+
413
+ return (
414
+ <div
415
+ className={`flex items-start space-x-2 p-3 rounded-md border ${
416
+ isAI
417
+ ? "bg-gray-50 border-gray-300 text-gray-800"
418
+ : "bg-blue-50 border-blue-200 text-blue-900"
419
+ }`}
420
+ >
421
+ <span
422
+ className={`text-xs font-bold px-2 py-1 rounded ${
423
+ isAI ? "bg-gray-200 text-gray-800" : "bg-blue-200 text-blue-900"
424
+ }`}
425
+ >
426
+ {isAI ? "AI" : "YOU"}
427
+ </span>
428
+ <p className="text-sm leading-relaxed">{content}</p>
429
+ </div>
430
+ )
431
+ }
@@ -0,0 +1,68 @@
1
+ @import "tailwindcss";
2
+
3
+ :root {
4
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
5
+ line-height: 1.5;
6
+ font-weight: 400;
7
+
8
+ color-scheme: light dark;
9
+ color: #213547;
10
+
11
+ font-synthesis: none;
12
+ text-rendering: optimizeLegibility;
13
+ -webkit-font-smoothing: antialiased;
14
+ -moz-osx-font-smoothing: grayscale;
15
+
16
+ --four-geeks-blue: #0097cf;
17
+ --soft-blue: #f3fafd;
18
+ --gray-text: #6883b4;
19
+ --learnpack-blue: #02a9ea;
20
+ }
21
+
22
+ a {
23
+ font-weight: 500;
24
+ color: var(--learnpack-blue);
25
+ text-decoration: inherit;
26
+ }
27
+ a:hover {
28
+ color: var(--learnpack-blue);
29
+ }
30
+
31
+ body {
32
+ margin: 0;
33
+ min-height: 100vh;
34
+ background-color: #f4f9fe;
35
+ }
36
+
37
+ h1 {
38
+ font-size: 3.2em;
39
+ line-height: 1.1;
40
+ }
41
+
42
+ .bg-learnpack-blue {
43
+ background-color: var(--soft-blue);
44
+ }
45
+
46
+ .bg-gray-blue {
47
+ background-color: #e7f1ff;
48
+ }
49
+
50
+ .index-circle {
51
+ width: 30px;
52
+ height: 30px;
53
+ font-size: 16px;
54
+ font-weight: 600;
55
+ border-radius: 50%;
56
+ color: var(--four-geeks-blue);
57
+ background-color: var(--soft-blue);
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ }
62
+ .text-gray {
63
+ color: var(--gray-text);
64
+ }
65
+
66
+ .scrollbar-hide {
67
+ scrollbar-width: none !important;
68
+ }
@@ -0,0 +1,19 @@
1
+ import { StrictMode } from "react"
2
+ import { createRoot } from "react-dom/client"
3
+ import { BrowserRouter, Route, Routes } from "react-router"
4
+ import "./index.css"
5
+ import App from "./App.tsx"
6
+ import SyllabusEditor from "./components/SyllabusEditor.tsx"
7
+ import { Toaster } from "react-hot-toast"
8
+
9
+ createRoot(document.getElementById("root")!).render(
10
+ <StrictMode>
11
+ <Toaster />
12
+ <BrowserRouter>
13
+ <Routes>
14
+ <Route path="/creator" element={<App />} />
15
+ <Route path="/creator/syllabus" element={<SyllabusEditor />} />
16
+ </Routes>
17
+ </BrowserRouter>
18
+ </StrictMode>
19
+ )
@@ -0,0 +1,122 @@
1
+ export interface IFile {
2
+ path: string
3
+ name: string
4
+ hidden: boolean
5
+ }
6
+
7
+ export interface IExercise {
8
+ position?: number
9
+ files: Array<IFile>
10
+ slug: string
11
+ path: string
12
+ done: boolean
13
+ language?: string | null
14
+ entry?: string | null
15
+ graded?: boolean
16
+ translations?: { [key: string]: string }
17
+ title: string
18
+ }
19
+
20
+ export type TGrading = "isolated" | "incremental" | "no-grading"
21
+
22
+ export type TMode = "preview" | "extension"
23
+
24
+ export type TConfigAction = "test" | "build" | "tutorial" | "reset" | "generate"
25
+
26
+ export type TConfigObjAttributes = "config" | "exercises" | "grading"
27
+
28
+ export type TCompiler =
29
+ | "webpack"
30
+ | "vanillajs"
31
+ | "react"
32
+ | "html"
33
+ | "node"
34
+ | "python"
35
+ | "css"
36
+
37
+ export interface IConfigPath {
38
+ base: string
39
+ }
40
+
41
+ export interface IEditor {
42
+ mode?: TMode
43
+ version?: string
44
+ agent?: TAgent
45
+ hideTerminal?: boolean
46
+ }
47
+
48
+ export interface TEntries {
49
+ python3?: string
50
+ html?: string
51
+ node?: string
52
+ react?: string
53
+ java?: string
54
+ }
55
+
56
+ export interface IConfig {
57
+ // os: string
58
+ // port?: string
59
+ // repository?: string
60
+ description?: string
61
+ slug?: string
62
+ // dirPath: string
63
+ // preview?: string // Picture thumbnail
64
+ // entries: TEntries
65
+ grading: TGrading
66
+ // configPath: string
67
+ translations: Array<string>
68
+ // outputPath?: string
69
+ // editor: IEditor
70
+ language: string
71
+ title: string
72
+ duration: number
73
+ difficulty?: string
74
+ exercisesPath: string
75
+ disableGrading: boolean // TODO: Deprecate
76
+ actions: Array<string> // TODO: Deprecate
77
+ autoPlay: boolean
78
+ projectType?: string
79
+ // TODO: nameExerciseValidation
80
+ contact?: string
81
+ disabledActions?: Array<TConfigAction>
82
+ compiler: TCompiler
83
+ compilers: Array<TCompiler>
84
+ publicPath: string
85
+ publicUrl?: string
86
+ bugsLink?: string
87
+ videoSolutions?: boolean
88
+ skills: Array<string>
89
+ // telemetry?: TTelemetryUrls
90
+ variables?: TVariables
91
+ suggestions: TSuggestions
92
+ warnings: TWarnings
93
+ runHook: (...agrs: Array<any>) => void
94
+ testingFinishedCallback: (arg: any | undefined) => void
95
+ }
96
+ export type TAgent = "os" | "vscode" | null
97
+
98
+ export type TSuggestions = {
99
+ agent: TAgent
100
+ }
101
+
102
+ type TWarnings = {
103
+ agent?: string
104
+ extension?: string
105
+ }
106
+
107
+ type TVariable = {
108
+ type: "command" | "string"
109
+ source: string
110
+ }
111
+ type TVariables = {
112
+ [key: string]: TVariable | string
113
+ }
114
+
115
+ export interface IConfigObj {
116
+ session?: number
117
+ currentExercise?: any
118
+ config?: IConfig
119
+ exercises?: Array<IExercise>
120
+ confPath?: IConfigPath
121
+ address?: string // Maybe
122
+ }
@@ -0,0 +1,2 @@
1
+ export const RIGOBOT_HOST = "https://rigobot.herokuapp.com"
2
+ export const BREATHECODE_HOST = "https://breathecode.herokuapp.com"
@@ -0,0 +1,36 @@
1
+ import axios from "axios"
2
+
3
+ type ParsedLesson = {
4
+ id: string
5
+ title: string
6
+ type: string
7
+ description: string
8
+ duration?: number
9
+ }
10
+
11
+ export function parseLesson(input: string): ParsedLesson | null {
12
+ const pattern = /^([\d.]+)\s*-\s*(.*?)\s*\[(\w+):\s*(.+)\]$/
13
+ const match = input.match(pattern)
14
+
15
+ if (!match) return null
16
+
17
+ const [, index, title, type, description] = match
18
+
19
+ return {
20
+ id: index,
21
+ title: title.trim(),
22
+ type: type.trim().toUpperCase(),
23
+ description: description.trim(),
24
+ duration: 2,
25
+ }
26
+ }
27
+
28
+ export const CREATOR_API_URL = "http://localhost:3000"
29
+
30
+ export const uploadFileToBucket = async (content: string, path: string) => {
31
+ const response = await axios.post(`${CREATOR_API_URL}/upload`, {
32
+ content,
33
+ destination: path,
34
+ })
35
+ return response.data
36
+ }