yt-transcript-strapi-plugin 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -3
- package/package.json +21 -8
- package/dist/_chunks/App-DKY3upWd.js +0 -23
- package/dist/_chunks/App-DKY3upWd.js.map +0 -1
- package/dist/_chunks/App-DXPFDMBH.mjs +0 -23
- package/dist/_chunks/App-DXPFDMBH.mjs.map +0 -1
- package/dist/_chunks/en-B4KWt_jN.js +0 -5
- package/dist/_chunks/en-B4KWt_jN.js.map +0 -1
- package/dist/_chunks/en-Byx4XI2L.mjs +0 -5
- package/dist/_chunks/en-Byx4XI2L.mjs.map +0 -1
- package/dist/_chunks/index-Cf76FLRe.mjs +0 -75
- package/dist/_chunks/index-Cf76FLRe.mjs.map +0 -1
- package/dist/_chunks/index-CpWYZFmv.js +0 -74
- package/dist/_chunks/index-CpWYZFmv.js.map +0 -1
- package/dist/admin/index.js +0 -4
- package/dist/admin/index.js.map +0 -1
- package/dist/admin/index.mjs +0 -5
- package/dist/admin/index.mjs.map +0 -1
- package/dist/admin/src/components/Initializer.d.ts +0 -5
- package/dist/admin/src/components/PluginIcon.d.ts +0 -2
- package/dist/admin/src/index.d.ts +0 -11
- package/dist/admin/src/pages/App.d.ts +0 -2
- package/dist/admin/src/pages/HomePage.d.ts +0 -2
- package/dist/admin/src/pluginId.d.ts +0 -1
- package/dist/admin/src/utils/getTranslation.d.ts +0 -2
- package/dist/server/index.js +0 -294
- package/dist/server/index.js.map +0 -1
- package/dist/server/index.mjs +0 -273
- package/dist/server/index.mjs.map +0 -1
- package/dist/server/src/bootstrap.d.ts +0 -5
- package/dist/server/src/config/index.d.ts +0 -5
- package/dist/server/src/content-types/index.d.ts +0 -42
- package/dist/server/src/content-types/transcript/index.d.ts +0 -40
- package/dist/server/src/controllers/controller.d.ts +0 -13
- package/dist/server/src/controllers/index.d.ts +0 -14
- package/dist/server/src/destroy.d.ts +0 -5
- package/dist/server/src/index.d.ts +0 -109
- package/dist/server/src/middlewares/index.d.ts +0 -2
- package/dist/server/src/policies/index.d.ts +0 -2
- package/dist/server/src/register.d.ts +0 -5
- package/dist/server/src/routes/admin.d.ts +0 -9
- package/dist/server/src/routes/content-api.d.ts +0 -9
- package/dist/server/src/routes/index.d.ts +0 -25
- package/dist/server/src/services/index.d.ts +0 -14
- package/dist/server/src/services/service.d.ts +0 -14
- package/dist/server/src/utils/extract-youtube-id.d.ts +0 -1
- package/dist/server/src/utils/fetch-transcript.d.ts +0 -13
- package/dist/server/src/utils/openai.d.ts +0 -9
package/dist/server/index.js
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
-
for (let key of __getOwnPropNames(from))
|
|
11
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
-
}
|
|
14
|
-
return to;
|
|
15
|
-
};
|
|
16
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
-
mod
|
|
23
|
-
));
|
|
24
|
-
const textsplitters = require("@langchain/textsplitters");
|
|
25
|
-
const prompts = require("@langchain/core/prompts");
|
|
26
|
-
const openai = require("@langchain/openai");
|
|
27
|
-
const bootstrap = ({ strapi: strapi2 }) => {
|
|
28
|
-
};
|
|
29
|
-
const destroy = ({ strapi: strapi2 }) => {
|
|
30
|
-
};
|
|
31
|
-
const register = ({ strapi: strapi2 }) => {
|
|
32
|
-
};
|
|
33
|
-
const config = {
|
|
34
|
-
default: {},
|
|
35
|
-
validator() {
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
const kind = "collectionType";
|
|
39
|
-
const collectionName = "transcript";
|
|
40
|
-
const info = {
|
|
41
|
-
singularName: "transcript",
|
|
42
|
-
pluralName: "transcripts",
|
|
43
|
-
displayName: "Transcript"
|
|
44
|
-
};
|
|
45
|
-
const options = {
|
|
46
|
-
draftAndPublish: false
|
|
47
|
-
};
|
|
48
|
-
const pluginOptions = {
|
|
49
|
-
"content-manager": {
|
|
50
|
-
visible: true
|
|
51
|
-
},
|
|
52
|
-
"content-type-builder": {
|
|
53
|
-
visible: true
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
const attributes = {
|
|
57
|
-
title: {
|
|
58
|
-
type: "string"
|
|
59
|
-
},
|
|
60
|
-
videoId: {
|
|
61
|
-
type: "string"
|
|
62
|
-
},
|
|
63
|
-
fullTranscript: {
|
|
64
|
-
type: "richtext"
|
|
65
|
-
},
|
|
66
|
-
transcriptWithTimeCodes: {
|
|
67
|
-
type: "json"
|
|
68
|
-
},
|
|
69
|
-
readableTranscript: {
|
|
70
|
-
type: "richtext"
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
const schema = {
|
|
74
|
-
kind,
|
|
75
|
-
collectionName,
|
|
76
|
-
info,
|
|
77
|
-
options,
|
|
78
|
-
pluginOptions,
|
|
79
|
-
attributes
|
|
80
|
-
};
|
|
81
|
-
const transcript = {
|
|
82
|
-
schema
|
|
83
|
-
};
|
|
84
|
-
const contentTypes = {
|
|
85
|
-
transcript
|
|
86
|
-
};
|
|
87
|
-
function extractYouTubeID(urlOrID) {
|
|
88
|
-
const regExpID = /^[a-zA-Z0-9_-]{11}$/;
|
|
89
|
-
if (regExpID.test(urlOrID)) {
|
|
90
|
-
return urlOrID;
|
|
91
|
-
}
|
|
92
|
-
const regExpStandard = /youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/;
|
|
93
|
-
const regExpShorts = /youtube\.com\/shorts\/([a-zA-Z0-9_-]+)/;
|
|
94
|
-
const matchStandard = urlOrID.match(regExpStandard);
|
|
95
|
-
if (matchStandard) {
|
|
96
|
-
return matchStandard[1];
|
|
97
|
-
}
|
|
98
|
-
const matchShorts = urlOrID.match(regExpShorts);
|
|
99
|
-
if (matchShorts) {
|
|
100
|
-
return matchShorts[1];
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
const controller = ({ strapi: strapi2 }) => ({
|
|
105
|
-
async getTranscript(ctx) {
|
|
106
|
-
const videoId = extractYouTubeID(ctx.params.videoId);
|
|
107
|
-
if (!videoId) return ctx.body = { error: "Invalid YouTube URL or ID", data: null };
|
|
108
|
-
console.log("Looking for transcript in database");
|
|
109
|
-
const found = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").findTranscript(videoId);
|
|
110
|
-
if (found) {
|
|
111
|
-
console.log("Transcript found.");
|
|
112
|
-
return ctx.body = { data: found };
|
|
113
|
-
}
|
|
114
|
-
console.log("Transcript not found. Fetching new transcript.");
|
|
115
|
-
const transcriptData = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").getTranscript(videoId);
|
|
116
|
-
console.log("New transcript fetched.");
|
|
117
|
-
const readableTranscript = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").generateHumanReadableTranscript(transcriptData.fullTranscript);
|
|
118
|
-
console.log("Human readable transcript generated.");
|
|
119
|
-
const payload = {
|
|
120
|
-
videoId,
|
|
121
|
-
title: transcriptData?.title || "No title found",
|
|
122
|
-
fullTranscript: transcriptData?.fullTranscript,
|
|
123
|
-
transcriptWithTimeCodes: transcriptData?.transcriptWithTimeCodes,
|
|
124
|
-
readableTranscript
|
|
125
|
-
};
|
|
126
|
-
console.log("Payload:", payload);
|
|
127
|
-
console.log("Saving new transcript to database.");
|
|
128
|
-
const transcript2 = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").saveTranscript(payload);
|
|
129
|
-
ctx.body = { data: transcript2 };
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
const controllers = {
|
|
133
|
-
controller
|
|
134
|
-
};
|
|
135
|
-
const middlewares = {};
|
|
136
|
-
const policies = {};
|
|
137
|
-
const contentApi = [
|
|
138
|
-
{
|
|
139
|
-
method: "GET",
|
|
140
|
-
path: "/yt-transcript/:videoId",
|
|
141
|
-
handler: "controller.getTranscript",
|
|
142
|
-
config: {
|
|
143
|
-
policies: []
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
];
|
|
147
|
-
const admin = [
|
|
148
|
-
{
|
|
149
|
-
method: "GET",
|
|
150
|
-
path: "/yt-transcript/:videoId",
|
|
151
|
-
handler: "controller.getTranscript",
|
|
152
|
-
config: {
|
|
153
|
-
policies: []
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
];
|
|
157
|
-
const routes = {
|
|
158
|
-
"content-api": {
|
|
159
|
-
type: "content-api",
|
|
160
|
-
routes: [...contentApi]
|
|
161
|
-
},
|
|
162
|
-
admin: {
|
|
163
|
-
type: "admin",
|
|
164
|
-
routes: [...admin]
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
async function initializeModel({
|
|
168
|
-
openAIApiKey,
|
|
169
|
-
model,
|
|
170
|
-
temp
|
|
171
|
-
}) {
|
|
172
|
-
return new openai.ChatOpenAI({
|
|
173
|
-
temperature: temp,
|
|
174
|
-
openAIApiKey,
|
|
175
|
-
modelName: model,
|
|
176
|
-
maxTokens: 1e3
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
const fetchTranscript = async (videoId) => {
|
|
180
|
-
console.log("Fetching Transcript - Calling fetchTranscript Utils");
|
|
181
|
-
const { Innertube } = await import("youtubei.js");
|
|
182
|
-
console.log("Creating YouTube instance");
|
|
183
|
-
const youtube = await Innertube.create({
|
|
184
|
-
lang: "en",
|
|
185
|
-
location: "US",
|
|
186
|
-
retrieve_player: false
|
|
187
|
-
});
|
|
188
|
-
try {
|
|
189
|
-
const info2 = await youtube.getInfo(videoId);
|
|
190
|
-
const transcriptData = await info2.getTranscript();
|
|
191
|
-
console.log("Transcript data fetched");
|
|
192
|
-
const transcriptWithTimeCodes = transcriptData?.transcript?.content?.body?.initial_segments.map((segment) => {
|
|
193
|
-
const segmentDuration = Number(segment.end_ms) - Number(segment.start_ms);
|
|
194
|
-
return {
|
|
195
|
-
text: segment.snippet.text,
|
|
196
|
-
start: Number(segment.start_ms),
|
|
197
|
-
end: Number(segment.end_ms),
|
|
198
|
-
duration: segmentDuration
|
|
199
|
-
};
|
|
200
|
-
});
|
|
201
|
-
console.log("Transcript with time codes generated");
|
|
202
|
-
const fullTranscript = transcriptData?.transcript?.content?.body?.initial_segments.map((segment) => segment.snippet.text).join(" ");
|
|
203
|
-
console.log(fullTranscript, "full transcript");
|
|
204
|
-
console.log("Full transcript generated");
|
|
205
|
-
console.log("Returning transcript data");
|
|
206
|
-
return {
|
|
207
|
-
videoId,
|
|
208
|
-
fullTranscript,
|
|
209
|
-
transcriptWithTimeCodes
|
|
210
|
-
};
|
|
211
|
-
} catch (error) {
|
|
212
|
-
console.error("Error fetching transcript:", error);
|
|
213
|
-
throw error;
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
async function processTextChunks(chunks, model) {
|
|
217
|
-
const punctuationPrompt = prompts.PromptTemplate.fromTemplate(
|
|
218
|
-
"Add proper punctuation and capitalization to the following text chunk:\n\n{chunk}"
|
|
219
|
-
);
|
|
220
|
-
const punctuationChain = punctuationPrompt.pipe(model);
|
|
221
|
-
const processedChunks = await Promise.all(
|
|
222
|
-
chunks.map(async (chunk) => {
|
|
223
|
-
const result = await punctuationChain.invoke({ chunk });
|
|
224
|
-
return result.content;
|
|
225
|
-
})
|
|
226
|
-
);
|
|
227
|
-
return processedChunks.join(" ");
|
|
228
|
-
}
|
|
229
|
-
async function generateModifiedTranscript(rawTranscript) {
|
|
230
|
-
const pluginSettings = await strapi.config.get("plugin::yt-transcript-strapi-plugin");
|
|
231
|
-
if (!pluginSettings.openAIApiKey || !pluginSettings.model || !pluginSettings.temp || !pluginSettings.maxTokens) {
|
|
232
|
-
throw new Error("Missing required configuration for YTTranscript");
|
|
233
|
-
}
|
|
234
|
-
const chatModel = await initializeModel({
|
|
235
|
-
openAIApiKey: pluginSettings.openAIApiKey,
|
|
236
|
-
model: pluginSettings.model,
|
|
237
|
-
temp: pluginSettings.temp,
|
|
238
|
-
maxTokens: pluginSettings.maxTokens
|
|
239
|
-
});
|
|
240
|
-
const splitter = new textsplitters.TokenTextSplitter({
|
|
241
|
-
chunkSize: 1e3,
|
|
242
|
-
chunkOverlap: 200
|
|
243
|
-
});
|
|
244
|
-
const transcriptChunks = await splitter.createDocuments([rawTranscript]);
|
|
245
|
-
const chunkTexts = transcriptChunks.map((chunk) => chunk.pageContent);
|
|
246
|
-
const modifiedTranscript = await processTextChunks(chunkTexts, chatModel);
|
|
247
|
-
return modifiedTranscript;
|
|
248
|
-
}
|
|
249
|
-
const service = ({ strapi: strapi2 }) => ({
|
|
250
|
-
async getTranscript(identifier) {
|
|
251
|
-
console.log("Fetching Transcript - Calling fetchTranscript Service");
|
|
252
|
-
const youtubeIdRegex = /^[a-zA-Z0-9_-]{11}$/;
|
|
253
|
-
const isValid = youtubeIdRegex.test(identifier);
|
|
254
|
-
if (!isValid) return { error: "Invalid video ID", data: null };
|
|
255
|
-
const transcriptData = await fetchTranscript(identifier);
|
|
256
|
-
return transcriptData;
|
|
257
|
-
},
|
|
258
|
-
async saveTranscript(payload) {
|
|
259
|
-
return await strapi2.documents("plugin::yt-transcript-strapi-plugin.transcript").create({
|
|
260
|
-
data: payload
|
|
261
|
-
});
|
|
262
|
-
},
|
|
263
|
-
async findTranscript(videoId) {
|
|
264
|
-
console.log("Finding transcript for videoId:", videoId);
|
|
265
|
-
const transcriptData = await strapi2.documents("plugin::yt-transcript-strapi-plugin.transcript").findFirst({
|
|
266
|
-
filters: { videoId }
|
|
267
|
-
});
|
|
268
|
-
console.log("Transcript found:", transcriptData?.title, "found");
|
|
269
|
-
if (!transcriptData) return null;
|
|
270
|
-
return transcriptData;
|
|
271
|
-
},
|
|
272
|
-
async generateHumanReadableTranscript(transcript2) {
|
|
273
|
-
console.log("Generating human readable transcript:");
|
|
274
|
-
const modifiedTranscript = await generateModifiedTranscript(transcript2);
|
|
275
|
-
return modifiedTranscript;
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
const services = {
|
|
279
|
-
service
|
|
280
|
-
};
|
|
281
|
-
const index = {
|
|
282
|
-
register,
|
|
283
|
-
bootstrap,
|
|
284
|
-
destroy,
|
|
285
|
-
config,
|
|
286
|
-
controllers,
|
|
287
|
-
routes,
|
|
288
|
-
services,
|
|
289
|
-
contentTypes,
|
|
290
|
-
policies,
|
|
291
|
-
middlewares
|
|
292
|
-
};
|
|
293
|
-
module.exports = index;
|
|
294
|
-
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../server/src/bootstrap.ts","../../server/src/destroy.ts","../../server/src/register.ts","../../server/src/config/index.ts","../../server/src/content-types/transcript/index.ts","../../server/src/content-types/index.ts","../../server/src/utils/extract-youtube-id.ts","../../server/src/controllers/controller.ts","../../server/src/controllers/index.ts","../../server/src/middlewares/index.ts","../../server/src/policies/index.ts","../../server/src/routes/content-api.ts","../../server/src/routes/admin.ts","../../server/src/routes/index.ts","../../server/src/utils/openai.ts","../../server/src/utils/fetch-transcript.ts","../../server/src/services/service.ts","../../server/src/services/index.ts","../../server/src/index.ts"],"sourcesContent":["import type { Core } from '@strapi/strapi';\n\nconst bootstrap = ({ strapi }: { strapi: Core.Strapi }) => {\n // bootstrap phase\n};\n\nexport default bootstrap;\n","import type { Core } from '@strapi/strapi';\n\nconst destroy = ({ strapi }: { strapi: Core.Strapi }) => {\n // destroy phase\n};\n\nexport default destroy;\n","import type { Core } from '@strapi/strapi';\n\nconst register = ({ strapi }: { strapi: Core.Strapi }) => {\n // register phase\n};\n\nexport default register;\n","export default {\n default: {},\n validator() {},\n};\n","import schema from './schema.json';\n\nexport default {\n schema,\n};","import transcript from './transcript';\n\nexport default {\n transcript,\n};\n\n\n","export function extractYouTubeID(urlOrID: string): string | null {\n // Regular expression for YouTube ID format\n const regExpID = /^[a-zA-Z0-9_-]{11}$/;\n\n // Check if the input is a YouTube ID\n if (regExpID.test(urlOrID)) {\n return urlOrID;\n }\n\n // Regular expression for standard YouTube links\n const regExpStandard = /youtube\\.com\\/watch\\?v=([a-zA-Z0-9_-]+)/;\n\n // Regular expression for YouTube Shorts links\n const regExpShorts = /youtube\\.com\\/shorts\\/([a-zA-Z0-9_-]+)/;\n\n // Check for standard YouTube link\n const matchStandard = urlOrID.match(regExpStandard);\n if (matchStandard) {\n return matchStandard[1];\n }\n\n // Check for YouTube Shorts link\n const matchShorts = urlOrID.match(regExpShorts);\n if (matchShorts) {\n return matchShorts[1];\n }\n\n // Return null if no match is found\n return null;\n}","import type { Core } from '@strapi/strapi';\nimport { extractYouTubeID } from '../utils/extract-youtube-id';\nconst controller = ({ strapi }: { strapi: Core.Strapi }) => ({\n async getTranscript(ctx) {\n const videoId = extractYouTubeID(ctx.params.videoId);\n\n if (!videoId) return (ctx.body = { error: 'Invalid YouTube URL or ID', data: null });\n\n console.log(\"Looking for transcript in database\");\n\n const found = await strapi\n .plugin('yt-transcript-strapi-plugin')\n .service('service')\n .findTranscript(videoId);\n\n if (found) {\n console.log(\"Transcript found.\");\n return (ctx.body = { data: found });\n }\n\n console.log(\"Transcript not found. Fetching new transcript.\");\n\n const transcriptData = await strapi\n .plugin('yt-transcript-strapi-plugin')\n .service('service')\n .getTranscript(videoId);\n\n console.log(\"New transcript fetched.\");\n\n const readableTranscript = await strapi\n .plugin('yt-transcript-strapi-plugin')\n .service('service')\n .generateHumanReadableTranscript(transcriptData.fullTranscript);\n\n console.log(\"Human readable transcript generated.\");\n\n const payload = {\n videoId,\n title: transcriptData?.title || \"No title found\",\n fullTranscript: transcriptData?.fullTranscript,\n transcriptWithTimeCodes: transcriptData?.transcriptWithTimeCodes,\n readableTranscript: readableTranscript,\n };\n\n console.log(\"Payload:\", payload);\n\n console.log(\"Saving new transcript to database.\");\n\n const transcript = await strapi\n .plugin('yt-transcript-strapi-plugin')\n .service('service')\n .saveTranscript(payload);\n\n ctx.body = { data: transcript };\n },\n});\n\nexport default controller;\n","import controller from './controller';\n\nexport default {\n controller,\n};\n","export default {};\n","export default {};\n","export default [\n {\n method: 'GET',\n path: '/yt-transcript/:videoId',\n handler: 'controller.getTranscript',\n config: { \n policies: [], \n }, \n },\n];","export default [\n {\n method: 'GET',\n path: '/yt-transcript/:videoId',\n handler: 'controller.getTranscript',\n config: { \n policies: [], \n }, \n },\n];","\"use strict\";\n\nimport contentApi from \"./content-api\";\nimport admin from \"./admin\";\n\nexport default {\n \"content-api\": {\n type: \"content-api\",\n routes: [...contentApi],\n },\n admin: {\n type: \"admin\",\n routes: [...admin],\n },\n};","import { ChatOpenAI } from \"@langchain/openai\";\n\ninterface InitializeModelProps {\n openAIApiKey: string;\n model: string;\n temp: number;\n maxTokens?: number;\n}\n\nexport async function initializeModel({\n openAIApiKey,\n model,\n temp,\n}: InitializeModelProps) {\n return new ChatOpenAI({\n temperature: temp,\n openAIApiKey: openAIApiKey,\n modelName: model,\n maxTokens: 1000,\n });\n}","export interface TranscriptSegment {\n text: string;\n start: number;\n end: number;\n duration: number;\n}\n\nexport interface TranscriptData {\n videoId: string;\n fullTranscript: string;\n transcriptWithTimeCodes: TranscriptSegment[];\n}\n\nconst fetchTranscript = async (videoId: string): Promise<TranscriptData> => {\n console.log('Fetching Transcript - Calling fetchTranscript Utils');\n const { Innertube } = await import('youtubei.js');\n\n console.log('Creating YouTube instance');\n\n const youtube = await Innertube.create({\n lang: 'en',\n location: 'US',\n retrieve_player: false,\n });\n\n try {\n const info = await youtube.getInfo(videoId);\n const transcriptData = await info.getTranscript();\n\n console.log('Transcript data fetched');\n\n const transcriptWithTimeCodes: TranscriptSegment[] =\n transcriptData?.transcript?.content?.body?.initial_segments.map((segment) => {\n const segmentDuration = Number(segment.end_ms) - Number(segment.start_ms);\n return {\n text: segment.snippet.text,\n start: Number(segment.start_ms),\n end: Number(segment.end_ms),\n duration: segmentDuration,\n };\n });\n\n console.log('Transcript with time codes generated');\n\n\n const fullTranscript = transcriptData?.transcript?.content?.body?.initial_segments\n .map((segment) => segment.snippet.text)\n .join(' ');\n\n console.log(fullTranscript, 'full transcript');\n console.log('Full transcript generated');\n console.log('Returning transcript data');\n\n return {\n videoId,\n fullTranscript,\n transcriptWithTimeCodes,\n };\n } catch (error) {\n console.error('Error fetching transcript:', error);\n throw error;\n }\n};\n\nexport default fetchTranscript;\n","import type { Core } from '@strapi/strapi';\nimport { ChatOpenAI } from \"@langchain/openai\";\nimport { TokenTextSplitter } from \"@langchain/textsplitters\";\nimport { PromptTemplate } from \"@langchain/core/prompts\";\n\nimport { initializeModel } from \"../utils/openai\";\nimport fetchTranscript from '../utils/fetch-transcript';\n\ninterface YTTranscriptConfig {\n openAIApiKey: string;\n model?: string;\n temp?: number;\n maxTokens?: number;\n}\n\nasync function processTextChunks(chunks: string[], model: ChatOpenAI) {\n const punctuationPrompt = PromptTemplate.fromTemplate(\n \"Add proper punctuation and capitalization to the following text chunk:\\n\\n{chunk}\"\n );\n const punctuationChain = punctuationPrompt.pipe(model);\n\n const processedChunks = await Promise.all(\n chunks.map(async (chunk) => {\n const result = await punctuationChain.invoke({ chunk });\n return result.content as string;\n })\n );\n\n return processedChunks.join(\" \");\n}\n\nexport async function generateModifiedTranscript (rawTranscript: string) {\n const pluginSettings = await strapi.config.get('plugin::yt-transcript-strapi-plugin') as YTTranscriptConfig; \n \n if (!pluginSettings.openAIApiKey || !pluginSettings.model || !pluginSettings.temp || !pluginSettings.maxTokens) {\n throw new Error('Missing required configuration for YTTranscript');\n }\n\n const chatModel = await initializeModel({\n openAIApiKey: pluginSettings.openAIApiKey,\n model: pluginSettings.model,\n temp: pluginSettings.temp,\n maxTokens: pluginSettings.maxTokens,\n });\n\n const splitter = new TokenTextSplitter({\n chunkSize: 1000,\n chunkOverlap: 200,\n });\n\n const transcriptChunks = await splitter.createDocuments([rawTranscript]);\n const chunkTexts = transcriptChunks.map(chunk => chunk.pageContent);\n const modifiedTranscript = await processTextChunks(chunkTexts, chatModel);\n return modifiedTranscript;\n}\n\nconst service = ({ strapi }: { strapi: Core.Strapi }) => ({\n async getTranscript(identifier: string) {\n console.log(\"Fetching Transcript - Calling fetchTranscript Service\");\n const youtubeIdRegex = /^[a-zA-Z0-9_-]{11}$/;\n const isValid = youtubeIdRegex.test(identifier);\n if (!isValid) return { error: 'Invalid video ID', data: null };\n const transcriptData = await fetchTranscript(identifier);\n return transcriptData;\n },\n\n async saveTranscript(payload) {\n // console.log('Saving transcript:', payload);\n return await strapi.documents('plugin::yt-transcript-strapi-plugin.transcript').create({\n data: payload,\n });\n },\n\n async findTranscript(videoId) {\n console.log('Finding transcript for videoId:', videoId);\n const transcriptData = await strapi.documents('plugin::yt-transcript-strapi-plugin.transcript').findFirst({\n filters: { videoId },\n });\n\n\n console.log('Transcript found:', transcriptData?.title, 'found');\n\n if (!transcriptData) return null;\n return transcriptData;\n },\n\n async generateHumanReadableTranscript(transcript) {\n console.log('Generating human readable transcript:');\n const modifiedTranscript = await generateModifiedTranscript(transcript);\n return modifiedTranscript;\n },\n});\n\nexport default service;\n","import service from './service';\n\nexport default {\n service,\n};\n","/**\n * Application methods\n */\nimport bootstrap from './bootstrap';\nimport destroy from './destroy';\nimport register from './register';\n\n/**\n * Plugin server methods\n */\nimport config from './config';\nimport contentTypes from './content-types';\nimport controllers from './controllers';\nimport middlewares from './middlewares';\nimport policies from './policies';\nimport routes from './routes';\nimport services from './services';\n\nexport default {\n register,\n bootstrap,\n destroy,\n config,\n controllers,\n routes,\n services,\n contentTypes,\n policies,\n middlewares,\n};\n"],"names":["strapi","transcript","ChatOpenAI","info","PromptTemplate","TokenTextSplitter"],"mappings":"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"}
|
package/dist/server/index.mjs
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { TokenTextSplitter } from "@langchain/textsplitters";
|
|
2
|
-
import { PromptTemplate } from "@langchain/core/prompts";
|
|
3
|
-
import { ChatOpenAI } from "@langchain/openai";
|
|
4
|
-
const bootstrap = ({ strapi: strapi2 }) => {
|
|
5
|
-
};
|
|
6
|
-
const destroy = ({ strapi: strapi2 }) => {
|
|
7
|
-
};
|
|
8
|
-
const register = ({ strapi: strapi2 }) => {
|
|
9
|
-
};
|
|
10
|
-
const config = {
|
|
11
|
-
default: {},
|
|
12
|
-
validator() {
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
const kind = "collectionType";
|
|
16
|
-
const collectionName = "transcript";
|
|
17
|
-
const info = {
|
|
18
|
-
singularName: "transcript",
|
|
19
|
-
pluralName: "transcripts",
|
|
20
|
-
displayName: "Transcript"
|
|
21
|
-
};
|
|
22
|
-
const options = {
|
|
23
|
-
draftAndPublish: false
|
|
24
|
-
};
|
|
25
|
-
const pluginOptions = {
|
|
26
|
-
"content-manager": {
|
|
27
|
-
visible: true
|
|
28
|
-
},
|
|
29
|
-
"content-type-builder": {
|
|
30
|
-
visible: true
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
const attributes = {
|
|
34
|
-
title: {
|
|
35
|
-
type: "string"
|
|
36
|
-
},
|
|
37
|
-
videoId: {
|
|
38
|
-
type: "string"
|
|
39
|
-
},
|
|
40
|
-
fullTranscript: {
|
|
41
|
-
type: "richtext"
|
|
42
|
-
},
|
|
43
|
-
transcriptWithTimeCodes: {
|
|
44
|
-
type: "json"
|
|
45
|
-
},
|
|
46
|
-
readableTranscript: {
|
|
47
|
-
type: "richtext"
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const schema = {
|
|
51
|
-
kind,
|
|
52
|
-
collectionName,
|
|
53
|
-
info,
|
|
54
|
-
options,
|
|
55
|
-
pluginOptions,
|
|
56
|
-
attributes
|
|
57
|
-
};
|
|
58
|
-
const transcript = {
|
|
59
|
-
schema
|
|
60
|
-
};
|
|
61
|
-
const contentTypes = {
|
|
62
|
-
transcript
|
|
63
|
-
};
|
|
64
|
-
function extractYouTubeID(urlOrID) {
|
|
65
|
-
const regExpID = /^[a-zA-Z0-9_-]{11}$/;
|
|
66
|
-
if (regExpID.test(urlOrID)) {
|
|
67
|
-
return urlOrID;
|
|
68
|
-
}
|
|
69
|
-
const regExpStandard = /youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/;
|
|
70
|
-
const regExpShorts = /youtube\.com\/shorts\/([a-zA-Z0-9_-]+)/;
|
|
71
|
-
const matchStandard = urlOrID.match(regExpStandard);
|
|
72
|
-
if (matchStandard) {
|
|
73
|
-
return matchStandard[1];
|
|
74
|
-
}
|
|
75
|
-
const matchShorts = urlOrID.match(regExpShorts);
|
|
76
|
-
if (matchShorts) {
|
|
77
|
-
return matchShorts[1];
|
|
78
|
-
}
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
const controller = ({ strapi: strapi2 }) => ({
|
|
82
|
-
async getTranscript(ctx) {
|
|
83
|
-
const videoId = extractYouTubeID(ctx.params.videoId);
|
|
84
|
-
if (!videoId) return ctx.body = { error: "Invalid YouTube URL or ID", data: null };
|
|
85
|
-
console.log("Looking for transcript in database");
|
|
86
|
-
const found = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").findTranscript(videoId);
|
|
87
|
-
if (found) {
|
|
88
|
-
console.log("Transcript found.");
|
|
89
|
-
return ctx.body = { data: found };
|
|
90
|
-
}
|
|
91
|
-
console.log("Transcript not found. Fetching new transcript.");
|
|
92
|
-
const transcriptData = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").getTranscript(videoId);
|
|
93
|
-
console.log("New transcript fetched.");
|
|
94
|
-
const readableTranscript = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").generateHumanReadableTranscript(transcriptData.fullTranscript);
|
|
95
|
-
console.log("Human readable transcript generated.");
|
|
96
|
-
const payload = {
|
|
97
|
-
videoId,
|
|
98
|
-
title: transcriptData?.title || "No title found",
|
|
99
|
-
fullTranscript: transcriptData?.fullTranscript,
|
|
100
|
-
transcriptWithTimeCodes: transcriptData?.transcriptWithTimeCodes,
|
|
101
|
-
readableTranscript
|
|
102
|
-
};
|
|
103
|
-
console.log("Payload:", payload);
|
|
104
|
-
console.log("Saving new transcript to database.");
|
|
105
|
-
const transcript2 = await strapi2.plugin("yt-transcript-strapi-plugin").service("service").saveTranscript(payload);
|
|
106
|
-
ctx.body = { data: transcript2 };
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
const controllers = {
|
|
110
|
-
controller
|
|
111
|
-
};
|
|
112
|
-
const middlewares = {};
|
|
113
|
-
const policies = {};
|
|
114
|
-
const contentApi = [
|
|
115
|
-
{
|
|
116
|
-
method: "GET",
|
|
117
|
-
path: "/yt-transcript/:videoId",
|
|
118
|
-
handler: "controller.getTranscript",
|
|
119
|
-
config: {
|
|
120
|
-
policies: []
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
];
|
|
124
|
-
const admin = [
|
|
125
|
-
{
|
|
126
|
-
method: "GET",
|
|
127
|
-
path: "/yt-transcript/:videoId",
|
|
128
|
-
handler: "controller.getTranscript",
|
|
129
|
-
config: {
|
|
130
|
-
policies: []
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
];
|
|
134
|
-
const routes = {
|
|
135
|
-
"content-api": {
|
|
136
|
-
type: "content-api",
|
|
137
|
-
routes: [...contentApi]
|
|
138
|
-
},
|
|
139
|
-
admin: {
|
|
140
|
-
type: "admin",
|
|
141
|
-
routes: [...admin]
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
async function initializeModel({
|
|
145
|
-
openAIApiKey,
|
|
146
|
-
model,
|
|
147
|
-
temp
|
|
148
|
-
}) {
|
|
149
|
-
return new ChatOpenAI({
|
|
150
|
-
temperature: temp,
|
|
151
|
-
openAIApiKey,
|
|
152
|
-
modelName: model,
|
|
153
|
-
maxTokens: 1e3
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
const fetchTranscript = async (videoId) => {
|
|
157
|
-
console.log("Fetching Transcript - Calling fetchTranscript Utils");
|
|
158
|
-
const { Innertube } = await import("youtubei.js");
|
|
159
|
-
console.log("Creating YouTube instance");
|
|
160
|
-
const youtube = await Innertube.create({
|
|
161
|
-
lang: "en",
|
|
162
|
-
location: "US",
|
|
163
|
-
retrieve_player: false
|
|
164
|
-
});
|
|
165
|
-
try {
|
|
166
|
-
const info2 = await youtube.getInfo(videoId);
|
|
167
|
-
const transcriptData = await info2.getTranscript();
|
|
168
|
-
console.log("Transcript data fetched");
|
|
169
|
-
const transcriptWithTimeCodes = transcriptData?.transcript?.content?.body?.initial_segments.map((segment) => {
|
|
170
|
-
const segmentDuration = Number(segment.end_ms) - Number(segment.start_ms);
|
|
171
|
-
return {
|
|
172
|
-
text: segment.snippet.text,
|
|
173
|
-
start: Number(segment.start_ms),
|
|
174
|
-
end: Number(segment.end_ms),
|
|
175
|
-
duration: segmentDuration
|
|
176
|
-
};
|
|
177
|
-
});
|
|
178
|
-
console.log("Transcript with time codes generated");
|
|
179
|
-
const fullTranscript = transcriptData?.transcript?.content?.body?.initial_segments.map((segment) => segment.snippet.text).join(" ");
|
|
180
|
-
console.log(fullTranscript, "full transcript");
|
|
181
|
-
console.log("Full transcript generated");
|
|
182
|
-
console.log("Returning transcript data");
|
|
183
|
-
return {
|
|
184
|
-
videoId,
|
|
185
|
-
fullTranscript,
|
|
186
|
-
transcriptWithTimeCodes
|
|
187
|
-
};
|
|
188
|
-
} catch (error) {
|
|
189
|
-
console.error("Error fetching transcript:", error);
|
|
190
|
-
throw error;
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
async function processTextChunks(chunks, model) {
|
|
194
|
-
const punctuationPrompt = PromptTemplate.fromTemplate(
|
|
195
|
-
"Add proper punctuation and capitalization to the following text chunk:\n\n{chunk}"
|
|
196
|
-
);
|
|
197
|
-
const punctuationChain = punctuationPrompt.pipe(model);
|
|
198
|
-
const processedChunks = await Promise.all(
|
|
199
|
-
chunks.map(async (chunk) => {
|
|
200
|
-
const result = await punctuationChain.invoke({ chunk });
|
|
201
|
-
return result.content;
|
|
202
|
-
})
|
|
203
|
-
);
|
|
204
|
-
return processedChunks.join(" ");
|
|
205
|
-
}
|
|
206
|
-
async function generateModifiedTranscript(rawTranscript) {
|
|
207
|
-
const pluginSettings = await strapi.config.get("plugin::yt-transcript-strapi-plugin");
|
|
208
|
-
if (!pluginSettings.openAIApiKey || !pluginSettings.model || !pluginSettings.temp || !pluginSettings.maxTokens) {
|
|
209
|
-
throw new Error("Missing required configuration for YTTranscript");
|
|
210
|
-
}
|
|
211
|
-
const chatModel = await initializeModel({
|
|
212
|
-
openAIApiKey: pluginSettings.openAIApiKey,
|
|
213
|
-
model: pluginSettings.model,
|
|
214
|
-
temp: pluginSettings.temp,
|
|
215
|
-
maxTokens: pluginSettings.maxTokens
|
|
216
|
-
});
|
|
217
|
-
const splitter = new TokenTextSplitter({
|
|
218
|
-
chunkSize: 1e3,
|
|
219
|
-
chunkOverlap: 200
|
|
220
|
-
});
|
|
221
|
-
const transcriptChunks = await splitter.createDocuments([rawTranscript]);
|
|
222
|
-
const chunkTexts = transcriptChunks.map((chunk) => chunk.pageContent);
|
|
223
|
-
const modifiedTranscript = await processTextChunks(chunkTexts, chatModel);
|
|
224
|
-
return modifiedTranscript;
|
|
225
|
-
}
|
|
226
|
-
const service = ({ strapi: strapi2 }) => ({
|
|
227
|
-
async getTranscript(identifier) {
|
|
228
|
-
console.log("Fetching Transcript - Calling fetchTranscript Service");
|
|
229
|
-
const youtubeIdRegex = /^[a-zA-Z0-9_-]{11}$/;
|
|
230
|
-
const isValid = youtubeIdRegex.test(identifier);
|
|
231
|
-
if (!isValid) return { error: "Invalid video ID", data: null };
|
|
232
|
-
const transcriptData = await fetchTranscript(identifier);
|
|
233
|
-
return transcriptData;
|
|
234
|
-
},
|
|
235
|
-
async saveTranscript(payload) {
|
|
236
|
-
return await strapi2.documents("plugin::yt-transcript-strapi-plugin.transcript").create({
|
|
237
|
-
data: payload
|
|
238
|
-
});
|
|
239
|
-
},
|
|
240
|
-
async findTranscript(videoId) {
|
|
241
|
-
console.log("Finding transcript for videoId:", videoId);
|
|
242
|
-
const transcriptData = await strapi2.documents("plugin::yt-transcript-strapi-plugin.transcript").findFirst({
|
|
243
|
-
filters: { videoId }
|
|
244
|
-
});
|
|
245
|
-
console.log("Transcript found:", transcriptData?.title, "found");
|
|
246
|
-
if (!transcriptData) return null;
|
|
247
|
-
return transcriptData;
|
|
248
|
-
},
|
|
249
|
-
async generateHumanReadableTranscript(transcript2) {
|
|
250
|
-
console.log("Generating human readable transcript:");
|
|
251
|
-
const modifiedTranscript = await generateModifiedTranscript(transcript2);
|
|
252
|
-
return modifiedTranscript;
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
const services = {
|
|
256
|
-
service
|
|
257
|
-
};
|
|
258
|
-
const index = {
|
|
259
|
-
register,
|
|
260
|
-
bootstrap,
|
|
261
|
-
destroy,
|
|
262
|
-
config,
|
|
263
|
-
controllers,
|
|
264
|
-
routes,
|
|
265
|
-
services,
|
|
266
|
-
contentTypes,
|
|
267
|
-
policies,
|
|
268
|
-
middlewares
|
|
269
|
-
};
|
|
270
|
-
export {
|
|
271
|
-
index as default
|
|
272
|
-
};
|
|
273
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../server/src/bootstrap.ts","../../server/src/destroy.ts","../../server/src/register.ts","../../server/src/config/index.ts","../../server/src/content-types/transcript/index.ts","../../server/src/content-types/index.ts","../../server/src/utils/extract-youtube-id.ts","../../server/src/controllers/controller.ts","../../server/src/controllers/index.ts","../../server/src/middlewares/index.ts","../../server/src/policies/index.ts","../../server/src/routes/content-api.ts","../../server/src/routes/admin.ts","../../server/src/routes/index.ts","../../server/src/utils/openai.ts","../../server/src/utils/fetch-transcript.ts","../../server/src/services/service.ts","../../server/src/services/index.ts","../../server/src/index.ts"],"sourcesContent":["import type { Core } from '@strapi/strapi';\n\nconst bootstrap = ({ strapi }: { strapi: Core.Strapi }) => {\n // bootstrap phase\n};\n\nexport default bootstrap;\n","import type { Core } from '@strapi/strapi';\n\nconst destroy = ({ strapi }: { strapi: Core.Strapi }) => {\n // destroy phase\n};\n\nexport default destroy;\n","import type { Core } from '@strapi/strapi';\n\nconst register = ({ strapi }: { strapi: Core.Strapi }) => {\n // register phase\n};\n\nexport default register;\n","export default {\n default: {},\n validator() {},\n};\n","import schema from './schema.json';\n\nexport default {\n schema,\n};","import transcript from './transcript';\n\nexport default {\n transcript,\n};\n\n\n","export function extractYouTubeID(urlOrID: string): string | null {\n // Regular expression for YouTube ID format\n const regExpID = /^[a-zA-Z0-9_-]{11}$/;\n\n // Check if the input is a YouTube ID\n if (regExpID.test(urlOrID)) {\n return urlOrID;\n }\n\n // Regular expression for standard YouTube links\n const regExpStandard = /youtube\\.com\\/watch\\?v=([a-zA-Z0-9_-]+)/;\n\n // Regular expression for YouTube Shorts links\n const regExpShorts = /youtube\\.com\\/shorts\\/([a-zA-Z0-9_-]+)/;\n\n // Check for standard YouTube link\n const matchStandard = urlOrID.match(regExpStandard);\n if (matchStandard) {\n return matchStandard[1];\n }\n\n // Check for YouTube Shorts link\n const matchShorts = urlOrID.match(regExpShorts);\n if (matchShorts) {\n return matchShorts[1];\n }\n\n // Return null if no match is found\n return null;\n}","import type { Core } from '@strapi/strapi';\nimport { extractYouTubeID } from '../utils/extract-youtube-id';\nconst controller = ({ strapi }: { strapi: Core.Strapi }) => ({\n async getTranscript(ctx) {\n const videoId = extractYouTubeID(ctx.params.videoId);\n\n if (!videoId) return (ctx.body = { error: 'Invalid YouTube URL or ID', data: null });\n\n console.log(\"Looking for transcript in database\");\n\n const found = await strapi\n .plugin('yt-transcript-strapi-plugin')\n .service('service')\n .findTranscript(videoId);\n\n if (found) {\n console.log(\"Transcript found.\");\n return (ctx.body = { data: found });\n }\n\n console.log(\"Transcript not found. Fetching new transcript.\");\n\n const transcriptData = await strapi\n .plugin('yt-transcript-strapi-plugin')\n .service('service')\n .getTranscript(videoId);\n\n console.log(\"New transcript fetched.\");\n\n const readableTranscript = await strapi\n .plugin('yt-transcript-strapi-plugin')\n .service('service')\n .generateHumanReadableTranscript(transcriptData.fullTranscript);\n\n console.log(\"Human readable transcript generated.\");\n\n const payload = {\n videoId,\n title: transcriptData?.title || \"No title found\",\n fullTranscript: transcriptData?.fullTranscript,\n transcriptWithTimeCodes: transcriptData?.transcriptWithTimeCodes,\n readableTranscript: readableTranscript,\n };\n\n console.log(\"Payload:\", payload);\n\n console.log(\"Saving new transcript to database.\");\n\n const transcript = await strapi\n .plugin('yt-transcript-strapi-plugin')\n .service('service')\n .saveTranscript(payload);\n\n ctx.body = { data: transcript };\n },\n});\n\nexport default controller;\n","import controller from './controller';\n\nexport default {\n controller,\n};\n","export default {};\n","export default {};\n","export default [\n {\n method: 'GET',\n path: '/yt-transcript/:videoId',\n handler: 'controller.getTranscript',\n config: { \n policies: [], \n }, \n },\n];","export default [\n {\n method: 'GET',\n path: '/yt-transcript/:videoId',\n handler: 'controller.getTranscript',\n config: { \n policies: [], \n }, \n },\n];","\"use strict\";\n\nimport contentApi from \"./content-api\";\nimport admin from \"./admin\";\n\nexport default {\n \"content-api\": {\n type: \"content-api\",\n routes: [...contentApi],\n },\n admin: {\n type: \"admin\",\n routes: [...admin],\n },\n};","import { ChatOpenAI } from \"@langchain/openai\";\n\ninterface InitializeModelProps {\n openAIApiKey: string;\n model: string;\n temp: number;\n maxTokens?: number;\n}\n\nexport async function initializeModel({\n openAIApiKey,\n model,\n temp,\n}: InitializeModelProps) {\n return new ChatOpenAI({\n temperature: temp,\n openAIApiKey: openAIApiKey,\n modelName: model,\n maxTokens: 1000,\n });\n}","export interface TranscriptSegment {\n text: string;\n start: number;\n end: number;\n duration: number;\n}\n\nexport interface TranscriptData {\n videoId: string;\n fullTranscript: string;\n transcriptWithTimeCodes: TranscriptSegment[];\n}\n\nconst fetchTranscript = async (videoId: string): Promise<TranscriptData> => {\n console.log('Fetching Transcript - Calling fetchTranscript Utils');\n const { Innertube } = await import('youtubei.js');\n\n console.log('Creating YouTube instance');\n\n const youtube = await Innertube.create({\n lang: 'en',\n location: 'US',\n retrieve_player: false,\n });\n\n try {\n const info = await youtube.getInfo(videoId);\n const transcriptData = await info.getTranscript();\n\n console.log('Transcript data fetched');\n\n const transcriptWithTimeCodes: TranscriptSegment[] =\n transcriptData?.transcript?.content?.body?.initial_segments.map((segment) => {\n const segmentDuration = Number(segment.end_ms) - Number(segment.start_ms);\n return {\n text: segment.snippet.text,\n start: Number(segment.start_ms),\n end: Number(segment.end_ms),\n duration: segmentDuration,\n };\n });\n\n console.log('Transcript with time codes generated');\n\n\n const fullTranscript = transcriptData?.transcript?.content?.body?.initial_segments\n .map((segment) => segment.snippet.text)\n .join(' ');\n\n console.log(fullTranscript, 'full transcript');\n console.log('Full transcript generated');\n console.log('Returning transcript data');\n\n return {\n videoId,\n fullTranscript,\n transcriptWithTimeCodes,\n };\n } catch (error) {\n console.error('Error fetching transcript:', error);\n throw error;\n }\n};\n\nexport default fetchTranscript;\n","import type { Core } from '@strapi/strapi';\nimport { ChatOpenAI } from \"@langchain/openai\";\nimport { TokenTextSplitter } from \"@langchain/textsplitters\";\nimport { PromptTemplate } from \"@langchain/core/prompts\";\n\nimport { initializeModel } from \"../utils/openai\";\nimport fetchTranscript from '../utils/fetch-transcript';\n\ninterface YTTranscriptConfig {\n openAIApiKey: string;\n model?: string;\n temp?: number;\n maxTokens?: number;\n}\n\nasync function processTextChunks(chunks: string[], model: ChatOpenAI) {\n const punctuationPrompt = PromptTemplate.fromTemplate(\n \"Add proper punctuation and capitalization to the following text chunk:\\n\\n{chunk}\"\n );\n const punctuationChain = punctuationPrompt.pipe(model);\n\n const processedChunks = await Promise.all(\n chunks.map(async (chunk) => {\n const result = await punctuationChain.invoke({ chunk });\n return result.content as string;\n })\n );\n\n return processedChunks.join(\" \");\n}\n\nexport async function generateModifiedTranscript (rawTranscript: string) {\n const pluginSettings = await strapi.config.get('plugin::yt-transcript-strapi-plugin') as YTTranscriptConfig; \n \n if (!pluginSettings.openAIApiKey || !pluginSettings.model || !pluginSettings.temp || !pluginSettings.maxTokens) {\n throw new Error('Missing required configuration for YTTranscript');\n }\n\n const chatModel = await initializeModel({\n openAIApiKey: pluginSettings.openAIApiKey,\n model: pluginSettings.model,\n temp: pluginSettings.temp,\n maxTokens: pluginSettings.maxTokens,\n });\n\n const splitter = new TokenTextSplitter({\n chunkSize: 1000,\n chunkOverlap: 200,\n });\n\n const transcriptChunks = await splitter.createDocuments([rawTranscript]);\n const chunkTexts = transcriptChunks.map(chunk => chunk.pageContent);\n const modifiedTranscript = await processTextChunks(chunkTexts, chatModel);\n return modifiedTranscript;\n}\n\nconst service = ({ strapi }: { strapi: Core.Strapi }) => ({\n async getTranscript(identifier: string) {\n console.log(\"Fetching Transcript - Calling fetchTranscript Service\");\n const youtubeIdRegex = /^[a-zA-Z0-9_-]{11}$/;\n const isValid = youtubeIdRegex.test(identifier);\n if (!isValid) return { error: 'Invalid video ID', data: null };\n const transcriptData = await fetchTranscript(identifier);\n return transcriptData;\n },\n\n async saveTranscript(payload) {\n // console.log('Saving transcript:', payload);\n return await strapi.documents('plugin::yt-transcript-strapi-plugin.transcript').create({\n data: payload,\n });\n },\n\n async findTranscript(videoId) {\n console.log('Finding transcript for videoId:', videoId);\n const transcriptData = await strapi.documents('plugin::yt-transcript-strapi-plugin.transcript').findFirst({\n filters: { videoId },\n });\n\n\n console.log('Transcript found:', transcriptData?.title, 'found');\n\n if (!transcriptData) return null;\n return transcriptData;\n },\n\n async generateHumanReadableTranscript(transcript) {\n console.log('Generating human readable transcript:');\n const modifiedTranscript = await generateModifiedTranscript(transcript);\n return modifiedTranscript;\n },\n});\n\nexport default service;\n","import service from './service';\n\nexport default {\n service,\n};\n","/**\n * Application methods\n */\nimport bootstrap from './bootstrap';\nimport destroy from './destroy';\nimport register from './register';\n\n/**\n * Plugin server methods\n */\nimport config from './config';\nimport contentTypes from './content-types';\nimport controllers from './controllers';\nimport middlewares from './middlewares';\nimport policies from './policies';\nimport routes from './routes';\nimport services from './services';\n\nexport default {\n register,\n bootstrap,\n destroy,\n config,\n controllers,\n routes,\n services,\n contentTypes,\n policies,\n middlewares,\n};\n"],"names":["strapi","transcript","info"],"mappings":";;;AAEA,MAAM,YAAY,CAAC,EAAE,QAAAA,cAAsC;AAE3D;ACFA,MAAM,UAAU,CAAC,EAAE,QAAAA,cAAsC;AAEzD;ACFA,MAAM,WAAW,CAAC,EAAE,QAAAA,cAAsC;AAE1D;ACJA,MAAe,SAAA;AAAA,EACb,SAAS,CAAC;AAAA,EACV,YAAY;AAAA,EAAA;AACd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACDA,MAAe,aAAA;AAAA,EACb;AACF;ACFA,MAAe,eAAA;AAAA,EACb;AACF;ACJO,SAAS,iBAAiB,SAAgC;AAE/D,QAAM,WAAW;AAGb,MAAA,SAAS,KAAK,OAAO,GAAG;AACnB,WAAA;AAAA,EAAA;AAIT,QAAM,iBAAiB;AAGvB,QAAM,eAAe;AAGf,QAAA,gBAAgB,QAAQ,MAAM,cAAc;AAClD,MAAI,eAAe;AACjB,WAAO,cAAc,CAAC;AAAA,EAAA;AAIlB,QAAA,cAAc,QAAQ,MAAM,YAAY;AAC9C,MAAI,aAAa;AACf,WAAO,YAAY,CAAC;AAAA,EAAA;AAIf,SAAA;AACT;AC3BA,MAAM,aAAa,CAAC,EAAE,QAAAA,eAAuC;AAAA,EAC3D,MAAM,cAAc,KAAK;AACvB,UAAM,UAAU,iBAAiB,IAAI,OAAO,OAAO;AAE/C,QAAA,CAAC,QAAiB,QAAA,IAAI,OAAO,EAAE,OAAO,6BAA6B,MAAM,KAAK;AAElF,YAAQ,IAAI,oCAAoC;AAE1C,UAAA,QAAQ,MAAMA,QACjB,OAAO,6BAA6B,EACpC,QAAQ,SAAS,EACjB,eAAe,OAAO;AAEzB,QAAI,OAAO;AACT,cAAQ,IAAI,mBAAmB;AAC/B,aAAQ,IAAI,OAAO,EAAE,MAAM,MAAM;AAAA,IAAA;AAGnC,YAAQ,IAAI,gDAAgD;AAEtD,UAAA,iBAAiB,MAAMA,QAC1B,OAAO,6BAA6B,EACpC,QAAQ,SAAS,EACjB,cAAc,OAAO;AAExB,YAAQ,IAAI,yBAAyB;AAE/B,UAAA,qBAAqB,MAAMA,QAC9B,OAAO,6BAA6B,EACpC,QAAQ,SAAS,EACjB,gCAAgC,eAAe,cAAc;AAEhE,YAAQ,IAAI,sCAAsC;AAElD,UAAM,UAAU;AAAA,MACd;AAAA,MACA,OAAO,gBAAgB,SAAS;AAAA,MAChC,gBAAgB,gBAAgB;AAAA,MAChC,yBAAyB,gBAAgB;AAAA,MACzC;AAAA,IACF;AAEQ,YAAA,IAAI,YAAY,OAAO;AAE/B,YAAQ,IAAI,oCAAoC;AAE1C,UAAAC,cAAa,MAAMD,QACtB,OAAO,6BAA6B,EACpC,QAAQ,SAAS,EACjB,eAAe,OAAO;AAErB,QAAA,OAAO,EAAE,MAAMC,YAAW;AAAA,EAAA;AAElC;ACrDA,MAAe,cAAA;AAAA,EACb;AACF;ACJA,MAAA,cAAe,CAAC;ACAhB,MAAA,WAAe,CAAC;ACAhB,MAAe,aAAA;AAAA,EACb;AAAA,IACE,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,IACT,QAAQ;AAAA,MACN,UAAU,CAAA;AAAA,IAAC;AAAA,EACb;AAEJ;ACTA,MAAe,QAAA;AAAA,EACb;AAAA,IACE,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,IACT,QAAQ;AAAA,MACN,UAAU,CAAA;AAAA,IAAC;AAAA,EACb;AAEJ;ACJA,MAAe,SAAA;AAAA,EACb,eAAe;AAAA,IACb,MAAM;AAAA,IACN,QAAQ,CAAC,GAAG,UAAU;AAAA,EACxB;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,CAAC,GAAG,KAAK;AAAA,EAAA;AAErB;ACLA,eAAsB,gBAAgB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,SAAO,IAAI,WAAW;AAAA,IACpB,aAAa;AAAA,IACb;AAAA,IACA,WAAW;AAAA,IACX,WAAW;AAAA,EAAA,CACZ;AACH;ACPA,MAAM,kBAAkB,OAAO,YAA6C;AAC1E,UAAQ,IAAI,qDAAqD;AACjE,QAAM,EAAE,UAAA,IAAc,MAAM,OAAO,aAAa;AAEhD,UAAQ,IAAI,2BAA2B;AAEjC,QAAA,UAAU,MAAM,UAAU,OAAO;AAAA,IACrC,MAAM;AAAA,IACN,UAAU;AAAA,IACV,iBAAiB;AAAA,EAAA,CAClB;AAEG,MAAA;AACF,UAAMC,QAAO,MAAM,QAAQ,QAAQ,OAAO;AACpC,UAAA,iBAAiB,MAAMA,MAAK,cAAc;AAEhD,YAAQ,IAAI,yBAAyB;AAE/B,UAAA,0BACJ,gBAAgB,YAAY,SAAS,MAAM,iBAAiB,IAAI,CAAC,YAAY;AAC3E,YAAM,kBAAkB,OAAO,QAAQ,MAAM,IAAI,OAAO,QAAQ,QAAQ;AACjE,aAAA;AAAA,QACL,MAAM,QAAQ,QAAQ;AAAA,QACtB,OAAO,OAAO,QAAQ,QAAQ;AAAA,QAC9B,KAAK,OAAO,QAAQ,MAAM;AAAA,QAC1B,UAAU;AAAA,MACZ;AAAA,IAAA,CACD;AAEH,YAAQ,IAAI,sCAAsC;AAGlD,UAAM,iBAAiB,gBAAgB,YAAY,SAAS,MAAM,iBAC/D,IAAI,CAAC,YAAY,QAAQ,QAAQ,IAAI,EACrC,KAAK,GAAG;AAEH,YAAA,IAAI,gBAAgB,iBAAiB;AAC7C,YAAQ,IAAI,2BAA2B;AACvC,YAAQ,IAAI,2BAA2B;AAEhC,WAAA;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,WACO,OAAO;AACN,YAAA,MAAM,8BAA8B,KAAK;AAC3C,UAAA;AAAA,EAAA;AAEV;AC/CA,eAAe,kBAAkB,QAAkB,OAAmB;AACpE,QAAM,oBAAoB,eAAe;AAAA,IACvC;AAAA,EACF;AACM,QAAA,mBAAmB,kBAAkB,KAAK,KAAK;AAE/C,QAAA,kBAAkB,MAAM,QAAQ;AAAA,IACpC,OAAO,IAAI,OAAO,UAAU;AAC1B,YAAM,SAAS,MAAM,iBAAiB,OAAO,EAAE,OAAO;AACtD,aAAO,OAAO;AAAA,IACf,CAAA;AAAA,EACH;AAEO,SAAA,gBAAgB,KAAK,GAAG;AACjC;AAEA,eAAsB,2BAA4B,eAAuB;AACvE,QAAM,iBAAiB,MAAM,OAAO,OAAO,IAAI,qCAAqC;AAEhF,MAAA,CAAC,eAAe,gBAAgB,CAAC,eAAe,SAAS,CAAC,eAAe,QAAQ,CAAC,eAAe,WAAW;AACxG,UAAA,IAAI,MAAM,iDAAiD;AAAA,EAAA;AAG7D,QAAA,YAAY,MAAM,gBAAgB;AAAA,IACtC,cAAc,eAAe;AAAA,IAC7B,OAAO,eAAe;AAAA,IACtB,MAAM,eAAe;AAAA,IACrB,WAAW,eAAe;AAAA,EAAA,CAC3B;AAEK,QAAA,WAAW,IAAI,kBAAkB;AAAA,IACrC,WAAW;AAAA,IACX,cAAc;AAAA,EAAA,CACf;AAED,QAAM,mBAAmB,MAAM,SAAS,gBAAgB,CAAC,aAAa,CAAC;AACvE,QAAM,aAAa,iBAAiB,IAAI,CAAA,UAAS,MAAM,WAAW;AAClE,QAAM,qBAAqB,MAAM,kBAAkB,YAAY,SAAS;AACjE,SAAA;AACT;AAEA,MAAM,UAAU,CAAC,EAAE,QAAAF,eAAuC;AAAA,EACxD,MAAM,cAAc,YAAoB;AACtC,YAAQ,IAAI,uDAAuD;AACnE,UAAM,iBAAiB;AACjB,UAAA,UAAU,eAAe,KAAK,UAAU;AAC9C,QAAI,CAAC,QAAS,QAAO,EAAE,OAAO,oBAAoB,MAAM,KAAK;AACvD,UAAA,iBAAiB,MAAM,gBAAgB,UAAU;AAChD,WAAA;AAAA,EACT;AAAA,EAEA,MAAM,eAAe,SAAS;AAE5B,WAAO,MAAMA,QAAO,UAAU,gDAAgD,EAAE,OAAO;AAAA,MACrF,MAAM;AAAA,IAAA,CACP;AAAA,EACH;AAAA,EAEA,MAAM,eAAe,SAAS;AACpB,YAAA,IAAI,mCAAmC,OAAO;AACtD,UAAM,iBAAmB,MAAMA,QAAO,UAAU,gDAAgD,EAAE,UAAU;AAAA,MAC1G,SAAS,EAAE,QAAQ;AAAA,IAAA,CACpB;AAGD,YAAQ,IAAI,qBAAqB,gBAAgB,OAAO,OAAO;AAE3D,QAAA,CAAC,eAAuB,QAAA;AACrB,WAAA;AAAA,EACT;AAAA,EAEA,MAAM,gCAAgCC,aAAY;AAChD,YAAQ,IAAI,uCAAuC;AAC7C,UAAA,qBAAqB,MAAM,2BAA2BA,WAAU;AAC/D,WAAA;AAAA,EAAA;AAEX;ACzFA,MAAe,WAAA;AAAA,EACb;AACF;ACcA,MAAe,QAAA;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;"}
|