Skip to content

Move edit logic into open AI file #35

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

Open
wants to merge 1 commit into
base: 06-16-add_toggle_to_control_buggy_code_and_fixer_usage
Choose a base branch
from
Open
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
66 changes: 5 additions & 61 deletions app/api/generate/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// app/api/generate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateApp, editApp } from '@/lib/openai';
import { processAppRequest } from '@/lib/openai';
import { createSandbox } from '@/lib/e2b';
import { componentSchema } from '@/lib/schemas';
import { Benchify } from 'benchify';
Expand All @@ -12,27 +12,6 @@ const benchify = new Benchify({
apiKey: process.env.BENCHIFY_API_KEY,
});

const buggyCode = [
{
path: "src/App.tsx",
content: `import React from 'react';

const App = () => {
const message = "Hello World; // Missing closing quote
const title = 'Welcome to my app';

return (
<div>
<h1>{title}</h1>
<p>{message}</p>
</div>
);
};

export default App;`
}
];

// Extended schema to support editing
const extendedComponentSchema = componentSchema.extend({
existingFiles: benchifyFileSchema.optional(),
Expand All @@ -41,18 +20,6 @@ const extendedComponentSchema = componentSchema.extend({
useFixer: z.boolean().optional().default(false),
});

// 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> {
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 {
console.log('🚀 API route started');
Expand Down Expand Up @@ -80,31 +47,8 @@ export async function POST(request: NextRequest) {
useFixer
});

let filesToSandbox;

// Determine if this is an edit request or new generation
if (existingFiles && editInstruction) {
// Edit existing code (including error fixes)
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 })));

// 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 {
// Generate new app
console.log('🆕 Processing new generation request...');
if (useBuggyCode) {
console.log('🐛 Using buggy code as requested');
filesToSandbox = buggyCode;
} else {
console.log('🤖 Calling AI to generate app...');
filesToSandbox = await generateApp(description);
}
}
// Process the app request using centralized logic
const filesToSandbox = await processAppRequest(description, existingFiles, editInstruction, useBuggyCode);

console.log('📦 Files ready for sandbox:', filesToSandbox.length);

