now open source

This commit is contained in:
NSDE 2023-06-13 15:25:08 +02:00
parent 5027fb71c6
commit 624413bf33
21 changed files with 532 additions and 0 deletions

22
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,22 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
".vscode": true,
"luna/__pycache__": true,
"luna/training/clock/clock/__pycache__": true,
"luna/training/clock/__pycache__": true,
"luna/training/getip/__pycache__": true
},
"hide-files.files": [
".vscode",
"luna/__pycache__",
"luna/training/clock/clock/__pycache__",
"luna/training/clock/__pycache__",
"luna/training/getip/__pycache__"
]
}

2
luna/__main__.py Normal file
View file

@ -0,0 +1,2 @@
import main
main.main()

23
luna/ai.py Normal file
View file

@ -0,0 +1,23 @@
"""Main module for Luna."""
import os
import openai
from dotenv import load_dotenv
load_dotenv()
if os.getenv('OPENAI_API_BASE'):
openai.api_base = os.getenv('OPENAI_API_BASE')
openai.api_key = os.getenv('OPENAI_API_KEY')
MODEL = os.getenv('OPENAI_CHAT_MODEL') or 'gpt-3.5-turbo'
def generate(messages: str):
"""Generates a new message based on the given messages."""
return openai.ChatCompletion.create(
model=MODEL,
messages=messages,
temperature=0.2
).choices[0].message.content

64
luna/main.py Normal file
View file

@ -0,0 +1,64 @@
import ai
import os
import prompts
import testing
from rich import print
from rich.progress import track
from rich.console import Console
OUTPUT_PATH = r'C:\Users\Lynx\Desktop\luna_outp'
console = Console(record=True)
print('[light_steel_blue]Welcome to [bold]L u n a[/] 💜')
def main():
"""Asks for a project description and generate the project."""
try:
prompt = console.input("[orchid1 bold]What kind of project should I create for you? 💬[/] ")
except KeyboardInterrupt:
console.print('\n[orange1 bold]Bye! 👋[/]')
return
console.print('[yellow2]This might take a while, so go ahead and enjoy a cup of tea. ☕[/]')
messages = prompts.generate_messages()
messages.append({
'role': 'user',
'content': f'[FILE-STRUCTURE]: {prompt}'
})
file_structure = ai.generate(messages).strip()
file_list = ''.join(['\t' + path for path in file_structure])
console.print(f'[turquoise2 bold]File structure:[/]\n{file_list}')
messages.append({
'role': 'assistant',
'content': file_structure
})
for file_path in track(file_structure.splitlines(), description='[light_steel_blue]Generating files...'):
messages.append({
'role': 'user',
'content': f'[FILE-CONTENT]: {file_path}'
})
file_content = ai.generate(messages)
messages.append({
'role': 'assistant',
'content': file_content
})
path = os.path.join(OUTPUT_PATH, file_path).replace('\\', '/')
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'w', encoding='utf8') as file_:
file_.write(file_content)
console.print('[sea_green1 bold]Done! ✅[/]')
if __name__ == '__main__':
main()

110
luna/prompts.json Normal file
View file

