From 6395f6fba78d43aa3ad5cb81fcd3dfa735c95bd8 Mon Sep 17 00:00:00 2001 From: Zvonimir Sabljic Date: Thu, 3 Aug 2023 20:40:10 +0200 Subject: [PATCH] Added saving command run responses and restoring them --- euclid/database/database.py | 69 ++++++++++++++++++++++++++++++++++++- euclid/helpers/Project.py | 1 + euclid/helpers/cli.py | 34 +++++++++++++----- 3 files changed, 95 insertions(+), 9 deletions(-) diff --git a/euclid/database/database.py b/euclid/database/database.py index d3d6517..5c64905 100644 --- a/euclid/database/database.py +++ b/euclid/database/database.py @@ -15,6 +15,7 @@ from database.models.development_steps import DevelopmentSteps 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 def save_user(user_id, email="email", password="password"): @@ -130,6 +131,58 @@ def save_development_step(app_id, prompt_path, prompt_data, llm_req_num, message return None return dev_step +def get_db_model_from_hash_id(data_to_hash, model, app_id): + hash_id = hash_data(data_to_hash) + try: + db_row = model.get((model.hash_id == hash_id) & (model.app == app_id)) + except DoesNotExist: + return None + return db_row + +def hash_and_save_step(Model, app_id, hash_data_args, data_fields, message): + app = get_app(app_id) + hash_id = hash_data(hash_data_args) + + data_to_insert = { + 'app': app, + 'hash_id': hash_id + } + for field, value in data_fields.items(): + data_to_insert[field] = value + + try: + inserted_id = (Model + .insert(**data_to_insert) + .on_conflict(conflict_target=[Model.app, Model.hash_id], + preserve=[field for field in data_fields.keys()], + update={}) + .execute()) + + record = Model.get_by_id(inserted_id) + print(colored(f"{message} with id {record.id}", "yellow")) + except IntegrityError: + print(f"A record with hash_id {hash_id} already exists for {Model}.") + return None + return record + +def save_command_run(project, command, cli_response): + hash_data_args = { + 'command': command, + 'command_runs_count': project.command_runs_count, + } + data_fields = { + 'command': command, + 'cli_response': cli_response, + } + return hash_and_save_step(CommandRuns, project.args['app_id'], hash_data_args, data_fields, "Saved Command Run") + +def get_command_run_from_hash_id(project, command): + data_to_hash = { + 'command': command, + 'command_runs_count': project.command_runs_count + } + return get_db_model_from_hash_id(data_to_hash, CommandRuns, project.args['app_id']) + def get_development_step_from_hash_id(app_id, prompt_path, prompt_data, llm_req_num): hash_id = hash_data({ @@ -159,12 +212,26 @@ def create_tables(): EnvironmentSetup, Development, FileSnapshot, + CommandRuns, ]) def drop_tables(): with database.atomic(): - for table in [User, App, ProjectDescription, UserStories, UserTasks, Architecture, DevelopmentPlanning, DevelopmentSteps, EnvironmentSetup, Development, FileSnapshot]: + for table in [ + User, + App, + ProjectDescription, + UserStories, + UserTasks, + Architecture, + DevelopmentPlanning, + DevelopmentSteps, + EnvironmentSetup, + Development, + FileSnapshot, + CommandRuns + ]: database.execute_sql(f'DROP TABLE IF EXISTS "{table._meta.table_name}" CASCADE') diff --git a/euclid/helpers/Project.py b/euclid/helpers/Project.py index 90a0045..12d6f71 100644 --- a/euclid/helpers/Project.py +++ b/euclid/helpers/Project.py @@ -17,6 +17,7 @@ class Project: def __init__(self, args, name=None, description=None, user_stories=None, user_tasks=None, architecture=None, development_plan=None, current_step=None): self.args = args self.llm_req_num = 0 + self.command_runs_count = 0 self.skip_steps = False if ('skip_until_dev_step' in args and args['skip_until_dev_step'] == '0') else True self.skip_until_dev_step = args['skip_until_dev_step'] if 'skip_until_dev_step' in args else None # TODO make flexible diff --git a/euclid/helpers/cli.py b/euclid/helpers/cli.py index a79464e..a470b48 100644 --- a/euclid/helpers/cli.py +++ b/euclid/helpers/cli.py @@ -6,6 +6,7 @@ import queue import time from termcolor import colored +from database.database import get_command_run_from_hash_id, save_command_run from utils.questionary import styled_text from const.code_execution import MAX_COMMAND_DEBUG_TRIES @@ -34,22 +35,33 @@ def run_command(command, root_path, q_stdout, q_stderr, pid_container): return process -def execute_command(root_path, command, timeout=5000): +def execute_command(project, command, timeout=5000): + # check if we already have the command run saved + print(colored(f'Can i execute the command: `{command}` with {timeout}ms timeout?', 'white', attrs=['bold'])) + project.command_runs_count += 1 + command_run = get_command_run_from_hash_id(project, command) + if command_run is not None and project.skip_steps: + # if we do, use it + print(colored(f'Restoring command run response id {command_run.id}:\n```\n{command_run.cli_response}```', 'yellow')) + return command_run.cli_response + answer = styled_text( - f'Can i execute the command: `{command}` with {timeout}ms timeout?\n' + 'If yes, just press ENTER and if not, please paste the output of running this command here and press ENTER' ) + + return_value = None + if answer != '': - return answer[-2000:] + return_value = answer[-2000:] q_stderr = queue.Queue() q = queue.Queue() pid_container = [None] - process = run_command(command, root_path, q, q_stderr, pid_container) + process = run_command(command, project.root_path, q, q_stderr, pid_container) output = '' start_time = time.time() - while True: + while True and return_value is None: elapsed_time = time.time() - start_time # Check if process has finished if process.poll() is not None: @@ -79,7 +91,13 @@ def execute_command(root_path, command, timeout=5000): stderr_output = '' while not q_stderr.empty(): stderr_output += q_stderr.get_nowait() - return output[-2000:] if output != '' else stderr_output[-2000:] + + if return_value is None: + return_value = output[-2000:] if output != '' else stderr_output[-2000:] + + command_run = save_command_run(project, command, return_value) + + return return_value def build_directory_tree(path, prefix="", ignore=None, is_last=False): """Build the directory tree structure in tree-like format. @@ -119,7 +137,7 @@ def build_directory_tree(path, prefix="", ignore=None, is_last=False): return output def execute_command_and_check_cli_response(command, timeout, convo): - cli_response = execute_command(command, timeout) + cli_response = execute_command(convo.agent.project, command, timeout) response = convo.send_message('dev_ops/ran_command.prompt', { 'cli_response': cli_response, 'command': command }) return response @@ -127,7 +145,7 @@ def execute_command_and_check_cli_response(command, timeout, convo): def run_command_until_success(command, timeout, convo): command_executed = False for _ in range(MAX_COMMAND_DEBUG_TRIES): - cli_response = execute_command(convo.agent.project.root_path, command, timeout) + cli_response = execute_command(convo.agent.project, command, timeout) response = convo.send_message('dev_ops/ran_command.prompt', {'cli_response': cli_response, 'command': command})