import React, { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { classNames } from '~/utils/classNames'; import { logStore } from '~/lib/stores/logs'; import type { LogEntry } from '~/lib/stores/logs'; interface SystemInfo { os: string; arch: string; platform: string; cpus: string; memory: { total: string; free: string; used: string; percentage: number; }; node: string; browser: { name: string; version: string; language: string; userAgent: string; cookiesEnabled: boolean; online: boolean; platform: string; cores: number; }; screen: { width: number; height: number; colorDepth: number; pixelRatio: number; }; time: { timezone: string; offset: number; locale: string; }; performance: { memory: { jsHeapSizeLimit: number; totalJSHeapSize: number; usedJSHeapSize: number; usagePercentage: number; }; timing: { loadTime: number; domReadyTime: number; readyStart: number; redirectTime: number; appcacheTime: number; unloadEventTime: number; lookupDomainTime: number; connectTime: number; requestTime: number; initDomTreeTime: number; loadEventTime: number; }; navigation: { type: number; redirectCount: number; }; }; network: { downlink: number; effectiveType: string; rtt: number; saveData: boolean; type: string; }; battery?: { charging: boolean; chargingTime: number; dischargingTime: number; level: number; }; storage: { quota: number; usage: number; persistent: boolean; temporary: boolean; }; } interface WebAppInfo { // Local WebApp Info name: string; version: string; description: string; license: string; nodeVersion: string; dependencies: { [key: string]: string }; devDependencies: { [key: string]: string }; // Build Info buildTime?: string; buildNumber?: string; environment?: string; // Git Info gitInfo?: { branch: string; commit: string; commitTime: string; author: string; remoteUrl: string; }; // GitHub Repository Info repoInfo?: { name: string; fullName: string; description: string; stars: number; forks: number; openIssues: number; defaultBranch: string; lastUpdate: string; owner: { login: string; avatarUrl: string; }; }; } interface GitInfo { branch: string; commit: string; commitTime: string; author: string; remoteUrl: string; } interface RepoData { name: string; full_name: string; description: string; stargazers_count: number; forks_count: number; open_issues_count: number; default_branch: string; updated_at: string; owner: { login: string; avatar_url: string; }; } interface AppData { name: string; version: string; description: string; license: string; nodeVersion: string; dependencies: { [key: string]: string }; devDependencies: { [key: string]: string }; } export default function DebugTab() { const [systemInfo, setSystemInfo] = useState(null); const [webAppInfo, setWebAppInfo] = useState(null); const [loading, setLoading] = useState({ systemInfo: false, performance: false, errors: false, webAppInfo: false, }); const [errorLog, setErrorLog] = useState<{ errors: any[]; lastCheck: string | null; }>({ errors: [], lastCheck: null, }); // Fetch initial data useEffect(() => { getSystemInfo(); getWebAppInfo(); }, []); // Set up error listeners when component mounts useEffect(() => { const errors: any[] = []; const handleError = (event: ErrorEvent) => { errors.push({ type: 'error', message: event.message, filename: event.filename, lineNumber: event.lineno, columnNumber: event.colno, error: event.error, timestamp: new Date().toISOString(), }); }; const handleRejection = (event: PromiseRejectionEvent) => { errors.push({ type: 'unhandledRejection', reason: event.reason, timestamp: new Date().toISOString(), }); }; window.addEventListener('error', handleError); window.addEventListener('unhandledrejection', handleRejection); return () => { window.removeEventListener('error', handleError); window.removeEventListener('unhandledrejection', handleRejection); }; }, []); const getSystemInfo = async () => { try { setLoading((prev) => ({ ...prev, systemInfo: true })); // Get browser info const ua = navigator.userAgent; const browserName = ua.includes('Firefox') ? 'Firefox' : ua.includes('Chrome') ? 'Chrome' : ua.includes('Safari') ? 'Safari' : ua.includes('Edge') ? 'Edge' : 'Unknown'; const browserVersion = ua.match(/(Firefox|Chrome|Safari|Edge)\/([0-9.]+)/)?.[2] || 'Unknown'; // Get performance metrics const memory = (performance as any).memory || {}; const timing = performance.timing; const navigation = performance.navigation; const connection = (navigator as any).connection; // Get battery info let batteryInfo; try { const battery = await (navigator as any).getBattery(); batteryInfo = { charging: battery.charging, chargingTime: battery.chargingTime, dischargingTime: battery.dischargingTime, level: battery.level * 100, }; } catch { console.log('Battery API not supported'); } // Get storage info let storageInfo = { quota: 0, usage: 0, persistent: false, temporary: false, }; try { const storage = await navigator.storage.estimate(); const persistent = await navigator.storage.persist(); storageInfo = { quota: storage.quota || 0, usage: storage.usage || 0, persistent, temporary: !persistent, }; } catch { console.log('Storage API not supported'); } // Get memory info from browser performance API const performanceMemory = (performance as any).memory || {}; const totalMemory = performanceMemory.jsHeapSizeLimit || 0; const usedMemory = performanceMemory.usedJSHeapSize || 0; const freeMemory = totalMemory - usedMemory; const memoryPercentage = totalMemory ? (usedMemory / totalMemory) * 100 : 0; const systemInfo: SystemInfo = { os: navigator.platform, arch: navigator.userAgent.includes('x64') ? 'x64' : navigator.userAgent.includes('arm') ? 'arm' : 'unknown', platform: navigator.platform, cpus: navigator.hardwareConcurrency + ' cores', memory: { total: formatBytes(totalMemory), free: formatBytes(freeMemory), used: formatBytes(usedMemory), percentage: Math.round(memoryPercentage), }, node: 'browser', browser: { name: browserName, version: browserVersion, language: navigator.language, userAgent: navigator.userAgent, cookiesEnabled: navigator.cookieEnabled, online: navigator.onLine, platform: navigator.platform, cores: navigator.hardwareConcurrency, }, screen: { width: window.screen.width, height: window.screen.height, colorDepth: window.screen.colorDepth, pixelRatio: window.devicePixelRatio, }, time: { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, offset: new Date().getTimezoneOffset(), locale: navigator.language, }, performance: { memory: { jsHeapSizeLimit: memory.jsHeapSizeLimit || 0, totalJSHeapSize: memory.totalJSHeapSize || 0, usedJSHeapSize: memory.usedJSHeapSize || 0, usagePercentage: memory.totalJSHeapSize ? (memory.usedJSHeapSize / memory.totalJSHeapSize) * 100 : 0, }, timing: { loadTime: timing.loadEventEnd - timing.navigationStart, domReadyTime: timing.domContentLoadedEventEnd - timing.navigationStart, readyStart: timing.fetchStart - timing.navigationStart, redirectTime: timing.redirectEnd - timing.redirectStart, appcacheTime: timing.domainLookupStart - timing.fetchStart, unloadEventTime: timing.unloadEventEnd - timing.unloadEventStart, lookupDomainTime: timing.domainLookupEnd - timing.domainLookupStart, connectTime: timing.connectEnd - timing.connectStart, requestTime: timing.responseEnd - timing.requestStart, initDomTreeTime: timing.domInteractive - timing.responseEnd, loadEventTime: timing.loadEventEnd - timing.loadEventStart, }, navigation: { type: navigation.type, redirectCount: navigation.redirectCount, }, }, network: { downlink: connection?.downlink || 0, effectiveType: connection?.effectiveType || 'unknown', rtt: connection?.rtt || 0, saveData: connection?.saveData || false, type: connection?.type || 'unknown', }, battery: batteryInfo, storage: storageInfo, }; setSystemInfo(systemInfo); toast.success('System information updated'); } catch (error) { toast.error('Failed to get system information'); console.error('Failed to get system information:', error); } finally { setLoading((prev) => ({ ...prev, systemInfo: false })); } }; const getWebAppInfo = async () => { try { setLoading((prev) => ({ ...prev, webAppInfo: true })); // Fetch local app info const appInfoResponse = await fetch('/api/system/app-info'); if (!appInfoResponse.ok) { throw new Error('Failed to fetch webapp info'); } const appData = (await appInfoResponse.json()) as AppData; // Fetch git info const gitInfoResponse = await fetch('/api/system/git-info'); let gitInfo: GitInfo | undefined; if (gitInfoResponse.ok) { gitInfo = (await gitInfoResponse.json()) as GitInfo; } // Fetch GitHub repository info const repoInfoResponse = await fetch('https://api.github.com/repos/stackblitz-labs/bolt.diy'); let repoInfo: WebAppInfo['repoInfo'] | undefined; if (repoInfoResponse.ok) { const repoData = (await repoInfoResponse.json()) as RepoData; repoInfo = { name: repoData.name, fullName: repoData.full_name, description: repoData.description, stars: repoData.stargazers_count, forks: repoData.forks_count, openIssues: repoData.open_issues_count, defaultBranch: repoData.default_branch, lastUpdate: repoData.updated_at, owner: { login: repoData.owner.login, avatarUrl: repoData.owner.avatar_url, }, }; } // Get build info from environment variables or config const buildInfo = { buildTime: process.env.NEXT_PUBLIC_BUILD_TIME || new Date().toISOString(), buildNumber: process.env.NEXT_PUBLIC_BUILD_NUMBER || 'development', environment: process.env.NEXT_PUBLIC_ENV || 'development', }; setWebAppInfo({ ...appData, ...buildInfo, gitInfo, repoInfo, }); } catch (error) { console.error('Failed to fetch webapp info:', error); toast.error('Failed to fetch webapp information'); } finally { setLoading((prev) => ({ ...prev, webAppInfo: false })); } }; // Helper function to format bytes to human readable format const formatBytes = (bytes: number) => { const units = ['B', 'KB', 'MB', 'GB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${Math.round(size)} ${units[unitIndex]}`; }; const handleLogPerformance = () => { try { setLoading((prev) => ({ ...prev, performance: true })); // Get performance metrics using modern Performance API const performanceEntries = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; const memory = (performance as any).memory; // Calculate timing metrics const timingMetrics = { loadTime: performanceEntries.loadEventEnd - performanceEntries.startTime, domReadyTime: performanceEntries.domContentLoadedEventEnd - performanceEntries.startTime, fetchTime: performanceEntries.responseEnd - performanceEntries.fetchStart, redirectTime: performanceEntries.redirectEnd - performanceEntries.redirectStart, dnsTime: performanceEntries.domainLookupEnd - performanceEntries.domainLookupStart, tcpTime: performanceEntries.connectEnd - performanceEntries.connectStart, ttfb: performanceEntries.responseStart - performanceEntries.requestStart, processingTime: performanceEntries.loadEventEnd - performanceEntries.responseEnd, }; // Get resource timing data const resourceEntries = performance.getEntriesByType('resource'); const resourceStats = { totalResources: resourceEntries.length, totalSize: resourceEntries.reduce((total, entry) => total + (entry as any).transferSize || 0, 0), totalTime: Math.max(...resourceEntries.map((entry) => entry.duration)), }; // Get memory metrics const memoryMetrics = memory ? { jsHeapSizeLimit: memory.jsHeapSizeLimit, totalJSHeapSize: memory.totalJSHeapSize, usedJSHeapSize: memory.usedJSHeapSize, heapUtilization: (memory.usedJSHeapSize / memory.totalJSHeapSize) * 100, } : null; // Get frame rate metrics let fps = 0; if ('requestAnimationFrame' in window) { const times: number[] = []; function calculateFPS(now: number) { times.push(now); if (times.length > 10) { const fps = Math.round((1000 * 10) / (now - times[0])); times.shift(); return fps; } requestAnimationFrame(calculateFPS); return 0; } fps = calculateFPS(performance.now()); } // Log all performance metrics logStore.logSystem('Performance Metrics', { timing: timingMetrics, resources: resourceStats, memory: memoryMetrics, fps, timestamp: new Date().toISOString(), navigationEntry: { type: performanceEntries.type, redirectCount: performanceEntries.redirectCount, }, }); toast.success('Performance metrics logged'); } catch (error) { toast.error('Failed to log performance metrics'); console.error('Failed to log performance metrics:', error); } finally { setLoading((prev) => ({ ...prev, performance: false })); } }; const checkErrors = async () => { try { setLoading((prev) => ({ ...prev, errors: true })); // Get errors from log store const storedErrors = logStore.getLogs().filter((log: LogEntry) => log.level === 'error'); // Combine with runtime errors const allErrors = [ ...errorLog.errors, ...storedErrors.map((error) => ({ type: 'stored', message: error.message, timestamp: error.timestamp, details: error.details || {}, })), ]; setErrorLog({ errors: allErrors, lastCheck: new Date().toISOString(), }); if (allErrors.length === 0) { toast.success('No errors found'); } else { toast.warning(`Found ${allErrors.length} error(s)`); } } catch (error) { toast.error('Failed to check errors'); console.error('Failed to check errors:', error); } finally { setLoading((prev) => ({ ...prev, errors: false })); } }; const exportDebugInfo = () => { try { const debugData = { timestamp: new Date().toISOString(), system: systemInfo, webApp: webAppInfo, errors: errorLog.errors, performance: { memory: (performance as any).memory || {}, timing: performance.timing, navigation: performance.navigation, }, }; const blob = new Blob([JSON.stringify(debugData, null, 2)], { type: 'application/json' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `bolt-debug-info-${new Date().toISOString()}.json`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); toast.success('Debug information exported successfully'); } catch (error) { console.error('Failed to export debug info:', error); toast.error('Failed to export debug information'); } }; return (
{/* Action Buttons */}
{/* System Information */}

System Information

{systemInfo ? (
OS: {systemInfo.os}
Platform: {systemInfo.platform}
Architecture: {systemInfo.arch}
CPU Cores: {systemInfo.cpus}
Node Version: {systemInfo.node}
Network Type: {systemInfo.network.type} ({systemInfo.network.effectiveType})
Network Speed: {systemInfo.network.downlink}Mbps (RTT: {systemInfo.network.rtt}ms)
{systemInfo.battery && (
Battery: {systemInfo.battery.level.toFixed(1)}% {systemInfo.battery.charging ? '(Charging)' : ''}
)}
Storage: {(systemInfo.storage.usage / (1024 * 1024 * 1024)).toFixed(2)}GB /{' '} {(systemInfo.storage.quota / (1024 * 1024 * 1024)).toFixed(2)}GB
Memory Usage: {systemInfo.memory.used} / {systemInfo.memory.total} ({systemInfo.memory.percentage}%)
Browser: {systemInfo.browser.name} {systemInfo.browser.version}
Screen: {systemInfo.screen.width}x{systemInfo.screen.height} ({systemInfo.screen.pixelRatio}x)
Timezone: {systemInfo.time.timezone}
Language: {systemInfo.browser.language}
JS Heap: {(systemInfo.performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(1)}MB /{' '} {(systemInfo.performance.memory.totalJSHeapSize / (1024 * 1024)).toFixed(1)}MB ( {systemInfo.performance.memory.usagePercentage.toFixed(1)}%)
Page Load: {(systemInfo.performance.timing.loadTime / 1000).toFixed(2)}s
DOM Ready: {(systemInfo.performance.timing.domReadyTime / 1000).toFixed(2)}s
) : (
Loading system information...
)}
{/* Performance Metrics */}

Performance Metrics

{systemInfo && (
Page Load Time: {(systemInfo.performance.timing.loadTime / 1000).toFixed(2)}s
DOM Ready Time: {(systemInfo.performance.timing.domReadyTime / 1000).toFixed(2)}s
Request Time: {(systemInfo.performance.timing.requestTime / 1000).toFixed(2)}s
Redirect Time: {(systemInfo.performance.timing.redirectTime / 1000).toFixed(2)}s
JS Heap Usage: {(systemInfo.performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(1)}MB /{' '} {(systemInfo.performance.memory.totalJSHeapSize / (1024 * 1024)).toFixed(1)}MB
Heap Utilization: {systemInfo.performance.memory.usagePercentage.toFixed(1)}%
Navigation Type: {systemInfo.performance.navigation.type === 0 ? 'Navigate' : systemInfo.performance.navigation.type === 1 ? 'Reload' : systemInfo.performance.navigation.type === 2 ? 'Back/Forward' : 'Other'}
Redirects: {systemInfo.performance.navigation.redirectCount}
)}
{/* WebApp Information */}

WebApp Information

{webAppInfo ? (
Name: {webAppInfo.name}
Version: {webAppInfo.version}
Description: {webAppInfo.description}
License: {webAppInfo.license}
Node Version: {webAppInfo.nodeVersion}
{webAppInfo.buildTime && (
Build Time: {webAppInfo.buildTime}
)} {webAppInfo.buildNumber && (
Build Number: {webAppInfo.buildNumber}
)} {webAppInfo.environment && (
Environment: {webAppInfo.environment}
)}
Key Dependencies:
{Object.entries(webAppInfo.dependencies) .filter(([key]) => ['react', '@remix-run/react', 'next', 'typescript'].includes(key)) .map(([key, version]) => (
{key}: {version}
))}
{webAppInfo.gitInfo && (
Git Info:
Branch: {webAppInfo.gitInfo.branch}
Commit: {webAppInfo.gitInfo.commit}
Commit Time: {webAppInfo.gitInfo.commitTime}
Author: {webAppInfo.gitInfo.author}
Remote URL: {webAppInfo.gitInfo.remoteUrl}
)} {webAppInfo.repoInfo && (
GitHub Repository:
Name: {webAppInfo.repoInfo.name}
Full Name: {webAppInfo.repoInfo.fullName}
Description: {webAppInfo.repoInfo.description}
Stars: {webAppInfo.repoInfo.stars}
Forks: {webAppInfo.repoInfo.forks}
Open Issues: {webAppInfo.repoInfo.openIssues}
Default Branch: {webAppInfo.repoInfo.defaultBranch}
Last Update: {webAppInfo.repoInfo.lastUpdate}
Owner: {webAppInfo.repoInfo.owner.login}
Avatar URL: {webAppInfo.repoInfo.owner.avatarUrl}
)}
) : (
{loading.webAppInfo ? 'Loading webapp information...' : 'No webapp information available'}
)}
{/* Error Check */}

Error Check

Checks for:
  • Unhandled JavaScript errors
  • Unhandled Promise rejections
  • Runtime exceptions
  • Network errors
Last Check: {loading.errors ? 'Checking...' : errorLog.lastCheck ? `Last checked ${new Date(errorLog.lastCheck).toLocaleString()} (${errorLog.errors.length} errors found)` : 'Click to check for errors'}
{errorLog.errors.length > 0 && (
Recent Errors:
{errorLog.errors.slice(0, 3).map((error, index) => (
{error.type === 'error' && `${error.message} (${error.filename}:${error.lineNumber})`} {error.type === 'unhandledRejection' && `Unhandled Promise Rejection: ${error.reason}`} {error.type === 'networkError' && `Network Error: Failed to load ${error.resource}`}
))} {errorLog.errors.length > 3 && (
And {errorLog.errors.length - 3} more errors...
)}
)}
); }