@learnpack/learnpack 5.0.316 → 5.0.318
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/lib/commands/serve.js +77 -26
- package/lib/creatorDist/assets/{index-D4SYZg0r.css → index-CjddKHB_.css} +12 -0
- package/lib/creatorDist/assets/{index-DxgQXudf.js → index-XZDcEWl9.js} +38343 -34250
- package/lib/creatorDist/index.html +2 -2
- package/package.json +1 -1
- package/src/commands/serve.ts +3267 -3204
- package/src/creator/components.json +18 -0
- package/src/creator/package-lock.json +470 -1
- package/src/creator/package.json +3 -0
- package/src/creator/src/App.tsx +0 -1
- package/src/creator/src/assets/svgs.tsx +0 -1
- package/src/creator/src/components/FileCard.tsx +15 -3
- package/src/creator/src/components/FileUploader.tsx +41 -23
- package/src/creator/src/components/LanguageDetectionModal.tsx +1 -1
- package/src/creator/src/components/LessonItem.tsx +80 -49
- package/src/creator/src/components/LinkUploader.tsx +55 -8
- package/src/creator/src/components/Source.tsx +15 -6
- package/src/creator/src/components/syllabus/ContentIndex.tsx +19 -7
- package/src/creator/src/components/syllabus/Sidebar.tsx +56 -48
- package/src/creator/src/components/ui/tooltip.tsx +31 -0
- package/src/creator/src/lib/utils.ts +6 -0
- package/src/creator/src/locales/en.json +24 -1
- package/src/creator/src/locales/es.json +24 -1
- package/src/creator/src/main.tsx +11 -7
- package/src/creator/src/utils/rigo.ts +85 -85
- package/src/creator/tsconfig.app.json +6 -0
- package/src/creator/vite.config.ts +10 -4
- package/src/creatorDist/assets/{index-D4SYZg0r.css → index-CjddKHB_.css} +12 -0
- package/src/creatorDist/assets/{index-DxgQXudf.js → index-XZDcEWl9.js} +38343 -34250
- package/src/creatorDist/index.html +2 -2
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +529 -529
- package/src/ui/_app/learnpack.svg +7 -7
- package/src/ui/_app/sw.js +59 -59
- package/src/ui/app.tar.gz +0 -0
|
@@ -7,6 +7,11 @@ import CreatorSocket from "../utils/socket"
|
|
|
7
7
|
import { DEV_MODE, RIGOBOT_HOST } from "../utils/constants"
|
|
8
8
|
import axios from "axios"
|
|
9
9
|
import { useTranslation } from "react-i18next"
|
|
10
|
+
import {
|
|
11
|
+
Tooltip,
|
|
12
|
+
TooltipContent,
|
|
13
|
+
TooltipTrigger,
|
|
14
|
+
} from "@/components/ui/tooltip"
|
|
10
15
|
|
|
11
16
|
const socketClient = new CreatorSocket("")
|
|
12
17
|
|
|
@@ -80,17 +85,18 @@ const UploadedFileCard = ({ idx, file }: { idx: number; file: ParsedFile }) => {
|
|
|
80
85
|
}, [])
|
|
81
86
|
|
|
82
87
|
return (
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
<Tooltip>
|
|
89
|
+
<TooltipTrigger asChild>
|
|
90
|
+
<div
|
|
91
|
+
className={
|
|
92
|
+
"p-3 rounded-md shadow-sm text-sm text-gray-800 text-left flex items-center gap-2" +
|
|
93
|
+
(file.status === "PROCESSING"
|
|
94
|
+
? " bg-gray-100"
|
|
95
|
+
: file.status === "ERROR"
|
|
96
|
+
? " bg-red-100"
|
|
97
|
+
: " bg-white")
|
|
98
|
+
}
|
|
99
|
+
>
|
|
94
100
|
{file.status === "PROCESSING" && (
|
|
95
101
|
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
|
96
102
|
)}
|
|
@@ -113,7 +119,12 @@ const UploadedFileCard = ({ idx, file }: { idx: number; file: ParsedFile }) => {
|
|
|
113
119
|
>
|
|
114
120
|
{SVGS.trash}
|
|
115
121
|
</button>
|
|
116
|
-
|
|
122
|
+
</div>
|
|
123
|
+
</TooltipTrigger>
|
|
124
|
+
<TooltipContent>
|
|
125
|
+
<p>{file.name}</p>
|
|
126
|
+
</TooltipContent>
|
|
127
|
+
</Tooltip>
|
|
117
128
|
)
|
|
118
129
|
}
|
|
119
130
|
|
|
@@ -256,17 +267,24 @@ const FileUploader: React.FC<FileUploaderProps> = ({
|
|
|
256
267
|
|
|
257
268
|
{styledAs === "button" && (
|
|
258
269
|
<div className="flex items-center justify-end gap-2 w-100">
|
|
259
|
-
<
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
<Tooltip>
|
|
271
|
+
<TooltipTrigger asChild>
|
|
272
|
+
<button
|
|
273
|
+
type="button"
|
|
274
|
+
className="cursor-pointer blue-on-hover flex items-center justify-center w-6 h-6"
|
|
275
|
+
onClick={() => inputRef.current?.click()}
|
|
276
|
+
>
|
|
277
|
+
{isLoading ? (
|
|
278
|
+
<div className="loader w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
|
279
|
+
) : (
|
|
280
|
+
SVGS.clip
|
|
281
|
+
)}
|
|
282
|
+
</button>
|
|
283
|
+
</TooltipTrigger>
|
|
284
|
+
<TooltipContent>
|
|
285
|
+
<p>{t("tooltips.attachFiles")}</p>
|
|
286
|
+
</TooltipContent>
|
|
287
|
+
</Tooltip>
|
|
270
288
|
</div>
|
|
271
289
|
)}
|
|
272
290
|
|
|
@@ -37,7 +37,7 @@ export default function LanguageDetectionModal({
|
|
|
37
37
|
onClick={onStay}
|
|
38
38
|
className="flex-1 bg-gray-200 text-gray-800 py-2 px-4 rounded-md font-semibold hover:bg-gray-300 transition-colors"
|
|
39
39
|
>
|
|
40
|
-
{t(`languageDetection.stayIn${i18n.language.toLowerCase()}`)}
|
|
40
|
+
{t(`languageDetection.stayIn${i18n.language.toLowerCase().split("-")[0]}`)}
|
|
41
41
|
</button>
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
@@ -6,6 +6,12 @@ import { useRef } from "react"
|
|
|
6
6
|
import { SVGS } from "../assets/svgs"
|
|
7
7
|
import useStore from "../utils/store"
|
|
8
8
|
import { Icon } from "./Icon"
|
|
9
|
+
import {
|
|
10
|
+
Tooltip,
|
|
11
|
+
TooltipContent,
|
|
12
|
+
TooltipTrigger,
|
|
13
|
+
} from "@/components/ui/tooltip"
|
|
14
|
+
import { useTranslation } from "react-i18next"
|
|
9
15
|
|
|
10
16
|
export interface Lesson {
|
|
11
17
|
id: string
|
|
@@ -56,6 +62,7 @@ export const LessonItem: React.FC<LessonItemProps> = ({
|
|
|
56
62
|
const mode = useStore((state) => state.mode)
|
|
57
63
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
58
64
|
const [isEditing, setIsEditing] = useState(false)
|
|
65
|
+
const { t } = useTranslation()
|
|
59
66
|
|
|
60
67
|
return (
|
|
61
68
|
<div
|
|
@@ -96,55 +103,79 @@ export const LessonItem: React.FC<LessonItemProps> = ({
|
|
|
96
103
|
|
|
97
104
|
{mode === "teacher" && (
|
|
98
105
|
<>
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
</
|
|
106
|
+
<Tooltip>
|
|
107
|
+
<TooltipTrigger asChild>
|
|
108
|
+
<span className="inline-flex">
|
|
109
|
+
<button
|
|
110
|
+
onClick={() => onToggleLock?.(lesson.uid)}
|
|
111
|
+
className={`cursor-pointer ${
|
|
112
|
+
lesson.locked
|
|
113
|
+
? "text-yellow-600 hover:text-yellow-700"
|
|
114
|
+
: "text-gray-400 hover:text-yellow-600"
|
|
115
|
+
}`}
|
|
116
|
+
>
|
|
117
|
+
<Icon
|
|
118
|
+
name={lesson.locked ? "Lock" : "Unlock"}
|
|
119
|
+
size={16}
|
|
120
|
+
/>
|
|
121
|
+
</button>
|
|
122
|
+
</span>
|
|
123
|
+
</TooltipTrigger>
|
|
124
|
+
<TooltipContent>
|
|
125
|
+
<p>{lesson.locked ? t("tooltips.unlockLesson") : t("tooltips.lockLesson")}</p>
|
|
126
|
+
</TooltipContent>
|
|
127
|
+
</Tooltip>
|
|
128
|
+
<Tooltip>
|
|
129
|
+
<TooltipTrigger asChild>
|
|
130
|
+
<span className="inline-flex">
|
|
131
|
+
<button
|
|
132
|
+
onClick={() => {
|
|
133
|
+
if (!lesson.locked) {
|
|
134
|
+
setIsEditing(!isEditing)
|
|
135
|
+
if (inputRef.current) {
|
|
136
|
+
onChange(lesson.uid, inputRef.current.value)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}}
|
|
140
|
+
className={`${
|
|
141
|
+
lesson.locked
|
|
142
|
+
? "text-gray-300 cursor-not-allowed"
|
|
143
|
+
: "text-gray-500 hover:text-blue-500 cursor-pointer"
|
|
144
|
+
}`}
|
|
145
|
+
disabled={lesson.locked}
|
|
146
|
+
>
|
|
147
|
+
<Icon name="Edit" size={16} />
|
|
148
|
+
</button>
|
|
149
|
+
</span>
|
|
150
|
+
</TooltipTrigger>
|
|
151
|
+
<TooltipContent>
|
|
152
|
+
<p>{lesson.locked ? t("tooltips.cannotEditLocked") : t("tooltips.editLesson")}</p>
|
|
153
|
+
</TooltipContent>
|
|
154
|
+
</Tooltip>
|
|
155
|
+
<Tooltip>
|
|
156
|
+
<TooltipTrigger asChild>
|
|
157
|
+
<span className="inline-flex">
|
|
158
|
+
<button
|
|
159
|
+
onClick={() => {
|
|
160
|
+
if (!lesson.locked) {
|
|
161
|
+
onRemove()
|
|
162
|
+
}
|
|
163
|
+
}}
|
|
164
|
+
className={`${
|
|
165
|
+
lesson.locked
|
|
166
|
+
? "text-gray-300 cursor-not-allowed"
|
|
167
|
+
: "text-red-500 hover:text-red-700 cursor-pointer"
|
|
168
|
+
}`}
|
|
169
|
+
disabled={lesson.locked}
|
|
170
|
+
>
|
|
171
|
+
{SVGS.trash}
|
|
172
|
+
</button>
|
|
173
|
+
</span>
|
|
174
|
+
</TooltipTrigger>
|
|
175
|
+
<TooltipContent>
|
|
176
|
+
<p>{lesson.locked ? t("tooltips.cannotDeleteLocked") : t("tooltips.deleteLesson")}</p>
|
|
177
|
+
</TooltipContent>
|
|
178
|
+
</Tooltip>
|
|
148
179
|
</>
|
|
149
180
|
)}
|
|
150
181
|
</div>
|
|
@@ -24,24 +24,59 @@ const LinkUploader: React.FC<LinkUploaderProps> = ({ onResult }) => {
|
|
|
24
24
|
const [url, setUrl] = useState("")
|
|
25
25
|
const [loading, setLoading] = useState(false)
|
|
26
26
|
const [error, setError] = useState<string | null>(null)
|
|
27
|
+
const [show404Help, setShow404Help] = useState(false)
|
|
27
28
|
|
|
28
29
|
const handleAdd = async () => {
|
|
29
30
|
const raw = url.trim()
|
|
30
|
-
if (!raw)
|
|
31
|
+
if (!raw) {
|
|
32
|
+
return setError(t("uploader.youtube.enterUrl"))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Validate YouTube URL format
|
|
36
|
+
const ytRegex =
|
|
37
|
+
/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)[A-Za-z0-9_-]{11}/
|
|
38
|
+
if (!ytRegex.test(raw)) {
|
|
39
|
+
return setError(t("uploader.youtube.invalidUrl"))
|
|
40
|
+
}
|
|
41
|
+
|
|
31
42
|
setError(null)
|
|
32
43
|
setLoading(true)
|
|
33
44
|
|
|
34
45
|
try {
|
|
35
46
|
const key = toBase64Url(raw)
|
|
36
|
-
const
|
|
37
|
-
|
|
47
|
+
const endpoint = `/actions/fetch/${key}`
|
|
48
|
+
const resp = await fetch(endpoint)
|
|
49
|
+
|
|
50
|
+
if (!resp.ok) {
|
|
51
|
+
// Handle specific error cases
|
|
52
|
+
if (resp.status === 404) {
|
|
53
|
+
setShow404Help(true)
|
|
54
|
+
throw new Error(t("uploader.youtube.notAvailable"))
|
|
55
|
+
}
|
|
56
|
+
if (resp.status === 429) {
|
|
57
|
+
const retryData = await resp.json()
|
|
58
|
+
throw new Error(
|
|
59
|
+
retryData.error || t("uploader.youtube.tooManyRequests")
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
if (resp.status === 500) {
|
|
63
|
+
const errorData = await resp.json()
|
|
64
|
+
throw new Error(
|
|
65
|
+
errorData.error || t("uploader.youtube.serviceUnavailable")
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
throw new Error(t("uploader.youtube.fetchError"))
|
|
69
|
+
}
|
|
70
|
+
|
|
38
71
|
const data = (await resp.json()) as ParsedLink
|
|
39
72
|
|
|
40
73
|
onResult(data) // emit the parsed link immediately
|
|
41
74
|
setUrl("") // clear input
|
|
42
|
-
|
|
75
|
+
setShow404Help(false)
|
|
76
|
+
} catch (err: unknown) {
|
|
43
77
|
console.error(err)
|
|
44
|
-
|
|
78
|
+
const message = err instanceof Error ? err.message : t("uploader.youtube.fetchError")
|
|
79
|
+
setError(message)
|
|
45
80
|
} finally {
|
|
46
81
|
setLoading(false)
|
|
47
82
|
}
|
|
@@ -57,6 +92,8 @@ const LinkUploader: React.FC<LinkUploaderProps> = ({ onResult }) => {
|
|
|
57
92
|
onKeyDown={(e) => e.key === "Enter" && handleAdd()}
|
|
58
93
|
disabled={loading}
|
|
59
94
|
placeholder={t("uploader.youtube.placeholder")}
|
|
95
|
+
pattern="(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)[A-Za-z0-9_-]{11}"
|
|
96
|
+
title="Please enter a valid YouTube URL (youtube.com/watch?v=... or youtu.be/...)"
|
|
60
97
|
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-200 transition disabled:bg-gray-100"
|
|
61
98
|
/>
|
|
62
99
|
<button
|
|
@@ -96,9 +133,19 @@ const LinkUploader: React.FC<LinkUploaderProps> = ({ onResult }) => {
|
|
|
96
133
|
</div>
|
|
97
134
|
|
|
98
135
|
{error && (
|
|
99
|
-
<
|
|
100
|
-
{error}
|
|
101
|
-
|
|
136
|
+
<div className="mt-3 text-sm bg-red-50 p-2 rounded">
|
|
137
|
+
<p className="text-red-600">{error}</p>
|
|
138
|
+
{show404Help && (
|
|
139
|
+
<div className="mt-2 text-gray-700">
|
|
140
|
+
<p>{t("uploader.youtube.notAvailableReasons.title")}</p>
|
|
141
|
+
<ul className="list-disc pl-5 mt-1">
|
|
142
|
+
<li>{t("uploader.youtube.notAvailableReasons.captions")}</li>
|
|
143
|
+
<li>{t("uploader.youtube.notAvailableReasons.privacy")}</li>
|
|
144
|
+
<li>{t("uploader.youtube.notAvailableReasons.region")}</li>
|
|
145
|
+
</ul>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
102
149
|
)}
|
|
103
150
|
</div>
|
|
104
151
|
)
|
|
@@ -2,6 +2,11 @@ import React from "react"
|
|
|
2
2
|
import { ParsedLink } from "./LinkUploader"
|
|
3
3
|
// import useStore from "../utils/store"
|
|
4
4
|
import { SVGS } from "../assets/svgs"
|
|
5
|
+
import {
|
|
6
|
+
Tooltip,
|
|
7
|
+
TooltipContent,
|
|
8
|
+
TooltipTrigger,
|
|
9
|
+
} from "@/components/ui/tooltip"
|
|
5
10
|
|
|
6
11
|
interface SourceProps {
|
|
7
12
|
source: ParsedLink
|
|
@@ -50,12 +55,16 @@ const Source: React.FC<SourceProps> = ({ source }) => {
|
|
|
50
55
|
/>
|
|
51
56
|
)}
|
|
52
57
|
{title && (
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
<Tooltip>
|
|
59
|
+
<TooltipTrigger asChild>
|
|
60
|
+
<span className="text-sm font-semibold text-left text-gray-800 ">
|
|
61
|
+
{title}
|
|
62
|
+
</span>
|
|
63
|
+
</TooltipTrigger>
|
|
64
|
+
<TooltipContent>
|
|
65
|
+
<p>{title}</p>
|
|
66
|
+
</TooltipContent>
|
|
67
|
+
</Tooltip>
|
|
59
68
|
)}
|
|
60
69
|
</div>
|
|
61
70
|
</div>
|
|
@@ -9,7 +9,11 @@ import { motion, AnimatePresence } from "framer-motion"
|
|
|
9
9
|
import toast from "react-hot-toast"
|
|
10
10
|
import { randomUUID } from "../../utils/creatorUtils"
|
|
11
11
|
import { useTranslation } from "react-i18next"
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
Tooltip,
|
|
14
|
+
TooltipContent,
|
|
15
|
+
TooltipTrigger,
|
|
16
|
+
} from "@/components/ui/tooltip"
|
|
13
17
|
|
|
14
18
|
const ContentIndexHeader = ({
|
|
15
19
|
messages,
|
|
@@ -163,6 +167,7 @@ export const ContentIndex = ({
|
|
|
163
167
|
const mode = useStore((state) => state.mode)
|
|
164
168
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
165
169
|
const [showScrollHint, setShowScrollHint] = useState(false)
|
|
170
|
+
const { t } = useTranslation()
|
|
166
171
|
|
|
167
172
|
const syllabus = history[history.length - 1]
|
|
168
173
|
|
|
@@ -282,12 +287,19 @@ export const ContentIndex = ({
|
|
|
282
287
|
{mode === "teacher" && (
|
|
283
288
|
<div className="relative h-6">
|
|
284
289
|
<div className="absolute left-1/2 -translate-x-1/2 -top-3">
|
|
285
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
290
|
+
<Tooltip>
|
|
291
|
+
<TooltipTrigger asChild>
|
|
292
|
+
<button
|
|
293
|
+
onClick={() => addLessonAfter(index, lesson.id)}
|
|
294
|
+
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"
|
|
295
|
+
>
|
|
296
|
+
+
|
|
297
|
+
</button>
|
|
298
|
+
</TooltipTrigger>
|
|
299
|
+
<TooltipContent>
|
|
300
|
+
<p>{t("tooltips.addLessonHere")}</p>
|
|
301
|
+
</TooltipContent>
|
|
302
|
+
</Tooltip>
|
|
291
303
|
</div>
|
|
292
304
|
</div>
|
|
293
305
|
)}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from "react"
|
|
2
|
-
import useStore from "../../utils/store"
|
|
3
|
-
import FileUploader from "../FileUploader"
|
|
4
|
-
import { SVGS } from "../../assets/svgs"
|
|
5
|
-
import { Message } from "../Message"
|
|
6
|
-
import { FileCard } from "../FileCard"
|
|
7
|
-
import { useTranslation } from "react-i18next"
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import useStore from "../../utils/store";
|
|
3
|
+
import FileUploader from "../FileUploader";
|
|
4
|
+
import { SVGS } from "../../assets/svgs";
|
|
5
|
+
import { Message } from "../Message";
|
|
6
|
+
import { FileCard } from "../FileCard";
|
|
7
|
+
import { useTranslation } from "react-i18next";
|
|
8
|
+
import { Tooltip, TooltipTrigger, TooltipContent } from "../ui/tooltip";
|
|
8
9
|
|
|
9
10
|
export const Sidebar = ({
|
|
10
11
|
// messages,
|
|
@@ -12,19 +13,19 @@ export const Sidebar = ({
|
|
|
12
13
|
handleSubmit,
|
|
13
14
|
}: {
|
|
14
15
|
// messages: TMessage[]
|
|
15
|
-
sendPrompt: (prompt: string) => void
|
|
16
|
-
handleSubmit: () => void
|
|
16
|
+
sendPrompt: (prompt: string) => void;
|
|
17
|
+
handleSubmit: () => void;
|
|
17
18
|
}) => {
|
|
18
|
-
const { t } = useTranslation()
|
|
19
|
+
const { t } = useTranslation();
|
|
19
20
|
|
|
20
|
-
const sidebarRef = useRef<HTMLDivElement>(null)
|
|
21
|
-
const inputRef = useRef<HTMLTextAreaElement>(null)
|
|
22
|
-
const uploadedFiles = useStore((state) => state.uploadedFiles)
|
|
23
|
-
const setUploadedFiles = useStore((state) => state.setUploadedFiles)
|
|
24
|
-
const messages = useStore((state) => state.messages)
|
|
25
|
-
const [showBubble, setShowBubble] = useState(true)
|
|
21
|
+
const sidebarRef = useRef<HTMLDivElement>(null);
|
|
22
|
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
|
23
|
+
const uploadedFiles = useStore((state) => state.uploadedFiles);
|
|
24
|
+
const setUploadedFiles = useStore((state) => state.setUploadedFiles);
|
|
25
|
+
const messages = useStore((state) => state.messages);
|
|
26
|
+
const [showBubble, setShowBubble] = useState(true);
|
|
26
27
|
|
|
27
|
-
const [isOpen, setIsOpen] = useState(false)
|
|
28
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
28
29
|
|
|
29
30
|
useEffect(() => {
|
|
30
31
|
const handleClickOutside = (e: MouseEvent) => {
|
|
@@ -33,12 +34,12 @@ export const Sidebar = ({
|
|
|
33
34
|
sidebarRef.current &&
|
|
34
35
|
!sidebarRef.current.contains(e.target as Node)
|
|
35
36
|
) {
|
|
36
|
-
setIsOpen(false)
|
|
37
|
+
setIsOpen(false);
|
|
37
38
|
}
|
|
38
|
-
}
|
|
39
|
-
document.addEventListener("mousedown", handleClickOutside)
|
|
40
|
-
return () => document.removeEventListener("mousedown", handleClickOutside)
|
|
41
|
-
}, [isOpen])
|
|
39
|
+
};
|
|
40
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
41
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
42
|
+
}, [isOpen]);
|
|
42
43
|
return (
|
|
43
44
|
<>
|
|
44
45
|
{!isOpen && (
|
|
@@ -98,9 +99,9 @@ export const Sidebar = ({
|
|
|
98
99
|
key={index}
|
|
99
100
|
file={file}
|
|
100
101
|
handleRemove={() => {
|
|
101
|
-
const newFiles = [...uploadedFiles]
|
|
102
|
-
newFiles.splice(index, 1)
|
|
103
|
-
setUploadedFiles(newFiles)
|
|
102
|
+
const newFiles = [...uploadedFiles];
|
|
103
|
+
newFiles.splice(index, 1);
|
|
104
|
+
setUploadedFiles(newFiles);
|
|
104
105
|
}}
|
|
105
106
|
/>
|
|
106
107
|
))}
|
|
@@ -113,40 +114,47 @@ export const Sidebar = ({
|
|
|
113
114
|
placeholder={t("sidebar.howCanLearnPackHelpYou")}
|
|
114
115
|
onKeyUp={(e) => {
|
|
115
116
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
116
|
-
e.preventDefault()
|
|
117
|
-
const val = inputRef.current?.value.trim() || ""
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
const val = inputRef.current?.value.trim() || "";
|
|
118
119
|
|
|
119
120
|
if (val.toLowerCase() === "ok") {
|
|
120
|
-
handleSubmit()
|
|
121
|
+
handleSubmit();
|
|
121
122
|
} else {
|
|
122
|
-
sendPrompt(val)
|
|
123
|
+
sendPrompt(val);
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
inputRef.current!.value = ""
|
|
126
|
+
inputRef.current!.value = "";
|
|
126
127
|
}
|
|
127
128
|
}}
|
|
128
129
|
/>
|
|
129
130
|
<div className="absolute bottom-2 right-2 flex gap-1 items-center">
|
|
130
131
|
<FileUploader />
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
132
|
+
<Tooltip>
|
|
133
|
+
<TooltipTrigger asChild>
|
|
134
|
+
<button
|
|
135
|
+
className="cursor-pointer blue-on-hover flex items-center justify-center w-6 h-6"
|
|
136
|
+
onClick={() => {
|
|
137
|
+
if (inputRef.current?.value) {
|
|
138
|
+
const val = inputRef.current?.value.trim() || "";
|
|
139
|
+
if (val.toLowerCase() === "ok") {
|
|
140
|
+
handleSubmit();
|
|
141
|
+
} else {
|
|
142
|
+
sendPrompt(val);
|
|
143
|
+
}
|
|
144
|
+
inputRef.current!.value = "";
|
|
145
|
+
}
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
{SVGS.send}
|
|
149
|
+
</button>
|
|
150
|
+
</TooltipTrigger>
|
|
151
|
+
<TooltipContent>
|
|
152
|
+
<p>{t("tooltips.send")}</p>
|
|
153
|
+
</TooltipContent>
|
|
154
|
+
</Tooltip>
|
|
147
155
|
</div>
|
|
148
156
|
</div>
|
|
149
157
|
</div>
|
|
150
158
|
</>
|
|
151
|
-
)
|
|
152
|
-
}
|
|
159
|
+
);
|
|
160
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
const TooltipProvider = TooltipPrimitive.Provider
|
|
9
|
+
|
|
10
|
+
const Tooltip = TooltipPrimitive.Root
|
|
11
|
+
|
|
12
|
+
const TooltipTrigger = TooltipPrimitive.Trigger
|
|
13
|
+
|
|
14
|
+
const TooltipContent = React.forwardRef<
|
|
15
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
16
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
17
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
18
|
+
<TooltipPrimitive.Content
|
|
19
|
+
ref={ref}
|
|
20
|
+
sideOffset={sideOffset}
|
|
21
|
+
className={cn(
|
|
22
|
+
"z-50 overflow-hidden rounded-md border border-gray-200 bg-white px-3 py-1.5 text-sm text-gray-800 shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
))
|
|
28
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
|
29
|
+
|
|
30
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
31
|
+
|
|
@@ -68,7 +68,19 @@
|
|
|
68
68
|
},
|
|
69
69
|
"youtube": {
|
|
70
70
|
"description": "Share a Youtube link",
|
|
71
|
-
"placeholder": "Paste your link here…"
|
|
71
|
+
"placeholder": "Paste your YouTube link here…",
|
|
72
|
+
"invalidUrl": "Please enter a valid YouTube URL",
|
|
73
|
+
"notAvailable": "Transcript not available for this video.",
|
|
74
|
+
"tooManyRequests": "Too many requests. Please try again later.",
|
|
75
|
+
"serviceUnavailable": "Service temporarily unavailable. Please try again later.",
|
|
76
|
+
"fetchError": "Failed to fetch transcript",
|
|
77
|
+
"enterUrl": "Please enter a URL",
|
|
78
|
+
"notAvailableReasons": {
|
|
79
|
+
"title": "Please check the following:",
|
|
80
|
+
"captions": "The video has captions/subtitles enabled.",
|
|
81
|
+
"privacy": "The video is public and not restricted.",
|
|
82
|
+
"region": "The video is not region-locked or blocked."
|
|
83
|
+
}
|
|
72
84
|
}
|
|
73
85
|
},
|
|
74
86
|
"loader": {
|
|
@@ -135,5 +147,16 @@
|
|
|
135
147
|
"turnstileModal": {
|
|
136
148
|
"title": "We need to verify you are a human, wait a moment...",
|
|
137
149
|
"error": "Error verifying you are a human, please try again, if the problem persists, use a different browser."
|
|
150
|
+
},
|
|
151
|
+
"tooltips": {
|
|
152
|
+
"lockLesson": "Lock lesson",
|
|
153
|
+
"unlockLesson": "Unlock lesson",
|
|
154
|
+
"editLesson": "Edit lesson",
|
|
155
|
+
"deleteLesson": "Delete lesson",
|
|
156
|
+
"cannotEditLocked": "Cannot edit locked lesson",
|
|
157
|
+
"cannotDeleteLocked": "Cannot delete locked lesson",
|
|
158
|
+
"addLessonHere": "Add lesson here",
|
|
159
|
+
"attachFiles": "Attach files",
|
|
160
|
+
"send": "Send"
|
|
138
161
|
}
|
|
139
162
|
}
|