diff --git a/README.md b/README.md index b5bbbcdf..3f8092b1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Chat with AI to build React apps instantly. An example app made by the [Firecrawl](https://firecrawl.dev/?ref=open-lovable-github) team. For a complete cloud solution, check out [Lovable.dev](https://lovable.dev/) ❤️. -Open Lovable Demo +Open Lovable Demo ## Setup @@ -64,4 +64,4 @@ Open [http://localhost:3000](http://localhost:3000) ## License -MIT \ No newline at end of file +MIT diff --git a/app/api/create-zip/route.ts b/app/api/create-zip/route.ts index 91418c43..fd3c3ed0 100644 --- a/app/api/create-zip/route.ts +++ b/app/api/create-zip/route.ts @@ -2,68 +2,294 @@ import { NextResponse } from 'next/server'; declare global { var activeSandbox: any; + var activeSandboxProvider: any; } export async function POST() { try { - if (!global.activeSandbox) { - return NextResponse.json({ - success: false, - error: 'No active sandbox' + // Check both V2 provider (new) and V1 sandbox (legacy) patterns + const provider = global.activeSandboxProvider; + const sandbox = global.activeSandbox; + + if (!provider && !sandbox) { + return NextResponse.json({ + success: false, + error: 'No active sandbox' }, { status: 400 }); } - + console.log('[create-zip] Creating project zip...'); - - // Create zip file in sandbox using standard commands - const zipResult = await global.activeSandbox.runCommand({ - cmd: 'bash', - args: ['-c', `zip -r /tmp/project.zip . -x "node_modules/*" ".git/*" ".next/*" "dist/*" "build/*" "*.log"`] - }); - - if (zipResult.exitCode !== 0) { - const error = await zipResult.stderr(); - throw new Error(`Failed to create zip: ${error}`); - } - - const sizeResult = await global.activeSandbox.runCommand({ - cmd: 'bash', - args: ['-c', `ls -la /tmp/project.zip | awk '{print $5}'`] - }); - - const fileSize = await sizeResult.stdout(); - console.log(`[create-zip] Created project.zip (${fileSize.trim()} bytes)`); - - // Read the zip file and convert to base64 - const readResult = await global.activeSandbox.runCommand({ - cmd: 'base64', - args: ['/tmp/project.zip'] - }); - - if (readResult.exitCode !== 0) { - const error = await readResult.stderr(); - throw new Error(`Failed to read zip file: ${error}`); + + // Detect provider type + const isE2B = provider && provider.constructor.name === 'E2BProvider'; + const isVercel = provider && provider.constructor.name === 'VercelProvider'; + const isV1Sandbox = !provider && sandbox; + + console.log('[create-zip] Provider type:', { isE2B, isVercel, isV1Sandbox }); + + if (isE2B && provider.sandbox) { + // E2B Provider - use Python code execution to avoid command parsing issues + try { + console.log('[create-zip] Using E2B Python-based zip creation'); + + // Create zip using Python's zipfile module + const zipCreationResult = await provider.sandbox.runCode(` +import zipfile +import os +import base64 +from pathlib import Path + +# Change to app directory +os.chdir('/home/user/app') + +# Create zip file +zip_path = '/tmp/project.zip' +exclude_patterns = ['node_modules', '.git', '.next', 'dist', 'build', '*.log', '__pycache__', '*.pyc'] + +def should_exclude(path): + path_str = str(path) + for pattern in exclude_patterns: + if pattern in path_str: + return True + if pattern.startswith('*') and path_str.endswith(pattern[1:]): + return True + return False + +with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + for root, dirs, files in os.walk('.'): + # Filter out excluded directories + dirs[:] = [d for d in dirs if not should_exclude(d)] + + for file in files: + file_path = os.path.join(root, file) + if not should_exclude(file_path): + # Add file to zip with relative path + arcname = os.path.relpath(file_path, '.') + zipf.write(file_path, arcname) + +# Get file size +file_size = os.path.getsize(zip_path) +print(f"ZIP_SIZE:{file_size}") + +# Read and encode to base64 +with open(zip_path, 'rb') as f: + zip_content = f.read() + base64_content = base64.b64encode(zip_content).decode('utf-8') + print(f"BASE64_START:{base64_content}:BASE64_END") + `); + + // Parse the output to extract base64 content + const output = zipCreationResult.logs.stdout.join('\n'); + + // Extract file size + const sizeMatch = output.match(/ZIP_SIZE:(\d+)/); + const fileSize = sizeMatch ? sizeMatch[1] : 'unknown'; + console.log(`[create-zip] Created project.zip (${fileSize} bytes)`); + + // Extract base64 content (using [\s\S] instead of 's' flag for compatibility) + const base64Match = output.match(/BASE64_START:([\s\S]*?):BASE64_END/); + if (!base64Match) { + throw new Error('Failed to extract base64 content from Python output'); + } + + const base64Content = base64Match[1].trim(); + + // Create a data URL for download + const dataUrl = `data:application/zip;base64,${base64Content}`; + + return NextResponse.json({ + success: true, + dataUrl, + fileName: 'e2b-sandbox-project.zip', + message: 'Zip file created successfully' + }); + + } catch (error) { + console.error('[create-zip] E2B Provider error:', error); + throw error; + } + + } else if (isVercel && provider) { + // Vercel Provider - use correct working directory + try { + console.log('[create-zip] Using Vercel Provider with /vercel/sandbox path'); + + // Install zip utility using dnf package manager with sudo + console.log('[create-zip] Installing zip utility...'); + const installResult = await provider.sandbox.runCommand({ + cmd: 'dnf', + args: ['install', '-y', 'zip'], + sudo: true + }); + + // Create zip file + const zipResult = await provider.sandbox.runCommand({ + cmd: 'zip', + args: ['-r', '/tmp/project.zip', '.', '-x', 'node_modules/*', '.git/*', '.next/*', 'dist/*', 'build/*', '*.log'], + cwd: '/vercel/sandbox' + }); + + // Handle stdout and stderr - they might be functions in Vercel SDK + let stderr = ''; + try { + if (typeof zipResult.stderr === 'function') { + stderr = await zipResult.stderr(); + } else { + stderr = zipResult.stderr || ''; + } + } catch (e) { + stderr = ''; + } + + if (zipResult.exitCode !== 0) { + throw new Error(`Failed to create zip: ${stderr}`); + } + + const sizeResult = await provider.sandbox.runCommand({ + cmd: 'sh', + args: ['-c', 'ls -la /tmp/project.zip | awk \'{print $5}\''] + }); + + let fileSize = ''; + try { + if (typeof sizeResult.stdout === 'function') { + fileSize = (await sizeResult.stdout()).trim(); + } else { + fileSize = (sizeResult.stdout || '').trim(); + } + } catch (e) { + fileSize = 'unknown'; + } + console.log(`[create-zip] Created project.zip (${fileSize} bytes)`); + + // Read the zip file and convert to base64 + const readResult = await provider.sandbox.runCommand({ + cmd: 'base64', + args: ['/tmp/project.zip'] + }); + + let readStderr = ''; + try { + if (typeof readResult.stderr === 'function') { + readStderr = await readResult.stderr(); + } else { + readStderr = readResult.stderr || ''; + } + } catch (e) { + readStderr = ''; + } + + if (readResult.exitCode !== 0) { + throw new Error(`Failed to read zip file: ${readStderr}`); + } + + let base64Content = ''; + try { + if (typeof readResult.stdout === 'function') { + base64Content = (await readResult.stdout()).trim(); + } else { + base64Content = (readResult.stdout || '').trim(); + } + } catch (e) { + throw new Error('Failed to get base64 content from command result'); + } + + // Create a data URL for download + const dataUrl = `data:application/zip;base64,${base64Content}`; + + return NextResponse.json({ + success: true, + dataUrl, + fileName: 'vercel-sandbox-project.zip', + message: 'Zip file created successfully' + }); + + } catch (error) { + console.error('[create-zip] Vercel Provider error:', error); + throw error; + } + + } else if (isV1Sandbox) { + // V1 Sandbox pattern - uses object with cmd/args (legacy) + try { + const zipResult = await sandbox.runCommand({ + cmd: 'bash', + args: ['-c', `zip -r /tmp/project.zip . -x "node_modules/*" ".git/*" ".next/*" "dist/*" "build/*" "*.log"`] + }); + + // Handle potential function-based stdout/stderr (Vercel SDK pattern) + const exitCode = zipResult.exitCode; + let stderr = ''; + + if (typeof zipResult.stderr === 'function') { + stderr = await zipResult.stderr(); + } else { + stderr = zipResult.stderr || ''; + } + + if (exitCode !== 0) { + throw new Error(`Failed to create zip: ${stderr}`); + } + + const sizeResult = await sandbox.runCommand({ + cmd: 'bash', + args: ['-c', `ls -la /tmp/project.zip | awk '{print $5}'`] + }); + + let fileSize = ''; + if (typeof sizeResult.stdout === 'function') { + fileSize = await sizeResult.stdout(); + } else { + fileSize = sizeResult.stdout || ''; + } + console.log(`[create-zip] Created project.zip (${fileSize.trim()} bytes)`); + + // Read the zip file and convert to base64 + const readResult = await sandbox.runCommand({ + cmd: 'base64', + args: ['/tmp/project.zip'] + }); + + if (readResult.exitCode !== 0) { + let error = ''; + if (typeof readResult.stderr === 'function') { + error = await readResult.stderr(); + } else { + error = readResult.stderr || 'Unknown error'; + } + throw new Error(`Failed to read zip file: ${error}`); + } + + let base64Content = ''; + if (typeof readResult.stdout === 'function') { + base64Content = (await readResult.stdout()).trim(); + } else { + base64Content = (readResult.stdout || '').trim(); + } + + // Create a data URL for download + const dataUrl = `data:application/zip;base64,${base64Content}`; + + return NextResponse.json({ + success: true, + dataUrl, + fileName: 'vercel-sandbox-project.zip', + message: 'Zip file created successfully' + }); + + } catch (error) { + console.error('[create-zip] V1 Sandbox error:', error); + throw error; + } } - - const base64Content = (await readResult.stdout()).trim(); - - // Create a data URL for download - const dataUrl = `data:application/zip;base64,${base64Content}`; - - return NextResponse.json({ - success: true, - dataUrl, - fileName: 'vercel-sandbox-project.zip', - message: 'Zip file created successfully' - }); - + } catch (error) { console.error('[create-zip] Error:', error); return NextResponse.json( - { - success: false, - error: (error as Error).message - }, + { + success: false, + error: (error as Error).message + }, { status: 500 } ); } diff --git a/app/api/generate-ai-code-stream/route.ts b/app/api/generate-ai-code-stream/route.ts index 6a8f6aac..9929d9b7 100644 --- a/app/api/generate-ai-code-stream/route.ts +++ b/app/api/generate-ai-code-stream/route.ts @@ -44,6 +44,11 @@ const openai = createOpenAI({ baseURL: isUsingAIGateway ? aiGatewayBaseURL : process.env.OPENAI_BASE_URL, }); +const aiStupidLevel = createOpenAI({ + apiKey: process.env.AI_STUPID_LEVEL_API_KEY, + baseURL: 'https://aistupidlevel.info/v1', +}); + // Helper function to analyze user preferences from conversation history function analyzeUserPreferences(messages: ConversationMessage[]): { commonPatterns: string[]; @@ -1216,11 +1221,13 @@ MORPH FAST APPLY MODE (EDIT-ONLY): const isAnthropic = model.startsWith('anthropic/'); const isGoogle = model.startsWith('google/'); const isOpenAI = model.startsWith('openai/'); + const isAIStupidLevel = model.startsWith('aistupidlevel/'); const isKimiGroq = model === 'moonshotai/kimi-k2-instruct-0905'; const modelProvider = isAnthropic ? anthropic : (isOpenAI ? openai : (isGoogle ? googleGenerativeAI : - (isKimiGroq ? groq : groq))); + (isAIStupidLevel ? aiStupidLevel : + (isKimiGroq ? groq : groq)))); // Fix model name transformation for different providers let actualModel: string; @@ -1228,6 +1235,9 @@ MORPH FAST APPLY MODE (EDIT-ONLY): actualModel = model.replace('anthropic/', ''); } else if (isOpenAI) { actualModel = model.replace('openai/', ''); + } else if (isAIStupidLevel) { + // AI Stupid Level uses the full model string + actualModel = model.replace('aistupidlevel/', ''); } else if (isKimiGroq) { // Kimi on Groq - use full model string actualModel = 'moonshotai/kimi-k2-instruct-0905'; @@ -1238,7 +1248,7 @@ MORPH FAST APPLY MODE (EDIT-ONLY): actualModel = model; } - console.log(`[generate-ai-code-stream] Using provider: ${isAnthropic ? 'Anthropic' : isGoogle ? 'Google' : isOpenAI ? 'OpenAI' : 'Groq'}, model: ${actualModel}`); + console.log(`[generate-ai-code-stream] Using provider: ${isAnthropic ? 'Anthropic' : isGoogle ? 'Google' : isOpenAI ? 'OpenAI' : isAIStupidLevel ? 'AI Stupid Level' : 'Groq'}, model: ${actualModel}`); console.log(`[generate-ai-code-stream] AI Gateway enabled: ${isUsingAIGateway}`); console.log(`[generate-ai-code-stream] Model string: ${model}`); @@ -1893,4 +1903,4 @@ Provide the complete file content without any truncation. Include all necessary error: (error as Error).message }, { status: 500 }); } -} \ No newline at end of file +} diff --git a/config/app.config.ts b/config/app.config.ts index b1a21e8e..ef4f9391 100644 --- a/config/app.config.ts +++ b/config/app.config.ts @@ -58,7 +58,13 @@ export const appConfig = { 'openai/gpt-5', 'moonshotai/kimi-k2-instruct-0905', 'anthropic/claude-sonnet-4-20250514', - 'google/gemini-3-pro-preview' + 'google/gemini-2.0-flash-exp', + 'aistupidlevel/auto', + 'aistupidlevel/auto-coding', + 'aistupidlevel/auto-reasoning', + 'aistupidlevel/auto-creative', + 'aistupidlevel/auto-fastest', + 'aistupidlevel/auto-cheapest' ], // Model display names @@ -66,7 +72,13 @@ export const appConfig = { 'openai/gpt-5': 'GPT-5', 'moonshotai/kimi-k2-instruct-0905': 'Kimi K2 (Groq)', 'anthropic/claude-sonnet-4-20250514': 'Sonnet 4', - 'google/gemini-3-pro-preview': 'Gemini 3 Pro (Preview)' + 'google/gemini-2.0-flash-exp': 'Gemini 2.0 Flash (Experimental)', + 'aistupidlevel/auto': 'AI Stupid Level (Auto)', + 'aistupidlevel/auto-coding': 'AI Stupid Level (Coding)', + 'aistupidlevel/auto-reasoning': 'AI Stupid Level (Reasoning)', + 'aistupidlevel/auto-creative': 'AI Stupid Level (Creative)', + 'aistupidlevel/auto-fastest': 'AI Stupid Level (Fastest)', + 'aistupidlevel/auto-cheapest': 'AI Stupid Level (Cheapest)' } as Record, // Model API configuration @@ -193,4 +205,4 @@ export function getConfigValue(path: string): any { return path.split('.').reduce((obj, key) => obj?.[key], appConfig as any); } -export default appConfig; \ No newline at end of file +export default appConfig; diff --git a/package.json b/package.json index 3fcb8be9..ad083896 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,7 @@ "dev": "next dev --turbopack", "build": "next build", "start": "next start", - "lint": "next lint", - "test:api": "node tests/api-endpoints.test.js", - "test:code": "node tests/code-execution.test.js", - "test:all": "npm run test:integration && npm run test:api && npm run test:code" + "lint": "next lint" }, "dependencies": { "@ai-sdk/anthropic": "^2.0.1",