@learnpack/learnpack 5.0.81 → 5.0.85

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/creatorDist/assets/{index-WzdhAujs.js → index-D15TgYvM.js} +38777 -31507
  3. package/lib/creatorDist/assets/{index-BJ2JJzVC.css → index-ldEC0yWM.css} +148 -41
  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 +6 -6
  11. package/src/creator/src/assets/svgs.tsx +18 -0
  12. package/src/creator/src/components/FileCard.tsx +22 -0
  13. package/src/creator/src/components/LessonItem.tsx +20 -9
  14. package/src/creator/src/components/Login.tsx +10 -24
  15. package/src/creator/src/components/MarkdownRenderer.tsx +11 -0
  16. package/src/creator/src/components/Message.tsx +4 -2
  17. package/src/creator/src/components/syllabus/ContentIndex.tsx +53 -42
  18. package/src/creator/src/components/syllabus/Sidebar.tsx +22 -20
  19. package/src/creator/src/components/syllabus/SyllabusEditor.tsx +63 -30
  20. package/src/creator/src/index.css +47 -0
  21. package/src/creator/src/utils/creatorUtils.ts +5 -1
  22. package/src/creator/src/utils/lib.ts +20 -10
  23. package/src/creator/src/utils/store.ts +31 -22
  24. package/src/creatorDist/assets/{index-WzdhAujs.js → index-D15TgYvM.js} +38777 -31507
  25. package/src/creatorDist/assets/{index-BJ2JJzVC.css → index-ldEC0yWM.css} +148 -41
  26. package/src/creatorDist/index.html +2 -2
  27. package/src/ui/_app/app.css +1 -1
  28. package/src/ui/_app/app.js +344 -344
  29. package/src/ui/app.tar.gz +0 -0
  30. 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,
