changes: Request cancelation and handle reset

This commit is contained in:
2025-10-08 23:12:39 -06:00
parent 1299da1a1a
commit c694c15dad
2 changed files with 81 additions and 8 deletions

View File

@@ -2,7 +2,7 @@
import { useEffect, useRef, useState, useCallback, Suspense } from 'react';
import { useSearchParams } from 'next/navigation';
import { Camera, History, VideoOff, Settings, Video } from 'lucide-react';
import { Camera, History, VideoOff, Settings, Video, X } from 'lucide-react';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { type Shoe } from '@/lib/shoe-database';
@@ -31,6 +31,7 @@ function HomePageContent() {
const [isSettingsPanelOpen, setSettingsPanelOpen] = useState(false);
const [isScanning, setIsScanning] = useState(false);
const [notFoundMessage, setNotFoundMessage] = useState(false);
const abortControllerRef = useRef<AbortController | null>(null);
// Effect to clean up the stream when component unmounts or stream changes
useEffect(() => {
@@ -164,6 +165,22 @@ function HomePageContent() {
const handleScan = async () => {
if (!videoRef.current || isScanning) return;
// 1. Cancelar petición anterior si existe
if (abortControllerRef.current) {
console.log('🚫 Cancelando petición anterior...');
abortControllerRef.current.abort();
}
// 2. Crear nuevo AbortController para esta petición
const controller = new AbortController();
abortControllerRef.current = controller;
// 3. Configurar timeout de 30 segundos
const timeoutId = setTimeout(() => {
console.log('⏱️ Timeout alcanzado - cancelando petición...');
controller.abort();
}, 30000);
try {
setIsScanning(true);
setNotFoundMessage(false); // Limpiar mensaje anterior
@@ -181,8 +198,12 @@ function HomePageContent() {
console.log('🔍 Llamando al servidor Python para identificar SKU...');
// Call SKU identification service
const sku = await skuIdentificationService.identifySKU(imageData);
// Call SKU identification service with abort signal
const sku = await skuIdentificationService.identifySKU(imageData, controller.signal);
// Limpiar timeout si la petición completó antes
clearTimeout(timeoutId);
console.log('📦 SKU result:', sku);
if (sku) {
@@ -217,6 +238,15 @@ function HomePageContent() {
}
} catch (error) {
// Limpiar timeout en caso de error
clearTimeout(timeoutId);
// No mostrar error si fue cancelación
if (error instanceof Error && error.name === 'AbortError') {
console.log('🚫 Escaneo cancelado');
return;
}
console.error('❌ Error en identificación:', error);
setDetectedSKU(null);
setNotFoundMessage(true);
@@ -227,6 +257,16 @@ function HomePageContent() {
}, 3000);
} finally {
setIsScanning(false);
abortControllerRef.current = null;
}
};
const handleCancelScan = () => {
if (abortControllerRef.current) {
console.log('🚫 Usuario canceló el escaneo');
abortControllerRef.current.abort();
setIsScanning(false);
abortControllerRef.current = null;
}
};
@@ -507,22 +547,46 @@ function HomePageContent() {
{/* Main Capture Button - Larger */}
<button
onClick={handleScan}
disabled={isScanning}
className='group relative'
>
<div className="w-16 h-16 bg-gradient-to-br from-red-500/40 to-pink-500/40 rounded-full flex items-center justify-center border-2 border-white/40 hover:from-red-500/60 hover:to-pink-500/60 transition-all duration-300 transform hover:scale-110 shadow-2xl">
<div className={`w-16 h-16 bg-gradient-to-br rounded-full flex items-center justify-center border-2 border-white/40 transition-all duration-300 shadow-2xl ${
isScanning
? 'from-blue-500/40 to-purple-500/40 animate-pulse cursor-not-allowed'
: 'from-red-500/40 to-pink-500/40 hover:from-red-500/60 hover:to-pink-500/60 transform hover:scale-110'
}`}>
<div className="w-12 h-12 bg-white/20 rounded-full flex items-center justify-center">
<Camera size={28} className="text-white drop-shadow-lg" />
</div>
</div>
<div className="absolute -top-12 left-1/2 transform -translate-x-1/2 bg-black/80 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap">
Detectar Zapato
{isScanning ? 'Analizando...' : 'Detectar Zapato'}
</div>
{/* Pulsing Ring */}
<div className="absolute inset-0 rounded-full border-2 border-red-400/50 animate-ping"></div>
{/* Pulsing Ring - solo cuando NO está escaneando */}
{!isScanning && (
<div className="absolute inset-0 rounded-full border-2 border-red-400/50 animate-ping"></div>
)}
{/* Glow Effect */}
<div className="absolute inset-0 bg-gradient-to-br from-red-500/30 to-pink-500/30 rounded-full blur opacity-0 group-hover:opacity-100 transition-opacity duration-300 transform scale-150"></div>
</button>
{/* Cancel Button - solo visible cuando está escaneando */}
{isScanning && (
<button
onClick={handleCancelScan}
className='group relative animate-in fade-in slide-in-from-bottom-2 duration-300'
>
<div className="w-12 h-12 bg-gradient-to-br from-orange-500/40 to-red-500/40 rounded-full flex items-center justify-center border border-white/40 hover:from-orange-500/60 hover:to-red-500/60 transition-all duration-300 transform hover:scale-110">
<X size={20} className="text-white drop-shadow-sm" />
</div>
<div className="absolute -top-10 left-1/2 transform -translate-x-1/2 bg-black/80 text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap">
Cancelar
</div>
{/* Glow Effect */}
<div className="absolute inset-0 bg-gradient-to-br from-orange-500/20 to-red-500/20 rounded-full blur opacity-0 group-hover:opacity-100 transition-opacity duration-300 transform scale-150"></div>
</button>
)}
{/* History Button */}
<button
onClick={() => setHistoryOpen(true)}

View File

@@ -64,9 +64,10 @@ export class SKUIdentificationService {
/**
* 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): Promise<string | null> {
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);
@@ -101,6 +102,7 @@ export class SKUIdentificationService {
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
@@ -154,6 +156,13 @@ export class SKUIdentificationService {
}
} 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',