diff --git a/app/components/settings/debug/DebugTab.tsx b/app/components/settings/debug/DebugTab.tsx index fc36101..be919c8 100644 --- a/app/components/settings/debug/DebugTab.tsx +++ b/app/components/settings/debug/DebugTab.tsx @@ -447,6 +447,8 @@ export default function DebugTab() { const appData = (await appResponse.json()) as Omit; const gitData = (await gitResponse.json()) as GitInfo; + console.log('Git Info Response:', gitData); // Add logging to debug + setWebAppInfo({ ...appData, gitInfo: gitData, @@ -1084,7 +1086,7 @@ export default function DebugTab() { <>
-
+
Repository: {webAppInfo.gitInfo.github.currentRepo.fullName} diff --git a/app/components/settings/task-manager/TaskManagerTab.tsx b/app/components/settings/task-manager/TaskManagerTab.tsx index 6cf56d1..936ee90 100644 --- a/app/components/settings/task-manager/TaskManagerTab.tsx +++ b/app/components/settings/task-manager/TaskManagerTab.tsx @@ -24,22 +24,66 @@ interface BatteryManager extends EventTarget { } interface SystemMetrics { - cpu: number; + cpu: { + usage: number; + cores: number[]; + temperature?: number; + frequency?: number; + }; memory: { used: number; total: number; percentage: number; + heap: { + used: number; + total: number; + limit: number; + }; + cache?: number; }; uptime: number; battery?: { level: number; charging: boolean; timeRemaining?: number; + temperature?: number; + cycles?: number; + health?: number; }; network: { downlink: number; + uplink?: number; latency: number; type: string; + activeConnections?: number; + bytesReceived: number; + bytesSent: number; + }; + performance: { + fps: number; + pageLoad: number; + domReady: number; + resources: { + total: number; + size: number; + loadTime: number; + }; + timing: { + ttfb: number; + fcp: number; + lcp: number; + }; + }; + storage: { + total: number; + used: number; + free: number; + type: string; + }; + health: { + score: number; + issues: string[]; + suggestions: string[]; }; } @@ -57,6 +101,26 @@ interface EnergySavings { estimatedEnergySaved: number; // in mWh (milliwatt-hours) } +interface PowerProfile { + name: string; + description: string; + settings: { + updateInterval: number; + enableAnimations: boolean; + backgroundProcessing: boolean; + networkThrottling: boolean; + }; +} + +interface PerformanceAlert { + type: 'warning' | 'error' | 'info'; + message: string; + timestamp: number; + metric: string; + threshold: number; + value: number; +} + declare global { interface Navigator { getBattery(): Promise; @@ -88,12 +152,61 @@ const ENERGY_COSTS = { rendering: 1, // mW per render }; +const PERFORMANCE_THRESHOLDS = { + cpu: { warning: 70, critical: 90 }, + memory: { warning: 80, critical: 95 }, + fps: { warning: 30, critical: 15 }, + loadTime: { warning: 3000, critical: 5000 }, +}; + +const POWER_PROFILES: PowerProfile[] = [ + { + name: 'Performance', + description: 'Maximum performance, higher power consumption', + settings: { + updateInterval: 1000, + enableAnimations: true, + backgroundProcessing: true, + networkThrottling: false, + }, + }, + { + name: 'Balanced', + description: 'Balance between performance and power saving', + settings: { + updateInterval: 2000, + enableAnimations: true, + backgroundProcessing: true, + networkThrottling: false, + }, + }, + { + name: 'Power Saver', + description: 'Maximum power saving, reduced performance', + settings: { + updateInterval: 5000, + enableAnimations: false, + backgroundProcessing: false, + networkThrottling: true, + }, + }, +]; + export default function TaskManagerTab() { const [metrics, setMetrics] = useState({ - cpu: 0, - memory: { used: 0, total: 0, percentage: 0 }, + cpu: { usage: 0, cores: [] }, + memory: { used: 0, total: 0, percentage: 0, heap: { used: 0, total: 0, limit: 0 } }, uptime: 0, - network: { downlink: 0, latency: 0, type: 'unknown' }, + network: { downlink: 0, latency: 0, type: 'unknown', bytesReceived: 0, bytesSent: 0 }, + performance: { + fps: 0, + pageLoad: 0, + domReady: 0, + resources: { total: 0, size: 0, loadTime: 0 }, + timing: { ttfb: 0, fcp: 0, lcp: 0 }, + }, + storage: { total: 0, used: 0, free: 0, type: 'unknown' }, + health: { score: 0, issues: [], suggestions: [] }, }); const [metricsHistory, setMetricsHistory] = useState({ timestamps: [], @@ -121,6 +234,8 @@ export default function TaskManagerTab() { }); const saverModeStartTime = useRef(null); + const [selectedProfile, setSelectedProfile] = useState(POWER_PROFILES[1]); // Default to Balanced + const [alerts, setAlerts] = useState([]); // Handle energy saver mode changes const handleEnergySaverChange = (checked: boolean) => { @@ -181,7 +296,135 @@ export default function TaskManagerTab() { return () => clearInterval(interval); }, [updateEnergySavings]); - // Update metrics + // Get detailed performance metrics + const getPerformanceMetrics = async (): Promise> => { + try { + // Get FPS + const fps = await measureFrameRate(); + + // Get page load metrics + const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; + const pageLoad = navigation.loadEventEnd - navigation.startTime; + const domReady = navigation.domContentLoadedEventEnd - navigation.startTime; + + // Get resource metrics + const resources = performance.getEntriesByType('resource'); + const resourceMetrics = { + total: resources.length, + size: resources.reduce((total, r) => total + (r as any).transferSize || 0, 0), + loadTime: Math.max(...resources.map((r) => r.duration)), + }; + + // Get Web Vitals + const ttfb = navigation.responseStart - navigation.requestStart; + const paintEntries = performance.getEntriesByType('paint'); + const fcp = paintEntries.find((entry) => entry.name === 'first-contentful-paint')?.startTime || 0; + const lcpEntry = await getLargestContentfulPaint(); + + return { + fps, + pageLoad, + domReady, + resources: resourceMetrics, + timing: { + ttfb, + fcp, + lcp: lcpEntry?.startTime || 0, + }, + }; + } catch (error) { + console.error('Failed to get performance metrics:', error); + return {}; + } + }; + + // Measure frame rate + const measureFrameRate = async (): Promise => { + return new Promise((resolve) => { + const frameCount = { value: 0 }; + const startTime = performance.now(); + + const countFrame = (time: number) => { + frameCount.value++; + + if (time - startTime >= 1000) { + resolve(Math.round((frameCount.value * 1000) / (time - startTime))); + } else { + requestAnimationFrame(countFrame); + } + }; + + requestAnimationFrame(countFrame); + }); + }; + + // Get Largest Contentful Paint + const getLargestContentfulPaint = async (): Promise => { + return new Promise((resolve) => { + new PerformanceObserver((list) => { + const entries = list.getEntries(); + resolve(entries[entries.length - 1]); + }).observe({ entryTypes: ['largest-contentful-paint'] }); + + // Resolve after 3 seconds if no LCP entry is found + setTimeout(() => resolve(undefined), 3000); + }); + }; + + // Analyze system health + const analyzeSystemHealth = (currentMetrics: SystemMetrics): SystemMetrics['health'] => { + const issues: string[] = []; + const suggestions: string[] = []; + let score = 100; + + // CPU analysis + if (currentMetrics.cpu.usage > PERFORMANCE_THRESHOLDS.cpu.critical) { + score -= 30; + issues.push('Critical CPU usage'); + suggestions.push('Consider closing resource-intensive applications'); + } else if (currentMetrics.cpu.usage > PERFORMANCE_THRESHOLDS.cpu.warning) { + score -= 15; + issues.push('High CPU usage'); + suggestions.push('Monitor system processes for unusual activity'); + } + + // Memory analysis + if (currentMetrics.memory.percentage > PERFORMANCE_THRESHOLDS.memory.critical) { + score -= 30; + issues.push('Critical memory usage'); + suggestions.push('Close unused applications to free up memory'); + } else if (currentMetrics.memory.percentage > PERFORMANCE_THRESHOLDS.memory.warning) { + score -= 15; + issues.push('High memory usage'); + suggestions.push('Consider freeing up memory by closing background applications'); + } + + // Performance analysis + if (currentMetrics.performance.fps < PERFORMANCE_THRESHOLDS.fps.critical) { + score -= 20; + issues.push('Very low frame rate'); + suggestions.push('Disable animations or switch to power saver mode'); + } else if (currentMetrics.performance.fps < PERFORMANCE_THRESHOLDS.fps.warning) { + score -= 10; + issues.push('Low frame rate'); + suggestions.push('Consider reducing visual effects'); + } + + // Battery analysis + if (currentMetrics.battery && !currentMetrics.battery.charging && currentMetrics.battery.level < 20) { + score -= 10; + issues.push('Low battery'); + suggestions.push('Connect to power source or enable power saver mode'); + } + + return { + score: Math.max(0, score), + issues, + suggestions, + }; + }; + + // Update metrics with enhanced data const updateMetrics = async () => { try { // Get memory info using Performance API @@ -216,36 +459,62 @@ export default function TaskManagerTab() { (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection; const networkInfo = { downlink: connection?.downlink || 0, + uplink: connection?.uplink, latency: connection?.rtt || 0, type: connection?.type || 'unknown', + activeConnections: connection?.activeConnections, + bytesReceived: connection?.bytesReceived || 0, + bytesSent: connection?.bytesSent || 0, }; - const newMetrics = { - cpu: cpuUsage, + // Get enhanced performance metrics + const performanceMetrics = await getPerformanceMetrics(); + + const metrics: SystemMetrics = { + cpu: { usage: cpuUsage, cores: [], temperature: undefined, frequency: undefined }, memory: { used: Math.round(usedMem), total: Math.round(totalMem), percentage: Math.round(memPercentage), + heap: { + used: Math.round(usedMem), + total: Math.round(totalMem), + limit: Math.round(totalMem), + }, }, uptime: performance.now() / 1000, battery: batteryInfo, network: networkInfo, + performance: performanceMetrics as SystemMetrics['performance'], + storage: { + total: 0, + used: 0, + free: 0, + type: 'unknown', + }, + health: { score: 0, issues: [], suggestions: [] }, }; - setMetrics(newMetrics); + // Analyze system health + metrics.health = analyzeSystemHealth(metrics); + + // Check for alerts + checkPerformanceAlerts(metrics); + + setMetrics(metrics); // Update metrics history const now = new Date().toLocaleTimeString(); setMetricsHistory((prev) => { const timestamps = [...prev.timestamps, now].slice(-MAX_HISTORY_POINTS); - const cpu = [...prev.cpu, newMetrics.cpu].slice(-MAX_HISTORY_POINTS); - const memory = [...prev.memory, newMetrics.memory.percentage].slice(-MAX_HISTORY_POINTS); + const cpu = [...prev.cpu, metrics.cpu.usage].slice(-MAX_HISTORY_POINTS); + const memory = [...prev.memory, metrics.memory.percentage].slice(-MAX_HISTORY_POINTS); const battery = [...prev.battery, batteryInfo?.level || 0].slice(-MAX_HISTORY_POINTS); const network = [...prev.network, networkInfo.downlink].slice(-MAX_HISTORY_POINTS); return { timestamps, cpu, memory, battery, network }; }); - } catch (error: unknown) { + } catch (error) { console.error('Failed to update system metrics:', error); } }; @@ -300,6 +569,8 @@ export default function TaskManagerTab() { downlink: connection.downlink || 0, latency: connection.rtt || 0, type: connection.type || 'unknown', + bytesReceived: connection.bytesReceived || 0, + bytesSent: connection.bytesSent || 0, }, })); }; @@ -427,12 +698,60 @@ export default function TaskManagerTab() { return () => clearInterval(batteryCheckInterval); }, [autoEnergySaver]); + // Check for performance alerts + const checkPerformanceAlerts = (currentMetrics: SystemMetrics) => { + const newAlerts: PerformanceAlert[] = []; + + // CPU alert + if (currentMetrics.cpu.usage > PERFORMANCE_THRESHOLDS.cpu.critical) { + newAlerts.push({ + type: 'error', + message: 'Critical CPU usage detected', + timestamp: Date.now(), + metric: 'cpu', + threshold: PERFORMANCE_THRESHOLDS.cpu.critical, + value: currentMetrics.cpu.usage, + }); + } + + // Memory alert + if (currentMetrics.memory.percentage > PERFORMANCE_THRESHOLDS.memory.critical) { + newAlerts.push({ + type: 'error', + message: 'Critical memory usage detected', + timestamp: Date.now(), + metric: 'memory', + threshold: PERFORMANCE_THRESHOLDS.memory.critical, + value: currentMetrics.memory.percentage, + }); + } + + // Performance alert + if (currentMetrics.performance.fps < PERFORMANCE_THRESHOLDS.fps.critical) { + newAlerts.push({ + type: 'warning', + message: 'Very low frame rate detected', + timestamp: Date.now(), + metric: 'fps', + threshold: PERFORMANCE_THRESHOLDS.fps.critical, + value: currentMetrics.performance.fps, + }); + } + + if (newAlerts.length > 0) { + setAlerts((prev) => [...prev, ...newAlerts]); + newAlerts.forEach((alert) => { + toast.warning(alert.message); + }); + } + }; + return (
- {/* System Metrics */} + {/* Power Profile Selection */}
-

System Metrics

+

Power Management

Active}
+
+
{selectedProfile.description}
+
+ {/* System Health Score */} +
+

System Health

+
+
+
+ Health Score + = 80, + 'text-yellow-500': metrics.health.score >= 60 && metrics.health.score < 80, + 'text-red-500': metrics.health.score < 60, + })} + > + {metrics.health.score}% + +
+ {metrics.health.issues.length > 0 && ( +
+
Issues:
+
    + {metrics.health.issues.map((issue, index) => ( +
  • +
    + {issue} +
  • + ))} +
+
+ )} + {metrics.health.suggestions.length > 0 && ( +
+
Suggestions:
+
    + {metrics.health.suggestions.map((suggestion, index) => ( +
  • +
    + {suggestion} +
  • + ))} +
+
+ )} +
+
+
+ + {/* System Metrics */} +
+

