← back to Letsbegin

lib/gemini-image.ts

158 lines

/**
 * Gemini Image Analysis Utility
 * Uses Gemini 2.0 Flash for all image-related AI tasks
 * 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'

export interface ImageAnalysisResult {
  description: string
  colors: string[]
  styles: string[]
  patterns: string[]
  backgroundColor: string
  tags: string[]
}

/**
 * Analyze an image using Gemini Vision
 * @param imageBase64 - Base64 encoded image data (without data URL prefix)
 * @param mimeType - Image MIME type (image/jpeg, image/png, etc.)
 * @param prompt - Custom prompt for analysis
 */
export async function analyzeImage(
  imageBase64: string,
  mimeType: string = 'image/jpeg',
  prompt?: string
): Promise<string> {
  const defaultPrompt = prompt || 'Describe this image in detail.'

  const response = await fetch(`${GEMINI_ENDPOINT}?key=${GEMINI_API_KEY}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      contents: [{
        parts: [
          { inlineData: { mimeType, data: imageBase64 } },
          { text: defaultPrompt }
        ]
      }],
      generationConfig: { temperature: 0.1, maxOutputTokens: 1000 }
    })
  })

  if (!response.ok) {
    const error = await response.text()
    throw new Error(`Gemini Vision API error: ${error}`)
  }

  const data = await response.json()
  return data.candidates?.[0]?.content?.parts?.[0]?.text || ''
}

/**
 * Analyze wallcovering/wallpaper image for interior design tagging
 * Returns structured analysis with colors, styles, patterns
 */
export async function analyzeWallcovering(
  imageBase64: string,
  productTitle: string,
  mimeType: string = 'image/jpeg'
): Promise<ImageAnalysisResult> {
  const prompt = `As an expert interior designer, analyze this wallcovering "${productTitle}".
Return ONLY valid JSON with this exact structure:
{
  "backgroundColor": "single dominant background color",
  "colors": ["max 3 most visible colors"],
  "styles": ["1-2 design styles from: Traditional, Contemporary, Coastal, Tropical, Art Deco, Mid-Century Modern, Bohemian, Transitional, Victorian, Chinoiserie, Retro, Pop Art"],
  "patterns": ["1-2 pattern types from: Botanical, Floral, Geometric, Damask, Palm, Trellis, Stripe, Abstract, Toile, Scenic, Solid, Textured"],
  "tags": ["3-5 interior design tags for search"],
  "description": "2-3 sentence professional interior designer description for commercial applications"
}`

  const response = await fetch(`${GEMINI_ENDPOINT}?key=${GEMINI_API_KEY}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      contents: [{
        parts: [
          { inlineData: { mimeType, data: imageBase64 } },
          { text: prompt }
        ]
      }],
      generationConfig: { temperature: 0.1, maxOutputTokens: 800 }
    })
  })

  if (!response.ok) {
    const error = await response.text()
    throw new Error(`Gemini Vision API error: ${error}`)
  }

  const data = await response.json()
  const text = data.candidates?.[0]?.content?.parts?.[0]?.text || ''

  // Parse JSON from response
  try {
    const jsonMatch = text.match(/\{[\s\S]*\}/)
    if (jsonMatch) {
      return JSON.parse(jsonMatch[0])
    }
  } catch (e) {
    console.error('Failed to parse Gemini response:', e)
  }

  // Return default structure if parsing fails
  return {
    description: text,
    colors: [],
    styles: [],
    patterns: [],
    backgroundColor: '',
    tags: []
  }
}

/**
 * Get image from URL as base64
 */
export async function fetchImageAsBase64(imageUrl: string): Promise<{ base64: string; mimeType: string }> {
  const response = await fetch(imageUrl)
  if (!response.ok) {
    throw new Error(`Failed to fetch image: ${response.status}`)
  }

  const buffer = await response.arrayBuffer()
  const base64 = Buffer.from(buffer).toString('base64')
  const mimeType = response.headers.get('content-type') || 'image/jpeg'

  return { base64, mimeType }
}

/**
 * Analyze image from URL
 */
export async function analyzeImageFromUrl(
  imageUrl: string,
  prompt?: string
): Promise<string> {
  const { base64, mimeType } = await fetchImageAsBase64(imageUrl)
  return analyzeImage(base64, mimeType, prompt)
}

/**
 * Analyze wallcovering from URL
 */
export async function analyzeWallcoveringFromUrl(
  imageUrl: string,
  productTitle: string
): Promise<ImageAnalysisResult> {
  const { base64, mimeType } = await fetchImageAsBase64(imageUrl)
  return analyzeWallcovering(base64, productTitle, mimeType)
}