yt-embeddings-strapi-plugin 0.0.1

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 (65) hide show
  1. package/README.md +531 -0
  2. package/dist/_chunks/App-Cv1cdLAr.js +587 -0
  3. package/dist/_chunks/App-bN58O1bN.mjs +583 -0
  4. package/dist/_chunks/en-B4KWt_jN.js +4 -0
  5. package/dist/_chunks/en-Byx4XI2L.mjs +4 -0
  6. package/dist/_chunks/index-BAfBs5PQ.js +172 -0
  7. package/dist/_chunks/index-K6X5FM2O.mjs +173 -0
  8. package/dist/admin/index.js +4 -0
  9. package/dist/admin/index.mjs +5 -0
  10. package/dist/admin/src/components/Initializer.d.ts +5 -0
  11. package/dist/admin/src/components/PluginIcon.d.ts +2 -0
  12. package/dist/admin/src/components/custom/BackLink.d.ts +5 -0
  13. package/dist/admin/src/components/custom/ChatModal.d.ts +1 -0
  14. package/dist/admin/src/components/custom/EmbeddingsModal.d.ts +1 -0
  15. package/dist/admin/src/components/custom/EmbeddingsWidget.d.ts +1 -0
  16. package/dist/admin/src/components/custom/Illo.d.ts +1 -0
  17. package/dist/admin/src/components/custom/Markdown.d.ts +5 -0
  18. package/dist/admin/src/components/custom/RobotIcon.d.ts +6 -0
  19. package/dist/admin/src/index.d.ts +12 -0
  20. package/dist/admin/src/pages/App.d.ts +2 -0
  21. package/dist/admin/src/pages/EmbeddingDetails.d.ts +1 -0
  22. package/dist/admin/src/pages/HomePage.d.ts +1 -0
  23. package/dist/admin/src/pluginId.d.ts +1 -0
  24. package/dist/admin/src/utils/api.d.ts +81 -0
  25. package/dist/admin/src/utils/getTranslation.d.ts +2 -0
  26. package/dist/server/index.js +2220 -0
  27. package/dist/server/index.mjs +2203 -0
  28. package/dist/server/src/bootstrap.d.ts +5 -0
  29. package/dist/server/src/config/index.d.ts +38 -0
  30. package/dist/server/src/content-types/index.d.ts +2 -0
  31. package/dist/server/src/controllers/controller.d.ts +13 -0
  32. package/dist/server/src/controllers/index.d.ts +30 -0
  33. package/dist/server/src/controllers/mcp.d.ts +18 -0
  34. package/dist/server/src/controllers/yt-controller.d.ts +13 -0
  35. package/dist/server/src/destroy.d.ts +5 -0
  36. package/dist/server/src/index.d.ts +280 -0
  37. package/dist/server/src/mcp/index.d.ts +6 -0
  38. package/dist/server/src/mcp/schemas/index.d.ts +55 -0
  39. package/dist/server/src/mcp/server.d.ts +8 -0
  40. package/dist/server/src/mcp/tools/get-video-transcript-range.d.ts +33 -0
  41. package/dist/server/src/mcp/tools/get-yt-video-summary.d.ts +23 -0
  42. package/dist/server/src/mcp/tools/index.d.ts +38 -0
  43. package/dist/server/src/mcp/tools/list-yt-videos.d.ts +28 -0
  44. package/dist/server/src/mcp/tools/search-yt-knowledge.d.ts +51 -0
  45. package/dist/server/src/middlewares/index.d.ts +2 -0
  46. package/dist/server/src/migrations/002-yt-tables.d.ts +2 -0
  47. package/dist/server/src/plugin-manager.d.ts +81 -0
  48. package/dist/server/src/policies/index.d.ts +2 -0
  49. package/dist/server/src/register.d.ts +5 -0
  50. package/dist/server/src/routes/admin.d.ts +14 -0
  51. package/dist/server/src/routes/content-api.d.ts +20 -0
  52. package/dist/server/src/routes/index.d.ts +41 -0
  53. package/dist/server/src/services/ai-tools.d.ts +127 -0
  54. package/dist/server/src/services/index.d.ts +185 -0
  55. package/dist/server/src/services/yt-embeddings.d.ts +68 -0
  56. package/dist/server/src/services/yt-metadata.d.ts +12 -0
  57. package/dist/server/src/tools/get-video-transcript-range.d.ts +32 -0
  58. package/dist/server/src/tools/get-yt-video-summary.d.ts +36 -0
  59. package/dist/server/src/tools/index.d.ts +126 -0
  60. package/dist/server/src/tools/list-yt-videos.d.ts +25 -0
  61. package/dist/server/src/tools/search-yt-knowledge.d.ts +35 -0
  62. package/dist/server/src/utils/chunking.d.ts +44 -0
  63. package/dist/server/src/utils/preprocessing.d.ts +26 -0
  64. package/dist/server/src/utils/yt-chunker.d.ts +16 -0
  65. package/package.json +106 -0
