← back to Letsbegin
app/api/url-info/route.ts
124 lines
import { NextRequest, NextResponse } from 'next/server'
import { logGemini } from '@/lib/cost-track'
// Gemini API configuration (FREE - no credits needed!)
const GEMINI_API_KEY = process.env.GEMINI_API_KEY
if (!GEMINI_API_KEY) {
throw new Error('GEMINI_API_KEY environment variable is required')
}
const GEMINI_ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent'
async function callGemini(prompt: string): Promise<string> {
const response = await fetch(`${GEMINI_ENDPOINT}?key=${GEMINI_API_KEY}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { temperature: 0.3, maxOutputTokens: 1024 }
})
})
if (!response.ok) {
const error = await response.text()
throw new Error(`Gemini API error: ${error}`)
}
const data = await response.json()
logGemini(data, { note: 'url-info' })
return data.candidates?.[0]?.content?.parts?.[0]?.text || ''
}
export async function POST(request: NextRequest) {
try {
let body: { url?: string }
try {
body = await request.json()
} catch {
return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 })
}
const { url } = body
if (!url || !url.startsWith('http')) {
return NextResponse.json({ error: 'Invalid URL' }, { status: 400 })
}
// Fetch the URL content
let pageContent = ''
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; LetsBegin/1.0; +https://letsbegin.app)',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
},
signal: AbortSignal.timeout(10000) // 10 second timeout
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const html = await response.text()
// Extract text content (simple HTML stripping)
pageContent = html
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.substring(0, 15000) // Limit content size
} catch (fetchError: any) {
console.error('Fetch error:', fetchError)
// If fetch fails, use Claude to analyze based on URL alone
pageContent = `Unable to fetch page content. URL: ${url}`
}
// Use Gemini to extract structured info
const aiPrompt = `Analyze this webpage/app and extract key information. If the content couldn't be fetched, make reasonable inferences from the URL.
URL: ${url}
Page Content (truncated):
${pageContent}
Respond in this exact JSON format only, no other text:
{
"title": "App/Site name",
"description": "One sentence description of what it does",
"features": ["feature 1", "feature 2", "feature 3", "feature 4", "feature 5"]
}
Extract the most notable features that would be useful for someone building a similar app. Focus on user-facing functionality.`
const responseText = await callGemini(aiPrompt)
try {
// Extract JSON from response (handle markdown code blocks)
let jsonStr = responseText
const jsonMatch = responseText.match(/\{[\s\S]*\}/)
if (jsonMatch) {
jsonStr = jsonMatch[0]
}
const parsed = JSON.parse(jsonStr)
return NextResponse.json({
title: parsed.title || 'Unknown',
description: parsed.description || 'No description available',
features: Array.isArray(parsed.features) ? parsed.features : []
})
} catch (parseError) {
console.error('Parse error:', parseError)
return NextResponse.json({
title: new URL(url).hostname,
description: 'Could not extract description',
features: []
})
}
} catch (error: any) {
console.error('URL info error:', error)
return NextResponse.json({ error: error.message }, { status: 500 })
}
}