diff --git a/frontend/src/components/common/ProgressModal.tsx b/frontend/src/components/common/ProgressModal.tsx index d951a4ad..e8833026 100644 --- a/frontend/src/components/common/ProgressModal.tsx +++ b/frontend/src/components/common/ProgressModal.tsx @@ -298,11 +298,24 @@ export default function ProgressModal({ // Build checklist items with visual completion state (needed for allStepsVisuallyCompleted) const checklistItems = useMemo(() => { - return steps.map((step) => { + const phaseOrder = ['INIT', 'PREP', 'AI_CALL', 'PARSE', 'SAVE', 'DONE']; + + return steps.map((step, index) => { const actuallyCompleted = isStepCompleted(step.phase, currentPhase, stepLogs); const visuallyCompleted = visuallyCompletedSteps.has(step.phase); - // Only show as in progress if it's the current phase AND not visually completed yet - const inProgress = step.phase === currentPhase && !visuallyCompleted && !actuallyCompleted; + + // Determine if this step should be in progress + // A step is in progress if: + // 1. It's the current phase (backend is working on it) + // 2. It's not visually completed yet + // 3. All previous steps are visually completed + // 4. Status is still processing (not completed or error) + const previousStepsCompleted = index === 0 || steps.slice(0, index).every(s => visuallyCompletedSteps.has(s.phase)); + const isCurrentPhase = step.phase === currentPhase; + const inProgress = isCurrentPhase && + !visuallyCompleted && + previousStepsCompleted && + status === 'processing'; // Get step log and format message const stepLog = stepLogs.find(log => log.stepName === step.phase); @@ -315,12 +328,15 @@ export default function ProgressModal({ inProgress, }; }); - }, [steps, currentPhase, stepLogs, visuallyCompletedSteps, functionId, title]); + }, [steps, currentPhase, stepLogs, visuallyCompletedSteps, functionId, title, status]); // Check if all steps are visually completed const allStepsVisuallyCompleted = steps.length > 0 && steps.every(step => visuallyCompletedSteps.has(step.phase)); + // Track the count of visually completed steps to detect changes without causing infinite loops + const visuallyCompletedCount = visuallyCompletedSteps.size; + // Track step completions with 2-second delay between each step useEffect(() => { if (!isOpen) { @@ -334,19 +350,11 @@ export default function ProgressModal({ return; } - // Only process if stepLogs or currentPhase actually changed in a meaningful way - if (stepLogsHash === lastProcessedStepLogsHashRef.current && currentPhase === lastProcessedPhaseRef.current) { - return; - } - - // Update last processed values - lastProcessedStepLogsHashRef.current = stepLogsHash; - lastProcessedPhaseRef.current = currentPhase; - const phaseOrder = ['INIT', 'PREP', 'AI_CALL', 'PARSE', 'SAVE', 'DONE']; - // Check each step in order - steps.forEach((step, index) => { + // Process steps sequentially - find the first step that should be completed but isn't visually completed yet + for (let index = 0; index < steps.length; index++) { + const step = steps[index]; const stepPhase = step.phase; const stepIndex = phaseOrder.indexOf(stepPhase); const currentIndex = phaseOrder.indexOf(currentPhase); @@ -364,6 +372,7 @@ export default function ProgressModal({ // Only schedule if previous step is completed (or this is first step) if (previousStepCompleted) { // Calculate delay: 2 seconds after previous step visually completed (or 0 for first step) + // For subsequent steps, add 2 seconds delay const delay = previousStep ? 2000 : 0; // Schedule completion @@ -376,16 +385,22 @@ export default function ProgressModal({ }, delay); stepCompletionTimersRef.current.set(stepPhase, timer); + + // Only process one step at a time - break after scheduling the first eligible step + break; + } else { + // Previous step is not completed yet, stop processing + break; } } - }); + } // Cleanup on unmount return () => { stepCompletionTimersRef.current.forEach(timer => clearTimeout(timer)); stepCompletionTimersRef.current.clear(); }; - }, [isOpen, currentPhase, stepLogsHash, steps]); // Use stepLogsHash instead of stepLogs + }, [isOpen, currentPhase, stepLogsHash, steps, stepLogs, visuallyCompletedCount]); // Use count instead of Set to avoid infinite loops // Don't auto-close - user must click close button @@ -413,15 +428,15 @@ export default function ProgressModal({
Processing...
)} - {/* Spinner below heading - only show when processing and not completed */} - {!showSuccess && status !== 'completed' && status !== 'error' && ( + {/* Spinner below heading - show when processing (including when completed but steps not all visually done) */} + {status === 'processing' || (status === 'completed' && !allStepsVisuallyCompleted) ? (