Skip to content

Commit

Permalink
Merge pull request #5 from unconv/pyinstaller
Browse files Browse the repository at this point in the history
Improved Windows support and added PyInstaller scripts
  • Loading branch information
unconv authored Jun 23, 2023
2 parents 9b871c9 + b544b66 commit 22d1f01
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 42 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.1] 2023-06-23

### Added

- Support for Windows
- PyInstaller scripts for creating portable versions

### Fixed

- Script sometimes got stuck at "Waiting for ChatGPT..." when the GPT API stalled. Added `request_timeout` parameter to fix this (30 seconds)

## [0.1.0] 2023-06-23

### Added
Expand Down
47 changes: 41 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@

A ChatGPT API powered Python script that can create multi-file applications in any programming language (or any plaintext-based content for that matter). Just tell it what you want to build, and it will build it and ask you clarifying questions along the way.

GPT-AutoPilot uses an iterative process, so after it has accomplished the task, it will ask you if you need some modifications. You can also run the script with an existing project in the `code/` folder and it will make modifications to it based on your prompt. **Note that the AI has the ability to delete and modify files, so have a backup**
GPT-AutoPilot uses an iterative process, so after it has accomplished the task, it will ask you if you need some modifications. You can also run the script with an existing project in the `code` folder and it will make modifications to it based on your prompt. **Note that the AI has the ability to delete and modify files, so have a backup**

## How to use
# Usage

