Merge remote-tracking branch 'origin/main' into feature/get_email-from-gitconfig

# Conflicts:
#	pilot/utils/arguments.py
This commit is contained in:
Nicholas Albion
2023-09-09 10:54:02 +10:00
20 changed files with 187 additions and 38 deletions

43
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Test & QA
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, 3.10, 3.11]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Lint
run: |
pip install flake8
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# stop the build if there are Python syntax errors or undefined names
#ruff --format=github --select=E9,F63,F7,F82 --target-version=py37 .
# default set of ruff rules with GitHub Annotations
#ruff --format=github --target-version=py37 .
- name: Run tests
run: |
pip install pytest
pytest

View File

@@ -156,7 +156,7 @@ Other than the research, GPT Pilot needs to be debugged to work in different sce
# 🔗 Connect with us
🌟 As an open source tool, it would mean the world to us if you starred the GPT-pilot repo 🌟
💬 Join [the Discord server](https://discord.gg/FWnRZdCb) to get in touch.
💬 Join [the Discord server](https://discord.gg/HaqXugmxr9) to get in touch.
<br><br>
<br><br>

View File

@@ -1,4 +1,4 @@
APP_TYPES = ['Web App', 'Script', 'Mobile App (unavailable)', 'Chrome Extension (unavailable)']
APP_TYPES = ['Web App', 'Script', 'Mobile App', 'Chrome Extension']
ROLES = {
'product_owner': ['project_description', 'user_stories', 'user_tasks'],
'architect': ['architecture'],

View File

@@ -23,6 +23,7 @@ from database.models.environment_setup import EnvironmentSetup
from database.models.development import Development
from database.models.file_snapshot import FileSnapshot
from database.models.command_runs import CommandRuns
from database.models.user_apps import UserApps
from database.models.user_inputs import UserInputs
from database.models.files import File
@@ -84,6 +85,16 @@ def save_app(args):
return app
def save_user_app(user_id, app_id, workspace):
try:
user_app = UserApps.get((UserApps.user == user_id) & (UserApps.app == app_id))
user_app.workspace = workspace
user_app.save()
except DoesNotExist:
user_app = UserApps.create(user=user_id, app=app_id, workspace=workspace)
return user_app
def save_progress(app_id, step, data):
progress_table_map = {
'project_description': ProjectDescription,
@@ -124,6 +135,14 @@ def get_app(app_id):
raise ValueError(f"No app with id: {app_id}")
def get_app_by_user_workspace(user_id, workspace):
try:
user_app = UserApps.get((UserApps.user == user_id) & (UserApps.workspace == workspace))
return user_app.app
except DoesNotExist:
return None
def get_progress_steps(app_id, step=None):
progress_table_map = {
'project_description': ProjectDescription,
@@ -309,7 +328,7 @@ def get_all_connected_steps(step, previous_step_field_name):
def delete_all_app_development_data(app):
models = [DevelopmentSteps, CommandRuns, UserInputs, File, FileSnapshot]
models = [DevelopmentSteps, CommandRuns, UserInputs, UserApps, File, FileSnapshot]
for model in models:
model.delete().where(model.app == app).execute()
@@ -354,6 +373,7 @@ def create_tables():
Development,
FileSnapshot,
CommandRuns,
UserApps,
UserInputs,
File,
])
@@ -374,10 +394,11 @@ def drop_tables():
Development,
FileSnapshot,
CommandRuns,
UserApps,
UserInputs,
File,
]:
if DATABASE_TYPE == "postgresql":
if DATABASE_TYPE == "postgres":
sql = f'DROP TABLE IF EXISTS "{table._meta.table_name}" CASCADE'
elif DATABASE_TYPE == "sqlite":
sql = f'DROP TABLE IF EXISTS "{table._meta.table_name}"'
@@ -423,7 +444,7 @@ def create_database():
def tables_exist():
tables = [User, App, ProjectDescription, UserStories, UserTasks, Architecture, DevelopmentPlanning,
DevelopmentSteps, EnvironmentSetup, Development, FileSnapshot, CommandRuns, UserInputs, File]
DevelopmentSteps, EnvironmentSetup, Development, FileSnapshot, CommandRuns, UserApps, UserInputs, File]
if DATABASE_TYPE == "postgres":
for table in tables:

View File

@@ -0,0 +1,18 @@
from peewee import *
from database.models.components.base_models import BaseModel
from database.models.app import App
from database.models.user import User
class UserApps(BaseModel):
id = AutoField()
app = ForeignKeyField(App, on_delete='CASCADE')
user = ForeignKeyField(User, on_delete='CASCADE')
workspace = CharField(null=True)
class Meta:
db_table = 'user_apps'
indexes = (
(('app', 'user'), True),
)

View File

@@ -25,7 +25,7 @@ class Project:
Initialize a project.
Args:
args (dict): Project arguments.
args (dict): Project arguments - app_id, (app_type, name), user_id, email, password, step
name (str, optional): Project name. Default is None.
description (str, optional): Project description. Default is None.
user_stories (list, optional): List of user stories. Default is None.
@@ -97,7 +97,7 @@ class Project:
# TODO END
self.developer = Developer(self)
self.developer.set_up_environment();
self.developer.set_up_environment()
self.developer.start_coding()

View File

@@ -40,6 +40,7 @@ class Developer(Agent):
convo_dev_task = AgentConvo(self)
task_description = convo_dev_task.send_message('development/task/breakdown.prompt', {
"name": self.project.args['name'],
"app_type": self.project.args['app_type'],
"app_summary": self.project.project_description,
"clarification": [],
"user_stories": self.project.user_stories,
@@ -134,6 +135,7 @@ class Developer(Agent):
iteration_convo = AgentConvo(self)
iteration_convo.send_message('development/iteration.prompt', {
"name": self.project.args['name'],
"app_type": self.project.args['app_type'],
"app_summary": self.project.project_description,
"clarification": [],
"user_stories": self.project.user_stories,
@@ -175,7 +177,12 @@ class Developer(Agent):
os_info = get_os_info()
os_specific_techologies = self.convo_os_specific_tech.send_message('development/env_setup/specs.prompt',
{ "name": self.project.args['name'], "os_info": os_info, "technologies": self.project.architecture }, FILTER_OS_TECHNOLOGIES)
{
"name": self.project.args['name'],
"app_type": self.project.args['app_type'],
"os_info": os_info,
"technologies": self.project.architecture
}, FILTER_OS_TECHNOLOGIES)
for technology in os_specific_techologies:
# TODO move the functions definisions to function_calls.py
@@ -248,7 +255,7 @@ class Developer(Agent):
'step_type': type,
'directory_tree': directory_tree,
'step_index': step_index
}, EXECUTE_COMMANDS);
}, EXECUTE_COMMANDS)
if type == 'COMMAND':
for cmd in step_details:
run_command_until_success(cmd['command'], cmd['timeout'], convo)

