New Frontend

This commit is contained in:
Urtzi Alfaro
2025-08-16 20:13:40 +02:00
parent 23c5f50111
commit 8914786973
35 changed files with 4223 additions and 538 deletions

View File

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