diff --git a/src/components/UiComponents/WorkflowStatus/WorkflowStatus.module.css b/src/components/UiComponents/WorkflowStatus/WorkflowStatus.module.css index 65a2877..465b22a 100644 --- a/src/components/UiComponents/WorkflowStatus/WorkflowStatus.module.css +++ b/src/components/UiComponents/WorkflowStatus/WorkflowStatus.module.css @@ -107,6 +107,34 @@ text-align: right; } +.statsContainer { + display: flex; + align-items: center; + gap: 16px; + flex-wrap: wrap; +} + +.statItem { + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; +} + +.statLabel { + font-weight: 600; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + font-size: 11px; +} + +.statValue { + font-weight: 600; + color: var(--color-text); + font-family: 'Courier New', monospace; +} + /* Dark theme support */ [data-theme="dark"] .workflowStatusContainer { background-color: var(--color-surface-dark); @@ -151,3 +179,11 @@ color: var(--color-text-dark); } +[data-theme="dark"] .statLabel { + color: var(--color-text-secondary-dark); +} + +[data-theme="dark"] .statValue { + color: var(--color-text-dark); +} + diff --git a/src/components/UiComponents/WorkflowStatus/WorkflowStatus.tsx b/src/components/UiComponents/WorkflowStatus/WorkflowStatus.tsx index 513714d..59031c8 100644 --- a/src/components/UiComponents/WorkflowStatus/WorkflowStatus.tsx +++ b/src/components/UiComponents/WorkflowStatus/WorkflowStatus.tsx @@ -88,43 +88,28 @@ const extractWorkflowStatus = (logs: any[]): { status: WorkflowStatusType; round }; }; -// Helper function to group logs by round and get latest progress -const getLatestRoundProgress = (logs: any[]): { round: number | null; progress: number | undefined } => { - if (!logs || logs.length === 0) { - return { round: null, progress: undefined }; +// Helper function to format bytes to KB or MB +const formatBytes = (bytes?: number): string => { + if (bytes === undefined || bytes === null) return '-'; + if (bytes === 0) return '0 B'; + const kb = bytes / 1024; + if (kb < 1024) { + return `${kb.toFixed(2)} KB`; } + const mb = kb / 1024; + return `${mb.toFixed(2)} MB`; +}; - // Find the latest round - let currentRound = 1; - let latestProgress: number | undefined = undefined; - let latestRound = 1; +// Helper function to format price +const formatPrice = (price?: number): string => { + if (price === undefined || price === null) return '-'; + return `$${price.toFixed(2)}`; +}; - const sortedLogs = [...logs].sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0)); - - sortedLogs.forEach((log) => { - const message = (log.message || '').toLowerCase(); - - // Check if this is a workflow status message that indicates a round change - if (message.includes('workflow started') || message.includes('workflow resumed')) { - const roundMatch = message.match(/\(?round\s+(\d+)\)?/i); - if (roundMatch) { - currentRound = parseInt(roundMatch[1], 10); - latestRound = currentRound; - } else if (message.includes('workflow started')) { - currentRound = 1; - latestRound = 1; - } - } - - // Update progress for current round - if (log.progress !== undefined && log.progress !== null) { - if (currentRound === latestRound) { - latestProgress = log.progress; - } - } - }); - - return { round: latestRound, progress: latestProgress }; +// Helper function to format processing time +const formatProcessingTime = (time?: number): string => { + if (time === undefined || time === null) return '-'; + return `${time.toFixed(2)}s`; }; const WorkflowStatus: React.FC = ({ @@ -132,7 +117,8 @@ const WorkflowStatus: React.FC = ({ logs = [], workflowStatus: workflowStatusFromApi, currentRound: currentRoundFromApi, - isRunning + isRunning, + latestStats }) => { // Use workflow status and round from API response, fallback to extracting from logs const workflowStatus = useMemo(() => { @@ -173,21 +159,12 @@ const WorkflowStatus: React.FC = ({ return extractWorkflowStatus(logs); }, [workflowStatusFromApi, currentRoundFromApi, logs]); - // Get latest round progress - const latestProgress = useMemo(() => getLatestRoundProgress(logs), [logs]); - // Determine if workflow is running (show spinner) // Show spinner if explicitly running OR if status indicates running state const showSpinner = isRunning === true || workflowStatus.status === 'started' || workflowStatus.status === 'resumed'; - - // Calculate progress percentage - const progressValue = latestProgress.progress !== undefined - ? Math.min(Math.max(latestProgress.progress, 0), 1) - : undefined; - const progressPercent = progressValue !== undefined ? Math.round(progressValue * 100) : undefined; - // Don't render if no status information (but always show if spinner should be visible) - if (!showSpinner && !workflowStatus.status && workflowStatus.round === null && progressValue === undefined) { + // Don't render if no status information and no stats (but always show if spinner should be visible) + if (!showSpinner && !workflowStatus.status && workflowStatus.round === null && !latestStats) { return null; } @@ -208,16 +185,33 @@ const WorkflowStatus: React.FC = ({ )} - {/* Progress Bar */} - {progressValue !== undefined && ( -
-
-
-
-
{progressPercent}%
+ {/* Stats Display */} + {latestStats && ( +
+ {latestStats.priceUsd !== undefined && ( +
+ Price: + {formatPrice(latestStats.priceUsd)} +
+ )} + {latestStats.processingTime !== undefined && ( +
+ Time: + {formatProcessingTime(latestStats.processingTime)} +
+ )} + {latestStats.bytesSent !== undefined && ( +
+ Sent: + {formatBytes(latestStats.bytesSent)} +
+ )} + {latestStats.bytesReceived !== undefined && ( +
+ Received: + {formatBytes(latestStats.bytesReceived)} +
+ )}
)}
diff --git a/src/components/UiComponents/WorkflowStatus/WorkflowStatusTypes.ts b/src/components/UiComponents/WorkflowStatus/WorkflowStatusTypes.ts index df3e40e..50dabb6 100644 --- a/src/components/UiComponents/WorkflowStatus/WorkflowStatusTypes.ts +++ b/src/components/UiComponents/WorkflowStatus/WorkflowStatusTypes.ts @@ -44,6 +44,16 @@ export interface WorkflowStatusProps { * Whether the workflow is currently running (shows spinner) */ isRunning?: boolean; + + /** + * Latest statistics from the workflow (price, processing time, bytes sent/received) + */ + latestStats?: { + priceUsd?: number; + processingTime?: number; + bytesSent?: number; + bytesReceived?: number; + } | null; } export type WorkflowStatusType = 'started' | 'resumed' | 'stopped' | 'failed' | 'completed' | null; diff --git a/src/core/PageManager/PageRenderer.tsx b/src/core/PageManager/PageRenderer.tsx index eb7cac3..83bb702 100644 --- a/src/core/PageManager/PageRenderer.tsx +++ b/src/core/PageManager/PageRenderer.tsx @@ -1471,6 +1471,7 @@ const PageRenderer: React.FC = ({ workflowStatus={hookData?.workflowStatus} currentRound={hookData?.currentRound || hookData?.workflowData?.currentRound} isRunning={hookData?.isRunning || false} + latestStats={hookData?.latestStats || null} />
)} diff --git a/src/hooks/playground/useDashboardInputForm.ts b/src/hooks/playground/useDashboardInputForm.ts index 9d0221d..62ec806 100644 --- a/src/hooks/playground/useDashboardInputForm.ts +++ b/src/hooks/playground/useDashboardInputForm.ts @@ -48,6 +48,7 @@ export function useDashboardInputForm() { logs, dashboardLogs, unifiedContentLogs, + latestStats, startWorkflow, stopWorkflow, resetWorkflow, @@ -755,7 +756,8 @@ export function useDashboardInputForm() { setIsFileAttachmentPopupOpen, allUserFiles: fileContext.files || [], handleFileAttach, - handleFileUploadAndAttach + handleFileUploadAndAttach, + latestStats }; } diff --git a/src/hooks/playground/useWorkflowLifecycle.ts b/src/hooks/playground/useWorkflowLifecycle.ts index 7001996..5ed9076 100644 --- a/src/hooks/playground/useWorkflowLifecycle.ts +++ b/src/hooks/playground/useWorkflowLifecycle.ts @@ -27,6 +27,7 @@ export function useWorkflowLifecycle() { const [dashboardLogs, setDashboardLogs] = useState([]); const [unifiedContentLogs, setUnifiedContentLogs] = useState([]); const [statusChangedFromRunningAt, setStatusChangedFromRunningAt] = useState(null); + const [latestStats, setLatestStats] = useState<{ priceUsd?: number; processingTime?: number; bytesSent?: number; bytesReceived?: number } | null>(null); const prevStatusRef = useRef('idle'); const statusRef = useRef('idle'); const statusChangedFromRunningAtRef = useRef(null); @@ -266,6 +267,25 @@ export function useWorkflowLifecycle() { return [...allLogs].sort(sortLogs); }); + + // Process stats and keep the latest one (highest createdAt) + const statsItems = timeline.filter(item => item.type === 'stat'); + if (statsItems.length > 0) { + // Sort by createdAt descending to get the latest + const sortedStats = [...statsItems].sort((a, b) => b.createdAt - a.createdAt); + const latestStatItem = sortedStats[0]; + const statData = latestStatItem.item || latestStatItem; + + if (statData && (statData.priceUsd !== undefined || statData.processingTime !== undefined || + statData.bytesSent !== undefined || statData.bytesReceived !== undefined)) { + setLatestStats({ + priceUsd: statData.priceUsd, + processingTime: statData.processingTime, + bytesSent: statData.bytesSent, + bytesReceived: statData.bytesReceived + }); + } + } }, [convertLogToFrontendFormat]); // Poll workflow data using unified chat data endpoint @@ -346,6 +366,7 @@ export function useWorkflowLifecycle() { setLogs([]); setDashboardLogs([]); setUnifiedContentLogs([]); + setLatestStats(null); return; } @@ -404,6 +425,7 @@ export function useWorkflowLifecycle() { setLogs(prev => prev.length > 0 ? [] : prev); setDashboardLogs(prev => prev.length > 0 ? [] : prev); setUnifiedContentLogs(prev => prev.length > 0 ? [] : prev); + setLatestStats(null); setCurrentRound(prev => prev !== undefined ? undefined : prev); if (statusChangedFromRunningAt !== null) { setStatusChangedFromRunningAt(null); @@ -493,6 +515,7 @@ export function useWorkflowLifecycle() { statusRef.current = 'idle'; updateWorkflowStatus('idle'); setCurrentRound(undefined); + setLatestStats(null); setStatusChangedFromRunningAt(null); statusChangedFromRunningAtRef.current = null; lastRenderedTimestampRef.current = null; @@ -512,6 +535,7 @@ export function useWorkflowLifecycle() { setLogs([]); setDashboardLogs([]); setUnifiedContentLogs([]); + setLatestStats(null); updateWorkflowStatus('idle'); return; } @@ -579,6 +603,7 @@ export function useWorkflowLifecycle() { logs, dashboardLogs, unifiedContentLogs, + latestStats, startWorkflow: handleStartWorkflow, stopWorkflow: handleStopWorkflow, resetWorkflow,