From 854c2af9d5f8112d3c0d4effb42c363ef078dedb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?wear=E5=B7=A5=E7=A8=8B=E5=B8=88?= <1225186748@qq.com>
Date: Thu, 7 Dec 2023 23:28:04 +0800
Subject: [PATCH] feat: better docker build strategy
---
.dockerignore | 5 ++
.gitignore | 4 ++
Dockerfile | 26 +++++++
backend/main.py | 19 ++++-
backend/poetry.lock | 155 +++++++++++++++++++++++++++++++++--------
backend/pyproject.toml | 5 +-
frontend/src/config.ts | 14 ++--
7 files changed, 193 insertions(+), 35 deletions(-)
create mode 100644 .dockerignore
create mode 100644 Dockerfile
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..c698837
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,5 @@
+node_modules
+dist
+build
+.git
+frontend/node_modules
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index fc073ff..03e62ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
.aider*
# Project-related files
+webui
# Run logs
backend/run_logs/*
@@ -14,3 +15,6 @@ frontend/.env.local
# Mac files
.DS_Store
+
+# Editor files
+.vscode
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..6b5c79a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,26 @@
+FROM node:20-alpine as build-frontend
+WORKDIR /app
+RUN corepack enable
+
+COPY ./frontend /app/
+ENV VITE_IS_WEB_UI_MODE=true
+RUN yarn install && yarn build
+
+
+FROM thehale/python-poetry as build-backend
+RUN apt update && apt install -y binutils
+WORKDIR /app
+COPY ./backend /app/
+RUN poetry install --no-interaction
+RUN poetry run pyinstaller --clean --onefile --name backend main.py
+
+
+FROM debian:bookworm-slim
+ENV FASTAPI_ENV=production
+ENV OPENAI_API_KEY=
+WORKDIR /app
+COPY --from=build-frontend /app/dist /app/webui
+COPY --from=build-backend /app/dist/backend /app/backend
+
+EXPOSE 8000
+CMD ["/app/backend"]
\ No newline at end of file
diff --git a/backend/main.py b/backend/main.py
index cb8242a..cd3b74f 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -9,8 +9,9 @@ import os
import traceback
from datetime import datetime
from fastapi import FastAPI, WebSocket
+from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
-from fastapi.responses import HTMLResponse
+from fastapi.responses import FileResponse, HTMLResponse
import openai
from llm import stream_openai_response
from mock import mock_completion
@@ -40,12 +41,19 @@ SHOULD_MOCK_AI_RESPONSE = bool(os.environ.get("MOCK", False))
# Used as a feature flag to enable or disable certain features
IS_PROD = os.environ.get("IS_PROD", False)
+# if webui folder exists, we are in build mode
+IS_BUILD = os.path.exists("webui")
+
app.include_router(screenshot.router)
@app.get("/")
async def get_status():
+ if IS_BUILD:
+ # if in build mode, return webui/index.html
+ return FileResponse("webui/index.html")
+
return HTMLResponse(
content="
Your backend is running correctly. Please open the front-end URL (default is http://localhost:5173) to use screenshot-to-code.
"
)
@@ -259,3 +267,12 @@ async def stream_code(websocket: WebSocket):
)
await websocket.close()
+
+
+# Serve the webui folder as static files
+if IS_BUILD:
+ app.mount("/", StaticFiles(directory="webui", html=True), name="webui")
+
+if __name__ == "__main__":
+ import uvicorn
+ uvicorn.run(app, host="0.0.0.0", port=8000)
\ No newline at end of file
diff --git a/backend/poetry.lock b/backend/poetry.lock
index 889d46d..dbf2122 100644
--- a/backend/poetry.lock
+++ b/backend/poetry.lock
@@ -1,10 +1,20 @@
-# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
+
+[[package]]
+name = "altgraph"
+version = "0.17.4"
+description = "Python graph (network) package"
+optional = false
+python-versions = "*"
+files = [
+ {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"},
+ {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
+]
[[package]]
name = "anyio"
version = "3.7.1"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -26,7 +36,6 @@ trio = ["trio (<0.22)"]
name = "beautifulsoup4"
version = "4.12.2"
description = "Screen-scraping library"
-category = "main"
optional = false
python-versions = ">=3.6.0"
files = [
@@ -45,7 +54,6 @@ lxml = ["lxml"]
name = "certifi"
version = "2023.11.17"
description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -57,7 +65,6 @@ files = [
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -72,7 +79,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
-category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
@@ -84,7 +90,6 @@ files = [
name = "distro"
version = "1.8.0"
description = "Distro - an OS platform information API"
-category = "main"
optional = false
python-versions = ">=3.6"
files = [
@@ -96,7 +101,6 @@ files = [
name = "exceptiongroup"
version = "1.2.0"
description = "Backport of PEP 654 (exception groups)"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -111,7 +115,6 @@ test = ["pytest (>=6)"]
name = "fastapi"
version = "0.95.2"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -133,7 +136,6 @@ test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -145,7 +147,6 @@ files = [
name = "httpcore"
version = "1.0.2"
description = "A minimal low-level HTTP client."
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -160,14 +161,13 @@ h11 = ">=0.13,<0.15"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
+socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<0.23.0)"]
[[package]]
name = "httpx"
version = "0.25.2"
description = "The next generation HTTP client."
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -178,21 +178,20 @@ files = [
[package.dependencies]
anyio = "*"
certifi = "*"
-httpcore = ">=1.0.0,<2.0.0"
+httpcore = "==1.*"
idna = "*"
sniffio = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
-cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"]
+cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (>=1.0.0,<2.0.0)"]
+socks = ["socksio (==1.*)"]
[[package]]
name = "idna"
version = "3.6"
description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
optional = false
python-versions = ">=3.5"
files = [
@@ -200,11 +199,24 @@ files = [
{file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"},
]
+[[package]]
+name = "macholib"
+version = "1.16.3"
+description = "Mach-O header analysis and editing"
+optional = false
+python-versions = "*"
+files = [
+ {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"},
+ {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"},
+]
+
+[package.dependencies]
+altgraph = ">=0.17"
+
[[package]]
name = "openai"
version = "1.3.7"
description = "The official Python library for the openai API"
-category = "main"
optional = false
python-versions = ">=3.7.1"
files = [
@@ -224,11 +236,32 @@ typing-extensions = ">=4.5,<5"
[package.extras]
datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"]
+[[package]]
+name = "packaging"
+version = "23.2"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
+ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
+]
+
+[[package]]
+name = "pefile"
+version = "2023.2.7"
+description = "Python PE parsing module"
+optional = false
+python-versions = ">=3.6.0"
+files = [
+ {file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"},
+ {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"},
+]
+
[[package]]
name = "pydantic"
version = "1.10.13"
description = "Data validation and settings management using python type hints"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -277,11 +310,55 @@ typing-extensions = ">=4.2.0"
dotenv = ["python-dotenv (>=0.10.4)"]
email = ["email-validator (>=1.0.3)"]
+[[package]]
+name = "pyinstaller"
+version = "6.2.0"
+description = "PyInstaller bundles a Python application and all its dependencies into a single package."
+optional = false
+python-versions = "<3.13,>=3.8"
+files = [
+ {file = "pyinstaller-6.2.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:a1adbd3cf25dc90926d783eae0f444d65cdfecc7bcdf6da522c3ae3ff47b4c25"},
+ {file = "pyinstaller-6.2.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:29d164394f1e949072f78a64c1e040f1c47b7f4aff08514c7666a031c8b44996"},
+ {file = "pyinstaller-6.2.0-py3-none-manylinux2014_i686.whl", hash = "sha256:ba602a38d7403de89c38b8956b221ce6de0280730d269bab522492fcad82ee33"},
+ {file = "pyinstaller-6.2.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:ebac06d99b80d2035594c3cc2fb5f2612d86289edd0510dbcbeb20a873f51d5a"},
+ {file = "pyinstaller-6.2.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:fcfabc0ff1d38a4262c051dea3fdc1f7f106405c1f1b491b4c79cd28df19cab6"},
+ {file = "pyinstaller-6.2.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:104430686149b2f1c135b2c17aa2967c85d54ef77dc92feb4e179ec846c0c467"},
+ {file = "pyinstaller-6.2.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:e87fd60292b53bb9965cb5a84122875469a2bd475fd0d0db0052a3f1be351f75"},
+ {file = "pyinstaller-6.2.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:8ec9d6c98972bb922cedb16a6638257aa66e5deadd79e2953f3464696237c413"},
+ {file = "pyinstaller-6.2.0-py3-none-win32.whl", hash = "sha256:e5561e9a9b946d835c8dbc11ae4c16cc21e62bc77d10cc043406dc2992dfb4c6"},
+ {file = "pyinstaller-6.2.0-py3-none-win_amd64.whl", hash = "sha256:3b586196277c4c54b69880650984c39c28bb6258c2b4b64200032e6ac69d53a0"},
+ {file = "pyinstaller-6.2.0-py3-none-win_arm64.whl", hash = "sha256:d0c87b605bf13c3a04dfaa1d2fa7cd36765b8137000eeadccba865e1d6a19bf0"},
+ {file = "pyinstaller-6.2.0.tar.gz", hash = "sha256:1ce77043929bf525be38289d78feecde0fcf15506215eda6500176a8715c5047"},
+]
+
+[package.dependencies]
+altgraph = "*"
+macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""}
+packaging = ">=22.0"
+pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""}
+pyinstaller-hooks-contrib = ">=2021.4"
+pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""}
+setuptools = ">=42.0.0"
+
+[package.extras]
+completion = ["argcomplete"]
+hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"]
+
+[[package]]
+name = "pyinstaller-hooks-contrib"
+version = "2023.10"
+description = "Community maintained hooks for PyInstaller"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pyinstaller-hooks-contrib-2023.10.tar.gz", hash = "sha256:4b4a998036abb713774cb26534ca06b7e6e09e4c628196017a10deb11a48747f"},
+ {file = "pyinstaller_hooks_contrib-2023.10-py2.py3-none-any.whl", hash = "sha256:6dc1786a8f452941245d5bb85893e2a33632ebdcbc4c23eea41f2ee08281b0c0"},
+]
+
[[package]]
name = "python-dotenv"
version = "1.0.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -292,11 +369,37 @@ files = [
[package.extras]
cli = ["click (>=5.0)"]
+[[package]]
+name = "pywin32-ctypes"
+version = "0.2.2"
+description = "A (partial) reimplementation of pywin32 using ctypes/cffi"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"},
+ {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"},
+]
+
+[[package]]
+name = "setuptools"
+version = "69.0.2"
+description = "Easily download, build, install, upgrade, and uninstall Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"},
+ {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+
[[package]]
name = "sniffio"
version = "1.3.0"
description = "Sniff out which async library your code is running under"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -308,7 +411,6 @@ files = [
name = "soupsieve"
version = "2.5"
description = "A modern CSS selector implementation for Beautiful Soup."
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -320,7 +422,6 @@ files = [
name = "starlette"
version = "0.27.0"
description = "The little ASGI library that shines."
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -338,7 +439,6 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyam
name = "tqdm"
version = "4.66.1"
description = "Fast, Extensible Progress Meter"
-category = "main"
optional = false
python-versions = ">=3.7"
files = [
@@ -359,7 +459,6 @@ telegram = ["requests"]
name = "typing-extensions"
version = "4.8.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -371,7 +470,6 @@ files = [
name = "uvicorn"
version = "0.24.0.post1"
description = "The lightning-fast ASGI server."
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -391,7 +489,6 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
name = "websockets"
version = "12.0"
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
-category = "main"
optional = false
python-versions = ">=3.8"
files = [
@@ -471,5 +568,5 @@ files = [
[metadata]
lock-version = "2.0"
-python-versions = "^3.10"
-content-hash = "b8d248a44a5eea9638a7726096de77d7a9aa8c00673da806534da2c228ffabb4"
+python-versions = "^3.10, <3.13"
+content-hash = "23222ae60dfaacf4357f3a66c4a363f3c7d9d7e3b365ceddc23585e7c5efc27b"
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
index cacdda9..d954787 100644
--- a/backend/pyproject.toml
+++ b/backend/pyproject.toml
@@ -6,7 +6,7 @@ authors = ["Abi Raja "]
license = "MIT"
[tool.poetry.dependencies]
-python = "^3.10"
+python = "^3.10, <3.13"
fastapi = "^0.95.0"
uvicorn = "^0.24.0.post1"
websockets = "^12.0"
@@ -15,6 +15,9 @@ python-dotenv = "^1.0.0"
beautifulsoup4 = "^4.12.2"
httpx = "^0.25.1"
+[tool.poetry.group.dev.dependencies]
+pyinstaller = "^6.2.0"
+
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
diff --git a/frontend/src/config.ts b/frontend/src/config.ts
index 4f0de2f..4bcb86a 100644
--- a/frontend/src/config.ts
+++ b/frontend/src/config.ts
@@ -2,11 +2,17 @@
export const IS_RUNNING_ON_CLOUD =
import.meta.env.VITE_IS_DEPLOYED === "true" || false;
-export const WS_BACKEND_URL =
- import.meta.env.VITE_WS_BACKEND_URL || "ws://127.0.0.1:7001";
+// If is web ui mode, use relative paths
+export const IS_WEB_UI_MODE =
+ import.meta.env.VITE_IS_WEB_UI_MODE === "true" || false;
-export const HTTP_BACKEND_URL =
- import.meta.env.VITE_HTTP_BACKEND_URL || "http://127.0.0.1:7001";
+export const WS_BACKEND_URL = IS_WEB_UI_MODE
+ ? `ws://${location.host}`
+ : import.meta.env.VITE_WS_BACKEND_URL || "ws://127.0.0.1:7001";
+
+export const HTTP_BACKEND_URL = IS_WEB_UI_MODE
+ ? location.origin
+ : import.meta.env.VITE_HTTP_BACKEND_URL || "http://127.0.0.1:7001";
export const PICO_BACKEND_FORM_SECRET =
import.meta.env.VITE_PICO_BACKEND_FORM_SECRET || null;