Skip to content

Commit 5a84cd9

Browse files
committed
fix: resolve YouTube video duplicate response issue
- Add YouTube transcript extraction service with multiple fallback methods - Implement YouTube video analysis API endpoint - Fix frontend message handling to prevent duplicate responses - Add isComplete flag for single-shot responses - Add YouTube video card component and utility functions - Add line-clamp utility class for video descriptions - Add comprehensive documentation for YouTube analysis feature This fixes the issue where YouTube video responses were being duplicated in the chat interface due to improper handling of single-shot vs streaming responses.
1 parent 6180016 commit 5a84cd9

8 files changed

Lines changed: 1202 additions & 12 deletions

File tree

docs/YOUTUBE_ANALYSIS.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# YouTube Video Analysis Feature
2+
3+
## Overview
4+
5+
Perplexica now includes a powerful YouTube video analysis feature that automatically detects YouTube links in messages and provides intelligent responses based on video content. This feature works similarly to Perplexity AI's video analysis capabilities.
6+
7+
## How It Works
8+
9+
### 1. Automatic Link Detection
10+
- The system automatically detects YouTube URLs in user messages
11+
- Supports various YouTube URL formats:
12+
- `https://www.youtube.com/watch?v=VIDEO_ID`
13+
- `https://youtu.be/VIDEO_ID`
14+
- `https://www.youtube.com/embed/VIDEO_ID`
15+
- `https://www.youtube.com/v/VIDEO_ID`
16+
17+
### 2. Video Data Extraction
18+
- Extracts video metadata including:
19+
- Title
20+
- Channel name
21+
- Duration
22+
- View count
23+
- Publication date
24+
- Thumbnail
25+
- Description
26+
27+
### 3. Transcript Processing
28+
- Attempts to extract video transcripts when available
29+
- Falls back to metadata-based summarization when transcripts are not available
30+
- Handles multiple language transcripts
31+
32+
### 4. AI-Powered Analysis
33+
- Uses the configured AI model to provide intelligent responses
34+
- Understands user intent (recipe, tutorial, summary, etc.)
35+
- Provides contextually appropriate responses
36+
- Maintains accuracy and relevance to user requests
37+
38+
## Usage
39+
40+
### Simple Usage
41+
Just paste a YouTube URL in your message and send it. The system will automatically:
42+
43+
1. Detect the YouTube link
44+
2. Extract video information
45+
3. Analyze your request and provide appropriate response
46+
4. Display the results in a formatted response
47+
48+
### Examples
49+
```
50+
User: Can you summarize this video? https://www.youtube.com/watch?v=dQw4w9WgXcQ
51+
User: Give me the recipe from this video https://www.youtube.com/watch?v=example
52+
User: How do I do this tutorial? https://www.youtube.com/watch?v=example
53+
```
54+
55+
The system will respond with contextually appropriate information including:
56+
- Video information (title, channel, duration, views)
57+
- Response tailored to your request (recipe, tutorial, summary, etc.)
58+
- Practical details and instructions when relevant
59+
- Link to watch the original video
60+
61+
## Technical Implementation
62+
63+
### Files Added/Modified
64+
65+
#### New Files:
66+
- `src/lib/utils/youtube.ts` - YouTube link detection and video ID extraction
67+
- `src/lib/services/youtubeTranscript.ts` - Video data and transcript extraction
68+
- `src/app/api/youtube/route.ts` - YouTube analysis API endpoint
69+
- `src/components/YouTubeVideoCard.tsx` - Video information display component
70+
71+
#### Modified Files:
72+
- `src/app/api/chat/route.ts` - Added YouTube link detection and processing
73+
- `src/app/globals.css` - Added line-clamp utility for text truncation
74+
75+
### API Endpoints
76+
77+
#### POST `/api/youtube`
78+
Analyzes YouTube videos and provides intelligent responses based on user requests.
79+
80+
**Request Body:**
81+
```json
82+
{
83+
"url": "https://www.youtube.com/watch?v=VIDEO_ID",
84+
"chatHistory": [...],
85+
"chatModel": {
86+
"provider": "openai",
87+
"name": "gpt-4"
88+
},
89+
"systemInstructions": "Optional custom instructions"
90+
}
91+
```
92+
93+
**Response:**
94+
```json
95+
{
96+
"summary": "Generated summary text...",
97+
"videoInfo": {
98+
"title": "Video Title",
99+
"channel": "Channel Name",
100+
"duration": "10:30",
101+
"viewCount": "1.2M",
102+
"publishedAt": "2024-01-01",
103+
"thumbnail": "https://...",
104+
"description": "Video description...",
105+
"videoId": "VIDEO_ID",
106+
"url": "https://www.youtube.com/watch?v=VIDEO_ID"
107+
},
108+
"hasTranscript": true
109+
}
110+
```
111+
112+
## Features
113+
114+
### ✅ Implemented
115+
- Automatic YouTube link detection
116+
- Video metadata extraction
117+
- Transcript extraction (when available)
118+
- AI-powered intelligent analysis
119+
- User intent understanding (recipe, tutorial, summary, etc.)
120+
- Contextually appropriate responses
121+
- Fallback handling for unavailable transcripts
122+
- Error handling for private/deleted videos
123+
- Formatted response display
124+
125+
### 🔄 Future Enhancements
126+
- Support for YouTube playlists
127+
- Video chapter extraction
128+
- Multiple video processing
129+
- Custom response styles
130+
- Transcript language detection
131+
- Video quality analysis
132+
- Enhanced recipe extraction
133+
- Step-by-step tutorial generation
134+
135+
## Error Handling
136+
137+
The system handles various error scenarios:
138+
139+
1. **Invalid URLs**: Returns error for non-YouTube URLs
140+
2. **Private Videos**: Handles access restrictions gracefully
141+
3. **Deleted Videos**: Provides clear error messages
142+
4. **No Transcript**: Falls back to metadata-based summarization
143+
5. **API Failures**: Graceful degradation with helpful error messages
144+
145+
## Configuration
146+
147+
The feature uses the same AI model configuration as the main chat system. Users can:
148+
149+
- Select different AI models for summarization
150+
- Customize system instructions
151+
- Use different optimization modes (speed/balanced/quality)
152+
153+
## Limitations
154+
155+
- Requires public YouTube videos (private videos cannot be processed)
156+
- Transcript availability depends on video settings
157+
- Some videos may have restricted access
158+
- Processing time depends on video length and transcript availability
159+
160+
## Troubleshooting
161+
162+
### Common Issues
163+
164+
1. **"Could not extract video data"**
165+
- Check if the video is public
166+
- Verify the URL format
167+
- Try refreshing the page
168+
169+
2. **"No transcript available"**
170+
- The video may not have captions enabled
171+
- Summary will be based on metadata only
172+
173+
3. **Slow processing**
174+
- Longer videos take more time to process
175+
- Check your internet connection
176+
- Try with a shorter video first
177+
178+
### Support
179+
180+
For issues or feature requests, please:
181+
1. Check the error message for specific details
182+
2. Verify the YouTube URL is accessible
183+
3. Try with a different video to isolate the issue
184+
4. Report bugs with specific URLs and error messages

