Skip to content

Commit

Permalink
Merge pull request #151 from hatkyinc2/feature/ux
Browse files Browse the repository at this point in the history
Improve UX
  • Loading branch information
hatkyinc2 authored Apr 30, 2023
2 parents cc5b61f + d72e123 commit fce01ae
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 37 deletions.
Binary file modified .autopilot/autopilot.db
Binary file not shown.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ Note: This project is currently operating autonomously on a server. Whenever an

1. You point AutoPilot at a codebase with a task.
1. AutoPilot generates and upkeeps a DB with metadata on the codebase files. (within the codebase directory)
1. AutoPilot decides which exisiting files it needs for the task by using the metadata DB.
1. AutoPilot tries to implement the requested task on each relavent file.
1. AutoPilot decides which existing files it needs for the task by using the metadata DB.
1. AutoPilot tries to implement the requested task on each relevant file.

## Features

- 📚 Pre-processes codebase files.
- 🤖 Does code updates for you.
- 📋 Shows you what was updated. (Full process log with each AI interaction also produced.)
- 🔧 Interactive mode - see the process with retry, continue, abort options.
- 📚 - Pre-processes codebase files.
- 🤖 - Implements code changes for you.
- 🚀 - Parallel calls to agents where possible.
- 📝 - Shows you what was updated. (Full process log with each AI interaction also produced)
- 🕹️ - Interactive mode - see the process with retry, continue, abort options.