Expand All @@ -115,7 +59,7 @@ export async function POST(request: NextRequest) {
console.log('🔧 Running Benchify fixer...');
try {
const { data } = await benchify.fixer.run({
files: filesToSandbox.map(file => ({
files: filesToSandbox.map((file: { path: string; content: string }) => ({
path: file.path,
contents: file.content
}))
Expand All @@ -126,7 +70,7 @@ export async function POST(request: NextRequest) {

if (success && diff) {
console.log('✅ Fixer applied successfully');
repairedFiles = filesToSandbox.map(file => {
repairedFiles = filesToSandbox.map((file: { path: string; content: string }) => {
const patchResult = applyPatch(file.content, diff);
return {
...file,
Expand Down
70 changes: 66 additions & 4 deletions lib/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,25 @@ export async function generateApp(
}
}

// Edit existing application using AI SDK
// 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> {
Copy link

Choose a reason for hiding this comment

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

✅ Output Schema Compliance

Ensure that the output array of the mergeFiles function conforms to the benchifyFileSchema.

Outcome Example Input # Inputs % of Total
superjson.parse('{"json":[[[{"path":"pSqGaaaaa"... view full input 200 100.0%

view all inputs
The property-based test has passed. The mergeFiles function has successfully merged the provided existing and updated files, and the resulting array conforms to the benchifyFileSchema, ensuring that each file has 'path' and 'content' as strings. The test has validated the function's behavior with the given input arrays.

Unit Tests
// Unit Test for "Output Schema Compliance": Ensure that the output array of the `mergeFiles` function conforms to the `benchifyFileSchema`.
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":"pSqGaaaaa","content":"YcTaaiMabat"},{"path":"3","content":"Q2a0Raaia"},{"path":"naMlKc","content":"az9laafg0eaP"},{"path":"aFCRAJQaVrza","content":"mmma0TBla"},{"path":"am","content":"aaS5"},{"path":"Bf","content":"adar6qyQy"},{"path":"u06","content":"aFtaw"},{"path":"anfagEaUaIa3","content":"zamWRYGMaa"}],[{"path":"r","content":"a"},{"path":"zXcatGB","content":"c4aSa"},{"path":"aah","content":"er"},{"path":"EaaqxaaxWaa","content":"Xau"},{"path":"seaaaanba","content":"C"}]]]}',
  );

  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.

✅ Unique Path Enforcement

The output array should not contain duplicate file objects for the same path.

Outcome Example Input # Inputs % of Total
superjson.parse('{"json":[[[{"path":"H%AR_wU","... view full input 200 100.0%

view all inputs
The property-based test has passed, which means the mergeFiles function correctly merges updated files with existing files without creating duplicate file objects for the same path. This test was successfully executed with an example input, ["{\"json\":[[[{\"path\":\"H%AR_wU\",\"content\":\"8ga\"}],[{\"path\":\"{&RFTuJ\",\"content\":\"apply\"}]]]}"], which did not reveal any duplicates in the output array.

Unit Tests
// Unit Test for "Unique Path Enforcement": The output array should not contain duplicate file objects for the same `path`.
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":"H%AR_wU","content":"8ga"}],[{"path":"{&RFTuJ","content":"apply"}]]]}',
  );

  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.

❌ Update Prioritization

Ensure that the file objects in the output array give precedence to entries found in updatedFiles over those in existingFiles when both contain a file object with the same path.

Outcome Example Input # Inputs % of Total
superjson.parse('{"json":[[[{"path":"R9laUNl4Da... view full input 387 96.8%
superjson.parse('{"json":[[[{"path":"a","conten... view full input 13 3.3%

view all inputs
The property test is failing because the mergeFiles function is not correctly giving precedence to entries found in updatedFiles over those in existingFiles when both contain a file object with the same path. In the failed example, the path "a" has a different content in existingFiles ("is") and updatedFiles ("ala"), but the merged result has the content from existingFiles ("aPxaakaaq") instead of the updated one. This is causing the assertion to fail, expecting "V2vC8iaaaey" but receiving "aPxaakaaq".

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

Expected: "V2vC8iaaaey"
Received: "aPxaakaaq"

    at toBe (unknown)
    at <anonymous> (/app/repo/lib/pver_4bc85394-d2cc-4321-8752-16078eaf0d63.test.ts:85:37)
    at forEach (native:1:11)
    at <anonymous> (/app/repo/lib/pver_4bc85394-d2cc-4321-8752-16078eaf0d63.test.ts:79:21)
    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)

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());
}

// Edit existing application using AI SDK and merge results
export async function editApp(
existingFiles: z.infer<typeof benchifyFileSchema>,
editInstruction: string,
): Promise<Array<{ path: string; content: string }>> {
): Promise<z.infer<typeof benchifyFileSchema>> {
console.log("Editing app with instruction: ", editInstruction);
console.log('Existing files:', existingFiles.map(f => ({ path: f.path, contentLength: f.content.length })));

try {
const { elementStream } = streamObject({
Expand All @@ -81,11 +94,60 @@ export async function editApp(
throw new Error("Failed to generate updated files - received empty response");
}

console.log("Generated updated files: ", updatedFiles);
console.log("Generated updated files: ", updatedFiles.map(f => ({ path: f.path, contentLength: f.content.length })));

// Merge the updated files with the existing files
const mergedFiles = mergeFiles(existingFiles, updatedFiles);
console.log('Final merged files:', mergedFiles.map(f => ({ path: f.path, contentLength: f.content.length })));

return updatedFiles;
return mergedFiles;
} catch (error) {
console.error('Error editing app:', error);
throw error;
}
}

// Main function to handle both generation and editing
export async function processAppRequest(
description: string,
existingFiles?: z.infer<typeof benchifyFileSchema>,
editInstruction?: string,
useBuggyCode: boolean = false
): Promise<z.infer<typeof benchifyFileSchema>> {
// Determine if this is an edit request or new generation
if (existingFiles && editInstruction) {
// Edit existing code (including error fixes)
console.log('📝 Processing edit request...');
return await editApp(existingFiles, editInstruction);
} else {
// Generate new app
console.log('🆕 Processing new generation request...');
if (useBuggyCode) {
console.log('🐛 Using buggy code as requested');
// Return the buggy code in the expected format
return [
{
path: "src/App.tsx",
content: `import React from 'react';

const App = () => {
const message = "Hello World; // Missing closing quote
const title = 'Welcome to my app';

return (
<div>
<h1>{title}</h1>
<p>{message}</p>
</div>
);
};

export default App;`
}
];
} else {
console.log('🤖 Calling AI to generate app...');
return await generateApp(description);
}
}
}