1\. Export your [OpenAI API key](https://platform.openai.com/account/api-keys) as `OPENAI_API_KEY` environment variable or put it in the `config.json` file (see `config.sample.json`)
GPT-AutoPilot works on both Linux and Windows (and probably macOS) and it has standalone packages, that don't need the Python interpreter.

## Linux

1\. Export your [OpenAI API key](https://platform.openai.com/account/api-keys) as `OPENAI_API_KEY` environment variable or put it in the `config.json` file (see `config.sample.json`). You can also run the program directly, and it will ask you for your API key.

```console
$ export OPENAI_API_KEY=YOUR_API_KEY
```

2\. Install the lastest version of the `openai` python package
2\. Install the **latest** version of the `openai` python package
```console
$ pip install --upgrade openai
```
Expand All @@ -25,9 +29,40 @@ $ ./gpt-autopilot.py

4\. For example, tell it to "create a JavaScript inventory application for the browser with a form that can add products with a name, quantity and price. Save the products to localstorage and list them in a table in the application. Calculate the total price of the products in the inventory. Add CSS styles to make the application look professional. Add a Inventory System header and hide the product add form when the page is printed."

The files will be written in the `code/` directory
## Windows: Standalone Package

On Windows, you can download the standalone package, unzip it and run `gpt-autopilot.exe`. It will ask you for your API key.

## Windows: Manual Installation

You can also [download](https://github.com/unconv/gpt-autopilot/archive/refs/heads/master.zip) or clone the repository and install it manually. You need [Python](https://www.python.org/) to be installed on your machine.

After you have downloaded and unzipped, or cloned the repository, go into the `gpt-autopilot` folder and do the following:

1\. Save your [OpenAI API key](https://platform.openai.com/account/api-keys) in the `OPENAI_API_KEY` environment variable or put it in the `config.json` file (see `config.sample.json`). You can also run the program directly, and it will ask you for your API key.

```console
> set OPENAI_API_KEY=YOUR_API_KEY
```

2\. Install the **latest** version of the `openai` python package
```console
> pip install --upgrade openai
```

3\. Run the script. It will ask you for a prompt.

```console
> python gpt-autopilot.py
```

## Where does the output go?

The files will be written to the `code` directory, relative to the path you ran the program from. If you use the `--versions` flag, the files will be written to the `versions` directory.

## Does it work with GPT-3.5?

The default model is `gpt-4-0613` and it works best, but you can still use the `gpt-3.5-turbo-16k-0613` or `gpt-3.5-turbo-0613` model. Just note that it is not as capable. To change, add `"model": "gpt-3.5-turbo-16k-0613"` to the `config.json` file. Make sure to use the 0613 model since only that supports function calling.
The default model is `gpt-4-0613` and it works best, but you can still use the `gpt-3.5-turbo-16k-0613` or `gpt-3.5-turbo-0613` model. Just note that they are not as capable as GPT-4. To change, add `"model": "gpt-3.5-turbo-16k-0613"` to the `config.json` file. Make sure to use the 0613 model since only that supports function calling.

## Multi-version branching

Expand Down
1 change: 1 addition & 0 deletions betterprompter.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def make_better(prompt, model, temp = 1.0):
model=model,
messages=messages,
temperature=temp,
request_timeout=30,
)

return response["choices"][0]["message"]["content"]
5 changes: 4 additions & 1 deletion chatgpt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import time
import json
import sys
import os
import copy

import gpt_functions
Expand Down Expand Up @@ -42,7 +43,8 @@ def send_message(

# save message history
if conv_id is not None:
with open(f"history/{conv_id}.json", "w") as f:
history_file = os.path.join("history", f"{conv_id}.json")
with open(history_file, "w") as f:
f.write(json.dumps(messages, indent=4))

# gpt-3.5 is not responsible enough for these functions
Expand All @@ -66,6 +68,7 @@ def send_message(
functions=gpt_functions.definitions,
function_call=function_call,
temperature=temp,
request_timeout=30,
)
except openai.error.AuthenticationError:
print("AuthenticationError: Check your API-key")
Expand Down
12 changes: 7 additions & 5 deletions gpt-autopilot.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
import copy

import gpt_functions
from helpers import yesno, safepath
from helpers import yesno, safepath, codedir
import chatgpt
import betterprompter
from config import get_config, save_config

VERSION = "0.1.0"
VERSION = "0.1.1"
CONFIG = get_config()

def compact_commands(messages):
Expand Down Expand Up @@ -92,14 +92,16 @@ def actually_write_file(filename, content):
if content != "" and content[-1] != "\n":
content = content + "\n"

fullpath = codedir(filename)

# Create parent directories if they don't exist
parent_dir = os.path.dirname(f"code/{filename}")
parent_dir = os.path.dirname(fullpath)
os.makedirs(parent_dir, exist_ok=True)

if os.path.isdir(f"code/{filename}"):
if os.path.isdir(fullpath):
return "ERROR: There is already a directory with this name"

with open(f"code/{filename}", "w") as f:
with open(fullpath, "w") as f:
f.write(content)

print(f"Wrote to file code/{filename}...")
Expand Down
59 changes: 30 additions & 29 deletions gpt_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import shutil
import subprocess

from helpers import yesno, safepath
from helpers import yesno, safepath, codedir

# Implementation of the functions given to ChatGPT

Expand All @@ -15,14 +15,14 @@ def replace_text(find, replace, filename):
filename = safepath(filename)

if ( len(find) + len(replace) ) > 37:
print(f"FUNCTION: Replacing text in code/{filename}...")
print(f"FUNCTION: Replacing text in {codedir(filename)}...")
else:
print(f"FUNCTION: Replacing '{find}' with '{replace}' in code/{filename}...")
print(f"FUNCTION: Replacing '{find}' with '{replace}' in {codedir(filename)}...")

with open(f"code/{filename}", "r") as f:
with open(codedir(filename), "r") as f:
file_content = f.read()

with open(f"code/{filename}", "w") as f:
with open(codedir(filename), "w") as f:
f.write(
re.sub(find, replace, file_content)
)
Expand All @@ -32,51 +32,51 @@ def replace_text(find, replace, filename):
def append_file(filename, content):
filename = safepath(filename)

print(f"FUNCTION: Appending to file code/{filename}...")
print(f"FUNCTION: Appending to file {codedir(filename)}...")

# Create parent directories if they don't exist
parent_dir = os.path.dirname(f"code/{filename}")
parent_dir = os.path.dirname(codedir(filename))
os.makedirs(parent_dir, exist_ok=True)

with open(f"code/{filename}", "a") as f:
with open(codedir(filename), "a") as f:
f.write(content)
return f"File {filename} appended successfully"

def read_file(filename):
filename = safepath(filename)

print(f"FUNCTION: Reading file code/{filename}...")
if not os.path.exists(f"code/{filename}"):
print(f"FUNCTION: Reading file {codedir(filename)}...")
if not os.path.exists(codedir(filename)):
print(f"File {filename} does not exist")
return f"File {filename} does not exist"
with open(f"code/{filename}", "r") as f:
with open(codedir(filename), "r") as f:
content = f.read()
return f"The contents of '{filename}':\n{content}"

def create_dir(directory):
directory = safepath(directory)

print(f"FUNCTION: Creating directory code/{directory}")
if os.path.exists( "code/"+directory+"/" ):
print(f"FUNCTION: Creating directory {codedir(directory)}")
if os.path.isdir(codedir("directory")):
return "ERROR: Directory exists"
else:
os.mkdir( "code/"+directory )
os.mkdir(codedir("directory"))
return f"Directory {directory} created!"

def move_file(source, destination):
source = safepath(source)
destination = safepath(destination)

print(f"FUNCTION: Move code/{source} to code/{destination}...")
print(f"FUNCTION: Move {codedir(source)} to {codedir(destination)}...")

# Create parent directories if they don't exist
parent_dir = os.path.dirname(f"code/{destination}")
parent_dir = os.path.dirname(codedir(destination))
os.makedirs(parent_dir, exist_ok=True)

try:
shutil.move(f"code/{source}", f"code/{destination}")
shutil.move(codedir(source), codedir(destination))
except:
if os.path.isdir(f"code/{source}") and os.path.isdir(f"code/{destination}"):
if os.path.isdir(codedir(source)) and os.path.isdir(codedir(destination)):
return "ERROR: Destination folder already exists."
return "Unable to move file."

Expand All @@ -86,26 +86,26 @@ def copy_file(source, destination):
source = safepath(source)
destination = safepath(destination)

print(f"FUNCTION: Copy code/{source} to code/{destination}...")
print(f"FUNCTION: Copy {codedir(source)} to {codedir(destination)}...")

# Create parent directories if they don't exist
parent_dir = os.path.dirname(f"code/{destination}")
parent_dir = os.path.dirname(codedir(destination))
os.makedirs(parent_dir, exist_ok=True)

try:
shutil.copy(f"code/{source}", f"code/{destination}")
shutil.copy(codedir(source), codedir(destination))
except:
if os.path.isdir(f"code/{source}") and os.path.isdir(f"code/{destination}"):
if os.path.isdir(codedir(source)) and os.path.isdir(codedir(destination)):
return "ERROR: Destination folder already exists."
return "Unable to copy file."

return f"File {source} copied to {destination}"

def delete_file(filename):
filename = safepath(filename)
path = codedir(filename)

print(f"FUNCTION: Deleting file code/{filename}")
path = f"code/{filename}"
print(f"FUNCTION: Deleting file {path}")

if not os.path.exists(path):
print(f"File {filename} does not exist")
Expand All @@ -123,7 +123,7 @@ def delete_file(filename):

def list_files(list = "", print_output = True):
files_by_depth = {}
directory = "code/"
directory = "code"

for root, _, filenames in os.walk(directory):
depth = str(root[len(directory):].count(os.sep))
Expand All @@ -144,10 +144,10 @@ def list_files(list = "", print_output = True):
break
files.append(filename)

# Remove "code/" from the beginning of file paths
files = [file_path.replace("code/", "", 1) for file_path in files]
# Remove code folder from the beginning of file paths
files = [file_path.replace("code/", "", 1).replace("code\\", "", 1) for file_path in files]

if print_output: print(f"FUNCTION: Files in code/ directory:\n{files}")
if print_output: print(f"FUNCTION: Files in code directory:\n{files}")
return f"The following files are currently in the project directory:\n{files}"

def ask_clarification(question):
Expand All @@ -156,10 +156,11 @@ def ask_clarification(question):

def run_cmd(base_dir, command, reason):
base_dir = safepath(base_dir)
base_dir = base_dir.strip("/").strip("\\")
print("FUNCTION: Run a command")
print("## ChatGPT wants to run a command! ##")

command = "cd code/" + base_dir.strip("/") + "; " + command
command = "cd code" + base_dir + "; " + command
print(f"Command: `{command}`")
print(f"Reason: `{reason}`")

Expand Down
5 changes: 4 additions & 1 deletion helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import sys
import os

def codedir(filename):
return os.path.join("code", filename)

def yesno(prompt, answers = ["y", "n"]):
answer = ""
while answer not in answers:
Expand All @@ -17,7 +20,7 @@ def safepath(path):
file = os.path.abspath(os.path.join(base, path))

if os.path.commonpath([base, file]) != base:
print(f"ERROR: Tried to access file '{file}' outside of code/ folder!")
print(f"ERROR: Tried to access file '{file}' outside of code folder!")
sys.exit(1)

return path
1 change: 1 addition & 0 deletions pyinstaller/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
zip/
7 changes: 7 additions & 0 deletions pyinstaller/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# GPT-AutoPilot PyInstaller

GPT-AutoPilot can be converted into a standalone application with [PyInstaller](https://www.pyinstaller.org/).

This folder has scripts for creating a zip package of the standalone application for different operating systems.

The standalone application will be created for the platform the script is run on.
19 changes: 19 additions & 0 deletions pyinstaller/linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash

# clone and cd into repo
git clone https://github.com/unconv/gpt-autopilot.git
cd gpt-autopilot

# run pyinstaller
pyinstaller gpt-autopilot.py

# add system_message to package
cp system_message dist/gpt-autopilot/

# make zip package
cd dist/
mkdir -p ../../zip/;
zip -r "../../zip/gpt-autopilot-linux.zip" gpt-autopilot

# remove git repo
cd ../../; rm -rf gpt-autopilot/
19 changes: 19 additions & 0 deletions pyinstaller/windows.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@echo off

REM clone and cd into repo
git clone https://github.com/unconv/gpt-autopilot.git
cd gpt-autopilot

REM run pyinstaller
pyinstaller gpt-autopilot.py

REM add system_message to package
copy system_message dist\gpt-autopilot\

REM make zip package using 7-Zip
cd dist
mkdir "..\..\zip\"
7z a "..\..\zip\gpt-autopilot-windows.zip" gpt-autopilot\*

REM remove git repo
cd ..\..\ & rmdir /s /q gpt-autopilot

0 comments on commit 22d1f01

Please sign in to comment.