@@ -12,7 +14,7 @@ Chat with AI to build React apps instantly. An example app made by the [Firecraw
```bash
git clone https://github.com/mendableai/open-lovable.git
cd open-lovable
-npm install
+pnpm install # or npm install / yarn install
```
2. **Add `.env.local`**
@@ -21,31 +23,72 @@ npm install
# Required
FIRECRAWL_API_KEY=your_firecrawl_api_key # Get from https://firecrawl.dev (Web scraping)
-# Vercel Sandbox Authentication (choose one method)
-# See: https://vercel.com/docs/vercel-sandbox#authentication
+# =================================================================
+# SANDBOX PROVIDER - Choose ONE: E2B or Vercel
+# =================================================================
-# Method 1: OIDC Token (recommended for development)
+# Optional: Specify sandbox provider (defaults to 'e2b' if not set)
+# SANDBOX_PROVIDER=e2b # or 'vercel'
+
+# -----------------------------------------------------------------
+# OPTION 1: E2B Sandbox
+# -----------------------------------------------------------------
+# Get your API key from: https://e2b.dev
+E2B_API_KEY=your_e2b_api_key
+
+# -----------------------------------------------------------------
+# OPTION 2: Vercel Sandbox
+# -----------------------------------------------------------------
+# Method 1: OIDC Token (automatic setup)
# Run `vercel link` then `vercel env pull` to get VERCEL_OIDC_TOKEN automatically
# VERCEL_OIDC_TOKEN=auto_generated_by_vercel_env_pull
-# Method 2: Personal Access Token (for production or when OIDC unavailable)
-# VERCEL_TEAM_ID=team_xxxxxxxxx # Your Vercel team ID
+# Method 2: Personal Access Token (manual setup)
+# VERCEL_TEAM_ID=team_xxxxxxxxx # Your Vercel team ID
# VERCEL_PROJECT_ID=prj_xxxxxxxxx # Your Vercel project ID
# VERCEL_TOKEN=vercel_xxxxxxxxxxxx # Personal access token from Vercel dashboard
+# See: https://vercel.com/docs/vercel-sandbox#authentication
-# Optional (need at least one AI provider)
+# =================================================================
+# AI PROVIDERS - Add at least one
+# =================================================================
ANTHROPIC_API_KEY=your_anthropic_api_key # Get from https://console.anthropic.com
-OPENAI_API_KEY=your_openai_api_key # Get from https://platform.openai.com (GPT-5)
-GEMINI_API_KEY=your_gemini_api_key # Get from https://aistudio.google.com/app/apikey
-GROQ_API_KEY=your_groq_api_key # Get from https://console.groq.com (Fast inference - Kimi K2 recommended)
+OPENAI_API_KEY=your_openai_api_key # Get from https://platform.openai.com (GPT-5)
+GEMINI_API_KEY=your_gemini_api_key # Get from https://aistudio.google.com/app/apikey
+GROQ_API_KEY=your_groq_api_key # Get from https://console.groq.com (Fast inference)
```
3. **Run**
```bash
-npm run dev
+pnpm dev # or npm run dev / yarn dev
```
-Open [http://localhost:3000](http://localhost:3000)
+Open [http://localhost:3000](http://localhost:3000)
+
+## Sandbox Providers
+
+Open Lovable supports two sandbox providers for code execution:
+
+### 🔧 E2B
+- **Full-featured development environment** with Node.js, npm, and pre-installed tools
+- **Persistent file system** across interactions
+- **Fast startup** and reliable performance
+- **Advanced debugging** capabilities
+- Perfect for complex applications and debugging
+
+### ⚡ Vercel
+- **Vercel-hosted sandboxes** with automatic scaling
+- **Integrated with Vercel ecosystem** for seamless deployment
+- **Automatic OIDC authentication** (run `vercel link` then `vercel env pull`)
+- Great for **production workflows** and Vercel users
+
+### Choosing Your Provider
+
+- **Use E2B** if you need full development capabilities and debugging features
+- **Use Vercel** if you're already in the Vercel ecosystem or prefer their hosted solution
+- **Default**: E2B (if no `SANDBOX_PROVIDER` is specified)
+
+You can switch providers anytime by updating the `SANDBOX_PROVIDER` environment variable.
## License
diff --git a/app/api/analyze-edit-intent/route.ts b/app/api/analyze-edit-intent/route.ts
index 5d7da65..07798a0 100644
--- a/app/api/analyze-edit-intent/route.ts
+++ b/app/api/analyze-edit-intent/route.ts
@@ -76,7 +76,7 @@ export async function POST(request: NextRequest) {
// Create a summary of available files for the AI
const validFiles = Object.entries(manifest.files as Record
+ {generationProgress.thinkingText}
+
+ {generationProgress.status || 'Preparing to generate code...'}
++ {targetUrl || homeUrlInput.replace(/^https?:\/\//i, '') || 'example.com'} +
++ {isCapturingScreenshot ? 'Analyzing website...' : + isPreparingDesign ? 'Preparing design...' : + generationProgress.isGenerating ? 'Generating code...' : + 'Loading...'} +
+ + {/* Subtle progress hint */} ++ {isCapturingScreenshot ? 'Taking a screenshot of the site' : + isPreparingDesign ? 'Understanding the layout and structure' : + generationProgress.isGenerating ? 'Writing React components' : + 'Please wait...'} +
++ {codeApplicationState.stage === 'analyzing' && 'Parsing generated code and detecting dependencies...'} + {codeApplicationState.stage === 'installing' && 'This may take a moment while npm installs the required packages...'} + {codeApplicationState.stage === 'applying' && 'Writing files to your sandbox environment...'} +
+Failed to capture screenshot
+{screenshotError}
+Loading preview...
+Start chatting to create your first app
+
+
+ {code}
+
+
+ );
+}
\ No newline at end of file
diff --git a/components/ui/motion/scramble-text.tsx b/components/ui/motion/scramble-text.tsx
new file mode 100644
index 0000000..64c0daa
--- /dev/null
+++ b/components/ui/motion/scramble-text.tsx
@@ -0,0 +1,48 @@
+import React, { useEffect, useState } from 'react';
+
+interface ScrambleTextProps {
+ text: string;
+ className?: string;
+ duration?: number;
+ delay?: number;
+ isInView?: boolean;
+}
+
+export default function ScrambleText({ text, className = '', duration = 1, delay = 0, isInView = true }: ScrambleTextProps) {
+ const [displayText, setDisplayText] = useState(text);
+ const [isScrambling, setIsScrambling] = useState(false);
+
+ useEffect(() => {
+ if (isScrambling) {
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ const durationMs = duration * 1000; // Convert seconds to milliseconds
+ const interval = setInterval(() => {
+ setDisplayText(
+ text
+ .split('')
+ .map((char) => (Math.random() > 0.5 ? chars[Math.floor(Math.random() * chars.length)] : char))
+ .join('')
+ );
+ }, 50);
+
+ setTimeout(() => {
+ clearInterval(interval);
+ setDisplayText(text);
+ setIsScrambling(false);
+ }, durationMs);
+
+ return () => clearInterval(interval);
+ }
+ }, [text, isScrambling, duration]);
+
+ useEffect(() => {
+ if (isInView) {
+ const timeout = setTimeout(() => {
+ setIsScrambling(true);
+ }, delay);
+ return () => clearTimeout(timeout);
+ }
+ }, [text, delay, isInView]);
+
+ return {displayText};
+}
\ No newline at end of file
diff --git a/components/ui/shadcn/switch.tsx b/components/ui/shadcn/switch.tsx
index de8a134..76ce40e 100644
--- a/components/ui/shadcn/switch.tsx
+++ b/components/ui/shadcn/switch.tsx
@@ -4,7 +4,7 @@ import * as React from "react";
import * as SwitchPrimitives from "@radix-ui/react-switch";
import { cva, type VariantProps } from "class-variance-authority";
-import { cx } from "@/lib/app/utils";
+import { cn } from "@/utils/cn";
const switchVariants = cva(
"peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-muted-foreground data-[state=unchecked]:bg-input",
@@ -46,11 +46,11 @@ const Switch = React.forwardRef<
SwitchProps
>(({ className, size, ...props }, ref) => (