230 lines
8.8 KiB
TypeScript
230 lines
8.8 KiB
TypeScript
interface SKUResponse {
|
|
status: boolean;
|
|
SKU: string | null;
|
|
}
|
|
|
|
/**
|
|
* Service for identifying product SKUs from shoe images using external API
|
|
*/
|
|
export class SKUIdentificationService {
|
|
private readonly API_ENDPOINT: string;
|
|
private isDevMode: boolean = false;
|
|
private readonly DEV_SKU: string = '171312'; // Hardcoded SKU for DEV mode
|
|
|
|
constructor() {
|
|
// Construir endpoint correctamente, manejando si la URL base termina con / o no
|
|
const baseUrl = process.env.NEXT_PUBLIC_PYTHON_SERVER_URL || '';
|
|
const cleanBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
this.API_ENDPOINT = `${cleanBaseUrl}/predictfile`;
|
|
|
|
console.log('🔧 SKU Service inicializado:');
|
|
console.log(' Base URL:', baseUrl);
|
|
console.log(' Endpoint final:', this.API_ENDPOINT);
|
|
}
|
|
|
|
/**
|
|
* Enable or disable DEV mode
|
|
* In DEV mode, identifySKU will return a hardcoded SKU without calling the API
|
|
*/
|
|
setDevMode(isDev: boolean): void {
|
|
this.isDevMode = isDev;
|
|
console.log(`🔧 DEV Mode ${isDev ? 'ACTIVADO' : 'DESACTIVADO'}`);
|
|
if (isDev) {
|
|
console.log(` SKU hardcodeado: ${this.DEV_SKU}`);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Validate API response format
|
|
* Expected format: { "status": true, "SKU": "171312" }
|
|
*/
|
|
private validateResponse(data: any): data is SKUResponse {
|
|
// Check if data is an object
|
|
if (!data || typeof data !== 'object') {
|
|
console.error('❌ Invalid response: not an object', data);
|
|
return false;
|
|
}
|
|
|
|
// Check if 'status' field exists
|
|
if (!('status' in data)) {
|
|
console.error('❌ Invalid response: missing "status" field', data);
|
|
return false;
|
|
}
|
|
|
|
// Validate status is boolean
|
|
if (typeof data.status !== 'boolean') {
|
|
console.error('❌ Invalid response: "status" must be boolean', data);
|
|
return false;
|
|
}
|
|
|
|
// Check if 'SKU' field exists
|
|
if (!('SKU' in data)) {
|
|
console.error('❌ Invalid response: missing "SKU" field', data);
|
|
return false;
|
|
}
|
|
|
|
// Validate SKU is string or null
|
|
if (data.SKU !== null && typeof data.SKU !== 'string') {
|
|
console.error('❌ Invalid response: "SKU" must be string or null', data);
|
|
return false;
|
|
}
|
|
|
|
// All validations passed
|
|
console.log('✅ Response format validated successfully');
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Identify product SKU from shoe image
|
|
* @param imageData - Image data captured from video
|
|
* @param signal - Optional AbortSignal for cancellation
|
|
* @returns Promise<string | null> - SKU if found, null otherwise
|
|
*/
|
|
async identifySKU(imageData: ImageData, signal?: AbortSignal): Promise<string | null> {
|
|
try {
|
|
console.log('\n🔍 ========== IDENTIFICACIÓN DE SKU ==========');
|
|
console.log('📊 Dimensiones imagen:', imageData.width, 'x', imageData.height);
|
|
|
|
// DEV MODE: Return hardcoded SKU immediately
|
|
if (this.isDevMode) {
|
|
console.log('🧪 MODO DEV ACTIVADO');
|
|
console.log('✅ Retornando SKU hardcodeado:', this.DEV_SKU);
|
|
console.log('=========================================\n');
|
|
|
|
// Simulate small delay for realistic behavior
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
return this.DEV_SKU;
|
|
}
|
|
|
|
console.log('🌐 Endpoint:', this.API_ENDPOINT);
|
|
console.log('📡 Llamando al API Python (sin cache)...');
|
|
|
|
// Convert ImageData to File synchronously (simplified approach)
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d')!;
|
|
canvas.width = imageData.width;
|
|
canvas.height = imageData.height;
|
|
ctx.putImageData(imageData, 0, 0);
|
|
|
|
// Convert canvas to blob and create File
|
|
const blob = await new Promise<Blob>((resolve) => {
|
|
canvas.toBlob((blob) => resolve(blob!), 'image/jpeg', 0.9); // Higher quality
|
|
});
|
|
|
|
const file = new File([blob], 'shoe.jpg', {
|
|
type: 'image/jpeg',
|
|
lastModified: Date.now()
|
|
});
|
|
|
|
console.log('📸 Imagen convertida a archivo:', file.size, 'bytes');
|
|
|
|
// Prepare form data
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
// Make API call with CORS headers
|
|
const response = await fetch(this.API_ENDPOINT, {
|
|
method: 'POST',
|
|
body: formData,
|
|
mode: 'cors', // Need CORS headers from server
|
|
signal, // Pass AbortSignal for cancellation support
|
|
headers: {
|
|
// Don't set Content-Type, let browser set it with boundary for multipart/form-data
|
|
'ngrok-skip-browser-warning': 'true', // Skip ngrok browser warning
|
|
'Accept': 'application/json', // Tell server we expect JSON
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API call failed: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
console.log('✓ Respuesta recibida - Status:', response.status);
|
|
console.log('✓ Headers:', Object.fromEntries(response.headers.entries()));
|
|
|
|
// Try to get response as text first
|
|
const responseText = await response.text();
|
|
console.log('📄 RESPUESTA RAW del servidor Python:');
|
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
console.log(responseText);
|
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
|
|
// Try to parse as JSON
|
|
let data: any;
|
|
try {
|
|
data = JSON.parse(responseText);
|
|
console.log('✓ Respuesta parseada como JSON:');
|
|
console.log(' - status:', data.status, '(tipo:', typeof data.status, ')');
|
|
console.log(' - SKU:', data.SKU, '(tipo:', typeof data.SKU, ')');
|
|
console.log(' - Objeto completo:', JSON.stringify(data, null, 2));
|
|
} catch (parseError) {
|
|
console.error('❌ Error al parsear JSON:', parseError);
|
|
console.error(' Respuesta recibida:', responseText);
|
|
throw new Error(`Invalid JSON response: ${parseError}`);
|
|
}
|
|
|
|
// Validate response format
|
|
console.log('🔍 Validando formato de respuesta...');
|
|
if (!this.validateResponse(data)) {
|
|
console.error('❌ Validación de formato falló');
|
|
throw new Error('Response format validation failed');
|
|
}
|
|
console.log('✓ Formato de respuesta válido');
|
|
|
|
// ONLY return SKU if status is EXACTLY true (not false, not null, not undefined)
|
|
console.log('🔍 Evaluando condiciones para detectar zapato:');
|
|
console.log(' - ¿data.status === true?', data.status === true);
|
|
console.log(' - ¿data.SKU existe?', !!data.SKU);
|
|
console.log(' - ¿data.SKU es string?', typeof data.SKU === 'string');
|
|
console.log(' - Valor de data.status:', data.status);
|
|
console.log(' - Valor de data.SKU:', data.SKU);
|
|
|
|
if (data.status === true && data.SKU) {
|
|
console.log('✅✅✅ ZAPATO ENCONTRADO ✅✅✅');
|
|
console.log('📦 SKU DETECTADO:', data.SKU);
|
|
console.log('=========================================\n');
|
|
return data.SKU;
|
|
} else {
|
|
console.log('❌❌❌ ZAPATO NO ENCONTRADO ❌❌❌');
|
|
// Log detailed reason for not finding shoe
|
|
if (data.status === false) {
|
|
console.log('💡 RAZÓN: El servidor reportó status: false');
|
|
} else if (data.status === null) {
|
|
console.log('💡 RAZÓN: El servidor reportó status: null');
|
|
} else if (data.status !== true) {
|
|
console.log('💡 RAZÓN: status no es exactamente true, es:', data.status, 'tipo:', typeof data.status);
|
|
} else if (!data.SKU) {
|
|
console.log('💡 RAZÓN: status es true pero SKU es null/vacío/undefined');
|
|
console.log(' SKU value:', data.SKU);
|
|
console.log(' SKU type:', typeof data.SKU);
|
|
} else {
|
|
console.log('💡 RAZÓN: Condición desconocida - status:', data.status, 'SKU:', data.SKU);
|
|
}
|
|
console.log('=========================================\n');
|
|
return null;
|
|
}
|
|
|
|
} catch (error) {
|
|
// Handle AbortError separately (request was cancelled)
|
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
console.log('🚫 PETICIÓN CANCELADA por el usuario o timeout');
|
|
console.log('=========================================\n');
|
|
return null;
|
|
}
|
|
|
|
console.error('❌ ERROR EN IDENTIFICACIÓN:', error);
|
|
console.error(' Detalles:', {
|
|
message: error instanceof Error ? error.message : 'Unknown error',
|
|
endpoint: this.API_ENDPOINT
|
|
});
|
|
console.log('=========================================\n');
|
|
// Return null on any error (network, parsing, validation, etc.)
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export singleton instance
|
|
export const skuIdentificationService = new SKUIdentificationService(); |