View File

@@ -24,16 +24,17 @@ class ProductOwner(Agent):
step = get_progress_steps(self.project.args['app_id'], self.project.current_step)
if step and not execute_step(self.project.args['step'], self.project.current_step):
step_already_finished(self.project.args, step)
self.project.root_path = setup_workspace(self.project.args['name'])
self.project.root_path = setup_workspace(self.project.args)
self.project.project_description = step['summary']
self.project.project_description_messages = step['messages']
return
# PROJECT DESCRIPTION
self.project.args['app_type'] = ask_for_app_type()
self.project.args['name'] = clean_filename(ask_user(self.project, 'What is the project name?'))
if 'name' not in self.project.args:
self.project.args['name'] = clean_filename(ask_user(self.project, 'What is the project name?'))
self.project.root_path = setup_workspace(self.project.args['name'])
self.project.root_path = setup_workspace(self.project.args)
self.project.app = save_app(self.project.args)
@@ -45,7 +46,9 @@ class ProductOwner(Agent):
print(colored('Project Summary:\n', 'green', attrs=['bold']))
high_level_summary = convo_project_description.send_message('utils/summary.prompt',
{'conversation': '\n'.join([f"{msg['role']}: {msg['content']}" for msg in high_level_messages])})
{'conversation': '\n'.join(
[f"{msg['role']}: {msg['content']}" for msg in
high_level_messages])})
save_progress(self.project.args['app_id'], self.project.current_step, {
"prompt": main_prompt,
@@ -59,7 +62,6 @@ class ProductOwner(Agent):
return
# PROJECT DESCRIPTION END
def get_user_stories(self):
self.project.current_step = 'user_stories'
self.convo_user_stories = AgentConvo(self)
@@ -111,7 +113,7 @@ class ProductOwner(Agent):
logger.info(msg)
self.project.user_tasks = self.convo_user_stories.continuous_conversation('user_stories/user_tasks.prompt',
{ 'END_RESPONSE': END_RESPONSE })
{'END_RESPONSE': END_RESPONSE})
logger.info(f"Final user tasks: {self.project.user_tasks}")

View File

@@ -36,6 +36,7 @@ class TechLead(Agent):
self.development_plan = self.convo_development_plan.send_message('development/plan.prompt',
{
"name": self.project.args['name'],
"app_type": self.project.args['app_type'],
"app_summary": self.project.project_description,
"clarification": [],
"user_stories": self.project.user_stories,

View File

@@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
import sys
from dotenv import load_dotenv
from termcolor import colored
load_dotenv()
from helpers.Project import Project
@@ -38,6 +39,9 @@ if __name__ == "__main__":
except KeyboardInterrupt:
exit_gpt_pilot()
except Exception as e:
print(colored('---------- GPT PILOT EXITING WITH ERROR ----------', 'red'))
print(colored(e, 'red'))
print(colored('--------------------------------------------------', 'red'))
exit_gpt_pilot()
finally:
sys.exit(0)

View File

@@ -1,4 +1,4 @@
You are working in a software development agency and a project manager and software architect approach you telling you that you're assigned to work on a new project. You are working on a web app called "{{ name }}" and your first job is to set up the environment on a computer.
You are working in a software development agency and a project manager and software architect approach you telling you that you're assigned to work on a new project. You are working on a {{ app_type }} called "{{ name }}" and your first job is to set up the environment on a computer.
Here are the technologies that you need to use for this project:
```

View File

@@ -1,4 +1,4 @@
You are working on a web app called "{{ name }}" and you need to write code for the entire application.
You are working on a {{ app_type }} called "{{ name }}" and you need to write code for the entire application.
Here is a high level description of "{{ name }}":
```

View File

@@ -1,4 +1,4 @@
You are working in a software development agency and a project manager and software architect approach you telling you that you're assigned to work on a new project. You are working on a web app called "{{ name }}" and you need to create a detailed development plan so that developers can start developing the app.
You are working in a software development agency and a project manager and software architect approach you telling you that you're assigned to work on a new project. You are working on a {{ app_type }} called "{{ name }}" and you need to create a detailed development plan so that developers can start developing the app.
Here is a high level description of "{{ name }}":
```

View File

@@ -1,4 +1,4 @@
You are working on a web app called "{{ name }}" and you need to write code for the entire application based on the tasks that the tech lead gives you. So that you understand better what you're working on, you're given other specs for "{{ name }}" as well.
You are working on a {{ app_type }} called "{{ name }}" and you need to write code for the entire application based on the tasks that the tech lead gives you. So that you understand better what you're working on, you're given other specs for "{{ name }}" as well.
Here is a high level description of "{{ name }}":
```

View File

@@ -12,7 +12,7 @@ from logger.logger import logger
def ask_for_app_type():
return 'app'
return 'Web App'
answer = styled_select(
"What type of app do you want to build?",
choices=common.APP_TYPES

View File

@@ -1,4 +1,4 @@
You are an experienced software architect. Your expertise is in creating an architecture for an MVP (minimum viable products) for web apps that can be developed as fast as possible by using as many ready-made technologies as possible. The technologies that you prefer using when other technologies are not explicitly specified are:
You are an experienced software architect. Your expertise is in creating an architecture for an MVP (minimum viable products) for {{ app_type }}s that can be developed as fast as possible by using as many ready-made technologies as possible. The technologies that you prefer using when other technologies are not explicitly specified are:
**Scripts**: you prefer using Node.js for writing scripts that are meant to be ran just with the CLI.
**Backend**: you prefer using Node.js with Mongo database if not explicitely specified otherwise. When you're using Mongo, you always use Mongoose and when you're using Postgresql, you always use PeeWee as an ORM.

View File

@@ -1,12 +1,11 @@
import getpass
import hashlib
import os
import re
import sys
import uuid
from getpass import getuser
from termcolor import colored
from database.database import get_app
from database.database import get_app, get_app_by_user_workspace
def get_arguments():
@@ -25,21 +24,38 @@ def get_arguments():
else:
arguments[arg] = True
if 'user_id' not in arguments:
arguments['user_id'] = username_to_uuid(getuser())
app = None
if 'workspace' in arguments:
app = get_app_by_user_workspace(arguments['user_id'], arguments['workspace'])
if app is not None:
arguments['app_id'] = app.id
else:
arguments['workspace'] = None
if 'app_id' in arguments:
try:
app = get_app(arguments['app_id'])
arguments['user_id'] = str(app.user.id)
if app is None:
app = get_app(arguments['app_id'])
arguments['app_type'] = app.app_type
arguments['name'] = app.name
# Add any other fields from the App model you wish to include
print(colored('\n------------------ LOADING PROJECT ----------------------', 'green', attrs=['bold']))
print(colored(f'{app.name} (app_id={arguments["app_id"]})', 'green', attrs=['bold']))
print(colored('--------------------------------------------------------------\n', 'green', attrs=['bold']))
except ValueError as e:
print(e)
# Handle the error as needed, possibly exiting the script
else:
arguments['app_id'] = str(uuid.uuid4())
if 'user_id' not in arguments:
arguments['user_id'] = getpass.getuser()
print(colored('\n------------------ STARTING NEW PROJECT ----------------------', 'green', attrs=['bold']))
print(f"If you wish to continue with this project in future run:")
print(colored(f'python {sys.argv[0]} app_id={arguments["app_id"]}', 'green', attrs=['bold']))
print(colored('--------------------------------------------------------------\n', 'green', attrs=['bold']))
if 'email' not in arguments:
arguments['email'] = get_email()
@@ -50,10 +66,6 @@ def get_arguments():
if 'step' not in arguments:
arguments['step'] = None
print(colored('\n------------------ STARTING NEW PROJECT ----------------------', 'green', attrs=['bold']))
print(f"If you wish to continue with this project in future run:")
print(colored(f'python main.py app_id={arguments["app_id"]}', 'green', attrs=['bold']))
print(colored('--------------------------------------------------------------\n', 'green', attrs=['bold']))
return arguments
@@ -75,3 +87,10 @@ def get_email():
# todo change email so its not uuid4 but make sure to fix storing of development steps where
# 1 user can have multiple apps. In that case each app should have its own development steps
return str(uuid.uuid4())
# TODO can we make BaseModel.id a CharField with default=uuid4?
def username_to_uuid(username):
sha1 = hashlib.sha1(username.encode()).hexdigest()
uuid_str = "{}-{}-{}-{}-{}".format(sha1[:8], sha1[8:12], sha1[12:16], sha1[16:20], sha1[20:32])
return str(uuid.UUID(uuid_str))

View File

@@ -1,6 +1,6 @@
import os
from pathlib import Path
from database.database import save_user_app
def get_parent_folder(folder_name):
current_path = Path(os.path.abspath(__file__)) # get the path of the current script
@@ -11,10 +11,18 @@ def get_parent_folder(folder_name):
return current_path.parent
def setup_workspace(project_name):
def setup_workspace(args):
if args['workspace'] is not None:
try:
save_user_app(args['user_id'], args['app_id'], args['workspace'])
except Exception as e:
print(str(e))
return args['workspace']
root = get_parent_folder('pilot')
create_directory(root, 'workspace')
project_path = create_directory(os.path.join(root, 'workspace'), project_name)
project_path = create_directory(os.path.join(root, 'workspace'), args['name'])
create_directory(project_path, 'tests')
return project_path

View File

@@ -43,4 +43,4 @@ def get_user_feedback():
config = {
'style': custom_style,
}
return questionary.text("Thank you for trying GPT-Pilot. Please give us your feedback or just press ENTER to exit: ", **config).unsafe_ask()
return questionary.text("How did GPT Pilot do? Were you able to create any app that works? Please write any feedback you have or just press ENTER to exit: ", **config).unsafe_ask()

26
pilot/utils/test_files.py Normal file
View File

@@ -0,0 +1,26 @@
import pytest
from .files import setup_workspace
def test_setup_workspace_with_existing_workspace():
args = {'workspace': 'some_directory', 'name': 'sample'}
result = setup_workspace(args)
assert result == 'some_directory'
def mocked_create_directory(path, exist_ok=True):
return
def mocked_abspath(file):
return "/root_path/pilot/helpers"
def test_setup_workspace_without_existing_workspace(monkeypatch):
args = {'workspace': None, 'name': 'project_name'}
monkeypatch.setattr('os.path.abspath', mocked_abspath)
monkeypatch.setattr('os.makedirs', mocked_create_directory)
result = setup_workspace(args)
assert result.replace('\\', '/') == "/root_path/workspace/project_name"