### Tasks expectations
- Referencing current code:
Expand All @@ -34,7 +35,8 @@ Note: This project is currently operating autonomously on a server. Whenever an
- ✅ Referencing all project files.
- 🤔 General logical requests. Your milage would differ by model, codebase and task. Some work. (Should introduce task scoring)
- Changes executed:
- ✅Create a new file.
- ✅Create a new file based on an existing file.
- ❌Start a new file from scratch.
- ✅Update an existing file.
- ✅Update multiple existing files.
- ❌Delete existing files. (It might empty them out, but not delete them currently)
Expand Down
1 change: 0 additions & 1 deletion agents/getFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ async function getRelevantFiles(task, summaries) {
const model = getModel(process.env.GET_FILES_MODEL);

const input = await prompt.format({ task, summaries });
console.log(input)
saveLog(`getFiles agent INPUT:\n${input}`)

const response = await model.call(input);
Expand Down
3 changes: 2 additions & 1 deletion modules/interactiveAgent.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const prompts = require('prompts');
const chalk = require('chalk');

/**
* @description Asynchronous function that runs an agent function with given variables.
Expand All @@ -9,7 +10,7 @@ const prompts = require('prompts');
* @returns {Promise<any>} A Promise that resolves with the return value of the agent function if not in interactive mode, otherwise resolves or rejects based on user input.
*/
async function runAgent(agentFunction, var1, var2, interactive=false){
console.log("(agent)", agentFunction.name);
console.log(`Agent ${chalk.yellow(agentFunction.name)} is running.`);
if (!interactive){
return await agentFunction(var1, var2);
}
Expand Down
13 changes: 7 additions & 6 deletions modules/interactiveGapFill.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ async function indexGapFill(codeBaseDirectory, interactive) {
const numberOfGaps = filesToDelete.length + filesToIndex.length;
if (numberOfGaps > 0) {
if (!interactive) {
console.log(chalk.green(`Gap fill: ${numberOfGaps} gaps found, fixing...`));
console.log(chalk.green(`Gap fill: ${chalk.yellow(numberOfGaps)} gaps found, fixing...`));
await gapFill(filesToDelete, codeBaseDirectory, filesToIndex);
} else {
tokenCount = countTokensOfFilesToIndex(filesToIndex);
const { calculateTokensCost } = require('./gpt');
cost = calculateTokensCost(process.env.INDEXER_MODEL, tokenCount, null, tokenCount);

console.log(chalk.yellow(`Gap fill: ${numberOfGaps} gaps found, estimated cost: $${chalk.yellow(cost.toFixed(4))}`));
console.log(chalk.yellow(`Gap fill: ${chalk.yellow(numberOfGaps)} gaps found, estimated cost: $${chalk.yellow(cost.toFixed(4))}`));
if (await approveGapFill()) {
await gapFill(filesToDelete, codeBaseDirectory, filesToIndex);
}
Expand Down Expand Up @@ -83,13 +83,14 @@ async function gapFill(filesToDelete, codeBaseDirectory, filesToIndex) {
const filePathRelative = file.path;
await deleteFile(codeBaseDirectory, filePathRelative);
}
for (const file of filesToIndex) {
const promises = filesToIndex.map(async (file) => {
const filePathRelative = file.filePath;
const filePathFull = path.posix.join(codeBaseDirectory, filePathRelative);
const fileContent = fs.readFileSync(filePathFull, 'utf-8');
console.log(`File modified: ${filePathRelative}`);
const fileContent = await fs.promises.readFile(filePathFull, 'utf-8');
await generateAndWriteFileSummary(codeBaseDirectory, filePathRelative, fileContent);
}
});

await Promise.all(promises);
}

module.exports = { indexGapFill };
10 changes: 3 additions & 7 deletions modules/summaries.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ async function readAllSummaries(codeBaseDirectory) {
}

let summariesString = "";
console.log("Summaries found in the database:", summaries.length);
for (const summary of summaries) {
try {
summariesString += `File Path: ${summary.path}\nSummary:\n${summary.summary}${summaryStringDelimiter}`;
Expand Down Expand Up @@ -120,7 +119,7 @@ async function generateAndWriteFileSummary(codeBaseDirectory, filePathRelative,
const parsedFile = parseFileContent(codeBaseDirectory, filePathFull, fileContent);
const fileTokensCount = parsedFile.fileTokensCount;

console.log(`Processing file: ${filePathRelative}`);
console.log(`Processing file: ${chalk.yellow(filePathRelative)}`);
if (fileTokensCount > maxTokenSingleFile) {
console.log(chalk.red('File too BIG'));
return;
Expand Down Expand Up @@ -150,11 +149,8 @@ async function generateAndWriteFileSummary(codeBaseDirectory, filePathRelative,
}
// Save to DB
insertOrUpdateFile(codeBaseDirectory, parsedFile, summary, dependenciesLibsString);

// TODO: Use logging library that already adds timestamps
const timestamp = new Date().toISOString();
const hour = timestamp.match(/\d\d:\d\d/);
console.log(`${hour}: Updated summary for ${filePathRelative}`);

console.log(`${chalk.green(`Updated summary for `)}${chalk.yellow(filePathRelative)}`);
}
} catch (error) {
console.error(`Error processing file: ${filePathRelative}`, error);
Expand Down
38 changes: 23 additions & 15 deletions ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ const { suggestChanges } = require('./agents/coder');
const { ChangesAdvice } = require('./agents/advisor');
const { getRelevantFiles } = require('./agents/getFiles');

const testingDirectory = '/benchmarks';

/**
*
* @param {string} task - The task to be completed.
Expand All @@ -30,6 +28,7 @@ async function main(task, test=false, suggestionMode) {
let codeBaseDirectory = options.dir;
// TODO: get rid of test parameter, should use normal functionality
if (test){
const testingDirectory = '/benchmarks';
codeBaseDirectory = codeBaseDirectory + testingDirectory
}
const interactive = options.interactive;
Expand Down Expand Up @@ -59,44 +58,53 @@ async function main(task, test=false, suggestionMode) {
// Get the summaries of the files in the directory
const summaries = await getSummaries(codeBaseDirectory);
const chunkedSummaries = chunkSummaries(summaries, maxSummaryTokenCount);
console.log(`Split summaries into ${chalk.yellow(chunkedSummaries.length)} chunks of up to ${chalk.yellow(maxSummaryTokenCount)} tokens each. (an agent would run for each)`)

let relevantFiles=[]
for (const summaries of chunkedSummaries){
const promises = chunkedSummaries.map(async (summaries) => {
// Decide which files are relevant to the task
relevantFilesChunk = await runAgent(getRelevantFiles, task, summaries, interactive);
relevantFiles = relevantFiles.concat(relevantFilesChunk)
}
const relevantFilesChunk = await runAgent(getRelevantFiles, task, summaries, interactive);
return relevantFilesChunk;
});
relevantFiles = await Promise.all(promises).then((results) => {
// Combine all the results into a single array
return results.flat();
});

const filePaths = relevantFiles.map(file => file.path).join(', ');
console.log(`${chalk.yellow(relevantFiles.length)} relevant files were identified by the agent:\n${chalk.yellow(filePaths)}`);

// Fetch code files the agent has deemed relevant
let files;
try {
files = getFiles(codeBaseDirectory, relevantFiles);
} catch (err) {
console.log(chalk.red(`The agent has identified files to fetch we couldn't find, please try again with a different task.`));
console.log(relevantFiles);
// TODO: find which files are the broken ones and only print them.
const fileReasons = relevantFiles.map(file => `${chalk.yellow(file.path)}: ${file.reason}`).join('\n');
console.log(fileReasons);
console.log(`Codebase directory: ${codeBaseDirectory}`)
process.exit(1);
}
if (files.length == 0) {
console.log(`The agent has not identified any relevant files for the task: ${task}.\nPlease try again with a different task.`);
console.log(chalk.red(`The agent has not identified any relevant files for the task: ${task}.\nPlease try again with a different task.`));
process.exit(1);
}

// Ask an agent about each file
let solutions = [];
for (const file of files) {

await Promise.all(files.map(async (file) => {
// For each file, ask the coder agent for a solution
if (!suggestionMode) {
const coderRes = await runAgent(suggestChanges, task, file, interactive);
for (const file of coderRes){
const filePathRelative = file.fileToUpdate;
const fileContent = file.content;
solutions.push({file:filePathRelative, code:fileContent})

if (autoApply){
// This actually applies the solution to the file
const filePathFull = path.posix.join(codeBaseDirectory, filePathRelative);
updateFile(filePathFull, fileContent);
console.log(`File modified: ${filePathRelative}`);
await generateAndWriteFileSummary(codeBaseDirectory, filePathRelative, fileContent);
}
// TODO: get current diff and feed it back to the next agent
Expand All @@ -106,8 +114,8 @@ async function main(task, test=false, suggestionMode) {
const advice = await runAgent(ChangesAdvice, task, {relevantFiles, file}, interactive);
solutions.push({file:file.path, code:advice})
}

}
}));


if (autoApply){
// Sends the saved output to GPT and ask for the necessary changes to do the TASK
Expand Down

0 comments on commit fce01ae

Please sign in to comment.