diff --git a/app/components/@settings/tabs/debug/DebugTab.tsx b/app/components/@settings/tabs/debug/DebugTab.tsx index 8d8b0307..453dfe3a 100644 --- a/app/components/@settings/tabs/debug/DebugTab.tsx +++ b/app/components/@settings/tabs/debug/DebugTab.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useMemo } from 'react'; +import React, { useEffect, useState, useMemo, useCallback } from 'react'; import { toast } from 'react-toastify'; import { classNames } from '~/utils/classNames'; import { logStore, type LogEntry } from '~/lib/stores/logs'; @@ -7,6 +7,8 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/component import { Progress } from '~/components/ui/Progress'; import { ScrollArea } from '~/components/ui/ScrollArea'; import { Badge } from '~/components/ui/Badge'; +import { Dialog, DialogRoot, DialogTitle } from '~/components/ui/Dialog'; +import { jsPDF } from 'jspdf'; interface SystemInfo { os: string; @@ -138,6 +140,13 @@ interface OllamaServiceStatus { error?: string; } +interface ExportFormat { + id: string; + label: string; + icon: string; + handler: () => void; +} + const DependencySection = ({ title, deps, @@ -541,7 +550,7 @@ export default function DebugTab() { const resourceEntries = performance.getEntriesByType('resource'); const resourceStats = { totalResources: resourceEntries.length, - totalSize: resourceEntries.reduce((total, entry) => total + (entry as any).transferSize || 0, 0), + totalSize: resourceEntries.reduce((total, entry) => total + ((entry as any).transferSize || 0), 0), totalTime: Math.max(...resourceEntries.map((entry) => entry.duration)), }; @@ -651,6 +660,438 @@ export default function DebugTab() { } }; + const exportAsCSV = () => { + try { + const debugData = { + system: systemInfo, + webApp: webAppInfo, + errors: logStore.getLogs().filter((log: LogEntry) => log.level === 'error'), + performance: { + memory: (performance as any).memory || {}, + timing: performance.timing, + navigation: performance.navigation, + }, + }; + + // Convert the data to CSV format + const csvData = [ + ['Category', 'Key', 'Value'], + ...Object.entries(debugData).flatMap(([category, data]) => + Object.entries(data || {}).map(([key, value]) => [ + category, + key, + typeof value === 'object' ? JSON.stringify(value) : String(value), + ]), + ), + ]; + + // Create CSV content + const csvContent = csvData.map((row) => row.join(',')).join('\n'); + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `bolt-debug-info-${new Date().toISOString()}.csv`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + toast.success('Debug information exported as CSV'); + } catch (error) { + console.error('Failed to export CSV:', error); + toast.error('Failed to export debug information as CSV'); + } + }; + + const exportAsPDF = () => { + try { + const debugData = { + system: systemInfo, + webApp: webAppInfo, + errors: logStore.getLogs().filter((log: LogEntry) => log.level === 'error'), + performance: { + memory: (performance as any).memory || {}, + timing: performance.timing, + navigation: performance.navigation, + }, + }; + + // Create new PDF document + const doc = new jsPDF(); + const lineHeight = 7; + let yPos = 20; + const margin = 20; + const pageWidth = doc.internal.pageSize.getWidth(); + const maxLineWidth = pageWidth - 2 * margin; + + // Add key-value pair with better formatting + const addKeyValue = (key: string, value: any, indent = 0) => { + // Check if we need a new page + if (yPos > doc.internal.pageSize.getHeight() - 20) { + doc.addPage(); + yPos = margin; + } + + doc.setFontSize(10); + doc.setTextColor('#374151'); + doc.setFont('helvetica', 'bold'); + + // Format the key with proper spacing + const formattedKey = key.replace(/([A-Z])/g, ' $1').trim(); + doc.text(formattedKey + ':', margin + indent, yPos); + doc.setFont('helvetica', 'normal'); + doc.setTextColor('#6B7280'); + + let valueText; + + if (typeof value === 'object' && value !== null) { + // Skip rendering if value is empty object + if (Object.keys(value).length === 0) { + return; + } + + yPos += lineHeight; + Object.entries(value).forEach(([subKey, subValue]) => { + // Check for page break before each sub-item + if (yPos > doc.internal.pageSize.getHeight() - 20) { + doc.addPage(); + yPos = margin; + } + + const formattedSubKey = subKey.replace(/([A-Z])/g, ' $1').trim(); + addKeyValue(formattedSubKey, subValue, indent + 10); + }); + + return; + } else { + valueText = String(value); + } + + const valueX = margin + indent + doc.getTextWidth(formattedKey + ': '); + const maxValueWidth = maxLineWidth - indent - doc.getTextWidth(formattedKey + ': '); + const lines = doc.splitTextToSize(valueText, maxValueWidth); + + // Check if we need a new page for the value + if (yPos + lines.length * lineHeight > doc.internal.pageSize.getHeight() - 20) { + doc.addPage(); + yPos = margin; + } + + doc.text(lines, valueX, yPos); + yPos += lines.length * lineHeight; + }; + + // Add section header with page break check + const addSectionHeader = (title: string) => { + // Check if we need a new page + if (yPos + 20 > doc.internal.pageSize.getHeight() - 20) { + doc.addPage(); + yPos = margin; + } + + yPos += lineHeight; + doc.setFillColor('#F3F4F6'); + doc.rect(margin - 2, yPos - 5, pageWidth - 2 * (margin - 2), lineHeight + 6, 'F'); + doc.setFont('helvetica', 'bold'); + doc.setTextColor('#111827'); + doc.setFontSize(12); + doc.text(title.toUpperCase(), margin, yPos); + doc.setFont('helvetica', 'normal'); + yPos += lineHeight * 1.5; + }; + + // Add horizontal line with page break check + const addHorizontalLine = () => { + // Check if we need a new page + if (yPos + 10 > doc.internal.pageSize.getHeight() - 20) { + doc.addPage(); + yPos = margin; + + return; // Skip drawing line if we just started a new page + } + + doc.setDrawColor('#E5E5E5'); + doc.line(margin, yPos, pageWidth - margin, yPos); + yPos += lineHeight; + }; + + // Helper function to add footer to all pages + const addFooters = () => { + const totalPages = doc.internal.pages.length - 1; + + for (let i = 1; i <= totalPages; i++) { + doc.setPage(i); + doc.setFontSize(8); + doc.setTextColor('#9CA3AF'); + doc.text(`Page ${i} of ${totalPages}`, pageWidth / 2, doc.internal.pageSize.getHeight() - 10, { + align: 'center', + }); + } + }; + + // Title and Header (first page only) + doc.setFillColor('#6366F1'); + doc.rect(0, 0, pageWidth, 40, 'F'); + doc.setTextColor('#FFFFFF'); + doc.setFontSize(24); + doc.setFont('helvetica', 'bold'); + doc.text('Debug Information Report', margin, 25); + yPos = 50; + + // Timestamp and metadata + doc.setTextColor('#6B7280'); + doc.setFontSize(10); + doc.setFont('helvetica', 'normal'); + + const timestamp = new Date().toLocaleString(undefined, { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + }); + doc.text(`Generated: ${timestamp}`, margin, yPos); + yPos += lineHeight * 2; + + // System Information Section + if (debugData.system) { + addSectionHeader('System Information'); + + // OS and Architecture + addKeyValue('Operating System', debugData.system.os); + addKeyValue('Architecture', debugData.system.arch); + addKeyValue('Platform', debugData.system.platform); + addKeyValue('CPU Cores', debugData.system.cpus); + + // Memory + const memory = debugData.system.memory; + addKeyValue('Memory', { + 'Total Memory': memory.total, + 'Used Memory': memory.used, + 'Free Memory': memory.free, + Usage: memory.percentage + '%', + }); + + // Browser Information + const browser = debugData.system.browser; + addKeyValue('Browser', { + Name: browser.name, + Version: browser.version, + Language: browser.language, + Platform: browser.platform, + 'Cookies Enabled': browser.cookiesEnabled ? 'Yes' : 'No', + 'Online Status': browser.online ? 'Online' : 'Offline', + }); + + // Screen Information + const screen = debugData.system.screen; + addKeyValue('Screen', { + Resolution: `${screen.width}x${screen.height}`, + 'Color Depth': screen.colorDepth + ' bit', + 'Pixel Ratio': screen.pixelRatio + 'x', + }); + + // Time Information + const time = debugData.system.time; + addKeyValue('Time Settings', { + Timezone: time.timezone, + 'UTC Offset': time.offset / 60 + ' hours', + Locale: time.locale, + }); + + addHorizontalLine(); + } + + // Web App Information Section + if (debugData.webApp) { + addSectionHeader('Web App Information'); + + // Basic Info + addKeyValue('Application', { + Name: debugData.webApp.name, + Version: debugData.webApp.version, + Environment: debugData.webApp.environment, + 'Node Version': debugData.webApp.runtimeInfo.nodeVersion, + }); + + // Git Information + if (debugData.webApp.gitInfo) { + const gitInfo = debugData.webApp.gitInfo.local; + addKeyValue('Git Information', { + Branch: gitInfo.branch, + Commit: gitInfo.commitHash, + Author: gitInfo.author, + 'Commit Time': gitInfo.commitTime, + Repository: gitInfo.repoName, + }); + + if (debugData.webApp.gitInfo.github) { + const githubInfo = debugData.webApp.gitInfo.github.currentRepo; + addKeyValue('GitHub Information', { + Repository: githubInfo.fullName, + 'Default Branch': githubInfo.defaultBranch, + Stars: githubInfo.stars, + Forks: githubInfo.forks, + 'Open Issues': githubInfo.openIssues || 0, + }); + } + } + + addHorizontalLine(); + } + + // Performance Section + if (debugData.performance) { + addSectionHeader('Performance Metrics'); + + // Memory Usage + const memory = debugData.performance.memory || {}; + const totalHeap = memory.totalJSHeapSize || 0; + const usedHeap = memory.usedJSHeapSize || 0; + const usagePercentage = memory.usagePercentage || 0; + + addKeyValue('Memory Usage', { + 'Total Heap Size': formatBytes(totalHeap), + 'Used Heap Size': formatBytes(usedHeap), + Usage: usagePercentage.toFixed(1) + '%', + }); + + // Timing Metrics + const timing = debugData.performance.timing || {}; + const navigationStart = timing.navigationStart || 0; + const loadEventEnd = timing.loadEventEnd || 0; + const domContentLoadedEventEnd = timing.domContentLoadedEventEnd || 0; + const responseEnd = timing.responseEnd || 0; + const requestStart = timing.requestStart || 0; + + const loadTime = loadEventEnd > navigationStart ? loadEventEnd - navigationStart : 0; + const domReadyTime = + domContentLoadedEventEnd > navigationStart ? domContentLoadedEventEnd - navigationStart : 0; + const requestTime = responseEnd > requestStart ? responseEnd - requestStart : 0; + + addKeyValue('Page Load Metrics', { + 'Total Load Time': (loadTime / 1000).toFixed(2) + ' seconds', + 'DOM Ready Time': (domReadyTime / 1000).toFixed(2) + ' seconds', + 'Request Time': (requestTime / 1000).toFixed(2) + ' seconds', + }); + + // Network Information + if (debugData.system?.network) { + const network = debugData.system.network; + addKeyValue('Network Information', { + 'Connection Type': network.type || 'Unknown', + 'Effective Type': network.effectiveType || 'Unknown', + 'Download Speed': (network.downlink || 0) + ' Mbps', + 'Latency (RTT)': (network.rtt || 0) + ' ms', + 'Data Saver': network.saveData ? 'Enabled' : 'Disabled', + }); + } + + addHorizontalLine(); + } + + // Errors Section + if (debugData.errors && debugData.errors.length > 0) { + addSectionHeader('Error Log'); + + debugData.errors.forEach((error: LogEntry, index: number) => { + doc.setTextColor('#DC2626'); + doc.setFontSize(10); + doc.setFont('helvetica', 'bold'); + doc.text(`Error ${index + 1}:`, margin, yPos); + yPos += lineHeight; + + doc.setFont('helvetica', 'normal'); + doc.setTextColor('#6B7280'); + addKeyValue('Message', error.message, 10); + + if (error.stack) { + addKeyValue('Stack', error.stack, 10); + } + + if (error.source) { + addKeyValue('Source', error.source, 10); + } + + yPos += lineHeight; + }); + } + + // Add footers to all pages at the end + addFooters(); + + // Save the PDF + doc.save(`bolt-debug-info-${new Date().toISOString()}.pdf`); + toast.success('Debug information exported as PDF'); + } catch (error) { + console.error('Failed to export PDF:', error); + toast.error('Failed to export debug information as PDF'); + } + }; + + const exportAsText = () => { + try { + const debugData = { + system: systemInfo, + webApp: webAppInfo, + errors: logStore.getLogs().filter((log: LogEntry) => log.level === 'error'), + performance: { + memory: (performance as any).memory || {}, + timing: performance.timing, + navigation: performance.navigation, + }, + }; + + const textContent = Object.entries(debugData) + .map(([category, data]) => { + return `${category.toUpperCase()}\n${'-'.repeat(30)}\n${JSON.stringify(data, null, 2)}\n\n`; + }) + .join('\n'); + + const blob = new Blob([textContent], { type: 'text/plain' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `bolt-debug-info-${new Date().toISOString()}.txt`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + toast.success('Debug information exported as text file'); + } catch (error) { + console.error('Failed to export text file:', error); + toast.error('Failed to export debug information as text file'); + } + }; + + const exportFormats: ExportFormat[] = [ + { + id: 'json', + label: 'Export as JSON', + icon: 'i-ph:file-json', + handler: exportDebugInfo, + }, + { + id: 'csv', + label: 'Export as CSV', + icon: 'i-ph:file-csv', + handler: exportAsCSV, + }, + { + id: 'pdf', + label: 'Export as PDF', + icon: 'i-ph:file-pdf', + handler: exportAsPDF, + }, + { + id: 'txt', + label: 'Export as Text', + icon: 'i-ph:file-text', + handler: exportAsText, + }, + ]; + // Add Ollama health check function const checkOllamaHealth = async () => { try { @@ -687,6 +1128,77 @@ export default function DebugTab() { return () => clearInterval(interval); }, []); + // Replace the existing export button with this new component + const ExportButton = () => { + const [isOpen, setIsOpen] = useState(false); + + const handleOpenChange = useCallback((open: boolean) => { + setIsOpen(open); + }, []); + + const handleFormatClick = useCallback((handler: () => void) => { + handler(); + setIsOpen(false); + }, []); + + return ( + + + + +
+ +
+ Export Debug Information + + +
+ {exportFormats.map((format) => ( + + ))} +
+
+
+
+ ); + }; + return (
{/* Quick Stats Banner */} @@ -830,20 +1342,7 @@ export default function DebugTab() { Fetch WebApp Info - +
{/* System Information */} diff --git a/app/components/@settings/tabs/event-logs/EventLogsTab.tsx b/app/components/@settings/tabs/event-logs/EventLogsTab.tsx index c414d6cf..8d28c26e 100644 --- a/app/components/@settings/tabs/event-logs/EventLogsTab.tsx +++ b/app/components/@settings/tabs/event-logs/EventLogsTab.tsx @@ -5,6 +5,9 @@ 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'; +import { Dialog, DialogRoot, DialogTitle } from '~/components/ui/Dialog'; +import { jsPDF } from 'jspdf'; +import { toast } from 'react-toastify'; interface SelectOption { value: string; @@ -252,6 +255,13 @@ const LogEntryItem = ({ log, isExpanded: forceExpanded, use24Hour, showTimestamp ); }; +interface ExportFormat { + id: string; + label: string; + icon: string; + handler: () => void; +} + export function EventLogsTab() { const logs = useStore(logStore.logs); const [selectedLevel, setSelectedLevel] = useState<'all' | string>('all'); @@ -329,51 +339,6 @@ export function EventLogsTab() { 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(); @@ -442,6 +407,455 @@ export function EventLogsTab() { const selectedLevelOption = logLevelOptions.find((opt) => opt.value === selectedLevel); + // Export functions + const exportAsJSON = () => { + try { + const exportData = { + timestamp: new Date().toISOString(), + logs: filteredLogs, + filters: { + level: selectedLevel, + searchQuery, + }, + preferences: { + use24Hour, + showTimestamps, + autoExpand, + }, + }; + + const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `bolt-event-logs-${new Date().toISOString()}.json`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + toast.success('Event logs exported successfully as JSON'); + } catch (error) { + console.error('Failed to export JSON:', error); + toast.error('Failed to export event logs as JSON'); + } + }; + + const exportAsCSV = () => { + try { + // Convert logs to CSV format + const headers = ['Timestamp', 'Level', 'Category', 'Message', 'Details']; + const csvData = [ + headers, + ...filteredLogs.map((log) => [ + new Date(log.timestamp).toISOString(), + log.level, + log.category || '', + log.message, + log.details ? JSON.stringify(log.details) : '', + ]), + ]; + + const csvContent = csvData + .map((row) => row.map((cell) => `"${String(cell).replace(/"/g, '""')}"`).join(',')) + .join('\n'); + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `bolt-event-logs-${new Date().toISOString()}.csv`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + toast.success('Event logs exported successfully as CSV'); + } catch (error) { + console.error('Failed to export CSV:', error); + toast.error('Failed to export event logs as CSV'); + } + }; + + const exportAsPDF = () => { + try { + // Create new PDF document + const doc = new jsPDF(); + const lineHeight = 7; + let yPos = 20; + const margin = 20; + const pageWidth = doc.internal.pageSize.getWidth(); + const maxLineWidth = pageWidth - 2 * margin; + + // Helper function to add section header + const addSectionHeader = (title: string) => { + // Check if we need a new page + if (yPos > doc.internal.pageSize.getHeight() - 30) { + doc.addPage(); + yPos = margin; + } + + doc.setFillColor('#F3F4F6'); + doc.rect(margin - 2, yPos - 5, pageWidth - 2 * (margin - 2), lineHeight + 6, 'F'); + doc.setFont('helvetica', 'bold'); + doc.setTextColor('#111827'); + doc.setFontSize(12); + doc.text(title.toUpperCase(), margin, yPos); + yPos += lineHeight * 2; + }; + + // Add title and header + doc.setFillColor('#6366F1'); + doc.rect(0, 0, pageWidth, 50, 'F'); + doc.setTextColor('#FFFFFF'); + doc.setFontSize(24); + doc.setFont('helvetica', 'bold'); + doc.text('Event Logs Report', margin, 35); + + // Add subtitle with bolt.diy + doc.setFontSize(12); + doc.setFont('helvetica', 'normal'); + doc.text('bolt.diy - AI Development Platform', margin, 45); + yPos = 70; + + // Add report summary section + addSectionHeader('Report Summary'); + + doc.setFontSize(10); + doc.setFont('helvetica', 'normal'); + doc.setTextColor('#374151'); + + const summaryItems = [ + { label: 'Generated', value: new Date().toLocaleString() }, + { label: 'Total Logs', value: filteredLogs.length.toString() }, + { label: 'Filter Applied', value: selectedLevel === 'all' ? 'All Types' : selectedLevel }, + { label: 'Search Query', value: searchQuery || 'None' }, + { label: 'Time Format', value: use24Hour ? '24-hour' : '12-hour' }, + ]; + + summaryItems.forEach((item) => { + doc.setFont('helvetica', 'bold'); + doc.text(`${item.label}:`, margin, yPos); + doc.setFont('helvetica', 'normal'); + doc.text(item.value, margin + 60, yPos); + yPos += lineHeight; + }); + + yPos += lineHeight * 2; + + // Add statistics section + addSectionHeader('Log Statistics'); + + // Calculate statistics + const stats = { + error: filteredLogs.filter((log) => log.level === 'error').length, + warning: filteredLogs.filter((log) => log.level === 'warning').length, + info: filteredLogs.filter((log) => log.level === 'info').length, + debug: filteredLogs.filter((log) => log.level === 'debug').length, + provider: filteredLogs.filter((log) => log.category === 'provider').length, + api: filteredLogs.filter((log) => log.category === 'api').length, + }; + + // Create two columns for statistics + const leftStats = [ + { label: 'Error Logs', value: stats.error, color: '#DC2626' }, + { label: 'Warning Logs', value: stats.warning, color: '#F59E0B' }, + { label: 'Info Logs', value: stats.info, color: '#3B82F6' }, + ]; + + const rightStats = [ + { label: 'Debug Logs', value: stats.debug, color: '#6B7280' }, + { label: 'LLM Logs', value: stats.provider, color: '#10B981' }, + { label: 'API Logs', value: stats.api, color: '#3B82F6' }, + ]; + + const colWidth = (pageWidth - 2 * margin) / 2; + + // Draw statistics in two columns + leftStats.forEach((stat, index) => { + doc.setTextColor(stat.color); + doc.setFont('helvetica', 'bold'); + doc.text(stat.value.toString(), margin, yPos); + doc.setTextColor('#374151'); + doc.setFont('helvetica', 'normal'); + doc.text(stat.label, margin + 20, yPos); + + if (rightStats[index]) { + doc.setTextColor(rightStats[index].color); + doc.setFont('helvetica', 'bold'); + doc.text(rightStats[index].value.toString(), margin + colWidth, yPos); + doc.setTextColor('#374151'); + doc.setFont('helvetica', 'normal'); + doc.text(rightStats[index].label, margin + colWidth + 20, yPos); + } + + yPos += lineHeight; + }); + + yPos += lineHeight * 2; + + // Add logs section + addSectionHeader('Event Logs'); + + // Helper function to add a log entry with improved formatting + const addLogEntry = (log: LogEntry) => { + const entryHeight = 20 + (log.details ? 40 : 0); // Estimate entry height + + // Check if we need a new page + if (yPos + entryHeight > doc.internal.pageSize.getHeight() - 20) { + doc.addPage(); + yPos = margin; + } + + // Add timestamp and level + const timestamp = new Date(log.timestamp).toLocaleString(undefined, { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: !use24Hour, + }); + + // Draw log level badge background + const levelColors: Record = { + error: '#FEE2E2', + warning: '#FEF3C7', + info: '#DBEAFE', + debug: '#F3F4F6', + }; + + const textColors: Record = { + error: '#DC2626', + warning: '#F59E0B', + info: '#3B82F6', + debug: '#6B7280', + }; + + const levelWidth = doc.getTextWidth(log.level.toUpperCase()) + 10; + doc.setFillColor(levelColors[log.level] || '#F3F4F6'); + doc.roundedRect(margin, yPos - 4, levelWidth, lineHeight + 4, 1, 1, 'F'); + + // Add log level text + doc.setTextColor(textColors[log.level] || '#6B7280'); + doc.setFont('helvetica', 'bold'); + doc.setFontSize(8); + doc.text(log.level.toUpperCase(), margin + 5, yPos); + + // Add timestamp + doc.setTextColor('#6B7280'); + doc.setFont('helvetica', 'normal'); + doc.setFontSize(9); + doc.text(timestamp, margin + levelWidth + 10, yPos); + + // Add category if present + if (log.category) { + const categoryX = margin + levelWidth + doc.getTextWidth(timestamp) + 20; + doc.setFillColor('#F3F4F6'); + + const categoryWidth = doc.getTextWidth(log.category) + 10; + doc.roundedRect(categoryX, yPos - 4, categoryWidth, lineHeight + 4, 2, 2, 'F'); + doc.setTextColor('#6B7280'); + doc.text(log.category, categoryX + 5, yPos); + } + + yPos += lineHeight * 1.5; + + // Add message + doc.setTextColor('#111827'); + doc.setFontSize(10); + + const messageLines = doc.splitTextToSize(log.message, maxLineWidth - 10); + doc.text(messageLines, margin + 5, yPos); + yPos += messageLines.length * lineHeight; + + // Add details if present + if (log.details) { + doc.setTextColor('#6B7280'); + doc.setFontSize(8); + + const detailsStr = JSON.stringify(log.details, null, 2); + const detailsLines = doc.splitTextToSize(detailsStr, maxLineWidth - 15); + + // Add details background + doc.setFillColor('#F9FAFB'); + doc.roundedRect(margin + 5, yPos - 2, maxLineWidth - 10, detailsLines.length * lineHeight + 8, 1, 1, 'F'); + + doc.text(detailsLines, margin + 10, yPos + 4); + yPos += detailsLines.length * lineHeight + 10; + } + + // Add separator line + doc.setDrawColor('#E5E7EB'); + doc.setLineWidth(0.1); + doc.line(margin, yPos, pageWidth - margin, yPos); + yPos += lineHeight * 1.5; + }; + + // Add all logs + filteredLogs.forEach((log) => { + addLogEntry(log); + }); + + // Add footer to all pages + const totalPages = doc.internal.pages.length - 1; + + for (let i = 1; i <= totalPages; i++) { + doc.setPage(i); + doc.setFontSize(8); + doc.setTextColor('#9CA3AF'); + + // Add page numbers + doc.text(`Page ${i} of ${totalPages}`, pageWidth / 2, doc.internal.pageSize.getHeight() - 10, { + align: 'center', + }); + + // Add footer text + doc.text('Generated by bolt.diy', margin, doc.internal.pageSize.getHeight() - 10); + + const dateStr = new Date().toLocaleDateString(); + doc.text(dateStr, pageWidth - margin, doc.internal.pageSize.getHeight() - 10, { align: 'right' }); + } + + // Save the PDF + doc.save(`bolt-event-logs-${new Date().toISOString()}.pdf`); + toast.success('Event logs exported successfully as PDF'); + } catch (error) { + console.error('Failed to export PDF:', error); + toast.error('Failed to export event logs as PDF'); + } + }; + + const exportAsText = () => { + try { + const textContent = filteredLogs + .map((log) => { + const timestamp = new Date(log.timestamp).toLocaleString(); + let content = `[${timestamp}] ${log.level.toUpperCase()}: ${log.message}\n`; + + if (log.category) { + content += `Category: ${log.category}\n`; + } + + if (log.details) { + content += `Details:\n${JSON.stringify(log.details, null, 2)}\n`; + } + + return content + '-'.repeat(80) + '\n'; + }) + .join('\n'); + + const blob = new Blob([textContent], { type: 'text/plain' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `bolt-event-logs-${new Date().toISOString()}.txt`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + toast.success('Event logs exported successfully as text file'); + } catch (error) { + console.error('Failed to export text file:', error); + toast.error('Failed to export event logs as text file'); + } + }; + + const exportFormats: ExportFormat[] = [ + { + id: 'json', + label: 'Export as JSON', + icon: 'i-ph:file-json', + handler: exportAsJSON, + }, + { + id: 'csv', + label: 'Export as CSV', + icon: 'i-ph:file-csv', + handler: exportAsCSV, + }, + { + id: 'pdf', + label: 'Export as PDF', + icon: 'i-ph:file-pdf', + handler: exportAsPDF, + }, + { + id: 'txt', + label: 'Export as Text', + icon: 'i-ph:file-text', + handler: exportAsText, + }, + ]; + + const ExportButton = () => { + const [isOpen, setIsOpen] = useState(false); + + const handleOpenChange = useCallback((open: boolean) => { + setIsOpen(open); + }, []); + + const handleFormatClick = useCallback((handler: () => void) => { + handler(); + setIsOpen(false); + }, []); + + return ( + + + + +
+ +
+ Export Event Logs + + +
+ {exportFormats.map((format) => ( + + ))} +
+
+
+
+ ); + }; + return (
@@ -540,21 +954,7 @@ export function EventLogsTab() { Refresh - +
diff --git a/package.json b/package.json index 663e2e69..66d7a9fa 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "istextorbinary": "^9.5.0", "jose": "^5.9.6", "js-cookie": "^3.0.5", + "jspdf": "^2.5.2", "jszip": "^3.10.1", "nanostores": "^0.10.3", "next": "^15.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c25c1ce..1005da02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -221,6 +221,9 @@ importers: js-cookie: specifier: ^3.0.5 version: 3.0.5 + jspdf: + specifier: ^2.5.2 + version: 2.5.2 jszip: specifier: ^3.10.1 version: 3.10.1 @@ -2841,6 +2844,9 @@ packages: '@types/prop-types@15.7.14': resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} + '@types/raf@3.4.3': + resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==} + '@types/react-beautiful-dnd@13.1.8': resolution: {integrity: sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==} @@ -3167,6 +3173,11 @@ packages: async-lock@1.4.1: resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + atob@2.1.2: + resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} + engines: {node: '>= 4.5.0'} + hasBin: true + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -3177,6 +3188,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -3254,6 +3269,11 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + btoa@1.2.1: + resolution: {integrity: sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==} + engines: {node: '>= 0.4.0'} + hasBin: true + buffer-builder@0.2.0: resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==} @@ -3310,6 +3330,10 @@ packages: caniuse-lite@1.0.30001695: resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==} + canvg@3.0.10: + resolution: {integrity: sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==} + engines: {node: '>=10.0.0'} + capnp-ts@0.7.0: resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} @@ -3469,6 +3493,9 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + core-js@3.40.0: + resolution: {integrity: sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -3503,6 +3530,9 @@ packages: css-box-model@1.2.1: resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} + css-line-break@2.1.0: + resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} + css-tree@2.3.1: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -3641,6 +3671,9 @@ packages: resolution: {integrity: sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==} engines: {node: '>=10'} + dompurify@2.5.8: + resolution: {integrity: sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==} + dotenv@16.4.7: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} @@ -3956,6 +3989,9 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -4199,6 +4235,10 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + html2canvas@1.4.1: + resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} + engines: {node: '>=8.0.0'} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -4460,6 +4500,9 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jspdf@2.5.2: + resolution: {integrity: sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==} + jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} @@ -5209,6 +5252,9 @@ packages: perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} @@ -5405,6 +5451,9 @@ packages: raf-schd@4.0.3: resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} + raf@3.4.1: + resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -5567,6 +5616,9 @@ packages: redux@4.2.1: resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -5685,6 +5737,10 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rgbcolor@1.0.1: + resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==} + engines: {node: '>= 0.8.15'} + ripemd160@2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} @@ -5986,6 +6042,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stackblur-canvas@2.7.0: + resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==} + engines: {node: '>=0.1.14'} + stacktracey@2.1.8: resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==} @@ -6093,6 +6153,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svg-pathdata@6.0.3: + resolution: {integrity: sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==} + engines: {node: '>=12.0.0'} + swr@2.3.0: resolution: {integrity: sha512-NyZ76wA4yElZWBHzSgEJc28a0u6QZvhb6w0azeL2k7+Q1gAzVK+IqQYXhVOC/mzi+HZIozrZvBVeSeOZNR2bqA==} peerDependencies: @@ -6131,6 +6195,9 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + text-segmentation@1.0.3: + resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} + textextensions@6.11.0: resolution: {integrity: sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==} engines: {node: '>=4'} @@ -6395,6 +6462,9 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + utrie@1.0.2: + resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true @@ -9417,6 +9487,9 @@ snapshots: '@types/prop-types@15.7.14': {} + '@types/raf@3.4.3': + optional: true + '@types/react-beautiful-dnd@13.1.8': dependencies: '@types/react': 18.3.18 @@ -9897,6 +9970,8 @@ snapshots: async-lock@1.4.1: {} + atob@2.1.2: {} + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 @@ -9905,6 +9980,9 @@ snapshots: balanced-match@1.0.2: {} + base64-arraybuffer@1.0.2: + optional: true + base64-js@1.5.1: {} before-after-hook@3.0.2: {} @@ -10021,6 +10099,8 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.2(browserslist@4.24.4) + btoa@1.2.1: {} + buffer-builder@0.2.0: {} buffer-from@1.1.2: {} @@ -10083,6 +10163,18 @@ snapshots: caniuse-lite@1.0.30001695: {} + canvg@3.0.10: + dependencies: + '@babel/runtime': 7.26.7 + '@types/raf': 3.4.3 + core-js: 3.40.0 + raf: 3.4.1 + regenerator-runtime: 0.13.11 + rgbcolor: 1.0.1 + stackblur-canvas: 2.7.0 + svg-pathdata: 6.0.3 + optional: true + capnp-ts@0.7.0: dependencies: debug: 4.4.0 @@ -10218,6 +10310,9 @@ snapshots: cookie@0.7.2: {} + core-js@3.40.0: + optional: true + core-util-is@1.0.3: {} crc-32@1.2.2: {} @@ -10273,6 +10368,11 @@ snapshots: dependencies: tiny-invariant: 1.3.3 + css-line-break@2.1.0: + dependencies: + utrie: 1.0.2 + optional: true + css-tree@2.3.1: dependencies: mdn-data: 2.0.30 @@ -10378,6 +10478,9 @@ snapshots: domain-browser@4.22.0: {} + dompurify@2.5.8: + optional: true + dotenv@16.4.7: {} dunder-proto@1.0.1: @@ -10821,6 +10924,8 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 + fflate@0.8.2: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -11148,6 +11253,12 @@ snapshots: html-void-elements@3.0.0: {} + html2canvas@1.4.1: + dependencies: + css-line-break: 2.1.0 + text-segmentation: 1.0.3 + optional: true + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -11379,6 +11490,18 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jspdf@2.5.2: + dependencies: + '@babel/runtime': 7.26.7 + atob: 2.1.2 + btoa: 1.2.1 + fflate: 0.8.2 + optionalDependencies: + canvg: 3.0.10 + core-js: 3.40.0 + dompurify: 2.5.8 + html2canvas: 1.4.1 + jszip@3.10.1: dependencies: lie: 3.3.0 @@ -12550,6 +12673,9 @@ snapshots: perfect-debounce@1.0.0: {} + performance-now@2.1.0: + optional: true + periscopic@3.1.0: dependencies: '@types/estree': 1.0.6 @@ -12728,6 +12854,11 @@ snapshots: raf-schd@4.0.3: {} + raf@3.4.1: + dependencies: + performance-now: 2.1.0 + optional: true + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -12910,6 +13041,9 @@ snapshots: dependencies: '@babel/runtime': 7.26.7 + regenerator-runtime@0.13.11: + optional: true + regenerator-runtime@0.14.1: {} regex-recursion@5.1.1: @@ -13045,6 +13179,9 @@ snapshots: reusify@1.0.4: {} + rgbcolor@1.0.1: + optional: true + ripemd160@2.0.2: dependencies: hash-base: 3.0.5 @@ -13394,6 +13531,9 @@ snapshots: stackback@0.0.2: {} + stackblur-canvas@2.7.0: + optional: true + stacktracey@2.1.8: dependencies: as-table: 1.0.55 @@ -13493,6 +13633,9 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svg-pathdata@6.0.3: + optional: true + swr@2.3.0(react@18.3.1): dependencies: dequal: 2.0.3 @@ -13542,6 +13685,11 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 + text-segmentation@1.0.3: + dependencies: + utrie: 1.0.2 + optional: true + textextensions@6.11.0: dependencies: editions: 6.21.0 @@ -13829,6 +13977,11 @@ snapshots: utils-merge@1.0.1: {} + utrie@1.0.2: + dependencies: + base64-arraybuffer: 1.0.2 + optional: true + uuid@9.0.1: {} uvu@0.5.6: