'use client'; import { useMemo } from 'react'; import { LineChart, Line, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine, } from 'recharts'; import { Activity, TrendingUp, HardDrive, Scroll } from 'lucide-react'; /** * Represents a single metrics sample collected during job execution */ export interface MetricsSample { timestamp_ms: number; reviews_extracted: number; scroll_count: number; memory_mb: number; extraction_rate: number; // reviews per second } interface MetricsDashboardProps { metricsHistory: MetricsSample[]; currentMetrics?: MetricsSample; isStreaming: boolean; } /** * Formats a timestamp (in ms) to a relative time string * e.g., "0s", "30s", "1m", "1m 30s", etc. */ function formatRelativeTime(timestampMs: number, startMs: number): string { const elapsedMs = timestampMs - startMs; const totalSeconds = Math.floor(elapsedMs / 1000); if (totalSeconds < 60) { return `${totalSeconds}s`; } const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; if (seconds === 0) { return `${minutes}m`; } return `${minutes}m ${seconds}s`; } /** * MetricsDashboard displays real-time metrics during job execution * with charts for extraction rate, cumulative reviews, and memory usage. */ export default function MetricsDashboard({ metricsHistory, currentMetrics, isStreaming, }: MetricsDashboardProps) { // Determine the starting timestamp for relative time calculations const startTimestamp = useMemo(() => { if (metricsHistory.length > 0) { return metricsHistory[0].timestamp_ms; } return currentMetrics?.timestamp_ms ?? Date.now(); }, [metricsHistory, currentMetrics]); // Transform metrics history for charts with relative time labels const chartData = useMemo(() => { return metricsHistory.map((sample) => ({ ...sample, time: formatRelativeTime(sample.timestamp_ms, startTimestamp), timeMs: sample.timestamp_ms - startTimestamp, })); }, [metricsHistory, startTimestamp]); // Get the latest metrics (either current or last from history) const latestMetrics = currentMetrics ?? metricsHistory[metricsHistory.length - 1]; // Memory warning threshold const MEMORY_WARNING_MB = 1500; // Check if memory is above warning threshold const isMemoryWarning = latestMetrics && latestMetrics.memory_mb >= MEMORY_WARNING_MB; // Custom tooltip style const tooltipStyle = { backgroundColor: '#1f2937', border: '1px solid #374151', borderRadius: '8px', padding: '8px 12px', }; return (
{/* Header with Live Indicator */}

Real-Time Metrics

{isStreaming && (
Live
)}
{/* Progress Summary - Current Stats */}
{/* Total Reviews */}
Total Reviews
{latestMetrics?.reviews_extracted ?? 0}
{/* Scroll Count */}
Scrolls
{latestMetrics?.scroll_count ?? 0}
{/* Extraction Rate */}
Rate (r/s)
{latestMetrics?.extraction_rate?.toFixed(2) ?? '0.00'}
{/* Memory Usage */}
Memory (MB)
{latestMetrics?.memory_mb?.toFixed(0) ?? '0'}
{isMemoryWarning && (
Warning: High memory
)}
{/* Charts Grid - 2x2 on desktop, stacked on mobile */}
{/* Extraction Rate Chart */}

Extraction Rate (reviews/second)

{chartData.length > 0 ? ( [`${(value as number)?.toFixed(2) ?? '0.00'} r/s`, 'Rate']} labelFormatter={(label) => `Time: ${label}`} /> ) : (
No data yet
)}
{/* Cumulative Reviews Chart */}

Cumulative Reviews Extracted

{chartData.length > 0 ? ( [`${value ?? 0} reviews`, 'Total']} labelFormatter={(label) => `Time: ${label}`} /> ) : (
No data yet
)}
{/* Memory Usage Chart */}

Memory Usage (MB) Warning threshold: {MEMORY_WARNING_MB}MB

{chartData.length > 0 ? ( Math.max(dataMax * 1.1, MEMORY_WARNING_MB * 1.2)]} /> [`${(value as number)?.toFixed(0) ?? '0'} MB`, 'Memory']} labelFormatter={(label) => `Time: ${label}`} /> ) : (
No data yet
)}
{/* Scroll Count Chart */}

Scroll Progress

{chartData.length > 0 ? ( [`${value ?? 0} scrolls`, 'Count']} labelFormatter={(label) => `Time: ${label}`} /> ) : (
No data yet
)}
); }