'use client'; import { useState, useMemo } from 'react'; import { Bug, Globe, Network, Cpu, Filter, ChevronDown, ChevronUp } from 'lucide-react'; // Type definitions export interface StructuredLog { timestamp: string; timestamp_ms: number; level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL'; category: 'scraper' | 'browser' | 'network' | 'system'; message: string; metrics?: Record; network?: Record; } export interface MetricsData { cpu_percent?: number; memory_mb?: number; duration_ms?: number; requests_made?: number; reviews_scraped?: number; [key: string]: any; } export interface CrashReport { error_type: string; error_message: string; stack_trace?: string; timestamp: string; context?: Record; } export interface SessionFingerprint { session_id: string; browser_version?: string; proxy_used?: boolean; locale?: string; viewport?: { width: number; height: number }; [key: string]: any; } export interface JobDevToolsProps { logs: StructuredLog[]; metrics?: MetricsData; crashReport?: CrashReport; sessionFingerprint?: SessionFingerprint; isStreaming?: boolean; } type TabType = 'all' | 'scraper' | 'browser' | 'network' | 'system'; type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL'; const TAB_CONFIG: { id: TabType; label: string; icon: typeof Bug; category?: StructuredLog['category'] }[] = [ { id: 'all', label: 'All', icon: Filter }, { id: 'scraper', label: 'Scraper', icon: Bug, category: 'scraper' }, { id: 'browser', label: 'Browser', icon: Globe, category: 'browser' }, { id: 'network', label: 'Network', icon: Network, category: 'network' }, { id: 'system', label: 'System', icon: Cpu, category: 'system' }, ]; const LEVEL_COLORS: Record = { DEBUG: { bg: 'bg-gray-700', text: 'text-gray-300', border: 'border-gray-600' }, INFO: { bg: 'bg-blue-900', text: 'text-blue-300', border: 'border-blue-700' }, WARN: { bg: 'bg-yellow-900', text: 'text-yellow-300', border: 'border-yellow-700' }, ERROR: { bg: 'bg-red-900', text: 'text-red-300', border: 'border-red-700' }, FATAL: { bg: 'bg-purple-900', text: 'text-purple-300', border: 'border-purple-700' }, }; const LEVEL_BADGE_COLORS: Record = { DEBUG: 'bg-gray-600 text-gray-200', INFO: 'bg-blue-600 text-blue-100', WARN: 'bg-yellow-600 text-yellow-100', ERROR: 'bg-red-600 text-red-100', FATAL: 'bg-purple-600 text-purple-100', }; export default function JobDevTools({ logs, metrics, crashReport, sessionFingerprint, isStreaming = false, }: JobDevToolsProps) { const [activeTab, setActiveTab] = useState('all'); const [enabledLevels, setEnabledLevels] = useState>( new Set(['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']) ); const [showLevelFilter, setShowLevelFilter] = useState(false); // Calculate counts per category const categoryCounts = useMemo(() => { const counts: Record = { all: logs.length, scraper: 0, browser: 0, network: 0, system: 0, }; logs.forEach((log) => { if (log.category in counts) { counts[log.category as keyof typeof counts]++; } }); return counts; }, [logs]); // Calculate counts per level const levelCounts = useMemo(() => { const counts: Record = { DEBUG: 0, INFO: 0, WARN: 0, ERROR: 0, FATAL: 0, }; logs.forEach((log) => { if (log.level in counts) { counts[log.level]++; } }); return counts; }, [logs]); // Filter logs by active tab and enabled levels const filteredLogs = useMemo(() => { return logs.filter((log) => { const matchesTab = activeTab === 'all' || log.category === activeTab; const matchesLevel = enabledLevels.has(log.level); return matchesTab && matchesLevel; }); }, [logs, activeTab, enabledLevels]); const toggleLevel = (level: LogLevel) => { setEnabledLevels((prev) => { const next = new Set(prev); if (next.has(level)) { // Don't allow deselecting all levels if (next.size > 1) { next.delete(level); } } else { next.add(level); } return next; }); }; const formatTimestamp = (timestamp: string) => { try { const date = new Date(timestamp); return date.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3, }); } catch { return timestamp; } }; return (
{/* Header with streaming indicator */}
Job DevTools {isStreaming && ( Streaming )}
{filteredLogs.length} / {logs.length} logs
{/* Tab bar */}
{TAB_CONFIG.map((tab) => { const Icon = tab.icon; const count = categoryCounts[tab.id]; const isActive = activeTab === tab.id; return ( ); })} {/* Level filter toggle */}
{/* Level filter dropdown */} {showLevelFilter && (
{(Object.keys(LEVEL_BADGE_COLORS) as LogLevel[]).map((level) => ( ))}
)}
{/* Log entries - scrollable area */}
{filteredLogs.length === 0 ? (

No logs to display

{logs.length > 0 ? 'Try adjusting your filters' : 'Logs will appear here during job execution'}

) : (
{filteredLogs.map((log, index) => { const levelStyle = LEVEL_COLORS[log.level]; return (
{/* Timestamp */} {formatTimestamp(log.timestamp)} {/* Level badge */} {log.level} {/* Category badge */} {log.category} {/* Message */} {log.message}
{/* Additional data (metrics/network) */} {(log.metrics || log.network) && (
{log.metrics && ( metrics: {JSON.stringify(log.metrics)} )} {log.network && ( network: {JSON.stringify(log.network)} )}
)}
); })}
)}
{/* Reserved space for metrics/session panels (footer) */}
{metrics && ( <> {metrics.duration_ms !== undefined && ( Duration: {(metrics.duration_ms / 1000).toFixed(2)}s )} {metrics.reviews_scraped !== undefined && ( Reviews: {metrics.reviews_scraped} )} {metrics.memory_mb !== undefined && ( Memory: {metrics.memory_mb.toFixed(1)}MB )} )}
{sessionFingerprint && ( Session: {sessionFingerprint.session_id?.slice(0, 8)}... )} {crashReport && ( Crash: {crashReport.error_type} )}
); }