- ) : /* If no files parsed yet, show loading or raw stream */
- generationProgress.files.length === 0 && !generationProgress.currentFile ? (
- generationProgress.isThinking ? (
- // Beautiful loading state while thinking
-
-
-
-
-
-
-
-
-
AI is analyzing your request
-
{generationProgress.status || 'Preparing to generate code...'}
- ))}
-
- {/* Show remaining raw stream if there's content after the last file */}
- {!generationProgress.currentFile && generationProgress.streamedCode.length > 0 && (
-
-
-
-
- Processing...
-
-
-
-
- {(() => {
- // Show only the tail of the stream after the last file
- const lastFileEnd = generationProgress.files.length > 0
- ? generationProgress.streamedCode.lastIndexOf('') + 7
- : 0;
- let remainingContent = generationProgress.streamedCode.slice(lastFileEnd).trim();
-
- // Remove explanation tags and content
- remainingContent = remainingContent.replace(/[\s\S]*?<\/explanation>/g, '').trim();
-
- // If only whitespace or nothing left, show waiting message
- return remainingContent || 'Waiting for next file...';
- })()}
-
-
+
);
} else if (activeTab === 'preview') {
- // Show screenshot when we have one and (loading OR generating OR no sandbox yet)
- if (urlScreenshot && (loading || generationProgress.isGenerating || !sandboxData?.url || isPreparingDesign)) {
- return (
-
-IMAGE HANDLING RULES:
-- When the scraped content includes images, USE THE ORIGINAL IMAGE URLS whenever appropriate
-- Keep existing images from the scraped site (logos, product images, hero images, icons, etc.)
-- Use the actual image URLs provided in the scraped content, not placeholders
-- Only use placeholder images or generic services when no real images are available
-- For company logos and brand images, ALWAYS use the original URLs to maintain brand identity
-- If scraped data contains image URLs, include them in your img tags
-- Example: If you see "https://example.com/logo.png" in the scraped content, use that exact URL
+ {/* Chat Sidebar */}
+
+
+
Chat History
+
+
+
+
+
+
-Focus on the key sections and content, making it clean and modern while preserving visual assets.`;
-
- setGenerationProgress(prev => ({
- isGenerating: true,
- status: 'Initializing AI...',
- components: [],
- currentComponent: 0,
- streamedCode: '',
- isStreaming: true,
- isThinking: false,
- thinkingText: undefined,
- thinkingDuration: undefined,
- // Keep previous files until new ones are generated
- files: prev.files || [],
- currentFile: undefined,
- lastProcessedPosition: 0
- }));
-
- // Switch to generation tab when starting
- setActiveTab('generation');
-
- const aiResponse = await fetch('/api/generate-ai-code-stream', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- prompt: recreatePrompt,
- model: aiModel,
- context: {
- sandboxId: sandboxData?.id,
- structure: structureContent,
- conversationContext: conversationContext
- }
- })
- });
-
- if (!aiResponse.ok) {
- throw new Error(`AI generation failed: ${aiResponse.status}`);
- }
-
- const reader = aiResponse.body?.getReader();
- const decoder = new TextDecoder();
- let generatedCode = '';
- let explanation = '';
-
- if (reader) {
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
-
- const chunk = decoder.decode(value);
- const lines = chunk.split('\n');
-
- for (const line of lines) {
- if (line.startsWith('data: ')) {
- try {
- const data = JSON.parse(line.slice(6));
-
- if (data.type === 'status') {
- setGenerationProgress(prev => ({ ...prev, status: data.message }));
- } else if (data.type === 'thinking') {
- setGenerationProgress(prev => ({
- ...prev,
- isThinking: true,
- thinkingText: (prev.thinkingText || '') + data.text
- }));
- } else if (data.type === 'thinking_complete') {
- setGenerationProgress(prev => ({
- ...prev,
- isThinking: false,
- thinkingDuration: data.duration
- }));
- } else if (data.type === 'conversation') {
- // Add conversational text to chat only if it's not code
- let text = data.text || '';
-
- // Remove package tags from the text
- text = text.replace(/[^<]*<\/package>/g, '');
- text = text.replace(/[^<]*<\/packages>/g, '');
-
- // Filter out any XML tags and file content that slipped through
- if (!text.includes(' 0) {
- addChatMessage(text.trim(), 'ai');
- }
- } else if (data.type === 'stream' && data.raw) {
- setGenerationProgress(prev => ({
- ...prev,
- streamedCode: prev.streamedCode + data.text,
- lastProcessedPosition: prev.lastProcessedPosition || 0
- }));
- } else if (data.type === 'component') {
- setGenerationProgress(prev => ({
- ...prev,
- status: `Generated ${data.name}`,
- components: [...prev.components, {
- name: data.name,
- path: data.path,
- completed: true
- }],
- currentComponent: prev.currentComponent + 1
- }));
- } else if (data.type === 'complete') {
- generatedCode = data.generatedCode;
- explanation = data.explanation;
-
- // Save the last generated code
- setConversationContext(prev => ({
- ...prev,
- lastGeneratedCode: generatedCode
- }));
- }
- } catch (e) {
- console.error('Error parsing streaming data:', e);
- }
- }
- }
- }
- }
-
- setGenerationProgress(prev => ({
- ...prev,
- isGenerating: false,
- isStreaming: false,
- status: 'Generation complete!',
- isEdit: prev.isEdit
- }));
-
- if (generatedCode) {
- addChatMessage('AI recreation generated!', 'system');
-
- // Add the explanation to chat if available
- if (explanation && explanation.trim()) {
- addChatMessage(explanation, 'ai');
- }
-
- setPromptInput(generatedCode);
- // Don't show the Generated Code panel by default
- // setLeftPanelVisible(true);
-
- // Wait for sandbox creation if it's still in progress
- if (sandboxPromise) {
- addChatMessage('Waiting for sandbox to be ready...', 'system');
- try {
- await sandboxPromise;
- // Remove the waiting message
- setChatMessages(prev => prev.filter(msg => msg.content !== 'Waiting for sandbox to be ready...'));
- } catch (error: any) {
- addChatMessage('Sandbox creation failed. Cannot apply code.', 'system');
- throw error;
- }
- }
-
- // First application for cloned site should not be in edit mode
- await applyGeneratedCode(generatedCode, false);
-
- addChatMessage(
- `Successfully recreated ${url} as a modern React app${homeContextInput ? ` with your requested context: "${homeContextInput}"` : ''}! The scraped content is now in my context, so you can ask me to modify specific sections or add features based on the original site.`,
- 'ai',
- {
- scrapedUrl: url,
- scrapedContent: scrapeData,
- generatedCode: generatedCode
- }
- );
-
- setUrlInput('');
- setUrlStatus([]);
- setHomeContextInput('');
-
- // Clear generation progress and all screenshot/design states
- setGenerationProgress(prev => ({
- ...prev,
- isGenerating: false,
- isStreaming: false,
- status: 'Generation complete!'
- }));
-
- // Clear screenshot and preparing design states to prevent them from showing on next run
- setUrlScreenshot(null);
- setIsPreparingDesign(false);
- setTargetUrl('');
- setScreenshotError(null);
- setLoadingStage(null); // Clear loading stage
-
- setTimeout(() => {
- // Switch back to preview tab but keep files
- setActiveTab('preview');
- }, 1000); // Show completion briefly then switch
- } else {
- throw new Error('Failed to generate recreation');
- }
-
- } catch (error: any) {
- addChatMessage(`Failed to clone website: ${error.message}`, 'system');
- setUrlStatus([]);
- setIsPreparingDesign(false);
- // Clear all states on error
- setUrlScreenshot(null);
- setTargetUrl('');
- setScreenshotError(null);
- setLoadingStage(null);
- setGenerationProgress(prev => ({
- ...prev,
- isGenerating: false,
- isStreaming: false,
- status: '',
- // Keep files to display in sidebar
- files: prev.files
- }));
- setActiveTab('preview');
- }
- };
+ {/* Code Application Progress Overlay */}
+
+ {codeApplicationState.stage && (
+
+
+
+ )}
+
- const captureUrlScreenshot = async (url: string) => {
- setIsCapturingScreenshot(true);
- setScreenshotError(null);
- try {
- const response = await fetch('/api/scrape-screenshot', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ url })
- });
-
- const data = await response.json();
- if (data.success && data.screenshot) {
- setUrlScreenshot(data.screenshot);
- // Set preparing design state
- setIsPreparingDesign(true);
- // Store the clean URL for display
- const cleanUrl = url.replace(/^https?:\/\//i, '');
- setTargetUrl(cleanUrl);
- // Switch to preview tab to show the screenshot
- if (activeTab !== 'preview') {
- setActiveTab('preview');
- }
- } else {
- setScreenshotError(data.error || 'Failed to capture screenshot');
- }
- } catch (error) {
- console.error('Failed to capture screenshot:', error);
- setScreenshotError('Network error while capturing screenshot');
- } finally {
- setIsCapturingScreenshot(false);
- }
- };
-
- const handleHomeScreenSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- if (!homeUrlInput.trim()) return;
-
- setHomeScreenFading(true);
-
- // Clear messages and immediately show the cloning message
- setChatMessages([]);
- let displayUrl = homeUrlInput.trim();
- if (!displayUrl.match(/^https?:\/\//i)) {
- displayUrl = 'https://' + displayUrl;
- }
- // Remove protocol for cleaner display
- const cleanUrl = displayUrl.replace(/^https?:\/\//i, '');
- addChatMessage(`Starting to clone ${cleanUrl}...`, 'system');
-
- // Start creating sandbox and capturing screenshot immediately in parallel
- const sandboxPromise = !sandboxData ? createSandbox(true) : Promise.resolve();
-
- // Only capture screenshot if we don't already have a sandbox (first generation)
- // After sandbox is set up, skip the screenshot phase for faster generation
- if (!sandboxData) {
- captureUrlScreenshot(displayUrl);
- }
-
- // Set loading stage immediately before hiding home screen
- setLoadingStage('gathering');
- // Also ensure we're on preview tab to show the loading overlay
- setActiveTab('preview');
-
- setTimeout(async () => {
- setShowHomeScreen(false);
- setHomeScreenFading(false);
-
- // Wait for sandbox to be ready (if it's still creating)
- await sandboxPromise;
-
- // Now start the clone process which will stream the generation
- setUrlInput(homeUrlInput);
- setUrlOverlayVisible(false); // Make sure overlay is closed
- setUrlStatus(['Scraping website content...']);
-
- try {
- // Scrape the website
- let url = homeUrlInput.trim();
- if (!url.match(/^https?:\/\//i)) {
- url = 'https://' + url;
- }
-
- // Screenshot is already being captured in parallel above
-
- const scrapeResponse = await fetch('/api/scrape-url-enhanced', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ url })
- });
-
- if (!scrapeResponse.ok) {
- throw new Error('Failed to scrape website');
- }
-
- const scrapeData = await scrapeResponse.json();
-
- if (!scrapeData.success) {
- throw new Error(scrapeData.error || 'Failed to scrape website');
- }
-
- setUrlStatus(['Website scraped successfully!', 'Generating React app...']);
-
- // Clear preparing design state and switch to generation tab
- setIsPreparingDesign(false);
- setUrlScreenshot(null); // Clear screenshot when starting generation
- setTargetUrl(''); // Clear target URL
-
- // Update loading stage to planning
- setLoadingStage('planning');
-
- // Brief pause before switching to generation tab
- setTimeout(() => {
- setLoadingStage('generating');
- setActiveTab('generation');
- }, 1500);
-
- // Store scraped data in conversation context
- setConversationContext(prev => ({
- ...prev,
- scrapedWebsites: [...prev.scrapedWebsites, {
- url: url,
- content: scrapeData,
- timestamp: new Date()
- }],
- currentProject: `${url} Clone`
- }));
-
- const prompt = `I want to recreate the ${url} website as a complete React application based on the scraped content below.
-
-${JSON.stringify(scrapeData, null, 2)}
-
-${homeContextInput ? `ADDITIONAL CONTEXT/REQUIREMENTS FROM USER:
-${homeContextInput}
-
-Please incorporate these requirements into the design and implementation.` : ''}
-
-IMPORTANT INSTRUCTIONS:
-- Create a COMPLETE, working React application
-- Implement ALL sections and features from the original site
-- Use Tailwind CSS for all styling (no custom CSS files)
-- Make it responsive and modern
-- Ensure all text content matches the original
-- Create proper component structure
-- Make sure the app actually renders visible content
-- Create ALL components that you reference in imports
-${homeContextInput ? '- Apply the user\'s context/theme requirements throughout the application' : ''}
-
-Focus on the key sections and content, making it clean and modern.`;
-
- setGenerationProgress(prev => ({
- isGenerating: true,
- status: 'Initializing AI...',
- components: [],
- currentComponent: 0,
- streamedCode: '',
- isStreaming: true,
- isThinking: false,
- thinkingText: undefined,
- thinkingDuration: undefined,
- // Keep previous files until new ones are generated
- files: prev.files || [],
- currentFile: undefined,
- lastProcessedPosition: 0
- }));
-
- const aiResponse = await fetch('/api/generate-ai-code-stream', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- prompt,
- model: aiModel,
- context: {
- sandboxId: sandboxData?.sandboxId,
- structure: structureContent,
- conversationContext: conversationContext
- }
- })
- });
-
- if (!aiResponse.ok || !aiResponse.body) {
- throw new Error('Failed to generate code');
- }
-
- const reader = aiResponse.body.getReader();
- const decoder = new TextDecoder();
- let generatedCode = '';
- let explanation = '';
-
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
-
- const chunk = decoder.decode(value);
- const lines = chunk.split('\n');
-
- for (const line of lines) {
- if (line.startsWith('data: ')) {
- try {
- const data = JSON.parse(line.slice(6));
-
- if (data.type === 'status') {
- setGenerationProgress(prev => ({ ...prev, status: data.message }));
- } else if (data.type === 'thinking') {
- setGenerationProgress(prev => ({
- ...prev,
- isThinking: true,
- thinkingText: (prev.thinkingText || '') + data.text
- }));
- } else if (data.type === 'thinking_complete') {
- setGenerationProgress(prev => ({
- ...prev,
- isThinking: false,
- thinkingDuration: data.duration
- }));
- } else if (data.type === 'conversation') {
- // Add conversational text to chat only if it's not code
- let text = data.text || '';
-
- // Remove package tags from the text
- text = text.replace(/[^<]*<\/package>/g, '');
- text = text.replace(/[^<]*<\/packages>/g, '');
-
- // Filter out any XML tags and file content that slipped through
- if (!text.includes(' 0) {
- addChatMessage(text.trim(), 'ai');
- }
- } else if (data.type === 'stream' && data.raw) {
- setGenerationProgress(prev => {
- const newStreamedCode = prev.streamedCode + data.text;
-
- // Tab is already switched after scraping
-
- const updatedState = {
- ...prev,
- streamedCode: newStreamedCode,
- isStreaming: true,
- isThinking: false,
- status: 'Generating code...'
- };
-
- // Process complete files from the accumulated stream
- const fileRegex = /([^]*?)<\/file>/g;
- let match;
- const processedFiles = new Set(prev.files.map(f => f.path));
-
- while ((match = fileRegex.exec(newStreamedCode)) !== null) {
- const filePath = match[1];
- const fileContent = match[2];
-
- // Only add if we haven't processed this file yet
- if (!processedFiles.has(filePath)) {
- const fileExt = filePath.split('.').pop() || '';
- const fileType = fileExt === 'jsx' || fileExt === 'js' ? 'javascript' :
- fileExt === 'css' ? 'css' :
- fileExt === 'json' ? 'json' :
- fileExt === 'html' ? 'html' : 'text';
-
- // Check if file already exists
- const existingFileIndex = updatedState.files.findIndex(f => f.path === filePath);
-
- if (existingFileIndex >= 0) {
- // Update existing file and mark as edited
- updatedState.files = [
- ...updatedState.files.slice(0, existingFileIndex),
- {
- ...updatedState.files[existingFileIndex],
- content: fileContent.trim(),
- type: fileType,
- completed: true,
- edited: true
- },
- ...updatedState.files.slice(existingFileIndex + 1)
- ];
- } else {
- // Add new file
- updatedState.files = [...updatedState.files, {
- path: filePath,
- content: fileContent.trim(),
- type: fileType,
- completed: true,
- edited: false
- }];
- }
-
- // Only show file status if not in edit mode
- if (!prev.isEdit) {
- updatedState.status = `Completed ${filePath}`;
- }
- processedFiles.add(filePath);
- }
- }
-
- // Check for current file being generated (incomplete file at the end)
- const lastFileMatch = newStreamedCode.match(/([^]*?)$/);
- if (lastFileMatch && !lastFileMatch[0].includes('')) {
- const filePath = lastFileMatch[1];
- const partialContent = lastFileMatch[2];
-
- if (!processedFiles.has(filePath)) {
- const fileExt = filePath.split('.').pop() || '';
- const fileType = fileExt === 'jsx' || fileExt === 'js' ? 'javascript' :
- fileExt === 'css' ? 'css' :
- fileExt === 'json' ? 'json' :
- fileExt === 'html' ? 'html' : 'text';
-
- updatedState.currentFile = {
- path: filePath,
- content: partialContent,
- type: fileType
- };
- // Only show file status if not in edit mode
- if (!prev.isEdit) {
- updatedState.status = `Generating ${filePath}`;
- }
- }
- } else {
- updatedState.currentFile = undefined;
- }
-
- return updatedState;
- });
- } else if (data.type === 'complete') {
- generatedCode = data.generatedCode;
- explanation = data.explanation;
-
- // Save the last generated code
- setConversationContext(prev => ({
- ...prev,
- lastGeneratedCode: generatedCode
- }));
- }
- } catch (e) {
- console.error('Failed to parse SSE data:', e);
- }
- }
- }
- }
-
- setGenerationProgress(prev => ({
- ...prev,
- isGenerating: false,
- isStreaming: false,
- status: 'Generation complete!'
- }));
-
- if (generatedCode) {
- addChatMessage('AI recreation generated!', 'system');
-
- // Add the explanation to chat if available
- if (explanation && explanation.trim()) {
- addChatMessage(explanation, 'ai');
- }
-
- setPromptInput(generatedCode);
-
- // First application for cloned site should not be in edit mode
- await applyGeneratedCode(generatedCode, false);
-
- addChatMessage(
- `Successfully recreated ${url} as a modern React app${homeContextInput ? ` with your requested context: "${homeContextInput}"` : ''}! The scraped content is now in my context, so you can ask me to modify specific sections or add features based on the original site.`,
- 'ai',
- {
- scrapedUrl: url,
- scrapedContent: scrapeData,
- generatedCode: generatedCode
- }
- );
-
- setConversationContext(prev => ({
- ...prev,
- generatedComponents: [],
- appliedCode: [...prev.appliedCode, {
- files: [],
- timestamp: new Date()
- }]
- }));
- } else {
- throw new Error('Failed to generate recreation');
- }
-
- setUrlInput('');
- setUrlStatus([]);
- setHomeContextInput('');
-
- // Clear generation progress and all screenshot/design states
- setGenerationProgress(prev => ({
- ...prev,
- isGenerating: false,
- isStreaming: false,
- status: 'Generation complete!'
- }));
-
- // Clear screenshot and preparing design states to prevent them from showing on next run
- setUrlScreenshot(null);
- setIsPreparingDesign(false);
- setTargetUrl('');
- setScreenshotError(null);
- setLoadingStage(null); // Clear loading stage
-
- setTimeout(() => {
- // Switch back to preview tab but keep files
- setActiveTab('preview');
- }, 1000); // Show completion briefly then switch
- } catch (error: any) {
- addChatMessage(`Failed to clone website: ${error.message}`, 'system');
- setUrlStatus([]);
- setIsPreparingDesign(false);
- // Also clear generation progress on error
- setGenerationProgress(prev => ({
- ...prev,
- isGenerating: false,
- isStreaming: false,
- status: '',
- // Keep files to display in sidebar
- files: prev.files
- }));
- }
- }, 500);
- };
-
- return (
-
{/* Home Screen Overlay */}
- {showHomeScreen && (
-
- {/* Simple Sun Gradient Background */}
-
- {/* Main Sun - Pulsing */}
-
-
- {/* Inner Sun Core - Brighter */}
-
-
- {/* Outer Glow - Subtle */}
-
-
- {/* Giant Glowing Orb - Center Bottom */}
-
-
-
-
-
-
-
-
-
-
-
- {/* Close button on hover */}
-
-
- {/* Header */}
-
- {chatMessages.map((msg, idx) => {
- // Check if this message is from a successful generation
- const isGenerationComplete = msg.content.includes('Successfully recreated') ||
- msg.content.includes('AI recreation generated!') ||
- msg.content.includes('Code generated!');
-
- // Get the files from metadata if this is a completion message
- const completedFiles = msg.metadata?.appliedFiles || [];
-
- return (
-
-
- {(() => {
- const lastContent = generationProgress.streamedCode.slice(-1000);
- // Show the last part of the stream, starting from a complete tag if possible
- const startIndex = lastContent.indexOf('<');
- return startIndex !== -1 ? lastContent.slice(startIndex) : lastContent;
- })()}
-
-
-
-
- )}
+ />
+
- )}
-
-
-
-
-
-
-
-
- {/* Right Panel - Preview or Generation (2/3 of remaining width) */}
-
- {/* Live Code Generation Status - Moved to far right */}
- {activeTab === 'generation' && (generationProgress.isGenerating || generationProgress.files.length > 0) && (
-