← 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 })
  }
}