@learnpack/learnpack 5.0.69 → 5.0.71

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 (30) hide show
  1. package/README.md +13 -13
  2. package/lib/commands/init.js +1 -1
  3. package/lib/commands/serve.js +12 -3
  4. package/lib/creatorDist/assets/{index-C7bLE5wU.js → index-Dm2fdeOs.js} +9076 -8991
  5. package/lib/creatorDist/assets/{index-C_Rp91QE.css → index-k_eF99Sf.css} +103 -45
  6. package/lib/creatorDist/index.html +2 -2
  7. package/lib/creatorDist/rigo-float.gif +0 -0
  8. package/oclif.manifest.json +1 -1
  9. package/package.json +1 -1
  10. package/src/commands/init.ts +1 -1
  11. package/src/commands/serve.ts +13 -5
  12. package/src/creator/public/rigo-float.gif +0 -0
  13. package/src/creator/src/App.tsx +203 -163
  14. package/src/creator/src/assets/svgs.tsx +2 -2
  15. package/src/creator/src/components/LessonItem.tsx +10 -3
  16. package/src/creator/src/components/Login.tsx +14 -18
  17. package/src/creator/src/components/Message.tsx +12 -2
  18. package/src/creator/src/components/Redirector.tsx +12 -0
  19. package/src/creator/src/components/StepWizard.tsx +32 -18
  20. package/src/creator/src/components/syllabus/ContentIndex.tsx +47 -20
  21. package/src/creator/src/components/syllabus/Sidebar.tsx +114 -0
  22. package/src/creator/src/components/syllabus/SyllabusEditor.tsx +20 -125
  23. package/src/creator/src/index.css +2 -6
  24. package/src/creator/src/main.tsx +0 -1
  25. package/src/creator/src/utils/store.ts +14 -3
  26. package/src/creatorDist/assets/{index-C7bLE5wU.js → index-Dm2fdeOs.js} +9076 -8991
  27. package/src/creatorDist/assets/{index-C_Rp91QE.css → index-k_eF99Sf.css} +103 -45
  28. package/src/creatorDist/index.html +2 -2
  29. package/src/creatorDist/rigo-float.gif +0 -0
  30. package/src/utils/creds.json +13 -0
@@ -1,7 +1,38 @@
1
1
  import { useEffect, useState, useRef } from "react"
2
- import useStore from "../../utils/store"
2
+ import useStore, { Syllabus } from "../../utils/store"
3
3
  import { Lesson, LessonItem } from "../LessonItem"
4
4
  import { SVGS } from "../../assets/svgs"
5
+ import { TMessage } from "../Message"
6
+
7
+ const ContentIndexHeader = ({
8
+ messages,
9
+ syllabus,
10
+ }: {
11
+ messages: TMessage[]
12
+ syllabus: Syllabus
13
+ }) => {
14
+ return (
15
+ <div className="mt-2">
16
+ <h2 className="text-lg font-semibold">
17
+ {messages.filter((m) => m.type === "assistant" && m.content.length > 0)
18
+ .length === 0
19
+ ? "I've created a detailed structure for your course."
20
+ : "I've updated the structure based on your feedback."}
21
+ </h2>
22
+ <p className="text-sm text-gray-600">
23
+ {messages.filter((m) => m.type === "assistant" && m.content.length > 0)
24
+ .length === 0
25
+ ? `It includes a mix of reading, coding exercises, and quizzes. Give
26
+ it a look and let me know if it aligns with your expectations or if
27
+ there are any changes you'd like to make.`
28
+ : "Based on your input, here is the new syllabus, updates are highlighted in yellow"}
29
+ </p>
30
+ <h3 className="text-sm text-gray-600 mt-2 font-bold">
31
+ {syllabus.courseInfo.title}
32
+ </h3>
33
+ </div>
34
+ )
35
+ }
5
36
 
