Skip to content

creating vite template & trying to get it to work #14

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
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
6 changes: 3 additions & 3 deletions app/api/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export async function POST(request: NextRequest) {
const validatedFiles = benchifyFileSchema.parse(generatedFiles);

// // Repair the generated code using Benchify's API
const { repairedFiles, buildOutput } = await repairCode(validatedFiles);
// const { repairedFiles, buildOutput } = await repairCode(validatedFiles);

const { sbxId, template, url } = await createSandbox({ files: generatedFiles });

Expand All @@ -38,8 +38,8 @@ export async function POST(request: NextRequest) {
// Return the results to the client
return NextResponse.json({
originalFiles: generatedFiles,
repairedFiles: repairedFiles,
buildOutput: buildOutput,
// repairedFiles: repairedFiles,
// buildOutput: buildOutput,
previewUrl: url,
});
} catch (error) {
Expand Down
93 changes: 70 additions & 23 deletions lib/e2b.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,64 @@ if (!E2B_API_KEY) {
}

export async function createSandbox({ files }: { files: z.infer<typeof benchifyFileSchema> }) {
const sandbox = await Sandbox.create('vue-dynamic-sandbox', { apiKey: E2B_API_KEY });
const sandbox = await Sandbox.create('vite-template', { 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}`,
// Debug: Log template files before writing anything
try {
console.log("TEMPLATE VERIFICATION:");
const { stdout: templateFiles } = await sandbox.commands.run('ls -la /app', { cwd: '/app' });
console.log("Template files in /app:", templateFiles);

const { stdout: templatePkgJson } = await sandbox.commands.run('cat /app/package.json', { cwd: '/app' });
console.log("Template package.json:", templatePkgJson);
} catch (error) {
console.error("Error checking template:", error);
}

// Find AI-generated package.json to extract dependencies
const aiPackageJsonFile = files.find(file => file.path === 'package.json');

// Filter out package.json from files to write (we'll handle it separately)
const filesToWrite = files
.filter(file => file.path !== 'package.json')
.map(file => ({
path: `/app/${file.path}`,
data: file.content
}))
);
}));

// Write all files to the sandbox EXCEPT package.json
await sandbox.files.write(filesToWrite);

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) {
// Debug: Verify files after writing
try {
console.log("AFTER WRITING FILES:");
const { stdout: rootContents } = await sandbox.commands.run('ls -la /app', { cwd: '/app' });
console.log("Files in /app:", rootContents);

const { stdout: packageJson } = await sandbox.commands.run('cat /app/package.json', { cwd: '/app' });
console.log("Current package.json:", packageJson);

const { stdout: scriptsList } = await sandbox.commands.run('npm run', { cwd: '/app' });
console.log("Available npm scripts:", scriptsList);
} catch (error) {
console.error("Error in debug commands:", error);
}

// Process dependencies if AI provided a package.json
if (aiPackageJsonFile) {
try {
const packageJson = JSON.parse(packageJsonFile.content);
const dependencies = packageJson.dependencies || {};
const devDependencies = packageJson.devDependencies || {};
const aiPackageJson = JSON.parse(aiPackageJsonFile.content);
const dependencies = aiPackageJson.dependencies || {};
const devDependencies = aiPackageJson.devDependencies || {};

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

// Get new deps that need to be installed
const newDeps = Object.keys(dependencies).filter(dep => !preInstalled.includes(dep));
Expand All @@ -39,29 +75,40 @@ export async function createSandbox({ files }: { files: z.infer<typeof benchifyF
// 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(' ')}`);
await sandbox.commands.run(`cd /app && npm install --legacy-peer-deps ${newDeps.join(' ')}`);
}

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.commands.run(`cd /app && npm install --legacy-peer-deps --save-dev ${newDevDeps.join(' ')}`);
}
} catch (error) {
console.error("Error parsing package.json:", error);
}
}

// Fix permissions with sudo before starting
try {
await sandbox.commands.run('sudo rm -rf /app/node_modules/.vite', { cwd: '/app' });
await sandbox.commands.run('sudo mkdir -p /app/node_modules/.vite', { cwd: '/app' });
await sandbox.commands.run('sudo chmod -R 777 /app/node_modules/.vite', { cwd: '/app' });
} catch (error) {
console.error("Error fixing permissions:", error);
}