System Metrics

{/* CPU Usage */}
CPU Usage - - {Math.round(metrics.cpu)}% + + {Math.round(metrics.cpu.usage)}%
{renderUsageGraph(metricsHistory.cpu, 'CPU', '#9333ea')} + {metrics.cpu.temperature && ( +
+ Temperature: {metrics.cpu.temperature}°C +
+ )} + {metrics.cpu.frequency && ( +
+ Frequency: {(metrics.cpu.frequency / 1000).toFixed(1)} GHz +
+ )}
{/* Memory Usage */} @@ -487,28 +886,42 @@ export default function TaskManagerTab() {
{renderUsageGraph(metricsHistory.memory, 'Memory', '#2563eb')} +
+ Used: {formatBytes(metrics.memory.used)} +
+
Total: {formatBytes(metrics.memory.total)}
+
+ Heap: {formatBytes(metrics.memory.heap.used)} / {formatBytes(metrics.memory.heap.total)} +
- {/* Battery */} - {metrics.battery && ( -
-
- Battery -
- {metrics.battery.charging &&
} - 20 ? 'text-bolt-elements-textPrimary' : 'text-red-500', - )} - > - {Math.round(metrics.battery.level)}% - -
-
- {renderUsageGraph(metricsHistory.battery, 'Battery', '#22c55e')} + {/* Performance */} +
+
+ Performance + = PERFORMANCE_THRESHOLDS.fps.warning, + })} + > + {Math.round(metrics.performance.fps)} FPS +
- )} +
+ Page Load: {(metrics.performance.pageLoad / 1000).toFixed(2)}s +
+
+ DOM Ready: {(metrics.performance.domReady / 1000).toFixed(2)}s +
+
+ TTFB: {(metrics.performance.timing.ttfb / 1000).toFixed(2)}s +
+
+ Resources: {metrics.performance.resources.total} ({formatBytes(metrics.performance.resources.size)}) +
+
{/* Network */}
@@ -519,9 +932,118 @@ export default function TaskManagerTab() {
{renderUsageGraph(metricsHistory.network, 'Network', '#f59e0b')} +
Type: {metrics.network.type}
+
Latency: {metrics.network.latency}ms
+
+ Received: {formatBytes(metrics.network.bytesReceived)} +
+
+ Sent: {formatBytes(metrics.network.bytesSent)} +
+ {/* Battery Section */} + {metrics.battery && ( +
+
+ Battery +
+ {metrics.battery.charging &&
} + 20 ? 'text-bolt-elements-textPrimary' : 'text-red-500', + )} + > + {Math.round(metrics.battery.level)}% + +
+
+ {renderUsageGraph(metricsHistory.battery, 'Battery', '#22c55e')} + {metrics.battery.timeRemaining && ( +
+ {metrics.battery.charging ? 'Time to full: ' : 'Time remaining: '} + {formatTime(metrics.battery.timeRemaining)} +
+ )} + {metrics.battery.temperature && ( +
+ Temperature: {metrics.battery.temperature}°C +
+ )} + {metrics.battery.cycles && ( +
Charge cycles: {metrics.battery.cycles}
+ )} + {metrics.battery.health && ( +
Battery health: {metrics.battery.health}%
+ )} +
+ )} + + {/* Storage Section */} +
+
+ Storage + + {formatBytes(metrics.storage.used)} / {formatBytes(metrics.storage.total)} + +
+
+
= 0.7 && + metrics.storage.used / metrics.storage.total < 0.9, + 'bg-red-500': metrics.storage.used / metrics.storage.total >= 0.9, + })} + style={{ width: `${(metrics.storage.used / metrics.storage.total) * 100}%` }} + /> +
+
Free: {formatBytes(metrics.storage.free)}
+
Type: {metrics.storage.type}
+
+ + {/* Performance Alerts */} + {alerts.length > 0 && ( +
+
+ Recent Alerts + +
+
+ {alerts.slice(-5).map((alert, index) => ( +
+
+ {alert.message} + + {new Date(alert.timestamp).toLocaleTimeString()} + +
+ ))} +
+
+ )} + {/* Energy Savings */} {energySaverMode && (
@@ -550,3 +1072,32 @@ export default function TaskManagerTab() {
); } + +// Helper function to format bytes +const formatBytes = (bytes: number): string => { + if (bytes === 0) { + return '0 B'; + } + + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; +}; + +// Helper function to format time +const formatTime = (seconds: number): string => { + if (!isFinite(seconds) || seconds === 0) { + return 'Unknown'; + } + + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } + + return `${minutes}m`; +}; diff --git a/app/routes/api.system.git-info.ts b/app/routes/api.system.git-info.ts index cd6c8be..d6bf997 100644 --- a/app/routes/api.system.git-info.ts +++ b/app/routes/api.system.git-info.ts @@ -20,7 +20,7 @@ interface GitHubRepoInfo { const getLocalGitInfo = () => { try { return { - commitHash: execSync('git rev-parse --short HEAD').toString().trim(), + commitHash: execSync('git rev-parse HEAD').toString().trim(), branch: execSync('git rev-parse --abbrev-ref HEAD').toString().trim(), commitTime: execSync('git log -1 --format=%cd').toString().trim(), author: execSync('git log -1 --format=%an').toString().trim(), @@ -40,13 +40,42 @@ const getLocalGitInfo = () => { const getGitHubInfo = async (repoFullName: string) => { try { - const response = await fetch(`https://api.github.com/repos/${repoFullName}`); + // Add GitHub token if available + const headers: Record = { + Accept: 'application/vnd.github.v3+json', + }; + + const githubToken = process.env.GITHUB_TOKEN; + + if (githubToken) { + headers.Authorization = `token ${githubToken}`; + } + + console.log('Fetching GitHub info for:', repoFullName); // Debug log + + const response = await fetch(`https://api.github.com/repos/${repoFullName}`, { + headers, + }); if (!response.ok) { + console.error('GitHub API error:', { + status: response.status, + statusText: response.statusText, + repoFullName, + }); + + // If we get a 404, try the main repo as fallback + if (response.status === 404 && repoFullName !== 'stackblitz-labs/bolt.diy') { + return getGitHubInfo('stackblitz-labs/bolt.diy'); + } + throw new Error(`GitHub API error: ${response.statusText}`); } - return (await response.json()) as GitHubRepoInfo; + const data = await response.json(); + console.log('GitHub API response:', data); // Debug log + + return data as GitHubRepoInfo; } catch (error) { console.error('Failed to get GitHub info:', error); return null; @@ -55,6 +84,7 @@ const getGitHubInfo = async (repoFullName: string) => { export const loader: LoaderFunction = async ({ request: _request }) => { const localInfo = getLocalGitInfo(); + console.log('Local git info:', localInfo); // Debug log // If we have local info, try to get GitHub info for both our fork and upstream let githubInfo = null; @@ -68,7 +98,7 @@ export const loader: LoaderFunction = async ({ request: _request }) => { githubInfo = await getGitHubInfo('stackblitz-labs/bolt.diy'); } - return json({ + const response = { local: localInfo || { commitHash: 'unknown', branch: 'unknown', @@ -99,5 +129,10 @@ export const loader: LoaderFunction = async ({ request: _request }) => { : null, isForked: Boolean(githubInfo?.parent), timestamp: new Date().toISOString(), - }); + }; + + console.log('Final response:', response); + + // Debug log + return json(response); }; diff --git a/pages/api/system/git-info.ts b/pages/api/system/git-info.ts deleted file mode 100644 index 845e4b1..0000000 --- a/pages/api/system/git-info.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { execSync } from 'child_process'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method !== 'GET') { - return res.status(405).json({ message: 'Method not allowed' }); - } - - try { - // Get git information using git commands - const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim(); - const commitHash = execSync('git rev-parse HEAD').toString().trim(); - const commitTime = execSync('git log -1 --format=%cd').toString().trim(); - const author = execSync('git log -1 --format=%an').toString().trim(); - const email = execSync('git log -1 --format=%ae').toString().trim(); - const remoteUrl = execSync('git config --get remote.origin.url').toString().trim(); - - // Extract repo name from remote URL - const repoName = remoteUrl.split('/').pop()?.replace('.git', '') || ''; - - const gitInfo = { - local: { - commitHash, - branch, - commitTime, - author, - email, - remoteUrl, - repoName, - }, - }; - - return res.status(200).json(gitInfo); - } catch (error) { - console.error('Failed to get git information:', error); - return res.status(500).json({ message: 'Failed to get git information' }); - } -}