/** * Log Entry Component * Terminal-style log entry with cassette futurism aesthetic * Professional log formatting without emojis */ import { memo } from "react"; import { ChevronDown, ChevronUp, Loader2, Clock, CheckCircle2, Wifi, XOctagon, AlertTriangle, Play, Square, ArrowRight } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { LOG_TYPE_CONFIG, SEVERITY_COLORS } from "../constants"; import type { LogEntryProps } from "../types"; // Log type labels for display const LOG_TYPE_LABELS: Record = { thinking: 'THINK', tool: 'TOOL', phase: 'PHASE', finding: 'VULN', dispatch: 'DISPATCH', info: 'INFO', error: 'ERROR', user: 'USER', }; // Helper to format title (remove emojis and clean up) function formatTitle(title: string, type: string): string { // Remove common emojis let cleaned = title .replace(/[\u{1F300}-\u{1F9FF}]/gu, '') .replace(/[\u{2600}-\u{26FF}]/gu, '') .replace(/[\u{2700}-\u{27BF}]/gu, '') .replace(/[\u{FE00}-\u{FE0F}]/gu, '') .replace(/[\u{1F000}-\u{1F02F}]/gu, '') .replace(/[โœ…๐Ÿ”—๐Ÿ›‘โœ•โš ๏ธโŒโšก๐Ÿ”„๐Ÿ”๐Ÿ’ก๐Ÿ“๐Ÿ“„๐Ÿ›๐Ÿ›ก๏ธ]/g, '') .trim(); // Remove leading punctuation/symbols cleaned = cleaned.replace(/^[:\-โ€“โ€”โ€ขยท]\s*/, ''); return cleaned || title; } // Get status icon for info/system messages function getStatusIcon(title: string) { const lowerTitle = title.toLowerCase(); if (lowerTitle.includes('connect') || lowerTitle.includes('stream')) { return ; } if (lowerTitle.includes('complete') || lowerTitle.includes('success') || lowerTitle.includes('done')) { return ; } if (lowerTitle.includes('cancel') || lowerTitle.includes('stop') || lowerTitle.includes('abort')) { return ; } if (lowerTitle.includes('error') || lowerTitle.includes('fail')) { return ; } if (lowerTitle.includes('start') || lowerTitle.includes('begin') || lowerTitle.includes('init')) { return ; } return null; } export const LogEntry = memo(function LogEntry({ item, isExpanded, onToggle }: LogEntryProps) { const config = LOG_TYPE_CONFIG[item.type] || LOG_TYPE_CONFIG.info; const isThinking = item.type === 'thinking'; const isTool = item.type === 'tool'; const isFinding = item.type === 'finding'; const isError = item.type === 'error'; const isInfo = item.type === 'info'; const showContent = isThinking || isExpanded; const isCollapsible = !isThinking && item.content; const formattedTitle = formatTitle(item.title, item.type); const statusIcon = isInfo ? getStatusIcon(formattedTitle) : null; return (
{/* Main card */}
{/* Subtle gradient overlay */}
{/* Content */}
{/* Header row */}
{/* Type icon */}
{config.icon}
{item.isStreaming && (
{config.icon}
)}
{/* Type label */} {LOG_TYPE_LABELS[item.type] || 'LOG'} {/* Timestamp */} {item.time} {/* Separator */} {/* Status icon for info messages */} {statusIcon && {statusIcon}} {/* Title - for non-thinking types */} {!isThinking && ( {formattedTitle} )} {/* Streaming cursor */} {item.isStreaming && ( )} {/* Tool status */} {item.tool?.status === 'running' && (
Running
)} {item.tool?.status === 'completed' && (
)} {/* Agent badge */} {item.agentName && ( {item.agentName} )} {/* Right side info */}
{/* Duration badge */} {item.tool?.duration !== undefined && ( {item.tool.duration}ms )} {/* Severity badge */} {item.severity && ( {item.severity} )} {/* Expand indicator */} {isCollapsible && (
{isExpanded ? ( ) : ( )}
)}
{/* Thinking content - always visible with special styling */} {isThinking && item.content && (
{item.content}
)} {/* Collapsible content */} {!isThinking && showContent && item.content && (
{/* Mini header */}
{isTool ? 'Output' : 'Details'}
{item.tool?.status === 'completed' && (
Complete
)}
{/* Content */}
                  {item.content}
                
)}
{/* Inline styles for cursor blink */}
); }); export default LogEntry;