// Run the Vite app
sandbox.commands.run('npx vite --host', {
cwd: '/home/user/app',
timeoutMs: 0,
}).catch((error) => {
try {
await sandbox.commands.run('npm run dev -- --host', {
cwd: '/app',
timeoutMs: 0,
});
} catch (error) {
console.error("Error running Vite:", error);
});
}

return {
sbxId: sandbox.sandboxId,
template: 'vue-dynamic-sandbox',
template: 'vite-template',
url: `https://${sandbox.getHost(5173)}`
};
}
Expand Down
6 changes: 3 additions & 3 deletions lib/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { streamObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
import { VUE_APP_SYSTEM_PROMPT, VUE_APP_USER_PROMPT, TEMPERATURE, MODEL } from './prompts';
import { REACT_APP_SYSTEM_PROMPT, REACT_APP_USER_PROMPT, TEMPERATURE, MODEL } from './prompts';

const OPENAI_API_KEY = process.env.OPENAI_API_KEY;

Expand All @@ -29,8 +29,8 @@ export async function generateApp(
schema: fileSchema,
temperature: TEMPERATURE,
messages: [
{ role: 'system', content: VUE_APP_SYSTEM_PROMPT },
{ role: 'user', content: VUE_APP_USER_PROMPT(description) }
{ role: 'system', content: REACT_APP_SYSTEM_PROMPT },
{ role: 'user', content: REACT_APP_USER_PROMPT(description) }
]
});

Expand Down
42 changes: 30 additions & 12 deletions lib/prompts.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
// lib/prompts.ts

export const VUE_APP_SYSTEM_PROMPT = `You are an expert Vue.js and Tailwind CSS developer.
You will be generating a complete Vue 3 application based on the provided description.
export const REACT_APP_SYSTEM_PROMPT = `You are an expert React, TypeScript, and Tailwind CSS developer.
You will be generating React application code based on the provided description.
This code will be inserted into an existing Vite + React + TypeScript template.

Follow these guidelines:
- Use Vue 3 Composition API with <script setup> syntax
- Use Tailwind CSS for styling
- Include the package.json file in the response
- The following files are provided and should not be modified: nuxt.config.ts, postcss.config.ts, tailwind.config.ts, vite.config.ts
- Follow Vue.js best practices and conventions
- Use React 19 with TypeScript
- Use Tailwind CSS v4 for styling
- DO NOT use component libraries like shadcn/ui or Material UI
- Build all UI components from scratch using Tailwind CSS
- Use the hooks pattern and follow React best practices
- Create a well-structured application with proper component organization
- Include proper TypeScript types
- Ensure proper TypeScript typing
- Add comments explaining complex logic
- Handle loading states and errors appropriately
- Ensure responsive design
- Use port 5173 for the Vite server
- Always remember to write an index.css file and import it in the main app file
- Import CSS in main.tsx as: import './index.css'
- Use relative imports (not path aliases): import App from './App.tsx'
- IMPORTANT: Always generate ALL components that you reference or import

