diff --git a/src/app/api/analyze/route.ts b/src/app/api/analyze/route.ts index 3a4849e..8c08ca4 100644 --- a/src/app/api/analyze/route.ts +++ b/src/app/api/analyze/route.ts @@ -1,24 +1,45 @@ // src/app/api/analyze/route.ts - import { NextRequest, NextResponse } from 'next/server' import { analyzeImageWithVision } from '../../../services/googleVisionCore' import { processAnimalImage } from '../../../services/animalProcessingService' +// Increase body parser size limit +export const config = { + api: { + bodyParser: { + sizeLimit: '10mb' + } + } +} + export async function POST(req: NextRequest): Promise { try { + // Log incoming request details + console.log('Incoming request:', { + method: req.method, + contentType: req.headers.get('content-type'), + contentLength: req.headers.get('content-length') + }) + + // Parse form data const formData = await req.formData() const imageFile = formData.get('image') - // Robust type checking + // Validate image file if (!imageFile || !(imageFile instanceof File)) { + console.error('Invalid file upload:', { + imageFile: imageFile ? 'Exists' : 'Not found', + type: imageFile ? typeof imageFile : 'undefined' + }) + return NextResponse.json( { error: 'No valid image provided' }, { status: 400 } ) } - // Log file details for debugging - console.log('Received file:', { + // Log file details + console.log('Uploaded file details:', { name: imageFile.name, type: imageFile.type, size: imageFile.size @@ -28,23 +49,42 @@ export async function POST(req: NextRequest): Promise { const arrayBuffer = await imageFile.arrayBuffer() const buffer = Buffer.from(arrayBuffer) - // Get vision analysis + // Log buffer details + console.log('Buffer details:', { + bufferLength: buffer.length + }) + + // Validate buffer size + const MAX_BUFFER_SIZE = 10 * 1024 * 1024 // 10MB + if (buffer.length > MAX_BUFFER_SIZE) { + console.error('Buffer too large:', { + bufferLength: buffer.length, + maxAllowedSize: MAX_BUFFER_SIZE + }) + + return NextResponse.json( + { error: 'Image file is too large' }, + { status: 413 } + ) + } + + // Analyze image const visionResult = await analyzeImageWithVision(buffer) - // Construct the response message + // Construct response message const responseMessage = `${visionResult.description}\n\n${ visionResult.isAnimal ? "✨ This image contains wildlife and has been added to our registry! Thank you for contributing to our wildlife database." : "🌿 No wildlife detected in this image. Try uploading a photo of an animal!" }` - // Send response to user + // Prepare user response const userResponse = NextResponse.json({ description: responseMessage, isAnimal: visionResult.isAnimal }) - // If animal detected, process in background + // Background processing for animal images if (visionResult.isAnimal) { processAnimalImage( buffer, @@ -57,7 +97,14 @@ export async function POST(req: NextRequest): Promise { return userResponse } catch (error) { - console.error('Analysis failed:', error) + // Comprehensive error logging + console.error('Analysis failed:', { + errorName: error instanceof Error ? error.name : 'Unknown Error', + errorMessage: error instanceof Error ? error.message : String(error), + errorStack: error instanceof Error ? error.stack : 'No stack trace' + }) + + // Return user-friendly error response return NextResponse.json( { error: 'Failed to analyze image' }, { status: 500 } @@ -65,4 +112,5 @@ export async function POST(req: NextRequest): Promise { } } +// Ensure dynamic routing export const dynamic = 'force-dynamic' diff --git a/src/services/googleVisionService.ts b/src/services/googleVisionService.ts index f609607..f84cf19 100644 --- a/src/services/googleVisionService.ts +++ b/src/services/googleVisionService.ts @@ -23,22 +23,19 @@ export const VISION_CONFIG: VisionConfig = { export async function analyzeImage(imageBuffer: Buffer, filename: string): Promise { try { + // Create FormData using more universal approach const formData = new FormData() - - // Create file from buffer using Blob + + // Create Blob with explicit JPEG type const blob = new Blob([imageBuffer], { type: 'image/jpeg' }) - - // Detailed logging for debugging - console.log('Analyzing image:', { - filename, - bufferLength: imageBuffer.length, - blobSize: blob.size, - blobType: blob.type - }) - formData.append('image', blob, filename) + // Use File constructor for maximum compatibility + const file = new File([blob], filename, { type: 'image/jpeg' }) - // Log FormData contents (if possible) + // Append file to FormData + formData.append('image', file, filename) + + // Log FormData contents for debugging for (const [key, value] of formData.entries()) { console.log('FormData entry:', key, value) } @@ -48,23 +45,19 @@ export async function analyzeImage(imageBuffer: Buffer, filename: string): Promi body: formData }) - // Log full response for debugging - const responseText = await response.text() - console.log('Raw response:', responseText) - if (!response.ok) { - try { - const errorData = JSON.parse(responseText) - throw new Error(errorData.error || 'Failed to analyze image') - } catch { - throw new Error(`HTTP error ${response.status}: ${responseText}`) - } + // More detailed error handling + const errorText = await response.text() + console.error('Analysis response error:', { + status: response.status, + statusText: response.statusText, + errorText + }) + + throw new Error(errorText || 'Failed to analyze image') } - // Parse response - const result = JSON.parse(responseText) - return result - + return await response.json() } catch (error) { console.error('Vision analysis error:', error) return { diff --git a/src/services/imageService.ts b/src/services/imageService.ts index 0fc5cbe..a081fe0 100644 --- a/src/services/imageService.ts +++ b/src/services/imageService.ts @@ -1,6 +1,6 @@ +// src/services/imageService.ts import imageCompression from 'browser-image-compression' import * as FileType from 'file-type' -import { analyzeImage, VisionAnalysisResult } from './googleVisionService' export interface ImageConversionOptions { maxSizeMB?: number; @@ -13,48 +13,44 @@ export async function normalizeImageUpload( options: ImageConversionOptions = {} ): Promise { try { - // Default conversion options + // Detailed initial logging + console.log('Original File Details:', { + name: file.name, + type: file.type, + size: file.size + }) + + // Default conversion options with cross-browser considerations const defaultOptions = { - maxSizeMB: 2, // Max file size - maxWidthOrHeight: 1920, // Max image dimensions + maxSizeMB: 2, // Keep original size limit + maxWidthOrHeight: 1920, // Standard max dimension useWebWorker: true } const mergedOptions = { ...defaultOptions, ...options } - // Step 1: Detect file type - const detectedType = await FileType.fileTypeFromBlob(file) - console.log('Detected file type:', detectedType) - - // List of supported input types - const supportedTypes = [ - 'image/jpeg', - 'image/png', - 'image/gif', - 'image/webp', - 'image/heif', - 'image/heic' - ] - - // Validate file type - if (!supportedTypes.includes(file.type) && - !(detectedType && supportedTypes.includes(detectedType.mime))) { - throw new Error('Unsupported image type') - } - - // Step 2: Compress image + // Force conversion to ensure compatibility const compressedFile = await imageCompression(file, { - maxSizeMB: mergedOptions.maxSizeMB, - maxWidthOrHeight: mergedOptions.maxWidthOrHeight, - useWebWorker: mergedOptions.useWebWorker, - // Explicitly convert to JPEG by forcing type conversion - initialQuality: 0.8, // Add some quality control + ...mergedOptions, + fileType: 'image/jpeg', // Force JPEG + initialQuality: 0.8 }) - // Step 3: Create a new JPEG file - return new File([compressedFile], 'converted-image.jpg', { - type: 'image/jpeg' + // Create a new File object with explicit JPEG type + const normalizedImage = new File( + [compressedFile], + 'converted-image.jpg', + { type: 'image/jpeg' } + ) + + // Logging normalized file details + console.log('Normalized File Details:', { + name: normalizedImage.name, + type: normalizedImage.type, + size: normalizedImage.size }) + + return normalizedImage } catch (error) { console.error('Image normalization error:', error) throw error