From cb579d8aba7d10468889de7e1f1729fe69043301 Mon Sep 17 00:00:00 2001 From: Zvonimir Sabljic Date: Tue, 18 Jul 2023 11:17:22 +0200 Subject: [PATCH] Added local files --- euclid/__init__.py | 0 euclid/bin/Activate.ps1 | 247 +++++++++++++++++++ euclid/bin/activate | 69 ++++++ euclid/bin/activate.csh | 26 ++ euclid/bin/activate.fish | 69 ++++++ euclid/bin/dotenv | 8 + euclid/bin/normalizer | 8 + euclid/bin/pip | 8 + euclid/bin/pip3 | 8 + euclid/bin/pip3.11 | 8 + euclid/bin/pygmentize | 8 + euclid/bin/python | 1 + euclid/bin/python3 | 1 + euclid/bin/python3.11 | 1 + euclid/const/common.py | 1 + euclid/const/db.py | 5 + euclid/const/llm.py | 2 + euclid/const/prompts.py | 4 + euclid/database.py | 107 ++++++++ euclid/instructions.comment.py | 65 +++++ euclid/main.py | 104 ++++++++ euclid/prompts/breakdown_1_app_type.pt | 1 + euclid/prompts/breakdown_2.1_user_flows.pt | 57 +++++ euclid/prompts/breakdown_2_user_flows.prompt | 1 + euclid/prompts/project_breakdown.pt | 14 ++ euclid/prompts/system.pt | 1 + euclid/pyvenv.cfg | 5 + euclid/utils/__init__.py | 0 euclid/utils/llm_connection.py | 99 ++++++++ 29 files changed, 928 insertions(+) create mode 100644 euclid/__init__.py create mode 100644 euclid/bin/Activate.ps1 create mode 100644 euclid/bin/activate create mode 100644 euclid/bin/activate.csh create mode 100644 euclid/bin/activate.fish create mode 100755 euclid/bin/dotenv create mode 100755 euclid/bin/normalizer create mode 100755 euclid/bin/pip create mode 100755 euclid/bin/pip3 create mode 100755 euclid/bin/pip3.11 create mode 100755 euclid/bin/pygmentize create mode 120000 euclid/bin/python create mode 120000 euclid/bin/python3 create mode 120000 euclid/bin/python3.11 create mode 100644 euclid/const/common.py create mode 100644 euclid/const/db.py create mode 100644 euclid/const/llm.py create mode 100644 euclid/const/prompts.py create mode 100644 euclid/database.py create mode 100644 euclid/instructions.comment.py create mode 100644 euclid/main.py create mode 100644 euclid/prompts/breakdown_1_app_type.pt create mode 100644 euclid/prompts/breakdown_2.1_user_flows.pt create mode 100644 euclid/prompts/breakdown_2_user_flows.prompt create mode 100644 euclid/prompts/project_breakdown.pt create mode 100644 euclid/prompts/system.pt create mode 100644 euclid/pyvenv.cfg create mode 100644 euclid/utils/__init__.py create mode 100644 euclid/utils/llm_connection.py diff --git a/euclid/__init__.py b/euclid/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/euclid/bin/Activate.ps1 b/euclid/bin/Activate.ps1 new file mode 100644 index 0000000..b49d77b --- /dev/null +++ b/euclid/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/euclid/bin/activate b/euclid/bin/activate new file mode 100644 index 0000000..e7d3a5b --- /dev/null +++ b/euclid/bin/activate @@ -0,0 +1,69 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null + fi + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV="/Users/zvonimirsabljic/Development/euclid/euclid" +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/bin:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1="(euclid) ${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT="(euclid) " + export VIRTUAL_ENV_PROMPT +fi + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null +fi diff --git a/euclid/bin/activate.csh b/euclid/bin/activate.csh new file mode 100644 index 0000000..73b9ad8 --- /dev/null +++ b/euclid/bin/activate.csh @@ -0,0 +1,26 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV "/Users/zvonimirsabljic/Development/euclid/euclid" + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/bin:$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = "(euclid) $prompt" + setenv VIRTUAL_ENV_PROMPT "(euclid) " +endif + +alias pydoc python -m pydoc + +rehash diff --git a/euclid/bin/activate.fish b/euclid/bin/activate.fish new file mode 100644 index 0000000..0fd1ab7 --- /dev/null +++ b/euclid/bin/activate.fish @@ -0,0 +1,69 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/); you cannot run it directly. + +function deactivate -d "Exit virtual environment and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + set -e _OLD_FISH_PROMPT_OVERRIDE + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV "/Users/zvonimirsabljic/Development/euclid/euclid" + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/bin" $PATH + +# Unset PYTHONHOME if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # Save the current fish_prompt function as the function _old_fish_prompt. + functions -c fish_prompt _old_fish_prompt + + # With the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command. + set -l old_status $status + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) "(euclid) " (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT "(euclid) " +end diff --git a/euclid/bin/dotenv b/euclid/bin/dotenv new file mode 100755 index 0000000..ccd565a --- /dev/null +++ b/euclid/bin/dotenv @@ -0,0 +1,8 @@ +#!/Users/zvonimirsabljic/Development/euclid/euclid/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from dotenv.__main__ import cli +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli()) diff --git a/euclid/bin/normalizer b/euclid/bin/normalizer new file mode 100755 index 0000000..b4af46b --- /dev/null +++ b/euclid/bin/normalizer @@ -0,0 +1,8 @@ +#!/Users/zvonimirsabljic/Development/euclid/euclid/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from charset_normalizer.cli.normalizer import cli_detect +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli_detect()) diff --git a/euclid/bin/pip b/euclid/bin/pip new file mode 100755 index 0000000..33f1b9a --- /dev/null +++ b/euclid/bin/pip @@ -0,0 +1,8 @@ +#!/Users/zvonimirsabljic/Development/euclid/euclid/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/euclid/bin/pip3 b/euclid/bin/pip3 new file mode 100755 index 0000000..33f1b9a --- /dev/null +++ b/euclid/bin/pip3 @@ -0,0 +1,8 @@ +#!/Users/zvonimirsabljic/Development/euclid/euclid/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/euclid/bin/pip3.11 b/euclid/bin/pip3.11 new file mode 100755 index 0000000..33f1b9a --- /dev/null +++ b/euclid/bin/pip3.11 @@ -0,0 +1,8 @@ +#!/Users/zvonimirsabljic/Development/euclid/euclid/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/euclid/bin/pygmentize b/euclid/bin/pygmentize new file mode 100755 index 0000000..7b454e1 --- /dev/null +++ b/euclid/bin/pygmentize @@ -0,0 +1,8 @@ +#!/Users/zvonimirsabljic/Development/euclid/euclid/bin/python3.11 +# -*- coding: utf-8 -*- +import re +import sys +from pygments.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/euclid/bin/python b/euclid/bin/python new file mode 120000 index 0000000..6e7f3c7 --- /dev/null +++ b/euclid/bin/python @@ -0,0 +1 @@ +python3.11 \ No newline at end of file diff --git a/euclid/bin/python3 b/euclid/bin/python3 new file mode 120000 index 0000000..6e7f3c7 --- /dev/null +++ b/euclid/bin/python3 @@ -0,0 +1 @@ +python3.11 \ No newline at end of file diff --git a/euclid/bin/python3.11 b/euclid/bin/python3.11 new file mode 120000 index 0000000..3cf1fbd --- /dev/null +++ b/euclid/bin/python3.11 @@ -0,0 +1 @@ +/opt/homebrew/opt/python@3.11/bin/python3.11 \ No newline at end of file diff --git a/euclid/const/common.py b/euclid/const/common.py new file mode 100644 index 0000000..11d5d3d --- /dev/null +++ b/euclid/const/common.py @@ -0,0 +1 @@ +APP_TYPES = ['Web App', 'Script', 'Mobile App (unavailable)', 'Chrome Extension (unavailable)'] \ No newline at end of file diff --git a/euclid/const/db.py b/euclid/const/db.py new file mode 100644 index 0000000..3354057 --- /dev/null +++ b/euclid/const/db.py @@ -0,0 +1,5 @@ +DB_NAME = 'euclid' +DB_HOST = 'localhost' +DB_PORT = '5432' +DB_USER = 'admin' +DB_PASSWORD = 'admin' \ No newline at end of file diff --git a/euclid/const/llm.py b/euclid/const/llm.py new file mode 100644 index 0000000..9474580 --- /dev/null +++ b/euclid/const/llm.py @@ -0,0 +1,2 @@ +MIN_TOKENS_FOR_GPT_RESPONSE = 60 +MAX_GPT_MODEL_TOKENS = 4096 \ No newline at end of file diff --git a/euclid/const/prompts.py b/euclid/const/prompts.py new file mode 100644 index 0000000..061d6d3 --- /dev/null +++ b/euclid/const/prompts.py @@ -0,0 +1,4 @@ +SYS_MESSAGE = { + 'tdd_engineer': {'role': 'system', 'content': 'You are a QA engineer and your main goal is to find ways to break the application you\'re testing. You are proficient in writing automated integration tests for Node.js API servers.\n' + + 'When you respond, you don\'t say anything except the code - no formatting, no explanation - only code.\n' }, +} \ No newline at end of file diff --git a/euclid/database.py b/euclid/database.py new file mode 100644 index 0000000..9607c20 --- /dev/null +++ b/euclid/database.py @@ -0,0 +1,107 @@ +# database.py + +import psycopg2 +from psycopg2 import sql +from euclid.const import db + +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) + 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_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 INTEGER NOT NULL, + step VARCHAR(255) NOT NULL, + 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 (id) + ON UPDATE CASCADE ON DELETE CASCADE + ) + """) + conn = None + 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() + + +def save_app(user_id, app_type): + conn = create_connection() + cursor = conn.cursor() + + 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 the app + cursor.execute("INSERT INTO apps (user_id, app_type, status) VALUES (%s, %s, 'started') RETURNING id", (str(user_id), app_type)) + app_id = cursor.fetchone()[0] + + conn.commit() + cursor.close() + conn.close() + return app_id + +def save_progress(app_id, step, data): + conn = create_connection() + cursor = conn.cursor() + + insert = sql.SQL( + "INSERT INTO progress_steps (app_id, step, data, completed) VALUES (%s, %s, %s, false)" + ) + cursor.execute(insert, (app_id, step, data)) + + conn.commit() + cursor.close() + conn.close() + +if __name__ == "__main__": + create_tables() diff --git a/euclid/instructions.comment.py b/euclid/instructions.comment.py new file mode 100644 index 0000000..73a91d6 --- /dev/null +++ b/euclid/instructions.comment.py @@ -0,0 +1,65 @@ + # init CLI + # 1. show the type of the app that needs to be created + # 1.c ask user to press enter if it's ok, or to add the type of the app they want + # if it's not ok, check if the wanted app CAN be created + # if it can, print confirmation message and continue + # if it can't, print error message and exit + # 2. ask user for the main definition of the app + # start the processing queue + + +# 2. show the user flow of the app +# 2.c ask user to press enter if it's ok, or to add the user flow they want + # ask for input until they just press enter + # recompute the user flow and ask again +# 3. show the COMPONENTS of the app + # 3.1 frontend + # 3.2 backend + # 3.3 database + # 3.4 config + # 3.x ask user to press enter if it's ok, or to add the components they want + # ask for input until they just press enter + # recompute the components and ask again +# 4. break down the FILES that need to be created to support each of the components + # ask user to press enter if it's ok, or to add the files they want + # ask for input until they just press enter + # recompute the files and ask again +# 5. loop through components (IMPORTANT!!!) + # 5.1 loop through use cases + # 5.1.1 for each case in each component, break down the files, functions and dependencies that need to be created + # each function will have a description + # in each loop, we will send all the previous files and functions so that LLM can change them if needed +# 6. break down the tests that need to be created + # in the prompt, send all the files and functions + # start from the high level tests and go down to the unit tests + # 6.1 ask user to press enter if it's ok, or to add the tests they want + # ask for input until they just press enter + # recompute the tests and ask again +# 7. write the tests +# 8. write the files for each test +# 9. run each created test once the code is written + # start from low level tests and do the high level tests last + # track which test is related to which code + # GPT should first say which functions will it use for a test and then we check if any of those functions is already written and if so, we send it to LLM to change it + # track code coverage and increase to get to 100% + # if the code requires something from config, ask the user to add it + # if the code requires + # when files overlap, ask LLM to combine them +# 10. try debugging 5 times + # 10.1 if it doesn't work, ask the user to debug (!!!IMPORTANT!!!) + # show them the explanations + # ask for input if they want to enter something and retry 5 debugging attempts +# 11. create build/run script +# 12. RUN THE APP + + +# 4. show the components of the app setup + # a. installation process + # b. configuration process + # c. running process + # d. building process + # e. testing process + + +# comments +# 1. Možemo koristiti dodatni model koji će izvlačiti iz GPT responsea što treba pokrenuti, što treba updateati, koji komentar složiti, etc. - da ne trebamo i to učiti originalni model in context diff --git a/euclid/main.py b/euclid/main.py new file mode 100644 index 0000000..d7859c8 --- /dev/null +++ b/euclid/main.py @@ -0,0 +1,104 @@ +# main.py + +from __future__ import print_function, unicode_literals +from euclid.const import common +from euclid.utils import llm_connection +import inquirer +import uuid +from inquirer.themes import GreenPassion +from euclid.database import save_progress, save_app + +def break_down_user_flows(description): + user_flows = parse_description_into_user_flows(description) + for flow_index, user_flow in enumerate(user_flows): + is_correct = False + while not is_correct: + print(f"User Flow {flow_index+1}: {user_flow}") + is_correct = ask_for_user_flow_confirmation(flow_index) + save_progress(app_id, f'user_flow_{flow_index+1}', user_flow) + +def ask_for_user_flow_confirmation(flow_index): + questions = [ + inquirer.List('confirmation', + message=f"Does user flow {flow_index+1} meet your requirements? (Yes/No)", + choices=['Yes', 'No'], + ) + ] + + answers = inquirer.prompt(questions, theme=GreenPassion()) + + if answers is None: + print("No input provided!") + return + + if answers['confirmation'] == 'Yes': + return True + else: + return modify_user_flow(flow_index) + +def modify_user_flow(flow_index): + questions = [ + inquirer.Text('correction', message=f"Please provide corrections for user flow {flow_index+1}.") + ] + + answers = inquirer.prompt(questions, theme=GreenPassion()) + user_flows[flow_index] = answers['correction'] + return False + +def ask_for_app_type(): + questions = [ + inquirer.List('type', + message="What type of app do you want to build?", + choices=common.APP_TYPES, + ) + ] + + answers = inquirer.prompt(questions, theme=GreenPassion()) + + while answers is None or 'unavailable' in answers['type']: + if answers is None: + print("You need to make a selection.") + else: + print("Sorry, that option is not available.") + + answers = inquirer.prompt(questions, theme=GreenPassion()) + + print("You chose: " + answers['type']) + return answers['type'] + +def ask_for_main_app_definition(): + questions = [ + inquirer.Text('description', message="Describe your app in as many details as possible.") + ] + + answers = inquirer.prompt(questions, theme=GreenPassion()) + if answers is None: + print("No input provided!") + return + + description = answers['description'] + + while True: + questions = [ + inquirer.Text('confirmation', message="Do you want to add anything else? If not, just press ENTER.") + ] + + answers = inquirer.prompt(questions, theme=GreenPassion()) + if answers is None or answers['confirmation'] == '': + break + elif description[-1] not in ['.', '!', '?', ';']: + description += '.' + + description += ' ' + answers['confirmation'] + + return description + + +if __name__ == "__main__": + app_type = ask_for_app_type(); + user_id = str(uuid.uuid4()); + app_id = save_app(user_id, app_type) + description = ask_for_main_app_definition(); + save_progress(app_id, 'main_description', description); + user_flows = break_down_user_flows(description); + diff --git a/euclid/prompts/breakdown_1_app_type.pt b/euclid/prompts/breakdown_1_app_type.pt new file mode 100644 index 0000000..7519709 --- /dev/null +++ b/euclid/prompts/breakdown_1_app_type.pt @@ -0,0 +1 @@ +Ok, now, think step by step and break down the first axis - Type of the app. \ No newline at end of file diff --git a/euclid/prompts/breakdown_2.1_user_flows.pt b/euclid/prompts/breakdown_2.1_user_flows.pt new file mode 100644 index 0000000..15c5c83 --- /dev/null +++ b/euclid/prompts/breakdown_2.1_user_flows.pt @@ -0,0 +1,57 @@ +I want you to create the application (let's call it Euclid) that can be described like this: +``` +{{ description }} +``` + +We are going to create this app together, step by step. Here is an overview of tasks that you need to do to create this app from scratch: + +1. break down the app's functionality. Here, you need to list out all the possible features of the app regardless of if a user can interact with the feature (like enabling user to do something) or if it's a technical feature that needs to exist to make the app work (like having a database to store the data). + +2. Break down all the different user journeys that a user might experience while interacting with Euclid. Each journey needs to be described in as much detail as possible because that will be the fundamental base upon which you will break down the details of how the code structure will look like. + + +# how to start the app +# how to open the results with the command line so that user sees what's going on +2. Break down the plan for iteratively working on Euclid. Throughout the process of coding Euclid, we will create functionality screen by screen, I will test it and tell you how it works. Then, you will change the code or move onto the next screen. In this task, you will need to list out low level user experience from the start when the app is run all the way throughout all user journeys. This will basically be a plan for coding later on. + +3. Break down high level components that show how the codebase needs to be structured. For example, do we need frontend and what kind - mobile app, HTML website, etc. How the backend needs to look like; do we need to have a database and what database is best to use for this project? Do we need some other technologies like sockets, cronjob or something else? What secret keys do we need to use for this project and how the environment variables will be handled? + +4. Break down how will the user start the app and what will be the first screen. Then, I'll try running the app and see if it works and I will give you the feedback. We will iterate on this step until we have the first screen working. + +5. Break down how the files and folders will be structured to support all the outlined user journeys and components. You will need to think step by step through each user journey and through each component and create the project structure. The project structure needs to contain every file and folder that will be eventually created with names along with a comment on what will be contained in each file or folder. In this step, you won't need to create code, just the project structure. + +# exporting of functions - how to make sure we export them +# what is with the code that's not necessarily a function - how to handle that +# we should save all comments from GPT +# how can we know what's a working database URL??? +6. Loop through each file that you listed in the previous task, think step by step through all components and user journeys and list all functions that will need to be created in each file that will eventually make the app work. In this task, you will just need to create names and descriptions of what each function will need to do. With each function, make sure that you determine the input arguments and the return value (if there is any) + +# trebamo imati procese za provjeru da li postoji node, mongo, da li je Mongo uključen, etc. +7. Since we will be building this app with TDD, in this task, you will need to loop through each function that you listed in the previous task, think step by step through all components and user journeys and list all tests that will test every functionality of the app Euclid. You will start from the high level tests (integration or end-to-end tests) like testing if user can click on a specific element or if backend returns correct data. Then, you will go down all the way to unit tests for each specific function and list the tests needed. For each test, you will just need to create the test name and a description of what it needs to test. +# u ovom trenu treba prvo posložiti strukturu podataka, a onda tek pisati testove +# treba se error handling složiti tako da, ako se dogodi error, da možemo GPT-u pokazati točno gdje je error i što kaže +8. Write the tests and the code. This task will be an iterative task where you will think step by step from the low level, unit, tests all the way up to the high level integration and end-to-end tests. You will create test by test and after each test, you will create the code for it. I will run the test to check if it passes and show you the error. Will will do this iteratively until all tests pass and all code is written. By the end of this task, the Eucild app should be working. + +9. Finally, you will write all other necessary files and scripts that will make the app actually work like script to run the app, script that initializes the database, script that installs all the dependencies, and everything else that a developer needs to run to make the Euclid app work. + +Ok, now, let's start with breaking down the task #1. Think step by step and break down the user journeys for the described app. + + +{% if old_user_flows %} +You've listed the following user flows: +{% for flow in old_user_flows %} +{{ flow }} +{% endfor %} + + +I want to modify the user flows so that these flows are included: +{% for flow in new_user_flows %} +{{ flow }} +{% endfor %} + +Now, rewrite the user flows so that they include the new flows. Keep in mind that the new user flows have a higher priority than the old ones so if any of the old user flows conflict with the new ones, the new ones should be used. +{% else %} + + +Ok, now, think step by step and break down the user flows for the described app. What are all flows that user can take that this app should support? +{% endif %} diff --git a/euclid/prompts/breakdown_2_user_flows.prompt b/euclid/prompts/breakdown_2_user_flows.prompt new file mode 100644 index 0000000..cbb8774 --- /dev/null +++ b/euclid/prompts/breakdown_2_user_flows.prompt @@ -0,0 +1 @@ +Ok, now, think step by step and break down the user flows for the described app. What are all flows that user can take that this app should support? \ No newline at end of file diff --git a/euclid/prompts/project_breakdown.pt b/euclid/prompts/project_breakdown.pt new file mode 100644 index 0000000..52ef083 --- /dev/null +++ b/euclid/prompts/project_breakdown.pt @@ -0,0 +1,14 @@ +I want you to create the application (let's call it Euclid) that can be described like this: +``` +{{prompt}} +``` + +First, you need to break down what needs to be done to create this app step by step. You will break it down by 8 axes: +1. Type of the app - is it a mobile app, a script, a chrome extension or a web app +2. User flows which outline how the user will use the app. Here, you will need to outline all possible flows that a user might want to take. +3. Steps to run the app - how to initialize the project, install dependencies, set up the database, run or build the app and other things like cronjob, etc. +4. Tests that need to written to verify if the app is running (remember, you always practice TDD so you will create tests first and then the code) +5. Files that need to be created to make the app run +6. Functions that need to exist in the files +7. Dependencies that need to be installed to make the app run +8. Values in the config file that cannot be hardcoded in the app \ No newline at end of file diff --git a/euclid/prompts/system.pt b/euclid/prompts/system.pt new file mode 100644 index 0000000..e6d3a56 --- /dev/null +++ b/euclid/prompts/system.pt @@ -0,0 +1 @@ +You are an experienced software engineer who is proficient in node.js and who practices TDD (Test Driven Development). Usually, you look at the code that already exists and a written test - then you think step by step and modify the function that's being tested to make the test pass. \ No newline at end of file diff --git a/euclid/pyvenv.cfg b/euclid/pyvenv.cfg new file mode 100644 index 0000000..b37c220 --- /dev/null +++ b/euclid/pyvenv.cfg @@ -0,0 +1,5 @@ +home = /opt/homebrew/opt/python@3.11/bin +include-system-site-packages = false +version = 3.11.3 +executable = /opt/homebrew/Cellar/python@3.11/3.11.3/Frameworks/Python.framework/Versions/3.11/bin/python3.11 +command = /opt/homebrew/opt/python@3.11/bin/python3.11 -m venv /Users/zvonimirsabljic/Development/euclid/euclid diff --git a/euclid/utils/__init__.py b/euclid/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/euclid/utils/llm_connection.py b/euclid/utils/llm_connection.py new file mode 100644 index 0000000..f0785ff --- /dev/null +++ b/euclid/utils/llm_connection.py @@ -0,0 +1,99 @@ +# llm_connection.py + +import re +import requests +from dotenv import load_dotenv +import os +from tiktoken import Tokenizer +from typing import List +from http.server import BaseHTTPRequestHandler +from socketserver import ThreadingMixIn +from http.server import HTTPServer +from euclid.const.llm import MIN_TOKENS_FOR_GPT_RESPONSE, MAX_GPT_MODEL_TOKENS +from euclid.const.prompts import SYS_MESSAGE +from jinja2 import Environment, FileSystemLoader + +def connect_to_llm(): + pass + +def get_user_flows(description): + prompt = get_prompt('breakdown_1_user_flows.prompt', {'description': description}) + + messages = [ + SYS_MESSAGE['tdd_engineer'], + # app type + # + {"role": "user", "content": prompt}, + ] + + create_gpt_chat_completion(messages, min_tokens=MIN_TOKENS_FOR_GPT_RESPONSE) + + +def get_prompt(prompt_name, data): + # Create a file system loader with the directory of the templates + file_loader = FileSystemLoader('../prompts') + + # Create the Jinja2 environment + env = Environment(loader=file_loader) + + # Load the template + template = env.get_template(prompt_name) + + # Render the template with the provided data + output = template.render(data) + + return output + +def get_tokens_in_messages(messages: List[str]) -> int: + tokenizer = Tokenizer() + tokenized_messages = [tokenizer.encode(message) for message in messages] + return sum(len(tokens) for tokens in tokenized_messages) + +def create_gpt_chat_completion(messages: List[dict], min_tokens=MIN_TOKENS_FOR_GPT_RESPONSE): + api_key = os.getenv("OPENAI_API_KEY") + tokens_in_messages = get_tokens_in_messages(messages) + if tokens_in_messages + min_tokens > MAX_GPT_MODEL_TOKENS: + raise ValueError(f'Too many tokens in messages: {tokens_in_messages}. Please try a different test.') + + gpt_data = { + 'model': 'gpt-4', + 'n': 1, + 'max_tokens': min(4096, MAX_GPT_MODEL_TOKENS - tokens_in_messages), + 'messages': messages, + 'stream': True + } + + try: + return stream_gpt_completion(gpt_data, api_key) + except Exception as e: + print('The request to OpenAI API failed. Might be due to GPT being down or due to the too large message. It\'s best if you try another export.') + print(e) + +def stream_gpt_completion(data, api_key): + response = requests.post( + 'https://api.openai.com/v1/chat/completions', + headers={'Content-Type': 'application/json', 'Authorization': 'Bearer ' + api_key}, + json=data, + stream=True + ) + + if response.status_code != 200: + print(f'problem with request: {response.text}') + return + + gpt_response = '' + for line in response.iter_lines(): + if line: # filter out keep-alive new lines + json_line = json.loads(line) + if 'error' in json_line or 'message' in json_line: + print(json_line, end="") + return + content = json_line.get('choices')[0]['message']['content'] + gpt_response += content + print(content, end="") + + new_code = postprocessing(gpt_response, 'user_flows') # TODO add type dynamically + return new_code + +def postprocessing(gpt_response, type): + pass \ No newline at end of file