8 Phases refactor

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-03 16:08:02 +00:00
parent 30bbcb08a1
commit 39df00e5ae
55 changed files with 2120 additions and 5527 deletions

View File

@@ -28,13 +28,13 @@ import {
} from '../../icons';
const STAGE_CONFIG = [
{ icon: ListIcon, color: 'from-blue-500 to-blue-600', hoverColor: 'hover:border-blue-500', name: 'Keywords → Clusters' },
{ icon: GroupIcon, color: 'from-purple-500 to-purple-600', hoverColor: 'hover:border-purple-500', name: 'Clusters → Ideas' },
{ icon: CheckCircleIcon, color: 'from-indigo-500 to-indigo-600', hoverColor: 'hover:border-indigo-500', name: 'Ideas → Tasks' },
{ icon: PencilIcon, color: 'from-green-500 to-green-600', hoverColor: 'hover:border-green-500', name: 'Tasks → Content' },
{ icon: FileIcon, color: 'from-amber-500 to-amber-600', hoverColor: 'hover:border-amber-500', name: 'Content → Image Prompts' },
{ icon: FileTextIcon, color: 'from-pink-500 to-pink-600', hoverColor: 'hover:border-pink-500', name: 'Image Prompts → Images' },
{ icon: PaperPlaneIcon, color: 'from-teal-500 to-teal-600', hoverColor: 'hover:border-teal-500', name: 'Manual Review Gate' },
{ icon: ListIcon, color: 'from-blue-500 to-blue-600', textColor: 'text-blue-600', hoverColor: 'hover:border-blue-500', name: 'Keywords → Clusters' },
{ icon: GroupIcon, color: 'from-purple-500 to-purple-600', textColor: 'text-purple-600', hoverColor: 'hover:border-purple-500', name: 'Clusters → Ideas' },
{ icon: CheckCircleIcon, color: 'from-indigo-500 to-indigo-600', textColor: 'text-indigo-600', hoverColor: 'hover:border-indigo-500', name: 'Ideas → Tasks' },
{ icon: PencilIcon, color: 'from-green-500 to-green-600', textColor: 'text-green-600', hoverColor: 'hover:border-green-500', name: 'Tasks → Content' },
{ icon: FileIcon, color: 'from-amber-500 to-amber-600', textColor: 'text-amber-600', hoverColor: 'hover:border-amber-500', name: 'Content → Image Prompts' },
{ icon: FileTextIcon, color: 'from-pink-500 to-pink-600', textColor: 'text-pink-600', hoverColor: 'hover:border-pink-500', name: 'Image Prompts → Images' },
{ icon: PaperPlaneIcon, color: 'from-teal-500 to-teal-600', textColor: 'text-teal-600', hoverColor: 'hover:border-teal-500', name: 'Manual Review Gate' },
];
const AutomationPage: React.FC = () => {
@@ -196,47 +196,41 @@ const AutomationPage: React.FC = () => {
<DebugSiteSelector />
</div>
{/* Schedule & Controls */}
{/* Compact Schedule & Controls Panel */}
{config && (
<ComponentCard className="border-2 border-slate-200 dark:border-gray-800">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
<div className="flex-1 grid grid-cols-2 md:grid-cols-4 gap-4">
<div>
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1.5">Status</div>
<div className="flex items-center gap-2">
{config.is_enabled ? (
<>
<div className="size-2 bg-success-500 rounded-full animate-pulse"></div>
<span className="text-sm font-semibold text-success-600 dark:text-success-400">Enabled</span>
</>
) : (
<>
<div className="size-2 bg-gray-400 rounded-full"></div>
<span className="text-sm font-semibold text-gray-600 dark:text-gray-400">Disabled</span>
</>
)}
</div>
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-3 py-1">
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
{config.is_enabled ? (
<>
<div className="size-2 bg-success-500 rounded-full animate-pulse"></div>
<span className="text-sm font-semibold text-success-600 dark:text-success-400">Enabled</span>
</>
) : (
<>
<div className="size-2 bg-gray-400 rounded-full"></div>
<span className="text-sm font-semibold text-gray-600 dark:text-gray-400">Disabled</span>
</>
)}
</div>
<div>
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1.5">Schedule</div>
<div className="text-sm font-semibold text-slate-900 dark:text-white/90 capitalize">
{config.frequency} at {config.scheduled_time}
</div>
<div className="h-4 w-px bg-slate-300 dark:bg-gray-700"></div>
<div className="text-sm text-slate-700 dark:text-gray-300">
<span className="font-medium capitalize">{config.frequency}</span> at {config.scheduled_time}
</div>
<div>
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1.5">Last Run</div>
<div className="text-sm font-semibold text-slate-900 dark:text-white/90">
{config.last_run_at ? new Date(config.last_run_at).toLocaleDateString() : 'Never'}
</div>
<div className="h-4 w-px bg-slate-300 dark:bg-gray-700"></div>
<div className="text-sm text-slate-600 dark:text-gray-400">
Last: {config.last_run_at ? new Date(config.last_run_at).toLocaleDateString() : 'Never'}
</div>
<div>
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1.5">Estimated Credits</div>
<div className="text-sm font-semibold text-brand-600 dark:text-brand-400">
<div className="h-4 w-px bg-slate-300 dark:bg-gray-700"></div>
<div className="text-sm">
<span className="text-gray-600 dark:text-gray-400">Est:</span>{' '}
<span className="font-semibold text-brand-600 dark:text-brand-400">
{estimate?.estimated_credits || 0} credits
{estimate && !estimate.sufficient && (
<span className="ml-1 text-error-600 dark:text-error-400">(Low)</span>
)}
</div>
</span>
{estimate && !estimate.sufficient && (
<span className="ml-1 text-error-600 dark:text-error-400 font-semibold">(Low)</span>
)}
</div>
</div>
<div className="flex items-center gap-2">
@@ -263,323 +257,426 @@ const AutomationPage: React.FC = () => {
</ComponentCard>
)}
{/* Pipeline Overview */}
<ComponentCard title="📊 Pipeline Overview" desc="Complete view of automation pipeline status and pending items">
<div className="mb-6 flex items-center justify-between px-4 py-3 bg-slate-50 dark:bg-white/[0.02] rounded-lg border border-slate-200 dark:border-gray-800">
<div className="text-sm font-medium">
{currentRun ? (
<span className="text-blue-600 dark:text-blue-400">
<span className="inline-block size-2 bg-blue-500 rounded-full animate-pulse mr-2"></span>
Live Run Active - Stage {currentRun.current_stage} of 7
</span>
) : (
<span className="text-slate-700 dark:text-gray-300">Pipeline Status - Ready to run</span>
)}
{/* Metrics Summary Cards */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
<div className="bg-gradient-to-br from-blue-50 to-blue-100 dark:from-blue-900/20 dark:to-blue-800/20 rounded-xl p-4 border-2 border-blue-200 dark:border-blue-800">
<div className="flex items-center gap-3 mb-3">
<div className="size-10 rounded-lg bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center">
<ListIcon className="size-5 text-white" />
</div>
<div className="text-sm font-bold text-blue-900 dark:text-blue-100">Keywords</div>
</div>
<div className="text-sm font-semibold text-slate-600 dark:text-gray-400">
{totalPending} items pending
<div className="space-y-1 text-xs">
<div className="flex justify-between">
<span className="text-blue-700 dark:text-blue-300">Total:</span>
<span className="font-bold text-blue-900 dark:text-blue-100">{pipelineOverview[0]?.pending || 0}</span>
</div>
</div>
</div>
{/* Pipeline Overview - 5 cards spread to full width */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
{/* Stages 1-6 in main row (with 3+4 combined) */}
{pipelineOverview.slice(0, 6).map((stage, index) => {
// Combine stages 3 and 4 into one card
if (index === 2) {
const stage3 = pipelineOverview[2];
const stage4 = pipelineOverview[3];
const isActive3 = currentRun?.current_stage === 3;
const isActive4 = currentRun?.current_stage === 4;
const isComplete3 = currentRun && currentRun.current_stage > 3;
const isComplete4 = currentRun && currentRun.current_stage > 4;
const result3 = currentRun ? (currentRun[`stage_3_result` as keyof AutomationRun] as any) : null;
const result4 = currentRun ? (currentRun[`stage_4_result` as keyof AutomationRun] as any) : null;
return (
<div
key="stage-3-4"
className={`
relative rounded-xl border-2 p-6 transition-all
${isActive3 || isActive4
? 'border-blue-500 bg-blue-50 dark:bg-blue-500/10 shadow-lg'
: isComplete3 && isComplete4
? 'border-success-500 bg-success-50 dark:bg-success-500/10'
: (stage3.pending > 0 || stage4.pending > 0)
? `border-slate-200 bg-white dark:bg-white/[0.03] dark:border-gray-800 hover:border-indigo-500 hover:shadow-lg`
: 'border-slate-200 bg-slate-50 dark:bg-white/[0.02] dark:border-gray-800'
}
`}
>
<div className="flex items-start justify-between mb-4">
<div className="text-base font-bold text-gray-500 dark:text-gray-400">Stages 3 & 4</div>
<div className={`size-14 rounded-xl bg-gradient-to-br from-indigo-500 to-green-600 flex items-center justify-center shadow-lg`}>
<ArrowRightIcon className="size-7 text-white" />
</div>
<div className="bg-gradient-to-br from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/20 rounded-xl p-4 border-2 border-purple-200 dark:border-purple-800">
<div className="flex items-center gap-3 mb-3">
<div className="size-10 rounded-lg bg-gradient-to-br from-purple-500 to-purple-600 flex items-center justify-center">
<GroupIcon className="size-5 text-white" />
</div>
<div className="text-sm font-bold text-purple-900 dark:text-purple-100">Clusters</div>
</div>
<div className="space-y-1 text-xs">
<div className="flex justify-between">
<span className="text-purple-700 dark:text-purple-300">Pending:</span>
<span className="font-bold text-purple-900 dark:text-purple-100">{pipelineOverview[1]?.pending || 0}</span>
</div>
</div>
</div>
<div className="bg-gradient-to-br from-indigo-50 to-indigo-100 dark:from-indigo-900/20 dark:to-indigo-800/20 rounded-xl p-4 border-2 border-indigo-200 dark:border-indigo-800">
<div className="flex items-center gap-3 mb-3">
<div className="size-10 rounded-lg bg-gradient-to-br from-indigo-500 to-indigo-600 flex items-center justify-center">
<CheckCircleIcon className="size-5 text-white" />
</div>
<div className="text-sm font-bold text-indigo-900 dark:text-indigo-100">Ideas</div>
</div>
<div className="space-y-1 text-xs">
<div className="flex justify-between">
<span className="text-indigo-700 dark:text-indigo-300">Pending:</span>
<span className="font-bold text-indigo-900 dark:text-indigo-100">{pipelineOverview[2]?.pending || 0}</span>
</div>
</div>
</div>
<div className="bg-gradient-to-br from-green-50 to-green-100 dark:from-green-900/20 dark:to-green-800/20 rounded-xl p-4 border-2 border-green-200 dark:border-green-800">
<div className="flex items-center gap-3 mb-3">
<div className="size-10 rounded-lg bg-gradient-to-br from-green-500 to-green-600 flex items-center justify-center">
<FileTextIcon className="size-5 text-white" />
</div>
<div className="text-sm font-bold text-green-900 dark:text-green-100">Content</div>
</div>
<div className="space-y-1 text-xs">
<div className="flex justify-between">
<span className="text-green-700 dark:text-green-300">Tasks:</span>
<span className="font-bold text-green-900 dark:text-green-100">{pipelineOverview[3]?.pending || 0}</span>
</div>
</div>
</div>
<div className="bg-gradient-to-br from-pink-50 to-pink-100 dark:from-pink-900/20 dark:to-pink-800/20 rounded-xl p-4 border-2 border-pink-200 dark:border-pink-800">
<div className="flex items-center gap-3 mb-3">
<div className="size-10 rounded-lg bg-gradient-to-br from-pink-500 to-pink-600 flex items-center justify-center">
<FileIcon className="size-5 text-white" />
</div>
<div className="text-sm font-bold text-pink-900 dark:text-pink-100">Images</div>
</div>
<div className="space-y-1 text-xs">
<div className="flex justify-between">
<span className="text-pink-700 dark:text-pink-300">Pending:</span>
<span className="font-bold text-pink-900 dark:text-pink-100">{pipelineOverview[5]?.pending || 0}</span>
</div>
</div>
</div>
</div>
{/* Pipeline Status Card - Centered */}
<div className="flex justify-center">
<div className="max-w-2xl w-full">
<div className={`
rounded-2xl border-3 p-6 shadow-xl transition-all
${currentRun?.status === 'running'
? 'border-blue-500 bg-gradient-to-br from-blue-50 to-blue-100 dark:from-blue-900/30 dark:to-blue-800/30'
: currentRun?.status === 'paused'
? 'border-amber-500 bg-gradient-to-br from-amber-50 to-amber-100 dark:from-amber-900/30 dark:to-amber-800/30'
: totalPending > 0
? 'border-success-500 bg-gradient-to-br from-success-50 to-success-100 dark:from-success-900/30 dark:to-success-800/30'
: 'border-slate-300 bg-gradient-to-br from-slate-50 to-slate-100 dark:from-gray-800/30 dark:to-gray-700/30'
}
`}>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-4">
<div className={`
size-16 rounded-2xl flex items-center justify-center shadow-lg
${currentRun?.status === 'running'
? 'bg-gradient-to-br from-blue-500 to-blue-600'
: currentRun?.status === 'paused'
? 'bg-gradient-to-br from-amber-500 to-amber-600'
: totalPending > 0
? 'bg-gradient-to-br from-success-500 to-success-600'
: 'bg-gradient-to-br from-slate-400 to-slate-500'
}
`}>
{currentRun?.status === 'running' && <div className="size-3 bg-white rounded-full animate-pulse"></div>}
{currentRun?.status === 'paused' && <ClockIcon className="size-8 text-white" />}
{!currentRun && totalPending > 0 && <CheckCircleIcon className="size-8 text-white" />}
{!currentRun && totalPending === 0 && <BoltIcon className="size-8 text-white" />}
</div>
<div>
<div className="text-2xl font-bold text-slate-900 dark:text-white mb-1">
{currentRun?.status === 'running' && `Running - Stage ${currentRun.current_stage}/7`}
{currentRun?.status === 'paused' && 'Paused'}
{!currentRun && totalPending > 0 && 'Ready to Run'}
{!currentRun && totalPending === 0 && 'No Items Pending'}
</div>
<div className="text-base font-bold text-slate-900 dark:text-white/90 mb-5 leading-tight">
Ideas Tasks Content
</div>
{/* Queue Details - Always Show */}
<div className="space-y-3">
{/* Stage 3 queue */}
<div className="pb-3 border-b border-slate-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-semibold text-indigo-600 dark:text-indigo-400">Ideas Tasks</span>
{isActive3 && <span className="text-xs px-2 py-0.5 bg-blue-500 text-white rounded-full"> Processing</span>}
{isComplete3 && <span className="text-xs px-2 py-0.5 bg-success-500 text-white rounded-full"> Completed</span>}
</div>
{result3 ? (
<div className="text-xs space-y-1">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Processed:</span>
<span className="font-bold text-slate-900 dark:text-white">{result3.ideas_processed || 0}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Created:</span>
<span className="font-bold text-slate-900 dark:text-white">{result3.tasks_created || 0}</span>
</div>
</div>
) : (
<div className="text-xs space-y-1.5">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Total Queue:</span>
<span className="font-bold text-slate-900 dark:text-white">{stage3.pending}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Processed:</span>
<span className="font-bold text-slate-900 dark:text-white">0</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Remaining:</span>
<span className="font-bold text-indigo-600 dark:text-indigo-400">{stage3.pending}</span>
</div>
</div>
)}
</div>
{/* Stage 4 queue */}
<div>
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-semibold text-green-600 dark:text-green-400">Tasks Content</span>
{isActive4 && <span className="text-xs px-2 py-0.5 bg-blue-500 text-white rounded-full"> Processing</span>}
{isComplete4 && <span className="text-xs px-2 py-0.5 bg-success-500 text-white rounded-full"> Completed</span>}
</div>
{result4 ? (
<div className="text-xs space-y-1">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Processed:</span>
<span className="font-bold text-slate-900 dark:text-white">{result4.tasks_processed || 0}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Created:</span>
<span className="font-bold text-slate-900 dark:text-white">{result4.content_created || 0}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Credits:</span>
<span className="font-bold text-amber-600 dark:text-amber-400">{result4.credits_used || 0}</span>
</div>
</div>
) : (
<div className="text-xs space-y-1.5">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Total Queue:</span>
<span className="font-bold text-slate-900 dark:text-white">{stage4.pending}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Processed:</span>
<span className="font-bold text-slate-900 dark:text-white">0</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Remaining:</span>
<span className="font-bold text-green-600 dark:text-green-400">{stage4.pending}</span>
</div>
</div>
)}
</div>
<div className="text-sm text-slate-600 dark:text-gray-300">
{currentRun && `Started: ${new Date(currentRun.started_at).toLocaleTimeString()}`}
{!currentRun && totalPending > 0 && `${totalPending} items in pipeline`}
{!currentRun && totalPending === 0 && 'All stages clear'}
</div>
</div>
);
}
</div>
{currentRun && (
<div className="text-right">
<div className="text-sm text-slate-600 dark:text-gray-400">Credits Used</div>
<div className="text-3xl font-bold text-brand-600 dark:text-brand-400">{currentRun.total_credits_used}</div>
</div>
)}
</div>
// Skip stage 4 since it's combined with 3
if (index === 3) return null;
// Adjust index for stages 5 and 6 (shift by 1 because we skip stage 4)
const actualStage = index < 3 ? stage : pipelineOverview[index + 1];
const stageConfig = STAGE_CONFIG[index < 3 ? index : index + 1];
{/* Overall Progress Bar */}
{currentRun && currentRun.status === 'running' && (
<div className="mt-4">
<div className="flex justify-between text-xs text-slate-600 dark:text-gray-400 mb-2">
<span>Overall Progress</span>
<span>{Math.round((currentRun.current_stage / 7) * 100)}%</span>
</div>
<div className="w-full bg-slate-200 dark:bg-gray-700 rounded-full h-3 overflow-hidden">
<div
className="bg-gradient-to-r from-blue-500 to-purple-600 h-3 rounded-full transition-all duration-500 animate-pulse"
style={{ width: `${(currentRun.current_stage / 7) * 100}%` }}
></div>
</div>
</div>
)}
</div>
</div>
</div>
{/* Pipeline Stages */}
<ComponentCard>
{/* Row 1: Stages 1-4 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
{pipelineOverview.slice(0, 4).map((stage, index) => {
const stageConfig = STAGE_CONFIG[index];
const StageIcon = stageConfig.icon;
const isActive = currentRun?.current_stage === actualStage.number;
const isComplete = currentRun && currentRun.current_stage > actualStage.number;
const result = currentRun ? (currentRun[`stage_${actualStage.number}_result` as keyof AutomationRun] as any) : null;
const isActive = currentRun?.current_stage === stage.number;
const isComplete = currentRun && currentRun.current_stage > stage.number;
const result = currentRun ? (currentRun[`stage_${stage.number}_result` as keyof AutomationRun] as any) : null;
const processed = result ? Object.values(result).reduce((sum: number, val) => typeof val === 'number' ? sum + val : sum, 0) : 0;
const progressPercent = stage.pending > 0 ? Math.round((processed / (processed + stage.pending)) * 100) : 0;
return (
<div
key={actualStage.number}
key={stage.number}
className={`
relative rounded-xl border-2 p-6 transition-all
relative rounded-xl border-2 p-5 transition-all
${isActive
? 'border-blue-500 bg-blue-50 dark:bg-blue-500/10 shadow-lg'
: isComplete
? 'border-success-500 bg-success-50 dark:bg-success-500/10'
: actualStage.pending > 0
: stage.pending > 0
? `border-slate-200 bg-white dark:bg-white/[0.03] dark:border-gray-800 ${stageConfig.hoverColor} hover:shadow-lg`
: 'border-slate-200 bg-slate-50 dark:bg-white/[0.02] dark:border-gray-800'
}
`}
>
{/* Header */}
<div className="flex items-start justify-between mb-4">
{/* Compact Header */}
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="text-base font-bold text-gray-500 dark:text-gray-400 mb-1">Stage {actualStage.number}</div>
{isActive && <span className="text-xs px-2 py-0.5 bg-blue-500 text-white rounded-full"> Processing</span>}
{isComplete && <span className="text-xs px-2 py-0.5 bg-success-500 text-white rounded-full"> Completed</span>}
{!isActive && !isComplete && actualStage.pending > 0 && <span className="text-xs px-2 py-0.5 bg-gray-400 text-white rounded-full">Ready</span>}
<div className="flex items-center gap-2 mb-1">
<div className="text-sm font-bold text-gray-900 dark:text-white">Stage {stage.number}</div>
{isActive && <span className="text-xs px-2 py-0.5 bg-blue-500 text-white rounded-full"> Active</span>}
{isComplete && <span className="text-xs px-2 py-0.5 bg-success-500 text-white rounded-full"></span>}
{!isActive && !isComplete && stage.pending > 0 && <span className="text-xs px-2 py-0.5 bg-gray-400 text-white rounded-full">Ready</span>}
</div>
<div className="text-xs font-medium text-gray-600 dark:text-gray-400">{stageConfig.name}</div>
</div>
<div className={`size-14 rounded-xl bg-gradient-to-br ${stageConfig.color} flex items-center justify-center shadow-lg`}>
<StageIcon className="size-7 text-white" />
<div className={`size-8 rounded-lg bg-gradient-to-br ${stageConfig.color} flex items-center justify-center shadow-md flex-shrink-0`}>
<StageIcon className="size-4 text-white" />
</div>
</div>
{/* Stage Name */}
<div className="text-base font-bold text-slate-900 dark:text-white/90 mb-5 leading-tight min-h-[40px]">
{actualStage.name}
{/* Queue Metrics */}
<div className="space-y-1.5 text-xs mb-3">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Total Queue:</span>
<span className="font-bold text-slate-900 dark:text-white">{stage.pending}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Processed:</span>
<span className="font-bold text-slate-900 dark:text-white">{processed}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Remaining:</span>
<span className={`font-bold ${stageConfig.textColor} dark:${stageConfig.textColor}`}>
{stage.pending}
</span>
</div>
</div>
{/* Status Details - Always Show */}
<div className="space-y-3">
{/* Show results if completed */}
{result && (
<div className="text-xs space-y-1.5">
{Object.entries(result).map(([key, value]) => (
<div key={key} className="flex justify-between items-center">
<span className="text-gray-600 dark:text-gray-400 capitalize">{key.replace(/_/g, ' ')}:</span>
<span className={`font-bold ${key.includes('credits') ? 'text-amber-600 dark:text-amber-400' : 'text-slate-900 dark:text-white'}`}>
{value}
</span>
</div>
))}
{/* Progress Bar */}
{(isActive || isComplete || processed > 0) && (
<div className="mt-3 pt-3 border-t border-slate-200 dark:border-gray-700">
<div className="flex justify-between text-xs text-gray-600 dark:text-gray-400 mb-1.5">
<span>Progress</span>
<span>{isComplete ? '100' : progressPercent}%</span>
</div>
)}
{/* Show queue details if not completed */}
{!result && (
<div className="text-xs space-y-1.5 border-t border-slate-200 dark:border-gray-700 pt-3">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Total Queue:</span>
<span className="font-bold text-slate-900 dark:text-white">{actualStage.pending}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Processed:</span>
<span className="font-bold text-slate-900 dark:text-white">0</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Remaining:</span>
<span className={`font-bold ${stageConfig.color.split(' ')[1]}`}>
{actualStage.pending}
</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 overflow-hidden">
<div
className={`bg-gradient-to-r ${stageConfig.color} h-2 rounded-full transition-all duration-500 ${isActive ? 'animate-pulse' : ''}`}
style={{ width: `${isComplete ? 100 : progressPercent}%` }}
></div>
</div>
)}
{/* Show processing indicator if active */}
{isActive && (
<div className="pt-3 border-t border-blue-200 dark:border-blue-700">
<div className="flex items-center gap-2 mb-2">
<div className="size-2 bg-blue-500 rounded-full animate-pulse"></div>
<span className="text-xs text-blue-600 dark:text-blue-400 font-semibold">Processing...</span>
</div>
{/* Progress bar placeholder */}
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-1.5">
<div className="bg-blue-500 h-1.5 rounded-full animate-pulse" style={{ width: '45%' }}></div>
</div>
</div>
)}
{/* Show empty state */}
{!result && actualStage.pending === 0 && !isActive && (
<div className="pt-3 border-t border-slate-200 dark:border-gray-700 text-center">
<span className="text-xs text-gray-500 dark:text-gray-400">No items to process</span>
</div>
)}
</div>
</div>
)}
</div>
);
})}
</div>
{/* Stage 7 - Manual Review Gate (Separate Row) */}
{pipelineOverview[6] && (
<div className="mt-8">
<div className="max-w-3xl mx-auto">
{(() => {
const stage7 = pipelineOverview[6];
const isActive = currentRun?.current_stage === 7;
const isComplete = currentRun && currentRun.current_stage > 7;
const result = currentRun ? (currentRun[`stage_7_result` as keyof AutomationRun] as any) : null;
{/* Row 2: Stages 5-7 + Status Summary */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* Stages 5-6 */}
{pipelineOverview.slice(4, 6).map((stage, index) => {
const actualIndex = index + 4;
const stageConfig = STAGE_CONFIG[actualIndex];
const StageIcon = stageConfig.icon;
const isActive = currentRun?.current_stage === stage.number;
const isComplete = currentRun && currentRun.current_stage > stage.number;
const result = currentRun ? (currentRun[`stage_${stage.number}_result` as keyof AutomationRun] as any) : null;
const processed = result ? Object.values(result).reduce((sum: number, val) => typeof val === 'number' ? sum + val : sum, 0) : 0;
const progressPercent = stage.pending > 0 ? Math.round((processed / (processed + stage.pending)) * 100) : 0;
return (
<div
key={stage.number}
className={`
relative rounded-xl border-2 p-5 transition-all
${isActive
? 'border-blue-500 bg-blue-50 dark:bg-blue-500/10 shadow-lg'
: isComplete
? 'border-success-500 bg-success-50 dark:bg-success-500/10'
: stage.pending > 0
? `border-slate-200 bg-white dark:bg-white/[0.03] dark:border-gray-800 ${stageConfig.hoverColor} hover:shadow-lg`
: 'border-slate-200 bg-slate-50 dark:bg-white/[0.02] dark:border-gray-800'
}
`}
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<div className="text-sm font-bold text-gray-900 dark:text-white">Stage {stage.number}</div>
{isActive && <span className="text-xs px-2 py-0.5 bg-blue-500 text-white rounded-full"> Active</span>}
{isComplete && <span className="text-xs px-2 py-0.5 bg-success-500 text-white rounded-full"></span>}
{!isActive && !isComplete && stage.pending > 0 && <span className="text-xs px-2 py-0.5 bg-gray-400 text-white rounded-full">Ready</span>}
</div>
<div className="text-xs font-medium text-gray-600 dark:text-gray-400">{stageConfig.name}</div>
</div>
<div className={`size-8 rounded-lg bg-gradient-to-br ${stageConfig.color} flex items-center justify-center shadow-md flex-shrink-0`}>
<StageIcon className="size-4 text-white" />
</div>
</div>
return (
<div
className={`
relative rounded-2xl border-3 p-8 transition-all text-center shadow-xl
${isActive
? 'border-blue-500 bg-blue-50 dark:bg-blue-500/10'
: isComplete
? 'border-success-500 bg-success-50 dark:bg-success-500/10'
: stage7.pending > 0
? `border-slate-300 bg-white dark:bg-white/[0.05] dark:border-gray-700 hover:border-teal-500`
: 'border-slate-200 bg-slate-50 dark:bg-white/[0.02] dark:border-gray-800'
}
`}
>
<div className="flex flex-col items-center">
<div className={`size-20 mb-5 rounded-2xl bg-gradient-to-br ${STAGE_CONFIG[6].color} flex items-center justify-center shadow-2xl`}>
<PaperPlaneIcon className="size-10 text-white" />
</div>
<div className="text-sm font-bold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3">Stage 7</div>
<div className="text-2xl font-bold text-slate-900 dark:text-white/90 mb-3">
Manual Review Gate
</div>
<div className="text-lg text-red-600 dark:text-red-400 font-semibold mb-6">
🚫 Automation Stops Here
</div>
{stage7.pending > 0 && (
<div className="mb-6 p-6 bg-teal-50 dark:bg-teal-900/20 rounded-xl border-2 border-teal-200 dark:border-teal-800">
<div className="text-base text-gray-600 dark:text-gray-300 mb-3 font-medium">Content Ready for Manual Review</div>
<div className="text-6xl font-bold text-teal-600 dark:text-teal-400">{stage7.pending}</div>
<div className="mt-3 text-sm text-gray-500 dark:text-gray-400">pieces of content waiting</div>
</div>
)}
{result && (
<div className="pt-6 border-t-2 border-slate-200 dark:border-gray-700 w-full max-w-lg mx-auto">
<div className="text-sm font-semibold text-gray-600 dark:text-gray-400 mb-4">Last Run Results</div>
<div className="grid grid-cols-2 gap-6">
{Object.entries(result).map(([key, value]) => (
<div key={key} className="text-center p-4 bg-slate-50 dark:bg-white/5 rounded-lg">
<div className="text-gray-600 dark:text-gray-400 capitalize text-sm mb-2">{key.replace(/_/g, ' ')}</div>
<div className="font-bold text-2xl text-slate-900 dark:text-white/90">{value}</div>
</div>
))}
</div>
</div>
)}
<div className="mt-6 p-4 bg-amber-50 dark:bg-amber-900/20 rounded-lg border border-amber-200 dark:border-amber-800 max-w-xl">
<div className="text-sm text-gray-700 dark:text-gray-300 leading-relaxed">
<strong>Note:</strong> Automation ends when content reaches draft status with all images generated.
Please review content quality, accuracy, and brand voice manually before publishing to WordPress.
</div>
</div>
<div className="space-y-1.5 text-xs mb-3">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Total Queue:</span>
<span className="font-bold text-slate-900 dark:text-white">{stage.pending}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Processed:</span>
<span className="font-bold text-slate-900 dark:text-white">{processed}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Remaining:</span>
<span className={`font-bold ${stageConfig.textColor}`}>
{stage.pending}
</span>
</div>
</div>
{(isActive || isComplete || processed > 0) && (
<div className="mt-3 pt-3 border-t border-slate-200 dark:border-gray-700">
<div className="flex justify-between text-xs text-gray-600 dark:text-gray-400 mb-1.5">
<span>Progress</span>
<span>{isComplete ? '100' : progressPercent}%</span>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 overflow-hidden">
<div
className={`bg-gradient-to-r ${stageConfig.color} h-2 rounded-full transition-all duration-500 ${isActive ? 'animate-pulse' : ''}`}
style={{ width: `${isComplete ? 100 : progressPercent}%` }}
></div>
</div>
</div>
);
})()}
)}
</div>
);
})}
{/* Stage 7 - Manual Review Gate */}
{pipelineOverview[6] && (() => {
const stage7 = pipelineOverview[6];
const isActive = currentRun?.current_stage === 7;
const isComplete = currentRun && currentRun.current_stage > 7;
return (
<div
className={`
relative rounded-xl border-3 p-5 transition-all
${isActive
? 'border-amber-500 bg-amber-50 dark:bg-amber-500/10 shadow-lg'
: isComplete
? 'border-success-500 bg-success-50 dark:bg-success-500/10'
: stage7.pending > 0
? 'border-amber-300 bg-amber-50 dark:bg-amber-900/20 dark:border-amber-700'
: 'border-slate-200 bg-slate-50 dark:bg-white/[0.02] dark:border-gray-800'
}
`}
>
<div className="flex items-start justify-between mb-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<div className="text-sm font-bold text-gray-900 dark:text-white">Stage 7</div>
<span className="text-xs px-2 py-0.5 bg-amber-500 text-white rounded-full">🚫 Stop</span>
</div>
<div className="text-xs font-medium text-amber-700 dark:text-amber-300">Manual Review Gate</div>
</div>
<div className="size-8 rounded-lg bg-gradient-to-br from-amber-500 to-amber-600 flex items-center justify-center shadow-md">
<PaperPlaneIcon className="size-4 text-white" />
</div>
</div>
{stage7.pending > 0 && (
<div className="text-center py-4">
<div className="text-3xl font-bold text-amber-600 dark:text-amber-400">{stage7.pending}</div>
<div className="text-xs text-amber-700 dark:text-amber-300 mt-1">ready for review</div>
</div>
)}
<div className="mt-3 pt-3 border-t border-amber-200 dark:border-amber-700">
<Button
variant="primary"
tone="brand"
size="sm"
className="w-full text-xs"
disabled={stage7.pending === 0}
>
Go to Review
</Button>
</div>
</div>
);
})()}
{/* Status Summary Card */}
{currentRun && (
<div className="relative rounded-xl border-2 border-slate-300 dark:border-gray-700 p-5 bg-gradient-to-br from-slate-100 to-slate-200 dark:from-gray-800/50 dark:to-gray-700/50">
<div className="mb-3">
<div className="text-sm font-bold text-gray-900 dark:text-white mb-1">Current Status</div>
<div className="text-xs font-medium text-gray-600 dark:text-gray-400">Run Summary</div>
</div>
<div className="space-y-2 text-xs">
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Run ID:</span>
<span className="font-mono text-xs text-slate-900 dark:text-white">{currentRun.run_id.split('_').pop()}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Started:</span>
<span className="font-semibold text-slate-900 dark:text-white">
{new Date(currentRun.started_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Current Stage:</span>
<span className="font-bold text-blue-600 dark:text-blue-400">{currentRun.current_stage}/7</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Credits Used:</span>
<span className="font-bold text-brand-600 dark:text-brand-400">{currentRun.total_credits_used}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600 dark:text-gray-400">Completion:</span>
<span className="font-bold text-slate-900 dark:text-white">{Math.round((currentRun.current_stage / 7) * 100)}%</span>
</div>
</div>
<div className="mt-4 pt-3 border-t border-slate-300 dark:border-gray-600">
<div className={`
size-12 mx-auto rounded-full flex items-center justify-center
${currentRun.status === 'running'
? 'bg-gradient-to-br from-blue-500 to-blue-600 animate-pulse'
: currentRun.status === 'paused'
? 'bg-gradient-to-br from-amber-500 to-amber-600'
: 'bg-gradient-to-br from-success-500 to-success-600'
}
`}>
{currentRun.status === 'running' && <div className="size-3 bg-white rounded-full"></div>}
{currentRun.status === 'paused' && <ClockIcon className="size-6 text-white" />}
{currentRun.status === 'completed' && <CheckCircleIcon className="size-6 text-white" />}
</div>
<div className="text-center mt-2 text-xs font-semibold text-gray-700 dark:text-gray-300 capitalize">
{currentRun.status}
</div>
</div>
</div>
</div>
)}
)}
</div>
</ComponentCard>
{/* Current Run Details */}