Skip to content

Added template and tweaked e2b logic #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 5 additions & 14 deletions app/api/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { generateApp } from '@/lib/openai';
import { repairCode } from '@/lib/benchify';
import { createSandbox, prepareVueEnvironment, deployApp } from '@/lib/e2b';
import { createSandbox } from '@/lib/e2b';
import { componentSchema } from '@/lib/schemas';
import { benchifyFileSchema } from '@/lib/schemas';

Expand Down Expand Up @@ -31,25 +31,16 @@ export async function POST(request: NextRequest) {
// // Repair the generated code using Benchify's API
// const repairedFiles = await repairCode(validatedFiles);

// // Set up E2B sandbox for preview
let previewUrl = undefined;
try {
const sandbox = await createSandbox();
await prepareVueEnvironment(sandbox);
const { previewUrl: url } = await deployApp(sandbox, generatedFiles);
previewUrl = url;
} catch (error) {
console.error('Error setting up preview:', error);
}
const { sbxId, template, url } = await createSandbox({ files: generatedFiles });

console.log("Preview URL: ", previewUrl);
console.log("Preview URL: ", url);

// Return the results to the client
return NextResponse.json({
originalFiles: generatedFiles,
// repairedFiles: repairedFiles,
buildOutput: '', // We don't get build output from Benchify in our current setup
previewUrl,
// buildOutput: '', // We don't get build output from Benchify in our current setup
previewUrl: url,
});
} catch (error) {
console.error('Error generating app:', error);
Expand Down
234 changes: 49 additions & 185 deletions lib/e2b.ts
Original file line number Diff line number Diff line change
@@ -1,199 +1,63 @@
// lib/e2b.ts
import { Sandbox } from '@e2b/sdk';
import { GeneratedFile, DeployResult } from '@/lib/types';

import { Sandbox } from '@e2b/code-interpreter';
import { benchifyFileSchema } from './schemas';
import { z } from 'zod';

const E2B_API_KEY = process.env.E2B_API_KEY;

if (!E2B_API_KEY) {
throw new Error('E2B_API_KEY is not set');
}

// Ensure path has a leading slash
function normalizePath(path: string): string {
return path.startsWith('/') ? path : `/${path}`;
}

// Initialize E2B SDK
export async function createSandbox() {
try {
const sandbox = await Sandbox.create({
apiKey: E2B_API_KEY,
});

return sandbox;
} catch (error: any) {
console.error('E2B Error Details:', {
message: error.message,
status: error.status,
statusText: error.statusText,
data: error.data,
headers: error.headers,
url: error.url
});
throw error;
}
}

// Set up a Vue environment with required dependencies
export async function prepareVueEnvironment(sandbox: Sandbox) {
try {
// Create necessary directories
await sandbox.filesystem.makeDir('/src');
await sandbox.filesystem.makeDir('/src/components');

// Define base configuration files
const packageJson = {
name: "vue-app",
version: "1.0.0",
type: "module",
scripts: {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
dependencies: {
"vue": "^3.3.0",
"vue-router": "^4.2.0",
"pinia": "^2.1.0",
"@vueuse/core": "^10.5.0"
},
devDependencies: {
"@vitejs/plugin-vue": "^4.4.0",
"typescript": "^5.2.0",
"vite": "^4.5.0",
"vue-tsc": "^1.8.0",
"tailwindcss": "^3.3.0",
"postcss": "^8.4.0",
"autoprefixer": "^10.4.0"
export async function createSandbox({ files }: { files: z.infer<typeof benchifyFileSchema> }) {
const sandbox = await Sandbox.create('vue-dynamic-sandbox', { apiKey: E2B_API_KEY });

// Write all files to the sandbox at once
await sandbox.files.write(
files.map(file => ({
path: `/home/user/app/${file.path}`,
data: file.contents
}))
);

console.log("sandbox created", sandbox.sandboxId);

// Find package.json to check for new dependencies
const packageJsonFile = files.find(file => file.path === 'package.json');
if (packageJsonFile) {
try {
const packageJson = JSON.parse(packageJsonFile.contents);
const dependencies = packageJson.dependencies || {};
const devDependencies = packageJson.devDependencies || {};

// Filter out pre-installed dependencies (vue, tailwindcss, etc.)
const preInstalled = ['vue', 'tailwindcss', 'autoprefixer', 'postcss', 'vite', '@vitejs/plugin-vue', '@vue/compiler-sfc'];

// Get new deps that need to be installed
const newDeps = Object.keys(dependencies).filter(dep => !preInstalled.includes(dep));
const newDevDeps = Object.keys(devDependencies).filter(dep => !preInstalled.includes(dep));

// Install only new dependencies if any exist
if (newDeps.length > 0) {
console.log("Installing new dependencies:", newDeps.join(", "));
await sandbox.commands.run(`cd /home/user/app && npm install --legacy-peer-deps ${newDeps.join(' ')}`);
}
};

// Write initial configuration
await sandbox.filesystem.write('/package.json', JSON.stringify(packageJson, null, 2));

await sandbox.filesystem.write('/vite.config.ts', `import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
plugins: [vue()],
server: {
host: true,
port: 3000
}
})`);

// Install dependencies with legacy peer deps to avoid conflicts
await sandbox.process.start({
cmd: 'npm install --legacy-peer-deps',
});

// Write Vue app files
await sandbox.filesystem.write('/src/App.vue', `<template>
<div class="min-h-screen">
<router-view />
</div>
</template>

<script setup lang="ts">
// App level setup
</script>`);

await sandbox.filesystem.write('/tailwind.config.js', `/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}`);

await sandbox.filesystem.write('/postcss.config.js', `export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}`);

// Create index files
await sandbox.filesystem.write('/index.html', `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>`);

await sandbox.filesystem.write('/src/main.ts', `import { createApp } from 'vue'
import App from './App.vue'
import './style.css'

createApp(App).mount('#app')`);

await sandbox.filesystem.write('/src/style.css', `@tailwind base;
@tailwind components;
@tailwind utilities;`);

return sandbox;
} catch (error: any) {
console.error('E2B Environment Setup Error:', {
message: error.message,
details: error.details,
command: error.command,
exitCode: error.exitCode,
stdout: error.stdout,
stderr: error.stderr
});
throw error;
}
}

// Deploy the app for preview
export async function deployApp(sandbox: Sandbox, files: GeneratedFile[]): Promise<DeployResult> {
try {
// Write all the generated files
for (const file of files) {
const normalizedPath = normalizePath(file.path);
const dirPath = normalizedPath.split('/').slice(0, -1).join('/');

if (dirPath && dirPath !== '/') {
await sandbox.filesystem.makeDir(dirPath);
if (newDevDeps.length > 0) {
console.log("Installing new dev dependencies:", newDevDeps.join(", "));
await sandbox.commands.run(`cd /home/user/app && npm install --legacy-peer-deps --save-dev ${newDevDeps.join(' ')}`);
}

await sandbox.filesystem.write(normalizedPath, file.contents);
} catch (error) {
console.error("Error parsing package.json:", error);
}
}

console.log('Starting development server...');
// Start the development server
const process = await sandbox.process.start({
cmd: 'npm run dev',
});
// Run the Vite app
await sandbox.commands.run('cd /home/user/app && npx vite');

const previewUrl = `https://${sandbox.id}-3000.code.e2b.dev`;
console.log('Preview URL generated:', previewUrl);
return {
sbxId: sandbox.sandboxId,
template: 'vue-dynamic-sandbox',
url: `https://${sandbox.getHost(5173)}`
};
}

// Return the URL for preview
return {
previewUrl,
process,
};
} catch (error: any) {
console.error('E2B Deployment Error:', {
message: error.message,
sandboxId: sandbox?.id,
status: error.status,
statusText: error.statusText,
data: error.data,
url: error.url
});
throw error;
}
}
14 changes: 14 additions & 0 deletions templates/vue-template/e2b.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# You can use most Debian-based base images
FROM node:21-slim

# Install dependencies and customize sandbox
WORKDIR /home/user/app

# Pre-install common dependencies to speed up runtime
RUN npm init -y
RUN npm install --legacy-peer-deps vue@3
RUN npm install --legacy-peer-deps --save-dev tailwindcss@3 autoprefixer@10 postcss@8 @vue/compiler-sfc@3 vite @vitejs/plugin-vue

# Set up basic Vite configuration for Vue
RUN npx tailwindcss init
COPY nuxt.config.ts /home/user/app/
16 changes: 16 additions & 0 deletions templates/vue-template/e2b.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This is a config for E2B sandbox template.
# You can use template ID (e9devag07oqgiyid3zox) or template name (vue-dynamic-sandbox) to create a sandbox:

# Python SDK
# from e2b import Sandbox, AsyncSandbox
# sandbox = Sandbox("vue-dynamic-sandbox") # Sync sandbox
# sandbox = await AsyncSandbox.create("vue-dynamic-sandbox") # Async sandbox

# JS SDK
# import { Sandbox } from 'e2b'
# const sandbox = await Sandbox.create('vue-dynamic-sandbox')

team_id = "35f2ed91-a6af-4e6c-a693-9e9e244fcdbd"
dockerfile = "e2b.Dockerfile"
template_name = "vue-dynamic-sandbox"
template_id = "e9devag07oqgiyid3zox"
14 changes: 14 additions & 0 deletions templates/vue-template/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
// @ts-ignore Ignored to pass Vercel deployment
export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
devtools: { enabled: false },
modules: ['@nuxtjs/tailwindcss'],
vite: {
server: {
hmr: {
protocol: 'wss'
}
}
}
})