src/app/api/chat/route.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
getCustomOpenaiModelName,
2222
} from '@/lib/config';
2323
import { searchHandlers } from '@/lib/search';
24+
import { containsYouTubeLink, extractYouTubeLinks } from '@/lib/utils/youtube';
2425

2526
export const runtime = 'nodejs';
2627
export const dynamic = 'force-dynamic';
@@ -182,6 +183,8 @@ const handleHistorySave = async (
182183
};
183184

184185
export const POST = async (req: Request) => {
186+
const requestId = Date.now().toString(36);
187+
console.log(`=== Chat API route called (ID: ${requestId}) ===`);
185188
try {
186189
const body = (await req.json()) as Body;
187190
const { message } = body;
@@ -261,6 +264,98 @@ export const POST = async (req: Request) => {
261264
}
262265
});
263266

267+
// Check for YouTube links in the message
268+
const youtubeLinks = extractYouTubeLinks(message.content);
269+
270+
if (youtubeLinks.length > 0) {
271+
console.log(`=== Found ${youtubeLinks.length} YouTube links, processing... (ID: ${requestId}) ===`);
272+
273+
// Process the first YouTube link found
274+
const youtubeUrl = youtubeLinks[0];
275+
276+
try {
277+
// Call the YouTube API directly
278+
console.log(`=== Calling YouTube API for URL: ${youtubeUrl} (ID: ${requestId}) ===`);
279+
const url = new URL(req.url);
280+
const summaryResponse = await fetch(`${url.origin}/api/youtube`, {
281+
method: 'POST',
282+
headers: {
283+
'Content-Type': 'application/json',
284+
},
285+
body: JSON.stringify({
286+
url: youtubeUrl,
287+
chatHistory: body.history.map(([role, content]) => ({ role, content })),
288+
chatModel: body.chatModel,
289+
systemInstructions: `${body.systemInstructions}\n\nUser's current request: ${message.content}`,
290+
}),
291+
});
292+
293+
if (!summaryResponse.ok) {
294+
throw new Error('Failed to generate YouTube analysis');
295+
}
296+
297+
const summaryData = await summaryResponse.json();
298+
console.log(`=== YouTube API response received, summary length: ${summaryData.summary?.length || 0} (ID: ${requestId}) ===`);
299+
300+
// Create response stream
301+
const responseStream = new TransformStream();
302+
const writer = responseStream.writable.getWriter();
303+
const encoder = new TextEncoder();
304+
305+
// Send the response based on user's request
306+
const responseTitle = message.content.toLowerCase().includes('recipe') ? 'YouTube Recipe' :
307+
message.content.toLowerCase().includes('tutorial') ? 'YouTube Tutorial' :
308+
message.content.toLowerCase().includes('how') ? 'YouTube How-to Guide' :
309+
'YouTube Video Analysis';
310+
311+
const responseData = `## ${responseTitle}\n\n**${summaryData.videoInfo.title}**\n\n*by ${summaryData.videoInfo.channel}${summaryData.videoInfo.duration}${summaryData.videoInfo.viewCount} views*\n\n${summaryData.summary}\n\n---\n*[Watch on YouTube](${youtubeUrl})*`;
312+
console.log(`=== Response data created, length: ${responseData.length} (ID: ${requestId}) ===`);
313+
314+
// Send the complete response in one message with a special flag
315+
console.log(`=== Sending complete response (ID: ${requestId}) ===`);
316+
writer.write(
317+
encoder.encode(
318+
JSON.stringify({
319+
type: 'message',
320+
data: responseData,
321+
messageId: aiMessageId,
322+
isComplete: true, // Flag to indicate this is a complete response
323+
}) + '\n',
324+
),
325+
);
326+
327+
writer.write(
328+
encoder.encode(
329+
JSON.stringify({
330+
type: 'messageEnd',
331+
messageId: aiMessageId,
332+
}) + '\n',
333+
),
334+
);
335+
336+
writer.close();
337+
338+
// Save to history
339+
handleHistorySave(message, humanMessageId, body.focusMode, body.files);
340+
341+
console.log(`YouTube processing completed successfully, returning response (ID: ${requestId})`);
342+
343+
// Return the response and EXIT - no fallback to normal search
344+
return new Response(responseStream.readable, {
345+
headers: {
346+
'Content-Type': 'text/event-stream',
347+
Connection: 'keep-alive',
348+
'Cache-Control': 'no-cache, no-transform',
349+
},
350+
});
351+
352+
} catch (error) {
353+
console.error('Error processing YouTube link:', error);
354+
console.log('YouTube processing failed, will continue to normal search');
355+
// Continue to normal search if YouTube processing fails
356+
}
357+
}
358+
264359
const handler = searchHandlers[body.focusMode];
265360

266361
if (!handler) {

0 commit comments

Comments
 (0)