@ -0,0 +1,110 @@
[
{
"role": "system",
"content": "You program Python projects by providing every file's\ncontents, a documentation in the form of a Markdown file as well as a list of all files.\nIt is really important that also tests are provided, which check the functionality of the\nproject! The tests should check the functionality of the project, and should be created at the end.\nThe first file content to provide is the README.md file, which should contain a description of the\nproject, as well as a simple documentation on how to run the project, as well as how it works.\n\nFor example, for a web app, you should not only provide the commands to run the project, but also\nthe URL paths and what they do.\n\nKeep in mind to also use a nice CSS design if the project is a web app!\nCompletely avoid using APIs which require an API key, as this would make it impossible to run the project!\nWhenever possible, use APIs which are free to use and don't require an API key instead!\n"
},
{
"role": "user",
"content": "[FILE-STRUCTURE]: A simple website called SimpleClock. It should display the current time on /."
},
{
"role": "assistant",
"content": "main.py\nREADME.md\ntests.py\nsimpleclock/__init__.py\nsimpleclock/static/index.js\nsimpleclock/static/style.css\nsimpleclock/templates/base.html\nsimpleclock/templates/index.html\n"
},
{
"role": "user",
"content": "[FILE-CONTENT]: main.py"
},
{
"role": "assistant",
"content": "\"\"\"Web server start module.\"\"\"\n\nimport sys\n\nfrom clock import create_app\n\ndef run(port=sys.argv[1] if len(sys.argv) > 1 else 5000):\n \"\"\"Starts the web server.\"\"\"\n create_app().run(port=port)\n\nif __name__ == '__main__':\n run()\n"
},
{
"role": "user",
"content": "[FILE-CONTENT]: README.md"
},
{
"role": "assistant",
"content": "# SimpleClock\nA simple web app which displays the current time.\n\n## Get started\nTo get started, follow these steps:\n\n1. Run the app using\n```bash\npython main.py\n```\n2. Open the app in your browser at http://localhost:5000\n3. Enjoy!"
},
{
"role": "user",
"content": "[FILE-CONTENT]: tests.py"
},
{
"role": "assistant",
"content": "\"\"\"LUNA Tests.\"\"\"\n\nimport time\nimport requests\nimport multiprocessing\n\nclass Tester:\n \"\"\"Tests if the program is working as expected.\"\"\"\n\n def __init__(self):\n \"\"\"Runs the tests.\"\"\"\n # pylint: disable=import-outside-toplevel\n import main\n\n self.server = multiprocessing.Process(target=main.run, args=(5000,))\n self.server.start()\n\n time.sleep(1)\n print('Server started!')\n\n resp = requests.get('http://localhost:5000/', timeout=5)\n assert 'The current time is:' in resp.text\n\n def stop(self):\n \"\"\"Runs after the tests are done, whether they have been successful or not.\"\"\"\n self.server.terminate()\n"
},
{
"role": "user",
"content": "[FILE-CONTENT]: simpleclock/__init__.py"
},
{
"role": "assistant",
"content": "import flask\n\ndef create_app():\n \"\"\"Returns the Flask app.\"\"\"\n\n app = flask.Flask(__name__)\n\n @app.route('/')\n def index():\n \"\"\"Display the current time.\"\"\"\n return flask.render_template('index.html')\n\n return app\n"
},
{
"role": "user",
"content": "[FILE-CONTENT]: simpleclock/static/index.js"
},
{
"role": "assistant",
"content": "// use momentjs to display the time, nicely formatted.\n// updates every second\n\nfunction update() {\n time.innerHTML = moment().format('h:mm:ss a');\n }\n\nupdate();\nsetInterval(update, 1000);\n"
},
{
"role": "user",
"content": "[FILE-CONTENT]: simpleclock/static/style.css"
},
{
"role": "assistant",
"content": "body {\n font-size: 1.2rem;\n scroll-behavior: smooth;\n}\n\n* {\n box-sizing: border-box;\n font-family: sans-serif;\n}\n\nmain {\n max-width: 800px;\n margin: 0 auto;\n padding: 1rem;\n}\n\nh1 {\n font-size: 2rem;\n}\n\np {\n font-size: 1.2rem;\n}\n\n#time {\n font-weight: bold;\n}\n"
},
{
"role": "user",
"content": "[FILE-CONTENT]: simpleclock/templates/base.html"
},
{
"role": "assistant",
"content": "<!DOCTYPE html>\n<html>\n<head>\n <title>SimpleClock</title>\n <link rel=\"stylesheet\" href=\"{{ url_for('static', filename='style.css') }}\">\n</head>\n<body>\n <main>\n {% block content %}\n {% endblock %}\n </main>\n <script src=\"https://cdn.jsdelivr.net/npm/moment/moment.min.js\"></script>\n <script src=\"{{ url_for('static', filename='index.js') }}\"></script>\n</body>\n</html>"
},
{
"role": "user",
"content": "[FILE-CONTENT]: simpleclock/templates/index.html"
},
{
"role": "assistant",
"content": "{% extends 'base.html' %}\n{% block content %}\n <h1>SimpleClock</h1>\n <p>Welcome to this website!</p>\n <p>The current time is: <span id=\"time\"></span>.</p>\n{% endblock %}"
},
{
"role": "user",
"content": "[FILE-STRUCTURE]: A very simple CLI which returns the user's IP address."
},
{
"role": "assistant",
"content": "cli.py\nREADME.md\ntests.py\n"
},
{
"role": "user",
"content": "[FILE-CONTENT]: cli.py"
},
{
"role": "assistant",
"content": "\"\"\"Get the IP address of the client.\"\"\"\n\nimport typer\nimport requests\n\napp = typer.Typer()\n\n@app.command()\ndef main():\n \"\"\"Shows the IP address of the client.\"\"\"\n print(requests.get('https://checkip.amazonaws.com', timeout=5).text.strip())\n\nif __name__ == '__main__':\n typer.run(main)\n"
},
{
"role": "user",
"content": "[FILE-CONTENT]: README.md"
},
{
"role": "assistant",
"content": "# `GetIP`\nA very simple CLI app that displays the user's IP address.\n\n## Get started\nIt's very simple to run this app. Just run the following command in the terminal:\n\n```bash\npython cli.py\n```\n\nExample output:\n```\n123.123.34.34\n```\n"
},
{
"role": "user",
"content": "[FILE-CONTENT]: tests.py"
},
{
"role": "assistant",
"content": "\"\"\"LUNA Tests.\"\"\"\n\nimport os\nimport requests\nimport subprocess\n\nclass Tester:\n \"\"\"Tests if the program is working as expected.\"\"\"\n\n def __init__(self):\n \"\"\"Runs the tests.\"\"\"\n\n # check the print output of cli.app.main()\n os.chdir(os.path.dirname(__file__))\n output = subprocess.check_output(['python', 'cli.py']).decode('utf8')\n\n ip_addr = requests.get('https://checkip.amazonaws.com', timeout=5).text.strip()\n assert ip_addr in output\n\n def stop(self):\n \"\"\"Runs after the tests are done, whether they have been successful or not.\"\"\"\n # no need to stop anything\n"
}
]

