@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.
- package/README.md +13 -13
- package/lib/creatorDist/assets/{index-WzdhAujs.js → index-D15TgYvM.js} +38777 -31507
- package/lib/creatorDist/assets/{index-BJ2JJzVC.css → index-ldEC0yWM.css} +148 -41
- 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 +6 -6
- package/src/creator/src/assets/svgs.tsx +18 -0
- 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 +10 -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 +53 -42
- package/src/creator/src/components/syllabus/Sidebar.tsx +22 -20
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +63 -30
- package/src/creator/src/index.css +47 -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-WzdhAujs.js → index-D15TgYvM.js} +38777 -31507
- package/src/creatorDist/assets/{index-BJ2JJzVC.css → index-ldEC0yWM.css} +148 -41
- 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,
|
@@ -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: (
|
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"
|
@@ -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>
|
@@ -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,
|
@@ -18,7 +20,7 @@ const ContentIndexHeader = ({
|
|
18
20
|
.length === 2
|
19
21
|
|
20
22
|
const headerText = isFirst
|
21
|
-
?
|
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-
|
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={
|
76
|
-
|
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
|
98
|
-
const
|
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
|
-
|
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 = (
|
112
|
-
|
120
|
+
const handleChange = (uid: string, newTitle: string) => {
|
121
|
+
push({
|
113
122
|
...syllabus,
|
114
123
|
lessons: syllabus.lessons.map((lesson) =>
|
115
|
-
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
|
-
|
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="
|
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-[
|
187
|
+
<Loader text="Thinking..." minheight="min-h-[69vh]" />
|
178
188
|
) : (
|
179
189
|
<>
|
180
190
|
{syllabus.lessons.map((lesson, index) => (
|
181
|
-
<div key={lesson.
|
191
|
+
<div key={lesson.uid + index}>
|
182
192
|
<LessonItem
|
183
|
-
key={lesson.
|
193
|
+
key={lesson.uid}
|
184
194
|
lesson={lesson}
|
185
195
|
onChange={handleChange}
|
186
196
|
onRemove={() => handleRemove(lesson)}
|
187
197
|
isNew={Boolean(
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
-
|
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-
|
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
|
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
|
|
@@ -24,7 +25,7 @@ export const Sidebar = ({
|
|
24
25
|
<>
|
25
26
|
{!isOpen && (
|
26
27
|
<button
|
27
|
-
className="fixed
|
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
|
-
|
96
|
-
...syllabus,
|
97
|
-
uploadedFiles: [...syllabus.uploadedFiles, ...res],
|
98
|
-
})
|
100
|
+
setUploadedFiles([...(uploadedFiles || []), ...res])
|
99
101
|
}}
|
100
102
|
/>
|
101
103
|
</div>
|