diff --git a/README.md b/README.md index a3ca834..f753aad 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Or python src/backends/main.py ``` -Or (recommended) +Or, using yarn (recommended) ```bash yarn server:dev @@ -164,9 +164,9 @@ Run the command below in powershell to set your env variables: ## Bundling - +If you already have the required toolkit files installed and have built for GPU then the necessary GPU drivers/dlls should be detected by PyInstaller and included in the `_deps` dir. ### Python server @@ -188,9 +188,15 @@ pyinstaller -c -F your_program.py #### Packaging python server with auto-py-to-exe (recommended): -This is a GUI tool that greatly simplifies the process. You can also save and load configs. +This is a GUI tool that greatly simplifies the process. You can also save and load configs. It uses PyInstaller under the hood and requires it to be installed. Please note if using a conda or virtual environment, be sure to install both PyInstaller and auto-py-to-exe in your virtual environment and also run them from there, otherwise one or both will build from incorrect deps. -To install: +**Note**, you will need to edit paths for the following in `auto-py-to-exe` to point to your base project directory: + +- Settings -> Output directory +- Additional Files +- Script Location + +To run: ```bash auto-py-to-exe @@ -282,6 +288,10 @@ This project deploys several backend servers exposed using the `/v1` endpoint. T A complete list of endpoint documentation can be found [here](https://localhost:8000/docs) after Obrew Server is started. +## API Keys and .env variables + +Put your .env file in the base directory alongside the executable. + ## Managing Python dependencies It is highly recommended to use an package/environment manager like Anaconda to manage Python installations and the versions of dependencies they require. This allows you to create virtual environments from which you can install different versions of software and build/deploy from within this sandboxed environment. @@ -290,10 +300,10 @@ It is highly recommended to use an package/environment manager like Anaconda to The following commands should be done in `Anaconda Prompt` terminal. If on Windows, `run as Admin`. -1. Create a new environment: +1. Create a new environment. This project uses v3.12: ```bash -conda create --name env1 python=3.10 +conda create --name env1 python=3.12 ``` 2. To work in this env, activate it: diff --git a/backends/main.py b/backends/main.py index addf64d..ead3476 100644 --- a/backends/main.py +++ b/backends/main.py @@ -1,4 +1,5 @@ import os +import signal import sys import threading import uvicorn @@ -6,6 +7,7 @@ import httpx import socket import pyqrcode +import tkinter as tk from dotenv import load_dotenv from fastapi import ( FastAPI, @@ -24,7 +26,6 @@ from settings.route import router as settings -# server_thread = None server_info = None api_version = "0.4.3" SERVER_PORT = 8008 @@ -45,10 +46,12 @@ def parse_runtime_args(): return mode +# Check what env is running - prod/dev buildEnv = parse_runtime_args() isDebug = hasattr(sys, "gettrace") and sys.gettrace() is not None isDev = buildEnv == "dev" or isDebug isProd = buildEnv == "prod" or not isDev +# Comment out if you want to debug on prod build if isProd: # Remove prints in prod when deploying in window mode sys.stdout = open(os.devnull, "w") @@ -66,7 +69,7 @@ async def lifespan(application: FastAPI): print(f"{common.PRNT_API} Lifespan startup", flush=True) # https://www.python-httpx.org/quickstart/ app.requests_client = httpx.Client() - # Store some state here if you want... + # Initialize global data here application.state.PORT_HOMEBREW_API = SERVER_PORT application.state.db_client = None application.state.llm = None # Set each time user loads a model @@ -81,8 +84,6 @@ async def lifespan(application: FastAPI): app = FastAPI(title="Obrew🍺Server", version=api_version, lifespan=lifespan) -templates_dir = os.path.join(os.path.dirname(__file__), "templates") -templates = Jinja2Templates(directory=templates_dir) # Get paths for SSL certificate SSL_KEY: str = common.dep_path("public/key.pem") @@ -106,11 +107,9 @@ async def lifespan(application: FastAPI): def shutdown_server(*args): - print(f"{common.PRNT_API} Shutting down server...", flush=True) - # os.kill(os.getpid(), signal.SIGINT) - # server_thread.join() - print(f"{common.PRNT_API} Server shutdown complete.", flush=True) - sys.exit(0) + print(f"{common.PRNT_API} Force shutting down server...", flush=True) + os.kill(os.getpid(), signal.SIGINT) + # sys.exit(0) def display_server_info(): @@ -152,12 +151,30 @@ def start_server(): print(f"{common.PRNT_API} Failed to start API server") -def run_server(): +def run_app_window(): # Start the API server in a separate thread from main - fastapi_thread = threading.Thread(target=start_server) - fastapi_thread.daemon = True # let the parent kill the child thread at exit - fastapi_thread.start() - return fastapi_thread + window_thread = threading.Thread(target=GUI) + window_thread.daemon = True # let the parent kill the child thread at exit + window_thread.start() + return window_thread + + +# Create and run the Tkinter window +def GUI(): + color_bg = "#333333" + root = tk.Tk() + root.title("Obrew Server") + root.geometry("500x500") + # Since /public folder is bundled inside _deps, we need to read from root `sys._MEIPASS` + root.iconbitmap(default=common.dep_path("public/favicon.ico")) + root.configure(bg=color_bg) + # Set font + Font_tuple = ("Verdana", 64, "bold") + root.bind("", lambda e: e.widget.quit()) + tk.Label(root, text="O🍺brew", font=Font_tuple).pack(fill=tk.BOTH, expand=True) + root.mainloop() + # Close server when user closes window + shutdown_server() ############## @@ -197,6 +214,9 @@ def run_server(): # QRcode generation -> https://github.com/arjones/qr-generator/tree/main @app.get("/", response_class=HTMLResponse) async def connect_page(request: Request): + # Be sure to link `public/templates` to the app's dependency dir (_deps) via PyInstaller + templates_dir = common.dep_path(os.path.join("public", "templates")) + templates = Jinja2Templates(directory=templates_dir) remote_url = server_info["remote_ip"] local_url = server_info["local_ip"] # Generate QR code - direct to remote url @@ -252,7 +272,9 @@ def connect() -> classes.ConnectResponse: ### Start ### ############# + if __name__ == "__main__": + App = None try: # Find IP info server_info = display_server_info() @@ -262,8 +284,11 @@ def connect() -> classes.ConnectResponse: print(f"{common.PRNT_API} API server started. Opening WebUI at {local_url}") webbrowser.open(local_url, new=2) print(f"{common.PRNT_API} Close this window to shutdown server.") + # Show a window + if isProd: + run_app_window() # Start API server start_server() - except KeyboardInterrupt: - print(f"{common.PRNT_API} User pressed Ctrl+C exiting...") + except (KeyboardInterrupt, Exception): + print(f"{common.PRNT_API} Something bad happenned, exiting...") shutdown_server() diff --git a/package.json b/package.json index f51c09c..9e0caab 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "server:dev": "python ./backends/main.py --mode=dev", "server:prod": "python ./backends/main.py --mode=prod", "build": "yarn run build:api && next build renderer", - "build:api:dev": "pyinstaller --noconfirm --onedir --console --icon C:/Project Files/brain-dump-ai/backend-homebrew-ai/public/favicon.ico --name OpenBrew-Server --contents-directory _deps --clean --debug bootloader --add-data C:/Python311/Lib/site-packages/llama_index/VERSION;llama_index --add-data C:/Project Files/brain-dump-ai/backend-homebrew-ai/public;public/ C:/Project Files/brain-dump-ai/backend-homebrew-ai/backends/main.py", - "build:api:prod": "pyinstaller --noconfirm --onedir --windowed --icon C:/Project Files/brain-dump-ai/backend-homebrew-ai/public/favicon.ico --name OpenBrew-Server --contents-directory _deps --clean --add-data C:/Python311/Lib/site-packages/llama_index/VERSION;llama_index --add-data C:/Project Files/brain-dump-ai/backend-homebrew-ai/public;public/ C:/Project Files/brain-dump-ai/backend-homebrew-ai/backends/main.py", + "build:api:dev": "pyinstaller --noconfirm --onedir --console --icon C:/Project Files/brain-dump-ai/backend-homebrew-ai/public/favicon.ico --name Obrew-Server --contents-directory _deps --clean --debug bootloader --add-data C:/Project Files/brain-dump-ai/backend-homebrew-ai/public;public/ --add-data C:/ProgramData/anaconda3/envs/llama-index/Lib/site-packages/llama_cpp;llama_cpp/ --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/posthog;posthog/ --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/chromadb;chromadb/ --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/importlib_resources;importlib_resources/ --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/backoff;backoff/ --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/pypika;pypika/ --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/hnswlib.cp312-win_amd64.pyd;. --hidden-import tiktoken_ext.openai_public --hidden-import tiktoken_ext C:/Project Files/brain-dump-ai/backend-homebrew-ai/backends/main.py", + "build:api:prod": "pyinstaller --noconfirm --onedir --windowed --icon C:/Project Files/brain-dump-ai/backend-homebrew-ai/public/favicon.ico --name Obrew-Server --contents-directory _deps --clean --add-data C:/Project Files/brain-dump-ai/backend-homebrew-ai/public;public/ --add-data C:/ProgramData/anaconda3/envs/llama-index/Lib/site-packages/llama_cpp;llama_cpp/ --hidden-import tiktoken_ext.openai_public --hidden-import tiktoken_ext --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/posthog;posthog/ --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/chromadb;chromadb/ --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/importlib_resources;importlib_resources/ --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/backoff;backoff/ --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/pypika;pypika/ --add-data C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/hnswlib.cp312-win_amd64.pyd;. C:/Project Files/brain-dump-ai/backend-homebrew-ai/backends/main.py", "python-deps": "pip install -r requirements.txt", "release": "yarn run build && electron-builder", "release:win": "yarn run build && electron-builder --win --x64", diff --git a/backends/templates/index.html b/public/templates/index.html similarity index 100% rename from backends/templates/index.html rename to public/templates/index.html diff --git a/py-to-exe-config.dev.json b/py-to-exe-config.dev.json index ffc6f44..f0b5ecf 100644 --- a/py-to-exe-config.dev.json +++ b/py-to-exe-config.dev.json @@ -1,81 +1,113 @@ { - "version": "auto-py-to-exe-configuration_v1", - "pyinstallerOptions": [ - { - "optionDest": "noconfirm", - "value": true - }, - { - "optionDest": "filenames", - "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/backends/main.py" - }, - { - "optionDest": "onefile", - "value": false - }, - { - "optionDest": "console", - "value": true - }, - { - "optionDest": "icon_file", - "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/public/favicon.ico" - }, - { - "optionDest": "name", - "value": "Obrew-Server" - }, - { - "optionDest": "contents_directory", - "value": "_deps" - }, - { - "optionDest": "clean_build", - "value": true - }, - { - "optionDest": "debug", - "value": "bootloader" - }, - { - "optionDest": "strip", - "value": false - }, - { - "optionDest": "noupx", - "value": false - }, - { - "optionDest": "disable_windowed_traceback", - "value": false - }, - { - "optionDest": "uac_admin", - "value": false - }, - { - "optionDest": "uac_uiaccess", - "value": false - }, - { - "optionDest": "argv_emulation", - "value": false - }, - { - "optionDest": "bootloader_ignore_signals", - "value": false - }, - { - "optionDest": "datas", - "value": "C:/Python311/Lib/site-packages/llama_index/VERSION;llama_index" - }, - { - "optionDest": "datas", - "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/public;public/" - } - ], - "nonPyinstallerOptions": { - "increaseRecursionLimit": true, - "manualArguments": "" + "version": "auto-py-to-exe-configuration_v1", + "pyinstallerOptions": [ + { + "optionDest": "noconfirm", + "value": true + }, + { + "optionDest": "filenames", + "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/backends/main.py" + }, + { + "optionDest": "onefile", + "value": false + }, + { + "optionDest": "console", + "value": true + }, + { + "optionDest": "icon_file", + "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/public/favicon.ico" + }, + { + "optionDest": "name", + "value": "Obrew-Server" + }, + { + "optionDest": "contents_directory", + "value": "_deps" + }, + { + "optionDest": "clean_build", + "value": true + }, + { + "optionDest": "debug", + "value": "bootloader" + }, + { + "optionDest": "strip", + "value": false + }, + { + "optionDest": "noupx", + "value": false + }, + { + "optionDest": "disable_windowed_traceback", + "value": false + }, + { + "optionDest": "uac_admin", + "value": false + }, + { + "optionDest": "uac_uiaccess", + "value": false + }, + { + "optionDest": "argv_emulation", + "value": false + }, + { + "optionDest": "bootloader_ignore_signals", + "value": false + }, + { + "optionDest": "datas", + "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/public;public/" + }, + { + "optionDest": "datas", + "value": "C:/ProgramData/anaconda3/envs/llama-index/Lib/site-packages/llama_cpp;llama_cpp/" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/posthog;posthog/" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/chromadb;chromadb/" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/importlib_resources;importlib_resources/" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/backoff;backoff/" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/pypika;pypika/" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/hnswlib.cp312-win_amd64.pyd;." + }, + { + "optionDest": "hiddenimports", + "value": "tiktoken_ext.openai_public" + }, + { + "optionDest": "hiddenimports", + "value": "tiktoken_ext" } -} + ], + "nonPyinstallerOptions": { + "increaseRecursionLimit": true, + "manualArguments": "" + } +} \ No newline at end of file diff --git a/py-to-exe-config.prod.json b/py-to-exe-config.prod.json index f4fb5b4..de17031 100644 --- a/py-to-exe-config.prod.json +++ b/py-to-exe-config.prod.json @@ -1,77 +1,109 @@ { - "version": "auto-py-to-exe-configuration_v1", - "pyinstallerOptions": [ - { - "optionDest": "noconfirm", - "value": true - }, - { - "optionDest": "filenames", - "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/backends/main.py" - }, - { - "optionDest": "onefile", - "value": false - }, - { - "optionDest": "console", - "value": false - }, - { - "optionDest": "icon_file", - "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/public/favicon.ico" - }, - { - "optionDest": "name", - "value": "Obrew-Server" - }, - { - "optionDest": "contents_directory", - "value": "_deps" - }, - { - "optionDest": "clean_build", - "value": true - }, - { - "optionDest": "strip", - "value": false - }, - { - "optionDest": "noupx", - "value": false - }, - { - "optionDest": "disable_windowed_traceback", - "value": false - }, - { - "optionDest": "uac_admin", - "value": false - }, - { - "optionDest": "uac_uiaccess", - "value": false - }, - { - "optionDest": "argv_emulation", - "value": false - }, - { - "optionDest": "bootloader_ignore_signals", - "value": false - }, - { - "optionDest": "datas", - "value": "C:/Python311/Lib/site-packages/llama_index/VERSION;llama_index" - }, - { - "optionDest": "datas", - "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/public;public/" - } - ], - "nonPyinstallerOptions": { - "increaseRecursionLimit": true, - "manualArguments": "" + "version": "auto-py-to-exe-configuration_v1", + "pyinstallerOptions": [ + { + "optionDest": "noconfirm", + "value": true + }, + { + "optionDest": "filenames", + "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/backends/main.py" + }, + { + "optionDest": "onefile", + "value": false + }, + { + "optionDest": "console", + "value": false + }, + { + "optionDest": "icon_file", + "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/public/favicon.ico" + }, + { + "optionDest": "name", + "value": "Obrew-Server" + }, + { + "optionDest": "contents_directory", + "value": "_deps" + }, + { + "optionDest": "clean_build", + "value": true + }, + { + "optionDest": "strip", + "value": false + }, + { + "optionDest": "noupx", + "value": false + }, + { + "optionDest": "disable_windowed_traceback", + "value": false + }, + { + "optionDest": "uac_admin", + "value": false + }, + { + "optionDest": "uac_uiaccess", + "value": false + }, + { + "optionDest": "argv_emulation", + "value": false + }, + { + "optionDest": "bootloader_ignore_signals", + "value": false + }, + { + "optionDest": "datas", + "value": "C:/Project Files/brain-dump-ai/backend-homebrew-ai/public;public/" + }, + { + "optionDest": "datas", + "value": "C:/ProgramData/anaconda3/envs/llama-index/Lib/site-packages/llama_cpp;llama_cpp/" + }, + { + "optionDest": "hiddenimports", + "value": "tiktoken_ext.openai_public" + }, + { + "optionDest": "hiddenimports", + "value": "tiktoken_ext" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/posthog;posthog/" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/chromadb;chromadb/" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/importlib_resources;importlib_resources/" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/backoff;backoff/" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/pypika;pypika/" + }, + { + "optionDest": "datas", + "value": "C:/Users/cybro/AppData/Roaming/Python/Python312/site-packages/hnswlib.cp312-win_amd64.pyd;." } -} + ], + "nonPyinstallerOptions": { + "increaseRecursionLimit": true, + "manualArguments": "" + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index dba4127..3d2c2b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,13 +20,14 @@ llama-cpp-python==0.2.32 # For retrieving embeddings llama-index==0.10.29 llama-index-readers-file==0.1.19 -llama-index-vector-stores-chroma==0.1.6 +llama-index-vector-stores-chroma==0.1.8 llama-index-embeddings-huggingface==0.2.0 llama-index-llms-llama-cpp==0.1.3 llama-parse==0.4.1 # For embeddings creation and storage -chromadb==0.4.24 +chromadb==0.5.0 sentence-transformers==2.6.1 # For building single exe python pyinstaller==6.6.0 +auto-py-to-exe==2.43.3 python-multipart==0.0.6 \ No newline at end of file