76
luna/prompts.py Normal file
View file

@ -0,0 +1,76 @@
"""The prompt module contains the demo "conversation" messages used to provide an example for the
AI of how to respond to the user."""
import os
import json
messages = [
{'role': 'system', 'content': """You program Python projects by providing every file's
contents, a documentation in the form of a Markdown file as well as a list of all files.
It is really important that also tests are provided, which check the functionality of the
project! The tests should check the functionality of the project, and should be created at the end.
The first file content to provide is the README.md file, which should contain a description of the
project, as well as a simple documentation on how to run the project, as well as how it works.
For example, for a web app, you should not only provide the commands to run the project, but also
the URL paths and what they do.
Keep in mind to also use a nice CSS design if the project is a web app!
Completely avoid using APIs which require an API key, as this would make it impossible to run the project!
Whenever possible, use APIs which are free to use and don't require an API key instead!
"""}
]
def generate_messages():
"""Generates the messages for the AI "conversation".
"""
for example in os.listdir('luna/training'):
example_path = f'luna/training/{example}'
with open(f'{example_path}/_prompt.luna.txt', encoding='utf8') as prompt_file:
prompt = prompt_file.read()
messages.append({
'role': 'user',
'content': f'[FILE-STRUCTURE]: {prompt}'
})
contents = {}
for root, _, files in os.walk(example_path):
for file_ in files:
file_path = os.path.join(root, file_)\
.replace('\\', '/').replace(example_path + '/', '')
if '__pycache__' in file_path or '.luna.' in file_path:
continue
contents[file_path] = open(os.path.join(root, file_), encoding='utf8').read()
structure = ''
for file_path in contents:
structure += f'{file_path}\n'
messages.append({
'role': 'assistant',
'content': structure
})
for file_path, content in contents.items():
messages.append({
'role': 'user',
'content': f'[FILE-CONTENT]: {file_path}'
})
messages.append({
'role': 'assistant',
'content': content
})
return messages
if __name__ == '__main__':
json.dump(generate_messages(), open('luna/prompts.json', 'w', encoding='utf8'), indent=4)

32
luna/testing.py Normal file
View file

@ -0,0 +1,32 @@
"""Provides a function to run tests in a module."""
import sys
import traceback
def run_tests(tests_module):
"""Runs the tests in the given module."""
success = False
tester = None
try:
tester = tests_module.Tester()
# pylint: disable=broad-except
except Exception:
print('-----BEGIN LUNA ERROR-----')
traceback.print_exc()
print('-----END LUNA ERROR-----')
else:
success = True
print('-----LUNA SUCCESS-----')
finally:
try:
tester.stop()
except AttributeError:
pass
sys.exit(int(not success))

31
luna/tokens.py Normal file
View file

