From f92a94555631760568e17a7564a1e53917bd0478 Mon Sep 17 00:00:00 2001 From: "AJ (@techfren)" Date: Fri, 27 Jun 2025 14:05:26 +0000 Subject: [PATCH] Remove all performance monitoring code and processes - Removed performance monitoring utilities (PerformanceMonitor class, measurePerformance function) - Removed debouncing hooks (useDebounce, useDebouncedCallback) - Removed lazy loading component (LazyLoad) - Removed performance metrics UI component (PerformanceMetrics) - Removed rate limiting logic and RateLimiter service - Removed performance documentation (PERFORMANCE_OPTIMIZATIONS.md) - Simplified next.config.ts by removing webpack optimizations - Removed throttling delays in API generation calls - Removed performance.now() timing measurements - Removed performance-related keyboard shortcuts - Cleaned up performance-related comments and documentation - Removed backup file (original-results.tsx) The application now focuses on core functionality without performance monitoring overhead. --- README.md | 1 - nextjs-web-app/PERFORMANCE_OPTIMIZATIONS.md | 109 --- nextjs-web-app/next.config.ts | 37 -- nextjs-web-app/src/app/globals.css | 19 +- nextjs-web-app/src/app/page.tsx | 31 +- nextjs-web-app/src/app/results/page.tsx | 47 +- .../src/components/CodePreviewPanel.tsx | 62 +- .../DevTools/PerformanceMetrics.tsx | 276 -------- .../src/components/DevTools/PromptInput.tsx | 16 +- .../src/components/KeyboardShortcuts.tsx | 9 +- nextjs-web-app/src/components/LazyLoad.tsx | 61 -- nextjs-web-app/src/hooks/useDebounce.ts | 50 -- nextjs-web-app/src/services/rateLimiter.ts | 56 -- nextjs-web-app/src/utils/performance.ts | 163 ----- original-results.tsx | 621 ------------------ 15 files changed, 39 insertions(+), 1519 deletions(-) delete mode 100644 nextjs-web-app/PERFORMANCE_OPTIMIZATIONS.md delete mode 100644 nextjs-web-app/src/components/DevTools/PerformanceMetrics.tsx delete mode 100644 nextjs-web-app/src/components/LazyLoad.tsx delete mode 100644 nextjs-web-app/src/hooks/useDebounce.ts delete mode 100644 nextjs-web-app/src/services/rateLimiter.ts delete mode 100644 nextjs-web-app/src/utils/performance.ts delete mode 100644 original-results.tsx diff --git a/README.md b/README.md index 4677b96..0bc15ff 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ The purpose of Chaos Coder is to accelerate the development process by providing - Real-time code preview for each variation - Interactive interface with theme toggling - Voice input support for hands-free prompting -- Performance metrics for generation times - Keyboard shortcuts for quick access to tools ## Tech Stack diff --git a/nextjs-web-app/PERFORMANCE_OPTIMIZATIONS.md b/nextjs-web-app/PERFORMANCE_OPTIMIZATIONS.md deleted file mode 100644 index 295df76..0000000 --- a/nextjs-web-app/PERFORMANCE_OPTIMIZATIONS.md +++ /dev/null @@ -1,109 +0,0 @@ -# Performance Optimizations - -This document outlines the performance optimizations implemented to reduce resource usage and improve the application's efficiency. - -## Issues Identified and Fixed - -### 1. Memory Leaks -- **RateLimiter**: Fixed `setInterval` that was never cleared, causing memory accumulation -- **VoiceInput**: Added proper cleanup for speech recognition and timeouts -- **AnimatedHero**: Ensured timeout cleanup in useEffect - -### 2. Excessive Re-renders -- **CodePreviewPanel**: Added `React.memo` and `useCallback` to prevent unnecessary re-renders -- **AppTile**: Memoized component and callbacks -- **PerformanceMetrics**: Added memoization for expensive calculations -- **Results Page**: Optimized state updates and API calls with throttling - -### 3. Resource-Intensive Operations -- **API Calls**: Added throttling between requests (100ms delay) to prevent overwhelming the system -- **Monaco Editor**: Lazy loaded to reduce initial bundle size -- **Bundle Optimization**: Configured webpack to split chunks for better caching - -### 4. CSS Animations -- **Aurora Background**: Added hardware acceleration with `transform3d` and `will-change` -- **Reduced Motion**: Added support for users who prefer reduced motion -- **Performance**: Optimized animation properties for better GPU utilization - -## New Features Added - -### 1. Debouncing (`src/hooks/useDebounce.ts`) -- Custom hooks for debouncing values and callbacks -- Prevents excessive API calls and function executions - -### 2. Lazy Loading (`src/components/LazyLoad.tsx`) -- Intersection Observer-based lazy loading -- Reduces initial render load -- Configurable thresholds and margins - -### 3. Bundle Optimization (`next.config.ts`) -- Code splitting for vendor libraries -- Separate chunks for heavy dependencies (Monaco Editor, Framer Motion) -- Image optimization with WebP/AVIF formats -- Response compression - -## Performance Improvements - -### Before Optimizations -- Multiple simultaneous API calls causing system overload -- Memory leaks from uncleaned timers and event listeners -- Large initial bundle size with Monaco Editor -- Continuous animations consuming CPU/GPU resources -- Unnecessary re-renders causing UI lag - -### After Optimizations -- Throttled API calls with 100ms delays -- Proper cleanup of all timers and event listeners -- Lazy-loaded Monaco Editor reducing initial bundle by ~2MB -- Hardware-accelerated animations with reduced motion support -- Memoized components preventing unnecessary re-renders - -## Usage Examples - -### Debouncing Usage -```typescript -import { useDebounce, useDebouncedCallback } from '@/hooks/useDebounce'; - -// Debounce a value -const debouncedValue = useDebounce(inputValue, 300); - -// Debounce a callback -const debouncedCallback = useDebouncedCallback(handleSearch, 300); -``` - -### Lazy Loading Usage -```typescript -import LazyLoad from '@/components/LazyLoad'; - -}> - - -``` - -## Browser DevTools Recommendations - -1. **Performance Tab**: Monitor for long tasks (>50ms) -2. **Memory Tab**: Check for memory leaks and heap growth -3. **Network Tab**: Verify bundle sizes and loading times -4. **Lighthouse**: Run regular performance audits - -## Best Practices Implemented - -1. **Component Memoization**: Use `React.memo` for expensive components -2. **Callback Optimization**: Use `useCallback` for event handlers -3. **Lazy Loading**: Load heavy components only when needed -4. **Bundle Splitting**: Separate vendor and feature code -5. **Animation Optimization**: Use CSS transforms and hardware acceleration -6. **Memory Management**: Always cleanup timers, observers, and event listeners -7. **API Throttling**: Prevent overwhelming backend services -8. **Accessibility**: Support for reduced motion preferences - - - -## Future Optimizations - -1. **Service Worker**: Cache API responses and static assets -2. **Virtual Scrolling**: For large lists of generated apps -3. **Image Optimization**: Implement next/image for better loading -4. **CDN**: Serve static assets from CDN -5. **Database Optimization**: Implement proper caching strategies diff --git a/nextjs-web-app/next.config.ts b/nextjs-web-app/next.config.ts index 0b3a97e..5e91004 100644 --- a/nextjs-web-app/next.config.ts +++ b/nextjs-web-app/next.config.ts @@ -12,44 +12,7 @@ const nextConfig: NextConfig = { // !! WARN !! ignoreBuildErrors: true, }, - // Performance optimizations - experimental: { - optimizePackageImports: ['framer-motion', 'react-icons'], - }, - // Bundle optimization - webpack: (config, { dev, isServer }) => { - // Optimize bundle size in production - if (!dev && !isServer) { - config.optimization.splitChunks = { - chunks: 'all', - cacheGroups: { - vendor: { - test: /[\\/]node_modules[\\/]/, - name: 'vendors', - chunks: 'all', - }, - framerMotion: { - test: /[\\/]node_modules[\\/]framer-motion[\\/]/, - name: 'framer-motion', - chunks: 'all', - }, - monaco: { - test: /[\\/]node_modules[\\/]@monaco-editor[\\/]/, - name: 'monaco-editor', - chunks: 'all', - }, - }, - }; - } - return config; - }, - // Compress responses - compress: true, - // Enable static optimization - trailingSlash: false, - // Optimize images images: { - formats: ['image/webp', 'image/avif'], domains: [ 'lh3.googleusercontent.com', // Google user avatars 'avatars.githubusercontent.com', // GitHub avatars (in case you add GitHub auth) diff --git a/nextjs-web-app/src/app/globals.css b/nextjs-web-app/src/app/globals.css index 6cd6551..2d6405d 100644 --- a/nextjs-web-app/src/app/globals.css +++ b/nextjs-web-app/src/app/globals.css @@ -31,7 +31,7 @@ } } -/* Aurora animation - optimized for performance */ +/* Aurora animation */ @keyframes aurora { from { background-position: 50% 50%, 50% 50%; @@ -43,23 +43,6 @@ .animate-aurora { animation: aurora 60s linear infinite; - /* Use will-change to optimize for animation */ - will-change: background-position; - /* Use transform3d to enable hardware acceleration */ - transform: translate3d(0, 0, 0); -} - -/* Reduce motion for users who prefer it */ -@media (prefers-reduced-motion: reduce) { - .animate-aurora { - animation: none; - } - - * { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } } /* Base styles */ diff --git a/nextjs-web-app/src/app/page.tsx b/nextjs-web-app/src/app/page.tsx index 00977c8..081c17d 100644 --- a/nextjs-web-app/src/app/page.tsx +++ b/nextjs-web-app/src/app/page.tsx @@ -163,37 +163,10 @@ export default function Home() { setIsLoading(true); try { - // Make a test request to check rate limit before redirecting - const response = await fetch("/api/generate", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - prompt: prompt.substring(0, 50), // Just send a small part of the prompt for the check - variation: "rate-limit-check", - framework: "none", - }), - }); - - if (response.status === 429) { - // Rate limit exceeded - setShowSignupModal(true); - setIsLoading(false); - return; - } - - const data = await response.json(); - if (data.error === "rate_limit_exceeded") { - setShowSignupModal(true); - setIsLoading(false); - return; - } - - // If no rate limit issues, proceed to results page + // Navigate directly to results page router.push(`/results?prompt=${encodeURIComponent(prompt)}&numGenerations=${numGenerations}`); } catch (error) { - console.error("Error checking rate limit:", error); - // Still try to navigate even if there was an error checking rate limit - router.push(`/results?prompt=${encodeURIComponent(prompt)}&numGenerations=${numGenerations}`); + console.error("Error navigating to results:", error); } finally { setIsLoading(false); } diff --git a/nextjs-web-app/src/app/results/page.tsx b/nextjs-web-app/src/app/results/page.tsx index ba10ba2..164023c 100644 --- a/nextjs-web-app/src/app/results/page.tsx +++ b/nextjs-web-app/src/app/results/page.tsx @@ -11,7 +11,7 @@ import { BrowserContainer } from "@/components/ui/browser-container"; import { useTheme } from "@/context/ThemeContext"; import ThemeToggle from "@/components/ThemeToggle"; import PromptInput from "@/components/DevTools/PromptInput"; -import PerformanceMetrics from "@/components/DevTools/PerformanceMetrics"; + import VoiceInput from "@/components/DevTools/VoiceInput"; import FullscreenPreview from "@/components/FullscreenPreview"; import MockDeployButton from "@/components/MockDeployButton"; @@ -95,10 +95,7 @@ function ResultsContent() { new Array(numGenerations).fill("") ); const [isPromptOpen, setIsPromptOpen] = useState(false); - const [isMetricsOpen, setIsMetricsOpen] = useState(false); - const [generationTimes, setGenerationTimes] = useState<{ - [key: number]: number; - }>({}); + const [isVoiceEnabled, setIsVoiceEnabled] = useState(true); const [showSignupModal, setShowSignupModal] = useState(false); const [isFullscreenOpen, setIsFullscreenOpen] = useState(false); @@ -122,7 +119,6 @@ function ResultsContent() { }, []); const generateApp = useCallback(async (index: number, promptText: string) => { - const startTime = performance.now(); try { const framework = getFramework(appTitles[index]); @@ -136,21 +132,12 @@ function ResultsContent() { }), }); - if (response.status === 429) { - // Show signup modal for rate limit - setShowSignupModal(true); - throw new Error("Rate limit exceeded. Please create an account to continue."); - } - if (!response.ok) { throw new Error(`Failed to generate app ${index + 1}`); } const data = await response.json(); - if (data.error === "rate_limit_exceeded") { - setShowSignupModal(true); - throw new Error("Rate limit exceeded. Please create an account to continue."); - } else if (data.error) { + if (data.error) { throw new Error(data.error); } @@ -166,11 +153,7 @@ function ResultsContent() { return newResults; }); - const endTime = performance.now(); - setGenerationTimes((prev) => ({ - ...prev, - [index]: (endTime - startTime) / 1000, // Convert to seconds - })); + } catch (err) { setError( err instanceof Error ? err.message : "Failed to generate applications" @@ -226,18 +209,10 @@ function ResultsContent() { return; } - // Generate apps with throttling to prevent overwhelming the system - const generateWithThrottle = async () => { - for (let i = 0; i < numGenerations; i++) { - generateApp(i, prompt); - // Add small delay between requests to reduce system load - if (i < numGenerations - 1) { - await new Promise(resolve => setTimeout(resolve, 100)); - } - } - }; - - generateWithThrottle(); + // Generate all apps + for (let i = 0; i < numGenerations; i++) { + generateApp(i, prompt); + } }, [searchParams, generateApp, numGenerations]); return ( @@ -460,11 +435,7 @@ function ResultsContent() { isUpdateMode={true} currentCode={editedResults[selectedAppIndex]} /> - setIsMetricsOpen(false)} - generationTimes={generationTimes} - /> + {isVoiceEnabled && ( handleVoiceInput(text)} theme={theme} /> )} diff --git a/nextjs-web-app/src/components/CodePreviewPanel.tsx b/nextjs-web-app/src/components/CodePreviewPanel.tsx index e42cf7e..c628a49 100644 --- a/nextjs-web-app/src/components/CodePreviewPanel.tsx +++ b/nextjs-web-app/src/components/CodePreviewPanel.tsx @@ -3,9 +3,8 @@ import { useEffect, useState, useCallback, memo, lazy, Suspense } from 'react'; import { motion } from 'framer-motion'; import { FaExpand } from 'react-icons/fa'; -import LazyLoad from './LazyLoad'; -// Lazy load Monaco Editor to reduce initial bundle size + const Editor = lazy(() => import('@monaco-editor/react')); interface CodePreviewPanelProps { @@ -108,42 +107,31 @@ const CodePreviewPanel = memo(function CodePreviewPanel({ ) : (
- -
-
-

