Skip to content

Commit f93f20a

Browse files
authored
Added template and tweaked e2b logic (#2)
### TL;DR Improved sandbox creation for Vue applications using E2B's code interpreter. ### What changed? - Replaced the complex sandbox setup with a more streamlined approach using E2B's code interpreter - Created a custom Vue template for E2B with pre-installed dependencies - Simplified the sandbox creation process by writing all files at once - Added automatic detection and installation of new dependencies from package.json - Updated the preview URL generation to use the correct port (5173) - Added Docker configuration for the Vue template ### How to test? 1. Generate a Vue application through the API 2. Verify that the sandbox is created successfully 3. Check that the preview URL works and displays the generated application 4. Test with applications that require additional dependencies to ensure they're installed correctly ### Why make this change? The previous implementation had a complex setup process with multiple steps that could fail. This new approach is more reliable and efficient, using a pre-configured template that speeds up sandbox creation. It also handles dependency management more intelligently by only installing new dependencies that aren't already pre-installed in the template.
2 parents ee53480 + dec7cb1 commit f93f20a

File tree

5 files changed

+98
-199
lines changed

5 files changed

+98
-199
lines changed

app/api/generate/route.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { NextRequest, NextResponse } from 'next/server';
33
import { generateApp } from '@/lib/openai';
44
import { repairCode } from '@/lib/benchify';
5-
import { createSandbox, prepareVueEnvironment, deployApp } from '@/lib/e2b';
5+
import { createSandbox } from '@/lib/e2b';
66
import { componentSchema } from '@/lib/schemas';
77
import { benchifyFileSchema } from '@/lib/schemas';
88

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

34-
// // Set up E2B sandbox for preview
35-
let previewUrl = undefined;
36-
try {
37-
const sandbox = await createSandbox();
38-
await prepareVueEnvironment(sandbox);
39-
const { previewUrl: url } = await deployApp(sandbox, generatedFiles);
40-
previewUrl = url;
41-
} catch (error) {
42-
console.error('Error setting up preview:', error);
43-
}
34+
const { sbxId, template, url } = await createSandbox({ files: generatedFiles });
4435

45-
console.log("Preview URL: ", previewUrl);
36+
console.log("Preview URL: ", url);
4637

4738
// Return the results to the client
4839
return NextResponse.json({
4940
originalFiles: generatedFiles,
5041
// repairedFiles: repairedFiles,
51-
buildOutput: '', // We don't get build output from Benchify in our current setup
52-
previewUrl,
42+
// buildOutput: '', // We don't get build output from Benchify in our current setup
43+
previewUrl: url,
5344
});
5445
} catch (error) {
5546
console.error('Error generating app:', error);

lib/e2b.ts

Lines changed: 49 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -1,199 +1,63 @@
1-
// lib/e2b.ts
2-
import { Sandbox } from '@e2b/sdk';
3-
import { GeneratedFile, DeployResult } from '@/lib/types';
4-
1+
import { Sandbox } from '@e2b/code-interpreter';
2+
import { benchifyFileSchema } from './schemas';
3+
import { z } from 'zod';
54

65
const E2B_API_KEY = process.env.E2B_API_KEY;
76

87
if (!E2B_API_KEY) {
98
throw new Error('E2B_API_KEY is not set');
109
}
1110

12-
// Ensure path has a leading slash
13-
function normalizePath(path: string): string {
14-
return path.startsWith('/') ? path : `/${path}`;
15-
}
16-
17-
// Initialize E2B SDK
18-
export async function createSandbox() {
19-
try {
20-
const sandbox = await Sandbox.create({
21-
apiKey: E2B_API_KEY,
22-
});
23-
24-
return sandbox;
25-
} catch (error: any) {
26-
console.error('E2B Error Details:', {
27-
message: error.message,
28-
status: error.status,
29-
statusText: error.statusText,
30-
data: error.data,
31-
headers: error.headers,
32-
url: error.url
33-
});
34-
throw error;
35-
}
36-
}
37-
38-
// Set up a Vue environment with required dependencies
39-
export async function prepareVueEnvironment(sandbox: Sandbox) {
40-
try {
41-
// Create necessary directories
42-
await sandbox.filesystem.makeDir('/src');
43-
await sandbox.filesystem.makeDir('/src/components');
44-
45-
// Define base configuration files
46-
const packageJson = {
47-
name: "vue-app",
48-
version: "1.0.0",
49-
type: "module",
50-
scripts: {
51-
"dev": "vite",
52-
"build": "vue-tsc && vite build",
53-
"preview": "vite preview"
54-
},
55-
dependencies: {
56-
"vue": "^3.3.0",
57-
"vue-router": "^4.2.0",
58-
"pinia": "^2.1.0",
59-
"@vueuse/core": "^10.5.0"
60-
},
61-
devDependencies: {
62-
"@vitejs/plugin-vue": "^4.4.0",
63-
"typescript": "^5.2.0",
64-
"vite": "^4.5.0",
65-
"vue-tsc": "^1.8.0",
66-
"tailwindcss": "^3.3.0",
67-
"postcss": "^8.4.0",
68-
"autoprefixer": "^10.4.0"
11+
export async function createSandbox({ files }: { files: z.infer<typeof benchifyFileSchema> }) {
12+
const sandbox = await Sandbox.create('vue-dynamic-sandbox', { apiKey: E2B_API_KEY });
13+
14+
// Write all files to the sandbox at once
15+
await sandbox.files.write(
16+
files.map(file => ({
17+
path: `/home/user/app/${file.path}`,
18+
data: file.contents
19+
}))
20+
);
21+
22+
console.log("sandbox created", sandbox.sandboxId);
23+
24+
// Find package.json to check for new dependencies
25+
const packageJsonFile = files.find(file => file.path === 'package.json');
26+
if (packageJsonFile) {
27+
try {
28+
const packageJson = JSON.parse(packageJsonFile.contents);
29+
const dependencies = packageJson.dependencies || {};
30+
const devDependencies = packageJson.devDependencies || {};
31+
32+
// Filter out pre-installed dependencies (vue, tailwindcss, etc.)
33+
const preInstalled = ['vue', 'tailwindcss', 'autoprefixer', 'postcss', 'vite', '@vitejs/plugin-vue', '@vue/compiler-sfc'];
34+
35+
// Get new deps that need to be installed
36+
const newDeps = Object.keys(dependencies).filter(dep => !preInstalled.includes(dep));
37+
const newDevDeps = Object.keys(devDependencies).filter(dep => !preInstalled.includes(dep));
38+
39+
// Install only new dependencies if any exist
40+
if (newDeps.length > 0) {
41+
console.log("Installing new dependencies:", newDeps.join(", "));
42+
await sandbox.commands.run(`cd /home/user/app && npm install --legacy-peer-deps ${newDeps.join(' ')}`);
6943
}
70-
};
71-
72-
// Write initial configuration
73-
await sandbox.filesystem.write('/package.json', JSON.stringify(packageJson, null, 2));
74-
75-
await sandbox.filesystem.write('/vite.config.ts', `import { defineConfig } from 'vite'
76-
import vue from '@vitejs/plugin-vue'
77-
78-
export default defineConfig({
79-
plugins: [vue()],
80-
server: {
81-
host: true,
82-
port: 3000
83-
}
84-
})`);
85-
86-
// Install dependencies with legacy peer deps to avoid conflicts
87-
await sandbox.process.start({
88-
cmd: 'npm install --legacy-peer-deps',
89-
});
90-
91-
// Write Vue app files
92-
await sandbox.filesystem.write('/src/App.vue', `<template>
93-
<div class="min-h-screen">
94-
<router-view />
95-
</div>
96-
</template>
97-
98-
<script setup lang="ts">
99-
// App level setup
100-
</script>`);
10144

102-
await sandbox.filesystem.write('/tailwind.config.js', `/** @type {import('tailwindcss').Config} */
103-
export default {
104-
content: [
105-
"./index.html",
106-
"./src/**/*.{vue,js,ts,jsx,tsx}",
107-
],
108-
theme: {
109-
extend: {},
110-
},
111-
plugins: [],
112-
}`);
113-
114-
await sandbox.filesystem.write('/postcss.config.js', `export default {
115-
plugins: {
116-
tailwindcss: {},
117-
autoprefixer: {},
118-
},
119-
}`);
120-
121-
// Create index files
122-
await sandbox.filesystem.write('/index.html', `<!DOCTYPE html>
123-
<html lang="en">
124-
<head>
125-
<meta charset="UTF-8" />
126-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
127-
<title>Vue App</title>
128-
</head>
129-
<body>
130-
<div id="app"></div>
131-
<script type="module" src="/src/main.ts"></script>
132-
</body>
133-
</html>`);
134-
135-
await sandbox.filesystem.write('/src/main.ts', `import { createApp } from 'vue'
136-
import App from './App.vue'
137-
import './style.css'
138-
139-
createApp(App).mount('#app')`);
140-
141-
await sandbox.filesystem.write('/src/style.css', `@tailwind base;
142-
@tailwind components;
143-
@tailwind utilities;`);
144-
145-
return sandbox;
146-
} catch (error: any) {
147-
console.error('E2B Environment Setup Error:', {
148-
message: error.message,
149-
details: error.details,
150-
command: error.command,
151-
exitCode: error.exitCode,
152-
stdout: error.stdout,
153-
stderr: error.stderr
154-
});
155-
throw error;
156-
}
157-
}
158-
159-
// Deploy the app for preview
160-
export async function deployApp(sandbox: Sandbox, files: GeneratedFile[]): Promise<DeployResult> {
161-
try {
162-
// Write all the generated files
163-
for (const file of files) {
164-
const normalizedPath = normalizePath(file.path);
165-
const dirPath = normalizedPath.split('/').slice(0, -1).join('/');
166-
167-
if (dirPath && dirPath !== '/') {
168-
await sandbox.filesystem.makeDir(dirPath);
45+
if (newDevDeps.length > 0) {
46+
console.log("Installing new dev dependencies:", newDevDeps.join(", "));
47+
await sandbox.commands.run(`cd /home/user/app && npm install --legacy-peer-deps --save-dev ${newDevDeps.join(' ')}`);
16948
}
170-
171-
await sandbox.filesystem.write(normalizedPath, file.contents);
49+
} catch (error) {
50+
console.error("Error parsing package.json:", error);
17251
}
52+
}
17353

174-
console.log('Starting development server...');
175-
// Start the development server
176-
const process = await sandbox.process.start({
177-
cmd: 'npm run dev',
178-
});
54+
// Run the Vite app
55+
await sandbox.commands.run('cd /home/user/app && npx vite');
17956

180-
const previewUrl = `https://${sandbox.id}-3000.code.e2b.dev`;
181-
console.log('Preview URL generated:', previewUrl);
57+
return {
58+
sbxId: sandbox.sandboxId,
59+
template: 'vue-dynamic-sandbox',
60+
url: `https://${sandbox.getHost(5173)}`
61+
};
62+
}
18263

183-
// Return the URL for preview
184-
return {
185-
previewUrl,
186-
process,
187-
};
188-
} catch (error: any) {
189-
console.error('E2B Deployment Error:', {
190-
message: error.message,
191-
sandboxId: sandbox?.id,
192-
status: error.status,
193-
statusText: error.statusText,
194-
data: error.data,
195-
url: error.url
196-
});
197-
throw error;
198-
}
199-
}

templates/vue-template/e2b.Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# You can use most Debian-based base images
2+
FROM node:21-slim
3+
4+
# Install dependencies and customize sandbox
5+
WORKDIR /home/user/app
6+
7+
# Pre-install common dependencies to speed up runtime
8+
RUN npm init -y
9+
RUN npm install --legacy-peer-deps vue@3
10+
RUN npm install --legacy-peer-deps --save-dev tailwindcss@3 autoprefixer@10 postcss@8 @vue/compiler-sfc@3 vite @vitejs/plugin-vue
11+
12+
# Set up basic Vite configuration for Vue
13+
RUN npx tailwindcss init
14+
COPY nuxt.config.ts /home/user/app/

templates/vue-template/e2b.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# This is a config for E2B sandbox template.
2+
# You can use template ID (e9devag07oqgiyid3zox) or template name (vue-dynamic-sandbox) to create a sandbox:
3+
4+
# Python SDK
5+
# from e2b import Sandbox, AsyncSandbox
6+
# sandbox = Sandbox("vue-dynamic-sandbox") # Sync sandbox
7+
# sandbox = await AsyncSandbox.create("vue-dynamic-sandbox") # Async sandbox
8+
9+
# JS SDK
10+
# import { Sandbox } from 'e2b'
11+
# const sandbox = await Sandbox.create('vue-dynamic-sandbox')
12+
13+
team_id = "35f2ed91-a6af-4e6c-a693-9e9e244fcdbd"
14+
dockerfile = "e2b.Dockerfile"
15+
template_name = "vue-dynamic-sandbox"
16+
template_id = "e9devag07oqgiyid3zox"

templates/vue-template/nuxt.config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// https://nuxt.com/docs/api/configuration/nuxt-config
2+
// @ts-ignore Ignored to pass Vercel deployment
3+
export default defineNuxtConfig({
4+
compatibilityDate: '2024-04-03',
5+
devtools: { enabled: false },
6+
modules: ['@nuxtjs/tailwindcss'],
7+
vite: {
8+
server: {
9+
hmr: {
10+
protocol: 'wss'
11+
}
12+
}
13+
}
14+
})

0 commit comments

Comments
 (0)