diff --git a/.gitignore b/.gitignore index 66381f8..2bbd41d 100644 --- a/.gitignore +++ b/.gitignore @@ -161,4 +161,7 @@ cython_debug/ # Logger -/euclid/logger/debug.log \ No newline at end of file +/euclid/logger/debug.log + +#dev +/euclid/brija.py \ No newline at end of file diff --git a/euclid/const/db.py b/euclid/const/db.py index 3354057..dc086ae 100644 --- a/euclid/const/db.py +++ b/euclid/const/db.py @@ -2,4 +2,4 @@ DB_NAME = 'euclid' DB_HOST = 'localhost' DB_PORT = '5432' DB_USER = 'admin' -DB_PASSWORD = 'admin' \ No newline at end of file +DB_PASSWORD = 'admin' diff --git a/euclid/database/database.py b/euclid/database/database.py index 6a9a95d..96bbd75 100644 --- a/euclid/database/database.py +++ b/euclid/database/database.py @@ -1,167 +1,163 @@ -# database.py +from playhouse.shortcuts import model_to_dict +from peewee import * -import psycopg2 -import json -from psycopg2 import sql -from psycopg2.extras import RealDictCursor -from const import db -from logger.logger import logger +from utils.utils import hash_data +from database.models.components.base_models import database +from database.models.user import User +from database.models.app import App +from database.models.project_description import ProjectDescription +from database.models.user_stories import UserStories +from database.models.user_tasks import UserTasks +from database.models.architecture import Architecture +from database.models.development_planning import DevelopmentPlanning +from database.models.development_steps import DevelopmentSteps +from database.models.environment_setup import EnvironmentSetup +from database.models.development import Development -def create_connection(): - conn = psycopg2.connect( - host=db.DB_HOST, - database=db.DB_NAME, - port=db.DB_PORT, - user=db.DB_USER, - password=db.DB_PASSWORD, - cursor_factory=RealDictCursor - ) - return conn - - -def create_tables(): - commands = ( - """ - DROP TABLE IF EXISTS progress_steps; - DROP TABLE IF EXISTS apps; - DROP TABLE IF EXISTS users; - """, - """ - CREATE TABLE users ( - id UUID PRIMARY KEY, - username VARCHAR(255) NOT NULL, - email VARCHAR(255) NOT NULL, - password VARCHAR(255) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP - ) - """, - """ - CREATE TABLE apps ( - id SERIAL PRIMARY KEY, - user_id UUID NOT NULL, - app_id UUID NOT NULL UNIQUE, - app_type VARCHAR(255) NOT NULL, - status VARCHAR(255) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) - REFERENCES users (id) - ON UPDATE CASCADE ON DELETE CASCADE - ) - """, - """ - CREATE TABLE progress_steps ( - id SERIAL PRIMARY KEY, - app_id UUID NOT NULL, - step VARCHAR(255) NOT NULL, - data TEXT, - completed BOOLEAN NOT NULL, - completed_at TIMESTAMP, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (app_id) - REFERENCES apps (app_id) - ON UPDATE CASCADE ON DELETE CASCADE, - UNIQUE (app_id, step) - ) - """) - - conn = None +def save_user(user_id, email="email", password="password"): try: - conn = create_connection() - cur = conn.cursor() - for command in commands: - cur.execute(command) - cur.close() - conn.commit() - except (Exception, psycopg2.DatabaseError) as error: - print(error) - finally: - if conn is not None: - conn.close() + user = User.get(User.id == user_id) + return user + except DoesNotExist: + return User.create(id=user_id, email=email, password=password) def save_app(user_id, app_id, app_type): - conn = create_connection() - cursor = conn.cursor() + try: + app = App.get(App.id == app_id) + except DoesNotExist: + user = save_user(user_id) + app = App.create(id=app_id, user=user, app_type=app_type) - cursor.execute("SELECT * FROM users WHERE id = %s", (str(user_id),)) - if cursor.fetchone() is None: - # If user doesn't exist, create a new user - cursor.execute("INSERT INTO users (id, username, email, password) VALUES (%s, 'username', 'email', 'password')", - (str(user_id),)) - - # Now save or update the app - cursor.execute(""" - INSERT INTO apps (user_id, app_id, app_type, status) - VALUES (%s, %s, %s, 'started') - ON CONFLICT (app_id) DO UPDATE SET - user_id = EXCLUDED.user_id, app_type = EXCLUDED.app_type, status = EXCLUDED.status - RETURNING id - """, (str(user_id), str(app_id), app_type)) - - conn.commit() - cursor.close() - conn.close() - - logger.info('App saved') - - return + return app def save_progress(app_id, step, data): - conn = create_connection() - cursor = conn.cursor() + progress_table_map = { + 'project_description': ProjectDescription, + 'user_stories': UserStories, + 'user_tasks': UserTasks, + 'architecture': Architecture, + 'development_planning': DevelopmentPlanning, + 'environment_setup': EnvironmentSetup, + 'development': Development, + } - # Check if the data is a dictionary. If it is, convert it to a JSON string. - if isinstance(data, dict): - data = json.dumps(data) + data['step'] = step - # INSERT the data, but on conflict (if the app_id and step combination already exists) UPDATE the data - insert = sql.SQL( - """INSERT INTO progress_steps (app_id, step, data, completed) - VALUES (%s, %s, %s, false) - ON CONFLICT (app_id, step) DO UPDATE - SET data = excluded.data, completed = excluded.completed""" - ) + ProgressTable = progress_table_map.get(step) + if not ProgressTable: + raise ValueError(f"Invalid step: {step}") - cursor.execute(insert, (app_id, step, data)) + app = get_app(app_id) - conn.commit() - cursor.close() - conn.close() + # Use the get_or_create method, which attempts to retrieve a record + # or creates a new one if it does not exist. + progress, created = ProgressTable.get_or_create(app=app, defaults=data) + + # If the record was not created, it already existed and we should update it + if not created: + for key, value in data.items(): + setattr(progress, key, value) + progress.save() + + return progress -def get_apps_by_id(app_id): - conn = create_connection() - cursor = conn.cursor() - - cursor.execute("SELECT * FROM apps WHERE app_id = %s", (str(app_id),)) - apps = cursor.fetchall() - - cursor.close() - conn.close() - - return apps +def get_app(app_id): + try: + app = App.get(App.id == app_id) + return app + except DoesNotExist: + raise ValueError(f"No app with id: {app_id}") def get_progress_steps(app_id, step=None): - conn = create_connection() - cursor = conn.cursor() + progress_table_map = { + 'project_description': ProjectDescription, + 'user_stories': UserStories, + 'user_tasks': UserTasks, + 'architecture': Architecture, + 'development_planning': DevelopmentPlanning, + 'environment_setup': EnvironmentSetup, + 'development': Development, + } if step: - cursor.execute("SELECT * FROM progress_steps WHERE app_id = %s AND step = %s", (app_id, step)) + ProgressTable = progress_table_map.get(step) + if not ProgressTable: + raise ValueError(f"Invalid step: {step}") + + try: + progress = ProgressTable.get(ProgressTable.app_id == app_id) + return model_to_dict(progress) + except DoesNotExist: + return None else: - cursor.execute("SELECT * FROM progress_steps WHERE app_id = %s", (app_id,)) - steps = cursor.fetchall() + steps = {} + for step, ProgressTable in progress_table_map.items(): + try: + progress = ProgressTable.get(ProgressTable.app_id == app_id) + steps[step] = model_to_dict(progress) + except DoesNotExist: + steps[step] = None - cursor.close() - conn.close() + return steps - return steps + +def save_development_step(app_id, messages): + app = get_app(app_id) + hash_id = hash_data(messages) + try: + dev_step = DevelopmentSteps.create(app=app, hash_id=hash_id, messages=messages) + except IntegrityError: + print(f"A Development Step with hash_id {hash_id} already exists.") + return None + return dev_step + + +def get_development_step_by_hash_id(hash_id): + try: + dev_step = DevelopmentSteps.get(DevelopmentSteps.hash_id == hash_id) + except DoesNotExist: + print(f"No Development Step found with hash_id {hash_id}") + return None + return dev_step + + +def create_tables(): + with database: + database.create_tables([ + User, + App, + ProjectDescription, + UserStories, + UserTasks, + Architecture, + DevelopmentPlanning, + DevelopmentSteps, + EnvironmentSetup, + Development + ]) + + +def drop_tables(): + with database: + database.drop_tables([ + User, + App, + ProjectDescription, + UserStories, + UserTasks, + Architecture, + DevelopmentPlanning, + DevelopmentSteps, + EnvironmentSetup, + Development + ]) if __name__ == "__main__": + drop_tables() create_tables() diff --git a/euclid/database/models/__init__.py b/euclid/database/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/euclid/database/models/app.py b/euclid/database/models/app.py new file mode 100644 index 0000000..4496ec9 --- /dev/null +++ b/euclid/database/models/app.py @@ -0,0 +1,10 @@ +from peewee import * + +from database.models.components.base_models import BaseModel +from database.models.user import User + + +class App(BaseModel): + user = ForeignKeyField(User, backref='apps') + app_type = CharField() + status = CharField(default='started') \ No newline at end of file diff --git a/euclid/database/models/architecture.py b/euclid/database/models/architecture.py new file mode 100644 index 0000000..07b5c8e --- /dev/null +++ b/euclid/database/models/architecture.py @@ -0,0 +1,9 @@ +from peewee import * + +from database.models.components.progress_step import ProgressStep + + +class Architecture(ProgressStep): + architecture = TextField() + class Meta: + db_table = 'architecture' diff --git a/euclid/database/models/components/__init__.py b/euclid/database/models/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/euclid/database/models/components/base_models.py b/euclid/database/models/components/base_models.py new file mode 100644 index 0000000..6edc650 --- /dev/null +++ b/euclid/database/models/components/base_models.py @@ -0,0 +1,24 @@ +from peewee import * +from datetime import datetime +from uuid import uuid4 + +from const import db + + +# Establish connection to the database +database = PostgresqlDatabase( + db.DB_NAME, + user=db.DB_USER, + password=db.DB_PASSWORD, + host=db.DB_HOST, + port=db.DB_PORT +) + + +class BaseModel(Model): + id = UUIDField(primary_key=True, default=uuid4) + created_at = DateTimeField(default=datetime.now) + updated_at = DateTimeField(default=datetime.now) + + class Meta: + database = database diff --git a/euclid/database/models/components/progress_step.py b/euclid/database/models/components/progress_step.py new file mode 100644 index 0000000..92cb87b --- /dev/null +++ b/euclid/database/models/components/progress_step.py @@ -0,0 +1,16 @@ +from peewee import * + +from playhouse.postgres_ext import BinaryJSONField + +from database.models.components.base_models import BaseModel +from database.models.app import App + + +class ProgressStep(BaseModel): + app = ForeignKeyField(App, primary_key=True) + step = CharField() + data = BinaryJSONField(null=True) + messages = BinaryJSONField(null=True) + app_data = BinaryJSONField() + completed = BooleanField(default=False) + completed_at = DateTimeField(null=True) diff --git a/euclid/database/models/development.py b/euclid/database/models/development.py new file mode 100644 index 0000000..dea5a7e --- /dev/null +++ b/euclid/database/models/development.py @@ -0,0 +1,8 @@ +from peewee import * + +from database.models.components.progress_step import ProgressStep + + +class Development(ProgressStep): + class Meta: + db_table = 'development' diff --git a/euclid/database/models/development_planning.py b/euclid/database/models/development_planning.py new file mode 100644 index 0000000..5f952a9 --- /dev/null +++ b/euclid/database/models/development_planning.py @@ -0,0 +1,10 @@ +from peewee import * + +from database.models.components.progress_step import ProgressStep + + +class DevelopmentPlanning(ProgressStep): + architecture = TextField() + + class Meta: + db_table = 'development_planning' diff --git a/euclid/database/models/development_steps.py b/euclid/database/models/development_steps.py new file mode 100644 index 0000000..fd6282f --- /dev/null +++ b/euclid/database/models/development_steps.py @@ -0,0 +1,12 @@ +from peewee import * + +from playhouse.postgres_ext import BinaryJSONField + +from database.models.components.base_models import BaseModel +from database.models.app import App + + +class DevelopmentSteps(BaseModel): + app = ForeignKeyField(App, primary_key=True) + hash_id = CharField(unique=True, null=False) + messages = BinaryJSONField(null=True) diff --git a/euclid/database/models/environment_setup.py b/euclid/database/models/environment_setup.py new file mode 100644 index 0000000..e34d609 --- /dev/null +++ b/euclid/database/models/environment_setup.py @@ -0,0 +1,6 @@ +from database.models.components.progress_step import ProgressStep + + +class EnvironmentSetup(ProgressStep): + class Meta: + db_table = 'environment_setup' diff --git a/euclid/database/models/project_description.py b/euclid/database/models/project_description.py new file mode 100644 index 0000000..21a5bc1 --- /dev/null +++ b/euclid/database/models/project_description.py @@ -0,0 +1,13 @@ +from peewee import * + +from playhouse.postgres_ext import BinaryJSONField + +from database.models.components.progress_step import ProgressStep + + +class ProjectDescription(ProgressStep): + prompt = TextField() + summary = TextField() + + class Meta: + db_table = 'project_description' diff --git a/euclid/database/models/user.py b/euclid/database/models/user.py new file mode 100644 index 0000000..556a11f --- /dev/null +++ b/euclid/database/models/user.py @@ -0,0 +1,8 @@ +from peewee import * + +from database.models.components.base_models import BaseModel + + +class User(BaseModel): + email = CharField(unique=True) + password = CharField() diff --git a/euclid/database/models/user_stories.py b/euclid/database/models/user_stories.py new file mode 100644 index 0000000..7c46d31 --- /dev/null +++ b/euclid/database/models/user_stories.py @@ -0,0 +1,9 @@ +from peewee import * + +from database.models.components.progress_step import ProgressStep + + +class UserStories(ProgressStep): + user_stories = TextField() + class Meta: + db_table = 'user_stories' diff --git a/euclid/database/models/user_tasks.py b/euclid/database/models/user_tasks.py new file mode 100644 index 0000000..6040f10 --- /dev/null +++ b/euclid/database/models/user_tasks.py @@ -0,0 +1,9 @@ +from peewee import * + +from database.models.components.progress_step import ProgressStep + + +class UserTasks(ProgressStep): + user_tasks = TextField() + class Meta: + db_table = 'user_tasks' diff --git a/euclid/main.py b/euclid/main.py index 82804c5..f4afd5e 100644 --- a/euclid/main.py +++ b/euclid/main.py @@ -10,9 +10,7 @@ from steps.project_description.project_description import get_project_descriptio from steps.user_stories.user_stories import get_user_stories from steps.user_tasks.user_tasks import get_user_tasks from steps.architecture.architecture import get_architecture -from steps.development.development import create_development_plan -from steps.development.development import set_up_environment -from steps.development.development import start_development +from steps.development.development import create_development_plan, set_up_environment, start_development def init(): diff --git a/euclid/steps/architecture/architecture.py b/euclid/steps/architecture/architecture.py index 8b0817b..e9a22e9 100644 --- a/euclid/steps/architecture/architecture.py +++ b/euclid/steps/architecture/architecture.py @@ -1,9 +1,8 @@ # user_stories.py -import json from termcolor import colored from const.function_calls import ARCHITECTURE -from utils.utils import execute_step, find_role_from_step, generate_app_data +from utils.utils import execute_step, find_role_from_step, generate_app_data, step_already_finished from database.database import save_progress, get_progress_steps from logger.logger import logger from prompts.prompts import get_additional_info_from_user @@ -15,17 +14,10 @@ def get_architecture(high_level_summary, user_stories, user_tasks, args): convo_architecture = AgentConvo(current_step) # If this app_id already did this step, just get all data from DB and don't ask user again - steps = get_progress_steps(args['app_id'], current_step) - if steps and not execute_step(args['step'], current_step): - first_step = steps[0] - data = json.loads(first_step['data']) - - architecture = data.get('architecture') - - message = f"Architecture already done for this app_id: {args['app_id']}. Moving to next step..." - print(colored(message, "green")) - logger.info(message) - return architecture + step = get_progress_steps(args['app_id'], current_step) + if step and not execute_step(args['step'], current_step): + step_already_finished(args, step) + return step['architecture'] # ARCHITECTURE print(colored(f"Planning project architecture...\n", "green")) diff --git a/euclid/steps/development/development.py b/euclid/steps/development/development.py index 28df7ff..14ce16c 100644 --- a/euclid/steps/development/development.py +++ b/euclid/steps/development/development.py @@ -1,8 +1,7 @@ -import json from termcolor import colored from helpers.AgentConvo import AgentConvo -from utils.utils import execute_step, find_role_from_step, generate_app_data +from utils.utils import execute_step, find_role_from_step, generate_app_data, step_already_finished from database.database import save_progress, get_progress_steps from logger.logger import logger from const.function_calls import FILTER_OS_TECHNOLOGIES, DEVELOPMENT_PLAN @@ -10,6 +9,7 @@ from const.code_execution import MAX_COMMAND_DEBUG_TRIES from utils.utils import get_os_info from helpers.cli import execute_command + def environment_setup(): # env_setup/specs.prompt # loop through returned array @@ -26,19 +26,21 @@ def implement_task(task): # development/task/step/specs.prompt pass + def execute_command_and_check_cli_response(command, timeout, convo): cli_response = execute_command(command, timeout) response = convo.send_message('dev_ops/ran_command.prompt', - { 'cli_response': cli_response, 'command': command }) + {'cli_response': cli_response, 'command': command}) return response + def run_command_until_success(command, timeout, convo): command_executed = False for _ in range(MAX_COMMAND_DEBUG_TRIES): cli_response = execute_command(command, timeout) response = convo.send_message('dev_ops/ran_command.prompt', - {'cli_response': cli_response, 'command': command}) - + {'cli_response': cli_response, 'command': command}) + command_executed = response == 'DONE' if command_executed: break @@ -49,24 +51,17 @@ def run_command_until_success(command, timeout, convo): # TODO ask user to debug and press enter to continue pass + def set_up_environment(technologies, args): current_step = 'environment_setup' convo_os_specific_tech = AgentConvo(current_step) - steps = get_progress_steps(args['app_id'], current_step) - if steps and not execute_step(args['step'], current_step): - first_step = steps[0] - data = json.loads(first_step['data']) - - app_data = data.get('app_data') - if app_data is not None: - args.update(app_data) - - message = f"Environment setup is already done for this app_id: {args['app_id']}. Moving to next step..." - print(colored(message, "green")) - logger.info(message) + # If this app_id already did this step, just get all data from DB and don't ask user again + step = get_progress_steps(args['app_id'], current_step) + if step and not execute_step(args['step'], current_step): + step_already_finished(args, step) return - + # ENVIRONMENT SETUP print(colored(f"Setting up the environment...\n", "green")) logger.info(f"Setting up the environment...") @@ -77,69 +72,71 @@ def set_up_environment(technologies, args): os_info = get_os_info() os_specific_techologies = convo_os_specific_tech.send_message('development/env_setup/specs.prompt', - { "os_info": os_info, "technologies": technologies }, FILTER_OS_TECHNOLOGIES) + {"os_info": os_info, "technologies": technologies}, + FILTER_OS_TECHNOLOGIES) for technology in os_specific_techologies: llm_response = convo_os_specific_tech.send_message('development/env_setup/install_next_technology.prompt', - { 'technology': technology}, { - 'definitions': [{ - 'name': 'execute_command', - 'description': f'Executes a command that should check if {technology} is installed on the machine. ', - 'parameters': { - 'type': 'object', - 'properties': { - 'command': { - 'type': 'string', - 'description': f'Command that needs to be executed to check if {technology} is installed on the machine.', - }, - 'timeout': { - 'type': 'number', - 'description': f'Timeout in seconds for the approcimate time this command takes to finish.', - } - }, - 'required': ['command', 'timeout'], - }, - }], - 'functions': { - 'execute_command': execute_command_and_check_cli_response - }, - 'send_convo': True - }) - + {'technology': technology}, { + 'definitions': [{ + 'name': 'execute_command', + 'description': f'Executes a command that should check if {technology} is installed on the machine. ', + 'parameters': { + 'type': 'object', + 'properties': { + 'command': { + 'type': 'string', + 'description': f'Command that needs to be executed to check if {technology} is installed on the machine.', + }, + 'timeout': { + 'type': 'number', + 'description': f'Timeout in seconds for the approcimate time this command takes to finish.', + } + }, + 'required': ['command', 'timeout'], + }, + }], + 'functions': { + 'execute_command': execute_command_and_check_cli_response + }, + 'send_convo': True + }) + if not llm_response == 'DONE': - installation_commands = convo_os_specific_tech.send_message('development/env_setup/unsuccessful_installation.prompt', - { 'technology': technology }, { - 'definitions': [{ - 'name': 'execute_commands', - 'description': f'Executes a list of commands that should install the {technology} on the machine. ', - 'parameters': { - 'type': 'object', - 'properties': { - 'commands': { - 'type': 'array', - 'description': f'List of commands that need to be executed to install {technology} on the machine.', - 'items': { - 'type': 'object', - 'properties': { - 'command': { - 'type': 'string', - 'description': f'Command that needs to be executed as a step to install {technology} on the machine.', - }, - 'timeout': { - 'type': 'number', - 'description': f'Timeout in seconds for the approcimate time this command takes to finish.', + installation_commands = convo_os_specific_tech.send_message( + 'development/env_setup/unsuccessful_installation.prompt', + {'technology': technology}, { + 'definitions': [{ + 'name': 'execute_commands', + 'description': f'Executes a list of commands that should install the {technology} on the machine. ', + 'parameters': { + 'type': 'object', + 'properties': { + 'commands': { + 'type': 'array', + 'description': f'List of commands that need to be executed to install {technology} on the machine.', + 'items': { + 'type': 'object', + 'properties': { + 'command': { + 'type': 'string', + 'description': f'Command that needs to be executed as a step to install {technology} on the machine.', + }, + 'timeout': { + 'type': 'number', + 'description': f'Timeout in seconds for the approcimate time this command takes to finish.', + } } } } - } + }, + 'required': ['commands'], }, - 'required': ['commands'], - }, - }], - 'functions': { - 'execute_commands': lambda commands: commands - } - }) + }], + 'functions': { + 'execute_commands': lambda commands: commands + } + }) if installation_commands is not None: for cmd in installation_commands: run_command_until_success(cmd['command'], cmd['timeout'], convo_os_specific_tech) @@ -147,50 +144,45 @@ def set_up_environment(technologies, args): logger.info('The entire tech stack neede is installed and ready to be used.') save_progress(args['app_id'], current_step, { - "os_specific_techologies": os_specific_techologies, "newly_installed_technologies": [], "app_data": generate_app_data(args) + "os_specific_techologies": os_specific_techologies, "newly_installed_technologies": [], + "app_data": generate_app_data(args) }) - # ENVIRONMENT SETUP END + def create_development_plan(high_level_summary, user_stories, user_tasks, technologies_to_use, args): current_step = 'development_planning' convo_development_plan = AgentConvo(current_step) - steps = get_progress_steps(args['app_id'], current_step) - if steps and not execute_step(args['step'], current_step): - first_step = steps[0] - data = json.loads(first_step['data']) + # If this app_id already did this step, just get all data from DB and don't ask user again + step = get_progress_steps(args['app_id'], current_step) + if step and not execute_step(args['step'], current_step): + step_already_finished(args, step) + return step['development_plan'] - app_data = data.get('app_data') - if app_data is not None: - args.update(app_data) - - message = f"Plan for development is already done for this app_id: {args['app_id']}. Moving to next step..." - print(colored(message, "green")) - logger.info(message) - return data.get('development_plan') - # DEVELOPMENT PLANNING print(colored(f"Starting to create the action plan for development...\n", "green")) logger.info(f"Starting to create the action plan for development...") # TODO add clarifications development_plan = convo_development_plan.send_message('development/plan.prompt', - { - "app_summary": high_level_summary, - "clarification": [], - "user_stories": user_stories, - "user_tasks": user_tasks, - "technologies": technologies_to_use - }, DEVELOPMENT_PLAN) + { + "app_summary": high_level_summary, + "clarification": [], + "user_stories": user_stories, + "user_tasks": user_tasks, + "technologies": technologies_to_use + }, DEVELOPMENT_PLAN) logger.info('Plan for development is created.') save_progress(args['app_id'], current_step, { - "development_plan": development_plan, "app_data": generate_app_data(args) + "development_plan": development_plan, + "app_data": generate_app_data(args) }) return development_plan + def start_development(user_stories, user_tasks, technologies_to_use, args): - pass \ No newline at end of file + pass diff --git a/euclid/steps/project_description/project_description.py b/euclid/steps/project_description/project_description.py index 3ba5cff..12991e3 100644 --- a/euclid/steps/project_description/project_description.py +++ b/euclid/steps/project_description/project_description.py @@ -1,11 +1,8 @@ # project_description.py -import json -from termcolor import colored from helpers.AgentConvo import AgentConvo -from logger.logger import logger from database.database import save_progress, save_app, get_progress_steps -from utils.utils import execute_step, generate_app_data +from utils.utils import execute_step, generate_app_data, step_already_finished from prompts.prompts import ask_for_app_type, ask_for_main_app_definition, get_additional_info_from_openai, \ generate_messages_from_description @@ -15,20 +12,10 @@ def get_project_description(args): convo_project_description = AgentConvo(current_step) # If this app_id already did this step, just get all data from DB and don't ask user again - steps = get_progress_steps(args['app_id'], current_step) - if steps and not execute_step(args['step'], current_step): - first_step = steps[0] - data = json.loads(first_step['data']) - - summary = data.get('summary') - app_data = data.get('app_data') - args.update(app_data) - - message = f"Project summary already done for this app_id: {args['app_id']}. Moving to next step..." - print(colored(message, "green")) - logger.info(message) - - return summary, data.get('messages') + step = get_progress_steps(args['app_id'], current_step) + if step and not execute_step(args['step'], current_step): + step_already_finished(args, step) + return step['summary'], step['messages'] # PROJECT DESCRIPTION args['app_type'] = ask_for_app_type() @@ -45,7 +32,11 @@ def get_project_description(args): [f"{msg['role']}: {msg['content']}" for msg in high_level_messages])}) save_progress(args['app_id'], current_step, - {"messages": high_level_messages, "summary": high_level_summary, "app_data": generate_app_data(args) + { + "prompt": description, + "messages": high_level_messages, + "summary": high_level_summary, + "app_data": generate_app_data(args) }) return high_level_summary, high_level_messages diff --git a/euclid/steps/user_stories/user_stories.py b/euclid/steps/user_stories/user_stories.py index e0affdc..c4e987e 100644 --- a/euclid/steps/user_stories/user_stories.py +++ b/euclid/steps/user_stories/user_stories.py @@ -3,40 +3,30 @@ import json from termcolor import colored from helpers.AgentConvo import AgentConvo -from utils.utils import execute_step, find_role_from_step, generate_app_data +from utils.utils import execute_step, find_role_from_step, generate_app_data, step_already_finished from database.database import save_progress, get_progress_steps from logger.logger import logger from prompts.prompts import get_additional_info_from_user from const.function_calls import USER_STORIES -def get_user_stories(summary, args): +def get_user_stories(prompt, args): current_step = 'user_stories' convo_user_stories = AgentConvo(current_step) - + # If this app_id already did this step, just get all data from DB and don't ask user again - steps = get_progress_steps(args['app_id'], current_step) - if steps and not execute_step(args['step'], current_step): - first_step = steps[0] - data = json.loads(first_step['data']) - - user_stories = data.get('user_stories') - app_data = data.get('app_data') - if app_data is not None: - args.update(app_data) - - message = f"User stories already done for this app_id: {args['app_id']}. Moving to next step..." - print(colored(message, "green")) - logger.info(message) - return user_stories, data.get('messages') + step = get_progress_steps(args['app_id'], current_step) + if step and not execute_step(args['step'], current_step): + step_already_finished(args, step) + return step['user_stories'], step['messages'] # USER STORIES print(colored(f"Generating user stories...\n", "green")) logger.info(f"Generating user stories...") user_stories = convo_user_stories.send_message('user_stories/specs.prompt', - {'prompt': summary, 'app_type': args['app_type']}, - USER_STORIES) + {'prompt': prompt, 'app_type': args['app_type']}, + USER_STORIES) logger.info(user_stories) user_stories = get_additional_info_from_user(user_stories, 'product_owner') @@ -44,8 +34,10 @@ def get_user_stories(summary, args): logger.info(f"Final user stories: {user_stories}") save_progress(args['app_id'], current_step, { - "messages": convo_user_stories.get_messages(), "user_stories": user_stories, "app_data": generate_app_data(args) + "messages": convo_user_stories.get_messages(), + "user_stories": user_stories, + "app_data": generate_app_data(args) }) return user_stories, convo_user_stories - # USER STORIES END \ No newline at end of file + # USER STORIES END diff --git a/euclid/steps/user_tasks/user_tasks.py b/euclid/steps/user_tasks/user_tasks.py index 7925d5a..30b91c7 100644 --- a/euclid/steps/user_tasks/user_tasks.py +++ b/euclid/steps/user_tasks/user_tasks.py @@ -1,9 +1,7 @@ # user_tasks.py -import json from termcolor import colored -from helpers.AgentConvo import AgentConvo -from utils.utils import execute_step, find_role_from_step, generate_app_data +from utils.utils import execute_step, find_role_from_step, generate_app_data, step_already_finished from database.database import save_progress, get_progress_steps from logger.logger import logger from prompts.prompts import get_additional_info_from_user @@ -14,19 +12,10 @@ def get_user_tasks(convo, args): current_step = 'user_tasks' # If this app_id already did this step, just get all data from DB and don't ask user again - steps = get_progress_steps(args['app_id'], current_step) - if steps and not execute_step(args['step'], current_step): - first_step = steps[0] - data = json.loads(first_step['data']) - - app_data = data.get('app_data') - if app_data is not None: - args.update(app_data) - - message = f"User tasks already done for this app_id: {args['app_id']}. Moving to next step..." - print(colored(message, "green")) - logger.info(message) - return data.get('user_tasks') + step = get_progress_steps(args['app_id'], current_step) + if step and not execute_step(args['step'], current_step): + step_already_finished(args, step) + return step['user_tasks'] # USER TASKS print(colored(f"Generating user tasks...\n", "green")) @@ -41,7 +30,9 @@ def get_user_tasks(convo, args): logger.info(f"Final user tasks: {user_tasks}") save_progress(args['app_id'], current_step, { - "messages": convo.get_messages(),"user_tasks": user_tasks, "app_data": generate_app_data(args) + "messages": convo.get_messages(), + "user_tasks": user_tasks, + "app_data": generate_app_data(args) }) return user_tasks diff --git a/euclid/utils/utils.py b/euclid/utils/utils.py index db32465..56ecb4d 100644 --- a/euclid/utils/utils.py +++ b/euclid/utils/utils.py @@ -5,11 +5,15 @@ import os import platform import distro import uuid +import json +import hashlib import re from jinja2 import Environment, FileSystemLoader +from termcolor import colored from const.llm import MAX_QUESTIONS, END_RESPONSE from const.common import ROLES, STEPS +from logger.logger import logger def get_arguments(): @@ -144,5 +148,18 @@ def execute_step(matching_step, current_step): return matching_step_index is not None and current_step_index is not None and current_step_index >= matching_step_index +def step_already_finished(args, step): + args.update(step['app_data']) + + message = f"{capitalize_first_word_with_underscores(step['step'])} already done for this app_id: {args['app_id']}. Moving to next step..." + print(colored(message, "green")) + logger.info(message) + + def generate_app_data(args): return {'app_id': args['app_id'], 'app_type': args['app_type']} + + +def hash_data(data): + serialized_data = json.dumps(data, sort_keys=True).encode('utf-8') + return hashlib.sha256(serialized_data).hexdigest() diff --git a/requirements.txt b/requirements.txt index f7bd36c..4f0caeb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ distro==1.8.0 idna==3.4 Jinja2==3.1.2 MarkupSafe==2.1.3 +peewee==3.16.2 prompt-toolkit==3.0.39 psycopg2==2.9.6 python-dotenv==1.0.0