Loading editor...

-
-
- } - > - -
-
-

Loading editor...

-
+ +
+
+

Loading editor...

- }> - -
- + + }> + +
)} diff --git a/nextjs-web-app/src/components/DevTools/PerformanceMetrics.tsx b/nextjs-web-app/src/components/DevTools/PerformanceMetrics.tsx deleted file mode 100644 index ce280f2..0000000 --- a/nextjs-web-app/src/components/DevTools/PerformanceMetrics.tsx +++ /dev/null @@ -1,276 +0,0 @@ -"use client"; - -import { motion, AnimatePresence } from "framer-motion"; -import { FaKeyboard, FaTimes, FaChartLine } from "react-icons/fa"; -import { useTheme } from "@/context/ThemeContext"; -import { memo, useMemo } from "react"; - -interface PerformanceMetricsProps { - isOpen: boolean; - onClose: () => void; - generationTimes: { [key: number]: number }; -} - -function getSpeedColor(time: number, fastestTime: number) { - const ratio = fastestTime / time; - if (ratio > 0.9) return "stroke-emerald-500"; // Very fast (90-100% of fastest) - if (ratio > 0.7) return "stroke-blue-500"; // Fast (70-90% of fastest) - if (ratio > 0.5) return "stroke-yellow-500"; // Medium (50-70% of fastest) - return "stroke-orange-500"; // Slower (< 50% of fastest) -} - -function getSpeedLabel(time: number, fastestTime: number) { - const ratio = fastestTime / time; - if (ratio > 0.9) - return { text: "Blazing Fast ⚡", color: "text-emerald-500" }; - if (ratio > 0.7) return { text: "Quick 🚀", color: "text-blue-500" }; - if (ratio > 0.5) return { text: "Good Speed 👍", color: "text-yellow-500" }; - return { text: "Standard ⚙️", color: "text-orange-500" }; -} - -const appNames = [ - "Standard Version", - "Visual Focus", - "Minimalist Version", - "Creative Approach", - "Enhanced Version", -]; - -const frameworkNames = [ - "Material UI", - "Chakra UI", - "CSS Modules", - "Styled Components + Framer Motion", - "Bulma", -]; - -const PerformanceMetrics = memo(function PerformanceMetrics({ - isOpen, - onClose, - generationTimes, -}: PerformanceMetricsProps) { - const { theme } = useTheme(); - - const { times, averageTime } = useMemo(() => { - const times = Object.values(generationTimes); - const averageTime = times.length > 0 ? times.reduce((a, b) => a + b, 0) / times.length : 0; - return { times, averageTime }; - }, [generationTimes]); - - return ( - - {isOpen && ( - -
- {theme === "dark" && ( -
- )} -
-
-
-
- -

- Performance Metrics -

-
- - Shift + P -
-
- -
- -
- {/* Average Generation Time */} -
-
- {/* Left side - Circle */} -
- - - - -
-
- {averageTime.toFixed(1)} -
-
seconds
-
-
- - {/* Right side - Stats */} -
-
- Average Generation -
Time
-
- -
- {Object.keys(generationTimes).length} Apps Generated -
- -
-
-
Fastest
-
- {Math.min(...times).toFixed(1)}s -
-
-
-
Slowest
-
- {Math.max(...times).toFixed(1)}s -
-
-
-
-
-
- - {/* Individual Times */} -
- {[ - { - time: 5.9, - name: "Standard Version", - framework: "Material UI", - color: "emerald", - }, - { - time: 7.6, - name: "Visual Focus", - framework: "Chakra UI", - color: "blue", - }, - { - time: 7.9, - name: "Minimalist Version", - framework: "CSS Modules", - color: "blue", - }, - { - time: 5.7, - name: "Creative Approach", - framework: "Styled Components + Framer Motion", - color: "emerald", - }, - { - time: 9.7, - name: "Enhanced Version", - framework: "Bulma", - color: "yellow", - }, - ].map((item) => { - const speed = getSpeedLabel(item.time, Math.min(...times)); - return ( -
-
-
- - - - -
-
- {item.time}s -
-
-
-
-

- {item.name} -

-

- {item.framework} -

-

- {speed.text} -

-
-
-
- ); - })} -
-
-
- - )} - - ); -}); - -export default PerformanceMetrics; diff --git a/nextjs-web-app/src/components/DevTools/PromptInput.tsx b/nextjs-web-app/src/components/DevTools/PromptInput.tsx index d3395f4..21f446e 100644 --- a/nextjs-web-app/src/components/DevTools/PromptInput.tsx +++ b/nextjs-web-app/src/components/DevTools/PromptInput.tsx @@ -30,23 +30,9 @@ export default function PromptInput({ const result = await onSubmit(prompt, isUpdateMode, chaosMode); setPrompt(""); - // If onSubmit returns a value with an error property - if (result && typeof result === 'object' && 'error' in result) { - if (result.error === 'rate_limit_exceeded') { - setShowSignupModal(true); - return; - } - } + } catch (error: any) { console.error("Error submitting prompt:", error); - - // Check for rate limit error in the caught exception - if (error?.error === 'rate_limit_exceeded' || - (error.response && error.response.status === 429) || - (error.message && error.message.includes('rate limit'))) { - setShowSignupModal(true); - return; - } } } }; diff --git a/nextjs-web-app/src/components/KeyboardShortcuts.tsx b/nextjs-web-app/src/components/KeyboardShortcuts.tsx index b5035f5..51d4331 100644 --- a/nextjs-web-app/src/components/KeyboardShortcuts.tsx +++ b/nextjs-web-app/src/components/KeyboardShortcuts.tsx @@ -5,11 +5,9 @@ import { atom, useAtom } from 'jotai'; // Global state atoms export const isPromptOpenAtom = atom(false); -export const isMetricsOpenAtom = atom(false); export default function KeyboardShortcuts() { const [isPromptOpen, setIsPromptOpen] = useAtom(isPromptOpenAtom); - const [isMetricsOpen, setIsMetricsOpen] = useAtom(isMetricsOpenAtom); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -19,11 +17,6 @@ export default function KeyboardShortcuts() { e.preventDefault(); setIsPromptOpen(prev => !prev); break; - case 'p': - case 'x': - e.preventDefault(); - setIsMetricsOpen(prev => !prev); - break; } } }; @@ -31,7 +24,7 @@ export default function KeyboardShortcuts() { // Add event listener to the document instead of window document.addEventListener('keydown', handleKeyDown, true); return () => document.removeEventListener('keydown', handleKeyDown, true); - }, [setIsPromptOpen, setIsMetricsOpen]); + }, [setIsPromptOpen]); return null; } diff --git a/nextjs-web-app/src/components/LazyLoad.tsx b/nextjs-web-app/src/components/LazyLoad.tsx deleted file mode 100644 index e209383..0000000 --- a/nextjs-web-app/src/components/LazyLoad.tsx +++ /dev/null @@ -1,61 +0,0 @@ -'use client'; - -import { useState, useEffect, useRef, ReactNode } from 'react'; - -interface LazyLoadProps { - children: ReactNode; - fallback?: ReactNode; - rootMargin?: string; - threshold?: number; - once?: boolean; -} - -/** - * LazyLoad component that only renders children when they come into view - * Helps reduce initial bundle size and improve performance - */ -export default function LazyLoad({ - children, - fallback =
, - rootMargin = '50px', - threshold = 0.1, - once = true, -}: LazyLoadProps) { - const [isInView, setIsInView] = useState(false); - const [hasBeenInView, setHasBeenInView] = useState(false); - const elementRef = useRef(null); - - useEffect(() => { - const element = elementRef.current; - if (!element) return; - - const observer = new IntersectionObserver( - ([entry]) => { - const inView = entry.isIntersecting; - setIsInView(inView); - - if (inView && once) { - setHasBeenInView(true); - } - }, - { - rootMargin, - threshold, - } - ); - - observer.observe(element); - - return () => { - observer.unobserve(element); - }; - }, [rootMargin, threshold, once]); - - const shouldRender = once ? (isInView || hasBeenInView) : isInView; - - return ( -
- {shouldRender ? children : fallback} -
- ); -} diff --git a/nextjs-web-app/src/hooks/useDebounce.ts b/nextjs-web-app/src/hooks/useDebounce.ts deleted file mode 100644 index 3178cc1..0000000 --- a/nextjs-web-app/src/hooks/useDebounce.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { useState, useEffect } from 'react'; - -/** - * Custom hook for debouncing values to prevent excessive API calls - * @param value - The value to debounce - * @param delay - The delay in milliseconds - * @returns The debounced value - */ -export function useDebounce(value: T, delay: number): T { - const [debouncedValue, setDebouncedValue] = useState(value); - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); - - return () => { - clearTimeout(handler); - }; - }, [value, delay]); - - return debouncedValue; -} - -/** - * Custom hook for debouncing callbacks to prevent excessive function calls - * @param callback - The callback function to debounce - * @param delay - The delay in milliseconds - * @param deps - Dependencies array for the callback - * @returns The debounced callback function - */ -export function useDebouncedCallback any>( - callback: T, - delay: number, - deps: React.DependencyList = [] -): T { - const [debouncedCallback, setDebouncedCallback] = useState(() => callback); - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedCallback(() => callback); - }, delay); - - return () => { - clearTimeout(handler); - }; - }, [callback, delay, ...deps]); - - return debouncedCallback; -} diff --git a/nextjs-web-app/src/services/rateLimiter.ts b/nextjs-web-app/src/services/rateLimiter.ts deleted file mode 100644 index c52c572..0000000 --- a/nextjs-web-app/src/services/rateLimiter.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Simple rate limiter service for tracking API usage - * In production, this should be replaced with Redis or another persistent store - */ -export class RateLimiter { - private static instance: RateLimiter; - private counts = new Map(); - private intervalId: NodeJS.Timeout | null = null; - - constructor() { - // Reset counts every hour - store interval ID for cleanup - this.intervalId = setInterval(() => this.counts.clear(), 60 * 60 * 1000); - } - - /** - * Clean up resources when the instance is no longer needed - */ - destroy() { - if (this.intervalId) { - clearInterval(this.intervalId); - this.intervalId = null; - } - this.counts.clear(); - } - - static getInstance() { - if (!RateLimiter.instance) { - RateLimiter.instance = new RateLimiter(); - } - return RateLimiter.instance; - } - - /** - * Check if a key has exceeded its rate limit - * @param key - Unique identifier (usually IP address) - * @param limit - Maximum number of requests allowed - * @returns boolean - true if under limit, false if exceeded - */ - check(key: string, limit: number): boolean { - const count = this.counts.get(key) || 0; - if (count >= limit) return false; - this.counts.set(key, count + 1); - return true; - } - - /** - * Get remaining requests for a key - * @param key - Unique identifier (usually IP address) - * @param limit - Maximum number of requests allowed - * @returns number - Remaining requests - */ - getRemainingRequests(key: string, limit: number): number { - const count = this.counts.get(key) || 0; - return Math.max(0, limit - count); - } -} diff --git a/nextjs-web-app/src/utils/performance.ts b/nextjs-web-app/src/utils/performance.ts deleted file mode 100644 index 56ec1c2..0000000 --- a/nextjs-web-app/src/utils/performance.ts +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Performance monitoring utilities - */ - -export class PerformanceMonitor { - private static instance: PerformanceMonitor; - private metrics: Map = new Map(); - private observers: PerformanceObserver[] = []; - - private constructor() { - this.initializeObservers(); - } - - static getInstance(): PerformanceMonitor { - if (!PerformanceMonitor.instance) { - PerformanceMonitor.instance = new PerformanceMonitor(); - } - return PerformanceMonitor.instance; - } - - private initializeObservers() { - if (typeof window === 'undefined') return; - - try { - // Monitor long tasks (tasks that block the main thread for >50ms) - const longTaskObserver = new PerformanceObserver((list) => { - for (const entry of list.getEntries()) { - console.warn(`Long task detected: ${entry.duration}ms`, entry); - this.recordMetric('longTasks', entry.duration); - } - }); - longTaskObserver.observe({ entryTypes: ['longtask'] }); - this.observers.push(longTaskObserver); - - // Monitor layout shifts - const layoutShiftObserver = new PerformanceObserver((list) => { - for (const entry of list.getEntries()) { - if ((entry as any).value > 0.1) { - console.warn(`Layout shift detected: ${(entry as any).value}`, entry); - this.recordMetric('layoutShifts', (entry as any).value); - } - } - }); - layoutShiftObserver.observe({ entryTypes: ['layout-shift'] }); - this.observers.push(layoutShiftObserver); - - // Monitor largest contentful paint - const lcpObserver = new PerformanceObserver((list) => { - for (const entry of list.getEntries()) { - this.recordMetric('lcp', entry.startTime); - } - }); - lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] }); - this.observers.push(lcpObserver); - - } catch (error) { - console.warn('Performance observers not supported:', error); - } - } - - recordMetric(name: string, value: number) { - if (!this.metrics.has(name)) { - this.metrics.set(name, []); - } - this.metrics.get(name)!.push(value); - - // Keep only last 100 measurements to prevent memory leaks - const values = this.metrics.get(name)!; - if (values.length > 100) { - values.shift(); - } - } - - getMetrics(name: string): number[] { - return this.metrics.get(name) || []; - } - - getAverageMetric(name: string): number { - const values = this.getMetrics(name); - return values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0; - } - - clearMetrics() { - this.metrics.clear(); - } - - destroy() { - this.observers.forEach(observer => observer.disconnect()); - this.observers = []; - this.metrics.clear(); - } -} - -/** - * Measure the performance of a function - */ -export function measurePerformance( - name: string, - fn: () => T | Promise -): T | Promise { - const start = performance.now(); - const result = fn(); - - if (result instanceof Promise) { - return result.finally(() => { - const end = performance.now(); - PerformanceMonitor.getInstance().recordMetric(name, end - start); - console.log(`${name} took ${end - start}ms`); - }); - } else { - const end = performance.now(); - PerformanceMonitor.getInstance().recordMetric(name, end - start); - console.log(`${name} took ${end - start}ms`); - return result; - } -} - -/** - * Throttle function calls to prevent excessive execution - */ -export function throttle any>( - func: T, - limit: number -): T { - let inThrottle: boolean; - return ((...args: any[]) => { - if (!inThrottle) { - func.apply(this, args); - inThrottle = true; - setTimeout(() => inThrottle = false, limit); - } - }) as T; -} - -/** - * Check if the device has limited resources - */ -export function isLowEndDevice(): boolean { - if (typeof navigator === 'undefined') return false; - - // Check for device memory (Chrome only) - const deviceMemory = (navigator as any).deviceMemory; - if (deviceMemory && deviceMemory <= 4) return true; - - // Check for hardware concurrency (number of CPU cores) - if (navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 2) return true; - - // Check for connection type - const connection = (navigator as any).connection; - if (connection && (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g')) { - return true; - } - - return false; -} - -/** - * Get memory usage information (Chrome only) - */ -export function getMemoryUsage(): any { - if (typeof window === 'undefined') return null; - return (performance as any).memory || null; -} diff --git a/original-results.tsx b/original-results.tsx deleted file mode 100644 index caa5fb9..0000000 --- a/original-results.tsx +++ /dev/null @@ -1,621 +0,0 @@ -"use client"; - -import { useSearchParams } from "next/navigation"; -import { useState, useEffect, Suspense, useCallback, useMemo } from "react"; -import Link from "next/link"; -import { motion } from "framer-motion"; -import { AuroraBackground } from "@/components/ui/aurora-background"; -import AppTile from "@/components/AppTile"; -import CodePreviewPanel from "@/components/CodePreviewPanel"; -import { BrowserContainer } from "@/components/ui/browser-container"; -import { useTheme } from "@/context/ThemeContext"; -import ThemeToggle from "@/components/ThemeToggle"; -import PromptInput from "@/components/DevTools/PromptInput"; -import PerformanceMetrics from "@/components/DevTools/PerformanceMetrics"; -import VoiceInput from "@/components/DevTools/VoiceInput"; -import FullscreenPreview from "@/components/FullscreenPreview"; -import MockDeployButton from "@/components/MockDeployButton"; -import { SignupModal } from "@/components/SignupModal"; -import styled from "styled-components"; - -const LoadingContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - min-height: 400px; - width: 100%; - gap: 20px; - color: #9ca3af; -`; - -const LoadingTitle = styled.div` - font-size: 24px; - margin-bottom: 10px; -`; - -const LoadingBar = styled(motion.div)` - width: 100%; - max-width: 500px; - height: 8px; - background: rgba(75, 85, 99, 0.3); - border-radius: 4px; - overflow: hidden; - position: relative; -`; - -const LoadingProgress = styled(motion.div)` - height: 100%; - background: #4b5563; - border-radius: 4px; -`; - -const ShortLoadingBar = styled(LoadingBar)` - max-width: 300px; -`; - -// Move constants outside component to prevent recreation on every render -const NUM_APPS = 9; // Single variable to control number of apps - -const variations = [ - "", - "Make it visually appealing and use a different framework than the other versions.", - "Focus on simplicity and performance. Use minimal dependencies.", - "Add some creative features that might not be explicitly mentioned in the prompt.", - "Create an enhanced version with additional features and modern design patterns.", - "Build a version with accessibility and internationalization features in mind.", - "Create a version optimized for mobile devices with responsive design.", - "Build a version with advanced animations and interactive elements.", - "Create a version with data visualization capabilities.", - "Build a version with offline functionality and progressive web app features.", -]; - -const appTitles = [ - "Standard Version", - "Visual Focus", - "Minimalist Version", - "Creative Approach", - "Enhanced Version", - "Accessible Version", - "Mobile Optimized", - "Interactive Version", - "Data Visualization", - "Progressive Web App", -]; - -// Wrapper component that uses searchParams -function ResultsContent() { - const searchParams = useSearchParams(); - const [loadingStates, setLoadingStates] = useState( - new Array(NUM_APPS).fill(true) - ); - const [results, setResults] = useState(new Array(NUM_APPS).fill("")); - const [error, setError] = useState(null); - const [selectedAppIndex, setSelectedAppIndex] = useState(0); - const [editedResults, setEditedResults] = useState( - new Array(NUM_APPS).fill("") - ); - const [isPromptOpen, setIsPromptOpen] = useState(false); - const [isMetricsOpen, setIsMetricsOpen] = useState(false); - const [generationTimes, setGenerationTimes] = useState<{ - [key: number]: number; - }>({}); - const [isVoiceEnabled, setIsVoiceEnabled] = useState(true); - const [showSignupModal, setShowSignupModal] = useState(false); - const [isFullscreenOpen, setIsFullscreenOpen] = useState(false); - const { theme } = useTheme(); - - // Handle keyboard shortcuts - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if (e.shiftKey) { - switch (e.key.toLowerCase()) { - case "l": - e.preventDefault(); - setIsPromptOpen((prev) => !prev); - break; - case "p": - case "x": - e.preventDefault(); - setIsMetricsOpen((prev) => !prev); - break; - } - } - }; - - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, []); - - const getFramework = useCallback((title: string) => { - switch (title) { - case "Standard Version": return "bootstrap"; - case "Visual Focus": return "materialize"; - case "Minimalist Version": return "pure"; - case "Creative Approach": return "tailwind"; - case "Accessible Version": return "foundation"; - default: return "Bulma"; - } - }, []); - - const generateApp = useCallback(async (index: number, promptText: string) => { - const startTime = performance.now(); - try { - const framework = getFramework(appTitles[index]); - - const response = await fetch("/api/generate", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - prompt: promptText, - variation: variations[index], - framework, - }), - }); - - if (response.status === 429) { - // Show signup modal for rate limit - setShowSignupModal(true); - throw new Error("Rate limit exceeded. Please create an account to continue."); - } - - if (!response.ok) { - throw new Error(`Failed to generate app ${index + 1}`); - } - - const data = await response.json(); - if (data.error === "rate_limit_exceeded") { - setShowSignupModal(true); - throw new Error("Rate limit exceeded. Please create an account to continue."); - } else if (data.error) { - throw new Error(data.error); - } - - setResults((prev) => { - const newResults = [...prev]; - newResults[index] = data.code; - return newResults; - }); - - setEditedResults((prev) => { - const newResults = [...prev]; - newResults[index] = data.code; - return newResults; - }); - - const endTime = performance.now(); - setGenerationTimes((prev) => ({ - ...prev, - [index]: (endTime - startTime) / 1000, // Convert to seconds - })); - } catch (err) { - setError( - err instanceof Error ? err.message : "Failed to generate applications" - ); - } finally { - setLoadingStates((prev) => { - const newStates = [...prev]; - newStates[index] = false; - return newStates; - }); - } - }, [getFramework]); // Remove appTitles and variations from dependencies since they're now constants - - const handleNewPrompt = async (prompt: string, isUpdate: boolean = false, chaosMode: boolean = false) => { - if (isUpdate) { - if (chaosMode) { - // Update all apps in chaos mode - setLoadingStates(new Array(NUM_APPS).fill(true)); - - try { - // Create an array of promises for all apps - const updatePromises = appTitles.map(async (title, index) => { - const framework = getFramework(title); - - const response = await fetch("/api/generate", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - prompt, - existingCode: editedResults[index], - framework, - isUpdate: true, - }), - }); - - if (!response.ok) { - throw new Error(`Failed to update app ${index + 1}`); - } - - const data = await response.json(); - if (data.error) { - throw new Error(data.error); - } - - return { index, code: data.code }; - }); - - // Wait for all updates to complete - const results = await Promise.all(updatePromises); - - // Update all results at once - setEditedResults((prev) => { - const newResults = [...prev]; - results.forEach(result => { - newResults[result.index] = result.code; - }); - return newResults; - }); - } catch (err) { - setError( - err instanceof Error ? err.message : "Failed to update applications in chaos mode" - ); - } finally { - setLoadingStates(new Array(NUM_APPS).fill(false)); - } - } else { - // Update only the selected app (original behavior) - setLoadingStates((prev) => { - const newStates = [...prev]; - newStates[selectedAppIndex] = true; - return newStates; - }); - - try { - const framework = getFramework(appTitles[selectedAppIndex]); - - const response = await fetch("/api/generate", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - prompt, - existingCode: editedResults[selectedAppIndex], - framework, - isUpdate: true, - }), - }); - - if (!response.ok) { - throw new Error(`Failed to update app ${selectedAppIndex + 1}`); - } - - const data = await response.json(); - if (data.error) { - throw new Error(data.error); - } - - setEditedResults((prev) => { - const newResults = [...prev]; - newResults[selectedAppIndex] = data.code; - return newResults; - }); - } catch (err) { - setError( - err instanceof Error ? err.message : "Failed to update application" - ); - } finally { - setLoadingStates((prev) => { - const newStates = [...prev]; - newStates[selectedAppIndex] = false; - return newStates; - }); - } - } - } else { - setLoadingStates(new Array(NUM_APPS).fill(true)); - setResults(new Array(NUM_APPS).fill("")); - setEditedResults(new Array(NUM_APPS).fill("")); - setGenerationTimes({}); - Promise.all(variations.map((_, index) => generateApp(index, prompt))); - } - }; - - const handleVoiceInput = (text: string) => { - handleNewPrompt(text, true, false); // Default to single mode for voice input - }; - - useEffect(() => { - const prompt = searchParams.get("prompt"); - if (!prompt) { - setError("No prompt provided"); - setLoadingStates(new Array(NUM_APPS).fill(false)); - return; - } - - // Generate apps with throttling to prevent overwhelming the system - const generateWithThrottle = async () => { - for (let i = 0; i < variations.length; i++) { - generateApp(i, prompt); - // Add small delay between requests to reduce system load - if (i < variations.length - 1) { - await new Promise(resolve => setTimeout(resolve, 100)); - } - } - }; - - generateWithThrottle(); - }, [searchParams, generateApp]); // Remove variations from dependencies since it's now a constant - - const handleCodeChange = (newCode: string) => { - const newResults = [...editedResults]; - newResults[selectedAppIndex] = newCode; - setEditedResults(newResults); - }; - - // Function to handle clicking on a tile - const handleTileClick = (index: number) => { - setSelectedAppIndex(index); - // Scroll to the detailed view (now at the top) - setTimeout(() => { - document.getElementById('detailed-view')?.scrollIntoView({ - behavior: 'smooth', - block: 'start' - }); - }, 100); - }; - - // Function to handle fullscreen toggle - const handleMaximize = () => { - setIsFullscreenOpen(true); - }; - - const handleCloseFullscreen = () => { - setIsFullscreenOpen(false); - }; - - return ( - - {showSignupModal && ( - setShowSignupModal(false)} - /> - )} - -
-
- - - Chaos Coder - - -
- - - ← Back to Prompt - -
- - {/* */} -
- - {error && ( -
-

- {error} -

-
- )} - - {results.length > 0 && ( -
- {/* Expanded view of selected app - moved to top */} - -

- {appTitles[selectedAppIndex]} - Detailed View -

-
- - {loadingStates[selectedAppIndex] ? ( - - Generating - - - - - - - - ) : ( -
- - } - /> -
- )} -
-
-
- - {/* Grid of all app previews - moved below detailed view */} -
- {appTitles.map((title, index) => ( - handleTileClick(index)} - > -
- { - setSelectedAppIndex(index); - handleMaximize(); - }} - > - {loadingStates[index] ? ( - - Generating - - - - - - - - ) : ( - { - const newResults = [...editedResults]; - newResults[index] = newCode; - setEditedResults(newResults); - }} - isLoading={loadingStates[index]} - theme={theme} - showControls={false} - /> - )} - -
-
- ))} -
-
- )} -
-
- - setIsMetricsOpen(false)} - generationTimes={generationTimes} - /> - {isVoiceEnabled && ( - handleVoiceInput(text)} theme={theme} /> - )} - -
- ); -} - -// Main component with Suspense boundary -export default function Results() { - return ( - -
-

Loading...

-
-
-
- }> - - - ); -}