Fix new Frontend 11

This commit is contained in:
Urtzi Alfaro
2025-08-04 16:19:00 +02:00
parent d13b8657df
commit d4c276c888
3 changed files with 167 additions and 125 deletions

View File

@@ -98,72 +98,102 @@ export const useWebSocket = (config: WebSocketConfig) => {
export const useTrainingWebSocket = (jobId: string, tenantId?: string) => {
const [jobUpdates, setJobUpdates] = useState<any[]>([]);
// 🚀 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])
};
};

View File

@@ -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;

View File

@@ -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<OnboardingPageProps> = ({ user, onComplete }) =>
// Training progress state
const [tenantId, setTenantId] = useState<string>('');
const [trainingJobId, setTrainingJobId] = useState<string>('');
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<TrainingProgress>({
progress: 0,
status: 'pending',
@@ -66,10 +78,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ 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<OnboardingPageProps> = ({ 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(() => {