IMPORTANT: Only generate these application files:
- src/main.tsx (entry point)
- src/App.tsx (main app component)
- src/index.css (with Tailwind imports)
- src/components/* (your React components)
- package.json (only for additional dependencies you need)

DO NOT generate configuration files like:
- vite.config.ts
- tsconfig files
- eslint configs
- index.html

These configuration files are already part of the template.

RESPONSE FORMAT:
You must return a valid JSON array of file objects. Each file object must have exactly this structure:
Expand All @@ -25,8 +43,8 @@ You must return a valid JSON array of file objects. Each file object must have e

Do not include any markdown formatting, code blocks, or explanatory text. The response must be pure JSON.`;

export const VUE_APP_USER_PROMPT = (description: string) => `
Create a Vue.js application with the following requirements:
export const REACT_APP_USER_PROMPT = (description: string) => `
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Output String Format Validation

The output string must start with 'Create a React application with the following requirements:'.

Outcome Example Input # Inputs % of Total
superjson.parse('{"json":[["MaFa"]]}')... view full input 400 100.0%

view all inputs
The property-based test failed because the generated string did not start with the expected prefix 'Create a React application with the following requirements:'. The input argument was "{"json":[["MaFa"]]}" and the actual output string started with a newline character, causing the assertion to fail.

Stack Trace
Error: expect(received).toStartWith(expected)

Expected to start with: "Create a React application with the following requirements:"
Received: "\nCreate a React application with the following requirements:\nMaFa"

    at toStartWith (unknown)
    at <anonymous> (/app/repo/lib/pver_5f848e4f-dedb-422b-8b7b-8caba561ef32.test.ts:44:52)
    at <anonymous> (/app/configuration/fc.setup.ts:183:11)
    at run (/app/node_modules/fast-check/lib/esm/check/property/Property.generic.js:46:33)
    at runIt (/app/node_modules/fast-check/lib/esm/check/runner/Runner.js:18:30)
    at check (/app/node_modules/fast-check/lib/esm/check/runner/Runner.js:62:11)
    at <anonymous> (/app/configuration/fc.setup.ts:197:14)
    at assertWithLogging (/app/configuration/fc.setup.ts:125:3)
    at <anonymous> (/app/repo/lib/pver_5f848e4f-dedb-422b-8b7b-8caba561ef32.test.ts:37:6)
Unit Tests
// Unit Test for "Output String Format Validation": The output string must start with 'Create a React application with the following requirements:'.
function benchify_s(s) {
    return s.replace(/[^a-zA-Z0-9]/g, 'a');
}

it('benchify_s_exec_test_failing_0', () => {
  const args = superjson.parse('{"json":[["MaFa"]]}');

  benchify_s(...args);
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Description Must Be a String

The function should accept only string inputs for the 'description' parameter.

Outcome Example Input # Inputs % of Total
superjson.parse('{"json":[["]xE"]]}')... view full input 200 100.0%

view all inputs
The test has passed, which means the REACT_APP_USER_PROMPT function successfully accepted the input string ["{\"json\":[[\"]xE\"]]"] and returned a defined result as expected. The function correctly handled the string input and returned a prompt message with the provided description. No issues were found in this test scenario.

Unit Tests
// Unit Test for "Description Must Be a String": The function should accept only string inputs for the 'description' parameter.
function benchify_description(description) {
    const result = REACT_APP_USER_PROMPT(description);
    expect(result).toBeDefined();
}

it('benchify_description_exec_test_passing_0', () => {
  const args = superjson.parse('{"json":[["]xE"]]}');

  benchify_description(...args);
});

Create a React application with the following requirements:
${description}`;

export const TEMPERATURE = 0.7;
Expand Down
Binary file added templates/.DS_Store
Binary file not shown.
56 changes: 56 additions & 0 deletions templates/vite-template/e2b.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# You can use most Debian-based base images
FROM ubuntu:22.04

# Install dependencies and customize sandbox
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
curl \
git \
bash \
ca-certificates \
gnupg \
build-essential \
sudo \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# Install Node.js 18.x LTS (more stable for this purpose)
RUN mkdir -p /etc/apt/keyrings && \
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
apt-get update && \
apt-get install -y nodejs && \
npm install -g [email protected]

# Create app user
RUN useradd -m -s /bin/bash viteuser && \
echo "viteuser ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/viteuser

# Create app directory
WORKDIR /app

# Install Vite globally
RUN npm install -g vite create-vite

# Create basic Vite project structure
COPY ./template /app

# Set proper ownership and permissions
RUN chown -R viteuser:viteuser /app && \
chmod -R 775 /app

# Install dependencies as the viteuser
USER viteuser
RUN cd /app && npm install

# Create Vite cache directories with proper permissions
RUN mkdir -p /app/node_modules/.vite && \
mkdir -p /app/node_modules/.vite-temp && \
chmod -R 777 /app/node_modules/.vite && \
chmod -R 777 /app/node_modules/.vite-temp

# Expose port
EXPOSE 5173

# Set entrypoint
CMD ["npm", "run", "dev"]
16 changes: 16 additions & 0 deletions templates/vite-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 (0szqhsrgikxynzgu09xv) or template name (vite-template) to create a sandbox:

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

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

team_id = "35f2ed91-a6af-4e6c-a693-9e9e244fcdbd"
dockerfile = "e2b.Dockerfile"
template_name = "vite-template"
template_id = "0szqhsrgikxynzgu09xv"
24 changes: 24 additions & 0 deletions templates/vite-template/template/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
54 changes: 54 additions & 0 deletions templates/vite-template/template/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

```js
export default tseslint.config({
extends: [
// Remove ...tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```

You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:

```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default tseslint.config({
plugins: {
// Add the react-x and react-dom plugins
'react-x': reactX,
'react-dom': reactDom,
},
rules: {
// other rules...
// Enable its recommended typescript rules
...reactX.configs['recommended-typescript'].rules,
...reactDom.configs.recommended.rules,
},
})
```
Loading