@learnpack/learnpack 5.0.83 → 5.0.87

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 (29) hide show
  1. package/README.md +13 -13
  2. package/lib/creatorDist/assets/{index-CwPh6b6M.js → index-BuYPhmTd.js} +38848 -31631
  3. package/lib/creatorDist/assets/{index-DSLkFVbA.css → index-ldEC0yWM.css} +125 -46
  4. package/lib/creatorDist/index.html +2 -2
  5. package/lib/utils/api.js +0 -8
  6. package/oclif.manifest.json +1 -1
  7. package/package.json +1 -1
  8. package/src/creator/package-lock.json +1229 -61
  9. package/src/creator/package.json +1 -0
  10. package/src/creator/src/App.tsx +12 -6
  11. package/src/creator/src/components/FileCard.tsx +22 -0
  12. package/src/creator/src/components/LessonItem.tsx +20 -9
  13. package/src/creator/src/components/Login.tsx +6 -24
  14. package/src/creator/src/components/MarkdownRenderer.tsx +11 -0
  15. package/src/creator/src/components/Message.tsx +4 -2
  16. package/src/creator/src/components/syllabus/ContentIndex.tsx +46 -64
  17. package/src/creator/src/components/syllabus/Sidebar.tsx +21 -19
  18. package/src/creator/src/components/syllabus/SyllabusEditor.tsx +62 -33
  19. package/src/creator/src/index.css +40 -0
  20. package/src/creator/src/utils/creatorUtils.ts +5 -1
  21. package/src/creator/src/utils/lib.ts +20 -10
  22. package/src/creator/src/utils/store.ts +31 -22
  23. package/src/creatorDist/assets/{index-CwPh6b6M.js → index-BuYPhmTd.js} +38848 -31631
  24. package/src/creatorDist/assets/{index-DSLkFVbA.css → index-ldEC0yWM.css} +125 -46
  25. package/src/creatorDist/index.html +2 -2
  26. package/src/ui/_app/app.css +1 -1
  27. package/src/ui/_app/app.js +344 -344
  28. package/src/ui/app.tar.gz +0 -0
  29. package/src/utils/api.ts +0 -9
@@ -22,6 +22,7 @@
22
22
  "react": "^19.0.0",
23
23
  "react-dom": "^19.0.0",
24
24
  "react-hot-toast": "^2.5.2",
25
+ "react-markdown": "^10.1.0",
25
26
  "react-router": "^7.5.0",
26
27
  "syllable": "^5.0.1",
27
28
  "zustand": "^5.0.3"
