From d4c276c8884b1889e11a30505afbcf2f5d5255a1 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Mon, 4 Aug 2025 16:19:00 +0200 Subject: [PATCH] Fix new Frontend 11 --- frontend/src/api/websocket/hooks.ts | 143 +++++++++++------- frontend/src/pages/auth/RegisterPage.tsx | 6 +- .../src/pages/onboarding/OnboardingPage.tsx | 143 +++++++++--------- 3 files changed, 167 insertions(+), 125 deletions(-) diff --git a/frontend/src/api/websocket/hooks.ts b/frontend/src/api/websocket/hooks.ts index 6775db00..ba010dd2 100644 --- a/frontend/src/api/websocket/hooks.ts +++ b/frontend/src/api/websocket/hooks.ts @@ -98,72 +98,102 @@ export const useWebSocket = (config: WebSocketConfig) => { export const useTrainingWebSocket = (jobId: string, tenantId?: string) => { const [jobUpdates, setJobUpdates] = useState([]); - // 🚀 FIX 1: Construct URL with actual tenantId (get from localStorage if not provided) + // Get tenant ID reliably const actualTenantId = tenantId || (() => { - const userData = localStorage.getItem('user_data'); - if (userData) { - try { + try { + const userData = localStorage.getItem('user_data'); + if (userData) { const parsed = JSON.parse(userData); return parsed.current_tenant_id || parsed.tenant_id; - } catch (e) { - console.error('Failed to parse user data for tenant ID:', e); } + + const authData = localStorage.getItem('auth_data'); + if (authData) { + const parsed = JSON.parse(authData); + return parsed.tenant_id; + } + } catch (e) { + console.error('Failed to parse tenant ID from storage:', e); } return null; })(); - const config: WebSocketConfig = { - // 🚀 FIX: Use actual tenant ID instead of placeholder + const config = { url: actualTenantId ? `ws://localhost:8002/api/v1/ws/tenants/${actualTenantId}/training/jobs/${jobId}/live` : `ws://localhost:8002/api/v1/ws/tenants/unknown/training/jobs/${jobId}/live`, reconnect: true, + reconnectInterval: 3000, + maxReconnectAttempts: 10 }; - const { status, connect, disconnect, addMessageHandler, isConnected, lastMessage } = useWebSocket(config); + const { + status, + connect, + disconnect, + addMessageHandler, + isConnected, + lastMessage, + sendMessage + } = useWebSocket(config); - // 🚀 FIX 2: Handle ALL message types, not just specific ones - useEffect(() => { - addMessageHandler((message) => { - console.log('🔥 WebSocket message received:', message); - - // Handle all training-related message types - const trainingMessageTypes = [ - 'progress', 'training_progress', - 'completed', 'training_completed', - 'failed', 'training_failed', - 'error', 'training_error', - 'started', 'training_started', - 'heartbeat', 'initial_status' - ]; - - if (trainingMessageTypes.includes(message.type)) { - console.log('✅ Processing training message:', message.type, message.data); - setJobUpdates(prev => [message, ...prev.slice(0, 99)]); // Keep full message object - } else { - console.log('ℹ️ Unhandled message type:', message.type); - // Still add to updates for debugging - setJobUpdates(prev => [message, ...prev.slice(0, 99)]); - } - }); - }, [addMessageHandler]); - - // 🚀 FIX 3: Log connection attempts and status - useEffect(() => { - console.log('🔌 WebSocket config:', { - url: config.url, - jobId, - tenantId: actualTenantId, - status - }); - }, [config.url, jobId, actualTenantId, status]); - - // 🚀 FIX 4: Debug latest message - useEffect(() => { - if (lastMessage) { - console.log('📨 Latest WebSocket message:', lastMessage); + // Enhanced message handler + const handleWebSocketMessage = useCallback((message: any) => { + // Handle different message structures + let processedMessage = message; + + // If message has nested data, flatten it for easier processing + if (message.data && typeof message.data === 'object') { + processedMessage = { + ...message, + // Merge data properties to root level for backward compatibility + ...message.data + }; } - }, [lastMessage]); + + // Comprehensive message type handling + const trainingMessageTypes = [ + 'progress', 'training_progress', + 'completed', 'training_completed', + 'failed', 'training_failed', + 'error', 'training_error', + 'started', 'training_started', + 'heartbeat', 'initial_status', + 'status_update' + ]; + + if (trainingMessageTypes.includes(message.type)) { + // Add to updates array with processed message + setJobUpdates(prev => { + const newUpdates = [processedMessage, ...prev.slice(0, 49)]; // Keep last 50 messages + return newUpdates; + }); + } else { + // Still add to updates for debugging purposes + setJobUpdates(prev => [processedMessage, ...prev.slice(0, 49)]); + } + }, []); + + // Set up message handler when hook initializes + useEffect(() => { + addMessageHandler(handleWebSocketMessage); + }, [addMessageHandler, handleWebSocketMessage]); + + // Send periodic ping to keep connection alive + useEffect(() => { + if (isConnected) { + const pingInterval = setInterval(() => { + sendMessage({ + type: 'ping', + data: undefined + }); + }, 30000); // Every 30 seconds + + return () => { + clearInterval(pingInterval); + }; + } + }, [isConnected, sendMessage]); return { status, @@ -171,9 +201,16 @@ export const useTrainingWebSocket = (jobId: string, tenantId?: string) => { connect, disconnect, isConnected, - lastMessage, // Expose for debugging - tenantId: actualTenantId, // Expose for debugging - wsUrl: config.url, // Expose for debugging + lastMessage, + tenantId: actualTenantId, + wsUrl: config.url, + // Manual refresh function + refreshConnection: useCallback(() => { + disconnect(); + setTimeout(() => { + connect(); + }, 1000); + }, [connect, disconnect]) }; }; diff --git a/frontend/src/pages/auth/RegisterPage.tsx b/frontend/src/pages/auth/RegisterPage.tsx index e849d4e4..7e75318a 100644 --- a/frontend/src/pages/auth/RegisterPage.tsx +++ b/frontend/src/pages/auth/RegisterPage.tsx @@ -2,8 +2,10 @@ import React, { useState } from 'react'; import { Eye, EyeOff, Loader2, Check } from 'lucide-react'; import toast from 'react-hot-toast'; -import { useAuth } from '../../api/hooks/useAuth'; -import type { RegisterRequest } from '../../api/types'; +import { + useAuth, + RegisterRequest +} from '../../api'; interface RegisterPageProps { onLogin: (user: any, token: string) => void; diff --git a/frontend/src/pages/onboarding/OnboardingPage.tsx b/frontend/src/pages/onboarding/OnboardingPage.tsx index c0ce7dad..0bff0c6e 100644 --- a/frontend/src/pages/onboarding/OnboardingPage.tsx +++ b/frontend/src/pages/onboarding/OnboardingPage.tsx @@ -1,12 +1,11 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { ChevronLeft, ChevronRight, Upload, MapPin, Store, Factory, Check, Brain, Clock, CheckCircle, AlertTriangle, Loader } from 'lucide-react'; import toast from 'react-hot-toast'; import { useTenant, useTraining, - useData, - useAuth, + useData, useTrainingWebSocket, TenantCreate, TrainingJobRequest @@ -57,6 +56,19 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => // Training progress state const [tenantId, setTenantId] = useState(''); const [trainingJobId, setTrainingJobId] = useState(''); + const { createTenant, isLoading: tenantLoading } = useTenant(); + const { startTrainingJob } = useTraining({ disablePolling: true }); + const { uploadSalesHistory, validateSalesData } = useData(); + + const steps = [ + { id: 1, title: 'Datos de Panadería', icon: Store }, + { id: 2, title: 'Productos y Servicios', icon: Factory }, + { id: 3, title: 'Datos Históricos', icon: Upload }, + { id: 4, title: 'Entrenamiento IA', icon: Brain }, + { id: 5, title: 'Configuración Final', icon: Check } + ]; + + const [trainingProgress, setTrainingProgress] = useState({ progress: 0, status: 'pending', @@ -66,10 +78,7 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => estimatedTimeRemaining: 0 }); - const { createTenant, isLoading: tenantLoading } = useTenant(); - const { startTrainingJob } = useTraining({ disablePolling: true }); - const { uploadSalesHistory, validateSalesData } = useData(); - + // WebSocket connection for real-time training updates const { status, @@ -82,74 +91,68 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => wsUrl } = useTrainingWebSocket(trainingJobId || 'pending', tenantId); - const steps = [ - { id: 1, title: 'Datos de Panadería', icon: Store }, - { id: 2, title: 'Productos y Servicios', icon: Factory }, - { id: 3, title: 'Datos Históricos', icon: Upload }, - { id: 4, title: 'Entrenamiento IA', icon: Brain }, - { id: 5, title: 'Configuración Final', icon: Check } - ]; - // Handle WebSocket job updates + const processWebSocketMessage = useCallback((message: any) => { + const messageType = message.type; + const data = message.data || message; // Fallback if data is at root level + + if (messageType === 'progress' || messageType === 'training_progress') { + setTrainingProgress(prev => ({ + ...prev, + progress: typeof data.progress === 'number' ? data.progress : prev.progress, + currentStep: data.current_step || data.currentStep || 'Procesando...', + productsCompleted: data.products_completed || data.productsCompleted || prev.productsCompleted, + productsTotal: data.products_total || data.productsTotal || prev.productsTotal, + estimatedTimeRemaining: data.estimated_time_remaining || data.estimatedTimeRemaining || prev.estimatedTimeRemaining, + status: 'running' + })); + + } else if (messageType === 'completed' || messageType === 'training_completed') { + setTrainingProgress(prev => ({ + ...prev, + progress: 100, + status: 'completed', + currentStep: 'Entrenamiento completado', + estimatedTimeRemaining: 0 + })); + + // Auto-advance to final step after 2 seconds + setTimeout(() => { + setCurrentStep(5); + }, 2000); + + } else if (messageType === 'failed' || messageType === 'training_failed' || messageType === 'training_error') { + setTrainingProgress(prev => ({ + ...prev, + status: 'failed', + error: data.error || data.message || 'Error en el entrenamiento', + currentStep: 'Error en el entrenamiento' + })); + + } else if (messageType === 'initial_status') { + setTrainingProgress(prev => ({ + ...prev, + progress: typeof data.progress === 'number' ? data.progress : prev.progress, + status: data.status || prev.status, + currentStep: data.current_step || data.currentStep || prev.currentStep + })); + } + }, []); + + // Process WebSocket messages + useEffect(() => { + if (lastMessage) { + processWebSocketMessage(lastMessage); + } + }, [lastMessage, processWebSocketMessage]); + + // Backup jobUpdates processing useEffect(() => { if (jobUpdates.length > 0) { const latestUpdate = jobUpdates[0]; - - console.log('📨 Processing WebSocket Update:', latestUpdate); - - // Handle the message structure from your test script - const messageType = latestUpdate.type; - const data = latestUpdate.data || {}; - - if (messageType === 'progress' || messageType === 'training_progress') { - console.log('📊 Progress update:', data); - setTrainingProgress(prev => ({ - ...prev, - progress: data.progress || 0, - currentStep: data.current_step || 'Procesando...', - productsCompleted: data.products_completed || 0, - productsTotal: data.products_total || prev.productsTotal, - estimatedTimeRemaining: data.estimated_time_remaining || 0, - status: 'running' - })); - } else if (messageType === 'completed' || messageType === 'training_completed') { - console.log('🎉 Training completed via WebSocket!'); - setTrainingProgress(prev => ({ - ...prev, - progress: 100, - status: 'completed', - currentStep: 'Entrenamiento completado', - estimatedTimeRemaining: 0 - })); - - // Auto-advance to final step after 2 seconds - setTimeout(() => { - setCurrentStep(5); - }, 2000); - - } else if (messageType === 'failed' || messageType === 'training_failed' || messageType === 'training_error') { - console.error('❌ Training failed via WebSocket:', latestUpdate); - setTrainingProgress(prev => ({ - ...prev, - status: 'failed', - error: data.error || 'Error en el entrenamiento', - currentStep: 'Error en el entrenamiento' - })); - } else if (messageType === 'heartbeat') { - console.log('💓 Heartbeat received'); - } else if (messageType === 'initial_status') { - console.log('ℹ️ Initial status:', data); - setTrainingProgress(prev => ({ - ...prev, - progress: data.progress || prev.progress, - status: data.status || prev.status, - currentStep: data.current_step || prev.currentStep - })); - } else { - console.log('🔍 Unhandled message type:', messageType, data); - } + processWebSocketMessage(latestUpdate); } - }, [jobUpdates]); + }, [jobUpdates, processWebSocketMessage]); // Connect to WebSocket when training starts useEffect(() => {