@learnpack/learnpack 5.0.66 → 5.0.68
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/commands/serve.js +15 -15
- package/lib/creatorDist/assets/index-B01XTAAq.js +75129 -0
- package/{src/creatorDist/assets/index-tt9JBVY0.css → lib/creatorDist/assets/index-t6ma_gVm.css} +118 -20
- package/lib/creatorDist/assets/pdf.worker-DSVOJ9H9.js +56037 -0
- package/lib/creatorDist/index.html +10 -5
- package/lib/creatorDist/logo-192 copy.png +0 -0
- package/lib/creatorDist/logo.png +0 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/serve.ts +24 -22
- package/src/creator/index.html +8 -3
- package/src/creator/package-lock.json +394 -0
- package/src/creator/package.json +3 -0
- package/src/creator/public/logo-192 copy.png +0 -0
- package/src/creator/public/logo.png +0 -0
- package/src/creator/src/App.tsx +30 -4
- package/src/creator/src/assets/svgs.tsx +138 -0
- package/src/creator/src/components/ConsumablesManager.tsx +21 -0
- package/src/creator/src/components/FileUploader.tsx +91 -0
- package/src/creator/src/components/LessonItem.tsx +70 -0
- package/src/creator/src/components/Loader.tsx +64 -19
- package/src/creator/src/components/Login.tsx +6 -12
- package/src/creator/src/components/Message.tsx +28 -0
- package/src/creator/src/components/RigoLoader.tsx +14 -0
- package/src/creator/src/components/StepWizard.tsx +1 -0
- package/src/creator/src/components/SyllabusEditor.tsx +135 -266
- package/src/creator/src/index.css +64 -0
- package/src/creator/src/utils/creatorUtils.ts +136 -0
- package/src/creator/src/utils/eventBus.ts +2 -0
- package/src/creator/src/utils/lib.ts +86 -0
- package/src/creator/src/utils/store.ts +25 -1
- package/src/creatorDist/assets/index-B01XTAAq.js +75129 -0
- package/{lib/creatorDist/assets/index-tt9JBVY0.css → src/creatorDist/assets/index-t6ma_gVm.css} +118 -20
- package/src/creatorDist/assets/pdf.worker-DSVOJ9H9.js +56037 -0
- package/src/creatorDist/index.html +10 -5
- package/src/creatorDist/logo-192 copy.png +0 -0
- package/src/creatorDist/logo.png +0 -0
- package/src/ui/_app/app.css +1 -0
- package/src/ui/_app/app.js +3027 -0
- package/src/ui/_app/favicon.ico +0 -0
- package/src/ui/_app/index.html +109 -0
- package/src/ui/_app/index.html.backup +91 -0
- package/src/ui/_app/learnpack.svg +7 -0
- package/src/ui/_app/logo-192.png +0 -0
- package/src/ui/_app/logo-512.png +0 -0
- package/src/ui/_app/logo.png +0 -0
- package/src/ui/_app/manifest.webmanifest +21 -0
- package/src/ui/_app/sw.js +30 -0
- package/src/ui/app.tar.gz +0 -0
- package/lib/creatorDist/assets/index-CrrS9sA3.js +0 -23718
- package/src/creator/src/App.css +0 -42
- package/src/creatorDist/assets/index-CrrS9sA3.js +0 -23718
@@ -1,159 +1,55 @@
|
|
1
1
|
import React, { useRef, useState } from "react"
|
2
2
|
import { SVGS } from "../assets/svgs"
|
3
3
|
import { useShallow } from "zustand/react/shallow"
|
4
|
-
import useStore
|
4
|
+
import useStore from "../utils/store"
|
5
|
+
import { interactiveCreation } from "../utils/rigo"
|
5
6
|
import {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
parseLesson,
|
8
|
+
uploadFileToBucket,
|
9
|
+
useConsumableCall,
|
10
|
+
} from "../utils/lib"
|
11
|
+
import {
|
12
|
+
createLearnJson,
|
13
|
+
processExercise,
|
14
|
+
slugify,
|
15
|
+
randomUUID,
|
16
|
+
} from "../utils/creatorUtils"
|
12
17
|
|
13
18
|
import Loader from "./Loader"
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
}
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
max_words: 200,
|
40
|
-
max_rewrite_attempts: 3,
|
41
|
-
max_title_length: 50,
|
42
|
-
}
|
43
|
-
async function processExercise(
|
44
|
-
rigoToken: string,
|
45
|
-
steps: Lesson[],
|
46
|
-
packageContext: string,
|
47
|
-
exercise: Lesson,
|
48
|
-
exercisesDir: string
|
49
|
-
): Promise<string> {
|
50
|
-
// const tid = toast.loading("Generating lesson...")
|
51
|
-
const readme = await readmeCreator(rigoToken, {
|
52
|
-
title: `${exercise.id} - ${exercise.title}`,
|
53
|
-
output_lang: "en",
|
54
|
-
list_of_exercises: JSON.stringify(steps),
|
55
|
-
tutorial_description: packageContext,
|
56
|
-
lesson_description: exercise.description,
|
57
|
-
kind: exercise.type.toLowerCase(),
|
58
|
-
})
|
59
|
-
|
60
|
-
console.log(exercise.id, "ID")
|
61
|
-
|
62
|
-
const duration = exercise.duration
|
63
|
-
let attempts = 0
|
64
|
-
let readability = checkReadability(readme.parsed.content, 200, duration || 1)
|
65
|
-
|
66
|
-
while (
|
67
|
-
readability.fkglResult.fkgl > PARAMS.max_fkgl &&
|
68
|
-
attempts < PARAMS.max_rewrite_attempts
|
69
|
-
) {
|
70
|
-
// Console.warning(
|
71
|
-
// `The lesson ${exTitle} has as readability score of ${
|
72
|
-
// readability.fkglResult.fkgl
|
73
|
-
// } . It exceeds the maximum of words per minute. Rewriting it... (Attempt ${
|
74
|
-
// attempts + 1
|
75
|
-
// })`
|
76
|
-
// )
|
77
|
-
|
78
|
-
// eslint-disable-next-line
|
79
|
-
const reducedReadme = await makeReadmeReadable(rigoToken, {
|
80
|
-
lesson: readability.body,
|
81
|
-
number_of_words: readability.minutes.toString(),
|
82
|
-
expected_number_words: PARAMS.max_words.toString(),
|
83
|
-
fkgl_results: JSON.stringify(readability.fkglResult),
|
84
|
-
expected_grade_level: PARAMS.expected_grade_level,
|
85
|
-
})
|
86
|
-
|
87
|
-
// console.log("REDUCED README START", reducedReadme, "REDUCED README END")
|
88
|
-
|
89
|
-
if (!reducedReadme) break
|
90
|
-
|
91
|
-
readability = checkReadability(
|
92
|
-
reducedReadme.parsed.content,
|
93
|
-
PARAMS.max_words,
|
94
|
-
duration || 1
|
95
|
-
)
|
96
|
-
|
97
|
-
attempts++
|
98
|
-
}
|
99
|
-
|
100
|
-
// Console.success(
|
101
|
-
// `After ${attempts} attempts, the lesson ${exTitle} has a readability score of ${
|
102
|
-
// readability.fkglResult.fkgl
|
103
|
-
// } using FKGL. And it has ${readability.minutes.toFixed(
|
104
|
-
// 2
|
105
|
-
// )} minutes of reading time.`
|
106
|
-
// )
|
107
|
-
|
108
|
-
const readmeFilename = "README.md"
|
109
|
-
await uploadFileToBucket(
|
110
|
-
readability.newMarkdown,
|
111
|
-
`${exercisesDir}/${slugify(
|
112
|
-
exercise.id + "-" + exercise.title
|
113
|
-
)}/${readmeFilename}`
|
19
|
+
import { Message, TMessage } from "./Message"
|
20
|
+
import { LessonItem, Lesson } from "./LessonItem"
|
21
|
+
import FileUploader from "./FileUploader"
|
22
|
+
import { ConsumablesManager } from "./ConsumablesManager"
|
23
|
+
import toast from "react-hot-toast"
|
24
|
+
|
25
|
+
const GenerateButton = ({ handleSubmit }: { handleSubmit: () => void }) => {
|
26
|
+
const auth = useStore((state) => state.auth)
|
27
|
+
return (
|
28
|
+
<div className="flex justify-end mt-6">
|
29
|
+
<button
|
30
|
+
onClick={async () => {
|
31
|
+
const success = await useConsumableCall(auth.bcToken, "ai-generation")
|
32
|
+
if (success) {
|
33
|
+
handleSubmit()
|
34
|
+
} else {
|
35
|
+
toast.error("You don't have enough credits to generate a course.")
|
36
|
+
}
|
37
|
+
}}
|
38
|
+
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 cursor-pointer flex items-center gap-2"
|
39
|
+
>
|
40
|
+
<span>I'm ready. Create the course for me!</span>
|
41
|
+
{SVGS.rigoSoftBlue}
|
42
|
+
</button>
|
43
|
+
</div>
|
114
44
|
)
|
115
|
-
|
116
|
-
if (exercise.type.toLowerCase() === "code") {
|
117
|
-
// const codeFile = await createCodeFile(rigoToken, {
|
118
|
-
// readme: readability.newMarkdown,
|
119
|
-
// tutorial_info: packageContext,
|
120
|
-
// })
|
121
|
-
// fs.writeFileSync(
|
122
|
-
// path.join(
|
123
|
-
// exerciseDir,
|
124
|
-
// `app.${codeFile.parsed.extension.replace(".", "")}`
|
125
|
-
// ),
|
126
|
-
// codeFile.parsed.content
|
127
|
-
// )
|
128
|
-
}
|
129
|
-
|
130
|
-
// toast.success("Lesson generated successfully", { id: tid })
|
131
|
-
return readability.newMarkdown
|
132
|
-
}
|
133
|
-
|
134
|
-
const randomUUID = () => {
|
135
|
-
return Math.random().toString(36).substring(2, 15)
|
136
|
-
}
|
137
|
-
|
138
|
-
// types.ts
|
139
|
-
export interface Lesson {
|
140
|
-
id: string
|
141
|
-
title: string
|
142
|
-
type: "READ" | "CODE" | "QUIZ"
|
143
|
-
description: string
|
144
|
-
duration?: number
|
145
|
-
}
|
146
|
-
|
147
|
-
type TMessage = {
|
148
|
-
type: "user" | "assistant"
|
149
|
-
content: string
|
150
45
|
}
|
151
46
|
|
152
47
|
const SyllabusEditor: React.FC = () => {
|
153
|
-
const inputRef = useRef<
|
48
|
+
const inputRef = useRef<HTMLTextAreaElement>(null)
|
154
49
|
// const navigate = useNavigate()
|
155
50
|
const [messages, setMessages] = useState<TMessage[]>([])
|
156
51
|
const [isGenerating, setIsGenerating] = useState(false)
|
52
|
+
const prevLessons = useRef<Lesson[]>([])
|
157
53
|
const { syllabus, setSyllabus, auth } = useStore(
|
158
54
|
useShallow((state) => ({
|
159
55
|
syllabus: state.syllabus,
|
@@ -195,7 +91,12 @@ const SyllabusEditor: React.FC = () => {
|
|
195
91
|
}
|
196
92
|
|
197
93
|
const sendPrompt = async (prompt: string) => {
|
198
|
-
setMessages([
|
94
|
+
setMessages([
|
95
|
+
...messages,
|
96
|
+
{ type: "user", content: prompt },
|
97
|
+
{ type: "assistant", content: "" },
|
98
|
+
])
|
99
|
+
prevLessons.current = syllabus.lessons
|
199
100
|
const res = await interactiveCreation(auth.rigoToken, {
|
200
101
|
courseInfo: JSON.stringify(syllabus),
|
201
102
|
prevInteractions:
|
@@ -204,7 +105,9 @@ const SyllabusEditor: React.FC = () => {
|
|
204
105
|
.join("\n") + `\nUSER: ${prompt}`,
|
205
106
|
})
|
206
107
|
console.log(res, "RES")
|
207
|
-
const lessons = res.parsed.listOfSteps.map((step: any) =>
|
108
|
+
const lessons: Lesson[] = res.parsed.listOfSteps.map((step: any) =>
|
109
|
+
parseLesson(step)
|
110
|
+
)
|
208
111
|
setSyllabus({
|
209
112
|
...syllabus,
|
210
113
|
lessons: lessons,
|
@@ -213,14 +116,16 @@ const SyllabusEditor: React.FC = () => {
|
|
213
116
|
title: res.parsed.title || syllabus.courseInfo.title,
|
214
117
|
},
|
215
118
|
})
|
216
|
-
setMessages((prev) =>
|
217
|
-
...prev
|
218
|
-
|
219
|
-
|
119
|
+
setMessages((prev) => {
|
120
|
+
const newMessages = [...prev]
|
121
|
+
newMessages[newMessages.length - 1].content = res.parsed.aiMessage
|
122
|
+
return newMessages
|
123
|
+
})
|
220
124
|
}
|
221
125
|
|
222
126
|
const handleSubmit = async () => {
|
223
127
|
setIsGenerating(true)
|
128
|
+
|
224
129
|
const lessonsPromises = syllabus.lessons.map((lesson) =>
|
225
130
|
processExercise(
|
226
131
|
auth.rigoToken,
|
@@ -250,29 +155,24 @@ const SyllabusEditor: React.FC = () => {
|
|
250
155
|
|
251
156
|
return isGenerating ? (
|
252
157
|
<Loader
|
253
|
-
|
158
|
+
listeningTo="course-generation"
|
159
|
+
icon={SVGS.rigoSoftBlue}
|
160
|
+
initialBuffer="🚀 Starting course generation..."
|
254
161
|
text="Learnpack is setting up your tutorial.
|
255
162
|
It may take a moment..."
|
256
163
|
/>
|
257
164
|
) : (
|
258
165
|
<div className="flex w-full bg-white rounded-md shadow-md overflow-hidden h-screen ">
|
166
|
+
<ConsumablesManager />
|
259
167
|
{/* Sidebar */}
|
260
|
-
<div className="w-1/3 p-6 text-sm text-gray-700 border-r bg-learnpack-blue h-screen overflow-y-auto scrollbar-hide">
|
261
|
-
<div className="p-4
|
168
|
+
<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">
|
169
|
+
<div className="p-4 rounded-md bg-white shadow-sm">
|
262
170
|
<p>We generated this syllabus based on your answers.</p>
|
263
171
|
<p className="mt-2">If you're satisfied, type "OK" in the chat.</p>
|
264
172
|
<p className="mt-2">If not, use the chat to give more context.</p>
|
265
|
-
<p className="mt-2 text-gray-500">We recommend:</p>
|
266
|
-
<ul className="mt-2 space-y-1 text-blue-500">
|
267
|
-
<li>
|
268
|
-
<a href="#">Make it have more readings</a>
|
269
|
-
</li>
|
270
|
-
<li>
|
271
|
-
<a href="#">Make it shorter</a>
|
272
|
-
</li>
|
273
|
-
</ul>
|
274
173
|
</div>
|
275
|
-
|
174
|
+
|
175
|
+
<div className="mt-10 space-y-2 pb-16">
|
276
176
|
{messages.map((message, index) => (
|
277
177
|
<Message
|
278
178
|
key={index}
|
@@ -281,29 +181,76 @@ It may take a moment..."
|
|
281
181
|
/>
|
282
182
|
))}
|
283
183
|
</div>
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
184
|
+
|
185
|
+
<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 ">
|
186
|
+
<textarea
|
187
|
+
ref={inputRef}
|
188
|
+
style={{ resize: "none" }}
|
189
|
+
className="w-full h-full p-2"
|
190
|
+
placeholder="How can Learnpack help you?"
|
191
|
+
autoFocus
|
192
|
+
onKeyUp={(e) => {
|
193
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
194
|
+
e.preventDefault()
|
195
|
+
sendPrompt(inputRef.current?.value || "")
|
196
|
+
inputRef.current!.value = ""
|
197
|
+
}
|
198
|
+
}}
|
199
|
+
/>
|
200
|
+
<div className="absolute bottom-2 right-2 flex gap-1 items-center">
|
201
|
+
<div className="relative inline-block">
|
202
|
+
{syllabus.uploadedFiles?.length > 0 && (
|
203
|
+
<span
|
204
|
+
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"
|
205
|
+
title="Remove uploaded files"
|
206
|
+
onClick={() => {
|
207
|
+
setSyllabus({
|
208
|
+
...syllabus,
|
209
|
+
uploadedFiles: [],
|
210
|
+
})
|
211
|
+
}}
|
212
|
+
>
|
213
|
+
{syllabus.uploadedFiles?.length}
|
214
|
+
</span>
|
215
|
+
)}
|
216
|
+
<FileUploader
|
217
|
+
onResult={(res) => {
|
218
|
+
setSyllabus({
|
219
|
+
...syllabus,
|
220
|
+
uploadedFiles: [...syllabus.uploadedFiles, ...res],
|
221
|
+
})
|
222
|
+
}}
|
223
|
+
/>
|
224
|
+
</div>
|
225
|
+
|
226
|
+
<button
|
227
|
+
className="cursor-pointer blue-on-hover flex items-center justify-center w-6 h-6"
|
228
|
+
onClick={() => sendPrompt(inputRef.current?.value || "")}
|
229
|
+
>
|
230
|
+
{SVGS.send}
|
231
|
+
</button>
|
232
|
+
</div>
|
233
|
+
</div>
|
295
234
|
</div>
|
296
235
|
|
297
236
|
{/* Editor */}
|
298
237
|
<div className="w-2/3 p-8 space-y-6">
|
299
238
|
<div>
|
300
239
|
<h2 className="text-lg font-semibold">
|
301
|
-
|
240
|
+
{messages.filter(
|
241
|
+
(m) => m.type === "assistant" && m.content.length > 0
|
242
|
+
).length === 0
|
243
|
+
? "I've created a detailed structure for your course."
|
244
|
+
: "I've updated the structure based on your feedback."}
|
302
245
|
</h2>
|
303
246
|
<p className="text-sm text-gray-600">
|
304
|
-
|
305
|
-
|
306
|
-
|
247
|
+
{messages.filter(
|
248
|
+
(m) => m.type === "assistant" && m.content.length > 0
|
249
|
+
).length === 0
|
250
|
+
? `It includes a mix of reading, coding exercises, and quizzes. Give
|
251
|
+
it a look and let me know if it aligns with your expectations or if
|
252
|
+
there are any changes you'd like to make.`
|
253
|
+
: "Based on your input, here is the new syllabus, updates are highlighted in yellow"}
|
307
254
|
</p>
|
308
255
|
<h3 className="text-sm text-gray-600 mt-2 font-bold">
|
309
256
|
{syllabus.courseInfo.title}
|
@@ -320,12 +267,21 @@ It may take a moment..."
|
|
320
267
|
lesson={lesson}
|
321
268
|
onChange={handleChange}
|
322
269
|
onRemove={handleRemove}
|
270
|
+
isNew={
|
271
|
+
prevLessons.current.length > 0 &&
|
272
|
+
!prevLessons.current.some(
|
273
|
+
(l) =>
|
274
|
+
l.id === lesson.id &&
|
275
|
+
l.title === lesson.title &&
|
276
|
+
l.type === lesson.type
|
277
|
+
)
|
278
|
+
}
|
323
279
|
/>
|
324
280
|
<div className="relative h-6">
|
325
281
|
<div className="absolute left-1/2 -translate-x-1/2 -top-3">
|
326
282
|
<button
|
327
283
|
onClick={() => addLessonAfter(index, lesson.id)}
|
328
|
-
className="w-6 h-6 flex items-center justify-center bg-blue-100 text-blue-600 rounded hover:bg-blue-200 shadow-sm text-sm font-semibold"
|
284
|
+
className="w-6 h-6 flex items-center justify-center bg-blue-100 text-blue-600 rounded hover:bg-blue-200 shadow-sm text-sm font-semibold cursor-pointer"
|
329
285
|
>
|
330
286
|
+
|
331
287
|
</button>
|
@@ -335,97 +291,10 @@ It may take a moment..."
|
|
335
291
|
))}
|
336
292
|
</div>
|
337
293
|
|
338
|
-
<
|
339
|
-
<button className="text-blue-600 hover:underline text-sm">
|
340
|
-
Skip
|
341
|
-
</button>
|
342
|
-
<button
|
343
|
-
onClick={handleSubmit}
|
344
|
-
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700"
|
345
|
-
>
|
346
|
-
Next
|
347
|
-
</button>
|
348
|
-
</div>
|
294
|
+
<GenerateButton handleSubmit={handleSubmit} />
|
349
295
|
</div>
|
350
296
|
</div>
|
351
297
|
)
|
352
298
|
}
|
353
299
|
|
354
300
|
export default SyllabusEditor
|
355
|
-
|
356
|
-
interface LessonItemProps {
|
357
|
-
lesson: Lesson
|
358
|
-
index: string
|
359
|
-
onChange: (id: string, newTitle: string) => void
|
360
|
-
onRemove: (id: string) => void
|
361
|
-
}
|
362
|
-
|
363
|
-
const LessonItem: React.FC<LessonItemProps> = ({
|
364
|
-
lesson,
|
365
|
-
index,
|
366
|
-
onChange,
|
367
|
-
onRemove,
|
368
|
-
}) => {
|
369
|
-
const [isEditing, setIsEditing] = useState(false)
|
370
|
-
|
371
|
-
return (
|
372
|
-
<div className="flex items-center space-x-2 border border-gray-200 rounded-md p-3">
|
373
|
-
<span className="index-circle">{index}</span>
|
374
|
-
<span className="text-gray-500 text-sm">
|
375
|
-
{lesson.type[0] + lesson.type.slice(1).toLowerCase()} ●
|
376
|
-
</span>
|
377
|
-
|
378
|
-
{isEditing ? (
|
379
|
-
<input
|
380
|
-
value={lesson.title}
|
381
|
-
onChange={(e) => onChange(lesson.id, e.target.value)}
|
382
|
-
onBlur={() => setIsEditing(false)}
|
383
|
-
autoFocus
|
384
|
-
className="flex-1 bg-white border border-gray-300 rounded-md px-2 py-1 text-sm"
|
385
|
-
/>
|
386
|
-
) : (
|
387
|
-
<span className="flex-1 text-sm text-gray-800">{lesson.title}</span>
|
388
|
-
)}
|
389
|
-
|
390
|
-
<span className="text-sm text-gray-600 bg-blue-100 px-2 py-1 rounded-full">
|
391
|
-
{lesson.duration} min
|
392
|
-
</span>
|
393
|
-
|
394
|
-
<button
|
395
|
-
onClick={() => setIsEditing(!isEditing)}
|
396
|
-
className="text-gray-500 hover:text-blue-500"
|
397
|
-
>
|
398
|
-
{SVGS.pen}
|
399
|
-
</button>
|
400
|
-
<button
|
401
|
-
onClick={() => onRemove(lesson.id)}
|
402
|
-
className="text-red-500 hover:text-red-700"
|
403
|
-
>
|
404
|
-
{SVGS.trash}
|
405
|
-
</button>
|
406
|
-
</div>
|
407
|
-
)
|
408
|
-
}
|
409
|
-
|
410
|
-
const Message: React.FC<TMessage> = ({ type, content }) => {
|
411
|
-
const isAI = type === "assistant"
|
412
|
-
|
413
|
-
return (
|
414
|
-
<div
|
415
|
-
className={`flex items-start space-x-2 p-3 rounded-md border ${
|
416
|
-
isAI
|
417
|
-
? "bg-gray-50 border-gray-300 text-gray-800"
|
418
|
-
: "bg-blue-50 border-blue-200 text-blue-900"
|
419
|
-
}`}
|
420
|
-
>
|
421
|
-
<span
|
422
|
-
className={`text-xs font-bold px-2 py-1 rounded ${
|
423
|
-
isAI ? "bg-gray-200 text-gray-800" : "bg-blue-200 text-blue-900"
|
424
|
-
}`}
|
425
|
-
>
|
426
|
-
{isAI ? "AI" : "YOU"}
|
427
|
-
</span>
|
428
|
-
<p className="text-sm leading-relaxed">{content}</p>
|
429
|
-
</div>
|
430
|
-
)
|
431
|
-
}
|
@@ -17,6 +17,7 @@
|
|
17
17
|
--soft-blue: #f3fafd;
|
18
18
|
--gray-text: #6883b4;
|
19
19
|
--learnpack-blue: #02a9ea;
|
20
|
+
--learnpack-light-blue: #c7f3fd;
|
20
21
|
}
|
21
22
|
|
22
23
|
a {
|
@@ -66,3 +67,66 @@ h1 {
|
|
66
67
|
.scrollbar-hide {
|
67
68
|
scrollbar-width: none !important;
|
68
69
|
}
|
70
|
+
|
71
|
+
.loader {
|
72
|
+
display: flex;
|
73
|
+
/* flex-direction: column; */
|
74
|
+
align-items: center;
|
75
|
+
gap: 10px;
|
76
|
+
justify-content: center;
|
77
|
+
animation: glowing 1000ms linear infinite;
|
78
|
+
}
|
79
|
+
@keyframes spin {
|
80
|
+
from {
|
81
|
+
transform: rotate(0deg);
|
82
|
+
}
|
83
|
+
to {
|
84
|
+
transform: rotate(360deg);
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
.loader-icon {
|
89
|
+
width: 40px;
|
90
|
+
height: 40px;
|
91
|
+
|
92
|
+
border: 2px solid var(--loader-color);
|
93
|
+
border-top-color: transparent;
|
94
|
+
position: relative;
|
95
|
+
display: flex;
|
96
|
+
align-items: center;
|
97
|
+
justify-content: center;
|
98
|
+
}
|
99
|
+
.loader-icon::after {
|
100
|
+
content: "";
|
101
|
+
display: block;
|
102
|
+
width: 100%;
|
103
|
+
top: 0;
|
104
|
+
left: 0;
|
105
|
+
position: absolute;
|
106
|
+
height: 100%;
|
107
|
+
border: 2px solid var(--gray-text);
|
108
|
+
border-top: 2px solid var(--learnpack-blue);
|
109
|
+
/* background-color: red; */
|
110
|
+
|
111
|
+
border-radius: 50%;
|
112
|
+
animation: spin 2s linear infinite;
|
113
|
+
}
|
114
|
+
|
115
|
+
@keyframes glowing {
|
116
|
+
0% {
|
117
|
+
opacity: 0.6;
|
118
|
+
}
|
119
|
+
100% {
|
120
|
+
opacity: 1;
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
.blue-on-hover:hover {
|
125
|
+
svg {
|
126
|
+
fill: var(--learnpack-blue);
|
127
|
+
|
128
|
+
path {
|
129
|
+
fill: var(--learnpack-blue);
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|