Automation revamp part 1

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-28 01:46:27 +00:00
parent 0605f650b1
commit ea9125b805
9 changed files with 1237 additions and 58 deletions

View File

@@ -259,9 +259,25 @@ const CurrentProcessingCard: React.FC<CurrentProcessingCardProps> = ({
const stageOverview = pipelineOverview && pipelineOverview[currentStageIndex] ? pipelineOverview[currentStageIndex] : null;
const stageResult = (currentRun as any)[`stage_${currentRun.current_stage}_result`];
// FIXED: Helper to get processed count using correct key
const getProcessedFromResult = (result: any, stageNumber: number): number => {
if (!result) return 0;
const keyMap: Record<number, string> = {
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;
};
const fallbackState: ProcessingState | null = ((): ProcessingState | null => {
if (!processingState && (stageOverview || stageResult)) {
const processed = stageResult ? Object.values(stageResult).reduce((s: number, v: any) => typeof v === 'number' ? s + v : s, 0) : 0;
// FIXED: Use stage-specific key instead of summing all numeric values
const processed = getProcessedFromResult(stageResult, currentRun.current_stage);
const total = (stageOverview?.pending || 0) + processed;
const percentage = total > 0 ? Math.round((processed / total) * 100) : 0;
@@ -288,8 +304,8 @@ const CurrentProcessingCard: React.FC<CurrentProcessingCardProps> = ({
const computedProcessed = ((): number => {
if (displayState && typeof displayState.processed_items === 'number') return displayState.processed_items;
if (stageResult) {
// Sum numeric values in stageResult as a heuristic for processed count
return Object.values(stageResult).reduce((s: number, v: any) => (typeof v === 'number' ? s + v : s), 0);
// FIXED: Use stage-specific key for processed count
return getProcessedFromResult(stageResult, currentRun.current_stage);
}
return 0;
})();

View File

@@ -0,0 +1,232 @@
/**
* Global Progress Bar Component
* Shows full pipeline progress across all 7 automation stages.
* Persists until 100% complete or run is finished.
*/
import React from 'react';
import { AutomationRun, InitialSnapshot, StageProgress, GlobalProgress } from '../../services/automationService';
import { BoltIcon, CheckCircleIcon, PauseIcon } from '../../icons';
// Stage colors matching AutomationPage STAGE_CONFIG
const STAGE_COLORS = [
'from-blue-500 to-blue-600', // Stage 1: Keywords → Clusters
'from-purple-500 to-purple-600', // Stage 2: Clusters → Ideas
'from-indigo-500 to-indigo-600', // Stage 3: Ideas → Tasks
'from-green-500 to-green-600', // Stage 4: Tasks → Content
'from-amber-500 to-amber-600', // Stage 5: Content → Image Prompts
'from-pink-500 to-pink-600', // Stage 6: Image Prompts → Images
'from-teal-500 to-teal-600', // Stage 7: Manual Review Gate
];
const STAGE_NAMES = [
'Keywords',
'Clusters',
'Ideas',
'Tasks',
'Content',
'Prompts',
'Review',
];
// Helper to get processed count from stage result using correct key
export const getProcessedFromResult = (result: any, stageNumber: number): number => {
if (!result) return 0;
const keyMap: Record<number, string> = {
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;
};
interface GlobalProgressBarProps {
currentRun: AutomationRun | null;
globalProgress?: GlobalProgress | null;
stages?: StageProgress[];
initialSnapshot?: InitialSnapshot | null;
}
const GlobalProgressBar: React.FC<GlobalProgressBarProps> = ({
currentRun,
globalProgress,
stages,
initialSnapshot,
}) => {
// Don't render if no run or run is completed with 100%
if (!currentRun) {
return null;
}
// Calculate global progress if not provided from API
const calculateGlobalProgress = (): { percentage: number; completed: number; total: number } => {
// If we have API-provided global progress, use it
if (globalProgress) {
return {
percentage: globalProgress.percentage,
completed: globalProgress.completed_items,
total: globalProgress.total_items,
};
}
// Fallback: Calculate from currentRun and initialSnapshot
const snapshot = initialSnapshot || (currentRun as any)?.initial_snapshot;
if (!snapshot) {
return { percentage: 0, completed: 0, total: 0 };
}
const totalInitial = snapshot.total_initial_items || 0;
let totalCompleted = 0;
for (let i = 1; i <= 7; i++) {
const result = (currentRun as any)[`stage_${i}_result`];
if (result) {
totalCompleted += getProcessedFromResult(result, i);
}
}
const percentage = totalInitial > 0 ? Math.round((totalCompleted / totalInitial) * 100) : 0;
return {
percentage: Math.min(percentage, 100),
completed: totalCompleted,
total: totalInitial,
};
};
const { percentage, completed, total } = calculateGlobalProgress();
// Hide if completed and at 100%
if (currentRun.status === 'completed' && percentage >= 100) {
return null;
}
const isPaused = currentRun.status === 'paused';
const currentStage = currentRun.current_stage;
// Get stage status for segmented bar
const getStageStatus = (stageNum: number): 'completed' | 'active' | 'pending' => {
if (stages && stages[stageNum - 1]) {
return stages[stageNum - 1].status as 'completed' | 'active' | 'pending';
}
if (currentStage > stageNum) return 'completed';
if (currentStage === stageNum) return 'active';
return 'pending';
};
const formatDuration = (): string => {
if (!currentRun.started_at) return '';
const start = new Date(currentRun.started_at).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`;
};
return (
<div className={`
rounded-xl p-4 mb-6 border-2 transition-all
${isPaused
? 'bg-gradient-to-r from-amber-50 to-amber-100 dark:from-amber-900/20 dark:to-amber-800/20 border-amber-300 dark:border-amber-700'
: 'bg-gradient-to-r from-brand-50 to-brand-100 dark:from-brand-900/20 dark:to-brand-800/20 border-brand-300 dark:border-brand-700'
}
`}>
{/* Header Row */}
<div className="flex justify-between items-center mb-3">
<div className="flex items-center gap-3">
<div className={`
size-10 rounded-lg flex items-center justify-center shadow-md
${isPaused
? 'bg-gradient-to-br from-amber-500 to-amber-600'
: 'bg-gradient-to-br from-brand-500 to-brand-600'
}
`}>
{isPaused ? (
<PauseIcon className="w-5 h-5 text-white" />
) : percentage >= 100 ? (
<CheckCircleIcon className="w-5 h-5 text-white" />
) : (
<BoltIcon className="w-5 h-5 text-white animate-pulse" />
)}
</div>
<div>
<div className={`font-bold ${isPaused ? 'text-amber-800 dark:text-amber-200' : 'text-brand-800 dark:text-brand-200'}`}>
{isPaused ? 'Pipeline Paused' : 'Full Pipeline Progress'}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400">
Stage {currentStage} of 7 {formatDuration()}
</div>
</div>
</div>
<div className={`text-3xl font-bold ${isPaused ? 'text-amber-600 dark:text-amber-400' : 'text-brand-600 dark:text-brand-400'}`}>
{percentage}%
</div>
</div>
{/* Segmented Progress Bar */}
<div className="flex h-4 rounded-full overflow-hidden bg-gray-200 dark:bg-gray-700 gap-0.5 mb-2">
{[1, 2, 3, 4, 5, 6, 7].map(stageNum => {
const status = getStageStatus(stageNum);
const stageColor = STAGE_COLORS[stageNum - 1];
return (
<div
key={stageNum}
className={`flex-1 transition-all duration-500 relative group ${
status === 'completed'
? `bg-gradient-to-r ${stageColor}`
: status === 'active'
? `bg-gradient-to-r ${stageColor} opacity-60 ${!isPaused ? 'animate-pulse' : ''}`
: 'bg-gray-300 dark:bg-gray-600'
}`}
title={`Stage ${stageNum}: ${STAGE_NAMES[stageNum - 1]}`}
>
{/* Tooltip on hover */}
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none z-10">
S{stageNum}: {STAGE_NAMES[stageNum - 1]}
{status === 'completed' && ' ✓'}
{status === 'active' && ' ●'}
</div>
</div>
);
})}
</div>
{/* Footer Row */}
<div className="flex justify-between text-xs text-gray-600 dark:text-gray-400">
<span>{completed} / {total} items processed</span>
<div className="flex gap-4">
{[1, 2, 3, 4, 5, 6, 7].map(stageNum => {
const status = getStageStatus(stageNum);
return (
<span
key={stageNum}
className={`
${status === 'completed' ? 'text-green-600 dark:text-green-400 font-medium' : ''}
${status === 'active' ? `${isPaused ? 'text-amber-600 dark:text-amber-400' : 'text-brand-600 dark:text-brand-400'} font-bold` : ''}
${status === 'pending' ? 'text-gray-400 dark:text-gray-500' : ''}
`}
>
{stageNum}
{status === 'completed' && '✓'}
{status === 'active' && '●'}
</span>
);
})}
</div>
</div>
</div>
);
};
export default GlobalProgressBar;