@@ -268,7 +268,7 @@ function App() {
268
268
  text="Learnpack is setting up your tutorial. It may take a moment..."
269
269
  icon={
270
270
  <img
271
- src={"rigo-float.gif"}
271
+ src={"creator/rigo-float.gif"}
272
272
  alt="rigo"
273
273
  className="w-20 h-20"
274
274
  />
@@ -263,4 +263,22 @@ export const SVGS = {
263
263
  />
264
264
  </svg>
265
265
  ),
266
+ undo: (
267
+ <svg
268
+ width="20px"
269
+ height="20px"
270
+ viewBox="0 0 24 24"
271
+ fill="none"
272
+ xmlns="http://www.w3.org/2000/svg"
273
+ >
274
+ <path
275
+ d="M12 20.75C10.078 20.7474 8.23546 19.9827 6.8764 18.6236C5.51733 17.2645 4.75265 15.422 4.75 13.5C4.75 13.3011 4.82902 13.1103 4.96967 12.9697C5.11032 12.829 5.30109 12.75 5.5 12.75C5.69891 12.75 5.88968 12.829 6.03033 12.9697C6.17098 13.1103 6.25 13.3011 6.25 13.5C6.25 14.6372 6.58723 15.7489 7.21905 16.6945C7.85087 17.6401 8.74889 18.3771 9.79957 18.8123C10.8502 19.2475 12.0064 19.3614 13.1218 19.1395C14.2372 18.9177 15.2617 18.37 16.0659 17.5659C16.87 16.7617 17.4177 15.7372 17.6395 14.6218C17.8614 13.5064 17.7475 12.3502 17.3123 11.2996C16.8771 10.2489 16.1401 9.35087 15.1945 8.71905C14.2489 8.08723 13.1372 7.75 12 7.75H9.5C9.30109 7.75 9.11032 7.67098 8.96967 7.53033C8.82902 7.38968 8.75 7.19891 8.75 7C8.75 6.80109 8.82902 6.61032 8.96967 6.46967C9.11032 6.32902 9.30109 6.25 9.5 6.25H12C13.9228 6.25 15.7669 7.01384 17.1265 8.37348C18.4862 9.73311 19.25 11.5772 19.25 13.5C19.25 15.4228 18.4862 17.2669 17.1265 18.6265C15.7669 19.9862 13.9228 20.75 12 20.75Z"
276
+ fill="#000000"
277
+ />
278
+ <path
279
+ d="M12 10.75C11.9015 10.7505 11.8038 10.7313 11.7128 10.6935C11.6218 10.6557 11.5393 10.6001 11.47 10.53L8.47001 7.53003C8.32956 7.38941 8.25067 7.19878 8.25067 7.00003C8.25067 6.80128 8.32956 6.61066 8.47001 6.47003L11.47 3.47003C11.5387 3.39634 11.6215 3.33724 11.7135 3.29625C11.8055 3.25526 11.9048 3.23322 12.0055 3.23144C12.1062 3.22966 12.2062 3.24819 12.2996 3.28591C12.393 3.32363 12.4778 3.37977 12.549 3.45099C12.6203 3.52221 12.6764 3.60705 12.7141 3.70043C12.7519 3.79382 12.7704 3.89385 12.7686 3.99455C12.7668 4.09526 12.7448 4.19457 12.7038 4.28657C12.6628 4.37857 12.6037 4.46137 12.53 4.53003L10.06 7.00003L12.53 9.47003C12.6705 9.61066 12.7494 9.80128 12.7494 10C12.7494 10.1988 12.6705 10.3894 12.53 10.53C12.4608 10.6001 12.3782 10.6557 12.2872 10.6935C12.1962 10.7313 12.0986 10.7505 12 10.75Z"
280
+ fill="#000000"
281
+ />
282
+ </svg>
283
+ ),
266
284
  }
@@ -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"
@@ -85,9 +67,13 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
85
67
  className="bg-white p-8 rounded-xl shadow-md max-w-sm w-full"
86
68
  onClick={(e) => e.stopPropagation()}
87
69
  >
70
+ <p className="mb-4 text-center text-gray-700">
71
+ You need to have a 4Geeks account with a creator plan to create a
72
+ tutorial.
73
+ </p>
88
74
  <button
89
75
  onClick={redirectGithub}
90
- 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"
91
77
  >
92
78
  {SVGS.github} LOGIN WITH GITHUB
93
79
  </button>
@@ -115,14 +101,14 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
115
101
  <div className="flex gap-2 mt-4">
116
102
  <button
117
103
  type="submit"
118
- 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"
119
105
  >
120
106
  {isLoading ? "Logging in..." : "Log in"}
121
107
  </button>
122
108
  <button
123
109
  type="button"
124
110
  onClick={() => setShowForm(false)}
125
- 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"
126
112
  >
127
113
  Skip
128
114
  </button>
@@ -140,7 +126,7 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
140
126
  ) : (
141
127
  <button
142
128
  onClick={() => setShowForm(true)}
143
- 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"
144
130
  >
145
131
  Login with Email
146
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,
@@ -18,7 +20,7 @@ const ContentIndexHeader = ({
18
20
  .length === 2
19
21
 
20
22
  const headerText = isFirst
21
- ? "I've created a detailed structure for your course."
23
+ ? `I've created a detailed structure for your course: ${syllabus.courseInfo.title}`
22
24
  : "I've updated the structure based on your feedback."
23
25
 
24
26
  const subText = isFirst
@@ -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,33 +51,39 @@ 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"
54
+ className="text-xs sm:text-sm text-gray-600 mt-1 mb-5"
53
55
  >
54
56
  {subText}
55
57
  </motion.p>
56
58
  </AnimatePresence>
57
-
58
- <h3 className="text-sm text-gray-600 mt-2 font-bold">
59
- {syllabus.courseInfo.title}
60
- </h3>
61
59
  </div>
62
60
  )
63
61
  }
64
62
 
65
- export default ContentIndexHeader
66
-
67
63
  export const GenerateButton = ({
68
64
  handleSubmit,
69
65
  }: {
70
66
  handleSubmit: () => void
71
67
  }) => {
68
+ const history = useStore((state) => state.history)
69
+ const undo = useStore((state) => state.undo)
72
70
  return (
73
- <div className="flex justify-end mt-6">
71
+ <div className="flex flex-col space-y-2 sm:flex-row sm:space-y-0 sm:space-x-4 justify-end mt-6">
72
+ {history.length > 1 && (
73
+ <button
74
+ onClick={() => {
75
+ undo()
76
+ toast.success("Changes reverted!")
77
+ }}
78
+ 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"
79
+ >
80
+ {SVGS.undo}
81
+ Revert changes
82
+ </button>
83
+ )}
74
84
  <button
75
- onClick={async () => {
76
- handleSubmit()
77
- }}
78
- className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 cursor-pointer flex items-center gap-2"
85
+ onClick={handleSubmit}
86
+ 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"
79
87
  >
80
88
  <span>I'm ready. Create the course for me!</span>
81
89
  {SVGS.rigoSoftBlue}
@@ -83,36 +91,37 @@ export const GenerateButton = ({
83
91
  </div>
84
92
  )
85
93
  }
94
+
86
95
  export const ContentIndex = ({
87
- prevLessons,
88
96
  handleSubmit,
89
97
  messages,
90
98
  isThinking,
91
99
  }: {
92
- prevLessons?: Lesson[]
93
100
  handleSubmit: () => void
94
101
  messages: TMessage[]
95
102
  isThinking: boolean
96
103
  }) => {
97
- const syllabus = useStore((state) => state.syllabus)
98
- const setSyllabus = useStore((state) => state.setSyllabus)
104
+ const history = useStore((state) => state.history)
105
+ const push = useStore((state) => state.push)
99
106
  const containerRef = useRef<HTMLDivElement>(null)
100
107
  const [showScrollHint, setShowScrollHint] = useState(false)
101
108
 
109
+ const syllabus = history[history.length - 1]
110
+
111
+ const previousSyllabus = history[history.length - 2] || null
112
+
102
113
  const handleRemove = (lesson: Lesson) => {
103
- setSyllabus({
114
+ push({
104
115
  ...syllabus,
105
- lessons: syllabus.lessons.filter(
106
- (l) => l.id !== lesson.id && l.title !== lesson.title
107
- ),
116
+ lessons: syllabus.lessons.filter((l) => l.uid !== lesson.uid),
108
117
  })
109
118
  }
110
119
 
111
- const handleChange = (id: string, newTitle: string) => {
112
- setSyllabus({
120
+ const handleChange = (uid: string, newTitle: string) => {
121
+ push({
113
122
  ...syllabus,
114
123
  lessons: syllabus.lessons.map((lesson) =>
115
- lesson.id === id ? { ...lesson, title: newTitle } : lesson
124
+ lesson.uid === uid ? { ...lesson, title: newTitle } : lesson
116
125
  ),
117
126
  })
118
127
  }
@@ -121,14 +130,15 @@ export const ContentIndex = ({
121
130
  const newLesson: Lesson = {
122
131
  id: (parseFloat(id) + 0.1).toFixed(1),
123
132
  title: "Hello World",
133
+ uid: randomUUID(),
124
134
  type: "READ",
125
135
  duration: 2,
126
136
  description: "Hello World",
127
137
  }
128
138
  const updated = [...syllabus.lessons]
129
139
  updated.splice(index + 1, 0, newLesson)
130
-
131
- setSyllabus({
140
+ push({
141
+ ...syllabus,
132
142
  lessons: updated,
133
143
  })
134
144
  }
@@ -167,31 +177,30 @@ export const ContentIndex = ({
167
177
  }
168
178
 
169
179
  return (
170
- <div className="relative ">
180
+ <div className="relative w-full p-2 sm:p-4 md:p-6 lg:p-8 space-y-6">
171
181
  <ContentIndexHeader messages={messages} syllabus={syllabus} />
172
182
  <div
173
183
  ref={containerRef}
174
- className=" space-y-3 overflow-y-auto max-h-[80vh] pr-2 scrollbar-hide relative pb-5"
184
+ className="space-y-3 overflow-y-auto max-h-[78vh] pr-2 scrollbar-hide relative pb-20"
175
185
  >
176
186
  {isThinking ? (
177
- <Loader text="Thinking..." minheight="min-h-[70vh]" />
187
+ <Loader text="Thinking..." minheight="min-h-[69vh]" />
178
188
  ) : (
179
189
  <>
180
190
  {syllabus.lessons.map((lesson, index) => (
181
- <div key={lesson.id}>
191
+ <div key={lesson.uid + index}>
182
192
  <LessonItem
183
- key={lesson.id + index + lesson.title}
193
+ key={lesson.uid}
184
194
  lesson={lesson}
185
195
  onChange={handleChange}
186
196
  onRemove={() => handleRemove(lesson)}
187
197
  isNew={Boolean(
188
- prevLessons &&
189
- prevLessons.length > 0 &&
190
- !prevLessons.some(
191
- (l) =>
192
- l.id === lesson.id &&
193
- l.title === lesson.title &&
194
- l.type === lesson.type
198
+ previousSyllabus &&
199
+ previousSyllabus &&
200
+ previousSyllabus.lessons &&
201
+ previousSyllabus.lessons.length > 0 &&
202
+ !previousSyllabus.lessons.some(
203
+ (l) => l.uid === lesson.uid
195
204
  )
196
205
  )}
197
206
  />
@@ -207,7 +216,9 @@ export const ContentIndex = ({
207
216
  </div>
208
217
  </div>
209
218
  ))}
210
- <GenerateButton handleSubmit={handleSubmit} />
219
+ {syllabus.lessons.length > 0 && (
220
+ <GenerateButton handleSubmit={handleSubmit} />
221
+ )}
211
222
  </>
212
223
  )}
213
224
  </div>
@@ -215,7 +226,7 @@ export const ContentIndex = ({
215
226
  {showScrollHint && !isThinking && (
216
227
  <div className="pointer-events-none relative">
217
228
  <div className="absolute bottom-0 left-0 w-full h-60 bg-gradient-to-t from-white to-transparent z-10" />
218
- <div className="absolute bottom-3 left-0 w-full flex justify-center z-20">
229
+ <div className="absolute bottom-10 left-0 w-full flex justify-center z-20">
219
230
  <button
220
231
  style={{ color: "#0084FF" }}
221
232
  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
 
@@ -24,7 +25,7 @@ export const Sidebar = ({
24
25
  <>
25
26
  {!isOpen && (
26
27
  <button
27
- className="fixed top-2 left-2 z-50 lg:hidden bg-white p-1 rounded shadow-md cursor-pointer p-2"
28
+ className="fixed bottom-5 left-2 z-50 lg:hidden p-1 shadow-md cursor-pointer p-2 w-15 h-15 flex items-center justify-center bg-blue-600 rounded-[50%] fluid-svg"
28
29
  onClick={() => setIsOpen(true)}
29
30
  >
30
31
  {SVGS.rigoSoftBlue}
@@ -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>