6
37
  export const GenerateButton = ({
7
38
  handleSubmit,
@@ -25,19 +56,23 @@ export const GenerateButton = ({
25
56
  export const ContentIndex = ({
26
57
  prevLessons,
27
58
  handleSubmit,
59
+ messages,
28
60
  }: {
29
61
  prevLessons?: Lesson[]
30
62
  handleSubmit: () => void
63
+ messages: TMessage[]
31
64
  }) => {
32
65
  const syllabus = useStore((state) => state.syllabus)
33
66
  const setSyllabus = useStore((state) => state.setSyllabus)
34
67
  const containerRef = useRef<HTMLDivElement>(null)
35
68
  const [showScrollHint, setShowScrollHint] = useState(false)
36
69
 
37
- const handleRemove = (id: string) => {
70
+ const handleRemove = (lesson: Lesson) => {
38
71
  setSyllabus({
39
72
  ...syllabus,
40
- lessons: syllabus.lessons.filter((lesson) => lesson.id !== id),
73
+ lessons: syllabus.lessons.filter(
74
+ (l) => l.id !== lesson.id && l.title !== lesson.title
75
+ ),
41
76
  })
42
77
  }
43
78
 
@@ -52,7 +87,7 @@ export const ContentIndex = ({
52
87
 
53
88
  const addLessonAfter = (index: number, id: string) => {
54
89
  const newLesson: Lesson = {
55
- id: (parseFloat(id) + 0.1).toString(),
90
+ id: (parseFloat(id) + 0.1).toFixed(1),
56
91
  title: "Hello World",
57
92
  type: "READ",
58
93
  duration: 2,
@@ -100,19 +135,19 @@ export const ContentIndex = ({
100
135
  }
101
136
 
102
137
  return (
103
- <div className="relative">
138
+ <div className="relative ">
139
+ <ContentIndexHeader messages={messages} syllabus={syllabus} />
104
140
  <div
105
141
  ref={containerRef}
106
- className=" space-y-3 overflow-y-auto max-h-[70vh] pr-2 scrollbar-hide relative"
142
+ className=" space-y-3 overflow-y-auto max-h-[80vh] pr-2 scrollbar-hide relative pb-5"
107
143
  >
108
144
  {syllabus.lessons.map((lesson, index) => (
109
145
  <div key={lesson.id}>
110
146
  <LessonItem
111
- key={lesson.id}
112
- index={lesson.id}
147
+ key={lesson.id + index + lesson.title}
113
148
  lesson={lesson}
114
149
  onChange={handleChange}
115
- onRemove={handleRemove}
150
+ onRemove={() => handleRemove(lesson)}
116
151
  isNew={Boolean(
117
152
  prevLessons &&
118
153
  prevLessons.length > 0 &&
@@ -141,24 +176,16 @@ export const ContentIndex = ({
141
176
 
142
177
  {showScrollHint && (
143
178
  <div className="pointer-events-none relative">
144
- <div className="absolute bottom-0 left-0 w-full h-20 bg-gradient-to-t from-white to-transparent z-10" />
179
+ <div className="absolute bottom-0 left-0 w-full h-50 bg-gradient-to-t from-white to-transparent z-10" />
145
180
  <div className="absolute bottom-3 left-0 w-full flex justify-center z-20">
146
181
  <button
147
182
  style={{ color: "#0084FF" }}
148
- onClick={() => scrollToBottom("continue")}
149
- className="px-4 py-1 bg-white text-sm rounded shadow hover:bg-blue-50 cursor-pointer pointer-events-auto font-bold flex items-center gap-2"
183
+ onClick={() => scrollToBottom("bottom")}
184
+ className="px-4 py-1 bg-white text-sm rounded hover:bg-blue-50 cursor-pointer pointer-events-auto font-bold flex items-center gap-2"
150
185
  >
151
186
  Continue scrolling
152
187
  {SVGS.downArrow}
153
188
  </button>
154
- <button
155
- style={{ color: "#0084FF" }}
156
- onClick={() => scrollToBottom("bottom")}
157
- className="px-4 py-1 bg-white text-sm rounded shadow hover:bg-blue-50 cursor-pointer pointer-events-auto font-bold flex items-center gap-2"
158
- >
159
- Scroll to bottom
160
- {SVGS.bottom}
161
- </button>
162
189
  </div>
163
190
  </div>
164
191
  )}
@@ -0,0 +1,114 @@
1
+ import { useRef, useState } from "react"
2
+ import useStore from "../../utils/store"
3
+ import { TMessage } from "../Message"
4
+ import FileUploader from "../FileUploader"
5
+ import { SVGS } from "../../assets/svgs"
6
+ import { Message } from "../Message"
7
+
8
+ export const Sidebar = ({
9
+ messages,
10
+ sendPrompt,
11
+ handleSubmit,
12
+ }: {
13
+ messages: TMessage[]
14
+ sendPrompt: (prompt: string) => void
15
+ handleSubmit: () => void
16
+ }) => {
17
+ const inputRef = useRef<HTMLTextAreaElement>(null)
18
+ const syllabus = useStore((state) => state.syllabus)
19
+ const setSyllabus = useStore((state) => state.setSyllabus)
20
+
21
+ const [isOpen, setIsOpen] = useState(false)
22
+
23
+ return (
24
+ <>
25
+ {!isOpen && (
26
+ <button
27
+ className="fixed top-2 left-2 z-50 lg:hidden bg-white p-1 rounded shadow-md cursor-pointer p-2"
28
+ onClick={() => setIsOpen(true)}
29
+ >
30
+ {SVGS.rigoSoftBlue}
31
+ </button>
32
+ )}
33
+
34
+ <div
35
+ className={`fixed z-40 top-0 left-0 h-full w-4/5 max-w-sm bg-learnpack-blue text-sm text-gray-700 border-r overflow-y-auto scrollbar-hide p-6 transition-transform duration-300 ease-in-out lg:relative lg:transform-none lg:w-1/3 ${
36
+ isOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
37
+ }`}
38
+ >
39
+ {isOpen && (
40
+ <button
41
+ className="lg:hidden bg-white p-1 rounded shadow-md mb-4 cursor-pointer absolute top-2 left-2"
42
+ onClick={() => setIsOpen(false)}
43
+ >
44
+
45
+ </button>
46
+ )}
47
+
48
+ <div className="space-y-2 pb-32 h-[85%] overflow-y-auto scrollbar-hide">
49
+ {messages.map((message, index) => (
50
+ <Message
51
+ key={index}
52
+ type={message.type}
53
+ content={message.content}
54
+ />
55
+ ))}
56
+ </div>
57
+
58
+ <div className="relative w-full rounded-md bg-white text-gray-700 h-24">
59
+ <textarea
60
+ ref={inputRef}
61
+ style={{ resize: "none" }}
62
+ className="w-full h-full p-2"
63
+ placeholder="How can Learnpack help you?"
64
+ autoFocus
65
+ onKeyUp={(e) => {
66
+ if (e.key === "Enter" && !e.shiftKey) {
67
+ e.preventDefault()
68
+ sendPrompt(inputRef.current?.value || "")
69
+ inputRef.current!.value = ""
70
+ }
71
+ if (
72
+ e.key.toLowerCase() === "k" &&
73
+ inputRef.current?.value.toLowerCase().trim() === "ok"
74
+ ) {
75
+ e.preventDefault()
76
+ handleSubmit()
77
+ }
78
+ }}
79
+ />
80
+ <div className="absolute bottom-2 right-2 flex gap-1 items-center">
81
+ <div className="relative inline-block">
82
+ {syllabus.uploadedFiles?.length > 0 && (
83
+ <span
84
+ 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"
85
+ title="Remove uploaded files"
86
+ onClick={() => {
87
+ setSyllabus({ ...syllabus, uploadedFiles: [] })
88
+ }}
89
+ >
90
+ {syllabus.uploadedFiles?.length}
91
+ </span>
92
+ )}
93
+ <FileUploader
94
+ onResult={(res) => {
95
+ setSyllabus({
96
+ ...syllabus,
97
+ uploadedFiles: [...syllabus.uploadedFiles, ...res],
98
+ })
99
+ }}
100
+ />
101
+ </div>
102
+
103
+ <button
104
+ className="cursor-pointer blue-on-hover flex items-center justify-center w-6 h-6"
105
+ onClick={() => sendPrompt(inputRef.current?.value || "")}
106
+ >
107
+ {SVGS.send}
108
+ </button>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </>
113
+ )
114
+ }
@@ -1,5 +1,4 @@
1
1
  import React, { useRef, useState } from "react"
2
- import { SVGS } from "../../assets/svgs"
3
2
  import { useShallow } from "zustand/react/shallow"
4
3
  import useStore from "../../utils/store"
5
4
  import { interactiveCreation } from "../../utils/rigo"
@@ -16,132 +15,28 @@ import {
16
15
  } from "../../utils/creatorUtils"
17
16
 
18
17
  import Loader from "../Loader"
19
- import { Message, TMessage } from "../Message"
18
+ import { TMessage } from "../Message"
20
19
  import { Lesson } from "../LessonItem"
21
- import FileUploader from "../FileUploader"
22
20
  import { ConsumablesManager } from "../ConsumablesManager"
23
21
  import toast from "react-hot-toast"
24
22
  import { ContentIndex } from "./ContentIndex"
25
- import { Syllabus } from "../../utils/store"
26
-
27
- const ContentIndexHeader = ({
28
- messages,
29
- syllabus,
30
- }: {
31
- messages: TMessage[]
32
- syllabus: Syllabus
33
- }) => {
34
- return (
35
- <div>
36
- <h2 className="text-lg font-semibold">
37
- {messages.filter((m) => m.type === "assistant" && m.content.length > 0)
38
- .length === 0
39
- ? "I've created a detailed structure for your course."
40
- : "I've updated the structure based on your feedback."}
41
- </h2>
42
- <p className="text-sm text-gray-600">
43
- {messages.filter((m) => m.type === "assistant" && m.content.length > 0)
44
- .length === 0
45
- ? `It includes a mix of reading, coding exercises, and quizzes. Give
46
- it a look and let me know if it aligns with your expectations or if
47
- there are any changes you'd like to make.`
48
- : "Based on your input, here is the new syllabus, updates are highlighted in yellow"}
49
- </p>
50
- <h3 className="text-sm text-gray-600 mt-2 font-bold">
51
- {syllabus.courseInfo.title}
52
- </h3>
53
- </div>
54
- )
55
- }
56
-
57
-
58
-
59
- const Sidebar = ({
60
- messages,
61
- sendPrompt,
62
- handleSubmit,
63
- }: {
64
- messages: TMessage[]
65
- sendPrompt: (prompt: string) => void
66
- handleSubmit: () => void
67
- }) => {
68
- const inputRef = useRef<HTMLTextAreaElement>(null)
69
- const syllabus = useStore((state) => state.syllabus)
70
- const setSyllabus = useStore((state) => state.setSyllabus)
71
- return (
72
- <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">
73
- <p className="mt-2">If you're satisfied, type "OK" in the chat.</p>
74
- <p className="mt-2">If not, use the chat to give more context.</p>
75
-
76
- <div className="mt-10 space-y-2 pb-16">
77
- {messages.map((message, index) => (
78
- <Message key={index} type={message.type} content={message.content} />
79
- ))}
80
- </div>
81
-
82
- <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 ">
83
- <textarea
84
- ref={inputRef}
85
- style={{ resize: "none" }}
86
- className="w-full h-full p-2"
87
- placeholder="How can Learnpack help you?"
88
- autoFocus
89
- onKeyUp={(e) => {
90
- if (e.key === "Enter" && !e.shiftKey) {
91
- e.preventDefault()
92
- sendPrompt(inputRef.current?.value || "")
93
- inputRef.current!.value = ""
94
- }
95
- // if the pressed key is K or k
96
- if (e.key === "K" || e.key === "k") {
97
- e.preventDefault()
98
-
99
- if (inputRef.current?.value.toLowerCase().trim() === "ok") {
100
- handleSubmit()
101
- }
102
- }
103
- }}
104
- />
105
- <div className="absolute bottom-2 right-2 flex gap-1 items-center">
106
- <div className="relative inline-block">
107
- {syllabus.uploadedFiles?.length > 0 && (
108
- <span
109
- 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"
110
- title="Remove uploaded files"
111
- onClick={() => {
112
- setSyllabus({
113
- ...syllabus,
114
- uploadedFiles: [],
115
- })
116
- }}
117
- >
118
- {syllabus.uploadedFiles?.length}
119
- </span>
120
- )}
121
- <FileUploader
122
- onResult={(res) => {
123
- setSyllabus({
124
- ...syllabus,
125
- uploadedFiles: [...syllabus.uploadedFiles, ...res],
126
- })
127
- }}
128
- />
129
- </div>
130
-
131
- <button
132
- className="cursor-pointer blue-on-hover flex items-center justify-center w-6 h-6"
133
- onClick={() => sendPrompt(inputRef.current?.value || "")}
134
- >
135
- {SVGS.send}
136
- </button>
137
- </div>
138
- </div>
139
- </div>
140
- )
141
- }
23
+ import { Sidebar } from "./Sidebar"
142
24
 
143
25
  const SyllabusEditor: React.FC = () => {
144
- const [messages, setMessages] = useState<TMessage[]>([])
26
+ const [messages, setMessages] = useState<TMessage[]>([
27
+ {
28
+ type: "assistant",
29
+ content: "If you're satisfied, type 'OK' in the chat.",
30
+ },
31
+ {
32
+ type: "assistant",
33
+ content: "If not, use the chat to give more context.",
34
+ },
35
+ {
36
+ type: "user",
37
+ content: "OK",
38
+ },
39
+ ])
145
40
  const [isGenerating, setIsGenerating] = useState(false)
146
41
  const prevLessons = useRef<Lesson[]>([])
147
42
  const { syllabus, setSyllabus, auth } = useStore(
@@ -223,7 +118,7 @@ const SyllabusEditor: React.FC = () => {
223
118
  return isGenerating ? (
224
119
  <Loader
225
120
  listeningTo="course-generation"
226
- icon={SVGS.rigoSoftBlue}
121
+ icon={<img src={"creator/rigo-float.gif"} alt="rigo" className="w-20 h-20" />}
227
122
  initialBuffer="🚀 Starting course generation..."
228
123
  text="Learnpack is setting up your tutorial.
229
124
  It may take a moment..."
@@ -231,18 +126,18 @@ It may take a moment..."
231
126
  ) : (
232
127
  <div className="flex w-full bg-white rounded-md shadow-md overflow-hidden h-screen ">
233
128
  <ConsumablesManager />
234
- {/* Sidebar */}
129
+
235
130
  <Sidebar
236
131
  messages={messages}
237
132
  sendPrompt={sendPrompt}
238
133
  handleSubmit={handleSubmit}
239
134
  />
240
135
 
241
- <div className="w-2/3 p-8 space-y-6">
242
- <ContentIndexHeader messages={messages} syllabus={syllabus} />
136
+ <div className="w-full p-8 space-y-6">
243
137
  <ContentIndex
244
138
  prevLessons={prevLessons.current}
245
139
  handleSubmit={handleSubmit}
140
+ messages={messages}
246
141
  />
247
142
  </div>
248
143
  </div>
@@ -88,15 +88,12 @@ h1 {
88
88
  .loader-icon {
89
89
  width: 40px;
90
90
  height: 40px;
91
-
92
- border: 2px solid var(--loader-color);
93
- border-top-color: transparent;
94
91
  position: relative;
95
92
  display: flex;
96
93
  align-items: center;
97
94
  justify-content: center;
98
95
  }
99
- .loader-icon::after {
96
+ /* .loader-icon::after {
100
97
  content: "";
101
98
  display: block;
102
99
  width: 100%;
@@ -106,11 +103,10 @@ h1 {
106
103
  height: 100%;
107
104
  border: 2px solid var(--gray-text);
108
105
  border-top: 2px solid var(--learnpack-blue);
109
- /* background-color: red; */
110
106
 
111
107
  border-radius: 50%;
112
108
  animation: spin 2s linear infinite;
113
- }
109
+ } */
114
110
 
115
111
  @keyframes glowing {
116
112
  0% {
@@ -5,7 +5,6 @@ import "./index.css"
5
5
  import App from "./App.tsx"
6
6
  import SyllabusEditor from "./components/syllabus/SyllabusEditor.tsx"
7
7
  import { Toaster } from "react-hot-toast"
8
-
9
8
  createRoot(document.getElementById("root")!).render(
10
9
  <StrictMode>
11
10
  <Toaster />
@@ -9,7 +9,8 @@ export type FormState = {
9
9
  hasContentIndex: boolean
10
10
  contentIndex: string
11
11
  isCompleted: boolean
12
- currentStep: number
12
+ variables: string[]
13
+ currentStep: string
13
14
  title?: string
14
15
  }
15
16
 
@@ -17,6 +18,7 @@ type Auth = {
17
18
  bcToken: string
18
19
  rigoToken: string
19
20
  userId: string
21
+ user: any
20
22
  }
21
23
  export type Syllabus = {
22
24
  lessons: Lesson[]
@@ -49,6 +51,7 @@ const useStore = create<Store>()(
49
51
  bcToken: "",
50
52
  rigoToken: "",
51
53
  userId: "",
54
+ user: null,
52
55
  },
53
56
  formState: {
54
57
  description: "",
@@ -57,7 +60,14 @@ const useStore = create<Store>()(
57
60
  hasContentIndex: false,
58
61
  contentIndex: "",
59
62
  isCompleted: false,
60
- currentStep: 0,
63
+ currentStep: "description",
64
+ variables: [
65
+ "description",
66
+ "duration",
67
+ "login",
68
+ "targetAudience",
69
+ "hasContentIndex",
70
+ ],
61
71
  },
62
72
  setFormState: (formState: Partial<FormState>) =>
63
73
  set((state) => ({ formState: { ...state.formState, ...formState } })),
@@ -71,8 +81,9 @@ const useStore = create<Store>()(
71
81
  hasContentIndex: false,
72
82
  contentIndex: "",
73
83
  isCompleted: false,
74
- currentStep: 0,
84
+ currentStep: "description",
75
85
  title: "",
86
+ variables: [],
76
87
  },
77
88
  uploadedFiles: [],
78
89
  },