@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.
- package/README.md +13 -13
- package/lib/creatorDist/assets/{index-CwPh6b6M.js → index-BuYPhmTd.js} +38848 -31631
- package/lib/creatorDist/assets/{index-DSLkFVbA.css → index-ldEC0yWM.css} +125 -46
- package/lib/creatorDist/index.html +2 -2
- package/lib/utils/api.js +0 -8
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/creator/package-lock.json +1229 -61
- package/src/creator/package.json +1 -0
- package/src/creator/src/App.tsx +12 -6
- package/src/creator/src/components/FileCard.tsx +22 -0
- package/src/creator/src/components/LessonItem.tsx +20 -9
- package/src/creator/src/components/Login.tsx +6 -24
- package/src/creator/src/components/MarkdownRenderer.tsx +11 -0
- package/src/creator/src/components/Message.tsx +4 -2
- package/src/creator/src/components/syllabus/ContentIndex.tsx +46 -64
- package/src/creator/src/components/syllabus/Sidebar.tsx +21 -19
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +62 -33
- package/src/creator/src/index.css +40 -0
- package/src/creator/src/utils/creatorUtils.ts +5 -1
- package/src/creator/src/utils/lib.ts +20 -10
- package/src/creator/src/utils/store.ts +31 -22
- package/src/creatorDist/assets/{index-CwPh6b6M.js → index-BuYPhmTd.js} +38848 -31631
- package/src/creatorDist/assets/{index-DSLkFVbA.css → index-ldEC0yWM.css} +125 -46
- package/src/creatorDist/index.html +2 -2
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +344 -344
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +0 -9
package/src/creator/package.json
CHANGED
package/src/creator/src/App.tsx
CHANGED
@@ -17,13 +17,12 @@ import FileUploader from "./components/FileUploader"
|
|
17
17
|
function App() {
|
18
18
|
const navigate = useNavigate()
|
19
19
|
|
20
|
-
const { formState, setFormState,
|
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
|
-
|
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={
|
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: (
|
16
|
-
onRemove: (
|
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
|
-
|
51
|
-
|
52
|
-
onBlur={() =>
|
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={() =>
|
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(
|
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 {
|
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 {
|
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>
|
@@ -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
|
-
<
|
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-
|
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
|
-
{
|
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
|
-
|
77
|
-
|
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={
|
85
|
-
|
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
|
124
|
-
const
|
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
|
-
|
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 = (
|
138
|
-
|
121
|
+
const handleChange = (uid: string, newTitle: string) => {
|
122
|
+
push({
|
139
123
|
...syllabus,
|
140
124
|
lessons: syllabus.lessons.map((lesson) =>
|
141
|
-
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
|
-
|
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="
|
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-[
|
188
|
+
<Loader text="Thinking..." minheight="min-h-[69vh]" />
|
204
189
|
) : (
|
205
190
|
<>
|
206
191
|
{syllabus.lessons.map((lesson, index) => (
|
207
|
-
<div key={lesson.
|
192
|
+
<div key={lesson.uid + index}>
|
208
193
|
<LessonItem
|
209
|
-
key={lesson.
|
194
|
+
key={lesson.uid}
|
210
195
|
lesson={lesson}
|
211
196
|
onChange={handleChange}
|
212
197
|
onRemove={() => handleRemove(lesson)}
|
213
198
|
isNew={Boolean(
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
237
|
-
handleSubmit={handleSubmit}
|
238
|
-
|
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-
|
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
|
19
|
-
const
|
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
|
-
|
96
|
-
...syllabus,
|
97
|
-
uploadedFiles: [...syllabus.uploadedFiles, ...res],
|
98
|
-
})
|
100
|
+
setUploadedFiles([...(uploadedFiles || []), ...res])
|
99
101
|
}}
|
100
102
|
/>
|
101
103
|
</div>
|