import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { motion } from 'framer-motion'; import { Switch } from '~/components/ui/Switch'; import { logStore, type LogEntry } from '~/lib/stores/logs'; import { useStore } from '@nanostores/react'; import { classNames } from '~/utils/classNames'; import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; interface SelectOption { value: string; label: string; icon?: string; color?: string; } const logLevelOptions: SelectOption[] = [ { value: 'all', label: 'All Types', icon: 'i-ph:funnel', color: '#9333ea', }, { value: 'provider', label: 'LLM', icon: 'i-ph:robot', color: '#10b981', }, { value: 'api', label: 'API', icon: 'i-ph:cloud', color: '#3b82f6', }, { value: 'error', label: 'Errors', icon: 'i-ph:warning-circle', color: '#ef4444', }, { value: 'warning', label: 'Warnings', icon: 'i-ph:warning', color: '#f59e0b', }, { value: 'info', label: 'Info', icon: 'i-ph:info', color: '#3b82f6', }, { value: 'debug', label: 'Debug', icon: 'i-ph:bug', color: '#6b7280', }, ]; interface LogEntryItemProps { log: LogEntry; isExpanded: boolean; use24Hour: boolean; showTimestamp: boolean; } const LogEntryItem = ({ log, isExpanded: forceExpanded, use24Hour, showTimestamp }: LogEntryItemProps) => { const [localExpanded, setLocalExpanded] = useState(forceExpanded); useEffect(() => { setLocalExpanded(forceExpanded); }, [forceExpanded]); const timestamp = useMemo(() => { const date = new Date(log.timestamp); return date.toLocaleTimeString('en-US', { hour12: !use24Hour }); }, [log.timestamp, use24Hour]); const style = useMemo(() => { if (log.category === 'provider') { return { icon: 'i-ph:robot', color: 'text-emerald-500 dark:text-emerald-400', bg: 'hover:bg-emerald-500/10 dark:hover:bg-emerald-500/20', badge: 'text-emerald-500 bg-emerald-50 dark:bg-emerald-500/10', }; } if (log.category === 'api') { return { icon: 'i-ph:cloud', color: 'text-blue-500 dark:text-blue-400', bg: 'hover:bg-blue-500/10 dark:hover:bg-blue-500/20', badge: 'text-blue-500 bg-blue-50 dark:bg-blue-500/10', }; } switch (log.level) { case 'error': return { icon: 'i-ph:warning-circle', color: 'text-red-500 dark:text-red-400', bg: 'hover:bg-red-500/10 dark:hover:bg-red-500/20', badge: 'text-red-500 bg-red-50 dark:bg-red-500/10', }; case 'warning': return { icon: 'i-ph:warning', color: 'text-yellow-500 dark:text-yellow-400', bg: 'hover:bg-yellow-500/10 dark:hover:bg-yellow-500/20', badge: 'text-yellow-500 bg-yellow-50 dark:bg-yellow-500/10', }; case 'debug': return { icon: 'i-ph:bug', color: 'text-gray-500 dark:text-gray-400', bg: 'hover:bg-gray-500/10 dark:hover:bg-gray-500/20', badge: 'text-gray-500 bg-gray-50 dark:bg-gray-500/10', }; default: return { icon: 'i-ph:info', color: 'text-blue-500 dark:text-blue-400', bg: 'hover:bg-blue-500/10 dark:hover:bg-blue-500/20', badge: 'text-blue-500 bg-blue-50 dark:bg-blue-500/10', }; } }, [log.level, log.category]); const renderDetails = (details: any) => { if (log.category === 'provider') { return (
Model: {details.model} Tokens: {details.totalTokens} Duration: {details.duration}ms
{details.prompt && (
Prompt:
                {details.prompt}
              
)} {details.response && (
Response:
                {details.response}
              
)}
); } if (log.category === 'api') { return (
{details.method} Status: {details.statusCode} Duration: {details.duration}ms
{details.url}
{details.request && (
Request:
                {JSON.stringify(details.request, null, 2)}
              
)} {details.response && (
Response:
                {JSON.stringify(details.response, null, 2)}
              
)} {details.error && (
Error:
                {JSON.stringify(details.error, null, 2)}
              
)}
); } return (
        {JSON.stringify(details, null, 2)}
      
); }; return (
{log.message}
{log.details && ( <> {localExpanded && renderDetails(log.details)} )}
{log.level}
{log.category && (
{log.category}
)}
{showTimestamp && }
); }; export function EventLogsTab() { const logs = useStore(logStore.logs); const [selectedLevel, setSelectedLevel] = useState<'all' | string>('all'); const [searchQuery, setSearchQuery] = useState(''); const [use24Hour, setUse24Hour] = useState(false); const [autoExpand, setAutoExpand] = useState(false); const [showTimestamps, setShowTimestamps] = useState(true); const [showLevelFilter, setShowLevelFilter] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const levelFilterRef = useRef(null); const filteredLogs = useMemo(() => { const allLogs = Object.values(logs); if (selectedLevel === 'all') { return allLogs.filter((log) => searchQuery ? log.message.toLowerCase().includes(searchQuery.toLowerCase()) : true, ); } return allLogs.filter((log) => { const matchesType = log.category === selectedLevel || log.level === selectedLevel; const matchesSearch = searchQuery ? log.message.toLowerCase().includes(searchQuery.toLowerCase()) : true; return matchesType && matchesSearch; }); }, [logs, selectedLevel, searchQuery]); // Add performance tracking on mount useEffect(() => { const startTime = performance.now(); logStore.logInfo('Event Logs tab mounted', { type: 'component_mount', message: 'Event Logs tab component mounted', component: 'EventLogsTab', }); return () => { const duration = performance.now() - startTime; logStore.logPerformanceMetric('EventLogsTab', 'mount-duration', duration); }; }, []); // Log filter changes const handleLevelFilterChange = useCallback( (newLevel: string) => { logStore.logInfo('Log level filter changed', { type: 'filter_change', message: `Log level filter changed from ${selectedLevel} to ${newLevel}`, component: 'EventLogsTab', previousLevel: selectedLevel, newLevel, }); setSelectedLevel(newLevel as string); setShowLevelFilter(false); }, [selectedLevel], ); // Log search changes with debounce useEffect(() => { const timeoutId = setTimeout(() => { if (searchQuery) { logStore.logInfo('Log search performed', { type: 'search', message: `Search performed with query "${searchQuery}" (${filteredLogs.length} results)`, component: 'EventLogsTab', query: searchQuery, resultsCount: filteredLogs.length, }); } }, 1000); return () => clearTimeout(timeoutId); }, [searchQuery, filteredLogs.length]); // Enhanced export logs handler const handleExportLogs = useCallback(() => { const startTime = performance.now(); try { const exportData = { timestamp: new Date().toISOString(), logs: filteredLogs, filters: { level: selectedLevel, searchQuery, }, }; const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `bolt-logs-${new Date().toISOString()}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); const duration = performance.now() - startTime; logStore.logSuccess('Logs exported successfully', { type: 'export', message: `Successfully exported ${filteredLogs.length} logs`, component: 'EventLogsTab', exportedCount: filteredLogs.length, filters: { level: selectedLevel, searchQuery, }, duration, }); } catch (error) { logStore.logError('Failed to export logs', error, { type: 'export_error', message: 'Failed to export logs', component: 'EventLogsTab', }); } }, [filteredLogs, selectedLevel, searchQuery]); // Enhanced refresh handler const handleRefresh = useCallback(async () => { const startTime = performance.now(); setIsRefreshing(true); try { await logStore.refreshLogs(); const duration = performance.now() - startTime; logStore.logSuccess('Logs refreshed successfully', { type: 'refresh', message: `Successfully refreshed ${Object.keys(logs).length} logs`, component: 'EventLogsTab', duration, logsCount: Object.keys(logs).length, }); } catch (error) { logStore.logError('Failed to refresh logs', error, { type: 'refresh_error', message: 'Failed to refresh logs', component: 'EventLogsTab', }); } finally { setTimeout(() => setIsRefreshing(false), 500); } }, [logs]); // Log preference changes const handlePreferenceChange = useCallback((type: string, value: boolean) => { logStore.logInfo('Log preference changed', { type: 'preference_change', message: `Log preference "${type}" changed to ${value}`, component: 'EventLogsTab', preference: type, value, }); switch (type) { case 'timestamps': setShowTimestamps(value); break; case '24hour': setUse24Hour(value); break; case 'autoExpand': setAutoExpand(value); break; } }, []); // Close filters when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (levelFilterRef.current && !levelFilterRef.current.contains(event.target as Node)) { setShowLevelFilter(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); const selectedLevelOption = logLevelOptions.find((opt) => opt.value === selectedLevel); return (
{logLevelOptions.map((option) => ( handleLevelFilterChange(option.value)} >
{option.label} ))}
handlePreferenceChange('timestamps', value)} className="data-[state=checked]:bg-purple-500" /> Show Timestamps
handlePreferenceChange('24hour', value)} className="data-[state=checked]:bg-purple-500" /> 24h Time
handlePreferenceChange('autoExpand', value)} className="data-[state=checked]:bg-purple-500" /> Auto Expand
setSearchQuery(e.target.value)} className={classNames( 'w-full px-4 py-2 pl-10 rounded-lg', 'bg-[#FAFAFA] dark:bg-[#0A0A0A]', 'border border-[#E5E5E5] dark:border-[#1A1A1A]', 'text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400', 'focus:outline-none focus:ring-2 focus:ring-purple-500/20 focus:border-purple-500', 'transition-all duration-200', )} />
{filteredLogs.length === 0 ? (

No Logs Found

Try adjusting your search or filters

) : ( filteredLogs.map((log) => ( )) )}
); }