-
Notifications
You must be signed in to change notification settings - Fork 0
Add editing functionality BEN-1079 #29
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -1,10 +1,12 @@ | ||||||||||
// app/api/generate/route.ts | ||||||||||
import { NextRequest, NextResponse } from 'next/server'; | ||||||||||
import { generateApp } from '@/lib/openai'; | ||||||||||
import { generateApp, editApp } from '@/lib/openai'; | ||||||||||
import { createSandbox } from '@/lib/e2b'; | ||||||||||
import { componentSchema } from '@/lib/schemas'; | ||||||||||
import { Benchify } from 'benchify'; | ||||||||||
import { applyPatch } from 'diff'; | ||||||||||
import { z } from 'zod'; | ||||||||||
import { benchifyFileSchema } from '@/lib/schemas'; | ||||||||||
|
||||||||||
const benchify = new Benchify({ | ||||||||||
apiKey: process.env.BENCHIFY_API_KEY, | ||||||||||
|
@@ -32,12 +34,30 @@ export default App;` | |||||||||
} | ||||||||||
]; | ||||||||||
|
||||||||||
// Extended schema to support editing | ||||||||||
const extendedComponentSchema = componentSchema.extend({ | ||||||||||
existingFiles: benchifyFileSchema.optional(), | ||||||||||
editInstruction: z.string().optional(), | ||||||||||
}); | ||||||||||
|
||||||||||
// Helper function to merge updated files with existing files | ||||||||||
function mergeFiles(existingFiles: z.infer<typeof benchifyFileSchema>, updatedFiles: z.infer<typeof benchifyFileSchema>): z.infer<typeof benchifyFileSchema> { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Implement Correct Override Logic for Merged FilesWhen merging files, updatedFiles should overwrite existingFiles based on matching path elements.
view all inputs Unit Tests// Unit Test for "Implement Correct Override Logic for Merged Files": When merging files, updatedFiles should overwrite existingFiles based on matching path elements.
function benchify_s(s) {
return s.replace(/[^a-zA-Z0-9]/g, 'a');
}
it('benchify_s_exec_test_passing_0', () => {
const args = superjson.parse(
'{"json":[[[{"path":"7e22ca07-0465-384d-88f4-c6e4a53ef4b6","content":"aaaFaa"}],[{"path":"d7cc1abd-8665-255a-a617-417238bcfa92","content":"Wa"},{"path":"9f250b89-56ff-18b1-9a8b-5734c0f8370a","content":"aIaag"},{"path":"113cc398-cd5b-50ed-8539-a5c522a97cfd","content":"xegaadaaCz1"}]]]}',
);
benchify_s(...args);
}); |
||||||||||
const existingMap = new Map(existingFiles.map(file => [file.path, file])); | ||||||||||
|
||||||||||
// Apply updates | ||||||||||
updatedFiles.forEach(updatedFile => { | ||||||||||
existingMap.set(updatedFile.path, updatedFile); | ||||||||||
}); | ||||||||||
|
||||||||||
return Array.from(existingMap.values()); | ||||||||||
} | ||||||||||
|
||||||||||
export async function POST(request: NextRequest) { | ||||||||||
try { | ||||||||||
const body = await request.json(); | ||||||||||
|
||||||||||
// Validate the request using Zod schema | ||||||||||
const validationResult = componentSchema.safeParse(body); | ||||||||||
// Validate the request using extended schema | ||||||||||
const validationResult = extendedComponentSchema.safeParse(body); | ||||||||||
|
||||||||||
if (!validationResult.success) { | ||||||||||
return NextResponse.json( | ||||||||||
|
@@ -46,30 +66,53 @@ export async function POST(request: NextRequest) { | |||||||||
); | ||||||||||
} | ||||||||||
|
||||||||||
const { description } = validationResult.data; | ||||||||||
const { description, existingFiles, editInstruction } = validationResult.data; | ||||||||||
|
||||||||||
console.log('API Request:', { | ||||||||||
isEdit: !!(existingFiles && editInstruction), | ||||||||||
filesCount: existingFiles?.length || 0, | ||||||||||
editInstruction: editInstruction || 'none', | ||||||||||
description: description || 'none' | ||||||||||
}); | ||||||||||
|
||||||||||
let filesToSandbox; | ||||||||||
|
||||||||||
// Determine if this is an edit request or new generation | ||||||||||
if (existingFiles && editInstruction) { | ||||||||||
// Edit existing code | ||||||||||
console.log('Processing edit request...'); | ||||||||||
console.log('Existing files:', existingFiles.map(f => ({ path: f.path, contentLength: f.content.length }))); | ||||||||||
|
||||||||||
const updatedFiles = await editApp(existingFiles, editInstruction); | ||||||||||
console.log('Updated files from AI:', updatedFiles.map(f => ({ path: f.path, contentLength: f.content.length }))); | ||||||||||
|
||||||||||
// Generate the Vue app using OpenAI | ||||||||||
let generatedFiles; | ||||||||||
if (debug) { | ||||||||||
generatedFiles = buggyCode; | ||||||||||
// Merge the updated files with the existing files | ||||||||||
filesToSandbox = mergeFiles(existingFiles, updatedFiles); | ||||||||||
console.log('Final merged files:', filesToSandbox.map(f => ({ path: f.path, contentLength: f.content.length }))); | ||||||||||
} else { | ||||||||||
generatedFiles = await generateApp(description); | ||||||||||
// Generate new app | ||||||||||
console.log('Processing new generation request...'); | ||||||||||
if (debug) { | ||||||||||
filesToSandbox = buggyCode; | ||||||||||
} else { | ||||||||||
filesToSandbox = await generateApp(description); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
// Repair the generated code using Benchify's API | ||||||||||
// const { data } = await benchify.fixer.run({ | ||||||||||
// files: generatedFiles.map(file => ({ | ||||||||||
// files: filesToSandbox.map(file => ({ | ||||||||||
// path: file.path, | ||||||||||
// contents: file.content | ||||||||||
// })) | ||||||||||
// }); | ||||||||||
|
||||||||||
let repairedFiles = generatedFiles; | ||||||||||
let repairedFiles = filesToSandbox; | ||||||||||
// if (data) { | ||||||||||
// const { success, diff } = data; | ||||||||||
|
||||||||||
// if (success && diff) { | ||||||||||
// repairedFiles = generatedFiles.map(file => { | ||||||||||
// repairedFiles = filesToSandbox.map(file => { | ||||||||||
// const patchResult = applyPatch(file.content, diff); | ||||||||||
// return { | ||||||||||
// ...file, | ||||||||||
|
@@ -83,12 +126,13 @@ export async function POST(request: NextRequest) { | |||||||||
|
||||||||||
// Return the results to the client | ||||||||||
return NextResponse.json({ | ||||||||||
originalFiles: generatedFiles, | ||||||||||
originalFiles: filesToSandbox, | ||||||||||
repairedFiles: sandboxResult.allFiles, // Use the allFiles from the sandbox | ||||||||||
buildOutput: `Sandbox created with template: ${sandboxResult.template}, ID: ${sandboxResult.sbxId}`, | ||||||||||
previewUrl: sandboxResult.url, | ||||||||||
buildErrors: sandboxResult.buildErrors, | ||||||||||
hasErrors: sandboxResult.hasErrors, | ||||||||||
...(editInstruction && { editInstruction }), | ||||||||||
}); | ||||||||||
} catch (error) { | ||||||||||
console.error('Error generating app:', error); | ||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,4 +1,6 @@ | ||||||||||||||||||||||||||
// lib/prompts.ts | ||||||||||||||||||||||||||
import { z } from 'zod'; | ||||||||||||||||||||||||||
import { benchifyFileSchema } from "./schemas"; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
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. | ||||||||||||||||||||||||||
|
@@ -47,5 +49,39 @@ export const REACT_APP_USER_PROMPT = (description: string) => ` | |||||||||||||||||||||||||
Create a React application with the following requirements: | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Output Format ConsistencyThe function should return a string that starts with 'Create a React application with the following requirements:' followed by the input 'description'.
view all inputs Unit Tests// Unit Test for "Output Format Consistency": The function should return a string that starts with 'Create a React application with the following requirements:' followed by the input 'description'.
function benchify_s(s) {
return s.replace(/[^a-zA-Z0-9]/g, 'a');
}
it('benchify_s_exec_test_passing_0', () => {
const args = superjson.parse('{"json":[["naasa"]]}');
benchify_s(...args);
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Description Must Be a StringThe function should accept only string inputs for the 'description' parameter.
view all inputs 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":[["`n-2~eX9(l"]]}');
benchify_description(...args);
}); |
||||||||||||||||||||||||||
${description}`; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export const EDIT_SYSTEM_PROMPT = `You are an expert React/TypeScript developer. You will be given existing code files and an edit instruction. Your job is to modify the existing code according to the instruction while maintaining: | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
1. Code quality and best practices | ||||||||||||||||||||||||||
2. Existing functionality that shouldn't be changed | ||||||||||||||||||||||||||
3. Proper TypeScript types | ||||||||||||||||||||||||||
4. Modern React patterns | ||||||||||||||||||||||||||
5. Tailwind CSS for styling | ||||||||||||||||||||||||||
6. shadcn/ui components where appropriate | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Return ONLY the files that need to be changed. Do not return unchanged files. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Rules: | ||||||||||||||||||||||||||
- Only return files that have actual changes | ||||||||||||||||||||||||||
- Make minimal changes necessary to fulfill the instruction | ||||||||||||||||||||||||||
- Keep all imports and dependencies that are still needed | ||||||||||||||||||||||||||
- Add new dependencies only if absolutely necessary | ||||||||||||||||||||||||||
- Use Tailwind classes for styling changes | ||||||||||||||||||||||||||
- Follow React best practices | ||||||||||||||||||||||||||
- Ensure all returned files are complete and valid`; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export function createEditUserPrompt(files: z.infer<typeof benchifyFileSchema>, editInstruction: string): string { | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Schema Adherence for FilesEnsure that each object in the 'files' array adheres to the 'benchifyFileSchema', containing at least a 'path' and 'content' as non-empty strings.
view all inputs Unit Tests// Unit Test for "Schema Adherence for Files": Ensure that each object in the 'files' array adheres to the 'benchifyFileSchema', containing at least a 'path' and 'content' as non-empty strings.
function benchify_files(files) {
expect(files).toBeTruthy();
files.forEach(file => {
expect(file.path).toBeTruthy();
expect(file.content).toBeTruthy();
});
}
it('benchify_files_exec_test_passing_0', () => {
const args = superjson.parse(
'{"json":[[[{"path":"jlgN","content":"HQA"},{"path":"#U","content":"uF"},{"path":"d\\"@52SBD","content":"^[O3Up{5Q"},{"path":"K -o\'Z@?a","content":"2:-~b&)e9l"},{"path":"5|","content":" pf!.oGd&6Y"},{"path":"(ygy%;","content":"b_qe"},{"path":"5j:q+kXm","content":"<7"},{"path":"0+DxMwyYq8(\\"","content":"Db JT"},{"path":"-F4zGt?W<","content":"B@{"},{"path":"j$**Q<;4E","content":"0S;%NEHzw"},{"path":"a!2<>-=_us3","content":"iI3&obq"}]]]}',
);
benchify_files(...args);
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ Consistent Output FormattingEnsure the output consistently starts with a specific introductory statement and concludes with a formatted edit instruction section.
view all inputs Stack Trace
Unit Tests// Unit Test for "Consistent Output Formatting": Ensure the output consistently starts with a specific introductory statement and concludes with a formatted edit instruction section.
function benchify_files(files, editInstruction) {
const output = createEditUserPrompt(files, editInstruction);
expect(output.startsWith("Here are the current files:\n\n")).toBe(true);
for (const file of files) {
expect(output.includes(`### ${file.path}\n\`\`\`\n${file.content}\n\`\`\``)).toBe(true);
}
expect(output.endsWith(`Edit instruction: ${editInstruction}`)).toBe(true);
}
it('benchify_files_exec_test_failing_0', () => {
const args = superjson.parse(
'{"json":[[[{"path":"goWAus)<4qvV","content":"elb"},{"path":"fe%PMi76L>%;","content":"J,Ps&WKS"},{"path":"nY6c^o G-^","content":"l Y`5"},{"path":"]2X","content":"A64qi+z~"}],"+i+_et=R-|Z"]]}',
);
benchify_files(...args);
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ Error Handling for Missing or Invalid InputHandle cases where the 'files' array is empty or 'editInstruction' does not provide a valid string, to avoid malformed output.
view all inputs The test failed with a TypeError due to an unexpected usage of an instance of FrequencyArbitrary as a function. This issue occurred when processing an array of files with specific paths and contents, where the function Stack Trace
Unit Tests// Unit Test for "Error Handling for Missing or Invalid Input": Handle cases where the 'files' array is empty or 'editInstruction' does not provide a valid string, to avoid malformed output.
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":[[[{"path":"(eN^%","content":"zaamah1"},{"path":")pi","content":"aWaaa4xaaYO"},{"path":"9 tZ2","content":"za"},{"path":"b=*wage[ #","content":"ur"},{"path":"A Ny*i/s","content":"NjaaUMaaK"}]]]}',
);
benchify_s(...args);
}); |
||||||||||||||||||||||||||
const filesContent = files.map(file => | ||||||||||||||||||||||||||
`### ${file.path}\n\`\`\`\n${file.content}\n\`\`\`` | ||||||||||||||||||||||||||
).join('\n\n'); | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return `Here are the current files: | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
${filesContent} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Edit instruction: ${editInstruction} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Please update the code according to this instruction and return all files with their updated content.`; | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export const TEMPERATURE = 0.7; | ||||||||||||||||||||||||||
export const MODEL = 'gpt-4o'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Ensure Uniqueness of File Paths in Result
The returned array should ensure uniqueness of files by their path, preventing any duplicate paths.
superjson.parse('{"json":[[[{"path":"aaaaXaaamo...
view full inputview all inputs
Your property-based test has passed! This test ensures that the
mergeFiles
function returns an array of files with unique paths. The test generated an example with two arrays of files,existingFiles
andupdatedFiles
, and successfully verified that the merged result contains no duplicate paths. The test's success indicates that themergeFiles
function is correctly handling file merging and maintaining path uniqueness.Unit Tests