@@ -17,13 +17,12 @@ import FileUploader from "./components/FileUploader"
17
17
  function App() {
18
18
  const navigate = useNavigate()
19
19
 
20
- const { formState, setFormState, setSyllabus, setAuth } = useStore(
20
+ const { formState, setFormState, setAuth, push } = useStore(
21
21
  useShallow((state) => ({
22
- // auth: state.auth,
23
22
  formState: state.formState,
24
23
  setFormState: state.setFormState,
25
- setSyllabus: state.setSyllabus,
26
24
  setAuth: state.setAuth,
25
+ push: state.push,
27
26
  }))
28
27
  )
29
28
 
@@ -74,9 +73,10 @@ function App() {
74
73
  prevInteractions: "",
75
74
  })
76
75
  const lessons = res.parsed.listOfSteps.map((lesson: any) => {
77
- return parseLesson(lesson)
76
+ return parseLesson(lesson, [])
78
77
  })
79
- setSyllabus({
78
+
79
+ push({
80
80
  lessons,
81
81
  courseInfo: {
82
82
  ...formState,
@@ -266,7 +266,13 @@ function App() {
266
266
  {formState.isCompleted ? (
267
267
  <Loader
268
268
  text="Learnpack is setting up your tutorial. It may take a moment..."
269
- icon={<img src={"creator/rigo-float.gif"} alt="rigo" className="w-20 h-20" />}
269
+ icon={
270
+ <img
271
+ src={"creator/rigo-float.gif"}
272
+ alt="rigo"
273
+ className="w-20 h-20"
274
+ />
275
+ }
270
276
  />
271
277
  ) : (
272
278
  <StepWizard
@@ -0,0 +1,22 @@
1
+ import { SVGS } from "../assets/svgs"
2
+ import { UploadedFile } from "../utils/store"
3
+
4
+ export const FileCard = ({
5
+ file,
6
+ handleRemove,
7
+ }: {
8
+ file: UploadedFile
9
+ handleRemove: () => void
10
+ }) => (
11
+ <div className="flex items-center justify-between bg-white shadow-sm p-2 rounded-lg w-[100px]">
12
+ <span title={file.name} className="text-xs text-gray-800 truncate">
13
+ {file.name}
14
+ </span>
15
+ <button
16
+ onClick={handleRemove}
17
+ className="p-1 text-red-500 hover:text-red-700 transition-colors cursor-pointer "
18
+ >
19
+ {SVGS.trash}
20
+ </button>
21
+ </div>
22
+ )
@@ -1,8 +1,9 @@
1
- import { useState } from "react"
1
+ import { useState, useRef } from "react"
2
2
  import { SVGS } from "../assets/svgs"
3
3
 
4
4
  export interface Lesson {
5
5
  id: string
6
+ uid: string
6
7
  title: string
7
8
  type: "READ" | "CODE" | "QUIZ"
8
9
  description: string
@@ -12,8 +13,8 @@ export interface Lesson {
12
13
  interface LessonItemProps {
13
14
  lesson: Lesson
14
15
  isNew: boolean
15
- onChange: (id: string, newTitle: string) => void
16
- onRemove: (id: string) => void
16
+ onChange: (uid: string, newTitle: string) => void
17
+ onRemove: () => void
17
18
  }
18
19
 
19
20
  function cleanFloatString(input: string): string {
@@ -31,12 +32,13 @@ export const LessonItem: React.FC<LessonItemProps> = ({
31
32
  onRemove,
32
33
  isNew,
33
34
  }) => {
35
+ const inputRef = useRef<HTMLInputElement>(null)
34
36
  const [isEditing, setIsEditing] = useState(false)
35
37
 
36
38
  return (
37
39
  <div
38
40
  className={`flex items-center space-x-2 relative rounded-md p-3 ${
39
- isNew ? "border border-learnpack-blue" : "border border-gray-200"
41
+ isNew ? "border border-learnpack-blue appear" : "border border-gray-200"
40
42
  }`}
41
43
  >
42
44
  {isNew && <span className="red-ball"></span>}
@@ -47,9 +49,13 @@ export const LessonItem: React.FC<LessonItemProps> = ({
47
49
 
48
50
  {isEditing ? (
49
51
  <input
50
- value={lesson.title}
51
- onChange={(e) => onChange(lesson.id, e.target.value)}
52
- onBlur={() => setIsEditing(false)}
52
+ defaultValue={lesson.title}
53
+ ref={inputRef}
54
+ onBlur={() => {
55
+ if (inputRef.current) {
56
+ onChange(lesson.uid, inputRef.current.value)
57
+ }
58
+ }}
53
59
  autoFocus
54
60
  className="flex-1 bg-white border border-gray-300 rounded-md px-2 py-1 text-sm"
55
61
  />
@@ -62,13 +68,18 @@ export const LessonItem: React.FC<LessonItemProps> = ({
62
68
  </span>
63
69
 
64
70
  <button
65
- onClick={() => setIsEditing(!isEditing)}
71
+ onClick={() => {
72
+ setIsEditing(!isEditing)
73
+ if (inputRef.current) {
74
+ onChange(lesson.uid, inputRef.current.value)
75
+ }
76
+ }}
66
77
  className="text-gray-500 hover:text-blue-500 cursor-pointer "
67
78
  >
68
79
  {SVGS.pen}
69
80
  </button>
70
81
  <button
71
- onClick={() => onRemove(lesson.id)}
82
+ onClick={() => onRemove()}
72
83
  className="text-red-500 hover:text-red-700 cursor-pointer"
73
84
  >
74
85
  {SVGS.trash}
@@ -1,10 +1,10 @@
1
- import { useEffect, useState } from "react"
1
+ import { useState } from "react"
2
2
  import { BREATHECODE_HOST } from "../utils/constants"
3
3
  import { SVGS } from "../assets/svgs"
4
4
  import toast from "react-hot-toast"
5
5
  import useStore from "../utils/store"
6
6
  import { useShallow } from "zustand/react/shallow"
7
- import { checkParams, login4Geeks, loginWithToken } from "../utils/lib"
7
+ import { login4Geeks } from "../utils/lib"
8
8
 
9
9
  export default function Login({ onFinish }: { onFinish: () => void }) {
10
10
  const [email, setEmail] = useState("")
@@ -58,24 +58,6 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
58
58
  window.location.href = `${BREATHECODE_HOST}/v1/auth/github/?url=${url}`
59
59
  }
60
60
 
61
- useEffect(() => {
62
- ;(async () => {
63
- const { token } = checkParams()
64
- if (token) {
65
- const user = await loginWithToken(token)
66
- if (user) {
67
- setAuth({
68
- bcToken: token,
69
- userId: user.id,
70
- rigoToken: user.rigobot.key,
71
- user,
72
- })
73
- onFinish()
74
- }
75
- }
76
- })()
77
- }, [])
78
-
79
61
  return (
80
62
  <div
81
63
  className="fixed inset-0 bg-black/50 flex items-center justify-center z-1000"
@@ -91,7 +73,7 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
91
73
  </p>
92
74
  <button
93
75
  onClick={redirectGithub}
94
- className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4"
76
+ className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4 cursor-pointer"
95
77
  >
96
78
  {SVGS.github} LOGIN WITH GITHUB
97
79
  </button>
@@ -119,14 +101,14 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
119
101
  <div className="flex gap-2 mt-4">
120
102
  <button
121
103
  type="submit"
122
- className="flex-1 bg-sky-500 text-white py-2 rounded-md font-semibold"
104
+ className="flex-1 bg-sky-500 text-white py-2 rounded-md font-semibold cursor-pointer"
123
105
  >
124
106
  {isLoading ? "Logging in..." : "Log in"}
125
107
  </button>
126
108
  <button
127
109
  type="button"
128
110
  onClick={() => setShowForm(false)}
129
- className="flex-1 border border-sky-500 text-sky-600 py-2 rounded-md font-semibold"
111
+ className="flex-1 border border-sky-500 text-sky-600 py-2 rounded-md font-semibold cursor-pointer"
130
112
  >
131
113
  Skip
132
114
  </button>
@@ -144,7 +126,7 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
144
126
  ) : (
145
127
  <button
146
128
  onClick={() => setShowForm(true)}
147
- className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold"
129
+ className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold cursor-pointer"
148
130
  >
149
131
  Login with Email
150
132
  </button>
@@ -0,0 +1,11 @@
1
+ import Markdown from "react-markdown"
2
+
3
+ export const MarkdownRenderer: React.FC<{
4
+ content: string
5
+ }> = ({ content }) => {
6
+ return (
7
+ <div className="markdown-renderer">
8
+ <Markdown>{content}</Markdown>
9
+ </div>
10
+ )
11
+ }
@@ -2,6 +2,7 @@ import { RigoLoader } from "./RigoLoader"
2
2
 
3
3
  import { SVGS } from "../assets/svgs"
4
4
  import useStore from "../utils/store"
5
+ import { MarkdownRenderer } from "./MarkdownRenderer"
5
6
 
6
7
  export type TMessage = {
7
8
  type: "user" | "assistant"
@@ -14,7 +15,6 @@ export const Message: React.FC<TMessage> = ({ type, content }) => {
14
15
 
15
16
  const isLoading = isAI && !content
16
17
 
17
- console.log("user", user)
18
18
  return isLoading ? (
19
19
  <RigoLoader text="Thinking..." svg={<img src="rigo-float.gif" />} />
20
20
  ) : (
@@ -32,7 +32,9 @@ export const Message: React.FC<TMessage> = ({ type, content }) => {
32
32
  ) : (
33
33
  <span className="mt-1">{SVGS.user}</span>
34
34
  )}
35
- <p className="text-sm leading-relaxed">{content}</p>
35
+ <div className="w-full break-words overflow-x-auto ">
36
+ <MarkdownRenderer content={content} />
37
+ </div>
36
38
  </div>
37
39
  )
38
40
  }
@@ -5,6 +5,8 @@ import { SVGS } from "../../assets/svgs"
5
5
  import { TMessage } from "../Message"
6
6
  import Loader from "../Loader"
7
7
  import { motion, AnimatePresence } from "framer-motion"
8
+ import { randomUUID } from "../../utils/creatorUtils"
9
+ import toast from "react-hot-toast"
8
10
 
9
11
  const ContentIndexHeader = ({
10
12
  messages,
@@ -28,7 +30,7 @@ const ContentIndexHeader = ({
28
30
  : "Based on your input, here is the new syllabus, updates are highlighted in yellow"
29
31
 
30
32
  return (
31
- <div className="mt-2">
33
+ <div className="mt-2 ">
32
34
  <AnimatePresence mode="wait">
33
35
  <motion.h2
34
36
  key={headerText}
@@ -36,7 +38,7 @@ const ContentIndexHeader = ({
36
38
  animate={{ opacity: 1, y: 0 }}
37
39
  exit={{ opacity: 0, y: 10 }}
38
40
  transition={{ duration: 0.2 }}
39
- className="text-lg font-semibold "
41
+ className="text-xs font-semibold sm:text-base md:text-lg"
40
42
  >
41
43
  {headerText}
42
44
  </motion.h2>
@@ -49,7 +51,7 @@ const ContentIndexHeader = ({
49
51
  animate={{ opacity: 1 }}
50
52
  exit={{ opacity: 0 }}
51
53
  transition={{ duration: 0.2, delay: 0.1 }}
52
- className="text-sm text-gray-600 mt-1 mb-5"
54
+ className="text-xs sm:text-sm text-gray-600 mt-1 mb-5"
53
55
  >
54
56
  {subText}
55
57
  </motion.p>
@@ -58,33 +60,31 @@ const ContentIndexHeader = ({
58
60
  )
59
61
  }
60
62
 
61
- export default ContentIndexHeader
62
-
63
63
  export const GenerateButton = ({
64
64
  handleSubmit,
65
- handleUndo,
66
- hasChanges,
67
65
  }: {
68
66
  handleSubmit: () => void
69
- handleUndo: () => void
70
- hasChanges: boolean
71
67
  }) => {
68
+ const history = useStore((state) => state.history)
69
+ const undo = useStore((state) => state.undo)
70
+ const prev = history[history.length - 2]
72
71
  return (
73
- <div className="flex justify-end mt-6">
74
- {hasChanges && (
72
+ <div className="flex flex-col space-y-2 sm:flex-row sm:space-y-0 sm:space-x-4 justify-end mt-6">
73
+ {prev && prev.lessons.length > 0 && (
75
74
  <button
76
- className="text-gray-500 mr-4 cursor-pointer bg-gray-200 rounded px-4 py-2 hover:bg-gray-300 flex items-center gap-2"
77
- onClick={() => handleUndo()}
75
+ onClick={() => {
76
+ undo()
77
+ toast.success("Changes reverted!")
78
+ }}
79
+ className="w-full sm:w-auto text-gray-500 bg-gray-200 rounded px-4 py-2 hover:bg-gray-300 flex items-center justify-center gap-2 cursor-pointer"
78
80
  >
79
81
  {SVGS.undo}
80
82
  Revert changes
81
83
  </button>
82
84
  )}
83
85
  <button
84
- onClick={async () => {
85
- handleSubmit()
86
- }}
87
- className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 cursor-pointer flex items-center gap-2"
86
+ onClick={handleSubmit}
87
+ className="w-full sm:w-auto bg-blue-600 text-white rounded px-4 py-2 hover:bg-blue-700 flex items-center justify-center gap-2 cursor-pointer"
88
88
  >
89
89
  <span>I'm ready. Create the course for me!</span>
90
90
  {SVGS.rigoSoftBlue}
@@ -93,52 +93,36 @@ export const GenerateButton = ({
93
93
  )
94
94
  }
95
95
 
96
- function hasChanges(current: Lesson[], previous: Lesson[]): boolean {
97
- return current.some(
98
- (c) =>
99
- !previous.some(
100
- (p) =>
101
- p.id === c.id &&
102
- p.title === c.title &&
103
- p.type === c.type &&
104
- p.duration === c.duration &&
105
- p.description === c.description
106
- )
107
- )
108
- }
109
-
110
96
  export const ContentIndex = ({
111
- prevLessons,
112
97
  handleSubmit,
113
98
  messages,
114
99
  isThinking,
115
- handleUndo,
116
100
  }: {
117
- prevLessons?: Lesson[]
118
101
  handleSubmit: () => void
119
102
  messages: TMessage[]
120
103
  isThinking: boolean
121
- handleUndo: () => void
122
104
  }) => {
123
- const syllabus = useStore((state) => state.syllabus)
124
- const setSyllabus = useStore((state) => state.setSyllabus)
105
+ const history = useStore((state) => state.history)
106
+ const push = useStore((state) => state.push)
125
107
  const containerRef = useRef<HTMLDivElement>(null)
126
108
  const [showScrollHint, setShowScrollHint] = useState(false)
127
109
 
110
+ const syllabus = history[history.length - 1]
111
+
112
+ const previousSyllabus = history[history.length - 2] || null
113
+
128
114
  const handleRemove = (lesson: Lesson) => {
129
- setSyllabus({
115
+ push({
130
116
  ...syllabus,
131
- lessons: syllabus.lessons.filter(
132
- (l) => l.id !== lesson.id && l.title !== lesson.title
133
- ),
117
+ lessons: syllabus.lessons.filter((l) => l.uid !== lesson.uid),
134
118
  })
135
119
  }
136
120
 
137
- const handleChange = (id: string, newTitle: string) => {
138
- setSyllabus({
121
+ const handleChange = (uid: string, newTitle: string) => {
122
+ push({
139
123
  ...syllabus,
140
124
  lessons: syllabus.lessons.map((lesson) =>
141
- lesson.id === id ? { ...lesson, title: newTitle } : lesson
125
+ lesson.uid === uid ? { ...lesson, title: newTitle } : lesson
142
126
  ),
143
127
  })
144
128
  }
@@ -147,14 +131,15 @@ export const ContentIndex = ({
147
131
  const newLesson: Lesson = {
148
132
  id: (parseFloat(id) + 0.1).toFixed(1),
149
133
  title: "Hello World",
134
+ uid: randomUUID(),
150
135
  type: "READ",
151
136
  duration: 2,
152
137
  description: "Hello World",
153
138
  }
154
139
  const updated = [...syllabus.lessons]
155
140
  updated.splice(index + 1, 0, newLesson)
156
-
157
- setSyllabus({
141
+ push({
142
+ ...syllabus,
158
143
  lessons: updated,
159
144
  })
160
145
  }
@@ -193,31 +178,30 @@ export const ContentIndex = ({
193
178
  }
194
179
 
195
180
  return (
196
- <div className="relative ">
181
+ <div className="relative w-full p-2 sm:p-4 md:p-6 lg:p-8 space-y-6">
197
182
  <ContentIndexHeader messages={messages} syllabus={syllabus} />
198
183
  <div
199
184
  ref={containerRef}
200
- className=" space-y-3 overflow-y-auto max-h-[75vh] pr-2 scrollbar-hide relative pb-5"
185
+ className="space-y-3 overflow-y-auto max-h-[78vh] pr-2 scrollbar-hide relative pb-20"
201
186
  >
202
187
  {isThinking ? (
203
- <Loader text="Thinking..." minheight="min-h-[70vh]" />
188
+ <Loader text="Thinking..." minheight="min-h-[69vh]" />
204
189
  ) : (
205
190
  <>
206
191
  {syllabus.lessons.map((lesson, index) => (
207
- <div key={lesson.id}>
192
+ <div key={lesson.uid + index}>
208
193
  <LessonItem
209
- key={lesson.id + index + lesson.title}
194
+ key={lesson.uid}
210
195
  lesson={lesson}
211
196
  onChange={handleChange}
212
197
  onRemove={() => handleRemove(lesson)}
213
198
  isNew={Boolean(
214
- prevLessons &&
215
- prevLessons.length > 0 &&
216
- !prevLessons.some(
217
- (l) =>
218
- l.id === lesson.id &&
219
- l.title === lesson.title &&
220
- l.type === lesson.type
199
+ previousSyllabus &&
200
+ previousSyllabus &&
201
+ previousSyllabus.lessons &&
202
+ previousSyllabus.lessons.length > 0 &&
203
+ !previousSyllabus.lessons.some(
204
+ (l) => l.uid === lesson.uid
221
205
  )
222
206
  )}
223
207
  />
@@ -233,11 +217,9 @@ export const ContentIndex = ({
233
217
  </div>
234
218
  </div>
235
219
  ))}
236
- <GenerateButton
237
- handleSubmit={handleSubmit}
238
- handleUndo={handleUndo}
239
- hasChanges={hasChanges(syllabus.lessons, prevLessons || [])}
240
- />
220
+ {syllabus.lessons.length > 0 && (
221
+ <GenerateButton handleSubmit={handleSubmit} />
222
+ )}
241
223
  </>
242
224
  )}
243
225
  </div>
@@ -245,7 +227,7 @@ export const ContentIndex = ({
245
227
  {showScrollHint && !isThinking && (
246
228
  <div className="pointer-events-none relative">
247
229
  <div className="absolute bottom-0 left-0 w-full h-60 bg-gradient-to-t from-white to-transparent z-10" />
248
- <div className="absolute bottom-3 left-0 w-full flex justify-center z-20">
230
+ <div className="absolute bottom-10 left-0 w-full flex justify-center z-20">
249
231
  <button
250
232
  style={{ color: "#0084FF" }}
251
233
  onClick={() => scrollToBottom("bottom")}
@@ -4,6 +4,7 @@ import { TMessage } from "../Message"
4
4
  import FileUploader from "../FileUploader"
5
5
  import { SVGS } from "../../assets/svgs"
6
6
  import { Message } from "../Message"
7
+ import { FileCard } from "../FileCard"
7
8
 
8
9
  export const Sidebar = ({
9
10
  messages,
@@ -15,8 +16,8 @@ export const Sidebar = ({
15
16
  handleSubmit: () => void
16
17
  }) => {
17
18
  const inputRef = useRef<HTMLTextAreaElement>(null)
18
- const syllabus = useStore((state) => state.syllabus)
19
- const setSyllabus = useStore((state) => state.setSyllabus)
19
+ const uploadedFiles = useStore((state) => state.uploadedFiles)
20
+ const setUploadedFiles = useStore((state) => state.setUploadedFiles)
20
21
 
21
22
  const [isOpen, setIsOpen] = useState(false)
22
23
 
@@ -32,7 +33,7 @@ export const Sidebar = ({
32
33
  )}
33
34
 
34
35
  <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
+ 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 border-C8DBFC overflow-y-auto scrollbar-hide p-6 transition-transform duration-300 ease-in-out lg:relative lg:transform-none lg:w-1/3 ${
36
37
  isOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
37
38
  }`}
38
39
  >
@@ -56,10 +57,25 @@ export const Sidebar = ({
56
57
  </div>
57
58
 
58
59
  <div className="relative w-full rounded-md bg-white text-gray-700 h-24">
60
+ {uploadedFiles?.length > 0 && (
61
+ <div className="absolute bottom-[100%] flex gap-2 w-full pb-1 rounded-md overflow-x-auto scrollbar-hide">
62
+ {uploadedFiles.map((file, index) => (
63
+ <FileCard
64
+ key={index}
65
+ file={file}
66
+ handleRemove={() => {
67
+ const newFiles = [...uploadedFiles]
68
+ newFiles.splice(index, 1)
69
+ setUploadedFiles(newFiles)
70
+ }}
71
+ />
72
+ ))}
73
+ </div>
74
+ )}
59
75
  <textarea
60
76
  ref={inputRef}
61
77
  style={{ resize: "none" }}
62
- className="w-full h-full p-2"
78
+ className="w-full h-full p-2 outline-blue rounded"
63
79
  placeholder="How can Learnpack help you?"
64
80
  autoFocus
65
81
  onKeyUp={(e) => {
@@ -79,23 +95,9 @@ export const Sidebar = ({
79
95
  />
80
96
  <div className="absolute bottom-2 right-2 flex gap-1 items-center">
81
97
  <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
98
  <FileUploader
94
99
  onResult={(res) => {
95
- setSyllabus({
96
- ...syllabus,
97
- uploadedFiles: [...syllabus.uploadedFiles, ...res],
98
- })
100
+ setUploadedFiles([...(uploadedFiles || []), ...res])
99
101
  }}
100
102
  />
101
103
  </div>