mirror of
https://github.com/mito-systems/ranger-app.git
synced 2026-05-07 12:04:16 +00:00
try now
Some checks failed
Publish Ranger template to Laconic Registry / laconic_publish (push) Failing after 1m5s
Some checks failed
Publish Ranger template to Laconic Registry / laconic_publish (push) Failing after 1m5s
This commit is contained in:
parent
2649cee033
commit
9a44248f51
@ -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<NextResponse> {
|
||||
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<NextResponse> {
|
||||
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<NextResponse> {
|
||||
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<NextResponse> {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure dynamic routing
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@ -23,22 +23,19 @@ export const VISION_CONFIG: VisionConfig = {
|
||||
|
||||
export async function analyzeImage(imageBuffer: Buffer, filename: string): Promise<VisionAnalysisResult> {
|
||||
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 {
|
||||
|
||||
@ -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<File> {
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user