/** * Current Processing Card V2 - Simplified * Shows real-time automation progress * Clean UI without cluttered "Currently Processing" and "Up Next" sections */ import React, { useEffect, useState } from 'react'; import { automationService, ProcessingState, AutomationRun, PipelineStage } from '../../services/automationService'; import { useToast } from '../ui/toast/ToastContainer'; import Button from '../ui/button/Button'; import { PlayIcon, PauseIcon, XMarkIcon, ClockIcon, BoltIcon } from '../../icons'; interface CurrentProcessingCardProps { runId: string; siteId: number; currentRun: AutomationRun; onUpdate: () => void; onClose: () => void; pipelineOverview?: PipelineStage[]; } // Stage config matching AutomationPage const STAGE_NAMES: Record = { 1: 'Keywords → Clusters', 2: 'Clusters → Ideas', 3: 'Ideas → Tasks', 4: 'Tasks → Content', 5: 'Content → Image Prompts', 6: 'Image Prompts → Images', 7: 'Review Gate', }; const STAGE_OUTPUT_LABELS: Record = { 1: 'Clusters', 2: 'Ideas', 3: 'Tasks', 4: 'Content', 5: 'Image Prompts', 6: 'Images', 7: 'Items', }; // Helper to get processed count from stage result using correct key const getProcessedFromResult = (result: any, stageNumber: number): number => { if (!result) return 0; const keyMap: Record = { 1: 'keywords_processed', 2: 'clusters_processed', 3: 'ideas_processed', 4: 'tasks_processed', 5: 'content_processed', 6: 'images_processed', 7: 'ready_for_review' }; return result[keyMap[stageNumber]] ?? 0; }; // Helper to get total from stage result const getTotalFromResult = (result: any, stageNumber: number): number => { if (!result) return 0; const keyMap: Record = { 1: 'keywords_total', 2: 'clusters_total', 3: 'ideas_total', 4: 'tasks_total', 5: 'content_total', 6: 'images_total', 7: 'review_total' }; return result[keyMap[stageNumber]] ?? 0; }; const formatDuration = (startedAt: string): string => { if (!startedAt) return '0m'; const start = new Date(startedAt).getTime(); const now = Date.now(); const diffMs = now - start; const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); if (diffHours > 0) { return `${diffHours}h ${diffMins % 60}m`; } return `${diffMins}m`; }; const CurrentProcessingCard: React.FC = ({ runId, siteId, currentRun, onUpdate, onClose, pipelineOverview, }) => { const [processingState, setProcessingState] = useState(null); const [isPausing, setIsPausing] = useState(false); const [isResuming, setIsResuming] = useState(false); const [isCancelling, setIsCancelling] = useState(false); const toast = useToast(); // Fetch processing state useEffect(() => { let isMounted = true; const fetchState = async () => { try { const state = await automationService.getCurrentProcessing(siteId, runId); if (!isMounted) return; setProcessingState(state); // If stage completed, trigger update if (state && state.processed_items >= state.total_items && state.total_items > 0) { onUpdate(); } } catch (err) { console.error('Error fetching processing state:', err); } }; if (currentRun.status === 'running' || currentRun.status === 'paused') { fetchState(); const interval = setInterval(fetchState, 3000); return () => { isMounted = false; clearInterval(interval); }; } return () => { isMounted = false; }; }, [siteId, runId, currentRun.status, currentRun.current_stage, onUpdate]); // Get real values const stageOverview = pipelineOverview?.find(s => s.number === currentRun.current_stage); const stageResult = (currentRun as any)[`stage_${currentRun.current_stage}_result`]; const realProcessed = processingState?.processed_items ?? getProcessedFromResult(stageResult, currentRun.current_stage); const realTotal = processingState?.total_items ?? getTotalFromResult(stageResult, currentRun.current_stage) ?? (stageOverview?.pending ?? 0) + realProcessed; const realPercent = realTotal > 0 ? Math.round((realProcessed / realTotal) * 100) : 0; // REMOVED: Animated progress that was causing confusion // Now using real percentage directly from backend const displayPercent = Math.min(realPercent, 100); const isPaused = currentRun.status === 'paused'; const stageName = STAGE_NAMES[currentRun.current_stage] || `Stage ${currentRun.current_stage}`; const outputLabel = STAGE_OUTPUT_LABELS[currentRun.current_stage] || 'Items'; const handlePause = async () => { setIsPausing(true); try { await automationService.pause(siteId, runId); toast?.success('Automation pausing...'); setTimeout(onUpdate, 1000); } catch (error: any) { toast?.error(error?.message || 'Failed to pause'); } finally { setIsPausing(false); } }; const handleResume = async () => { setIsResuming(true); try { await automationService.resume(siteId, runId); toast?.success('Automation resumed'); setTimeout(onUpdate, 1000); } catch (error: any) { toast?.error(error?.message || 'Failed to resume'); } finally { setIsResuming(false); } }; const handleCancel = async () => { if (!confirm('Cancel this automation run? Progress will be saved.')) return; setIsCancelling(true); try { await automationService.cancel(siteId, runId); toast?.success('Automation cancelled'); setTimeout(onUpdate, 500); } catch (error: any) { toast?.error(error?.message || 'Failed to cancel'); } finally { setIsCancelling(false); } }; return (
{/* Header Row */}
{isPaused ? ( ) : ( )}

{isPaused ? 'Automation Paused' : 'Automation In Progress'}

Stage {currentRun.current_stage}: {stageName} {stageOverview?.type || 'AI'}
{/* Close and Actions */}
{/* Progress Section */}
{/* Main Progress - 70% width */}
{/* Progress Text */}
{displayPercent}% {realProcessed}/{realTotal} {outputLabel}
{realTotal - realProcessed} remaining
{/* Progress Bar */}
{/* Control Buttons */}
{currentRun.status === 'running' ? ( ) : currentRun.status === 'paused' ? ( ) : null}
{/* Metrics - 30% width */}
Duration
{formatDuration(currentRun.started_at)}
Credits
{currentRun.total_credits_used}
Stage {currentRun.current_stage} of 7
Status {isPaused ? 'Paused' : 'Running'}
); }; export default CurrentProcessingCard;