|
1 | | -import { db } from '@/lib/db'; |
2 | | -import { strict_output } from '@/lib/gpt'; |
3 | 1 | import { getQuestionsFromTranscript, getTranscript, searchYoutube } from '@/lib/youtube'; |
| 2 | + |
4 | 3 | import { NextResponse } from 'next/server'; |
5 | | -import { resolve } from 'path'; |
| 4 | +import { db } from '@/lib/db'; |
| 5 | +import { strict_output } from '@/lib/gpt'; |
6 | 6 | import { z } from 'zod'; |
7 | 7 |
|
8 | | -// api/chapter/getInfo |
9 | | - |
10 | | -// sleep for 0-4 seconds to simulate async behavior of real world api calls to database etc. |
11 | | - |
12 | 8 | const bodyParser = z.object({ |
13 | | - // converting the body to a zod schema to validate it and get the types |
14 | 9 | chapterId: z.string(), |
15 | 10 | }); |
16 | | -// const sleep = async () => |
17 | | -// new Promise((resolve) => { |
18 | | -// setTimeout(resolve, Math.random() * 4000); |
19 | | -// }); |
| 11 | + |
| 12 | +// Proper Fisher-Yates shuffle implementation |
| 13 | +const shuffleArray = (array: string[]) => { |
| 14 | + for (let i = array.length - 1; i > 0; i--) { |
| 15 | + const j = Math.floor(Math.random() * (i + 1)); |
| 16 | + [array[i], array[j]] = [array[j], array[i]]; |
| 17 | + } |
| 18 | + return array; |
| 19 | +}; |
20 | 20 |
|
21 | 21 | export async function POST(req: Request, res: Response) { |
22 | 22 | try { |
| 23 | + console.log('[CHAPTER PROCESSING] Starting processing'); |
| 24 | + |
23 | 25 | const body = await req.json(); |
24 | 26 | const { chapterId } = bodyParser.parse(body); |
25 | 27 |
|
26 | | - // Get Chapter Info from Database using chapterId |
27 | | - |
| 28 | + // Get Chapter Info |
28 | 29 | const chapter = await db.chapter.findUnique({ |
29 | | - where: { |
30 | | - id: chapterId, |
31 | | - }, |
| 30 | + where: { id: chapterId }, |
32 | 31 | }); |
33 | 32 |
|
34 | 33 | if (!chapter) { |
35 | | - return NextResponse.json( |
36 | | - { |
37 | | - success: false, |
38 | | - error: 'Chapter not found', |
39 | | - }, |
40 | | - { |
41 | | - status: 404, |
42 | | - }, |
43 | | - ); |
| 34 | + console.error(`[CHAPTER ${chapterId}] Not found`); |
| 35 | + return NextResponse.json({ success: false, error: 'Chapter not found' }, { status: 404 }); |
44 | 36 | } |
45 | 37 |
|
46 | | - // Get Youtube Video Id from Youtube Search Query |
| 38 | + // Validate YouTube search query |
| 39 | + if (!chapter.youtubeSearchQuery?.trim()) { |
| 40 | + console.error(`[CHAPTER ${chapterId}] Invalid search query`); |
| 41 | + return NextResponse.json({ success: false, error: 'Invalid YouTube search query' }, { status: 400 }); |
| 42 | + } |
| 43 | + |
| 44 | + // Get YouTube Video ID |
47 | 45 | const videoId = await searchYoutube(chapter.youtubeSearchQuery); |
| 46 | + if (!videoId) { |
| 47 | + console.error(`[CHAPTER ${chapterId}] No YouTube video found`); |
| 48 | + return NextResponse.json({ success: false, error: 'No YouTube video found' }, { status: 404 }); |
| 49 | + } |
| 50 | + console.log(`[CHAPTER ${chapterId}] Found video ID: ${videoId}`); |
48 | 51 |
|
49 | | - // Get Transcript from Youtube Video Id |
50 | | - let videoTranscript = await getTranscript(videoId); |
51 | | - |
52 | | - // Getting summary from transcript using GPT-3 API (OpenAI) and returning it in the response (strict_output is a helper function to make the API call to GPT-3) (see lib/gpt.ts) |
53 | | - let maxLength = 500; |
54 | | - videoTranscript = videoTranscript.split(' ').slice(0, maxLength).join(' '); |
55 | | - const { summary }: { summary: string } = await strict_output( |
56 | | - 'You are an AI capable of summarising a youtube transcript', |
57 | | - 'summarise in 250 words or less and do not talk of the sponsors or anything unrelated to the main topic, also do not introduce what the summary is about.\n' + |
58 | | - videoTranscript, |
59 | | - { summary: 'summary of the transcript' }, |
60 | | - ); |
61 | | - |
62 | | - const questions = await getQuestionsFromTranscript(videoTranscript, chapter.name); |
63 | | - |
64 | | - // create questions in database |
65 | | - await db.question.createMany({ |
66 | | - data: questions.map((question) => { |
67 | | - let options = [question.answer, question.option1, question.option2, question.option3]; |
68 | | - options = options.sort(() => Math.random() - 0.5); // shuffle the options array randomly (see https://stackoverflow.com/a/2450976/13697995) |
69 | | - return { |
70 | | - question: question.question, |
71 | | - answer: question.answer, |
72 | | - options: JSON.stringify(options), |
73 | | - chapterId: chapterId, |
74 | | - }; |
75 | | - }), |
76 | | - }); |
| 52 | + // Get Transcript |
| 53 | + const videoTranscript = await getTranscript(videoId); |
| 54 | + if (!videoTranscript) { |
| 55 | + console.error(`[CHAPTER ${chapterId}] No transcript available`); |
| 56 | + return NextResponse.json({ success: false, error: 'No transcript available' }, { status: 404 }); |
| 57 | + } |
| 58 | + console.log(`[CHAPTER ${chapterId}] Transcript length: ${videoTranscript.length}`); |
77 | 59 |
|
78 | | - // Update Chapter in Database with videoId and summary |
| 60 | + // Truncate transcript intelligently |
| 61 | + const truncatedTranscript = videoTranscript.split(' ').slice(0, 500).join(' '); |
79 | 62 |
|
80 | | - await db.chapter.update({ |
81 | | - where: { |
82 | | - id: chapterId, |
83 | | - }, |
84 | | - data: { |
85 | | - videoId, |
86 | | - summary, |
87 | | - }, |
88 | | - }); |
| 63 | + // Generate Summary |
| 64 | + let summary; |
| 65 | + try { |
| 66 | + const result = await strict_output( |
| 67 | + 'You are an AI capable of summarising YouTube transcripts', |
| 68 | + `Summarize this transcript in 250 words or less, focusing on core concepts:\n${truncatedTranscript}`, |
| 69 | + { summary: 'summary of the transcript' }, |
| 70 | + ); |
| 71 | + summary = result.summary; |
| 72 | + } catch (error) { |
| 73 | + console.error(`[CHAPTER ${chapterId}] Summary generation failed:`, error); |
| 74 | + return NextResponse.json({ success: false, error: 'Failed to generate summary' }, { status: 500 }); |
| 75 | + } |
89 | 76 |
|
90 | | - return NextResponse.json({ |
91 | | - success: true, |
92 | | - }); |
| 77 | + // Generate Questions |
| 78 | + let questions; |
| 79 | + try { |
| 80 | + questions = await getQuestionsFromTranscript(truncatedTranscript, chapter.name); |
| 81 | + } catch (error) { |
| 82 | + console.error(`[CHAPTER ${chapterId}] Question generation failed:`, error); |
| 83 | + return NextResponse.json({ success: false, error: 'Failed to generate questions' }, { status: 500 }); |
| 84 | + } |
| 85 | + |
| 86 | + // Database Transaction |
| 87 | + try { |
| 88 | + await db.$transaction([ |
| 89 | + db.chapter.update({ |
| 90 | + where: { id: chapterId }, |
| 91 | + data: { videoId, summary }, |
| 92 | + }), |
| 93 | + db.question.createMany({ |
| 94 | + data: questions.map((question) => { |
| 95 | + const options = shuffleArray([question.answer, question.option1, question.option2, question.option3]); |
| 96 | + return { |
| 97 | + question: question.question, |
| 98 | + answer: question.answer, |
| 99 | + options: JSON.stringify(options), |
| 100 | + chapterId: chapterId, |
| 101 | + }; |
| 102 | + }), |
| 103 | + }), |
| 104 | + ]); |
| 105 | + console.log(`[CHAPTER ${chapterId}] Successfully updated database`); |
| 106 | + } catch (dbError) { |
| 107 | + console.error(`[CHAPTER ${chapterId}] Database transaction failed:`, dbError); |
| 108 | + return NextResponse.json({ success: false, error: 'Database update failed' }, { status: 500 }); |
| 109 | + } |
| 110 | + |
| 111 | + return NextResponse.json({ success: true }); |
93 | 112 | } catch (error) { |
| 113 | + console.error('[CHAPTER PROCESSING] General error:', error); |
| 114 | + |
94 | 115 | if (error instanceof z.ZodError) { |
95 | | - return NextResponse.json( |
96 | | - { |
97 | | - success: false, |
98 | | - error: error.issues, |
99 | | - }, |
100 | | - { |
101 | | - status: 400, |
102 | | - }, |
103 | | - ); |
104 | | - } else { |
105 | | - return NextResponse.json( |
106 | | - { |
107 | | - success: false, |
108 | | - error: 'Something went wrong', |
109 | | - }, |
110 | | - { |
111 | | - status: 500, |
112 | | - }, |
113 | | - ); |
| 116 | + return NextResponse.json({ success: false, error: error.errors }, { status: 400 }); |
114 | 117 | } |
| 118 | + |
| 119 | + return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 }); |
115 | 120 | } |
116 | 121 | } |
0 commit comments