@@ -0,0 +1,587 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const admin = require("@strapi/strapi/admin");
5
+ const reactRouterDom = require("react-router-dom");
6
+ const react = require("react");
7
+ const styled = require("styled-components");
8
+ const designSystem = require("@strapi/design-system");
9
+ const icons = require("@strapi/icons");
10
+ const index = require("./index-BAfBs5PQ.js");
11
+ const qs = require("qs");
12
+ const ReactMarkdown = require("react-markdown");
13
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
14
+ const styled__default = /* @__PURE__ */ _interopDefault(styled);
15
+ const qs__default = /* @__PURE__ */ _interopDefault(qs);
16
+ const ReactMarkdown__default = /* @__PURE__ */ _interopDefault(ReactMarkdown);
17
+ const MarkdownWrapper = styled__default.default.div`
18
+ /* Headers */
19
+ h1 {
20
+ font-size: 2.25rem;
21
+ font-weight: 700;
22
+ margin-bottom: 1rem;
23
+ color: #272728;
24
+ }
25
+
26
+ h2 {
27
+ font-size: 1.75rem;
28
+ font-weight: 700;
29
+ margin-bottom: 1rem;
30
+ color: #272728;
31
+ }
32
+
33
+ h3 {
34
+ font-size: 1.5rem;
35
+ font-weight: 700;
36
+ margin-bottom: 1rem;
37
+ color: #272728;
38
+ }
39
+
40
+ h4 {
41
+ font-size: 1.25rem;
42
+ font-weight: 700;
43
+ margin-bottom: 1rem;
44
+ color: #272728;
45
+ }
46
+
47
+ h5 {
48
+ font-size: 1.125rem;
49
+ font-weight: 700;
50
+ margin-bottom: 1rem;
51
+ color: #272728;
52
+ }
53
+
54
+ h6 {
55
+ font-size: 1rem;
56
+ font-weight: 700;
57
+ margin-bottom: 1rem;
58
+ color: #9d4edd;
59
+ }
60
+
61
+ /* Horizontal rules */
62
+ hr {
63
+ border-color: #d1d5db;
64
+ margin-top: 2rem;
65
+ margin-bottom: 2rem;
66
+ }
67
+
68
+ a {
69
+ color: #4945ff;
70
+ text-decoration: underline;
71
+ }
72
+
73
+ /* Paragraphs */
74
+ p {
75
+ margin-bottom: 1rem;
76
+ line-height: 1.5rem;
77
+ color: #39393a;
78
+ }
79
+
80
+ /* Emphasis */
81
+ strong {
82
+ font-weight: 700;
83
+ }
84
+
85
+ em {
86
+ font-style: italic;
87
+ }
88
+
89
+ del {
90
+ text-decoration: line-through;
91
+ }
92
+
93
+ /* Blockquotes */
94
+ blockquote {
95
+ border-left-width: 1px;
96
+ border-color: #9ca3af;
97
+ padding-left: 1rem;
98
+ padding-top: 0.5rem;
99
+ padding-bottom: 0.5rem;
100
+ margin-bottom: 1rem;
101
+ }
102
+
103
+ /* Lists */
104
+ ul {
105
+ list-style-type: disc;
106
+ padding-left: 1rem;
107
+ margin-bottom: 1rem;
108
+ }
109
+
110
+ ol {
111
+ list-style-type: decimal;
112
+ padding-left: 1rem;
113
+ margin-bottom: 1rem;
114
+ }
115
+
116
+ li {
117
+ margin-bottom: 0.5rem;
118
+ }
119
+
120
+ li > ul {
121
+ list-style-type: disc;
122
+ padding-left: 1rem;
123
+ margin-bottom: 0.5rem;
124
+ }
125
+
126
+ li > ol {
127
+ list-style-type: decimal;
128
+ padding-left: 1rem;
129
+ margin-bottom: 0.5rem;
130
+ }
131
+
132
+ /* Code blocks */
133
+ pre {
134
+ font-family: monospace;
135
+ background-color: #1f2937;
136
+ color: #f9fafb;
137
+ border-radius: 0.375rem;
138
+ padding: 1rem;
139
+ margin-top: 1.5rem;
140
+ margin-bottom: 1.5rem;
141
+ line-height: 1.5rem;
142
+ overflow: auto;
143
+ }
144
+
145
+ code {
146
+ font-family: monospace;
147
+ background-color: #1f2937;
148
+ color: #f9fafb;
149
+ border-radius: 0.375rem;
150
+ padding-left: 0.5rem;
151
+ padding-right: 0.5rem;
152
+ padding-top: 0.25rem;
153
+ padding-bottom: 0.25rem;
154
+ }
155
+
156
+ /* Tables */
157
+ table {
158
+ width: 100%;
159
+ border-collapse: collapse;
160
+ border-color: #d1d5db;
161
+ margin-top: 1.5rem;
162
+ margin-bottom: 1.5rem;
163
+ }
164
+
165
+ th {
166
+ background-color: #1f2937;
167
+ text-align: left;
168
+ padding-top: 0.5rem;
169
+ padding-bottom: 0.5rem;
170
+ padding-left: 1rem;
171
+ padding-right: 1rem;
172
+ font-weight: 600;
173
+ border-bottom-width: 1px;
174
+ border-color: #d1d5db;
175
+ }
176
+
177
+ td {
178
+ padding-top: 0.5rem;
179
+ padding-bottom: 0.5rem;
180
+ padding-left: 1rem;
181
+ padding-right: 1rem;
182
+ border-bottom-width: 1px;
183
+ border-color: #d1d5db;
184
+ }
185
+
186
+ /* Images */
187
+ img {
188
+ width: 100%;
189
+ object-fit: cover;
190
+ border-radius: 0.75rem;
191
+ margin-top: 1.5rem;
192
+ margin-bottom: 1.5rem;
193
+ }
194
+ `;
195
+ function Markdown({ children }) {
196
+ return /* @__PURE__ */ jsxRuntime.jsx(MarkdownWrapper, { children: /* @__PURE__ */ jsxRuntime.jsx(
197
+ ReactMarkdown__default.default,
198
+ {
199
+ components: {
200
+ a: ({ href, children: linkChildren }) => /* @__PURE__ */ jsxRuntime.jsx("a", { href, target: "_blank", rel: "noopener noreferrer", children: linkChildren })
201
+ },
202
+ children
203
+ }
204
+ ) });
205
+ }
206
+ const StyledButton = styled__default.default(designSystem.Button)`
207
+ position: fixed;
208
+ bottom: 1.5rem;
209
+ right: 1.5rem;
210
+ height: 3.5rem;
211
+ width: 3.5rem;
212
+ border-radius: 50%;
213
+ display: flex;
214
+ align-items: center;
215
+ justify-content: center;
216
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
217
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
218
+ z-index: 100;
219
+
220
+ svg {
221
+ height: 1.75rem;
222
+ width: 1.75rem;
223
+ }
224
+ `;
225
+ const ResponseContainer = styled__default.default.div`
226
+ border: solid 1px #e3e9f3;
227
+ border-radius: 4px;
228
+ padding: 1rem;
229
+ color: #32324d;
230
+ font-weight: 400;
231
+ font-size: 0.875rem;
232
+ display: block;
233
+ width: 100%;
234
+ max-height: 400px;
235
+ background: inherit;
236
+ overflow-y: auto;
237
+ scroll-behavior: smooth;
238
+ `;
239
+ function AccordionDetails({ title, content, children }) {
240
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 1, background: "primary100", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Root, { size: "S", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Item, { value: "acc-1", children: [
241
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Trigger, { children: title }) }),
242
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 3, children: [
243
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: content }),
244
+ children && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 1, children })
245
+ ] }) })
246
+ ] }) }) });
247
+ }
248
+ function ShowResponse({ data, onNavigate }) {
249
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: data.map((item, index2) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 4, children: [
250
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 1, children: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { children: item.text }) }),
251
+ item.sourceDocuments?.length > 0 && item.sourceDocuments.map((doc, docIndex) => /* @__PURE__ */ jsxRuntime.jsx(
252
+ AccordionDetails,
253
+ {
254
+ title: `Source: ${doc.metadata.title}`,
255
+ content: /* @__PURE__ */ jsxRuntime.jsx(Markdown, { children: doc.pageContent }),
256
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { display: "flex", gap: 3, children: [
257
+ doc.metadata.deepLink && /* @__PURE__ */ jsxRuntime.jsx(
258
+ "a",
259
+ {
260
+ href: doc.metadata.deepLink,
261
+ target: "_blank",
262
+ rel: "noopener noreferrer",
263
+ style: { color: "#4945ff", textDecoration: "none", fontSize: "0.875rem" },
264
+ children: "Watch on YouTube ↗"
265
+ }
266
+ ),
267
+ /* @__PURE__ */ jsxRuntime.jsx(
268
+ designSystem.Link,
269
+ {
270
+ onClick: () => onNavigate(doc.metadata.id),
271
+ style: { cursor: "pointer" },
272
+ children: "View in Dashboard"
273
+ }
274
+ )
275
+ ] })
276
+ },
277
+ docIndex
278
+ ))
279
+ ] }, index2)) });
280
+ }
281
+ function ChatModal() {
282
+ const { get } = admin.useFetchClient();
283
+ const containerRef = react.useRef(null);
284
+ const navigate = reactRouterDom.useNavigate();
285
+ const [isVisible, setIsVisible] = react.useState(false);
286
+ const [isLoading, setIsLoading] = react.useState(false);
287
+ const [inputValue, setInputValue] = react.useState("");
288
+ const [data, setData] = react.useState([]);
289
+ react.useEffect(() => {
290
+ if (containerRef.current) {
291
+ containerRef.current.scrollTop = containerRef.current.scrollHeight;
292
+ }
293
+ }, [data]);
294
+ function handleNavigate(id) {
295
+ setIsVisible(false);
296
+ navigate(`/plugins/${index.PLUGIN_ID}/video/${id}`);
297
+ }
298
+ async function handleQueryEmbeddings(e) {
299
+ e.preventDefault();
300
+ if (!inputValue.trim() || isLoading) return;
301
+ setIsLoading(true);
302
+ try {
303
+ const response = await get(
304
+ `/${index.PLUGIN_ID}/embeddings/embeddings-query?${qs__default.default.stringify({
305
+ query: inputValue
306
+ })}`
307
+ );
308
+ if (response.data && !response.data.error) {
309
+ setData((prev) => [...prev, response.data]);
310
+ }
311
+ setInputValue("");
312
+ } catch (error) {
313
+ console.error("Query failed:", error);
314
+ } finally {
315
+ setIsLoading(false);
316
+ }
317
+ }
318
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
319
+ /* @__PURE__ */ jsxRuntime.jsx(StyledButton, { onClick: () => setIsVisible(true), "aria-label": "Open chat", children: /* @__PURE__ */ jsxRuntime.jsx(index.RobotIcon, { height: 28, width: 28 }) }),
320
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open: isVisible, onOpenChange: setIsVisible, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
321
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: "Chat With Your Data" }) }),
322
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Body, { children: [
323
+ data.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 1, marginBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(ResponseContainer, { ref: containerRef, children: /* @__PURE__ */ jsxRuntime.jsx(ShowResponse, { data, onNavigate: handleNavigate }) }) }),
324
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 1, children: /* @__PURE__ */ jsxRuntime.jsx("form", { onSubmit: handleQueryEmbeddings, children: /* @__PURE__ */ jsxRuntime.jsx(
325
+ designSystem.TextInput,
326
+ {
327
+ placeholder: "Enter your question",
328
+ type: "text",
329
+ "aria-label": "Question",
330
+ name: "question",
331
+ onChange: (e) => setInputValue(e.target.value),
332
+ value: inputValue
333
+ }
334
+ ) }) })
335
+ ] }),
336
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
337
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", children: "Cancel" }) }),
338
+ /* @__PURE__ */ jsxRuntime.jsx(
339
+ designSystem.Button,
340
+ {
341
+ onClick: handleQueryEmbeddings,
342
+ disabled: !inputValue.trim() || isLoading,
343
+ loading: isLoading,
344
+ children: isLoading ? "Sending..." : "Send"
345
+ }
346
+ )
347
+ ] })
348
+ ] }) })
349
+ ] });
350
+ }
351
+ const ClickableTr = styled__default.default(designSystem.Tr)`
352
+ cursor: pointer;
353
+ &:hover { background-color: #f0f0ff; }
354
+ `;
355
+ function formatDuration(seconds) {
356
+ const m = Math.floor(seconds / 60);
357
+ const s = Math.floor(seconds % 60);
358
+ return `${m}:${s.toString().padStart(2, "0")}`;
359
+ }
360
+ function formatDate(iso) {
361
+ if (!iso) return "-";
362
+ return new Date(iso).toLocaleDateString(void 0, {
363
+ month: "short",
364
+ day: "numeric",
365
+ year: "numeric"
366
+ });
367
+ }
368
+ function HomePage() {
369
+ const navigate = reactRouterDom.useNavigate();
370
+ const { get, post } = admin.useFetchClient();
371
+ const [videos, setVideos] = react.useState([]);
372
+ const [total, setTotal] = react.useState(0);
373
+ const [isLoading, setIsLoading] = react.useState(true);
374
+ const [isRecomputing, setIsRecomputing] = react.useState(false);
375
+ const fetchVideos = react.useCallback(async () => {
376
+ setIsLoading(true);
377
+ try {
378
+ const response = await get(`/${index.PLUGIN_ID}/yt/videos?pageSize=100`);
379
+ const data = response?.data || response;
380
+ setVideos(data?.data || []);
381
+ setTotal(data?.total || 0);
382
+ } catch (error) {
383
+ console.error("Failed to fetch videos:", error);
384
+ setVideos([]);
385
+ } finally {
386
+ setIsLoading(false);
387
+ }
388
+ }, [get]);
389
+ react.useEffect(() => {
390
+ fetchVideos();
391
+ }, [fetchVideos]);
392
+ const handleRecompute = async () => {
393
+ if (!confirm("This will delete ALL embeddings and re-embed every transcript. Continue?")) return;
394
+ setIsRecomputing(true);
395
+ try {
396
+ await post(`/${index.PLUGIN_ID}/yt/recompute`);
397
+ await fetchVideos();
398
+ } catch (error) {
399
+ console.error("Recompute failed:", error);
400
+ } finally {
401
+ setIsRecomputing(false);
402
+ }
403
+ };
404
+ const headerActions = /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, children: /* @__PURE__ */ jsxRuntime.jsx(
405
+ designSystem.Button,
406
+ {
407
+ variant: "secondary",
408
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowClockwise, {}),
409
+ onClick: handleRecompute,
410
+ loading: isRecomputing,
411
+ disabled: isRecomputing,
412
+ children: isRecomputing ? "Re-embedding..." : "Re-embed All"
413
+ }
414
+ ) });
415
+ if (isLoading) {
416
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
417
+ /* @__PURE__ */ jsxRuntime.jsx(
418
+ admin.Layouts.Header,
419
+ {
420
+ title: "YouTube Embeddings",
421
+ subtitle: "Embedded transcripts in vector store",
422
+ primaryAction: headerActions
423
+ }
424
+ ),
425
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading..." }) }) })
426
+ ] });
427
+ }
428
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
429
+ /* @__PURE__ */ jsxRuntime.jsx(
430
+ admin.Layouts.Header,
431
+ {
432
+ title: "YouTube Embeddings",
433
+ subtitle: `${total} video${total !== 1 ? "s" : ""} embedded`,
434
+ primaryAction: headerActions
435
+ }
436
+ ),
437
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: videos.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 8, background: "neutral100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "center", gap: 4, children: [
438
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", textColor: "neutral600", children: "No embedded videos yet" }),
439
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: 'Open a Transcript entry and click "Create Embedding" to get started.' })
440
+ ] }) }) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 5, rowCount: videos.length + 1, children: [
441
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
442
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Title" }) }),
443
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Topics" }) }),
444
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Chunks" }) }),
445
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Duration" }) }),
446
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Embedded" }) })
447
+ ] }) }),
448
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: videos.map((video) => /* @__PURE__ */ jsxRuntime.jsxs(ClickableTr, { onClick: () => navigate(`/plugins/${index.PLUGIN_ID}/video/${video.video_id}`), children: [
449
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Td, { children: [
450
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", fontWeight: "semiBold", children: video.title }),
451
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: video.video_id })
452
+ ] }),
453
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, wrap: "wrap", children: [
454
+ (video.topics || []).slice(0, 3).map((topic, i) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: topic }, i)),
455
+ (video.topics || []).length > 3 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
456
+ "+",
457
+ video.topics.length - 3
458
+ ] })
459
+ ] }) }),
460
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: video.chunk_count }) }),
461
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: video.duration_seconds ? formatDuration(video.duration_seconds) : "-" }) }),
462
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: formatDate(video.embedded_at || video.created_at) }) })
463
+ ] }, video.video_id)) })
464
+ ] }) }),
465
+ /* @__PURE__ */ jsxRuntime.jsx(ChatModal, {})
466
+ ] });
467
+ }
468
+ function BackLink({ to }) {
469
+ if (to) {
470
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { tag: reactRouterDom.NavLink, to, startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), children: "Go back" });
471
+ }
472
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { tag: reactRouterDom.NavLink, to: "..", relative: "path", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}), children: "Go back" });
473
+ }
474
+ function formatTime(seconds) {
475
+ const m = Math.floor(seconds / 60);
476
+ const s = Math.floor(seconds % 60);
477
+ return `${m}:${s.toString().padStart(2, "0")}`;
478
+ }
479
+ function VideoDetails() {
480
+ const { videoId } = reactRouterDom.useParams();
481
+ const { get } = admin.useFetchClient();
482
+ const [video, setVideo] = react.useState(null);
483
+ const [chunks, setChunks] = react.useState([]);
484
+ const [isLoading, setIsLoading] = react.useState(true);
485
+ react.useEffect(() => {
486
+ if (!videoId) return;
487
+ Promise.all([
488
+ get(`/${index.PLUGIN_ID}/yt/videos/${videoId}`),
489
+ get(`/${index.PLUGIN_ID}/yt/videos/${videoId}/chunks`)
490
+ ]).then(([videoRes, chunksRes]) => {
491
+ setVideo(videoRes?.data?.data || videoRes?.data || null);
492
+ setChunks(chunksRes?.data?.data || chunksRes?.data || []);
493
+ }).catch((err) => console.error("Failed to load video:", err)).finally(() => setIsLoading(false));
494
+ }, [videoId, get]);
495
+ if (isLoading) {
496
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
497
+ /* @__PURE__ */ jsxRuntime.jsx(
498
+ admin.Layouts.Header,
499
+ {
500
+ title: "Loading...",
501
+ navigationAction: /* @__PURE__ */ jsxRuntime.jsx(BackLink, { to: `/plugins/${index.PLUGIN_ID}` })
502
+ }
503
+ ),
504
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading video details..." }) }) })
505
+ ] });
506
+ }
507
+ if (!video) {
508
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
509
+ /* @__PURE__ */ jsxRuntime.jsx(
510
+ admin.Layouts.Header,
511
+ {
512
+ title: "Video Not Found",
513
+ navigationAction: /* @__PURE__ */ jsxRuntime.jsx(BackLink, { to: `/plugins/${index.PLUGIN_ID}` })
514
+ }
515
+ ),
516
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 8, textAlign: "center", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: "Video not found in the vector store." }) }) })
517
+ ] });
518
+ }
519
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Main, { children: [
520
+ /* @__PURE__ */ jsxRuntime.jsx(
521
+ admin.Layouts.Header,
522
+ {
523
+ title: video.title,
524
+ subtitle: `${video.chunk_count} chunks | ${formatTime(video.duration_seconds || 0)} duration`,
525
+ navigationAction: /* @__PURE__ */ jsxRuntime.jsx(BackLink, { to: `/plugins/${index.PLUGIN_ID}` })
526
+ }
527
+ ),
528
+ /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Content, { children: [
529
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", padding: 4, hasRadius: true, marginBottom: 4, children: [
530
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", style: { display: "block", marginBottom: "0.5rem" }, children: "Summary" }),
531
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral700", style: { display: "block", marginBottom: "1rem" }, children: video.summary }),
532
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, wrap: "wrap", children: (video.topics || []).map((topic, i) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: topic }, i)) })
533
+ ] }),
534
+ video.key_moments?.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", padding: 4, hasRadius: true, marginBottom: 4, children: [
535
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", style: { display: "block", marginBottom: "0.5rem" }, children: "Key Moments" }),
536
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 3, rowCount: video.key_moments.length + 1, children: [
537
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
538
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Time" }) }),
539
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Label" }) }),
540
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Summary" }) })
541
+ ] }) }),
542
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: video.key_moments.map((moment, i) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
543
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: formatTime(moment.startSeconds) }) }),
544
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "semiBold", children: moment.label }) }),
545
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: moment.summary }) })
546
+ ] }, i)) })
547
+ ] })
548
+ ] }),
549
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", padding: 4, hasRadius: true, children: [
550
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "delta", style: { display: "block", marginBottom: "0.75rem" }, children: [
551
+ "Chunks (",
552
+ chunks.length,
553
+ ")"
554
+ ] }),
555
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 4, rowCount: chunks.length + 1, children: [
556
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
557
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "#" }) }),
558
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Time Range" }) }),
559
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Text" }) }),
560
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Tokens" }) })
561
+ ] }) }),
562
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: chunks.map((chunk) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
563
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: chunk.chunk_index }) }),
564
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { children: [
565
+ formatTime(chunk.start_seconds),
566
+ " - ",
567
+ formatTime(chunk.end_seconds)
568
+ ] }) }),
569
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral700", style: { fontSize: "0.8125rem", lineHeight: 1.4 }, children: chunk.text.length > 200 ? chunk.text.slice(0, 200) + "..." : chunk.text }) }),
570
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
571
+ "~",
572
+ chunk.tokens
573
+ ] }) })
574
+ ] }, chunk.id)) })
575
+ ] })
576
+ ] })
577
+ ] })
578
+ ] });
579
+ }
580
+ const App = () => {
581
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
582
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(HomePage, {}) }),
583
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "/video/:videoId", element: /* @__PURE__ */ jsxRuntime.jsx(VideoDetails, {}) }),
584
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "*", element: /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Error, {}) })
585
+ ] });
586
+ };
587
+ exports.App = App;