yt-transcript-strapi-plugin 0.1.5 → 0.2.0
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/dist/_chunks/en-B4KWt_jN.js +0 -1
- package/dist/_chunks/en-Byx4XI2L.mjs +0 -1
- package/dist/admin/index.js +0 -1
- package/dist/admin/index.mjs +0 -1
- package/dist/server/index.js +299 -343
- package/dist/server/index.mjs +299 -343
- package/dist/server/src/index.d.ts +5 -0
- package/dist/server/src/mcp/tools/fetch-transcript.d.ts +6 -1
- package/dist/server/src/mcp/tools/find-transcripts.d.ts +6 -4
- package/dist/server/src/mcp/tools/get-transcript.d.ts +6 -3
- package/dist/server/src/mcp/tools/index.d.ts +0 -3
- package/dist/server/src/mcp/tools/list-transcripts.d.ts +6 -4
- package/dist/server/src/mcp/tools/search-transcript.d.ts +6 -2
- package/dist/server/src/services/ai-tools.d.ts +13 -0
- package/dist/server/src/services/index.d.ts +5 -0
- package/dist/server/src/tools/fetch-transcript.d.ts +2 -0
- package/dist/server/src/tools/find-transcripts.d.ts +2 -0
- package/dist/server/src/tools/get-transcript.d.ts +2 -0
- package/dist/server/src/tools/index.d.ts +19 -0
- package/dist/server/src/tools/list-transcripts.d.ts +2 -0
- package/dist/server/src/tools/search-transcript.d.ts +2 -0
- package/package.json +1 -1
- package/dist/_chunks/en-B4KWt_jN.js.map +0 -1
- package/dist/_chunks/en-Byx4XI2L.mjs.map +0 -1
- package/dist/admin/index.js.map +0 -1
- package/dist/admin/index.mjs.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/server/index.mjs.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -36,26 +36,6 @@ const FindTranscriptsSchema = z.object({
|
|
|
36
36
|
pageSize: z.number().int().min(1).max(100).optional().default(25),
|
|
37
37
|
sort: z.string().optional().default("createdAt:desc")
|
|
38
38
|
});
|
|
39
|
-
const ToolSchemas = {
|
|
40
|
-
fetch_transcript: FetchTranscriptSchema,
|
|
41
|
-
list_transcripts: ListTranscriptsSchema,
|
|
42
|
-
get_transcript: GetTranscriptSchema,
|
|
43
|
-
search_transcript: SearchTranscriptSchema,
|
|
44
|
-
find_transcripts: FindTranscriptsSchema
|
|
45
|
-
};
|
|
46
|
-
function validateToolInput(toolName, input) {
|
|
47
|
-
const schema2 = ToolSchemas[toolName];
|
|
48
|
-
const result = schema2.safeParse(input);
|
|
49
|
-
if (!result.success) {
|
|
50
|
-
const errorMessages = result.error.issues.map((err) => {
|
|
51
|
-
const path = err.path.length > 0 ? `${err.path.join(".")}: ` : "";
|
|
52
|
-
return `${path}${err.message}`;
|
|
53
|
-
});
|
|
54
|
-
throw new Error(`Validation failed for ${toolName}:
|
|
55
|
-
${errorMessages.join("\n")}`);
|
|
56
|
-
}
|
|
57
|
-
return result.data;
|
|
58
|
-
}
|
|
59
39
|
function extractYouTubeID(urlOrID) {
|
|
60
40
|
const regExpID = /^[a-zA-Z0-9_-]{11}$/;
|
|
61
41
|
if (regExpID.test(urlOrID)) {
|
|
@@ -73,20 +53,6 @@ function extractYouTubeID(urlOrID) {
|
|
|
73
53
|
}
|
|
74
54
|
return null;
|
|
75
55
|
}
|
|
76
|
-
const fetchTranscriptTool = {
|
|
77
|
-
name: "fetch_transcript",
|
|
78
|
-
description: "Fetch a transcript from YouTube for a given video ID or URL. The transcript is saved to the database. Returns metadata and preview only to avoid context overflow. Use get_transcript to retrieve content.",
|
|
79
|
-
inputSchema: {
|
|
80
|
-
type: "object",
|
|
81
|
-
properties: {
|
|
82
|
-
videoId: {
|
|
83
|
-
type: "string",
|
|
84
|
-
description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
required: ["videoId"]
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
56
|
function getVideoDurationMs$1(timecodes) {
|
|
91
57
|
if (!timecodes || timecodes.length === 0) return 0;
|
|
92
58
|
const lastEntry = timecodes[timecodes.length - 1];
|
|
@@ -120,11 +86,11 @@ function buildMetadataResponse(transcript2, previewLength, cached) {
|
|
|
120
86
|
durationSeconds: Math.floor(durationMs / 1e3)
|
|
121
87
|
},
|
|
122
88
|
preview,
|
|
123
|
-
usage: "Use
|
|
89
|
+
usage: "Use getTranscript with videoId to retrieve full content, specific time ranges, or paginated chunks."
|
|
124
90
|
};
|
|
125
91
|
}
|
|
126
|
-
async function
|
|
127
|
-
const validatedArgs =
|
|
92
|
+
async function execute$4(args, strapi) {
|
|
93
|
+
const validatedArgs = FetchTranscriptSchema.parse(args);
|
|
128
94
|
const { videoId: videoIdOrUrl } = validatedArgs;
|
|
129
95
|
const pluginConfig = await strapi.config.get("plugin::yt-transcript-strapi-plugin");
|
|
130
96
|
const previewLength = pluginConfig?.previewLength || 500;
|
|
@@ -135,18 +101,7 @@ async function handleFetchTranscript(strapi, args) {
|
|
|
135
101
|
const service2 = strapi.plugin("yt-transcript-strapi-plugin").service("service");
|
|
136
102
|
const existingTranscript = await service2.findTranscript(videoId);
|
|
137
103
|
if (existingTranscript) {
|
|
138
|
-
return
|
|
139
|
-
content: [
|
|
140
|
-
{
|
|
141
|
-
type: "text",
|
|
142
|
-
text: JSON.stringify(
|
|
143
|
-
buildMetadataResponse(existingTranscript, previewLength, true),
|
|
144
|
-
null,
|
|
145
|
-
2
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
]
|
|
149
|
-
};
|
|
104
|
+
return buildMetadataResponse(existingTranscript, previewLength, true);
|
|
150
105
|
}
|
|
151
106
|
const transcriptData = await service2.getTranscript(videoId);
|
|
152
107
|
if (!transcriptData || !transcriptData.fullTranscript) {
|
|
@@ -159,46 +114,17 @@ async function handleFetchTranscript(strapi, args) {
|
|
|
159
114
|
transcriptWithTimeCodes: transcriptData.transcriptWithTimeCodes
|
|
160
115
|
};
|
|
161
116
|
const savedTranscript = await service2.saveTranscript(payload);
|
|
162
|
-
return
|
|
163
|
-
content: [
|
|
164
|
-
{
|
|
165
|
-
type: "text",
|
|
166
|
-
text: JSON.stringify(
|
|
167
|
-
buildMetadataResponse(savedTranscript, previewLength, false),
|
|
168
|
-
null,
|
|
169
|
-
2
|
|
170
|
-
)
|
|
171
|
-
}
|
|
172
|
-
]
|
|
173
|
-
};
|
|
117
|
+
return buildMetadataResponse(savedTranscript, previewLength, false);
|
|
174
118
|
}
|
|
175
|
-
const
|
|
176
|
-
name: "
|
|
177
|
-
description: "
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
page: {
|
|
182
|
-
type: "number",
|
|
183
|
-
description: "Page number (starts at 1)",
|
|
184
|
-
default: 1
|
|
185
|
-
},
|
|
186
|
-
pageSize: {
|
|
187
|
-
type: "number",
|
|
188
|
-
description: "Number of items per page (max 100)",
|
|
189
|
-
default: 25
|
|
190
|
-
},
|
|
191
|
-
sort: {
|
|
192
|
-
type: "string",
|
|
193
|
-
description: 'Sort order (e.g., "createdAt:desc", "title:asc")',
|
|
194
|
-
default: "createdAt:desc"
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
required: []
|
|
198
|
-
}
|
|
119
|
+
const fetchTranscriptTool = {
|
|
120
|
+
name: "fetchTranscript",
|
|
121
|
+
description: "Fetch a transcript from YouTube for a given video ID or URL. The transcript is saved to the database. Returns metadata and preview only to avoid context overflow. Use getTranscript to retrieve content.",
|
|
122
|
+
schema: FetchTranscriptSchema,
|
|
123
|
+
execute: execute$4,
|
|
124
|
+
publicSafe: true
|
|
199
125
|
};
|
|
200
|
-
async function
|
|
201
|
-
const validatedArgs =
|
|
126
|
+
async function execute$3(args, strapi) {
|
|
127
|
+
const validatedArgs = ListTranscriptsSchema.parse(args);
|
|
202
128
|
const { page, pageSize, sort } = validatedArgs;
|
|
203
129
|
const start = (page - 1) * pageSize;
|
|
204
130
|
const transcripts = await strapi.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({
|
|
@@ -210,65 +136,21 @@ async function handleListTranscripts(strapi, args) {
|
|
|
210
136
|
const allTranscripts = await strapi.documents("plugin::yt-transcript-strapi-plugin.transcript").findMany({});
|
|
211
137
|
const total = allTranscripts.length;
|
|
212
138
|
return {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
page,
|
|
221
|
-
pageSize,
|
|
222
|
-
total,
|
|
223
|
-
pageCount: Math.ceil(total / pageSize)
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
null,
|
|
227
|
-
2
|
|
228
|
-
)
|
|
229
|
-
}
|
|
230
|
-
]
|
|
139
|
+
data: transcripts,
|
|
140
|
+
pagination: {
|
|
141
|
+
page,
|
|
142
|
+
pageSize,
|
|
143
|
+
total,
|
|
144
|
+
pageCount: Math.ceil(total / pageSize)
|
|
145
|
+
}
|
|
231
146
|
};
|
|
232
147
|
}
|
|
233
|
-
const
|
|
234
|
-
name: "
|
|
235
|
-
description: "
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
videoId: {
|
|
240
|
-
type: "string",
|
|
241
|
-
description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
|
|
242
|
-
},
|
|
243
|
-
includeFullTranscript: {
|
|
244
|
-
type: "boolean",
|
|
245
|
-
description: "Include the complete transcript text. Warning: may cause context overflow for long videos. Default: false",
|
|
246
|
-
default: false
|
|
247
|
-
},
|
|
248
|
-
includeTimecodes: {
|
|
249
|
-
type: "boolean",
|
|
250
|
-
description: "Include the transcript with timecodes array. Warning: significantly increases response size. Default: false",
|
|
251
|
-
default: false
|
|
252
|
-
},
|
|
253
|
-
startTime: {
|
|
254
|
-
type: "number",
|
|
255
|
-
description: "Start time in seconds for fetching a specific portion of the transcript"
|
|
256
|
-
},
|
|
257
|
-
endTime: {
|
|
258
|
-
type: "number",
|
|
259
|
-
description: "End time in seconds for fetching a specific portion of the transcript"
|
|
260
|
-
},
|
|
261
|
-
chunkIndex: {
|
|
262
|
-
type: "number",
|
|
263
|
-
description: "Chunk index (0-based) when paginating through transcript. Use with chunkSize to paginate through long videos."
|
|
264
|
-
},
|
|
265
|
-
chunkSize: {
|
|
266
|
-
type: "number",
|
|
267
|
-
description: "Chunk size in seconds. Overrides config default. Use with chunkIndex for pagination."
|
|
268
|
-
}
|
|
269
|
-
},
|
|
270
|
-
required: ["videoId"]
|
|
271
|
-
}
|
|
148
|
+
const listTranscriptsTool = {
|
|
149
|
+
name: "listTranscripts",
|
|
150
|
+
description: "List all saved YouTube transcripts from the database. Supports pagination and sorting.",
|
|
151
|
+
schema: ListTranscriptsSchema,
|
|
152
|
+
execute: execute$3,
|
|
153
|
+
publicSafe: true
|
|
272
154
|
};
|
|
273
155
|
function getTranscriptForTimeRange(timecodes, startTimeMs, endTimeMs) {
|
|
274
156
|
const entries = timecodes.filter(
|
|
@@ -292,8 +174,8 @@ function formatTime$1(ms) {
|
|
|
292
174
|
}
|
|
293
175
|
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
|
294
176
|
}
|
|
295
|
-
async function
|
|
296
|
-
const validatedArgs =
|
|
177
|
+
async function execute$2(args, strapi) {
|
|
178
|
+
const validatedArgs = GetTranscriptSchema.parse(args);
|
|
297
179
|
const {
|
|
298
180
|
videoId: videoIdOrUrl,
|
|
299
181
|
includeFullTranscript,
|
|
@@ -316,20 +198,9 @@ async function handleGetTranscript(strapi, args) {
|
|
|
316
198
|
const transcript2 = await service2.findTranscript(videoId);
|
|
317
199
|
if (!transcript2) {
|
|
318
200
|
return {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
text: JSON.stringify(
|
|
323
|
-
{
|
|
324
|
-
error: true,
|
|
325
|
-
message: `No transcript found for video ID: ${videoId}. Use fetch_transcript to fetch it from YouTube first.`,
|
|
326
|
-
videoId
|
|
327
|
-
},
|
|
328
|
-
null,
|
|
329
|
-
2
|
|
330
|
-
)
|
|
331
|
-
}
|
|
332
|
-
]
|
|
201
|
+
error: true,
|
|
202
|
+
message: `No transcript found for video ID: ${videoId}. Use fetchTranscript to fetch it from YouTube first.`,
|
|
203
|
+
videoId
|
|
333
204
|
};
|
|
334
205
|
}
|
|
335
206
|
const timecodes = transcript2.transcriptWithTimeCodes || [];
|
|
@@ -395,7 +266,7 @@ async function handleGetTranscript(strapi, args) {
|
|
|
395
266
|
response.transcriptWithTimeCodes = timecodes;
|
|
396
267
|
}
|
|
397
268
|
if (includeFullTranscript && fullText.length > maxFullTranscriptLength) {
|
|
398
|
-
response.warning = "Full transcript included. For long videos, consider using chunkIndex, startTime/endTime, or
|
|
269
|
+
response.warning = "Full transcript included. For long videos, consider using chunkIndex, startTime/endTime, or searchTranscript to reduce response size.";
|
|
399
270
|
} else if (fullText.length <= maxFullTranscriptLength) {
|
|
400
271
|
response.note = "Full transcript auto-loaded (fits within context limit).";
|
|
401
272
|
}
|
|
@@ -405,42 +276,19 @@ async function handleGetTranscript(strapi, args) {
|
|
|
405
276
|
response.isLargeTranscript = true;
|
|
406
277
|
response.usage = {
|
|
407
278
|
fullTranscript: "Set includeFullTranscript: true to get complete text (warning: may exceed context)",
|
|
408
|
-
search: "Use
|
|
279
|
+
search: "Use searchTranscript to find relevant portions by keyword (recommended for large transcripts)",
|
|
409
280
|
timeRange: "Use startTime and endTime (in seconds) to get a specific portion",
|
|
410
281
|
pagination: `Use chunkIndex (0-${totalChunks - 1}) to paginate through ${chunkSizeSeconds}s chunks`
|
|
411
282
|
};
|
|
412
283
|
}
|
|
413
|
-
return
|
|
414
|
-
content: [
|
|
415
|
-
{
|
|
416
|
-
type: "text",
|
|
417
|
-
text: JSON.stringify(response, null, 2)
|
|
418
|
-
}
|
|
419
|
-
]
|
|
420
|
-
};
|
|
284
|
+
return response;
|
|
421
285
|
}
|
|
422
|
-
const
|
|
423
|
-
name: "
|
|
424
|
-
description: "
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
videoId: {
|
|
429
|
-
type: "string",
|
|
430
|
-
description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
|
|
431
|
-
},
|
|
432
|
-
query: {
|
|
433
|
-
type: "string",
|
|
434
|
-
description: "Search query - keywords or phrases to find in the transcript"
|
|
435
|
-
},
|
|
436
|
-
maxResults: {
|
|
437
|
-
type: "number",
|
|
438
|
-
description: "Maximum number of results to return (default: 5, max: 20)",
|
|
439
|
-
default: 5
|
|
440
|
-
}
|
|
441
|
-
},
|
|
442
|
-
required: ["videoId", "query"]
|
|
443
|
-
}
|
|
286
|
+
const getTranscriptTool = {
|
|
287
|
+
name: "getTranscript",
|
|
288
|
+
description: "Get a saved transcript by YouTube video ID. Returns metadata and preview by default. Use parameters to get full content or specific time ranges to avoid context overflow.",
|
|
289
|
+
schema: GetTranscriptSchema,
|
|
290
|
+
execute: execute$2,
|
|
291
|
+
publicSafe: true
|
|
444
292
|
};
|
|
445
293
|
function tokenize(text) {
|
|
446
294
|
return text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((word) => word.length > 1);
|
|
@@ -520,8 +368,8 @@ function createSegments(timecodes, segmentDurationMs) {
|
|
|
520
368
|
}
|
|
521
369
|
return segments;
|
|
522
370
|
}
|
|
523
|
-
async function
|
|
524
|
-
const validatedArgs =
|
|
371
|
+
async function execute$1(args, strapi) {
|
|
372
|
+
const validatedArgs = SearchTranscriptSchema.parse(args);
|
|
525
373
|
const { videoId: videoIdOrUrl, query, maxResults: maxResultsInput } = validatedArgs;
|
|
526
374
|
const pluginConfig = await strapi.config.get("plugin::yt-transcript-strapi-plugin");
|
|
527
375
|
const segmentSeconds = pluginConfig?.searchSegmentSeconds || 30;
|
|
@@ -534,77 +382,33 @@ async function handleSearchTranscript(strapi, args) {
|
|
|
534
382
|
const transcript2 = await service2.findTranscript(videoId);
|
|
535
383
|
if (!transcript2) {
|
|
536
384
|
return {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
text: JSON.stringify(
|
|
541
|
-
{
|
|
542
|
-
error: true,
|
|
543
|
-
message: `No transcript found for video ID: ${videoId}. Use fetch_transcript to fetch it from YouTube first.`,
|
|
544
|
-
videoId
|
|
545
|
-
},
|
|
546
|
-
null,
|
|
547
|
-
2
|
|
548
|
-
)
|
|
549
|
-
}
|
|
550
|
-
]
|
|
385
|
+
error: true,
|
|
386
|
+
message: `No transcript found for video ID: ${videoId}. Use fetchTranscript to fetch it from YouTube first.`,
|
|
387
|
+
videoId
|
|
551
388
|
};
|
|
552
389
|
}
|
|
553
390
|
const timecodes = transcript2.transcriptWithTimeCodes || [];
|
|
554
391
|
if (timecodes.length === 0) {
|
|
555
392
|
return {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
text: JSON.stringify(
|
|
560
|
-
{
|
|
561
|
-
error: true,
|
|
562
|
-
message: "Transcript has no timecode data for searching.",
|
|
563
|
-
videoId
|
|
564
|
-
},
|
|
565
|
-
null,
|
|
566
|
-
2
|
|
567
|
-
)
|
|
568
|
-
}
|
|
569
|
-
]
|
|
393
|
+
error: true,
|
|
394
|
+
message: "Transcript has no timecode data for searching.",
|
|
395
|
+
videoId
|
|
570
396
|
};
|
|
571
397
|
}
|
|
572
398
|
const segments = createSegments(timecodes, segmentSeconds * 1e3);
|
|
573
399
|
if (segments.length === 0) {
|
|
574
400
|
return {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
text: JSON.stringify(
|
|
579
|
-
{
|
|
580
|
-
error: true,
|
|
581
|
-
message: "Could not create searchable segments from transcript.",
|
|
582
|
-
videoId
|
|
583
|
-
},
|
|
584
|
-
null,
|
|
585
|
-
2
|
|
586
|
-
)
|
|
587
|
-
}
|
|
588
|
-
]
|
|
401
|
+
error: true,
|
|
402
|
+
message: "Could not create searchable segments from transcript.",
|
|
403
|
+
videoId
|
|
589
404
|
};
|
|
590
405
|
}
|
|
591
406
|
const queryTokens = tokenize(query);
|
|
592
407
|
if (queryTokens.length === 0) {
|
|
593
408
|
return {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
text: JSON.stringify(
|
|
598
|
-
{
|
|
599
|
-
error: true,
|
|
600
|
-
message: "Query is empty or contains only stop words.",
|
|
601
|
-
query
|
|
602
|
-
},
|
|
603
|
-
null,
|
|
604
|
-
2
|
|
605
|
-
)
|
|
606
|
-
}
|
|
607
|
-
]
|
|
409
|
+
error: true,
|
|
410
|
+
message: "Query is empty or contains only stop words.",
|
|
411
|
+
query
|
|
608
412
|
};
|
|
609
413
|
}
|
|
610
414
|
const vocabulary = new Set(queryTokens);
|
|
@@ -616,74 +420,29 @@ async function handleSearchTranscript(strapi, args) {
|
|
|
616
420
|
}));
|
|
617
421
|
const results = scoredSegments.filter((seg) => seg.score > 0).sort((a, b) => b.score - a.score).slice(0, maxResults);
|
|
618
422
|
return {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
endTime: r.endTime,
|
|
633
|
-
timeRange: `${r.startFormatted} - ${r.endFormatted}`,
|
|
634
|
-
score: Math.round(r.score * 100) / 100
|
|
635
|
-
})),
|
|
636
|
-
usage: results.length > 0 ? `Use get_transcript with startTime: ${results[0].startTime} and endTime: ${results[0].endTime} to get full context for the top result.` : "No matches found. Try different keywords."
|
|
637
|
-
},
|
|
638
|
-
null,
|
|
639
|
-
2
|
|
640
|
-
)
|
|
641
|
-
}
|
|
642
|
-
]
|
|
423
|
+
videoId: transcript2.videoId,
|
|
424
|
+
title: transcript2.title,
|
|
425
|
+
query,
|
|
426
|
+
totalSegments: segments.length,
|
|
427
|
+
matchingResults: results.length,
|
|
428
|
+
results: results.map((r) => ({
|
|
429
|
+
text: r.text,
|
|
430
|
+
startTime: r.startTime,
|
|
431
|
+
endTime: r.endTime,
|
|
432
|
+
timeRange: `${r.startFormatted} - ${r.endFormatted}`,
|
|
433
|
+
score: Math.round(r.score * 100) / 100
|
|
434
|
+
})),
|
|
435
|
+
usage: results.length > 0 ? `Use getTranscript with startTime: ${results[0].startTime} and endTime: ${results[0].endTime} to get full context for the top result.` : "No matches found. Try different keywords."
|
|
643
436
|
};
|
|
644
437
|
}
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
properties: {
|
|
652
|
-
query: {
|
|
653
|
-
type: "string",
|
|
654
|
-
description: "Search query to match against title or transcript content"
|
|
655
|
-
},
|
|
656
|
-
videoId: {
|
|
657
|
-
type: "string",
|
|
658
|
-
description: "Filter by specific video ID (partial match supported)"
|
|
659
|
-
},
|
|
660
|
-
title: {
|
|
661
|
-
type: "string",
|
|
662
|
-
description: "Filter by title (partial match, case-insensitive)"
|
|
663
|
-
},
|
|
664
|
-
includeFullContent: {
|
|
665
|
-
type: "boolean",
|
|
666
|
-
description: "Set to true to include full transcript content. Default: false. Warning: may cause context overflow with multiple results."
|
|
667
|
-
},
|
|
668
|
-
page: {
|
|
669
|
-
type: "number",
|
|
670
|
-
description: "Page number (starts at 1)",
|
|
671
|
-
default: 1
|
|
672
|
-
},
|
|
673
|
-
pageSize: {
|
|
674
|
-
type: "number",
|
|
675
|
-
description: "Number of items per page (max 100)",
|
|
676
|
-
default: 25
|
|
677
|
-
},
|
|
678
|
-
sort: {
|
|
679
|
-
type: "string",
|
|
680
|
-
description: 'Sort order (e.g., "createdAt:desc", "title:asc")',
|
|
681
|
-
default: "createdAt:desc"
|
|
682
|
-
}
|
|
683
|
-
},
|
|
684
|
-
required: []
|
|
685
|
-
}
|
|
438
|
+
const searchTranscriptTool = {
|
|
439
|
+
name: "searchTranscript",
|
|
440
|
+
description: "Search within a saved transcript using BM25 scoring. Returns the most relevant segments matching your query with timestamps. Use this to find specific content in long videos without loading the entire transcript.",
|
|
441
|
+
schema: SearchTranscriptSchema,
|
|
442
|
+
execute: execute$1,
|
|
443
|
+
publicSafe: true
|
|
686
444
|
};
|
|
445
|
+
const TRANSCRIPT_PREVIEW_LENGTH = 244;
|
|
687
446
|
function truncateText(text, maxLength) {
|
|
688
447
|
if (!text) return null;
|
|
689
448
|
if (text.length <= maxLength) return text;
|
|
@@ -695,8 +454,8 @@ function truncateTranscripts(transcripts) {
|
|
|
695
454
|
fullTranscript: truncateText(transcript2.fullTranscript, TRANSCRIPT_PREVIEW_LENGTH)
|
|
696
455
|
}));
|
|
697
456
|
}
|
|
698
|
-
async function
|
|
699
|
-
const validatedArgs =
|
|
457
|
+
async function execute(args, strapi) {
|
|
458
|
+
const validatedArgs = FindTranscriptsSchema.parse(args);
|
|
700
459
|
const { query, videoId, title, includeFullContent, page, pageSize, sort } = validatedArgs;
|
|
701
460
|
const start = (page - 1) * pageSize;
|
|
702
461
|
const filters = {};
|
|
@@ -724,39 +483,231 @@ async function handleFindTranscripts(strapi, args) {
|
|
|
724
483
|
});
|
|
725
484
|
const total = allMatching.length;
|
|
726
485
|
const processedTranscripts = includeFullContent ? transcripts : truncateTranscripts(transcripts);
|
|
486
|
+
return {
|
|
487
|
+
data: processedTranscripts,
|
|
488
|
+
pagination: {
|
|
489
|
+
page,
|
|
490
|
+
pageSize,
|
|
491
|
+
total,
|
|
492
|
+
pageCount: Math.ceil(total / pageSize)
|
|
493
|
+
},
|
|
494
|
+
filters: {
|
|
495
|
+
query: query || null,
|
|
496
|
+
videoId: videoId || null,
|
|
497
|
+
title: title || null
|
|
498
|
+
},
|
|
499
|
+
...!includeFullContent && { note: "Transcript content truncated to 244 chars. Use getTranscript for full content or set includeFullContent=true." }
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
const findTranscriptsTool = {
|
|
503
|
+
name: "findTranscripts",
|
|
504
|
+
description: "Search and filter transcripts based on query criteria. Returns multiple matching transcripts with truncated previews (244 chars). Use getTranscript for full content. Supports filtering by title, videoId, and full-text search.",
|
|
505
|
+
schema: FindTranscriptsSchema,
|
|
506
|
+
execute,
|
|
507
|
+
publicSafe: true
|
|
508
|
+
};
|
|
509
|
+
const tools$1 = [
|
|
510
|
+
fetchTranscriptTool,
|
|
511
|
+
listTranscriptsTool,
|
|
512
|
+
getTranscriptTool,
|
|
513
|
+
searchTranscriptTool,
|
|
514
|
+
findTranscriptsTool
|
|
515
|
+
];
|
|
516
|
+
const fetchTranscriptToolMcp = {
|
|
517
|
+
name: "fetch_transcript",
|
|
518
|
+
description: fetchTranscriptTool.description,
|
|
519
|
+
inputSchema: {
|
|
520
|
+
type: "object",
|
|
521
|
+
properties: {
|
|
522
|
+
videoId: {
|
|
523
|
+
type: "string",
|
|
524
|
+
description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
required: ["videoId"]
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
async function handleFetchTranscript(strapi, args) {
|
|
531
|
+
const result = await fetchTranscriptTool.execute(args, strapi);
|
|
727
532
|
return {
|
|
728
533
|
content: [
|
|
729
534
|
{
|
|
730
535
|
type: "text",
|
|
731
|
-
text: JSON.stringify(
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
536
|
+
text: JSON.stringify(result, null, 2)
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const listTranscriptsToolMcp = {
|
|
542
|
+
name: "list_transcripts",
|
|
543
|
+
description: listTranscriptsTool.description,
|
|
544
|
+
inputSchema: {
|
|
545
|
+
type: "object",
|
|
546
|
+
properties: {
|
|
547
|
+
page: {
|
|
548
|
+
type: "number",
|
|
549
|
+
description: "Page number (starts at 1)"
|
|
550
|
+
},
|
|
551
|
+
pageSize: {
|
|
552
|
+
type: "number",
|
|
553
|
+
description: "Number of items per page (max 100)"
|
|
554
|
+
},
|
|
555
|
+
sort: {
|
|
556
|
+
type: "string",
|
|
557
|
+
description: 'Sort order (e.g., "createdAt:desc", "title:asc")'
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
required: []
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
async function handleListTranscripts(strapi, args) {
|
|
564
|
+
const result = await listTranscriptsTool.execute(args, strapi);
|
|
565
|
+
return {
|
|
566
|
+
content: [
|
|
567
|
+
{
|
|
568
|
+
type: "text",
|
|
569
|
+
text: JSON.stringify(result, null, 2)
|
|
570
|
+
}
|
|
571
|
+
]
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
const getTranscriptToolMcp = {
|
|
575
|
+
name: "get_transcript",
|
|
576
|
+
description: getTranscriptTool.description,
|
|
577
|
+
inputSchema: {
|
|
578
|
+
type: "object",
|
|
579
|
+
properties: {
|
|
580
|
+
videoId: {
|
|
581
|
+
type: "string",
|
|
582
|
+
description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
|
|
583
|
+
},
|
|
584
|
+
includeFullTranscript: {
|
|
585
|
+
type: "boolean",
|
|
586
|
+
description: "Include the complete transcript text. Warning: may cause context overflow for long videos. Default: false"
|
|
587
|
+
},
|
|
588
|
+
includeTimecodes: {
|
|
589
|
+
type: "boolean",
|
|
590
|
+
description: "Include the transcript with timecodes array. Warning: significantly increases response size. Default: false"
|
|
591
|
+
},
|
|
592
|
+
startTime: {
|
|
593
|
+
type: "number",
|
|
594
|
+
description: "Start time in seconds for fetching a specific portion of the transcript"
|
|
595
|
+
},
|
|
596
|
+
endTime: {
|
|
597
|
+
type: "number",
|
|
598
|
+
description: "End time in seconds for fetching a specific portion of the transcript"
|
|
599
|
+
},
|
|
600
|
+
chunkIndex: {
|
|
601
|
+
type: "number",
|
|
602
|
+
description: "Chunk index (0-based) when paginating through transcript. Use with chunkSize to paginate through long videos."
|
|
603
|
+
},
|
|
604
|
+
chunkSize: {
|
|
605
|
+
type: "number",
|
|
606
|
+
description: "Chunk size in seconds. Overrides config default. Use with chunkIndex for pagination."
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
required: ["videoId"]
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
async function handleGetTranscript(strapi, args) {
|
|
613
|
+
const result = await getTranscriptTool.execute(args, strapi);
|
|
614
|
+
return {
|
|
615
|
+
content: [
|
|
616
|
+
{
|
|
617
|
+
type: "text",
|
|
618
|
+
text: JSON.stringify(result, null, 2)
|
|
619
|
+
}
|
|
620
|
+
]
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
const searchTranscriptToolMcp = {
|
|
624
|
+
name: "search_transcript",
|
|
625
|
+
description: searchTranscriptTool.description,
|
|
626
|
+
inputSchema: {
|
|
627
|
+
type: "object",
|
|
628
|
+
properties: {
|
|
629
|
+
videoId: {
|
|
630
|
+
type: "string",
|
|
631
|
+
description: 'YouTube video ID (e.g., "dQw4w9WgXcQ") or full YouTube URL'
|
|
632
|
+
},
|
|
633
|
+
query: {
|
|
634
|
+
type: "string",
|
|
635
|
+
description: "Search query - keywords or phrases to find in the transcript"
|
|
636
|
+
},
|
|
637
|
+
maxResults: {
|
|
638
|
+
type: "number",
|
|
639
|
+
description: "Maximum number of results to return (default: 5, max: 20)"
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
required: ["videoId", "query"]
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
async function handleSearchTranscript(strapi, args) {
|
|
646
|
+
const result = await searchTranscriptTool.execute(args, strapi);
|
|
647
|
+
return {
|
|
648
|
+
content: [
|
|
649
|
+
{
|
|
650
|
+
type: "text",
|
|
651
|
+
text: JSON.stringify(result, null, 2)
|
|
652
|
+
}
|
|
653
|
+
]
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
const findTranscriptsToolMcp = {
|
|
657
|
+
name: "find_transcripts",
|
|
658
|
+
description: findTranscriptsTool.description,
|
|
659
|
+
inputSchema: {
|
|
660
|
+
type: "object",
|
|
661
|
+
properties: {
|
|
662
|
+
query: {
|
|
663
|
+
type: "string",
|
|
664
|
+
description: "Search query to match against title or transcript content"
|
|
665
|
+
},
|
|
666
|
+
videoId: {
|
|
667
|
+
type: "string",
|
|
668
|
+
description: "Filter by specific video ID (partial match supported)"
|
|
669
|
+
},
|
|
670
|
+
title: {
|
|
671
|
+
type: "string",
|
|
672
|
+
description: "Filter by title (partial match, case-insensitive)"
|
|
673
|
+
},
|
|
674
|
+
includeFullContent: {
|
|
675
|
+
type: "boolean",
|
|
676
|
+
description: "Set to true to include full transcript content. Default: false. Warning: may cause context overflow with multiple results."
|
|
677
|
+
},
|
|
678
|
+
page: {
|
|
679
|
+
type: "number",
|
|
680
|
+
description: "Page number (starts at 1)"
|
|
681
|
+
},
|
|
682
|
+
pageSize: {
|
|
683
|
+
type: "number",
|
|
684
|
+
description: "Number of items per page (max 100)"
|
|
685
|
+
},
|
|
686
|
+
sort: {
|
|
687
|
+
type: "string",
|
|
688
|
+
description: 'Sort order (e.g., "createdAt:desc", "title:asc")'
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
required: []
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
async function handleFindTranscripts(strapi, args) {
|
|
695
|
+
const result = await findTranscriptsTool.execute(args, strapi);
|
|
696
|
+
return {
|
|
697
|
+
content: [
|
|
698
|
+
{
|
|
699
|
+
type: "text",
|
|
700
|
+
text: JSON.stringify(result, null, 2)
|
|
750
701
|
}
|
|
751
702
|
]
|
|
752
703
|
};
|
|
753
704
|
}
|
|
754
705
|
const tools = [
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
706
|
+
fetchTranscriptToolMcp,
|
|
707
|
+
listTranscriptsToolMcp,
|
|
708
|
+
getTranscriptToolMcp,
|
|
709
|
+
searchTranscriptToolMcp,
|
|
710
|
+
findTranscriptsToolMcp
|
|
760
711
|
];
|
|
761
712
|
const toolHandlers = {
|
|
762
713
|
fetch_transcript: handleFetchTranscript,
|
|
@@ -1451,8 +1402,14 @@ const service = ({ strapi }) => ({
|
|
|
1451
1402
|
return transcriptData;
|
|
1452
1403
|
}
|
|
1453
1404
|
});
|
|
1405
|
+
const aiTools = ({ strapi }) => ({
|
|
1406
|
+
getTools() {
|
|
1407
|
+
return tools$1;
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1454
1410
|
const services = {
|
|
1455
|
-
service
|
|
1411
|
+
service,
|
|
1412
|
+
"ai-tools": aiTools
|
|
1456
1413
|
};
|
|
1457
1414
|
const index = {
|
|
1458
1415
|
register,
|
|
@@ -1469,4 +1426,3 @@ const index = {
|
|
|
1469
1426
|
export {
|
|
1470
1427
|
index as default
|
|
1471
1428
|
};
|
|
1472
|
-
//# sourceMappingURL=index.mjs.map
|