@ -0,0 +1,31 @@
import tiktoken
def num_tokens_from_messages(messages, model="gpt-3.5-turbo-0301"):
"""Returns the number of tokens used by a list of messages."""
try:
encoding = tiktoken.encoding_for_model(model)
except KeyError:
encoding = tiktoken.get_encoding("cl100k_base")
if model == "gpt-3.5-turbo":
return num_tokens_from_messages(messages, model="gpt-3.5-turbo-0301")
elif model == "gpt-4":
return num_tokens_from_messages(messages, model="gpt-4-0314")
elif model == "gpt-3.5-turbo-0301":
tokens_per_message = 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n
tokens_per_name = -1 # if there's a name, the role is omitted
elif model == "gpt-4-0314":
tokens_per_message = 3
tokens_per_name = 1
else:
raise NotImplementedError(f"""num_tokens_from_messages() is not implemented for model {model}. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.""")
num_tokens = 0
for message in messages:
num_tokens += tokens_per_message
for key, value in message.items():
num_tokens += len(encoding.encode(value))
if key == "name":
num_tokens += tokens_per_name
num_tokens += 3 # every reply is primed with <|start|>assistant<|message|>
return num_tokens
print(num_tokens_from_messages(messages))

View file

@ -0,0 +1,12 @@
# SimpleClock
A simple web app which displays the current time.
## Get started
To get started, follow these steps:
1. Run the app using
```bash
python main.py
```
2. Open the app in your browser at http://localhost:5000
3. Enjoy!

View file

@ -0,0 +1 @@
A simple website called SimpleClock. It should display the current time on /.

View file

@ -0,0 +1,12 @@
"""Web server start module."""
import sys
from clock import create_app
def run(port=sys.argv[1] if len(sys.argv) > 1 else 5000):
"""Starts the web server."""
create_app().run(port=port)
if __name__ == '__main__':
run()

View file

@ -0,0 +1,13 @@
import flask
def create_app():
"""Returns the Flask app."""
app = flask.Flask(__name__)
@app.route('/')
def index():
"""Display the current time."""
return flask.render_template('index.html')
return app

View file

@ -0,0 +1,9 @@
// use momentjs to display the time, nicely formatted.
// updates every second
function update() {
time.innerHTML = moment().format('h:mm:ss a');
}
update();
setInterval(update, 1000);

View file

@ -0,0 +1,27 @@
body {
font-size: 1.2rem;
scroll-behavior: smooth;
}
* {
box-sizing: border-box;
font-family: sans-serif;
}
main {
max-width: 800px;
margin: 0 auto;
padding: 1rem;
}
h1 {
font-size: 2rem;
}
p {
font-size: 1.2rem;
}
#time {
font-weight: bold;
}

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<title>SimpleClock</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<main>
{% block content %}
{% endblock %}
</main>
<script src="https://cdn.jsdelivr.net/npm/moment/moment.min.js"></script>
<script src="{{ url_for('static', filename='index.js') }}"></script>
</body>
</html>

View file

@ -0,0 +1,6 @@
{% extends 'base.html' %}
{% block content %}
<h1>SimpleClock</h1>
<p>Welcome to this website!</p>
<p>The current time is: <span id="time"></span>.</p>
{% endblock %}

View file

@ -0,0 +1,26 @@
"""LUNA Tests."""
import time
import multiprocessing
import requests
class Tester:
"""Tests if the program is working as expected."""
def __init__(self):
"""Runs the tests."""
# pylint: disable=import-outside-toplevel
import main
self.server = multiprocessing.Process(target=main.run, args=(5000,))
self.server.start()
time.sleep(1)
print('Server started!')
resp = requests.get('http://localhost:5000/', timeout=5)
assert 'The current time is:' in resp.text
def stop(self):
"""Runs after the tests are done, whether they have been successful or not."""
self.server.terminate()

View file

@ -0,0 +1,14 @@
# `GetIP`
A very simple CLI app that displays the user's IP address.
## Get started
It's very simple to run this app. Just run the following command in the terminal:
```bash
python cli.py
```
Example output:
```
123.123.34.34
```

View file

@ -0,0 +1 @@
A very simple CLI which returns the user's IP address.

View file

@ -0,0 +1,14 @@
"""Get the IP address of the client."""
import typer
import requests
app = typer.Typer()
@app.command()
def main():
"""Shows the IP address of the client."""
print(requests.get('https://checkip.amazonaws.com', timeout=5).text.strip())
if __name__ == '__main__':
typer.run(main)

View file

@ -0,0 +1,22 @@
"""LUNA Tests."""
import os
import requests
import subprocess
class Tester:
"""Tests if the program is working as expected."""
def __init__(self):
"""Runs the tests."""
# check the print output of cli.app.main()
os.chdir(os.path.dirname(__file__))
output = subprocess.check_output(['python', 'cli.py']).decode('utf8')
ip_addr = requests.get('https://checkip.amazonaws.com', timeout=5).text.strip()
assert ip_addr in output
def stop(self):
"""Runs after the tests are done, whether they have been successful or not."""
# no need to stop anything