New Frontend
This commit is contained in:
@@ -60,6 +60,62 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
completeStep,
|
||||
refreshProgress
|
||||
} = useOnboarding();
|
||||
|
||||
// Helper function to complete steps ensuring dependencies are met
|
||||
const completeStepWithDependencies = async (stepName: string, stepData: any = {}, allowDirectTrainingCompletion: boolean = false) => {
|
||||
try {
|
||||
console.log(`🔄 Completing step: ${stepName} with dependencies check`);
|
||||
|
||||
// Special case: Allow direct completion of training_completed when called from WebSocket
|
||||
if (stepName === 'training_completed' && allowDirectTrainingCompletion) {
|
||||
console.log(`🎯 Direct training completion via WebSocket - bypassing dependency checks`);
|
||||
await completeStep(stepName, stepData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Define step dependencies
|
||||
const stepOrder = ['user_registered', 'bakery_registered', 'sales_data_uploaded', 'training_completed', 'dashboard_accessible'];
|
||||
const stepIndex = stepOrder.indexOf(stepName);
|
||||
|
||||
if (stepIndex === -1) {
|
||||
throw new Error(`Unknown step: ${stepName}`);
|
||||
}
|
||||
|
||||
// Complete all prerequisite steps first, EXCEPT training_completed
|
||||
// training_completed can only be marked when actual training finishes via WebSocket
|
||||
for (let i = 0; i < stepIndex; i++) {
|
||||
const prereqStep = stepOrder[i];
|
||||
const prereqCompleted = progress?.steps.find(s => s.step_name === prereqStep)?.completed;
|
||||
|
||||
if (!prereqCompleted) {
|
||||
// NEVER auto-complete training_completed as a prerequisite
|
||||
// It must be completed only when actual training finishes via WebSocket
|
||||
if (prereqStep === 'training_completed') {
|
||||
console.warn(`⚠️ Cannot auto-complete training_completed as prerequisite. Training must finish first.`);
|
||||
console.warn(`⚠️ Skipping prerequisite ${prereqStep} - it will be completed when training finishes`);
|
||||
continue; // Skip this prerequisite instead of throwing error
|
||||
}
|
||||
|
||||
console.log(`🔄 Completing prerequisite step: ${prereqStep}`);
|
||||
|
||||
// user_registered should have been completed during registration
|
||||
if (prereqStep === 'user_registered') {
|
||||
console.warn('⚠️ user_registered step not completed - this should have been done during registration');
|
||||
}
|
||||
|
||||
await completeStep(prereqStep, { user_id: user?.id });
|
||||
}
|
||||
}
|
||||
|
||||
// Now complete the target step
|
||||
console.log(`✅ Completing target step: ${stepName}`);
|
||||
await completeStep(stepName, stepData);
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`Step completion error for ${stepName}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
const [bakeryData, setBakeryData] = useState<BakeryData>({
|
||||
name: '',
|
||||
address: '',
|
||||
@@ -179,12 +235,14 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
}));
|
||||
|
||||
// Mark training step as completed in onboarding API
|
||||
completeStep('training_completed', {
|
||||
// Use allowDirectTrainingCompletion=true since this is triggered by WebSocket completion
|
||||
completeStepWithDependencies('training_completed', {
|
||||
training_completed_at: new Date().toISOString(),
|
||||
user_id: user?.id,
|
||||
tenant_id: tenantId
|
||||
}).catch(error => {
|
||||
// Failed to mark training as completed in API
|
||||
tenant_id: tenantId,
|
||||
completion_source: 'websocket_training_completion'
|
||||
}, true).catch(error => {
|
||||
console.error('Failed to mark training as completed in API:', error);
|
||||
});
|
||||
|
||||
// Show celebration and auto-advance to final step after 3 seconds
|
||||
@@ -245,81 +303,14 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
console.log('Connecting to training WebSocket:', { tenantId, trainingJobId, wsUrl });
|
||||
connect();
|
||||
|
||||
// Simple polling fallback for training completion detection (now that we fixed the 404 issue)
|
||||
const pollingInterval = setInterval(async () => {
|
||||
if (trainingProgress.status === 'running' || trainingProgress.status === 'pending') {
|
||||
try {
|
||||
// Check training job status via REST API as fallback
|
||||
const response = await fetch(`http://localhost:8000/api/v1/tenants/${tenantId}/training/jobs/${trainingJobId}/status`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`,
|
||||
'X-Tenant-ID': tenantId
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const jobStatus = await response.json();
|
||||
|
||||
// If the job is completed but we haven't received WebSocket notification
|
||||
if (jobStatus.status === 'completed' && (trainingProgress.status === 'running' || trainingProgress.status === 'pending')) {
|
||||
console.log('Training completed detected via REST polling fallback');
|
||||
|
||||
setTrainingProgress(prev => ({
|
||||
...prev,
|
||||
progress: 100,
|
||||
status: 'completed',
|
||||
currentStep: 'Entrenamiento completado',
|
||||
estimatedTimeRemaining: 0
|
||||
}));
|
||||
|
||||
// Mark training step as completed in onboarding API
|
||||
completeStep('training_completed', {
|
||||
training_completed_at: new Date().toISOString(),
|
||||
user_id: user?.id,
|
||||
tenant_id: tenantId,
|
||||
completion_detected_via: 'rest_polling_fallback'
|
||||
}).catch(error => {
|
||||
console.warn('Failed to mark training as completed in API:', error);
|
||||
});
|
||||
|
||||
// Show celebration and auto-advance to final step after 3 seconds
|
||||
toast.success('🎉 Training completed! Your AI model is ready to use.', {
|
||||
duration: 5000,
|
||||
icon: '🤖'
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
manualNavigation.current = true;
|
||||
setCurrentStep(4);
|
||||
}, 3000);
|
||||
|
||||
// Clear the polling interval
|
||||
clearInterval(pollingInterval);
|
||||
}
|
||||
|
||||
// If job failed, update status
|
||||
if (jobStatus.status === 'failed' && (trainingProgress.status === 'running' || trainingProgress.status === 'pending')) {
|
||||
console.log('Training failure detected via REST polling fallback');
|
||||
|
||||
setTrainingProgress(prev => ({
|
||||
...prev,
|
||||
status: 'failed',
|
||||
error: jobStatus.error_message || 'Error en el entrenamiento',
|
||||
currentStep: 'Error en el entrenamiento'
|
||||
}));
|
||||
|
||||
clearInterval(pollingInterval);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore polling errors to avoid noise
|
||||
console.debug('REST polling error (expected if training not started):', error);
|
||||
}
|
||||
} else if (trainingProgress.status === 'completed' || trainingProgress.status === 'failed') {
|
||||
// Clear polling if training is finished
|
||||
clearInterval(pollingInterval);
|
||||
}
|
||||
}, 15000); // Poll every 15 seconds (less aggressive than before)
|
||||
// ✅ DISABLED: Polling fallback now unnecessary since WebSocket is working properly
|
||||
// The WebSocket connection now handles all training status updates in real-time
|
||||
console.log('🚫 REST polling disabled - using WebSocket exclusively for training updates');
|
||||
|
||||
// Create dummy interval for cleanup compatibility (no actual polling)
|
||||
const pollingInterval = setInterval(() => {
|
||||
// No-op - REST polling is disabled, WebSocket handles all training updates
|
||||
}, 60000); // Set to 1 minute but does nothing
|
||||
|
||||
return () => {
|
||||
if (isConnected) {
|
||||
@@ -445,9 +436,9 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
storeTenantId(newTenant.id);
|
||||
}
|
||||
|
||||
// Mark step as completed in onboarding API (non-blocking)
|
||||
// Mark bakery_registered step as completed (dependencies will be handled automatically)
|
||||
try {
|
||||
await completeStep('bakery_registered', {
|
||||
await completeStepWithDependencies('bakery_registered', {
|
||||
bakery_name: bakeryData.name,
|
||||
bakery_address: bakeryData.address,
|
||||
business_type: 'bakery', // Default - will be auto-detected from sales data
|
||||
@@ -456,6 +447,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
user_id: user?.id
|
||||
});
|
||||
} catch (stepError) {
|
||||
console.warn('Step completion error:', stepError);
|
||||
// Don't throw here - step completion is not critical for UI flow
|
||||
}
|
||||
|
||||
@@ -500,7 +492,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
stepData.has_historical_data = bakeryData.hasHistoricalData;
|
||||
}
|
||||
|
||||
await completeStep(stepName, stepData);
|
||||
await completeStepWithDependencies(stepName, stepData);
|
||||
// Note: Not calling refreshProgress() here to avoid step reset
|
||||
|
||||
toast.success(`✅ Paso ${currentStep} completado`);
|
||||
@@ -589,7 +581,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
} else {
|
||||
try {
|
||||
// Mark final step as completed
|
||||
await completeStep('dashboard_accessible', {
|
||||
await completeStepWithDependencies('dashboard_accessible', {
|
||||
completion_time: new Date().toISOString(),
|
||||
user_id: user?.id,
|
||||
tenant_id: tenantId,
|
||||
@@ -724,7 +716,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
tenantId={tenantId}
|
||||
onComplete={(result) => {
|
||||
// Mark sales data as uploaded and proceed to training
|
||||
completeStep('sales_data_uploaded', {
|
||||
completeStepWithDependencies('sales_data_uploaded', {
|
||||
smart_import: true,
|
||||
records_imported: result.successful_imports,
|
||||
import_job_id: result.import_job_id,
|
||||
|
||||
Reference in New Issue
Block a user