diff --git a/frontend/src/components/common/ProgressModal.tsx b/frontend/src/components/common/ProgressModal.tsx index 178aa2b7..afc39b85 100644 --- a/frontend/src/components/common/ProgressModal.tsx +++ b/frontend/src/components/common/ProgressModal.tsx @@ -150,29 +150,90 @@ export default function ProgressModal({ stepLogs = [], }: ProgressModalProps) { const hasAutoClosedRef = useRef(false); + // Track which steps are visually completed (with delay) + const [visuallyCompletedSteps, setVisuallyCompletedSteps] = React.useState>(new Set()); + const stepCompletionTimersRef = useRef>(new Map()); - // Auto-close on completion after 3 seconds + // Get steps for this function + const steps = getStepsForFunction(functionId, title); + const currentPhase = getCurrentPhase(stepLogs, percentage); + + // Track step completions with 2-second delay between each step + useEffect(() => { + if (!isOpen) { + // Reset when modal closes + setVisuallyCompletedSteps(new Set()); + stepCompletionTimersRef.current.forEach(timer => clearTimeout(timer)); + stepCompletionTimersRef.current.clear(); + return; + } + + const phaseOrder = ['INIT', 'PREP', 'AI_CALL', 'PARSE', 'SAVE', 'DONE']; + + // Check each step in order + steps.forEach((step, index) => { + const stepPhase = step.phase; + const stepIndex = phaseOrder.indexOf(stepPhase); + const currentIndex = phaseOrder.indexOf(currentPhase); + + // Check if step should be completed (we've moved past it or have log entry) + const shouldBeCompleted = currentIndex > stepIndex || + stepLogs.some(log => log.stepName === stepPhase && log.status === 'success'); + + // If step should be completed but isn't visually completed yet + if (shouldBeCompleted && !visuallyCompletedSteps.has(stepPhase)) { + // Check if previous step is visually completed (or if this is the first step) + const previousStep = index > 0 ? steps[index - 1] : null; + const previousStepCompleted = !previousStep || visuallyCompletedSteps.has(previousStep.phase); + + // Only schedule if previous step is completed (or this is first step) + if (previousStepCompleted) { + // Clear any existing timer for this step + const existingTimer = stepCompletionTimersRef.current.get(stepPhase); + if (existingTimer) { + clearTimeout(existingTimer); + } + + // Calculate delay: 2 seconds after previous step visually completed (or 0 for first step) + const delay = previousStep ? 2000 : 0; + + // Schedule completion + const timer = setTimeout(() => { + setVisuallyCompletedSteps(prev => new Set([...prev, stepPhase])); + stepCompletionTimersRef.current.delete(stepPhase); + }, delay); + + stepCompletionTimersRef.current.set(stepPhase, timer); + } + } + }); + + // Cleanup on unmount + return () => { + stepCompletionTimersRef.current.forEach(timer => clearTimeout(timer)); + stepCompletionTimersRef.current.clear(); + }; + }, [isOpen, currentPhase, stepLogs, steps, visuallyCompletedSteps]); + + // Auto-close on completion after 5 seconds (increased to allow all steps to show) useEffect(() => { if (status === 'completed' && onClose && !hasAutoClosedRef.current) { hasAutoClosedRef.current = true; const timer = setTimeout(() => { onClose(); - }, 3000); + }, 5000); return () => clearTimeout(timer); } if (status !== 'completed') { hasAutoClosedRef.current = false; } }, [status, onClose]); - - // Get steps for this function - const steps = getStepsForFunction(functionId, title); - const currentPhase = getCurrentPhase(stepLogs, percentage); - // Build checklist items + // Build checklist items with visual completion state const checklistItems = steps.map((step) => { - const completed = isStepCompleted(step.phase, currentPhase, stepLogs); - const inProgress = isStepInProgress(step.phase, currentPhase); + const actuallyCompleted = isStepCompleted(step.phase, currentPhase, stepLogs); + const visuallyCompleted = visuallyCompletedSteps.has(step.phase); + const inProgress = isStepInProgress(step.phase, currentPhase) && !visuallyCompleted; // Get user-friendly message from step logs if available const stepLog = stepLogs.find(log => log.stepName === step.phase); @@ -181,7 +242,7 @@ export default function ProgressModal({ return { label: stepMessage, phase: step.phase, - completed, + completed: visuallyCompleted, // Use visual completion state inProgress, }; }); @@ -200,20 +261,22 @@ export default function ProgressModal({
{/* Header */}
- {!showSuccess && ( -
- {status === 'error' ? ( - - - - ) : ( - - - - - )} -
- )} +
+ {status === 'error' ? ( + + + + ) : showSuccess ? ( + + + + ) : ( + + + + + )} +

{title} @@ -238,52 +301,50 @@ export default function ProgressModal({

)} - {/* Checklist-style Progress Steps */} - {!showSuccess && ( -
- {checklistItems.map((item, index) => ( -
+ {checklistItems.map((item, index) => ( +
+ {/* Icon */} +
+ {item.completed ? ( + + + + ) : item.inProgress ? ( + + + + + ) : ( +
+ )} +
+ + {/* Step Text */} + - {/* Icon */} -
- {item.completed ? ( - - - - ) : item.inProgress ? ( - - - - - ) : ( -
- )} -
- - {/* Step Text */} - - {item.label} - -
- ))} -
- )} + {item.label} + +
+ ))} +
{/* Footer */}