diff --git a/frontend/src/api/hooks/useTraining.ts b/frontend/src/api/hooks/useTraining.ts index 03ce26aa..6c22438a 100644 --- a/frontend/src/api/hooks/useTraining.ts +++ b/frontend/src/api/hooks/useTraining.ts @@ -13,7 +13,13 @@ import type { SingleProductTrainingRequest, } from '../types'; -export const useTraining = () => { +interface UseTrainingOptions { + disablePolling?: boolean; // New option to disable HTTP status polling +} + +export const useTraining = (options: UseTrainingOptions = {}) => { + + const { disablePolling = false } = options; const [jobs, setJobs] = useState([]); const [currentJob, setCurrentJob] = useState(null); const [models, setModels] = useState([]); @@ -186,12 +192,19 @@ export const useTraining = () => { } }, []); - // Auto-refresh job status for running jobs useEffect(() => { + // Skip polling if disabled or no running jobs + if (disablePolling) { + console.log('🚫 HTTP status polling disabled - using WebSocket instead'); + return; + } + const runningJobs = jobs.filter(job => job.status === 'running' || job.status === 'pending'); if (runningJobs.length === 0) return; + console.log('πŸ”„ Starting HTTP status polling for', runningJobs.length, 'jobs'); + const interval = setInterval(async () => { for (const job of runningJobs) { try { @@ -203,8 +216,12 @@ export const useTraining = () => { } }, 5000); // Refresh every 5 seconds - return () => clearInterval(interval); - }, [jobs, getTrainingJobStatus]); + return () => { + console.log('πŸ›‘ Stopping HTTP status polling'); + clearInterval(interval); + }; + }, [jobs, getTrainingJobStatus, disablePolling]); + return { jobs, diff --git a/frontend/src/api/websocket/hooks.ts b/frontend/src/api/websocket/hooks.ts index f4b7e666..6775db00 100644 --- a/frontend/src/api/websocket/hooks.ts +++ b/frontend/src/api/websocket/hooks.ts @@ -95,31 +95,85 @@ export const useWebSocket = (config: WebSocketConfig) => { }; // Hook for training job updates -export const useTrainingWebSocket = (jobId: string) => { +export const useTrainingWebSocket = (jobId: string, tenantId?: string) => { + const [jobUpdates, setJobUpdates] = useState([]); + + // πŸš€ FIX 1: Construct URL with actual tenantId (get from localStorage if not provided) + const actualTenantId = tenantId || (() => { + const userData = localStorage.getItem('user_data'); + if (userData) { + try { + 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); + } + } + return null; + })(); const config: WebSocketConfig = { - url: `ws://localhost:8002/api/v1/ws/tenants/{tenant_id}/training/jobs/${jobId}/live`, + // πŸš€ FIX: Use actual tenant ID instead of placeholder + 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, }; - const [jobUpdates, setJobUpdates] = useState([]); - - const { status, connect, disconnect, addMessageHandler, isConnected } = useWebSocket(config); + const { status, connect, disconnect, addMessageHandler, isConnected, lastMessage } = useWebSocket(config); + // πŸš€ FIX 2: Handle ALL message types, not just specific ones useEffect(() => { addMessageHandler((message) => { - if (message.type === 'training_progress' || message.type === 'training_completed') { - setJobUpdates(prev => [message.data, ...prev.slice(0, 99)]); // Keep last 100 updates + 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); + } + }, [lastMessage]); + return { status, jobUpdates, connect, disconnect, isConnected, + lastMessage, // Expose for debugging + tenantId: actualTenantId, // Expose for debugging + wsUrl: config.url, // Expose for debugging }; }; diff --git a/frontend/src/pages/onboarding/OnboardingPage.tsx b/frontend/src/pages/onboarding/OnboardingPage.tsx index 787376ce..c0ce7dad 100644 --- a/frontend/src/pages/onboarding/OnboardingPage.tsx +++ b/frontend/src/pages/onboarding/OnboardingPage.tsx @@ -67,11 +67,20 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => }); const { createTenant, isLoading: tenantLoading } = useTenant(); - const { startTrainingJob, getTrainingJobStatus } = useTraining(); + const { startTrainingJob } = useTraining({ disablePolling: true }); const { uploadSalesHistory, validateSalesData } = useData(); // WebSocket connection for real-time training updates - const { status, jobUpdates, connect, disconnect, isConnected } = useTrainingWebSocket(trainingJobId || 'pending'); + const { + status, + jobUpdates, + connect, + disconnect, + isConnected, + lastMessage, + tenantId: resolvedTenantId, + wsUrl + } = useTrainingWebSocket(trainingJobId || 'pending', tenantId); const steps = [ { id: 1, title: 'Datos de PanaderΓ­a', icon: Store }, @@ -86,20 +95,27 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => if (jobUpdates.length > 0) { const latestUpdate = jobUpdates[0]; - // Update training progress based on WebSocket messages - if (latestUpdate.type === 'training_progress') { + 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: latestUpdate.progress || 0, - currentStep: latestUpdate.current_step || 'Procesando...', - productsCompleted: latestUpdate.products_completed || 0, - productsTotal: latestUpdate.products_total || prev.productsTotal, - estimatedTimeRemaining: latestUpdate.estimated_time_remaining || 0, + 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 (latestUpdate.type === 'training_completed') { + } else if (messageType === 'completed' || messageType === 'training_completed') { + console.log('πŸŽ‰ Training completed via WebSocket!'); setTrainingProgress(prev => ({ - ...prev, + ...prev, progress: 100, status: 'completed', currentStep: 'Entrenamiento completado', @@ -111,13 +127,26 @@ const OnboardingPage: React.FC = ({ user, onComplete }) => setCurrentStep(5); }, 2000); - } else if (latestUpdate.type === 'training_failed' || latestUpdate.type === 'training_error') { + } else if (messageType === 'failed' || messageType === 'training_failed' || messageType === 'training_error') { + console.error('❌ Training failed via WebSocket:', latestUpdate); setTrainingProgress(prev => ({ ...prev, status: 'failed', - error: latestUpdate.error || 'Error en el entrenamiento', + 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); } } }, [jobUpdates]);