import React, { useEffect, useRef, useMemo } from 'react'; import { Modal } from '../ui/modal'; import Button from '../ui/button/Button'; export interface ProgressModalProps { isOpen: boolean; title: string; percentage: number; // 0-100 status: 'pending' | 'processing' | 'completed' | 'error'; message: string; details?: { current: number; total: number; completed: number; currentItem?: string; phase?: string; }; onClose?: () => void; onCancel?: () => void; taskId?: string; functionId?: string; // AI function ID for tracking (e.g., "ai-cluster-01") stepLogs?: Array<{ stepNumber: number; stepName: string; status: string; message: string; timestamp?: number; }>; // Step logs for debugging } // Success messages per function with counts const getSuccessMessage = (functionId?: string, title?: string, stepLogs?: any[]): string => { const funcName = functionId?.toLowerCase() || title?.toLowerCase() || ''; // Extract counts from step logs const extractCount = (pattern: RegExp, logs: any[]): string => { for (const log of logs) { const match = log.message?.match(pattern); if (match && match[1]) return match[1]; } return ''; }; if (funcName.includes('cluster')) { const keywordCount = extractCount(/(\d+)\s+keyword/i, stepLogs || []); const clusterCount = extractCount(/(\d+)\s+cluster/i, stepLogs || []); if (keywordCount && clusterCount) { return `Clustering complete\n${keywordCount} keyword${keywordCount !== '1' ? 's' : ''} mapped and grouped into ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''}`; } else if (clusterCount) { return `Clustering complete\n${clusterCount} cluster${clusterCount !== '1' ? 's' : ''} created`; } else if (keywordCount) { return `Clustering complete\n${keywordCount} keyword${keywordCount !== '1' ? 's' : ''} mapped and grouped into clusters`; } return 'Clustering complete\nKeywords mapped and grouped into clusters'; } if (funcName.includes('idea')) { const ideaCount = extractCount(/(\d+)\s+idea/i, stepLogs || []); if (ideaCount) { return `Content ideas & outlines created successfully`; } return 'Content ideas & outlines created successfully'; } if (funcName.includes('content')) { const taskCount = extractCount(/(\d+)\s+task/i, stepLogs || []); const articleCount = extractCount(/(\d+)\s+article/i, stepLogs || []); if (articleCount) { return `Article${articleCount !== '1' ? 's' : ''} drafted successfully — ${articleCount} article${articleCount !== '1' ? 's' : ''} generated.`; } else if (taskCount) { return `Article${taskCount !== '1' ? 's' : ''} drafted successfully — ${taskCount} task${taskCount !== '1' ? 's' : ''} completed.`; } return 'Article drafted successfully.'; } // Check for image generation from prompts FIRST (more specific) if (funcName.includes('image') && funcName.includes('from')) { // Image generation from prompts const imageCount = extractCount(/(\d+)\s+image/i, stepLogs || []); if (imageCount) { return `${imageCount} image${imageCount !== '1' ? 's' : ''} generated successfully`; } return 'Images generated successfully'; } else if (funcName.includes('image') && (funcName.includes('prompt') || funcName.includes('extract'))) { // Image prompt generation // Try to extract from SAVE step message first (most reliable) const saveStepLog = stepLogs?.find(log => log.stepName === 'SAVE'); if (saveStepLog?.message) { // Look for "Assigning X Prompts to Dedicated Slots" const countMatch = saveStepLog.message.match(/Assigning (\d+)\s+Prompts/i); if (countMatch) { const totalPrompts = parseInt(countMatch[1], 10); const inArticleCount = totalPrompts > 1 ? totalPrompts - 1 : 0; if (inArticleCount > 0) { return `Featured Image and ${inArticleCount} In‑article Image Prompts ready for image generation`; } else { return `Featured Image Prompt ready for image generation`; } } } // Try to extract from PREP step to get total count const prepStepLog = stepLogs?.find(log => log.stepName === 'PREP'); if (prepStepLog?.message) { const match = prepStepLog.message.match(/Mapping Content for (\d+)\s+Image Prompts/i); if (match && match[1]) { const totalPrompts = parseInt(match[1], 10); const inArticleCount = totalPrompts > 1 ? totalPrompts - 1 : 0; if (inArticleCount > 0) { return `Featured Image and ${inArticleCount} In‑article Image Prompts ready for image generation`; } else { return `Featured Image Prompt ready for image generation`; } } } // Fallback: extract prompt count from any step log const promptCount = extractCount(/(\d+)\s+prompt/i, stepLogs || []); if (promptCount) { const totalPrompts = parseInt(promptCount, 10); const inArticleCount = totalPrompts > 1 ? totalPrompts - 1 : 0; if (inArticleCount > 0) { return `Featured Image and ${inArticleCount} In‑article Image Prompts ready for image generation`; } else { return `Featured Image Prompt ready for image generation`; } } // Default message return 'Featured Image and X In‑article Image Prompts ready for image generation'; } return 'Task completed successfully.'; }; // Get step definitions per function const getStepsForFunction = (functionId?: string, title?: string): Array<{phase: string, label: string}> => { const funcName = functionId?.toLowerCase() || title?.toLowerCase() || ''; if (funcName.includes('cluster')) { return [ { phase: 'INIT', label: 'Validating keywords' }, { phase: 'PREP', label: 'Loading keyword data' }, { phase: 'AI_CALL', label: 'Generating clusters with Igny8 Semantic SEO Model' }, { phase: 'PARSE', label: 'Organizing clusters' }, { phase: 'SAVE', label: 'Saving clusters' }, ]; } if (funcName.includes('idea')) { return [ { phase: 'INIT', label: 'Verifying cluster integrity' }, { phase: 'PREP', label: 'Loading cluster keywords' }, { phase: 'AI_CALL', label: 'Generating ideas with Igny8 Semantic AI' }, { phase: 'PARSE', label: 'High-opportunity ideas generated' }, { phase: 'SAVE', label: 'Content Outline for Ideas generated' }, ]; } if (funcName.includes('content')) { return [ { phase: 'INIT', label: 'Validating task' }, { phase: 'PREP', label: 'Preparing content idea' }, { phase: 'AI_CALL', label: 'Writing article with Igny8 Semantic AI' }, { phase: 'PARSE', label: 'Formatting content' }, { phase: 'SAVE', label: 'Saving article' }, ]; } // Check for image generation from prompts FIRST (more specific) if (funcName.includes('image') && funcName.includes('from')) { // Image generation from prompts return [ { phase: 'INIT', label: 'Validating image prompts' }, { phase: 'PREP', label: 'Preparing image generation queue' }, { phase: 'AI_CALL', label: 'Generating images with AI' }, { phase: 'PARSE', label: 'Processing image URLs' }, { phase: 'SAVE', label: 'Saving image URLs' }, ]; } else if (funcName.includes('image') && (funcName.includes('prompt') || funcName.includes('extract'))) { // Image prompt generation return [ { phase: 'INIT', label: 'Checking content and image slots' }, { phase: 'PREP', label: 'Mapping Content for X Image Prompts' }, { phase: 'AI_CALL', label: 'Writing Featured Image Prompts' }, { phase: 'PARSE', label: 'Writing X In‑article Image Prompts' }, { phase: 'SAVE', label: 'Assigning Prompts to Dedicated Slots' }, ]; } // Default fallback return [ { phase: 'INIT', label: 'Initializing...' }, { phase: 'PREP', label: 'Preparing...' }, { phase: 'AI_CALL', label: 'Processing with Igny8 Semantic AI...' }, { phase: 'PARSE', label: 'Processing results...' }, { phase: 'SAVE', label: 'Saving results...' }, ]; }; // Get current phase from step logs or percentage const getCurrentPhase = (stepLogs: any[], percentage: number): string => { if (stepLogs.length > 0) { const lastStep = stepLogs[stepLogs.length - 1]; return lastStep.stepName || ''; } // Fallback to percentage if (percentage < 10) return 'INIT'; if (percentage < 25) return 'PREP'; if (percentage < 70) return 'AI_CALL'; if (percentage < 85) return 'PARSE'; if (percentage < 100) return 'SAVE'; return 'DONE'; }; // Check if step is completed const isStepCompleted = (stepPhase: string, currentPhase: string, stepLogs: any[]): boolean => { const phaseOrder = ['INIT', 'PREP', 'AI_CALL', 'PARSE', 'SAVE', 'DONE']; const stepIndex = phaseOrder.indexOf(stepPhase); const currentIndex = phaseOrder.indexOf(currentPhase); // Step is completed if we've moved past it if (currentIndex > stepIndex) return true; // Or if we have a log entry for it with success status return stepLogs.some(log => log.stepName === stepPhase && log.status === 'success' ); }; // Check if step is in progress const isStepInProgress = (stepPhase: string, currentPhase: string): boolean => { return stepPhase === currentPhase; }; export default function ProgressModal({ isOpen, title, percentage, status, message, onClose, onCancel, taskId, functionId, stepLogs = [], }: ProgressModalProps) { // Track which steps are visually completed (with delay) const [visuallyCompletedSteps, setVisuallyCompletedSteps] = React.useState>(new Set()); const stepCompletionTimersRef = useRef>(new Map()); const visuallyCompletedStepsRef = useRef>(new Set()); const lastProcessedStepLogsHashRef = useRef(''); const lastProcessedPhaseRef = useRef(''); const lastVisuallyCompletedCountRef = useRef(0); // Sync ref with state useEffect(() => { visuallyCompletedStepsRef.current = visuallyCompletedSteps; }, [visuallyCompletedSteps]); // Track count to detect when steps complete visually (without causing loops) const visuallyCompletedCount = visuallyCompletedSteps.size; // Memoize steps to prevent unnecessary re-renders const steps = useMemo(() => getStepsForFunction(functionId, title), [functionId, title]); // Memoize currentPhase to prevent unnecessary re-renders const currentPhase = useMemo(() => getCurrentPhase(stepLogs, percentage), [stepLogs, percentage]); // Create a stable hash of stepLogs to detect meaningful changes const stepLogsHash = useMemo(() => { return JSON.stringify(stepLogs.map(log => ({ stepName: log.stepName, status: log.status, }))); }, [stepLogs]); // Format step message with counts and better formatting const formatStepMessage = (stepPhase: string, stepLog: any, defaultLabel: string, allStepLogs: any[], functionId?: string, title?: string): string => { const funcName = (functionId || title || '').toLowerCase(); const message = stepLog?.message || defaultLabel; // Extract counts from message const extractCount = (pattern: RegExp): string => { const match = message.match(pattern); return match && match[1] ? match[1] : ''; }; if (funcName.includes('cluster')) { if (stepPhase === 'INIT') { // For INIT: Try to extract keyword count from message or stepLogs // Backend message might include keyword names (e.g., "Validating keyword1, keyword2, keyword3 and 5 more keywords") // Or we need to extract the total count if (message && message !== defaultLabel && message.includes('Validating')) { // Try to extract total count from message const countMatch = message.match(/(\d+)\s+more keyword/i); if (countMatch) { const moreCount = parseInt(countMatch[1], 10); // Count keywords before "and X more" - typically 3 const shownCount = 3; const totalCount = shownCount + moreCount; return `Validating ${totalCount} keyword${totalCount !== 1 ? 's' : ''}`; } // Try to find total keyword count in any step log for (const log of allStepLogs) { const keywordCountMatch = log.message?.match(/(\d+)\s+keyword/i); if (keywordCountMatch) { const totalCount = parseInt(keywordCountMatch[1], 10); return `Validating ${totalCount} keyword${totalCount !== 1 ? 's' : ''}`; } } // If message has keyword names but no count, return as-is return message; } // Fallback: try to extract count from stepLogs for (const log of allStepLogs) { const keywordCountMatch = log.message?.match(/(\d+)\s+keyword/i); if (keywordCountMatch) { const totalCount = parseInt(keywordCountMatch[1], 10); return `Validating ${totalCount} keyword${totalCount !== 1 ? 's' : ''}`; } } // Final fallback: use default label return defaultLabel; } else if (stepPhase === 'PREP') { // For PREP: Show count of keywords being loaded const keywordCount = extractCount(/(\d+)\s+keyword/i); if (keywordCount) { return `Loading ${keywordCount} keyword${keywordCount !== '1' ? 's' : ''} for clustering`; } return message; } else if (stepPhase === 'AI_CALL') { // For AI_CALL: Show "Generating clusters with Igny8 Semantic SEO Model" return 'Generating clusters with Igny8 Semantic SEO Model'; } else if (stepPhase === 'PARSE') { // For PARSE: Show count of clusters created const clusterCount = extractCount(/(\d+)\s+cluster/i); if (clusterCount) { return `${clusterCount} cluster${clusterCount !== '1' ? 's' : ''} created`; } // Try to find cluster count in any step log for (const log of allStepLogs) { const count = log.message?.match(/(\d+)\s+cluster/i); if (count && count[1]) { return `${count[1]} cluster${count[1] !== '1' ? 's' : ''} created`; } } return message; } else if (stepPhase === 'SAVE') { // For SAVE: Show count of clusters being saved const clusterCount = extractCount(/(\d+)\s+cluster/i); if (clusterCount) { return `Saving ${clusterCount} cluster${clusterCount !== '1' ? 's' : ''}`; } return message; } } else if (funcName.includes('idea')) { if (stepPhase === 'INIT') { // For INIT: Show "Verifying cluster integrity" return 'Verifying cluster integrity'; } else if (stepPhase === 'PREP') { // For PREP: Show "Loading cluster keywords" return 'Loading cluster keywords'; } else if (stepPhase === 'AI_CALL') { // For AI_CALL: Show "Generating ideas with Igny8 Semantic AI" return 'Generating ideas with Igny8 Semantic AI'; } else if (stepPhase === 'PARSE') { // For PARSE: Show "X high-opportunity ideas generated" const ideaCount = extractCount(/(\d+)\s+idea/i); if (ideaCount) { return `${ideaCount} high-opportunity idea${ideaCount !== '1' ? 's' : ''} generated`; } // Try to find idea count in any step log for (const log of allStepLogs) { const count = log.message?.match(/(\d+)\s+idea/i); if (count && count[1]) { return `${count[1]} high-opportunity idea${count[1] !== '1' ? 's' : ''} generated`; } } return message; } else if (stepPhase === 'SAVE') { // For SAVE: Show "Content Outline for Ideas generated" return 'Content Outline for Ideas generated'; } } else if (funcName.includes('content')) { if (stepPhase === 'AI_CALL') { // For AI_CALL: Show "Writing article with Igny8 Semantic AI" return 'Writing article with Igny8 Semantic AI'; } else if (stepPhase === 'PARSE') { const articleCount = extractCount(/(\d+)\s+article/i); if (articleCount) { return `${articleCount} article${articleCount !== '1' ? 's' : ''} created`; } } } else if (funcName.includes('image') && funcName.includes('from')) { // Image generation from prompts if (stepPhase === 'PREP') { // Extract image count from PREP step message const imageCount = extractCount(/(\d+)\s+image/i); if (imageCount) { return `Preparing to generate ${imageCount} image${imageCount !== '1' ? 's' : ''}`; } if (stepLog?.message) { const match = stepLog.message.match(/Preparing to generate (\d+)\s+image/i); if (match && match[1]) { return `Preparing to generate ${match[1]} image${match[1] !== '1' ? 's' : ''}`; } } return 'Preparing image generation queue'; } else if (stepPhase === 'AI_CALL') { // Extract current image number from message const match = stepLog?.message?.match(/Generating.*image (\d+)/i); if (match && match[1]) { return `Generating image ${match[1]} with AI`; } return 'Generating images with AI'; } else if (stepPhase === 'PARSE') { // Extract image count from PARSE step const imageCount = extractCount(/(\d+)\s+image/i); if (imageCount) { return `${imageCount} image${imageCount !== '1' ? 's' : ''} generated successfully`; } if (stepLog?.message) { const match = stepLog.message.match(/(\d+)\s+image.*generated/i); if (match && match[1]) { return `${match[1]} image${match[1] !== '1' ? 's' : ''} generated successfully`; } } return 'Processing image URLs'; } else if (stepPhase === 'SAVE') { // Extract image count from SAVE step const imageCount = extractCount(/(\d+)\s+image/i); if (imageCount) { return `Saving ${imageCount} image${imageCount !== '1' ? 's' : ''}`; } if (stepLog?.message) { const match = stepLog.message.match(/Saved image (\d+)/i); if (match && match[1]) { return `Saving image ${match[1]}`; } } return 'Saving image URLs'; } } else if (funcName.includes('image') && (funcName.includes('prompt') || funcName.includes('extract'))) { // Image prompt generation if (stepPhase === 'PREP') { // Extract total image count from PREP step message // Look for "Mapping Content for X Image Prompts" const totalCount = extractCount(/(\d+)\s+Image Prompts/i) || extractCount(/(\d+)\s+image/i); if (totalCount) { return `Mapping Content for ${totalCount} Image Prompts`; } // Try to extract from step log message if (stepLog?.message) { const match = stepLog.message.match(/Mapping Content for (\d+)\s+Image Prompts/i); if (match && match[1]) { return `Mapping Content for ${match[1]} Image Prompts`; } } return 'Mapping Content for X Image Prompts'; } else if (stepPhase === 'AI_CALL') { // For AI_CALL: Show "Writing Featured Image Prompts" return 'Writing Featured Image Prompts'; } else if (stepPhase === 'PARSE') { // Extract in-article image count from PARSE step // Look for "Writing X In‑article Image Prompts" const inArticleCount = extractCount(/(\d+)\s+In‑article/i) || extractCount(/(\d+)\s+In-article/i); if (inArticleCount) { return `Writing ${inArticleCount} In‑article Image Prompts`; } // Try to extract from step log message if (stepLog?.message) { const match = stepLog.message.match(/Writing (\d+)\s+In[‑-]article Image Prompts/i); if (match && match[1]) { return `Writing ${match[1]} In‑article Image Prompts`; } } return 'Writing X In‑article Image Prompts'; } else if (stepPhase === 'SAVE') { // For SAVE: Extract prompt count from message const promptCount = extractCount(/(\d+)\s+Prompts/i) || extractCount(/(\d+)\s+prompt/i); if (promptCount) { return `Assigning ${promptCount} Prompts to Dedicated Slots`; } // Try to extract from step log message if (stepLog?.message) { const match = stepLog.message.match(/Assigning (\d+)\s+Prompts/i); if (match && match[1]) { return `Assigning ${match[1]} Prompts to Dedicated Slots`; } } return 'Assigning Prompts to Dedicated Slots'; } } return message; }; // Build checklist items with visual completion state (needed for allStepsVisuallyCompleted) const checklistItems = useMemo(() => { return steps.map((step) => { const actuallyCompleted = isStepCompleted(step.phase, currentPhase, stepLogs); const visuallyCompleted = visuallyCompletedSteps.has(step.phase); // Don't show any step as in-progress (no blue styling) // Steps are either completed (green) or pending (gray) const inProgress = false; // Get step log and format message const stepLog = stepLogs.find(log => log.stepName === step.phase); const stepMessage = formatStepMessage(step.phase, stepLog, step.label, stepLogs, functionId, title); return { label: stepMessage, phase: step.phase, completed: visuallyCompleted, inProgress, }; }); }, [steps, currentPhase, stepLogs, visuallyCompletedSteps, functionId, title]); // Check if all steps are visually completed const allStepsVisuallyCompleted = steps.length > 0 && steps.every(step => visuallyCompletedSteps.has(step.phase)); // Track step completions with 2-second delay between each step useEffect(() => { if (!isOpen) { // Reset when modal closes setVisuallyCompletedSteps(new Set()); visuallyCompletedStepsRef.current = new Set(); lastProcessedStepLogsHashRef.current = ''; lastProcessedPhaseRef.current = ''; lastVisuallyCompletedCountRef.current = 0; stepCompletionTimersRef.current.forEach(timer => clearTimeout(timer)); stepCompletionTimersRef.current.clear(); return; } // Check if we need to process: // 1. Backend progress changed (stepLogsHash or currentPhase) // 2. A step completed visually (count increased) const hashChanged = stepLogsHash !== lastProcessedStepLogsHashRef.current; const phaseChanged = currentPhase !== lastProcessedPhaseRef.current; const countChanged = visuallyCompletedCount > lastVisuallyCompletedCountRef.current; if (!hashChanged && !phaseChanged && !countChanged) { return; // Nothing changed, skip processing } // Update last processed values lastProcessedStepLogsHashRef.current = stepLogsHash; lastProcessedPhaseRef.current = currentPhase; lastVisuallyCompletedCountRef.current = visuallyCompletedCount; const phaseOrder = ['INIT', 'PREP', 'AI_CALL', 'PARSE', 'SAVE', 'DONE']; // If status is completed, mark all steps as shouldBeCompleted const allStepsShouldComplete = status === 'completed'; // Check each step in order 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); // Check if step should be completed: // 1. Status is completed (all steps should complete) // 2. We've moved past it (currentIndex > stepIndex) // 3. We have a log entry for it with success status const shouldBeCompleted = allStepsShouldComplete || currentIndex > stepIndex || stepLogs.some(log => log.stepName === stepPhase && log.status === 'success'); // If step should be completed but isn't visually completed yet and not already scheduled if (shouldBeCompleted && !visuallyCompletedStepsRef.current.has(stepPhase) && !stepCompletionTimersRef.current.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 || visuallyCompletedStepsRef.current.has(previousStep.phase); // 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) const delay = previousStep ? 2000 : 0; // Schedule completion const timer = setTimeout(() => { setVisuallyCompletedSteps(prev => { const newSet = new Set([...prev, stepPhase]); stepCompletionTimersRef.current.delete(stepPhase); return newSet; }); }, 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, status, visuallyCompletedCount]); // Added status and count to detect completion // Don't auto-close - user must click close button // Show success alert only when all steps are visually completed AND status is completed const showSuccess = status === 'completed' && allStepsVisuallyCompleted; const successMessage = getSuccessMessage(functionId, title, stepLogs); return ( {})} className="max-w-lg" showCloseButton={false} >
{/* Header */}

{(() => { const funcName = (functionId || title || '').toLowerCase(); if (funcName.includes('cluster')) { // Try to extract keyword count from INIT step const initStepLog = stepLogs.find(log => log.stepName === 'INIT'); if (initStepLog?.message) { const message = initStepLog.message; // Try to extract from "Validating keyword1, keyword2, keyword3 and 5 more keywords" const countMatch = message.match(/(\d+)\s+more keyword/i); if (countMatch) { const moreCount = parseInt(countMatch[1], 10); const shownCount = 3; // Typically shows 3 keywords const totalCount = shownCount + moreCount; return `Mapping ${totalCount} Keywords into Keyword Clusters`; } // Try to extract from "Validating X keywords" const simpleMatch = message.match(/(\d+)\s+keyword/i); if (simpleMatch) { return `Mapping ${simpleMatch[1]} Keywords into Keyword Clusters`; } } // Try to find keyword count in any step log for (const log of stepLogs) { const keywordCountMatch = log.message?.match(/(\d+)\s+keyword/i); if (keywordCountMatch) { return `Mapping ${keywordCountMatch[1]} Keywords into Keyword Clusters`; } } } else if (funcName.includes('idea')) { // For idea generation, use fixed heading return 'Generating Content Ideas & Outline'; } else if (funcName.includes('image') && funcName.includes('from')) { // For image generation from prompts return 'Generate Images'; } else if (funcName.includes('image') && (funcName.includes('prompt') || funcName.includes('extract'))) { // For image prompt generation return 'Smart Image Prompts'; } else if (funcName.includes('image')) { // Fallback for other image functions return 'Generate Images'; } return title; })()}

{/* Subtitle for image functions */} {(() => { const funcName = (functionId || title || '').toLowerCase(); if (funcName.includes('image') && funcName.includes('from')) { // Image generation from prompts return (

Generating images from prompts using AI

); } else if (funcName.includes('image') && (funcName.includes('prompt') || funcName.includes('extract'))) { // Image prompt generation return (

Powered by Igny8 Visual Intelligence

); } return null; })()} {!showSuccess && status !== 'completed' && (

{(() => { const funcName = (functionId || title || '').toLowerCase(); // For image generation, show a more specific message if (funcName.includes('image') && funcName.includes('from')) { // Get current step message from step logs const currentStepLog = stepLogs.find(log => log.stepName === currentPhase); if (currentStepLog?.message) { return currentStepLog.message; } // Fallback to step label const currentStep = steps.find(s => s.phase === currentPhase); return currentStep?.label || 'Generating images...'; } // For other functions, use the message prop return message; })()}

)} {status === 'completed' && !allStepsVisuallyCompleted && (

Processing...

)} {/* Spinner below heading - show when processing OR when completed but steps not all visually done */} {(status === 'processing' || (status === 'completed' && !allStepsVisuallyCompleted)) && (
)}
{/* Success Alert (shown when all steps are visually completed) */} {showSuccess && (
{/* Big centered check icon */}
{/* Dark success alert box with centered text */}
{successMessage}
)} {/* Checklist-style Progress Steps - Always visible */}
{checklistItems.map((item, index) => (
{/* Icon - only checkmark for completed, gray circle for pending */}
{item.completed ? ( ) : (
)}
{/* Step Text */} {item.label}
))}
{/* Footer */} {showSuccess && onClose && (
)} {onCancel && !showSuccess && status !== 'error' && (
)} {status === 'error' && onClose && (
)}
); }