Identify Clips API & Component
YouTube video submission and clip identification interface
Identify Clips Feature
The Identify Clips feature allows users to submit YouTube videos for AI-powered viral moment detection. This document covers the submission interface and API endpoint.
Upload Page Component
The main entry point for submitting videos for clip identification.
Location: /app/dashboard/clips/page.tsx
Component Structure
export default function ClipsPage() {
const router = useRouter();
const [youtubeUrl, setYoutubeUrl] = useState('');
const [prompt, setPrompt] = useState('');
const [isLoading, setIsLoading] = useState(false);
// Form submission handler
const handleSubmit = async (e: React.FormEvent) => {
// ... validation and API call
};
return (
// UI components
);
}How It Works
Step 1: Form Validation
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Check if URL is empty
if (!youtubeUrl || !youtubeUrl.trim()) {
toast.error('Please enter a YouTube video URL');
return;
}
// Validate YouTube URL format
const validation = validateYoutubeVideoUrl(youtubeUrl);
if (!validation.isValid) {
toast.error(validation.error || 'Please enter a valid YouTube video URL');
return;
}
setIsLoading(true);
// ... proceed with API call
};Validation Checks:
- URL is not empty
- URL matches YouTube video format
- URL contains valid video ID
- Uses
validateYoutubeVideoUrl()utility
Step 2: API Request
try {
const response = await fetch('/api/clips/identify', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
youtubeUrl,
prompt
})
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to start clip identification');
}
toast.success('Video processing started. This may take a few minutes.');
// Reset form
setYoutubeUrl('');
setPrompt('');
// Redirect to videos list
router.push('/dashboard/clips/videos');
} catch (error) {
console.error('Error:', error);
toast.error(
error instanceof Error ? error.message : 'Failed to start processing'
);
} finally {
setIsLoading(false);
}API Call Flow:
- POSTs to
/api/clips/identify - Sends YouTube URL and optional prompt
- Receives success confirmation
- Shows success notification
- Resets form fields
- Redirects to videos list page
- Background processing starts via Inngest
Step 3: Error Handling
- Network errors caught and displayed
- API errors shown via toast notifications
- Loading state prevents duplicate submissions
- Finally block ensures loading state reset
UI Layout
Header Section:
<div className='mb-10 text-center'>
<div className='mb-4 flex items-center justify-center gap-3'>
<div className='bg-primary/10 rounded-full p-3'>
<Youtube className='text-primary h-8 w-8' />
</div>
<h1 className='from-primary to-primary/60 bg-gradient-to-r bg-clip-text text-4xl font-bold text-transparent'>
Upload YouTube Link
</h1>
</div>
<p className='text-muted-foreground mx-auto max-w-2xl text-lg'>
Transform your YouTube videos into viral-worthy clips with
AI-powered analysis
</p>
</div>Shows centered title with YouTube icon and description.
Feature Cards:
<div className='mb-8 grid grid-cols-1 gap-6 lg:grid-cols-3'>
<Card className='border-2'>
<CardContent className='pt-6 text-center'>
<div className='mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-blue-500/10'>
<Sparkles className='h-6 w-6 text-blue-500' />
</div>
<h3 className='mb-2 font-semibold'>AI-Powered Analysis</h3>
<p className='text-muted-foreground text-sm'>
Advanced algorithms identify the most engaging moments
</p>
</CardContent>
</Card>
// Two more feature cards...
</div>Three cards highlighting key features:
- AI-Powered Analysis: Algorithm highlights
- Precise Timestamps: Exact clip boundaries
- Detailed Insights: Scores, summaries, topics
Main Form:
<Card className='border-2 shadow-lg'>
<CardHeader className='bg-muted/50 border-b'>
<CardTitle className='flex items-center gap-2 text-2xl'>
<Youtube className='h-6 w-6 text-red-500' />
Video Information
</CardTitle>
<CardDescription className='text-base'>
Enter a YouTube URL and optional custom prompt to guide the AI
</CardDescription>
</CardHeader>
<CardContent className='pt-6'>
<form onSubmit={handleSubmit} className='space-y-8'>
{/* URL Input */}
<div className='space-y-3'>
<Label htmlFor='youtubeUrl'>YouTube URL *</Label>
<Input
id='youtubeUrl'
type='url'
placeholder='https://www.youtube.com/watch?v=dQw4w9WgXcQ'
value={youtubeUrl}
onChange={(e) => setYoutubeUrl(e.target.value)}
required
disabled={isLoading}
/>
<p className='text-muted-foreground text-sm'>
Paste the full URL of the YouTube video
</p>
</div>
{/* Prompt Input */}
<div className='space-y-3'>
<Label htmlFor='prompt'>Custom Prompt (Optional)</Label>
<Textarea
id='prompt'
placeholder='e.g., Focus on emotional moments, technical explanations, or funny segments...'
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
disabled={isLoading}
rows={4}
/>
<p className='text-muted-foreground text-sm'>
Add specific instructions to guide the AI
</p>
</div>
{/* Action Buttons */}
<div className='flex gap-4 pt-4'>
<Button
type='submit'
disabled={isLoading || !youtubeUrl}
size='lg'
className='h-12 flex-1'
>
{isLoading ? (
<>
<Loader2 className='mr-2 h-5 w-5 animate-spin' />
Processing Video...
</>
) : (
<>
<Sparkles className='mr-2 h-5 w-5' />
Identify Viral Clips
</>
)}
</Button>
<Button
type='button'
variant='outline'
size='lg'
onClick={() => router.push('/dashboard/clips/videos')}
disabled={isLoading}
>
<FolderOpen className='mr-2 h-5 w-5' />
View Spaces
</Button>
</div>
</form>
</CardContent>
</Card>Form Fields:
- YouTube URL (required): Text input for video URL
- Custom Prompt (optional): Textarea for AI instructions
- Submit Button: Disabled when loading or URL empty
- View Spaces Button: Navigate to existing videos
Info Section:
<div className='bg-muted/50 mt-8 rounded-lg border p-6'>
<h3 className='mb-3 flex items-center gap-2 font-semibold'>
<Video className='h-5 w-5' />
How it works
</h3>
<ol className='text-muted-foreground ml-7 space-y-2 text-sm'>
<li className='list-decimal'>Submit your YouTube video URL</li>
<li className='list-decimal'>
AI analyzes the entire video for viral moments
</li>
<li className='list-decimal'>
Receive clips with timestamps, scores, and summaries
</li>
<li className='list-decimal'>
Preview and manage your clips in the Spaces section
</li>
</ol>
</div>Explains the process to users in 4 steps.
Identify Clips API Route
Location: /app/api/clips/identify/route.ts
Implementation
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@clerk/nextjs/server';
import { inngest } from '@/lib/inngest';
import { z } from 'zod';
const identifyClipsSchema = z.object({
youtubeUrl: z.string().url(),
prompt: z.string().optional().default('')
});
export async function POST(req: NextRequest) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const body = await req.json();
const validation = identifyClipsSchema.safeParse(body);
if (!validation.success) {
return NextResponse.json(
{ error: 'Invalid request', details: validation.error.issues },
{ status: 400 }
);
}
const { youtubeUrl, prompt } = validation.data;
// Trigger the Inngest function
await inngest.send({
name: 'video/identify.clips',
data: {
youtubeUrl,
prompt,
userId
}
});
return NextResponse.json({
success: true,
message: 'Video processing started'
});
} catch (error) {
console.error('Error identifying clips:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}How It Works
Step 1: Authentication
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}Uses Clerk to authenticate user. Returns 401 if not logged in.
Step 2: Request Validation
const identifyClipsSchema = z.object({
youtubeUrl: z.string().url(),
prompt: z.string().optional().default('')
});
const body = await req.json();
const validation = identifyClipsSchema.safeParse(body);
if (!validation.success) {
return NextResponse.json(
{ error: 'Invalid request', details: validation.error.issues },
{ status: 400 }
);
}Uses Zod schema to validate:
youtubeUrl: Must be valid URL stringprompt: Optional string, defaults to empty
Returns 400 with validation errors if invalid.
Step 3: Trigger Inngest Function
await inngest.send({
name: 'video/identify.clips',
data: {
youtubeUrl,
prompt,
userId
}
});Sends event to Inngest to trigger background processing:
- Event name:
'video/identify.clips' - Event data: URL, prompt, user ID
- Non-blocking: Returns immediately without waiting for processing
Step 4: Success Response
return NextResponse.json({
success: true,
message: 'Video processing started'
});Returns success immediately. Actual processing happens asynchronously.
Error Handling:
catch (error) {
console.error('Error identifying clips:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}Catches unexpected errors and returns 500.
Custom Prompt Usage
The optional prompt field allows users to guide the AI's clip identification:
Examples:
- "Focus on emotional moments and audience reactions"
- "Identify technical explanations and tutorials"
- "Find funny segments and comedic timing"
- "Look for action-packed or dramatic moments"
- "Highlight key insights and takeaways"
How AI Uses Prompt:
- Sent to external clip identification API
- AI adjusts focus during analysis
- May affect virality scores
- Influences clip selection criteria
Default Behavior (empty prompt):
- AI uses general viral content detection
- Looks for emotional peaks, engaging moments
- Considers pacing, energy, and content variety
Videos List Page
After submission, users are redirected to the videos list to track processing.
Location: /app/dashboard/clips/videos/page.tsx
Key Features
Auto-Refresh:
useEffect(() => {
fetchVideos();
// Poll every 5 seconds for updates
const interval = setInterval(fetchVideos, 5000);
return () => clearInterval(interval);
}, []);Polls API every 5 seconds to check if clips have been identified.
Processing Indicator:
{video.totalClips === 0 && (
<Badge variant='secondary' className='w-full justify-center'>
Processing...
</Badge>
)}Shows "Processing..." badge when totalClips is 0 (not yet complete).
Video Cards:
- YouTube thumbnail preview
- Video ID snippet
- Creation timestamp
- Clips count badge
- Duration and language badges
- Delete button (hover to show)
Empty State:
{videos.length === 0 ? (
<Card>
<CardContent className='flex flex-col items-center justify-center py-16'>
<Video className='text-muted-foreground mb-4 h-16 w-16' />
<h3 className='mb-2 text-xl font-semibold'>No videos yet</h3>
<p className='text-muted-foreground mb-4'>
Start by adding your first YouTube video
</p>
<Button onClick={() => router.push('/dashboard/clips')}>
<Plus className='mr-2 h-4 w-4' />
Add Video
</Button>
</CardContent>
</Card>
) : (
// Video grid
)}Shows empty state with "Add Video" button if no videos exist.
Response Status Flow
Immediate Response
{
"success": true,
"message": "Video processing started"
}User receives this instantly (< 100ms).
Background Processing
- Inngest receives event
identifyClipsfunction executes- Video downloaded from YouTube
- Database record created
- External API analyzes video
- Clips saved to database
- Total clips count updated
Polling Updates
- Frontend polls every 5 seconds
- Checks if
totalClips > 0 - Updates UI when clips available
- User can click to view details
Error Scenarios
Invalid URL:
- Caught by Zod validation
- Returns 400 with error details
- User sees toast notification
Unauthenticated:
- Returns 401 Unauthorized
- User redirected to login
YouTube Video Unavailable:
- Error occurs in Inngest function
- Video marked as failed in database
- User sees processing error in UI
API Rate Limit:
- Handled by Inngest retry logic
- Automatic exponential backoff
- Eventually succeeds or fails after max retries
Network Timeout:
- Inngest handles timeouts
- Retries automatically
- Shows error if all retries fail
Best Practices
User Experience:
- Clear loading states during submission
- Success feedback before redirect
- Auto-refresh on videos page
- Processing indicators for pending videos
Performance:
- Non-blocking API calls (Inngest)
- Efficient polling (5-second intervals)
- Lazy loading for video thumbnails
Error Handling:
- Validate before submission
- Show specific error messages
- Allow retry on failure
- Graceful degradation
Security:
- Authenticate all requests
- Validate input with Zod
- User can only see own videos
- Prevent injection attacks