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 && (
)}
{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
{filteredLogs.length === 0 ? (
No Logs Found
Try adjusting your search or filters
) : (
filteredLogs.map((log) => (
))
)}
);
}