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