Fix new Frontend 10
This commit is contained in:
@@ -13,7 +13,13 @@ import type {
|
|||||||
SingleProductTrainingRequest,
|
SingleProductTrainingRequest,
|
||||||
} from '../types';
|
} 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<TrainingJobResponse[]>([]);
|
const [jobs, setJobs] = useState<TrainingJobResponse[]>([]);
|
||||||
const [currentJob, setCurrentJob] = useState<TrainingJobResponse | null>(null);
|
const [currentJob, setCurrentJob] = useState<TrainingJobResponse | null>(null);
|
||||||
const [models, setModels] = useState<ModelInfo[]>([]);
|
const [models, setModels] = useState<ModelInfo[]>([]);
|
||||||
@@ -186,12 +192,19 @@ export const useTraining = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Auto-refresh job status for running jobs
|
|
||||||
useEffect(() => {
|
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');
|
const runningJobs = jobs.filter(job => job.status === 'running' || job.status === 'pending');
|
||||||
|
|
||||||
if (runningJobs.length === 0) return;
|
if (runningJobs.length === 0) return;
|
||||||
|
|
||||||
|
console.log('🔄 Starting HTTP status polling for', runningJobs.length, 'jobs');
|
||||||
|
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
for (const job of runningJobs) {
|
for (const job of runningJobs) {
|
||||||
try {
|
try {
|
||||||
@@ -203,8 +216,12 @@ export const useTraining = () => {
|
|||||||
}
|
}
|
||||||
}, 5000); // Refresh every 5 seconds
|
}, 5000); // Refresh every 5 seconds
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => {
|
||||||
}, [jobs, getTrainingJobStatus]);
|
console.log('🛑 Stopping HTTP status polling');
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [jobs, getTrainingJobStatus, disablePolling]);
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
jobs,
|
jobs,
|
||||||
|
|||||||
@@ -95,31 +95,85 @@ export const useWebSocket = (config: WebSocketConfig) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Hook for training job updates
|
// Hook for training job updates
|
||||||
export const useTrainingWebSocket = (jobId: string) => {
|
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)
|
||||||
|
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 = {
|
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,
|
reconnect: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [jobUpdates, setJobUpdates] = useState<any[]>([]);
|
const { status, connect, disconnect, addMessageHandler, isConnected, lastMessage } = useWebSocket(config);
|
||||||
|
|
||||||
const { status, connect, disconnect, addMessageHandler, isConnected } = useWebSocket(config);
|
|
||||||
|
|
||||||
|
// 🚀 FIX 2: Handle ALL message types, not just specific ones
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addMessageHandler((message) => {
|
addMessageHandler((message) => {
|
||||||
if (message.type === 'training_progress' || message.type === 'training_completed') {
|
console.log('🔥 WebSocket message received:', message);
|
||||||
setJobUpdates(prev => [message.data, ...prev.slice(0, 99)]); // Keep last 100 updates
|
|
||||||
|
// 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]);
|
}, [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 {
|
return {
|
||||||
status,
|
status,
|
||||||
jobUpdates,
|
jobUpdates,
|
||||||
connect,
|
connect,
|
||||||
disconnect,
|
disconnect,
|
||||||
isConnected,
|
isConnected,
|
||||||
|
lastMessage, // Expose for debugging
|
||||||
|
tenantId: actualTenantId, // Expose for debugging
|
||||||
|
wsUrl: config.url, // Expose for debugging
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -67,11 +67,20 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { createTenant, isLoading: tenantLoading } = useTenant();
|
const { createTenant, isLoading: tenantLoading } = useTenant();
|
||||||
const { startTrainingJob, getTrainingJobStatus } = useTraining();
|
const { startTrainingJob } = useTraining({ disablePolling: true });
|
||||||
const { uploadSalesHistory, validateSalesData } = useData();
|
const { uploadSalesHistory, validateSalesData } = useData();
|
||||||
|
|
||||||
// WebSocket connection for real-time training updates
|
// 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 = [
|
const steps = [
|
||||||
{ id: 1, title: 'Datos de Panadería', icon: Store },
|
{ id: 1, title: 'Datos de Panadería', icon: Store },
|
||||||
@@ -86,20 +95,27 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
|||||||
if (jobUpdates.length > 0) {
|
if (jobUpdates.length > 0) {
|
||||||
const latestUpdate = jobUpdates[0];
|
const latestUpdate = jobUpdates[0];
|
||||||
|
|
||||||
// Update training progress based on WebSocket messages
|
console.log('📨 Processing WebSocket Update:', latestUpdate);
|
||||||
if (latestUpdate.type === 'training_progress') {
|
|
||||||
|
// 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 => ({
|
setTrainingProgress(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
progress: latestUpdate.progress || 0,
|
progress: data.progress || 0,
|
||||||
currentStep: latestUpdate.current_step || 'Procesando...',
|
currentStep: data.current_step || 'Procesando...',
|
||||||
productsCompleted: latestUpdate.products_completed || 0,
|
productsCompleted: data.products_completed || 0,
|
||||||
productsTotal: latestUpdate.products_total || prev.productsTotal,
|
productsTotal: data.products_total || prev.productsTotal,
|
||||||
estimatedTimeRemaining: latestUpdate.estimated_time_remaining || 0,
|
estimatedTimeRemaining: data.estimated_time_remaining || 0,
|
||||||
status: 'running'
|
status: 'running'
|
||||||
}));
|
}));
|
||||||
} else if (latestUpdate.type === 'training_completed') {
|
} else if (messageType === 'completed' || messageType === 'training_completed') {
|
||||||
|
console.log('🎉 Training completed via WebSocket!');
|
||||||
setTrainingProgress(prev => ({
|
setTrainingProgress(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
progress: 100,
|
progress: 100,
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
currentStep: 'Entrenamiento completado',
|
currentStep: 'Entrenamiento completado',
|
||||||
@@ -111,13 +127,26 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
|||||||
setCurrentStep(5);
|
setCurrentStep(5);
|
||||||
}, 2000);
|
}, 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 => ({
|
setTrainingProgress(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
status: 'failed',
|
status: 'failed',
|
||||||
error: latestUpdate.error || 'Error en el entrenamiento',
|
error: data.error || 'Error en el entrenamiento',
|
||||||
currentStep: '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]);
|
}, [jobUpdates]);
|
||||||
|
|||||||
Reference in New Issue
Block a user