From 8197eec851ce1414c1d8ed0f57b971eff65cfb57 Mon Sep 17 00:00:00 2001 From: Hakeem Almidan Date: Sun, 7 Jan 2024 17:09:30 +0400 Subject: [PATCH 001/144] Update Troubleshooting.md --- Troubleshooting.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Troubleshooting.md b/Troubleshooting.md index 20fa815..ac6fe11 100644 --- a/Troubleshooting.md +++ b/Troubleshooting.md @@ -5,11 +5,12 @@ You don't need a ChatGPT Pro account. Screenshot to code uses API keys from your 1. Open [OpenAI Dashboard](https://platform.openai.com/) 1. Go to Settings > Billing 1. Click at the Add payment details -285636868-c80deb92-ab47-45cd-988f-deee67fbd44d -4. You have to buy some credits. The minimum is $5. +285636868-c80deb92-ab47-45cd-988f-deee67fbd44d +4. You have to buy some credits. The minimum is $5. 5. Go to Settings > Limits and check at the bottom of the page, your current tier has to be "Tier 1" to have GPT4 access -285636973-da38bd4d-8a78-4904-8027-ca67d729b933 +285636973-da38bd4d-8a78-4904-8027-ca67d729b933 + 6. Go to Screenshot to code and paste it in the Settings dialog under OpenAI key (gear icon). Your key is only stored in your browser. Never stored on our servers. Some users have also reported that it can take upto 30 minutes after your credit purchase for the GPT4 vision model to be activated. From c0e084aa86b2fced5d3f78c3351bdf38d0d84b54 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Wed, 21 Feb 2024 09:30:42 -0500 Subject: [PATCH 002/144] update evals --- backend/evals/core.py | 3 --- backend/run_evals.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/backend/evals/core.py b/backend/evals/core.py index 61db1a3..ba00c40 100644 --- a/backend/evals/core.py +++ b/backend/evals/core.py @@ -3,7 +3,6 @@ import os from llm import stream_openai_response from prompts import assemble_prompt from prompts.types import Stack -from utils import pprint_prompt async def generate_code_core(image_url: str, stack: Stack) -> str: @@ -11,8 +10,6 @@ async def generate_code_core(image_url: str, stack: Stack) -> str: openai_api_key = os.environ.get("OPENAI_API_KEY") openai_base_url = None - pprint_prompt(prompt_messages) - async def process_chunk(content: str): pass diff --git a/backend/run_evals.py b/backend/run_evals.py index ddb7eaa..75e76eb 100644 --- a/backend/run_evals.py +++ b/backend/run_evals.py @@ -23,7 +23,7 @@ async def main(): for filename in evals: filepath = os.path.join(INPUT_DIR, filename) data_url = await image_to_data_url(filepath) - task = generate_code_core(data_url, "vue_tailwind") + task = generate_code_core(data_url, "html_tailwind") tasks.append(task) results = await asyncio.gather(*tasks) From 5e2c2b25c37508084e09c34646edac51e6ff2643 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 23 Feb 2024 15:08:34 -0500 Subject: [PATCH 003/144] add pre-commit to run tests before each commit --- backend/.pre-commit-config.yaml | 9 ++ backend/poetry.lock | 206 ++++++++++++++++++++++++++------ backend/pyproject.toml | 1 + 3 files changed, 180 insertions(+), 36 deletions(-) create mode 100644 backend/.pre-commit-config.yaml diff --git a/backend/.pre-commit-config.yaml b/backend/.pre-commit-config.yaml new file mode 100644 index 0000000..eeff765 --- /dev/null +++ b/backend/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files diff --git a/backend/poetry.lock b/backend/poetry.lock index 42a44ee..544daab 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,10 +1,9 @@ -# 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 = "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 +25,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 +43,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 = [ @@ -53,11 +50,21 @@ files = [ {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] 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 = [ @@ -80,11 +86,21 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + [[package]] name = "distro" version = "1.8.0" description = "Distro - an OS platform information API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -96,7 +112,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 +126,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 = [ @@ -129,11 +143,26 @@ dev = ["pre-commit (>=2.17.0,<3.0.0)", "ruff (==0.0.138)", "uvicorn[standard] (> doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer-cli (>=0.0.13,<0.0.14)", "typer[all] (>=0.6.1,<0.8.0)"] test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6.5.0,<8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.982)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.7)", "pyyaml (>=5.3.1,<7.0.0)", "ruff (==0.0.138)", "sqlalchemy (>=1.3.18,<1.4.43)", "types-orjson (==3.6.2)", "types-ujson (==5.7.0.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"] +[[package]] +name = "filelock" +version = "3.13.1" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, + {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] + [[package]] 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 +174,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 +188,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 +205,34 @@ 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 = "identify" +version = "2.5.35" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, + {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, +] + +[package.extras] +license = ["ukkonen"] [[package]] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -204,7 +244,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -216,7 +255,6 @@ files = [ name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -231,7 +269,6 @@ setuptools = "*" 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 = [ @@ -255,7 +292,6 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -263,11 +299,25 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] +[[package]] +name = "platformdirs" +version = "4.2.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] + [[package]] name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -279,11 +329,28 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pre-commit" +version = "3.6.2" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.6.2-py2.py3-none-any.whl", hash = "sha256:ba637c2d7a670c10daedc059f5c49b5bd0aadbccfcd7ec15592cf9665117532c"}, + {file = "pre_commit-3.6.2.tar.gz", hash = "sha256:c3ef34f463045c88658c5b99f38c1e297abdcc0ff13f98d3370055fbbfabc67e"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[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 = [ @@ -336,7 +403,6 @@ email = ["email-validator (>=1.0.3)"] name = "pyright" version = "1.1.345" description = "Command line wrapper for pyright" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -355,7 +421,6 @@ dev = ["twine (>=3.4.1)"] name = "pytest" version = "7.4.3" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -378,7 +443,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no 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 = [ @@ -389,11 +453,69 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + [[package]] name = "setuptools" version = "69.0.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -410,7 +532,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar 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 = [ @@ -422,7 +543,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 = [ @@ -434,7 +554,6 @@ files = [ name = "starlette" version = "0.27.0" description = "The little ASGI library that shines." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -452,7 +571,6 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyam name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -464,7 +582,6 @@ files = [ name = "tqdm" version = "4.66.1" description = "Fast, Extensible Progress Meter" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -485,7 +602,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 = [ @@ -497,7 +613,6 @@ files = [ name = "uvicorn" version = "0.25.0" description = "The lightning-fast ASGI server." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -513,11 +628,30 @@ typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +[[package]] +name = "virtualenv" +version = "20.25.1" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, + {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + [[package]] name = "websockets" version = "12.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -598,4 +732,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "00950515c8d7f2061811ce196e4aa47204eb98ea078bbeb875548072dbfa38b1" +content-hash = "f364973209a56780ddca65ae126ebd42f024b1bd8c75e0132ecaaea8057c45e4" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index bdaaed1..cf0b8b1 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -14,6 +14,7 @@ openai = "^1.2.4" python-dotenv = "^1.0.0" beautifulsoup4 = "^4.12.2" httpx = "^0.25.1" +pre-commit = "^3.6.2" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" From 27165c02869b34dd491ab4bdbcf3805cb7ff295a Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 23 Feb 2024 15:08:55 -0500 Subject: [PATCH 004/144] fix end of file newlines --- frontend/Dockerfile | 2 +- frontend/components.json | 2 +- frontend/src/components/ui/select.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 938e2bf..b176926 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -16,4 +16,4 @@ COPY ./ /app/ EXPOSE 5173 # Command to run the application -CMD ["yarn", "dev", "--host", "0.0.0.0"] \ No newline at end of file +CMD ["yarn", "dev", "--host", "0.0.0.0"] diff --git a/frontend/components.json b/frontend/components.json index 88a9a6e..1cd599c 100644 --- a/frontend/components.json +++ b/frontend/components.json @@ -13,4 +13,4 @@ "components": "@/components", "utils": "@/lib/utils" } -} \ No newline at end of file +} diff --git a/frontend/src/components/ui/select.tsx b/frontend/src/components/ui/select.tsx index 1dcfce0..cdfb8ce 100644 --- a/frontend/src/components/ui/select.tsx +++ b/frontend/src/components/ui/select.tsx @@ -159,4 +159,4 @@ export { SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, -} \ No newline at end of file +} From d58d66d7ff2c8274415a18c31d4cab3680261cdb Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 23 Feb 2024 15:26:09 -0500 Subject: [PATCH 005/144] test committing a failing test --- backend/.pre-commit-config.yaml | 9 +++++++++ backend/prompts/test_prompts.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/.pre-commit-config.yaml b/backend/.pre-commit-config.yaml index eeff765..7a2fa43 100644 --- a/backend/.pre-commit-config.yaml +++ b/backend/.pre-commit-config.yaml @@ -7,3 +7,12 @@ repos: - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files + - repo: local + hooks: + - id: poetry-pytest + name: Run pytest with Poetry + entry: cd backend && poetry run pytest + language: system + pass_filenames: false + always_run: true + files: ^backend/ diff --git a/backend/prompts/test_prompts.py b/backend/prompts/test_prompts.py index 9e60410..28f3b0d 100644 --- a/backend/prompts/test_prompts.py +++ b/backend/prompts/test_prompts.py @@ -392,6 +392,6 @@ def test_imported_code_prompts(): svg = assemble_imported_code_prompt("code", "svg", "result_image_data_url") expected_svg = [ {"role": "system", "content": IMPORTED_CODE_SVG_SYSTEM_PROMPT}, - {"role": "user", "content": "Here is the code of the SVG: code"}, + {"role": "user", "content": "Hee is the code of the SVG: code"}, ] assert svg == expected_svg From 5e3a174203dd6e59603c2fa944b14c7b398bfade Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 23 Feb 2024 15:33:23 -0500 Subject: [PATCH 006/144] test again with fixed test now --- backend/.pre-commit-config.yaml | 2 +- backend/prompts/test_prompts.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/.pre-commit-config.yaml b/backend/.pre-commit-config.yaml index 7a2fa43..9a495ad 100644 --- a/backend/.pre-commit-config.yaml +++ b/backend/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: hooks: - id: poetry-pytest name: Run pytest with Poetry - entry: cd backend && poetry run pytest + entry: poetry run --directory backend pytest language: system pass_filenames: false always_run: true diff --git a/backend/prompts/test_prompts.py b/backend/prompts/test_prompts.py index 28f3b0d..9e60410 100644 --- a/backend/prompts/test_prompts.py +++ b/backend/prompts/test_prompts.py @@ -392,6 +392,6 @@ def test_imported_code_prompts(): svg = assemble_imported_code_prompt("code", "svg", "result_image_data_url") expected_svg = [ {"role": "system", "content": IMPORTED_CODE_SVG_SYSTEM_PROMPT}, - {"role": "user", "content": "Hee is the code of the SVG: code"}, + {"role": "user", "content": "Here is the code of the SVG: code"}, ] assert svg == expected_svg From 072b286b6dfa65eaa646f68def8a0b0f6d157217 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Wed, 28 Feb 2024 15:44:40 -0500 Subject: [PATCH 007/144] Update README.md --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index bc82497..2f0ccc4 100644 --- a/README.md +++ b/README.md @@ -38,12 +38,6 @@ poetry shell poetry run uvicorn main:app --reload --port 7001 ``` -You can also run the backend (when you're in `backend`): - -```bash -poetry run pyright -``` - Run the frontend: ```bash From bd407e51f9453b639303f67fd16f320f0aa9d358 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Mon, 4 Mar 2024 14:58:01 -0500 Subject: [PATCH 008/144] code clean up --- backend/run_evals.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/run_evals.py b/backend/run_evals.py index 75e76eb..a5cfefb 100644 --- a/backend/run_evals.py +++ b/backend/run_evals.py @@ -11,6 +11,8 @@ from evals.config import EVALS_DIR from evals.core import generate_code_core from evals.utils import image_to_data_url +STACK = "html_tailwind" + async def main(): INPUT_DIR = EVALS_DIR + "/inputs" @@ -23,7 +25,7 @@ async def main(): for filename in evals: filepath = os.path.join(INPUT_DIR, filename) data_url = await image_to_data_url(filepath) - task = generate_code_core(data_url, "html_tailwind") + task = generate_code_core(data_url, STACK) tasks.append(task) results = await asyncio.gather(*tasks) From f0ff9b21ba25767b8df57e654a15a51f78055e53 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Mon, 4 Mar 2024 16:21:04 -0500 Subject: [PATCH 009/144] fix vs code settings for python import path --- .vscode/settings.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d6e2638..bfbce46 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,5 @@ { - "python.analysis.typeCheckingMode": "strict" + "python.analysis.typeCheckingMode": "strict", + "python.analysis.extraPaths": ["./backend"], + "python.autoComplete.extraPaths": ["./backend"] } From 1bc26b616fc6ec9c8fd749a002fe256a72f272f9 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 09:44:23 -0500 Subject: [PATCH 010/144] Update README.md --- README.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/README.md b/README.md index 2f0ccc4..03a4250 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,6 @@ See the [Examples](#-examples) section below for more demos. 🆕 [Try it here](https://screenshottocode.com) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#%EF%B8%8F-faqs) section below for details**). Or see [Getting Started](#-getting-started) below for local install instructions. -## 🌟 Recent Updates - -- Dec 11 - Start a new project from existing code (allows you to come back to an older project) -- Dec 7 - 🔥 🔥 🔥 View a history of your edits, and branch off them -- Nov 30 - Dark mode, output code in Ionic (thanks [@dialmedu](https://github.com/dialmedu)), set OpenAI base URL -- Nov 28 - 🔥 🔥 🔥 Customize your stack: React or Bootstrap or TailwindCSS -- Nov 23 - Send in a screenshot of the current replicated version (sometimes improves quality of subsequent generations) -- Nov 21 - Edit code in the code editor and preview changes live thanks to [@clean99](https://github.com/clean99) -- Nov 20 - Paste in a URL to screenshot and clone (requires [ScreenshotOne free API key](https://screenshotone.com?via=screenshot-to-code)) -- Nov 19 - Support for dark/light code editor theme - thanks [@kachbit](https://github.com/kachbit) -- Nov 16 - Added a setting to disable DALL-E image generation if you don't need that -- Nov 16 - View code directly within the app -- Nov 15 - You can now instruct the AI to update the code as you wish. It is helpful if the AI messed up some styles or missed a section. ## 🛠 Getting Started From 7b62147a584983b74ed11ef6b01baabd86f3ba0f Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 12:24:30 -0500 Subject: [PATCH 011/144] support Claude 3 Sonnet --- backend/config.py | 1 + backend/evals/core.py | 32 +- backend/llm.py | 56 ++- backend/poetry.lock | 358 +++++++++++++++++- backend/pyproject.toml | 1 + backend/routes/generate_code.py | 44 ++- frontend/src/App.tsx | 22 ++ .../src/components/ModelSettingsSection.tsx | 65 ++++ frontend/src/lib/models.ts | 12 + frontend/src/types.ts | 2 + 10 files changed, 573 insertions(+), 20 deletions(-) create mode 100644 frontend/src/components/ModelSettingsSection.tsx create mode 100644 frontend/src/lib/models.ts diff --git a/backend/config.py b/backend/config.py index 7dd21f9..d173231 100644 --- a/backend/config.py +++ b/backend/config.py @@ -3,6 +3,7 @@ # TODO: Should only be set to true when value is 'True', not any abitrary truthy value import os +ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", None) SHOULD_MOCK_AI_RESPONSE = bool(os.environ.get("MOCK", False)) diff --git a/backend/evals/core.py b/backend/evals/core.py index ba00c40..3438a7d 100644 --- a/backend/evals/core.py +++ b/backend/evals/core.py @@ -1,26 +1,40 @@ import os +from config import ANTHROPIC_API_KEY -from llm import stream_openai_response +from llm import stream_claude_response, stream_openai_response from prompts import assemble_prompt from prompts.types import Stack async def generate_code_core(image_url: str, stack: Stack) -> str: + model = "CLAUDE" + prompt_messages = assemble_prompt(image_url, stack) openai_api_key = os.environ.get("OPENAI_API_KEY") + anthropic_api_key = ANTHROPIC_API_KEY openai_base_url = None async def process_chunk(content: str): pass - if not openai_api_key: - raise Exception("OpenAI API key not found") + if model == "CLAUDE": + if not anthropic_api_key: + raise Exception("Anthropic API key not found") - completion = await stream_openai_response( - prompt_messages, - api_key=openai_api_key, - base_url=openai_base_url, - callback=lambda x: process_chunk(x), - ) + completion = await stream_claude_response( + prompt_messages, + api_key=anthropic_api_key, + callback=lambda x: process_chunk(x), + ) + else: + if not openai_api_key: + raise Exception("OpenAI API key not found") + + completion = await stream_openai_response( + prompt_messages, + api_key=openai_api_key, + base_url=openai_base_url, + callback=lambda x: process_chunk(x), + ) return completion diff --git a/backend/llm.py b/backend/llm.py index 66e3a47..c6d629e 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -1,8 +1,18 @@ -from typing import Awaitable, Callable, List +from typing import Awaitable, Callable, List, cast +from anthropic import AsyncAnthropic from openai import AsyncOpenAI from openai.types.chat import ChatCompletionMessageParam, ChatCompletionChunk MODEL_GPT_4_VISION = "gpt-4-vision-preview" +MODEL_CLAUDE_SONNET = "claude-3-sonnet-20240229" +MODEL_CLAUDE_OPUS = "claude-3-opus-20240229" + + +# Keep in sync with frontend (lib/models.ts) +CODE_GENERATION_MODELS = [ + "gpt_4_vision", + "claude_3_sonnet", +] async def stream_openai_response( @@ -34,3 +44,47 @@ async def stream_openai_response( await client.close() return full_response + + +async def stream_claude_response( + messages: List[ChatCompletionMessageParam], + api_key: str, + callback: Callable[[str], Awaitable[None]], +) -> str: + + client = AsyncAnthropic(api_key=api_key) + + # Base parameters + model = MODEL_CLAUDE_SONNET + max_tokens = 4096 + temperature = 0.0 + + # Translate OpenAI messages to Claude messages + system_prompt = cast(str, messages[0]["content"]) + claude_messages = [dict(message) for message in messages[1:]] + for message in claude_messages: + if not isinstance(message["content"], list): + continue + + for content in message["content"]: # type: ignore + if content["type"] == "image_url": + content["type"] = "image" + content["source"] = { + "type": "base64", + "media_type": "image/png", # TODO: Automatically detect media type + "data": content["image_url"]["url"].split(",")[1], # type: ignore + } + del content["image_url"] + + async with client.messages.stream( + model=model, + max_tokens=max_tokens, + temperature=temperature, + system=system_prompt, + messages=claude_messages, # type: ignore + ) as stream: + async for text in stream.text_stream: + await callback(text) + + response = await stream.get_final_message() + return response.content[0].text diff --git a/backend/poetry.lock b/backend/poetry.lock index 544daab..53e5215 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1,5 +1,29 @@ # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +[[package]] +name = "anthropic" +version = "0.18.0" +description = "The official Python library for the anthropic API" +optional = false +python-versions = ">=3.7" +files = [ + {file = "anthropic-0.18.0-py3-none-any.whl", hash = "sha256:af2a65a48ba4661e3902b49f96710d168782af9deb06d58c12f25e53448fb570"}, + {file = "anthropic-0.18.0.tar.gz", hash = "sha256:80a6134f6562792be8ec30f743e2211476cb1e1f59b01795b901f099ae854825"}, +] + +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +tokenizers = ">=0.13.0" +typing-extensions = ">=4.7,<5" + +[package.extras] +bedrock = ["boto3 (>=1.28.57)", "botocore (>=1.31.57)"] +vertex = ["google-auth (>=2,<3)"] + [[package]] name = "anyio" version = "3.7.1" @@ -61,6 +85,105 @@ files = [ {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + [[package]] name = "click" version = "8.1.7" @@ -159,6 +282,41 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1 testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] +[[package]] +name = "fsspec" +version = "2024.2.0" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fsspec-2024.2.0-py3-none-any.whl", hash = "sha256:817f969556fa5916bc682e02ca2045f96ff7f586d45110fcb76022063ad2c7d8"}, + {file = "fsspec-2024.2.0.tar.gz", hash = "sha256:b6ad1a679f760dda52b1168c859d01b7b80648ea6f7f7c7f5a8a91dc3f3ecb84"}, +] + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +devel = ["pytest", "pytest-cov"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +tqdm = ["tqdm"] + [[package]] name = "h11" version = "0.14.0" @@ -215,6 +373,39 @@ cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +[[package]] +name = "huggingface-hub" +version = "0.21.3" +description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "huggingface_hub-0.21.3-py3-none-any.whl", hash = "sha256:b183144336fdf2810a8c109822e0bb6ef1fd61c65da6fb60e8c3f658b7144016"}, + {file = "huggingface_hub-0.21.3.tar.gz", hash = "sha256:26a15b604e4fc7bad37c467b76456543ec849386cbca9cd7e1e135f53e500423"}, +] + +[package.dependencies] +filelock = "*" +fsspec = ">=2023.5.0" +packaging = ">=20.9" +pyyaml = ">=5.1" +requests = "*" +tqdm = ">=4.42.1" +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "mypy (==1.5.1)", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.1.3)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +cli = ["InquirerPy (==0.3.4)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "mypy (==1.5.1)", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.1.3)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] +hf-transfer = ["hf-transfer (>=0.1.4)"] +inference = ["aiohttp", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)"] +quality = ["mypy (==1.5.1)", "ruff (>=0.1.3)"] +tensorflow = ["graphviz", "pydot", "tensorflow"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "gradio", "jedi", "numpy", "pydantic (>1.1,<2.0)", "pydantic (>1.1,<3.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +torch = ["safetensors", "torch"] +typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] + [[package]] name = "identify" version = "2.5.35" @@ -512,6 +703,27 @@ files = [ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + [[package]] name = "setuptools" version = "69.0.3" @@ -567,6 +779,133 @@ anyio = ">=3.4.0,<5" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +[[package]] +name = "tokenizers" +version = "0.15.2" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tokenizers-0.15.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:52f6130c9cbf70544287575a985bf44ae1bda2da7e8c24e97716080593638012"}, + {file = "tokenizers-0.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:054c1cc9c6d68f7ffa4e810b3d5131e0ba511b6e4be34157aa08ee54c2f8d9ee"}, + {file = "tokenizers-0.15.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a9b9b070fdad06e347563b88c278995735292ded1132f8657084989a4c84a6d5"}, + {file = "tokenizers-0.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea621a7eef4b70e1f7a4e84dd989ae3f0eeb50fc8690254eacc08acb623e82f1"}, + {file = "tokenizers-0.15.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf7fd9a5141634fa3aa8d6b7be362e6ae1b4cda60da81388fa533e0b552c98fd"}, + {file = "tokenizers-0.15.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44f2a832cd0825295f7179eaf173381dc45230f9227ec4b44378322d900447c9"}, + {file = "tokenizers-0.15.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b9ec69247a23747669ec4b0ca10f8e3dfb3545d550258129bd62291aabe8605"}, + {file = "tokenizers-0.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b6a4c78da863ff26dbd5ad9a8ecc33d8a8d97b535172601cf00aee9d7ce9ce"}, + {file = "tokenizers-0.15.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5ab2a4d21dcf76af60e05af8063138849eb1d6553a0d059f6534357bce8ba364"}, + {file = "tokenizers-0.15.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a47acfac7e511f6bbfcf2d3fb8c26979c780a91e06fb5b9a43831b2c0153d024"}, + {file = "tokenizers-0.15.2-cp310-none-win32.whl", hash = "sha256:064ff87bb6acdbd693666de9a4b692add41308a2c0ec0770d6385737117215f2"}, + {file = "tokenizers-0.15.2-cp310-none-win_amd64.whl", hash = "sha256:3b919afe4df7eb6ac7cafd2bd14fb507d3f408db7a68c43117f579c984a73843"}, + {file = "tokenizers-0.15.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:89cd1cb93e4b12ff39bb2d626ad77e35209de9309a71e4d3d4672667b4b256e7"}, + {file = "tokenizers-0.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfed5c64e5be23d7ee0f0e98081a25c2a46b0b77ce99a4f0605b1ec43dd481fa"}, + {file = "tokenizers-0.15.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a907d76dcfda37023ba203ab4ceeb21bc5683436ebefbd895a0841fd52f6f6f2"}, + {file = "tokenizers-0.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20ea60479de6fc7b8ae756b4b097572372d7e4032e2521c1bbf3d90c90a99ff0"}, + {file = "tokenizers-0.15.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48e2b9335be2bc0171df9281385c2ed06a15f5cf121c44094338306ab7b33f2c"}, + {file = "tokenizers-0.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:112a1dd436d2cc06e6ffdc0b06d55ac019a35a63afd26475205cb4b1bf0bfbff"}, + {file = "tokenizers-0.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4620cca5c2817177ee8706f860364cc3a8845bc1e291aaf661fb899e5d1c45b0"}, + {file = "tokenizers-0.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccd73a82751c523b3fc31ff8194702e4af4db21dc20e55b30ecc2079c5d43cb7"}, + {file = "tokenizers-0.15.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:107089f135b4ae7817affe6264f8c7a5c5b4fd9a90f9439ed495f54fcea56fb4"}, + {file = "tokenizers-0.15.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0ff110ecc57b7aa4a594396525a3451ad70988e517237fe91c540997c4e50e29"}, + {file = "tokenizers-0.15.2-cp311-none-win32.whl", hash = "sha256:6d76f00f5c32da36c61f41c58346a4fa7f0a61be02f4301fd30ad59834977cc3"}, + {file = "tokenizers-0.15.2-cp311-none-win_amd64.whl", hash = "sha256:cc90102ed17271cf0a1262babe5939e0134b3890345d11a19c3145184b706055"}, + {file = "tokenizers-0.15.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f86593c18d2e6248e72fb91c77d413a815153b8ea4e31f7cd443bdf28e467670"}, + {file = "tokenizers-0.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0774bccc6608eca23eb9d620196687c8b2360624619623cf4ba9dc9bd53e8b51"}, + {file = "tokenizers-0.15.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d0222c5b7c9b26c0b4822a82f6a7011de0a9d3060e1da176f66274b70f846b98"}, + {file = "tokenizers-0.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3835738be1de66624fff2f4f6f6684775da4e9c00bde053be7564cbf3545cc66"}, + {file = "tokenizers-0.15.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0143e7d9dcd811855c1ce1ab9bf5d96d29bf5e528fd6c7824d0465741e8c10fd"}, + {file = "tokenizers-0.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db35825f6d54215f6b6009a7ff3eedee0848c99a6271c870d2826fbbedf31a38"}, + {file = "tokenizers-0.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f5e64b0389a2be47091d8cc53c87859783b837ea1a06edd9d8e04004df55a5c"}, + {file = "tokenizers-0.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e0480c452217edd35eca56fafe2029fb4d368b7c0475f8dfa3c5c9c400a7456"}, + {file = "tokenizers-0.15.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a33ab881c8fe70474980577e033d0bc9a27b7ab8272896e500708b212995d834"}, + {file = "tokenizers-0.15.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a308a607ca9de2c64c1b9ba79ec9a403969715a1b8ba5f998a676826f1a7039d"}, + {file = "tokenizers-0.15.2-cp312-none-win32.whl", hash = "sha256:b8fcfa81bcb9447df582c5bc96a031e6df4da2a774b8080d4f02c0c16b42be0b"}, + {file = "tokenizers-0.15.2-cp312-none-win_amd64.whl", hash = "sha256:38d7ab43c6825abfc0b661d95f39c7f8af2449364f01d331f3b51c94dcff7221"}, + {file = "tokenizers-0.15.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:38bfb0204ff3246ca4d5e726e8cc8403bfc931090151e6eede54d0e0cf162ef0"}, + {file = "tokenizers-0.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c861d35e8286a53e06e9e28d030b5a05bcbf5ac9d7229e561e53c352a85b1fc"}, + {file = "tokenizers-0.15.2-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:936bf3842db5b2048eaa53dade907b1160f318e7c90c74bfab86f1e47720bdd6"}, + {file = "tokenizers-0.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:620beacc3373277700d0e27718aa8b25f7b383eb8001fba94ee00aeea1459d89"}, + {file = "tokenizers-0.15.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2735ecbbf37e52db4ea970e539fd2d450d213517b77745114f92867f3fc246eb"}, + {file = "tokenizers-0.15.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:473c83c5e2359bb81b0b6fde870b41b2764fcdd36d997485e07e72cc3a62264a"}, + {file = "tokenizers-0.15.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968fa1fb3c27398b28a4eca1cbd1e19355c4d3a6007f7398d48826bbe3a0f728"}, + {file = "tokenizers-0.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:865c60ae6eaebdde7da66191ee9b7db52e542ed8ee9d2c653b6d190a9351b980"}, + {file = "tokenizers-0.15.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7c0d8b52664ab2d4a8d6686eb5effc68b78608a9008f086a122a7b2996befbab"}, + {file = "tokenizers-0.15.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f33dfbdec3784093a9aebb3680d1f91336c56d86cc70ddf88708251da1fe9064"}, + {file = "tokenizers-0.15.2-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:d44ba80988ff9424e33e0a49445072ac7029d8c0e1601ad25a0ca5f41ed0c1d6"}, + {file = "tokenizers-0.15.2-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:dce74266919b892f82b1b86025a613956ea0ea62a4843d4c4237be2c5498ed3a"}, + {file = "tokenizers-0.15.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0ef06b9707baeb98b316577acb04f4852239d856b93e9ec3a299622f6084e4be"}, + {file = "tokenizers-0.15.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c73e2e74bbb07910da0d37c326869f34113137b23eadad3fc00856e6b3d9930c"}, + {file = "tokenizers-0.15.2-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4eeb12daf02a59e29f578a865f55d87cd103ce62bd8a3a5874f8fdeaa82e336b"}, + {file = "tokenizers-0.15.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ba9f6895af58487ca4f54e8a664a322f16c26bbb442effd01087eba391a719e"}, + {file = "tokenizers-0.15.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccec77aa7150e38eec6878a493bf8c263ff1fa8a62404e16c6203c64c1f16a26"}, + {file = "tokenizers-0.15.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3f40604f5042ff210ba82743dda2b6aa3e55aa12df4e9f2378ee01a17e2855e"}, + {file = "tokenizers-0.15.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5645938a42d78c4885086767c70923abad047163d809c16da75d6b290cb30bbe"}, + {file = "tokenizers-0.15.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:05a77cbfebe28a61ab5c3891f9939cc24798b63fa236d84e5f29f3a85a200c00"}, + {file = "tokenizers-0.15.2-cp37-none-win32.whl", hash = "sha256:361abdc068e8afe9c5b818769a48624687fb6aaed49636ee39bec4e95e1a215b"}, + {file = "tokenizers-0.15.2-cp37-none-win_amd64.whl", hash = "sha256:7ef789f83eb0f9baeb4d09a86cd639c0a5518528f9992f38b28e819df397eb06"}, + {file = "tokenizers-0.15.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4fe1f74a902bee74a3b25aff180fbfbf4f8b444ab37c4d496af7afd13a784ed2"}, + {file = "tokenizers-0.15.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c4b89038a684f40a6b15d6b09f49650ac64d951ad0f2a3ea9169687bbf2a8ba"}, + {file = "tokenizers-0.15.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d05a1b06f986d41aed5f2de464c003004b2df8aaf66f2b7628254bcbfb72a438"}, + {file = "tokenizers-0.15.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508711a108684111ec8af89d3a9e9e08755247eda27d0ba5e3c50e9da1600f6d"}, + {file = "tokenizers-0.15.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:daa348f02d15160cb35439098ac96e3a53bacf35885072611cd9e5be7d333daa"}, + {file = "tokenizers-0.15.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:494fdbe5932d3416de2a85fc2470b797e6f3226c12845cadf054dd906afd0442"}, + {file = "tokenizers-0.15.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2d60f5246f4da9373f75ff18d64c69cbf60c3bca597290cea01059c336d2470"}, + {file = "tokenizers-0.15.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93268e788825f52de4c7bdcb6ebc1fcd4a5442c02e730faa9b6b08f23ead0e24"}, + {file = "tokenizers-0.15.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6fc7083ab404019fc9acafe78662c192673c1e696bd598d16dc005bd663a5cf9"}, + {file = "tokenizers-0.15.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e39b41e5531d6b2122a77532dbea60e171ef87a3820b5a3888daa847df4153"}, + {file = "tokenizers-0.15.2-cp38-none-win32.whl", hash = "sha256:06cd0487b1cbfabefb2cc52fbd6b1f8d4c37799bd6c6e1641281adaa6b2504a7"}, + {file = "tokenizers-0.15.2-cp38-none-win_amd64.whl", hash = "sha256:5179c271aa5de9c71712e31cb5a79e436ecd0d7532a408fa42a8dbfa4bc23fd9"}, + {file = "tokenizers-0.15.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82f8652a74cc107052328b87ea8b34291c0f55b96d8fb261b3880216a9f9e48e"}, + {file = "tokenizers-0.15.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:02458bee6f5f3139f1ebbb6d042b283af712c0981f5bc50edf771d6b762d5e4f"}, + {file = "tokenizers-0.15.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c9a09cd26cca2e1c349f91aa665309ddb48d71636370749414fbf67bc83c5343"}, + {file = "tokenizers-0.15.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:158be8ea8554e5ed69acc1ce3fbb23a06060bd4bbb09029431ad6b9a466a7121"}, + {file = "tokenizers-0.15.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ddba9a2b0c8c81633eca0bb2e1aa5b3a15362b1277f1ae64176d0f6eba78ab1"}, + {file = "tokenizers-0.15.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ef5dd1d39797044642dbe53eb2bc56435308432e9c7907728da74c69ee2adca"}, + {file = "tokenizers-0.15.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:454c203164e07a860dbeb3b1f4a733be52b0edbb4dd2e5bd75023ffa8b49403a"}, + {file = "tokenizers-0.15.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cf6b7f1d4dc59af960e6ffdc4faffe6460bbfa8dce27a58bf75755ffdb2526d"}, + {file = "tokenizers-0.15.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2ef09bbc16519f6c25d0c7fc0c6a33a6f62923e263c9d7cca4e58b8c61572afb"}, + {file = "tokenizers-0.15.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c9a2ebdd2ad4ec7a68e7615086e633857c85e2f18025bd05d2a4399e6c5f7169"}, + {file = "tokenizers-0.15.2-cp39-none-win32.whl", hash = "sha256:918fbb0eab96fe08e72a8c2b5461e9cce95585d82a58688e7f01c2bd546c79d0"}, + {file = "tokenizers-0.15.2-cp39-none-win_amd64.whl", hash = "sha256:524e60da0135e106b254bd71f0659be9f89d83f006ea9093ce4d1fab498c6d0d"}, + {file = "tokenizers-0.15.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6a9b648a58281c4672212fab04e60648fde574877d0139cd4b4f93fe28ca8944"}, + {file = "tokenizers-0.15.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7c7d18b733be6bbca8a55084027f7be428c947ddf871c500ee603e375013ffba"}, + {file = "tokenizers-0.15.2-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:13ca3611de8d9ddfbc4dc39ef54ab1d2d4aaa114ac8727dfdc6a6ec4be017378"}, + {file = "tokenizers-0.15.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:237d1bf3361cf2e6463e6c140628e6406766e8b27274f5fcc62c747ae3c6f094"}, + {file = "tokenizers-0.15.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67a0fe1e49e60c664915e9fb6b0cb19bac082ab1f309188230e4b2920230edb3"}, + {file = "tokenizers-0.15.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4e022fe65e99230b8fd89ebdfea138c24421f91c1a4f4781a8f5016fd5cdfb4d"}, + {file = "tokenizers-0.15.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d857be2df69763362ac699f8b251a8cd3fac9d21893de129bc788f8baaef2693"}, + {file = "tokenizers-0.15.2-pp37-pypy37_pp73-macosx_10_12_x86_64.whl", hash = "sha256:708bb3e4283177236309e698da5fcd0879ce8fd37457d7c266d16b550bcbbd18"}, + {file = "tokenizers-0.15.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:64c35e09e9899b72a76e762f9854e8750213f67567787d45f37ce06daf57ca78"}, + {file = "tokenizers-0.15.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1257f4394be0d3b00de8c9e840ca5601d0a4a8438361ce9c2b05c7d25f6057b"}, + {file = "tokenizers-0.15.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02272fe48280e0293a04245ca5d919b2c94a48b408b55e858feae9618138aeda"}, + {file = "tokenizers-0.15.2-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dc3ad9ebc76eabe8b1d7c04d38be884b8f9d60c0cdc09b0aa4e3bcf746de0388"}, + {file = "tokenizers-0.15.2-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:32e16bdeffa7c4f46bf2152172ca511808b952701d13e7c18833c0b73cb5c23f"}, + {file = "tokenizers-0.15.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fb16ba563d59003028b678d2361a27f7e4ae0ab29c7a80690efa20d829c81fdb"}, + {file = "tokenizers-0.15.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:2277c36d2d6cdb7876c274547921a42425b6810d38354327dd65a8009acf870c"}, + {file = "tokenizers-0.15.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1cf75d32e8d250781940d07f7eece253f2fe9ecdb1dc7ba6e3833fa17b82fcbc"}, + {file = "tokenizers-0.15.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1b3b31884dc8e9b21508bb76da80ebf7308fdb947a17affce815665d5c4d028"}, + {file = "tokenizers-0.15.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10122d8d8e30afb43bb1fe21a3619f62c3e2574bff2699cf8af8b0b6c5dc4a3"}, + {file = "tokenizers-0.15.2-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d88b96ff0fe8e91f6ef01ba50b0d71db5017fa4e3b1d99681cec89a85faf7bf7"}, + {file = "tokenizers-0.15.2-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:37aaec5a52e959892870a7c47cef80c53797c0db9149d458460f4f31e2fb250e"}, + {file = "tokenizers-0.15.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e2ea752f2b0fe96eb6e2f3adbbf4d72aaa1272079b0dfa1145507bd6a5d537e6"}, + {file = "tokenizers-0.15.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4b19a808d8799fda23504a5cd31d2f58e6f52f140380082b352f877017d6342b"}, + {file = "tokenizers-0.15.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:64c86e5e068ac8b19204419ed8ca90f9d25db20578f5881e337d203b314f4104"}, + {file = "tokenizers-0.15.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de19c4dc503c612847edf833c82e9f73cd79926a384af9d801dcf93f110cea4e"}, + {file = "tokenizers-0.15.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea09acd2fe3324174063d61ad620dec3bcf042b495515f27f638270a7d466e8b"}, + {file = "tokenizers-0.15.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cf27fd43472e07b57cf420eee1e814549203d56de00b5af8659cb99885472f1f"}, + {file = "tokenizers-0.15.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:7ca22bd897537a0080521445d91a58886c8c04084a6a19e6c78c586e0cfa92a5"}, + {file = "tokenizers-0.15.2.tar.gz", hash = "sha256:e6e9c6e019dd5484be5beafc775ae6c925f4c69a3487040ed09b45e13df2cb91"}, +] + +[package.dependencies] +huggingface_hub = ">=0.16.4,<1.0" + +[package.extras] +dev = ["tokenizers[testing]"] +docs = ["setuptools_rust", "sphinx", "sphinx_rtd_theme"] +testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests"] + [[package]] name = "tomli" version = "2.0.1" @@ -609,6 +948,23 @@ files = [ {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] +[[package]] +name = "urllib3" +version = "2.2.1" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "uvicorn" version = "0.25.0" @@ -732,4 +1088,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "f364973209a56780ddca65ae126ebd42f024b1bd8c75e0132ecaaea8057c45e4" +content-hash = "6fa98703309c862dd33d9d9ce210b71bfcb37e7d7ff030198d9044d83c249060" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index cf0b8b1..8c64b3f 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -15,6 +15,7 @@ python-dotenv = "^1.0.0" beautifulsoup4 = "^4.12.2" httpx = "^0.25.1" pre-commit = "^3.6.2" +anthropic = "^0.18.0" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" diff --git a/backend/routes/generate_code.py b/backend/routes/generate_code.py index 092289c..589be2d 100644 --- a/backend/routes/generate_code.py +++ b/backend/routes/generate_code.py @@ -2,8 +2,12 @@ import os import traceback from fastapi import APIRouter, WebSocket import openai -from config import IS_PROD, SHOULD_MOCK_AI_RESPONSE -from llm import stream_openai_response +from config import ANTHROPIC_API_KEY, IS_PROD, SHOULD_MOCK_AI_RESPONSE +from llm import ( + CODE_GENERATION_MODELS, + stream_claude_response, + stream_openai_response, +) from openai.types.chat import ChatCompletionMessageParam from mock_llm import mock_completion from typing import Dict, List, cast, get_args @@ -60,7 +64,16 @@ async def stream_code(websocket: WebSocket): generated_code_config = "" if "generatedCodeConfig" in params and params["generatedCodeConfig"]: generated_code_config = params["generatedCodeConfig"] - print(f"Generating {generated_code_config} code") + + # Read the model from the request. Fall back to default if not provided. + code_generation_model = params.get("codeGenerationModel", "gpt_4_vision") + if code_generation_model not in CODE_GENERATION_MODELS: + await throw_error(f"Invalid model: {code_generation_model}") + raise Exception(f"Invalid model: {code_generation_model}") + + print( + f"Generating {generated_code_config} code using {code_generation_model} model..." + ) # Get the OpenAI API key from the request. Fall back to environment variable if not provided. # If neither is provided, we throw an error. @@ -196,12 +209,25 @@ async def stream_code(websocket: WebSocket): completion = await mock_completion(process_chunk) else: try: - completion = await stream_openai_response( - prompt_messages, - api_key=openai_api_key, - base_url=openai_base_url, - callback=lambda x: process_chunk(x), - ) + if code_generation_model == "claude_3_sonnet": + if not ANTHROPIC_API_KEY: + await throw_error( + "No Anthropic API key found. Please add it to backend/.env" + ) + raise Exception("No Anthropic key") + + completion = await stream_claude_response( + prompt_messages, + api_key=ANTHROPIC_API_KEY, + callback=lambda x: process_chunk(x), + ) + else: + completion = await stream_openai_response( + prompt_messages, + api_key=openai_api_key, + base_url=openai_base_url, + callback=lambda x: process_chunk(x), + ) except openai.AuthenticationError as e: print("[GENERATE_CODE] Authentication failed", e) error_message = ( diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3f7a025..6a2a38c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -35,6 +35,8 @@ import { extractHistoryTree } from "./components/history/utils"; import toast from "react-hot-toast"; import ImportCodeSection from "./components/ImportCodeSection"; import { Stack } from "./lib/stacks"; +import { CodeGenerationModel } from "./lib/models"; +import ModelSettingsSection from "./components/ModelSettingsSection"; const IS_OPENAI_DOWN = false; @@ -56,6 +58,7 @@ function App() { isImageGenerationEnabled: true, editorTheme: EditorTheme.COBALT, generatedCodeConfig: Stack.HTML_TAILWIND, + codeGenerationModel: CodeGenerationModel.GPT_4_VISION, // Only relevant for hosted version isTermOfServiceAccepted: false, accessCode: null, @@ -63,6 +66,10 @@ function App() { "setting" ); + // Code generation model from local storage or the default value + const selectedCodeGenerationModel = + settings.codeGenerationModel || CodeGenerationModel.GPT_4_VISION; + // App history const [appHistory, setAppHistory] = useState([]); // Tracks the currently shown version from app history @@ -291,6 +298,13 @@ function App() { })); } + function setCodeGenerationModel(codeGenerationModel: CodeGenerationModel) { + setSettings((prev) => ({ + ...prev, + codeGenerationModel, + })); + } + function importFromCode(code: string, stack: Stack) { setIsImportedFromCode(true); @@ -334,6 +348,14 @@ function App() { } /> + + {IS_RUNNING_ON_CLOUD && !(settings.openAiApiKey || settings.accessCode) && ( diff --git a/frontend/src/components/ModelSettingsSection.tsx b/frontend/src/components/ModelSettingsSection.tsx new file mode 100644 index 0000000..a105e63 --- /dev/null +++ b/frontend/src/components/ModelSettingsSection.tsx @@ -0,0 +1,65 @@ +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, +} from "./ui/select"; +import { + CODE_GENERATION_MODEL_DESCRIPTIONS, + CodeGenerationModel, +} from "../lib/models"; +import { Badge } from "./ui/badge"; + +interface Props { + codeGenerationModel: CodeGenerationModel; + setCodeGenerationModel: (codeGenerationModel: CodeGenerationModel) => void; + shouldDisableUpdates?: boolean; +} + +function ModelSettingsSection({ + codeGenerationModel, + setCodeGenerationModel, + shouldDisableUpdates = false, +}: Props) { + return ( +
+
+ Model: + +
+
+ ); +} + +export default ModelSettingsSection; diff --git a/frontend/src/lib/models.ts b/frontend/src/lib/models.ts new file mode 100644 index 0000000..a972f78 --- /dev/null +++ b/frontend/src/lib/models.ts @@ -0,0 +1,12 @@ +// Keep in sync with backend (llm.py) +export enum CodeGenerationModel { + GPT_4_VISION = "gpt_4_vision", + CLAUDE_3_SONNET = "claude_3_sonnet", +} + +export const CODE_GENERATION_MODEL_DESCRIPTIONS: { + [key in CodeGenerationModel]: { name: string; inBeta: boolean }; +} = { + gpt_4_vision: { name: "GPT-4 Vision", inBeta: false }, + claude_3_sonnet: { name: "Claude 3 Sonnet", inBeta: true }, +}; diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 4e2c2fa..4308f47 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -1,4 +1,5 @@ import { Stack } from "./lib/stacks"; +import { CodeGenerationModel } from "./lib/models"; export enum EditorTheme { ESPRESSO = "espresso", @@ -12,6 +13,7 @@ export interface Settings { isImageGenerationEnabled: boolean; editorTheme: EditorTheme; generatedCodeConfig: Stack; + codeGenerationModel: CodeGenerationModel; // Only relevant for hosted version isTermOfServiceAccepted: boolean; accessCode: string | null; From 788c93f8d7b17d8ba5017394c8bdc2860e5e1340 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 12:32:24 -0500 Subject: [PATCH 012/144] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 03a4250..80d102f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ See the [Examples](#-examples) section below for more demos. 🆕 [Try it here](https://screenshottocode.com) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#%EF%B8%8F-faqs) section below for details**). Or see [Getting Started](#-getting-started) below for local install instructions. +## 🌟 Recent Updates + +- Mar 5 - Added support for Claude Sonnet 3 (as capable as or better than GPT-4 Vision, and faster!) ## 🛠 Getting Started From 0b1bdd45159861a2aec3f2c2e6af3e79ca0554cb Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 13:31:08 -0500 Subject: [PATCH 013/144] Delete sweep.yaml --- sweep.yaml | 42 ------------------------------------------ 1 file changed, 42 deletions(-) delete mode 100644 sweep.yaml diff --git a/sweep.yaml b/sweep.yaml deleted file mode 100644 index ada0e25..0000000 --- a/sweep.yaml +++ /dev/null @@ -1,42 +0,0 @@ -# Sweep AI turns bugs & feature requests into code changes (https://sweep.dev) -# For details on our config file, check out our docs at https://docs.sweep.dev/usage/config - -# This setting contains a list of rules that Sweep will check for. If any of these rules are broken in a new commit, Sweep will create an pull request to fix the broken rule. -rules: - - "All docstrings and comments should be up to date." -['All new business logic should have corresponding unit tests.', 'Refactor large functions to be more modular.', 'Add docstrings to all functions and file headers.'] - -# This is the branch that Sweep will develop from and make pull requests to. Most people use 'main' or 'master' but some users also use 'dev' or 'staging'. -branch: 'main' - -# By default Sweep will read the logs and outputs from your existing Github Actions. To disable this, set this to false. -gha_enabled: True - -# This is the description of your project. It will be used by sweep when creating PRs. You can tell Sweep what's unique about your project, what frameworks you use, or anything else you want. -# -# Example: -# -# description: sweepai/sweep is a python project. The main api endpoints are in sweepai/api.py. Write code that adheres to PEP8. -description: '' - -# This sets whether to create pull requests as drafts. If this is set to True, then all pull requests will be created as drafts and GitHub Actions will not be triggered. -draft: False - -# This is a list of directories that Sweep will not be able to edit. -blocked_dirs: [] - -# This is a list of documentation links that Sweep will use to help it understand your code. You can add links to documentation for any packages you use here. -# -# Example: -# -# docs: -# - PyGitHub: ["https://pygithub.readthedocs.io/en/latest/", "We use pygithub to interact with the GitHub API"] -docs: [] - -# Sandbox executes commands in a sandboxed environment to validate code changes after every edit to guarantee pristine code. For more details, see the [Sandbox](./sandbox) page. -sandbox: - install: - - trunk init - check: - - trunk fmt {file_path} || return 0 - - trunk check --fix --print-failures {file_path} From 9e59050961e66026a949649f208e3763d8ce9dc9 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 13:38:51 -0500 Subject: [PATCH 014/144] Update README.md --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 80d102f..d97c9f4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # screenshot-to-code -This simple app converts a screenshot to code (HTML/Tailwind CSS, or React or Bootstrap or Vue). It uses GPT-4 Vision to generate the code and DALL-E 3 to generate similar-looking images. You can now also enter a URL to clone a live website! +This simple app converts a screenshot to code (HTML/Tailwind CSS, or React or Bootstrap or Vue). It uses GPT-4 Vision (or Claude 3) to generate the code and DALL-E 3 to generate similar-looking images. You can now also enter a URL to clone a live website. https://github.com/abi/screenshot-to-code/assets/23818/6cebadae-2fe3-4986-ac6a-8fb9db030045 @@ -50,6 +50,13 @@ MOCK=true poetry run uvicorn main:app --reload --port 7001 - You can configure the OpenAI base URL if you need to use a proxy: Set OPENAI_BASE_URL in the `backend/.env` or directly in the UI in the settings dialog +## Using Claude 3 + +We recently added support for Claude 3 Sonnet. It performs well, on par or better than GPT-4 vision for many inputs, and it tends to be faster. + +1. Add an env var `ANTHROPIC_API_KEY` to `backend/.env` with your API key from Anthropic +2. When using the front-end, select "Claude 3 Sonnet" from the model dropdown + ## Docker If you have Docker installed on your system, in the root directory, run: From a7e51f6f1ab96c0e120f606e575df27f0ed61b2b Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 13:39:49 -0500 Subject: [PATCH 015/144] update error message from anthropic --- backend/routes/generate_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/routes/generate_code.py b/backend/routes/generate_code.py index 589be2d..d90e82c 100644 --- a/backend/routes/generate_code.py +++ b/backend/routes/generate_code.py @@ -212,7 +212,7 @@ async def stream_code(websocket: WebSocket): if code_generation_model == "claude_3_sonnet": if not ANTHROPIC_API_KEY: await throw_error( - "No Anthropic API key found. Please add it to backend/.env" + "No Anthropic API key found. Please add the environment variable ANTHROPIC_API_KEY to backend/.env" ) raise Exception("No Anthropic key") From 3a23d9eae31dcf83e00a9796db870fb9bc3d4478 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 13:50:55 -0500 Subject: [PATCH 016/144] add pyright as part of pre-commit (but disable it for now due to failing type check) --- backend/.pre-commit-config.yaml | 7 +++++++ backend/image_generation.py | 18 +++++++++--------- backend/poetry.lock | 8 ++++---- backend/pyproject.toml | 2 +- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/backend/.pre-commit-config.yaml b/backend/.pre-commit-config.yaml index 9a495ad..b54da93 100644 --- a/backend/.pre-commit-config.yaml +++ b/backend/.pre-commit-config.yaml @@ -16,3 +16,10 @@ repos: pass_filenames: false always_run: true files: ^backend/ + # - id: poetry-pyright + # name: Run pyright with Poetry + # entry: poetry run --directory backend pyright + # language: system + # pass_filenames: false + # always_run: true + # files: ^backend/ diff --git a/backend/image_generation.py b/backend/image_generation.py index d3e71b1..b93792c 100644 --- a/backend/image_generation.py +++ b/backend/image_generation.py @@ -15,7 +15,7 @@ async def process_tasks(prompts: List[str], api_key: str, base_url: str): print(f"An exception occurred: {result}") processed_results.append(None) else: - processed_results.append(result) + processed_results.append(result) # type: ignore return processed_results @@ -30,7 +30,7 @@ async def generate_image(prompt: str, api_key: str, base_url: str): "size": "1024x1024", "prompt": prompt, } - res = await client.images.generate(**image_params) + res = await client.images.generate(**image_params) # type: ignore await client.close() return res.data[0].url @@ -77,26 +77,26 @@ async def generate_images( img["src"].startswith("https://placehold.co") and image_cache.get(img.get("alt")) is None ): - alts.append(img.get("alt", None)) + alts.append(img.get("alt", None)) # type: ignore # Exclude images with no alt text - alts = [alt for alt in alts if alt is not None] + alts = [alt for alt in alts if alt is not None] # type: ignore # Remove duplicates - prompts = list(set(alts)) + prompts = list(set(alts)) # type: ignore # Return early if there are no images to replace - if len(prompts) == 0: + if len(prompts) == 0: # type: ignore return code # Generate images - results = await process_tasks(prompts, api_key, base_url) + results = await process_tasks(prompts, api_key, base_url) # type: ignore # Create a dict mapping alt text to image URL - mapped_image_urls = dict(zip(prompts, results)) + mapped_image_urls = dict(zip(prompts, results)) # type: ignore # Merge with image_cache - mapped_image_urls = {**mapped_image_urls, **image_cache} + mapped_image_urls = {**mapped_image_urls, **image_cache} # type: ignore # Replace old image URLs with the generated URLs for img in images: diff --git a/backend/poetry.lock b/backend/poetry.lock index 53e5215..663f7a2 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -592,13 +592,13 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pyright" -version = "1.1.345" +version = "1.1.352" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.345-py3-none-any.whl", hash = "sha256:00891361baf58698aa660d9374823d65782823ceb4a65515ff5dd159b0d4d2b1"}, - {file = "pyright-1.1.345.tar.gz", hash = "sha256:bb8c80671cdaeb913142b49642a741959f3fcd728c99814631c2bde3a7864938"}, + {file = "pyright-1.1.352-py3-none-any.whl", hash = "sha256:0040cf173c6a60704e553bfd129dfe54de59cc76d0b2b80f77cfab4f50701d64"}, + {file = "pyright-1.1.352.tar.gz", hash = "sha256:a621c0dfbcf1291b3610641a07380fefaa1d0e182890a1b2a7f13b446e8109a9"}, ] [package.dependencies] @@ -1088,4 +1088,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "6fa98703309c862dd33d9d9ce210b71bfcb37e7d7ff030198d9044d83c249060" +content-hash = "17d8bb6d9e4a392f2512e4265df564708d59c1d83b26146f23e2595b6764c711" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 8c64b3f..14009f6 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -19,7 +19,7 @@ anthropic = "^0.18.0" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" -pyright = "^1.1.345" +pyright = "^1.1.352" [build-system] requires = ["poetry-core"] From c69edeb4b6c954d12b25facb9c2b61e6c0f52027 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 14:02:48 -0500 Subject: [PATCH 017/144] send correct media type to Claude --- backend/llm.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/backend/llm.py b/backend/llm.py index c6d629e..f042223 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -69,13 +69,23 @@ async def stream_claude_response( for content in message["content"]: # type: ignore if content["type"] == "image_url": content["type"] = "image" - content["source"] = { - "type": "base64", - "media_type": "image/png", # TODO: Automatically detect media type - "data": content["image_url"]["url"].split(",")[1], # type: ignore - } + + # Extract base64 data and media type from data URL + # Example base64 data URL: data:image/png;base64,iVBOR... + image_data_url = cast(str, content["image_url"]["url"]) + media_type = image_data_url.split(";")[0].split(":")[1] + base64_data = image_data_url.split(",")[1] + + # Remove OpenAI parameter del content["image_url"] + content["source"] = { + "type": "base64", + "media_type": media_type, + "data": base64_data, + } + + # Stream Claude response async with client.messages.stream( model=model, max_tokens=max_tokens, @@ -86,5 +96,6 @@ async def stream_claude_response( async for text in stream.text_stream: await callback(text) + # Return final message response = await stream.get_final_message() return response.content[0].text From 4ed4ec1cfe99d727b3de57fbfdea71adf2be2a12 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 14:15:09 -0500 Subject: [PATCH 018/144] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d97c9f4..5af6b06 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ https://github.com/abi/screenshot-to-code/assets/23818/6cebadae-2fe3-4986-ac6a-8 See the [Examples](#-examples) section below for more demos. +[Follow me on Twitter for updates](https://twitter.com/_abi_). + ## 🚀 Try It Out! 🆕 [Try it here](https://screenshottocode.com) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#%EF%B8%8F-faqs) section below for details**). Or see [Getting Started](#-getting-started) below for local install instructions. From 4f4d2e36f66f91c3958af8e92b5b8174fc1af1ad Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 14:51:11 -0500 Subject: [PATCH 019/144] add evaluation description --- Evaluation.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 Evaluation.md diff --git a/Evaluation.md b/Evaluation.md new file mode 100644 index 0000000..5fd5da8 --- /dev/null +++ b/Evaluation.md @@ -0,0 +1,19 @@ +## Evaluating models and prompts + +Evaluation dataset consists of 16 screenshots. A Python script for running screenshot-to-code on the dataset and a UI for rating outputs is included. With this set up, we can compare and evaluate various models and prompts. + +### Running evals + +- Input screenshots should be located at `backend/evals_data/inputs` and the outputs will be `backend/evals_data/outputs`. If you want to modify this, modify `EVALS_DIR` in `backend/evals/config.py`. You can download the input screenshot dataset here: TODO. +- Set a stack (`STACK` var) in `backend/run_evals.py` +- Run `python backend/run_evals.py` - this runs the screenshot-to-code on the input dataset in parallel but it will still take a few minutes to complete. +- Once the script is done, you can find the outputs in `backend/evals_data/outputs`. + +### Rating evals + +In order to view and rate the outputs, visit your front-end at `/evals`. + +- Rate each output on a scale of 1-4 +- You can also print the page as PDF to share your results with others. + +Generally, I run three tests for each model/prompt + stack combo and take the average score out of those tests to evaluate. From 123e8ffce2874e58a643ebb20bb4ee6bbb5c2753 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 20:22:09 -0500 Subject: [PATCH 020/144] Add Claude prompts file (not used yet - we use the same prompts for both models) --- backend/prompts/claude_prompts.py | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 backend/prompts/claude_prompts.py diff --git a/backend/prompts/claude_prompts.py b/backend/prompts/claude_prompts.py new file mode 100644 index 0000000..018f268 --- /dev/null +++ b/backend/prompts/claude_prompts.py @@ -0,0 +1,33 @@ +# Not used yet +# References: +# https://github.com/hundredblocks/transcription_demo +# https://docs.anthropic.com/claude/docs/prompt-engineering +# https://github.com/anthropics/anthropic-cookbook/blob/main/multimodal/best_practices_for_vision.ipynb + +HTML_TAILWIND_CLAUDE_SYSTEM_PROMPT = """ +You have perfect vision and pay great attention to detail which makes you an expert at building single page apps using Tailwind, HTML and JS. +You take screenshots of a reference web page from the user, and then build single page apps +using Tailwind, HTML and JS. +You might also be given a screenshot (The second image) of a web page that you have already built, and asked to +update it to look more like the reference image(The first image). + +- Make sure the app looks exactly like the screenshot. +- Do not leave out smaller UI elements. Make sure to include every single thing in the screenshot. +- Pay close attention to background color, text color, font size, font family, +padding, margin, border, etc. Match the colors and sizes exactly. +- In particular, pay attention to background color and overall color scheme. +- Use the exact text from the screenshot. +- Do not add comments in the code such as "" and "" in place of writing the full code. WRITE THE FULL CODE. +- Make sure to always get the layout right (if things are arranged in a row in the screenshot, they should be in a row in the app as well) +- Repeat elements as needed to match the screenshot. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "" or bad things will happen. +- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later. + +In terms of libraries, + +- Use this script to include Tailwind: +- You can use Google Fonts +- Font Awesome for icons: + +Return only the full code in tags. +Do not include markdown "```" or "```html" at the start or end. +""" From f2cb1a4dc3babce7ed451ece6cccf22d250f790a Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 20:42:50 -0500 Subject: [PATCH 021/144] initial version of blogpost --- blog/evaluating-claude.md | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 blog/evaluating-claude.md diff --git a/blog/evaluating-claude.md b/blog/evaluating-claude.md new file mode 100644 index 0000000..3b48839 --- /dev/null +++ b/blog/evaluating-claude.md @@ -0,0 +1,46 @@ +# Evaluating Claude 3 at converting screenshots to code + +[video running both] + +Claude 3 dropped yesterday, claiming to rival GPT-4 on a wide variety of tasks. I maintain a very popular open source project (41k+ Github stars) called “screenshot-to-code” (this one!) that uses GPT-4 vision to convert screenshots/designs into clean code. Naturally, I was excited to see how good Claude 3 was at this task! + +## Evaluation Setup + +I don’t know of a public benchmark for the “screenshot to code” task so I created simple evaluation set up for the purposes of testing\*: + +- **Evaluation Dataset**: 16 screenshots with a mix of UI elements, landing pages, dashboards and popular websites. +- **Evaluation Metric**: Replication accuracy, as in “How close does the generated code look to the screenshot?” There are other metrics that are important for sure but this is by far the #1 thing that most developers and users of the repo care about. Each output is subjectively rated by a human (me!) on a rating scale from 0 to 4. 4 = very close to an exact replica while 0 = nothing like the screenshot. With 16 screenshots, the maximum any model can score is 64. I like to compare the percentage of the maximum possible score for each run. + +\*I’ve used it mostly for testing prompt variations until now since no model has come close to GPT-4 vision yet. + +To make the evaluation process easy, I created [a Python script](https://github.com/abi/screenshot-to-code/blob/main/backend/run_evals.py) that runs code for all the inputs in parallel. I also have a simple UI to do a side-by-side comparison of the input and output, shown below. + +[video of input/output] + +## Results + +Quick note about what kind of code we’ll be generating: currently, screenshot-to-code supports generating code in HTML + Tailwind, React, Vue, and several other frameworks. Stacks can impact the replication accuracy quite a bit. For example, because Bootstrap uses a relatively restrictive set of user elements, generations using Bootstrap tend to have a distinct "Bootstrap" style. + +I only ran the evals on HTML/Tailwind here which is the stack where GPT-4 vision tends to perform the best. + +Here are the results (average of 3 runs for each model): + +- GPT-4 Vision obtains a score of **65.10%** - this is what we’re trying to beat +- Claude 3 Sonnet receives a score of **70.31%**, which is ~7.5% better. +- Surprisingly, Claude 3 Opus which is supposed to be the smarter and slower model scores worse than both GPT-4 vision and Claude 3 Sonnet, coming in at **61.46%**. Strange result. + +Overall, a very strong showing for Claude 3! Obviously, there's a lot of subjectivity involved in this evaluation but Claude 3 is definitely on par with GPT-4 Vision, if not better. It's also a lot less lazy than GPT-4 Vision. + +You can see the results for a run of Claude 3 Sonnet here. You can see the results for a run of GPT-4 Vision here. + +Some other notes: + +- The prompts used are optimized for GPT-4 vision. I played around with adjusting the prompts for Claude and that did yield a small improvement. Nothing game-changing and potentially not worth the trade-off of maintaining two sets of prompts. +- All these models excel at code quality - the quality is usually comparable to a human or better +- All the models seem to struggle with side-by-side layouts (the struggle is mostly in seeing, not in implementing) TODO: add iamge +- Claude 3 is much less lazy than GPT-4 Vision. TODO: Hacker News example +- Claude 3 Sonnet is faster and more steerable + +Overall, I'm very impressed with Claude 3 Sonnet as a multimodal model for this use case. I've added it as an alternative to GPT-4 Vision in the open source repo (hosted version update coming soon). I'm sure it's possible to get Opus to be quite good as well. + +If you’d like to contribute to this effort, I have some documentation on [running these evals yourself here](https://github.com/abi/screenshot-to-code/blob/main/Evaluation.md). If you know of a good tool for running evals like this (image input, code output), it would save me a lot of effort from having to build the tool from scratch. From 0245f2c3599f0a9eff0ae5dbffdd8927821c0944 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 21:35:38 -0500 Subject: [PATCH 022/144] Update evaluating-claude.md --- blog/evaluating-claude.md | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/blog/evaluating-claude.md b/blog/evaluating-claude.md index 3b48839..4e390e7 100644 --- a/blog/evaluating-claude.md +++ b/blog/evaluating-claude.md @@ -1,7 +1,5 @@ # Evaluating Claude 3 at converting screenshots to code -[video running both] - Claude 3 dropped yesterday, claiming to rival GPT-4 on a wide variety of tasks. I maintain a very popular open source project (41k+ Github stars) called “screenshot-to-code” (this one!) that uses GPT-4 vision to convert screenshots/designs into clean code. Naturally, I was excited to see how good Claude 3 was at this task! ## Evaluation Setup @@ -9,13 +7,16 @@ Claude 3 dropped yesterday, claiming to rival GPT-4 on a wide variety of tasks. I don’t know of a public benchmark for the “screenshot to code” task so I created simple evaluation set up for the purposes of testing\*: - **Evaluation Dataset**: 16 screenshots with a mix of UI elements, landing pages, dashboards and popular websites. +Screenshot 2024-03-05 at 3 05 52 PM + - **Evaluation Metric**: Replication accuracy, as in “How close does the generated code look to the screenshot?” There are other metrics that are important for sure but this is by far the #1 thing that most developers and users of the repo care about. Each output is subjectively rated by a human (me!) on a rating scale from 0 to 4. 4 = very close to an exact replica while 0 = nothing like the screenshot. With 16 screenshots, the maximum any model can score is 64. I like to compare the percentage of the maximum possible score for each run. \*I’ve used it mostly for testing prompt variations until now since no model has come close to GPT-4 vision yet. -To make the evaluation process easy, I created [a Python script](https://github.com/abi/screenshot-to-code/blob/main/backend/run_evals.py) that runs code for all the inputs in parallel. I also have a simple UI to do a side-by-side comparison of the input and output, shown below. +To make the evaluation process easy, I created [a Python script](https://github.com/abi/screenshot-to-code/blob/main/backend/run_evals.py) that runs code for all the inputs in parallel. I also made a simple UI to do a side-by-side comparison of the input and output, shown below. + +![Google Chrome](https://github.com/abi/screenshot-to-code/assets/23818/38126f8f-205d-4ed1-b8cf-039e81dcc3d0) -[video of input/output] ## Results @@ -29,18 +30,27 @@ Here are the results (average of 3 runs for each model): - Claude 3 Sonnet receives a score of **70.31%**, which is ~7.5% better. - Surprisingly, Claude 3 Opus which is supposed to be the smarter and slower model scores worse than both GPT-4 vision and Claude 3 Sonnet, coming in at **61.46%**. Strange result. -Overall, a very strong showing for Claude 3! Obviously, there's a lot of subjectivity involved in this evaluation but Claude 3 is definitely on par with GPT-4 Vision, if not better. It's also a lot less lazy than GPT-4 Vision. +Overall, a very strong showing for Claude 3. Obviously, there's a lot of subjectivity involved in this evaluation but Claude 3 is definitely on par with GPT-4 Vision, if not better. -You can see the results for a run of Claude 3 Sonnet here. You can see the results for a run of GPT-4 Vision here. +You can see the [side-by-side comparison for a run of Claude 3 Sonnet here](https://github.com/abi/screenshot-to-code-files/blob/main/sonnet%20results.png). And for [a run of GPT-4 Vision here](https://github.com/abi/screenshot-to-code-files/blob/main/gpt%204%20vision%20results.png). Some other notes: -- The prompts used are optimized for GPT-4 vision. I played around with adjusting the prompts for Claude and that did yield a small improvement. Nothing game-changing and potentially not worth the trade-off of maintaining two sets of prompts. -- All these models excel at code quality - the quality is usually comparable to a human or better -- All the models seem to struggle with side-by-side layouts (the struggle is mostly in seeing, not in implementing) TODO: add iamge -- Claude 3 is much less lazy than GPT-4 Vision. TODO: Hacker News example -- Claude 3 Sonnet is faster and more steerable +- The prompts used are optimized for GPT-4 vision. I played around with adjusting the prompts for Claude and that did yield a small improvement. But it was nothing game-changing and potentially not worth the trade-off of maintaining two sets of prompts. +- All the models excel at code quality - the quality is usually comparable to a human or better +- Claude 3 is much less lazy than GPT-4 Vision. When asked to recreate Hacker News, GPT-4 Vision will only create two items in the list and leave comments in this code like `` and ``. +Screenshot 2024-03-05 at 9 25 04 PM -Overall, I'm very impressed with Claude 3 Sonnet as a multimodal model for this use case. I've added it as an alternative to GPT-4 Vision in the open source repo (hosted version update coming soon). I'm sure it's possible to get Opus to be quite good as well. +But Claude 3 Sonnet can sometimes be lazy too but most of the time, does what you ask it to do! + +Screenshot 2024-03-05 at 9 30 23 PM + +- For some reasons, all the models struggle with side-by-side "flex" layouts +Screenshot 2024-03-05 at 9 20 58 PM +- Claude 3 Sonnet is a lot faster +- Claude 3 gets background and text colors wrong quite often! +- Claude 3 Opus likely just requires more prompting on my part + +Overall, I'm very impressed with Claude 3 Sonnet as a multimodal model for this use case. I've added it as an alternative to GPT-4 Vision in the open source repo (hosted version update coming soon). If you’d like to contribute to this effort, I have some documentation on [running these evals yourself here](https://github.com/abi/screenshot-to-code/blob/main/Evaluation.md). If you know of a good tool for running evals like this (image input, code output), it would save me a lot of effort from having to build the tool from scratch. From 6029a9bec5d101c764b35e4510fdf0a16b3f1694 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 21:48:55 -0500 Subject: [PATCH 023/144] Update evaluating-claude.md --- blog/evaluating-claude.md | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/blog/evaluating-claude.md b/blog/evaluating-claude.md index 4e390e7..99dc94d 100644 --- a/blog/evaluating-claude.md +++ b/blog/evaluating-claude.md @@ -1,19 +1,19 @@ -# Evaluating Claude 3 at converting screenshots to code +# Claude 3 for converting screenshots to code -Claude 3 dropped yesterday, claiming to rival GPT-4 on a wide variety of tasks. I maintain a very popular open source project (41k+ Github stars) called “screenshot-to-code” (this one!) that uses GPT-4 vision to convert screenshots/designs into clean code. Naturally, I was excited to see how good Claude 3 was at this task! +Claude 3 dropped yesterday, claiming to rival GPT-4 on a wide variety of tasks. I maintain a very popular open source project called “screenshot-to-code” (this one!) that uses GPT-4 vision to convert screenshots/designs into clean code. Naturally, I was excited to see how good Claude 3 was at this task. ## Evaluation Setup -I don’t know of a public benchmark for the “screenshot to code” task so I created simple evaluation set up for the purposes of testing\*: +I don’t know of a public benchmark for “screenshot to code” so I created simple evaluation setup for the purposes of testing: - **Evaluation Dataset**: 16 screenshots with a mix of UI elements, landing pages, dashboards and popular websites. Screenshot 2024-03-05 at 3 05 52 PM -- **Evaluation Metric**: Replication accuracy, as in “How close does the generated code look to the screenshot?” There are other metrics that are important for sure but this is by far the #1 thing that most developers and users of the repo care about. Each output is subjectively rated by a human (me!) on a rating scale from 0 to 4. 4 = very close to an exact replica while 0 = nothing like the screenshot. With 16 screenshots, the maximum any model can score is 64. I like to compare the percentage of the maximum possible score for each run. +- **Evaluation Metric**: Replication accuracy, as in “How close does the generated code look to the screenshot?” While there are other metrics that are important like code quality, speed and so on, this is by far the #1 thing most users of the repo care about. +- **Evaluation Mechanism**: Each output is subjectively rated by a human on a rating scale from 0 to 4. 4 = very close to an exact replica while 0 = nothing like the screenshot. With 16 screenshots, the maximum any model can score is 64. -\*I’ve used it mostly for testing prompt variations until now since no model has come close to GPT-4 vision yet. -To make the evaluation process easy, I created [a Python script](https://github.com/abi/screenshot-to-code/blob/main/backend/run_evals.py) that runs code for all the inputs in parallel. I also made a simple UI to do a side-by-side comparison of the input and output, shown below. +To make the evaluation process easy, I created [a Python script](https://github.com/abi/screenshot-to-code/blob/main/backend/run_evals.py) that runs code for all the inputs in parallel. I also made a simple UI to do a side-by-side comparison of the input and output. ![Google Chrome](https://github.com/abi/screenshot-to-code/assets/23818/38126f8f-205d-4ed1-b8cf-039e81dcc3d0) @@ -27,30 +27,31 @@ I only ran the evals on HTML/Tailwind here which is the stack where GPT-4 vision Here are the results (average of 3 runs for each model): - GPT-4 Vision obtains a score of **65.10%** - this is what we’re trying to beat -- Claude 3 Sonnet receives a score of **70.31%**, which is ~7.5% better. -- Surprisingly, Claude 3 Opus which is supposed to be the smarter and slower model scores worse than both GPT-4 vision and Claude 3 Sonnet, coming in at **61.46%**. Strange result. +- Claude 3 Sonnet receives a score of **70.31%**, which is a bit better. +- Surprisingly, Claude 3 Opus which is supposed to be the smarter and slower model scores worse than both GPT-4 vision and Claude 3 Sonnet, comes in at **61.46%**. Overall, a very strong showing for Claude 3. Obviously, there's a lot of subjectivity involved in this evaluation but Claude 3 is definitely on par with GPT-4 Vision, if not better. You can see the [side-by-side comparison for a run of Claude 3 Sonnet here](https://github.com/abi/screenshot-to-code-files/blob/main/sonnet%20results.png). And for [a run of GPT-4 Vision here](https://github.com/abi/screenshot-to-code-files/blob/main/gpt%204%20vision%20results.png). -Some other notes: +Other notes: -- The prompts used are optimized for GPT-4 vision. I played around with adjusting the prompts for Claude and that did yield a small improvement. But it was nothing game-changing and potentially not worth the trade-off of maintaining two sets of prompts. -- All the models excel at code quality - the quality is usually comparable to a human or better +- The prompts used are optimized for GPT-4 vision. Adjusting the prompts a bit for Claude did yield a small improvement. But nothing game-changing and potentially not worth the trade-off of maintaining two sets of prompts. +- All the models excel at code quality - the quality is usually comparable to a human or better. - Claude 3 is much less lazy than GPT-4 Vision. When asked to recreate Hacker News, GPT-4 Vision will only create two items in the list and leave comments in this code like `` and ``. Screenshot 2024-03-05 at 9 25 04 PM -But Claude 3 Sonnet can sometimes be lazy too but most of the time, does what you ask it to do! +While Claude 3 Sonnet can sometimes be lazy too, most of the time, it does what you ask it to do. Screenshot 2024-03-05 at 9 30 23 PM -- For some reasons, all the models struggle with side-by-side "flex" layouts +- For some reason, all the models struggle with side-by-side "flex" layouts Screenshot 2024-03-05 at 9 20 58 PM -- Claude 3 Sonnet is a lot faster -- Claude 3 gets background and text colors wrong quite often! -- Claude 3 Opus likely just requires more prompting on my part - -Overall, I'm very impressed with Claude 3 Sonnet as a multimodal model for this use case. I've added it as an alternative to GPT-4 Vision in the open source repo (hosted version update coming soon). -If you’d like to contribute to this effort, I have some documentation on [running these evals yourself here](https://github.com/abi/screenshot-to-code/blob/main/Evaluation.md). If you know of a good tool for running evals like this (image input, code output), it would save me a lot of effort from having to build the tool from scratch. +- Claude 3 Sonnet is a lot faster +- Claude 3 gets background and text colors wrong quite often! (like in the Hacker News image above) +- My suspicion is that Claude 3 Opus results can be improved to be on par with the other models through better prompting + +Overall, I'm very impressed with Claude 3 Sonnet for this use case. I've added it as an alternative to GPT-4 Vision in the open source repo (hosted version update coming soon). + +If you’d like to contribute to this effort, I have some documentation on [running these evals yourself here](https://github.com/abi/screenshot-to-code/blob/main/Evaluation.md). I'm also working on a better evaluation mechanism with Elo ratings and would love some help on that. From cd7cd841ed9ac110fb98cae1bf4548a6ecab22a9 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 5 Mar 2024 21:52:24 -0500 Subject: [PATCH 024/144] Update evaluating-claude.md --- blog/evaluating-claude.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/blog/evaluating-claude.md b/blog/evaluating-claude.md index 99dc94d..373a343 100644 --- a/blog/evaluating-claude.md +++ b/blog/evaluating-claude.md @@ -2,6 +2,8 @@ Claude 3 dropped yesterday, claiming to rival GPT-4 on a wide variety of tasks. I maintain a very popular open source project called “screenshot-to-code” (this one!) that uses GPT-4 vision to convert screenshots/designs into clean code. Naturally, I was excited to see how good Claude 3 was at this task. +**TLDR:** Claude 3 is on par with GPT-4 vision for screenshot to code, better in some ways but worse in others. + ## Evaluation Setup I don’t know of a public benchmark for “screenshot to code” so I created simple evaluation setup for the purposes of testing: From d8b75c43d23e16bf947577fb99de3ddb4af2ae56 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Wed, 6 Mar 2024 07:49:20 -0500 Subject: [PATCH 025/144] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5af6b06..f55fdf9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ This simple app converts a screenshot to code (HTML/Tailwind CSS, or React or Bootstrap or Vue). It uses GPT-4 Vision (or Claude 3) to generate the code and DALL-E 3 to generate similar-looking images. You can now also enter a URL to clone a live website. +🆕 Now, supporting Claude 3! + https://github.com/abi/screenshot-to-code/assets/23818/6cebadae-2fe3-4986-ac6a-8fb9db030045 See the [Examples](#-examples) section below for more demos. From c2f230a8c9179564870c9751b5f567c01e45698b Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Wed, 6 Mar 2024 20:47:28 -0500 Subject: [PATCH 026/144] initial version of script --- backend/.gitignore | 4 + backend/llm.py | 47 ++++++- backend/prompts/claude_prompts.py | 81 +++++++++++ backend/video_to_app.py | 226 ++++++++++++++++++++++++++++++ 4 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 backend/video_to_app.py diff --git a/backend/.gitignore b/backend/.gitignore index a42aad3..5d03006 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -154,3 +154,7 @@ cython_debug/ # Temporary eval output evals_data + + +# Temporary video evals (Remove before merge) +video_evals diff --git a/backend/llm.py b/backend/llm.py index f042223..2be7792 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -1,4 +1,4 @@ -from typing import Awaitable, Callable, List, cast +from typing import Any, Awaitable, Callable, List, cast from anthropic import AsyncAnthropic from openai import AsyncOpenAI from openai.types.chat import ChatCompletionMessageParam, ChatCompletionChunk @@ -46,6 +46,7 @@ async def stream_openai_response( return full_response +# TODO: Have a seperate function that translates OpenAI messages to Claude messages async def stream_claude_response( messages: List[ChatCompletionMessageParam], api_key: str, @@ -99,3 +100,47 @@ async def stream_claude_response( # Return final message response = await stream.get_final_message() return response.content[0].text + + +async def stream_claude_response_native( + system_prompt: str, + messages: list[Any], + api_key: str, + callback: Callable[[str], Awaitable[None]], + include_thinking: bool = False, + model: str = MODEL_CLAUDE_OPUS, +) -> str: + + client = AsyncAnthropic(api_key=api_key) + + # Base parameters + max_tokens = 4096 + temperature = 0.0 + + # Stream Claude response + + # Set up message depending on whether we have a prefix + messages = ( + messages + [{"role": "assistant", "content": ""}] + if include_thinking + else messages + ) + + async with client.messages.stream( + model=model, + max_tokens=max_tokens, + temperature=temperature, + system=system_prompt, + messages=messages, # type: ignore + ) as stream: + async for text in stream.text_stream: + await callback(text) + + # Return final message + response = await stream.get_final_message() + + print( + f"Token usage: Input Tokens: {response.usage.input_tokens}, Output Tokens: {response.usage.output_tokens}" + ) + + return response.content[0].text diff --git a/backend/prompts/claude_prompts.py b/backend/prompts/claude_prompts.py index 018f268..3519e49 100644 --- a/backend/prompts/claude_prompts.py +++ b/backend/prompts/claude_prompts.py @@ -4,6 +4,53 @@ # https://docs.anthropic.com/claude/docs/prompt-engineering # https://github.com/anthropics/anthropic-cookbook/blob/main/multimodal/best_practices_for_vision.ipynb +VIDEO_PROMPT = """ +You are an expert at building single page, funtional apps using HTML, Jquery and Tailwind CSS. +You also have perfect vision and pay great attention to detail. + +You will be given screenshots in order at consistent intervals from a video of a user interacting with a web app. You need to re-create the same app exactly such that the same user interactions will produce the same results in the app you build. + +- Make sure the app looks exactly like the screenshot. +- Pay close attention to background color, text color, font size, font family, +padding, margin, border, etc. Match the colors and sizes exactly. +- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later. +- If some fuctionality requires a backend call, just mock the data instead. +- MAKE THE APP FUNCTIONAL using Javascript. Allow the user to interact with the app and get the same behavior as the video. + +In terms of libraries, + +- Use this script to include Tailwind: +- You can use Google Fonts +- Font Awesome for icons: +- Use jQuery: + +Before generating the code for the app, think step-by-step: first, about the user flow depicated in the video and then about you how would you build it and how you would structure the code. Do the thinking within tags. Then, provide your code within tags. +""" + +VIDEO_PROMPT_ALPINE_JS = """ +You are an expert at building single page, funtional apps using HTML, Alpine.js and Tailwind CSS. +You also have perfect vision and pay great attention to detail. + +You will be given screenshots in order at consistent intervals from a video of a user interacting with a web app. You need to re-create the same app exactly such that the same user interactions will produce the same results in the app you build. + +- Make sure the app looks exactly like the screenshot. +- Pay close attention to background color, text color, font size, font family, +padding, margin, border, etc. Match the colors and sizes exactly. +- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later. +- If some fuctionality requires a backend call, just mock the data instead. +- MAKE THE APP FUNCTIONAL using Javascript. Allow the user to interact with the app and get the same behavior as the video. + +In terms of libraries, + +- Use this script to include Tailwind: +- You can use Google Fonts +- Font Awesome for icons: +- Use Alpine.js: + +Before generating the code for the app, think step-by-step: first, about the user flow depicated in the video and then about you how would you build it and how you would structure the code. Do the thinking within tags. Then, provide your code within tags. +""" + + HTML_TAILWIND_CLAUDE_SYSTEM_PROMPT = """ You have perfect vision and pay great attention to detail which makes you an expert at building single page apps using Tailwind, HTML and JS. You take screenshots of a reference web page from the user, and then build single page apps @@ -31,3 +78,37 @@ In terms of libraries, Return only the full code in tags. Do not include markdown "```" or "```html" at the start or end. """ + +# + +REACT_TAILWIND_CLAUDE_SYSTEM_PROMPT = """ +You have perfect vision and pay great attention to detail which makes you an expert at building single page apps using React/Tailwind. +You take screenshots of a reference web page from the user, and then build single page apps +using React and Tailwind CSS. +You might also be given a screenshot (The second image) of a web page that you have already built, and asked to +update it to look more like the reference image(The first image). + +- Make sure the app looks exactly like the screenshot. +- Do not leave out smaller UI elements. Make sure to include every single thing in the screenshot. +- Pay close attention to background color, text color, font size, font family, +padding, margin, border, etc. Match the colors and sizes exactly. +- In particular, pay attention to background color and overall color scheme. +- Use the exact text from the screenshot. +- Do not add comments in the code such as "" and "" in place of writing the full code. WRITE THE FULL CODE. +- Make sure to always get the layout right (if things are arranged in a row in the screenshot, they should be in a row in the app as well) +- CREATE REUSABLE COMPONENTS FOR REPEATING ELEMENTS. For example, if there are 15 similar items in the screenshot, your code should include a reusable component that generates these items. and use loops to instantiate these components as needed. +- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later. + +In terms of libraries, + +- Use these script to include React so that it can run on a standalone page: + + + +- Use this script to include Tailwind: +- You can use Google Fonts +- Font Awesome for icons: + +Return only the full code in tags. +Do not include markdown "```" or "```html" at the start or end. +""" diff --git a/backend/video_to_app.py b/backend/video_to_app.py new file mode 100644 index 0000000..7fb7c44 --- /dev/null +++ b/backend/video_to_app.py @@ -0,0 +1,226 @@ +# Load environment variables first +import base64 +import shutil +from dotenv import load_dotenv + +load_dotenv() + +import time +import subprocess +import os +from typing import Union +import asyncio +from datetime import datetime +from prompts.claude_prompts import VIDEO_PROMPT, VIDEO_PROMPT_ALPINE_JS +from utils import pprint_prompt +from config import ANTHROPIC_API_KEY +from llm import ( + MODEL_CLAUDE_OPUS, + # MODEL_CLAUDE_SONNET, + stream_claude_response_native, +) + +STACK = "html_tailwind" + +VIDEO_DIR = "./video_evals/videos" +SCREENSHOTS_DIR = "./video_evals/screenshots" +OUTPUTS_DIR = "./video_evals/outputs" + + +async def main(): + + video_filename = "mortgage-calculator.mov" + screenshot_interval = 850 + is_followup = False + + # Get previous HTML + previous_html = "" + if is_followup: + previous_html_file = max( + [ + os.path.join(OUTPUTS_DIR, f) + for f in os.listdir(OUTPUTS_DIR) + if f.endswith(".html") + ], + key=os.path.getctime, + ) + print(previous_html_file) + with open(previous_html_file, "r") as file: + previous_html = file.read() + + if not ANTHROPIC_API_KEY: + raise ValueError("ANTHROPIC_API_KEY is not set") + + # Create the SCREENSHOTS_DIR if it doesn't exist + if not os.path.exists(SCREENSHOTS_DIR): + os.makedirs(SCREENSHOTS_DIR) + + # Clear out the SCREENSHOTS_DIR before generating new screenshots + for filename in os.listdir(SCREENSHOTS_DIR): + file_path = os.path.join(SCREENSHOTS_DIR, filename) + try: + if os.path.isfile(file_path) or os.path.islink(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + except Exception as e: + print(f"Failed to delete {file_path}. Reason: {e}") + + # Split the video into screenshots + split_video_into_screenshots( + os.path.join(VIDEO_DIR, video_filename), SCREENSHOTS_DIR, screenshot_interval + ) + + # Get all the screenshots in the directory + screenshots = [f for f in os.listdir(SCREENSHOTS_DIR) if f.endswith(".jpg")] + + if len(screenshots) > 20: + print(f"Too many screenshots: {len(screenshots)}") + return + + input_image_urls: list[str] = [] + sorted_screenshots = sorted(screenshots, key=lambda x: int(x.split(".")[0])) + for filename in sorted_screenshots: + filepath = os.path.join(SCREENSHOTS_DIR, filename) + data_url = await image_to_data_url(filepath) + print(filename) + input_image_urls.append(data_url) + + # Convert images to the message format for Claude + content_messages: list[dict[str, Union[dict[str, str], str]]] = [] + for url in input_image_urls: + media_type = url.split(";")[0].split(":")[1] + base64_data = url.split(",")[1] + content_messages.append( + { + "type": "image", + "source": { + "type": "base64", + "media_type": media_type, + "data": base64_data, + }, + } + ) + + prompt_messages = [ + { + "role": "user", + "content": content_messages, + }, + # {"role": "assistant", "content": SECOND_MESSAGE}, + # {"role": "user", "content": "continue"}, + ] + + if is_followup: + prompt_messages += [ + {"role": "assistant", "content": previous_html}, + { + "role": "user", + "content": "You've done a good job with a first draft. Improve this further based on the original instructions so that the app is fully functional like in the original video.", + }, + ] # type: ignore + + async def process_chunk(content: str): + print(content, end="", flush=True) + + response_prefix = "" + + pprint_prompt(prompt_messages) # type: ignore + + start_time = time.time() + + completion = await stream_claude_response_native( + system_prompt=VIDEO_PROMPT, + messages=prompt_messages, + api_key=ANTHROPIC_API_KEY, + callback=lambda x: process_chunk(x), + model=MODEL_CLAUDE_OPUS, + include_thinking=True, + ) + + end_time = time.time() + + # Prepend the response prefix to the completion + completion = response_prefix + completion + + # Extract the outputs + html_content = extract_tag_content("html", completion) + thinking = extract_tag_content("thinking", completion) + + print(thinking) + print(f"Operation took {end_time - start_time} seconds") + + os.makedirs(OUTPUTS_DIR, exist_ok=True) + + # Generate a unique filename based on the current time + timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + filename = f"video_test_output_{timestamp}.html" + output_path = os.path.join(OUTPUTS_DIR, filename) + + # Write the HTML content to the file + with open(output_path, "w") as file: + file.write(html_content) + + # Show a notification + subprocess.run(["osascript", "-e", 'display notification "Coding Complete"']) + + +# Extract HTML content from the completion string +def extract_tag_content(tag: str, text: str) -> str: + """ + Extracts content for a given tag from the provided text. + + :param tag: The tag to search for. + :param text: The text to search within. + :return: The content found within the tag, if any. + """ + tag_start = f"<{tag}>" + tag_end = f"" + start_idx = text.find(tag_start) + end_idx = text.find(tag_end, start_idx) + if start_idx != -1 and end_idx != -1: + return text[start_idx : end_idx + len(tag_end)] + return "" + + +def split_video_into_screenshots(video_path: str, output_dir: str, interval: int): + # Create the output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Calculate the number of zeros needed for padding + # duration = float( + # subprocess.check_output( + # [ + # "ffprobe", + # "-v", + # "error", + # "-show_entries", + # "format=duration", + # "-of", + # "default=noprint_wrappers=1:nokey=1", + # video_path, + # ] + # ) + # ) + + # Run the ffmpeg command to extract screenshots + subprocess.call( + [ + "ffmpeg", + "-i", + video_path, + "-vf", + f"fps=1/{interval/1000}", + f"{output_dir}/%d.jpg", + ] + ) + + +# TODO: Don't hard-code the media type +async def image_to_data_url(filepath: str): + with open(filepath, "rb") as image_file: + encoded_string = base64.b64encode(image_file.read()).decode() + return f"data:image/jpeg;base64,{encoded_string}" + + +asyncio.run(main()) From 4937a92f42176d7d358ea30a90219265da008f2f Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Thu, 7 Mar 2024 14:59:26 -0500 Subject: [PATCH 027/144] support video uploads through the interface --- backend/custom_types.py | 7 + backend/mock_llm.py | 245 +++++++++++- backend/poetry.lock | 419 +++++++++++++++----- backend/pyproject.toml | 1 + backend/routes/generate_code.py | 65 ++- backend/video/utils.py | 134 +++++++ backend/video_to_app.py | 147 +------ frontend/src/App.tsx | 18 +- frontend/src/components/ImageUpload.tsx | 18 +- frontend/src/components/UrlInputSection.tsx | 4 +- frontend/src/types.ts | 1 + 11 files changed, 813 insertions(+), 246 deletions(-) create mode 100644 backend/custom_types.py create mode 100644 backend/video/utils.py diff --git a/backend/custom_types.py b/backend/custom_types.py new file mode 100644 index 0000000..b6c9fee --- /dev/null +++ b/backend/custom_types.py @@ -0,0 +1,7 @@ +from typing import Literal + + +InputMode = Literal[ + "image", + "video", +] diff --git a/backend/mock_llm.py b/backend/mock_llm.py index 0102bad..0c1060d 100644 --- a/backend/mock_llm.py +++ b/backend/mock_llm.py @@ -1,12 +1,20 @@ import asyncio from typing import Awaitable, Callable +from custom_types import InputMode -async def mock_completion(process_chunk: Callable[[str], Awaitable[None]]) -> str: - code_to_return = NO_IMAGES_NYTIMES_MOCK_CODE - for i in range(0, len(code_to_return), 10): - await process_chunk(code_to_return[i : i + 10]) +async def mock_completion( + process_chunk: Callable[[str], Awaitable[None]], input_mode: InputMode +) -> str: + code_to_return = ( + MORTGAGE_CALCULATOR_VIDEO_PROMPT_MOCK + if input_mode == "video" + else NO_IMAGES_NYTIMES_MOCK_CODE + ) + + for i in range(0, len(code_to_return), 100): + await process_chunk(code_to_return[i : i + 100]) await asyncio.sleep(0.01) return code_to_return @@ -206,3 +214,232 @@ NO_IMAGES_NYTIMES_MOCK_CODE = """ """ + +MORTGAGE_CALCULATOR_VIDEO_PROMPT_MOCK = """ + +The user flow in the video seems to be: +1. The calculator starts with some default values for loan amount, loan term, interest rate, etc. +2. The user toggles the "Include taxes & fees" checkbox which shows an explanation tooltip. +3. The user selects different loan terms from the dropdown, which updates the monthly payment amount. +4. The user enters a custom loan amount. +5. The user selects a different loan term (30-yr fixed FHA). +6. The user enters additional details like home price, down payment, state, credit score, property tax, home insurance, and HOA fees. +7. The calculator updates the total monthly payment breakdown. + +To build this: +- Use a container div for the whole calculator +- Have sections for Monthly Payment, Purchase Budget, loan details, additional costs +- Use input fields, dropdowns, and checkboxes for user input +- Update values dynamically using JavaScript when inputs change +- Show/hide explanation tooltip when checkbox is toggled +- Update monthly payment whenever loan amount, interest rate or term is changed +- Allow selecting loan term from a dropdown +- Update total monthly payment breakdown as additional costs are entered +- Style everything to match the screenshots using Tailwind utility classes + + + + + + + + + +
+
Mortgage Calculator
+ +
+
Monthly payment
+
Purchase budget
+
+ +
+ + + + +
+ +
$1,696
+ +
+
Loan amount
+ +
+ +
+
Loan term
+ +
+ +
+
+
Interest
+
7.61 %
+
+ +
+ +
+
+
Home price
+ +
+
+
Down payment
+
+ +
20 %
+
+
+
+ +
+
+
State
+ +
+
+
Credit score
+ +
+
+ +
+
+
Property tax (yearly)
+ +
+
+
Home insurance (yearly)
+ +
+
+
Private mortgage insurance (monthly)
+ +
+
+
Homeowners association (monthly)
+ +
+
+ +
+
+
Total monthly payment
+
$2,036
+
+
+
Loan
+
$1,635
+
Taxes & fees
+
$401
+
+
+ +
+
Disclaimer • Feedback
+
+
+ + + + +""" diff --git a/backend/poetry.lock b/backend/poetry.lock index 663f7a2..61ff096 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "anthropic" -version = "0.18.0" +version = "0.18.1" description = "The official Python library for the anthropic API" optional = false python-versions = ">=3.7" files = [ - {file = "anthropic-0.18.0-py3-none-any.whl", hash = "sha256:af2a65a48ba4661e3902b49f96710d168782af9deb06d58c12f25e53448fb570"}, - {file = "anthropic-0.18.0.tar.gz", hash = "sha256:80a6134f6562792be8ec30f743e2211476cb1e1f59b01795b901f099ae854825"}, + {file = "anthropic-0.18.1-py3-none-any.whl", hash = "sha256:b85aee64f619ce1b1964ba733a09adc4053e7bc4e6d4186001229ec191099dcf"}, + {file = "anthropic-0.18.1.tar.gz", hash = "sha256:f5d1caafd43f6cc933a79753a93531605095f040a384f6a900c3de9c3fb6694e"}, ] [package.dependencies] @@ -26,52 +26,56 @@ vertex = ["google-auth (>=2,<3)"] [[package]] name = "anyio" -version = "3.7.1" +version = "4.3.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, - {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, + {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, + {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, ] [package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} [package.extras] -doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] -test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (<0.22)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] [[package]] name = "beautifulsoup4" -version = "4.12.2" +version = "4.12.3" description = "Screen-scraping library" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, - {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, ] [package.dependencies] soupsieve = ">1.2" [package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -209,6 +213,17 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "decorator" +version = "4.4.2" +description = "Decorators for Humans" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +files = [ + {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, + {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, +] + [[package]] name = "distlib" version = "0.3.8" @@ -222,13 +237,13 @@ files = [ [[package]] name = "distro" -version = "1.8.0" +version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" files = [ - {file = "distro-1.8.0-py3-none-any.whl", hash = "sha256:99522ca3e365cac527b44bde033f64c6945d90eb9f769703caaec52b09bbd3ff"}, - {file = "distro-1.8.0.tar.gz", hash = "sha256:02e111d1dc6a50abb8eed6bf31c3e48ed8b0830d1ea2a1b78c61765c2513fdd8"}, + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, ] [[package]] @@ -330,13 +345,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.2" +version = "1.0.4" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, - {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, + {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, + {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, ] [package.dependencies] @@ -347,7 +362,7 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.23.0)"] +trio = ["trio (>=0.22.0,<0.25.0)"] [[package]] name = "httpx" @@ -375,13 +390,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "huggingface-hub" -version = "0.21.3" +version = "0.21.4" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" files = [ - {file = "huggingface_hub-0.21.3-py3-none-any.whl", hash = "sha256:b183144336fdf2810a8c109822e0bb6ef1fd61c65da6fb60e8c3f658b7144016"}, - {file = "huggingface_hub-0.21.3.tar.gz", hash = "sha256:26a15b604e4fc7bad37c467b76456543ec849386cbca9cd7e1e135f53e500423"}, + {file = "huggingface_hub-0.21.4-py3-none-any.whl", hash = "sha256:df37c2c37fc6c82163cdd8a67ede261687d80d1e262526d6c0ce73b6b3630a7b"}, + {file = "huggingface_hub-0.21.4.tar.gz", hash = "sha256:e1f4968c93726565a80edf6dc309763c7b546d0cfe79aa221206034d50155531"}, ] [package.dependencies] @@ -431,6 +446,56 @@ files = [ {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] +[[package]] +name = "imageio" +version = "2.34.0" +description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." +optional = false +python-versions = ">=3.8" +files = [ + {file = "imageio-2.34.0-py3-none-any.whl", hash = "sha256:08082bf47ccb54843d9c73fe9fc8f3a88c72452ab676b58aca74f36167e8ccba"}, + {file = "imageio-2.34.0.tar.gz", hash = "sha256:ae9732e10acf807a22c389aef193f42215718e16bd06eed0c5bb57e1034a4d53"}, +] + +[package.dependencies] +numpy = "*" +pillow = ">=8.3.2" + +[package.extras] +all-plugins = ["astropy", "av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"] +all-plugins-pypy = ["av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"] +build = ["wheel"] +dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"] +docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"] +ffmpeg = ["imageio-ffmpeg", "psutil"] +fits = ["astropy"] +full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "itk", "numpydoc", "pillow-heif", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "sphinx (<6)", "tifffile", "wheel"] +gdal = ["gdal"] +itk = ["itk"] +linting = ["black", "flake8"] +pillow-heif = ["pillow-heif"] +pyav = ["av"] +test = ["fsspec[github]", "pytest", "pytest-cov"] +tifffile = ["tifffile"] + +[[package]] +name = "imageio-ffmpeg" +version = "0.4.9" +description = "FFMPEG wrapper for Python" +optional = false +python-versions = ">=3.5" +files = [ + {file = "imageio-ffmpeg-0.4.9.tar.gz", hash = "sha256:39bcd1660118ef360fa4047456501071364661aa9d9021d3d26c58f1ee2081f5"}, + {file = "imageio_ffmpeg-0.4.9-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24095e882a126a0d217197b86265f821b4bb3cf9004104f67c1384a2b4b49168"}, + {file = "imageio_ffmpeg-0.4.9-py3-none-manylinux2010_x86_64.whl", hash = "sha256:2996c64af3e5489227096580269317719ea1a8121d207f2e28d6c24ebc4a253e"}, + {file = "imageio_ffmpeg-0.4.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7eead662d2f46d748c0ab446b68f423eb63d2b54d0a8ef96f80607245540866d"}, + {file = "imageio_ffmpeg-0.4.9-py3-none-win32.whl", hash = "sha256:b6de1e18911687c538d5585d8287ab1a23624ca9dc2044fcc4607de667bcf11e"}, + {file = "imageio_ffmpeg-0.4.9-py3-none-win_amd64.whl", hash = "sha256:7e900c695c6541b1cb17feb1baacd4009b30a53a45b81c23d53a67ab13ffb766"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "iniconfig" version = "2.0.0" @@ -442,6 +507,30 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "moviepy" +version = "1.0.3" +description = "Video editing with Python" +optional = false +python-versions = "*" +files = [ + {file = "moviepy-1.0.3.tar.gz", hash = "sha256:2884e35d1788077db3ff89e763c5ba7bfddbd7ae9108c9bc809e7ba58fa433f5"}, +] + +[package.dependencies] +decorator = ">=4.0.2,<5.0" +imageio = {version = ">=2.5,<3.0", markers = "python_version >= \"3.4\""} +imageio_ffmpeg = {version = ">=0.2.0", markers = "python_version >= \"3.4\""} +numpy = {version = ">=1.17.3", markers = "python_version > \"2.7\""} +proglog = "<=1.0.0" +requests = ">=2.8.1,<3.0" +tqdm = ">=4.11.2,<5.0" + +[package.extras] +doc = ["Sphinx (>=1.5.2,<2.0)", "numpydoc (>=0.6.0,<1.0)", "pygame (>=1.9.3,<2.0)", "sphinx_rtd_theme (>=0.1.10b0,<1.0)"] +optional = ["matplotlib (>=2.0.0,<3.0)", "opencv-python (>=3.0,<4.0)", "scikit-image (>=0.13.0,<1.0)", "scikit-learn", "scipy (>=0.19.0,<1.5)", "youtube_dl"] +test = ["coverage (<5.0)", "coveralls (>=1.1,<2.0)", "pytest (>=3.0.0,<4.0)", "pytest-cov (>=2.5.1,<3.0)", "requests (>=2.8.1,<3.0)"] + [[package]] name = "nodeenv" version = "1.8.0" @@ -456,25 +545,70 @@ files = [ [package.dependencies] setuptools = "*" +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + [[package]] name = "openai" -version = "1.3.7" +version = "1.13.3" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.3.7-py3-none-any.whl", hash = "sha256:e5c51367a910297e4d1cd33d2298fb87d7edf681edbe012873925ac16f95bee0"}, - {file = "openai-1.3.7.tar.gz", hash = "sha256:18074a0f51f9b49d1ae268c7abc36f7f33212a0c0d08ce11b7053ab2d17798de"}, + {file = "openai-1.13.3-py3-none-any.whl", hash = "sha256:5769b62abd02f350a8dd1a3a242d8972c947860654466171d60fb0972ae0a41c"}, + {file = "openai-1.13.3.tar.gz", hash = "sha256:ff6c6b3bc7327e715e4b3592a923a5a1c7519ff5dd764a83d69f633d49e77a7b"}, ] [package.dependencies] -anyio = ">=3.5.0,<4" +anyio = ">=3.5.0,<5" distro = ">=1.7.0,<2" httpx = ">=0.23.0,<1" pydantic = ">=1.9.0,<3" sniffio = "*" tqdm = ">4" -typing-extensions = ">=4.5,<5" +typing-extensions = ">=4.7,<5" [package.extras] datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] @@ -490,6 +624,91 @@ files = [ {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] +[[package]] +name = "pillow" +version = "10.2.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, + {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, + {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, + {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, + {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, + {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, + {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, + {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, + {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, + {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, + {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, + {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, + {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, + {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, + {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, + {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, + {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + [[package]] name = "platformdirs" version = "4.2.0" @@ -507,13 +726,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest- [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -538,49 +757,63 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "proglog" +version = "0.1.10" +description = "Log and progress bar manager for console, notebooks, web..." +optional = false +python-versions = "*" +files = [ + {file = "proglog-0.1.10-py3-none-any.whl", hash = "sha256:19d5da037e8c813da480b741e3fa71fb1ac0a5b02bf21c41577c7f327485ec50"}, + {file = "proglog-0.1.10.tar.gz", hash = "sha256:658c28c9c82e4caeb2f25f488fff9ceace22f8d69b15d0c1c86d64275e4ddab4"}, +] + +[package.dependencies] +tqdm = "*" + [[package]] name = "pydantic" -version = "1.10.13" +version = "1.10.14" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, - {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, - {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, - {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, - {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, - {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, - {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, - {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, - {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, - {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, - {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, - {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, - {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, - {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, - {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, - {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, - {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, - {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, - {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, - {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, - {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, - {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, - {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, + {file = "pydantic-1.10.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4fcec873f90537c382840f330b90f4715eebc2bc9925f04cb92de593eae054"}, + {file = "pydantic-1.10.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e3a76f571970fcd3c43ad982daf936ae39b3e90b8a2e96c04113a369869dc87"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d886bd3c3fbeaa963692ef6b643159ccb4b4cefaf7ff1617720cbead04fd1d"}, + {file = "pydantic-1.10.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:798a3d05ee3b71967844a1164fd5bdb8c22c6d674f26274e78b9f29d81770c4e"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:23d47a4b57a38e8652bcab15a658fdb13c785b9ce217cc3a729504ab4e1d6bc9"}, + {file = "pydantic-1.10.14-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9f674b5c3bebc2eba401de64f29948ae1e646ba2735f884d1594c5f675d6f2a"}, + {file = "pydantic-1.10.14-cp310-cp310-win_amd64.whl", hash = "sha256:24a7679fab2e0eeedb5a8924fc4a694b3bcaac7d305aeeac72dd7d4e05ecbebf"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d578ac4bf7fdf10ce14caba6f734c178379bd35c486c6deb6f49006e1ba78a7"}, + {file = "pydantic-1.10.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa7790e94c60f809c95602a26d906eba01a0abee9cc24150e4ce2189352deb1b"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad4e10efa5474ed1a611b6d7f0d130f4aafadceb73c11d9e72823e8f508e663"}, + {file = "pydantic-1.10.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245f4f61f467cb3dfeced2b119afef3db386aec3d24a22a1de08c65038b255f"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:21efacc678a11114c765eb52ec0db62edffa89e9a562a94cbf8fa10b5db5c046"}, + {file = "pydantic-1.10.14-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:412ab4a3f6dbd2bf18aefa9f79c7cca23744846b31f1d6555c2ee2b05a2e14ca"}, + {file = "pydantic-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:e897c9f35281f7889873a3e6d6b69aa1447ceb024e8495a5f0d02ecd17742a7f"}, + {file = "pydantic-1.10.14-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d604be0f0b44d473e54fdcb12302495fe0467c56509a2f80483476f3ba92b33c"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a42c7d17706911199798d4c464b352e640cab4351efe69c2267823d619a937e5"}, + {file = "pydantic-1.10.14-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:596f12a1085e38dbda5cbb874d0973303e34227b400b6414782bf205cc14940c"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bfb113860e9288d0886e3b9e49d9cf4a9d48b441f52ded7d96db7819028514cc"}, + {file = "pydantic-1.10.14-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bc3ed06ab13660b565eed80887fcfbc0070f0aa0691fbb351657041d3e874efe"}, + {file = "pydantic-1.10.14-cp37-cp37m-win_amd64.whl", hash = "sha256:ad8c2bc677ae5f6dbd3cf92f2c7dc613507eafe8f71719727cbc0a7dec9a8c01"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c37c28449752bb1f47975d22ef2882d70513c546f8f37201e0fec3a97b816eee"}, + {file = "pydantic-1.10.14-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:49a46a0994dd551ec051986806122767cf144b9702e31d47f6d493c336462597"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53e3819bd20a42470d6dd0fe7fc1c121c92247bca104ce608e609b59bc7a77ee"}, + {file = "pydantic-1.10.14-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbb503bbbbab0c588ed3cd21975a1d0d4163b87e360fec17a792f7d8c4ff29f"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:336709883c15c050b9c55a63d6c7ff09be883dbc17805d2b063395dd9d9d0022"}, + {file = "pydantic-1.10.14-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4ae57b4d8e3312d486e2498d42aed3ece7b51848336964e43abbf9671584e67f"}, + {file = "pydantic-1.10.14-cp38-cp38-win_amd64.whl", hash = "sha256:dba49d52500c35cfec0b28aa8b3ea5c37c9df183ffc7210b10ff2a415c125c4a"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c66609e138c31cba607d8e2a7b6a5dc38979a06c900815495b2d90ce6ded35b4"}, + {file = "pydantic-1.10.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d986e115e0b39604b9eee3507987368ff8148222da213cd38c359f6f57b3b347"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:646b2b12df4295b4c3148850c85bff29ef6d0d9621a8d091e98094871a62e5c7"}, + {file = "pydantic-1.10.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282613a5969c47c83a8710cc8bfd1e70c9223feb76566f74683af889faadc0ea"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:466669501d08ad8eb3c4fecd991c5e793c4e0bbd62299d05111d4f827cded64f"}, + {file = "pydantic-1.10.14-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:13e86a19dca96373dcf3190fcb8797d40a6f12f154a244a8d1e8e03b8f280593"}, + {file = "pydantic-1.10.14-cp39-cp39-win_amd64.whl", hash = "sha256:08b6ec0917c30861e3fe71a93be1648a2aa4f62f866142ba21670b24444d7fd8"}, + {file = "pydantic-1.10.14-py3-none-any.whl", hash = "sha256:8ee853cd12ac2ddbf0ecbac1c289f95882b2d4482258048079d13be700aa114c"}, + {file = "pydantic-1.10.14.tar.gz", hash = "sha256:46f17b832fe27de7850896f3afee50ea682220dd218f7e9c88d436788419dca6"}, ] [package.dependencies] @@ -610,13 +843,13 @@ dev = ["twine (>=3.4.1)"] [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -632,13 +865,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no [[package]] name = "python-dotenv" -version = "1.0.0" +version = "1.0.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.8" files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, ] [package.extras] @@ -726,29 +959,29 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "setuptools" -version = "69.0.3" +version = "69.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, + {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, + {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, ] [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"] +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)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "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.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] @@ -919,13 +1152,13 @@ files = [ [[package]] name = "tqdm" -version = "4.66.1" +version = "4.66.2" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, - {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, + {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, + {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, ] [package.dependencies] @@ -939,13 +1172,13 @@ telegram = ["requests"] [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] @@ -1088,4 +1321,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "17d8bb6d9e4a392f2512e4265df564708d59c1d83b26146f23e2595b6764c711" +content-hash = "274fed55cf4a2f4e9954b3d196c103b72225409c6050759a939f0ca197ae3f79" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 14009f6..263b4a4 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -16,6 +16,7 @@ beautifulsoup4 = "^4.12.2" httpx = "^0.25.1" pre-commit = "^3.6.2" anthropic = "^0.18.0" +moviepy = "^1.0.3" [tool.poetry.group.dev.dependencies] pytest = "^7.4.3" diff --git a/backend/routes/generate_code.py b/backend/routes/generate_code.py index d90e82c..2efc7a0 100644 --- a/backend/routes/generate_code.py +++ b/backend/routes/generate_code.py @@ -3,9 +3,12 @@ import traceback from fastapi import APIRouter, WebSocket import openai from config import ANTHROPIC_API_KEY, IS_PROD, SHOULD_MOCK_AI_RESPONSE +from custom_types import InputMode from llm import ( CODE_GENERATION_MODELS, + MODEL_CLAUDE_OPUS, stream_claude_response, + stream_claude_response_native, stream_openai_response, ) from openai.types.chat import ChatCompletionMessageParam @@ -16,9 +19,11 @@ from prompts import assemble_imported_code_prompt, assemble_prompt from access_token import validate_access_token from datetime import datetime import json +from prompts.claude_prompts import VIDEO_PROMPT from prompts.types import Stack -from utils import pprint_prompt # type: ignore +# from utils import pprint_prompt +from video.utils import extract_tag_content, assemble_claude_prompt_video # type: ignore router = APIRouter() @@ -64,6 +69,19 @@ async def stream_code(websocket: WebSocket): generated_code_config = "" if "generatedCodeConfig" in params and params["generatedCodeConfig"]: generated_code_config = params["generatedCodeConfig"] + if not generated_code_config in get_args(Stack): + await throw_error(f"Invalid generated code config: {generated_code_config}") + return + # Cast the variable to the Stack type + valid_stack = cast(Stack, generated_code_config) + + # Validate the input mode + input_mode = params.get("inputMode") + if not input_mode in get_args(InputMode): + await throw_error(f"Invalid input mode: {input_mode}") + raise Exception(f"Invalid input mode: {input_mode}") + # Cast the variable to the right type + validated_input_mode = cast(InputMode, input_mode) # Read the model from the request. Fall back to default if not provided. code_generation_model = params.get("codeGenerationModel", "gpt_4_vision") @@ -72,7 +90,7 @@ async def stream_code(websocket: WebSocket): raise Exception(f"Invalid model: {code_generation_model}") print( - f"Generating {generated_code_config} code using {code_generation_model} model..." + f"Generating {generated_code_config} code for uploaded {input_mode} using {code_generation_model} model..." ) # Get the OpenAI API key from the request. Fall back to environment variable if not provided. @@ -110,13 +128,6 @@ async def stream_code(websocket: WebSocket): ) return - # Validate the generated code config - if not generated_code_config in get_args(Stack): - await throw_error(f"Invalid generated code config: {generated_code_config}") - return - # Cast the variable to the Stack type - valid_stack = cast(Stack, generated_code_config) - # Get the OpenAI Base URL from the request. Fall back to environment variable if not provided. openai_base_url = None # Disable user-specified OpenAI Base URL in prod @@ -203,13 +214,34 @@ async def stream_code(websocket: WebSocket): image_cache = create_alt_url_mapping(params["history"][-2]) - pprint_prompt(prompt_messages) + if validated_input_mode == "video": + video_data_url = params["image"] + prompt_messages = await assemble_claude_prompt_video(video_data_url) + + # pprint_prompt(prompt_messages) # type: ignore if SHOULD_MOCK_AI_RESPONSE: - completion = await mock_completion(process_chunk) + completion = await mock_completion( + process_chunk, input_mode=validated_input_mode + ) else: try: - if code_generation_model == "claude_3_sonnet": + if validated_input_mode == "video": + if not ANTHROPIC_API_KEY: + await throw_error( + "No Anthropic API key found. Please add the environment variable ANTHROPIC_API_KEY to backend/.env" + ) + raise Exception("No Anthropic key") + + completion = await stream_claude_response_native( + system_prompt=VIDEO_PROMPT, + messages=prompt_messages, # type: ignore + api_key=ANTHROPIC_API_KEY, + callback=lambda x: process_chunk(x), + model=MODEL_CLAUDE_OPUS, + include_thinking=True, + ) + elif code_generation_model == "claude_3_sonnet": if not ANTHROPIC_API_KEY: await throw_error( "No Anthropic API key found. Please add the environment variable ANTHROPIC_API_KEY to backend/.env" @@ -217,13 +249,13 @@ async def stream_code(websocket: WebSocket): raise Exception("No Anthropic key") completion = await stream_claude_response( - prompt_messages, + prompt_messages, # type: ignore api_key=ANTHROPIC_API_KEY, callback=lambda x: process_chunk(x), ) else: completion = await stream_openai_response( - prompt_messages, + prompt_messages, # type: ignore api_key=openai_api_key, base_url=openai_base_url, callback=lambda x: process_chunk(x), @@ -263,8 +295,11 @@ async def stream_code(websocket: WebSocket): ) return await throw_error(error_message) + if validated_input_mode == "video": + completion = extract_tag_content("html", completion) + # Write the messages dict into a log so that we can debug later - write_logs(prompt_messages, completion) + write_logs(prompt_messages, completion) # type: ignore try: if should_generate_images: diff --git a/backend/video/utils.py b/backend/video/utils.py new file mode 100644 index 0000000..94501a2 --- /dev/null +++ b/backend/video/utils.py @@ -0,0 +1,134 @@ +# Extract HTML content from the completion string +import base64 +import io +import mimetypes +import os +import tempfile +import uuid +from typing import Union, cast +from moviepy.editor import VideoFileClip # type: ignore +from PIL import Image +import math + + +DEBUG = True +TARGET_NUM_SCREENSHOTS = ( + 20 # Should be max that Claude supports (20) - reduce to save tokens on testing +) + + +async def assemble_claude_prompt_video(video_data_url: str): + images = split_video_into_screenshots(video_data_url) + + # Save images to tmp if we're debugging + if DEBUG: + save_images_to_tmp(images) + + # Validate number of images + print(f"Number of frames extracted from video: {len(images)}") + if len(images) > 20: + print(f"Too many screenshots: {len(images)}") + return + + # Convert images to the message format for Claude + content_messages: list[dict[str, Union[dict[str, str], str]]] = [] + for image in images: + + # Convert Image to buffer + buffered = io.BytesIO() + image.save(buffered, format="JPEG") + + # Encode bytes as base64 + base64_data = base64.b64encode(buffered.getvalue()).decode("utf-8") + media_type = "image/jpeg" + + content_messages.append( + { + "type": "image", + "source": { + "type": "base64", + "media_type": media_type, + "data": base64_data, + }, + } + ) + + return [ + { + "role": "user", + "content": content_messages, + }, + ] + + +# Returns a list of images/frame (RGB format) +def split_video_into_screenshots(video_data_url: str) -> list[Image.Image]: + target_num_screenshots = TARGET_NUM_SCREENSHOTS + + # Decode the base64 URL to get the video bytes + video_encoded_data = video_data_url.split(",")[1] + video_bytes = base64.b64decode(video_encoded_data) + + mime_type = video_data_url.split(";")[0].split(":")[1] + suffix = mimetypes.guess_extension(mime_type) + + with tempfile.NamedTemporaryFile(suffix=suffix, delete=True) as temp_video_file: + print(temp_video_file.name) + temp_video_file.write(video_bytes) + temp_video_file.flush() + clip = VideoFileClip(temp_video_file.name) + images: list[Image.Image] = [] + total_frames = cast(int, clip.reader.nframes) # type: ignore + + # Calculate frame skip interval by dividing total frames by the target number of screenshots + # Ensuring a minimum skip of 1 frame + frame_skip = max(1, math.ceil(total_frames / target_num_screenshots)) + + # Iterate over each frame in the clip + for i, frame in enumerate(clip.iter_frames()): + # Save every nth frame + if i % frame_skip == 0: + frame_image = Image.fromarray(frame) # type: ignore + images.append(frame_image) + # Ensure that we don't capture more than the desired number of frames + if len(images) >= target_num_screenshots: + break + + # Close the video file to release resources + clip.close() + + return images + + +# Save a list of PIL images to a random temporary directory +def save_images_to_tmp(images: list[Image.Image]): + + # Create a unique temporary directory + unique_dir_name = f"screenshots_{uuid.uuid4()}" + tmp_screenshots_dir = os.path.join(tempfile.gettempdir(), unique_dir_name) + os.makedirs(tmp_screenshots_dir, exist_ok=True) + + for idx, image in enumerate(images): + # Generate a unique image filename using index + image_filename = f"screenshot_{idx}.jpg" + tmp_filepath = os.path.join(tmp_screenshots_dir, image_filename) + image.save(tmp_filepath, format="JPEG") + + print("Saved to " + tmp_screenshots_dir) + + +def extract_tag_content(tag: str, text: str) -> str: + """ + Extracts content for a given tag from the provided text. + + :param tag: The tag to search for. + :param text: The text to search within. + :return: The content found within the tag, if any. + """ + tag_start = f"<{tag}>" + tag_end = f"" + start_idx = text.find(tag_start) + end_idx = text.find(tag_end, start_idx) + if start_idx != -1 and end_idx != -1: + return text[start_idx : end_idx + len(tag_end)] + return "" diff --git a/backend/video_to_app.py b/backend/video_to_app.py index 7fb7c44..597cbbf 100644 --- a/backend/video_to_app.py +++ b/backend/video_to_app.py @@ -1,19 +1,21 @@ # Load environment variables first -import base64 -import shutil + from dotenv import load_dotenv load_dotenv() + +import base64 +import mimetypes import time import subprocess import os -from typing import Union import asyncio from datetime import datetime -from prompts.claude_prompts import VIDEO_PROMPT, VIDEO_PROMPT_ALPINE_JS +from prompts.claude_prompts import VIDEO_PROMPT from utils import pprint_prompt from config import ANTHROPIC_API_KEY +from video.utils import extract_tag_content, assemble_claude_prompt_video from llm import ( MODEL_CLAUDE_OPUS, # MODEL_CLAUDE_SONNET, @@ -28,11 +30,12 @@ OUTPUTS_DIR = "./video_evals/outputs" async def main(): - - video_filename = "mortgage-calculator.mov" - screenshot_interval = 850 + video_filename = "shortest.mov" is_followup = False + if not ANTHROPIC_API_KEY: + raise ValueError("ANTHROPIC_API_KEY is not set") + # Get previous HTML previous_html = "" if is_followup: @@ -44,72 +47,22 @@ async def main(): ], key=os.path.getctime, ) - print(previous_html_file) with open(previous_html_file, "r") as file: previous_html = file.read() - if not ANTHROPIC_API_KEY: - raise ValueError("ANTHROPIC_API_KEY is not set") - - # Create the SCREENSHOTS_DIR if it doesn't exist - if not os.path.exists(SCREENSHOTS_DIR): - os.makedirs(SCREENSHOTS_DIR) - - # Clear out the SCREENSHOTS_DIR before generating new screenshots - for filename in os.listdir(SCREENSHOTS_DIR): - file_path = os.path.join(SCREENSHOTS_DIR, filename) - try: - if os.path.isfile(file_path) or os.path.islink(file_path): - os.unlink(file_path) - elif os.path.isdir(file_path): - shutil.rmtree(file_path) - except Exception as e: - print(f"Failed to delete {file_path}. Reason: {e}") - - # Split the video into screenshots - split_video_into_screenshots( - os.path.join(VIDEO_DIR, video_filename), SCREENSHOTS_DIR, screenshot_interval + video_file = os.path.join(VIDEO_DIR, video_filename) + mime_type = mimetypes.guess_type(video_file)[0] + with open(video_file, "rb") as file: + video_content = file.read() + video_data_url = ( + f"data:{mime_type};base64,{base64.b64encode(video_content).decode('utf-8')}" ) - # Get all the screenshots in the directory - screenshots = [f for f in os.listdir(SCREENSHOTS_DIR) if f.endswith(".jpg")] + prompt_messages = await assemble_claude_prompt_video(video_data_url) - if len(screenshots) > 20: - print(f"Too many screenshots: {len(screenshots)}") - return - - input_image_urls: list[str] = [] - sorted_screenshots = sorted(screenshots, key=lambda x: int(x.split(".")[0])) - for filename in sorted_screenshots: - filepath = os.path.join(SCREENSHOTS_DIR, filename) - data_url = await image_to_data_url(filepath) - print(filename) - input_image_urls.append(data_url) - - # Convert images to the message format for Claude - content_messages: list[dict[str, Union[dict[str, str], str]]] = [] - for url in input_image_urls: - media_type = url.split(";")[0].split(":")[1] - base64_data = url.split(",")[1] - content_messages.append( - { - "type": "image", - "source": { - "type": "base64", - "media_type": media_type, - "data": base64_data, - }, - } - ) - - prompt_messages = [ - { - "role": "user", - "content": content_messages, - }, - # {"role": "assistant", "content": SECOND_MESSAGE}, - # {"role": "user", "content": "continue"}, - ] + # Tell the model to continue + # {"role": "assistant", "content": SECOND_MESSAGE}, + # {"role": "user", "content": "continue"}, if is_followup: prompt_messages += [ @@ -161,66 +114,10 @@ async def main(): with open(output_path, "w") as file: file.write(html_content) + print(f"Output file path: {output_path}") + # Show a notification subprocess.run(["osascript", "-e", 'display notification "Coding Complete"']) -# Extract HTML content from the completion string -def extract_tag_content(tag: str, text: str) -> str: - """ - Extracts content for a given tag from the provided text. - - :param tag: The tag to search for. - :param text: The text to search within. - :return: The content found within the tag, if any. - """ - tag_start = f"<{tag}>" - tag_end = f"" - start_idx = text.find(tag_start) - end_idx = text.find(tag_end, start_idx) - if start_idx != -1 and end_idx != -1: - return text[start_idx : end_idx + len(tag_end)] - return "" - - -def split_video_into_screenshots(video_path: str, output_dir: str, interval: int): - # Create the output directory if it doesn't exist - os.makedirs(output_dir, exist_ok=True) - - # Calculate the number of zeros needed for padding - # duration = float( - # subprocess.check_output( - # [ - # "ffprobe", - # "-v", - # "error", - # "-show_entries", - # "format=duration", - # "-of", - # "default=noprint_wrappers=1:nokey=1", - # video_path, - # ] - # ) - # ) - - # Run the ffmpeg command to extract screenshots - subprocess.call( - [ - "ffmpeg", - "-i", - video_path, - "-vf", - f"fps=1/{interval/1000}", - f"{output_dir}/%d.jpg", - ] - ) - - -# TODO: Don't hard-code the media type -async def image_to_data_url(filepath: str): - with open(filepath, "rb") as image_file: - encoded_string = base64.b64encode(image_file.read()).decode() - return f"data:image/jpeg;base64,{encoded_string}" - - asyncio.run(main()) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6a2a38c..f0825a8 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -44,6 +44,8 @@ function App() { const [appState, setAppState] = useState(AppState.INITIAL); const [generatedCode, setGeneratedCode] = useState(""); + const [inputMode, setInputMode] = useState<"image" | "video">("image"); + const [referenceImages, setReferenceImages] = useState([]); const [executionConsole, setExecutionConsole] = useState([]); const [updateInstruction, setUpdateInstruction] = useState(""); @@ -140,6 +142,10 @@ function App() { cancelCodeGenerationAndReset(); }; + const shouldDisablePreview = inputMode === "video"; + const previewCode = + shouldDisablePreview && appState === AppState.CODING ? "" : generatedCode; + const cancelCodeGenerationAndReset = () => { // When this is the first version, reset the entire app state if (currentVersion === null) { @@ -219,16 +225,18 @@ function App() { } // Initial version creation - function doCreate(referenceImages: string[]) { + function doCreate(referenceImages: string[], inputMode: "image" | "video") { // Reset any existing state reset(); setReferenceImages(referenceImages); + setInputMode(inputMode); if (referenceImages.length > 0) { doGenerateCode( { generationType: "create", image: referenceImages[0], + inputMode, }, currentVersion ); @@ -261,6 +269,7 @@ function App() { doGenerateCode( { generationType: "update", + inputMode, image: referenceImages[0], resultImage: resultImage, history: updatedHistory, @@ -272,6 +281,7 @@ function App() { doGenerateCode( { generationType: "update", + inputMode, image: referenceImages[0], history: updatedHistory, isImportedFromCode, @@ -519,14 +529,14 @@ function App() { - + - + diff --git a/frontend/src/components/ImageUpload.tsx b/frontend/src/components/ImageUpload.tsx index ff97466..520f595 100644 --- a/frontend/src/components/ImageUpload.tsx +++ b/frontend/src/components/ImageUpload.tsx @@ -51,7 +51,10 @@ type FileWithPreview = { } & File; interface Props { - setReferenceImages: (referenceImages: string[]) => void; + setReferenceImages: ( + referenceImages: string[], + inputMode: "image" | "video" + ) => void; } function ImageUpload({ setReferenceImages }: Props) { @@ -59,11 +62,13 @@ function ImageUpload({ setReferenceImages }: Props) { const { getRootProps, getInputProps, isFocused, isDragAccept, isDragReject } = useDropzone({ maxFiles: 1, - maxSize: 1024 * 1024 * 5, // 5 MB + maxSize: 1024 * 1024 * 20, // 20 MB accept: { "image/png": [".png"], "image/jpeg": [".jpeg"], "image/jpg": [".jpg"], + "video/quicktime": [".mov"], + "video/mp4": [".mp4"], }, onDrop: (acceptedFiles) => { // Set up the preview thumbnail images @@ -78,7 +83,14 @@ function ImageUpload({ setReferenceImages }: Props) { // Convert images to data URLs and set the prompt images state Promise.all(acceptedFiles.map((file) => fileToDataURL(file))) .then((dataUrls) => { - setReferenceImages(dataUrls.map((dataUrl) => dataUrl as string)); + if (dataUrls.length > 0) { + setReferenceImages( + dataUrls.map((dataUrl) => dataUrl as string), + (dataUrls[0] as string).startsWith("data:video") + ? "video" + : "image" + ); + } }) .catch((error) => { toast.error("Error reading files" + error); diff --git a/frontend/src/components/UrlInputSection.tsx b/frontend/src/components/UrlInputSection.tsx index e2137e1..35ad622 100644 --- a/frontend/src/components/UrlInputSection.tsx +++ b/frontend/src/components/UrlInputSection.tsx @@ -6,7 +6,7 @@ import { toast } from "react-hot-toast"; interface Props { screenshotOneApiKey: string | null; - doCreate: (urls: string[]) => void; + doCreate: (urls: string[], inputMode: "image" | "video") => void; } export function UrlInputSection({ doCreate, screenshotOneApiKey }: Props) { @@ -46,7 +46,7 @@ export function UrlInputSection({ doCreate, screenshotOneApiKey }: Props) { } const res = await response.json(); - doCreate([res.url]); + doCreate([res.url], "image"); } catch (error) { console.error(error); toast.error( diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 4308f47..89440fe 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -27,6 +27,7 @@ export enum AppState { export interface CodeGenerationParams { generationType: "create" | "update"; + inputMode: "image" | "video"; image: string; resultImage?: string; history?: string[]; From 28d33a4d7e768034fc35a01b6269fe0c7e2e0161 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Thu, 7 Mar 2024 17:31:39 -0500 Subject: [PATCH 028/144] do multiple passes for video claude --- backend/llm.py | 71 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/backend/llm.py b/backend/llm.py index 2be7792..b9be56d 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -3,6 +3,8 @@ from anthropic import AsyncAnthropic from openai import AsyncOpenAI from openai.types.chat import ChatCompletionMessageParam, ChatCompletionChunk +from utils import pprint_prompt + MODEL_GPT_4_VISION = "gpt-4-vision-preview" MODEL_CLAUDE_SONNET = "claude-3-sonnet-20240229" MODEL_CLAUDE_OPUS = "claude-3-opus-20240229" @@ -113,34 +115,57 @@ async def stream_claude_response_native( client = AsyncAnthropic(api_key=api_key) - # Base parameters + # Base model parameters max_tokens = 4096 temperature = 0.0 - # Stream Claude response + # Multi-pass flow + current_pass_num = 1 + max_passes = 2 - # Set up message depending on whether we have a prefix - messages = ( - messages + [{"role": "assistant", "content": ""}] - if include_thinking - else messages - ) + prefix = "" + response = None - async with client.messages.stream( - model=model, - max_tokens=max_tokens, - temperature=temperature, - system=system_prompt, - messages=messages, # type: ignore - ) as stream: - async for text in stream.text_stream: - await callback(text) + while current_pass_num <= max_passes: + current_pass_num += 1 - # Return final message - response = await stream.get_final_message() + # Set up message depending on whether we have a prefix + messages_to_send = ( + messages + [{"role": "assistant", "content": prefix}] + if include_thinking + else messages + ) - print( - f"Token usage: Input Tokens: {response.usage.input_tokens}, Output Tokens: {response.usage.output_tokens}" - ) + pprint_prompt(messages_to_send) - return response.content[0].text + async with client.messages.stream( + model=model, + max_tokens=max_tokens, + temperature=temperature, + system=system_prompt, + messages=messages_to_send, # type: ignore + ) as stream: + async for text in stream.text_stream: + print(text, end="", flush=True) + await callback(text) + + # Return final message + response = await stream.get_final_message() + + # Set up messages array for next pass + messages += [ + {"role": "assistant", "content": str(prefix) + response.content[0].text}, + { + "role": "user", + "content": "You've done a good job with a first draft. Improve this further based on the original instructions so that the app is fully functional and looks like the original video of the app we're trying to replicate.", + }, + ] + + print( + f"Token usage: Input Tokens: {response.usage.input_tokens}, Output Tokens: {response.usage.output_tokens}" + ) + + if not response: + raise Exception("No HTML response found in AI response") + else: + return response.content[0].text From df0f30d40381d06dfcce8c343ba4a9b2ffeb158b Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 8 Mar 2024 04:47:36 -0500 Subject: [PATCH 029/144] Create video-to-app --- blog/video-to-app | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 blog/video-to-app diff --git a/blog/video-to-app b/blog/video-to-app new file mode 100644 index 0000000..8e9d1ac --- /dev/null +++ b/blog/video-to-app @@ -0,0 +1,13 @@ + +* You can now capture a video of a web app in action and have the AI build it for you. +* Unlike screenshots, the app will not visually look exactly like the screen recording but it will be functional +* This uses Claude 3 by Anthropic. You need to add an Anthropic API key to your .env file. See the README.md for details +* IMPORTANT: This is very experimental and each call is expensive. I would recommend setting up usage limits on your Anthropic account to avoid excess charges. + +## Examples + +TBD + +## Tips for taking videos + +* We extract frames from your video so linger over each feature for a second or two. From 6d283ea9ef5965e1e323a2ecd58fd3acfdff91ee Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 8 Mar 2024 04:47:57 -0500 Subject: [PATCH 030/144] Rename video-to-app to video-to-app.md --- blog/{video-to-app => video-to-app.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename blog/{video-to-app => video-to-app.md} (100%) diff --git a/blog/video-to-app b/blog/video-to-app.md similarity index 100% rename from blog/video-to-app rename to blog/video-to-app.md From 3c97a401f8e3d9ff204f9b5e54acb531743829f0 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 8 Mar 2024 04:49:47 -0500 Subject: [PATCH 031/144] highlight video feature --- frontend/src/components/ImageUpload.tsx | 10 +++++++++- frontend/src/urls.ts | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 frontend/src/urls.ts diff --git a/frontend/src/components/ImageUpload.tsx b/frontend/src/components/ImageUpload.tsx index 520f595..f5f5fc7 100644 --- a/frontend/src/components/ImageUpload.tsx +++ b/frontend/src/components/ImageUpload.tsx @@ -3,6 +3,8 @@ import { useState, useEffect, useMemo } from "react"; import { useDropzone } from "react-dropzone"; // import { PromptImage } from "../../../types"; import { toast } from "react-hot-toast"; +import { URLS } from "../urls"; +import { Badge } from "./ui/badge"; const baseStyle = { flex: 1, @@ -157,10 +159,16 @@ function ImageUpload({ setReferenceImages }: Props) {

Drag & drop a screenshot here,
- or paste from clipboard,
or click to upload

+
+ New! Upload a screen recording in .mp4 or .mov format to + clone a whole app (experimental).{" "} + + Learn more. + +
); } diff --git a/frontend/src/urls.ts b/frontend/src/urls.ts new file mode 100644 index 0000000..1c74cab --- /dev/null +++ b/frontend/src/urls.ts @@ -0,0 +1,4 @@ +export const URLS = { + "intro-to-video": + "https://github.com/abi/screenshot-to-code/blob/main/blog/video-to-app.md", +}; From fa58f2ce8f27b805fb1522128dba6cbd72a77ff2 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 8 Mar 2024 05:00:48 -0500 Subject: [PATCH 032/144] add note when input mode is video --- frontend/src/App.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f0825a8..43c2a15 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -384,11 +384,25 @@ function App() { {/* Show code preview only when coding */} {appState === AppState.CODING && (
+ {/* Speed disclaimer for video mode */} + {inputMode === "video" && ( +
+ Code generation from videos can take 3-4 minutes. We do + multiple passes to get the best result. Please be patient. +
+ )} +
{executionConsole.slice(-1)[0]}
-
+ + + +
-
)} From a0f5af0fdcc318e5e0101f0be58ba898bc5e20df Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 8 Mar 2024 05:02:23 -0500 Subject: [PATCH 033/144] improve mock functionality --- backend/mock_llm.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/mock_llm.py b/backend/mock_llm.py index 0c1060d..df0b8ed 100644 --- a/backend/mock_llm.py +++ b/backend/mock_llm.py @@ -4,6 +4,9 @@ from typing import Awaitable, Callable from custom_types import InputMode +STREAM_CHUNK_SIZE = 100 + + async def mock_completion( process_chunk: Callable[[str], Awaitable[None]], input_mode: InputMode ) -> str: @@ -13,8 +16,8 @@ async def mock_completion( else NO_IMAGES_NYTIMES_MOCK_CODE ) - for i in range(0, len(code_to_return), 100): - await process_chunk(code_to_return[i : i + 100]) + for i in range(0, len(code_to_return), STREAM_CHUNK_SIZE): + await process_chunk(code_to_return[i : i + STREAM_CHUNK_SIZE]) await asyncio.sleep(0.01) return code_to_return From caa63013f52a44edeac6d729a3b78ede438b08f3 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 8 Mar 2024 11:16:49 -0500 Subject: [PATCH 034/144] extract html to show a preview for video mode and switch to .srcdoc with throttling for the preview --- frontend/src/App.tsx | 6 ++++-- frontend/src/components/Preview.tsx | 15 ++++++--------- frontend/src/components/preview/extractHtml.ts | 4 ++++ frontend/src/components/preview/simpleHash.ts | 10 ++++++++++ 4 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/preview/extractHtml.ts create mode 100644 frontend/src/components/preview/simpleHash.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 43c2a15..a6b3358 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -37,6 +37,7 @@ import ImportCodeSection from "./components/ImportCodeSection"; import { Stack } from "./lib/stacks"; import { CodeGenerationModel } from "./lib/models"; import ModelSettingsSection from "./components/ModelSettingsSection"; +import { extractHtml } from "./components/preview/extractHtml"; const IS_OPENAI_DOWN = false; @@ -142,9 +143,10 @@ function App() { cancelCodeGenerationAndReset(); }; - const shouldDisablePreview = inputMode === "video"; const previewCode = - shouldDisablePreview && appState === AppState.CODING ? "" : generatedCode; + inputMode === "video" && appState === AppState.CODING + ? extractHtml(generatedCode) + : generatedCode; const cancelCodeGenerationAndReset = () => { // When this is the first version, reset the entire app state diff --git a/frontend/src/components/Preview.tsx b/frontend/src/components/Preview.tsx index d2eff16..eb9ea6d 100644 --- a/frontend/src/components/Preview.tsx +++ b/frontend/src/components/Preview.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef } from "react"; import classNames from "classnames"; -// import useThrottle from "../hooks/useThrottle"; +import useThrottle from "../hooks/useThrottle"; interface Props { code: string; @@ -8,17 +8,14 @@ interface Props { } function Preview({ code, device }: Props) { - const throttledCode = code; - // Temporary disable throttling for the preview not updating when the code changes - // useThrottle(code, 200); const iframeRef = useRef(null); + // Don't update code more often than every 200ms. + const throttledCode = useThrottle(code, 200); + useEffect(() => { - const iframe = iframeRef.current; - if (iframe && iframe.contentDocument) { - iframe.contentDocument.open(); - iframe.contentDocument.write(throttledCode); - iframe.contentDocument.close(); + if (iframeRef.current) { + iframeRef.current.srcdoc = throttledCode; } }, [throttledCode]); diff --git a/frontend/src/components/preview/extractHtml.ts b/frontend/src/components/preview/extractHtml.ts new file mode 100644 index 0000000..f33b206 --- /dev/null +++ b/frontend/src/components/preview/extractHtml.ts @@ -0,0 +1,4 @@ +export function extractHtml(code: string): string { + const htmlStartIndex = code.indexOf(""); + return htmlStartIndex !== -1 ? code.slice(htmlStartIndex) : ""; +} diff --git a/frontend/src/components/preview/simpleHash.ts b/frontend/src/components/preview/simpleHash.ts new file mode 100644 index 0000000..0fbd17e --- /dev/null +++ b/frontend/src/components/preview/simpleHash.ts @@ -0,0 +1,10 @@ + +export function simpleHash(str: string, seed = 0) { + let hash = seed; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash |= 0; // Convert to 32bit integer + } + return hash; +} From cacf78a4bb125f394bf2d6fe025966afc5660a9e Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 8 Mar 2024 11:21:30 -0500 Subject: [PATCH 035/144] show preview of videos (only works for .MP4) --- frontend/src/App.tsx | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a6b3358..b9b528b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -467,14 +467,27 @@ function App() { "scanning relative": appState === AppState.CODING, })} > - Reference + {inputMode === "image" && ( + Reference + )} + {inputMode === "video" && ( +
- Original Screenshot + {inputMode === "video" + ? "Original Video" + : "Original Screenshot"}
)} From f09b4c7808988452a089a6c2af03c2bf095a765c Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 8 Mar 2024 12:37:37 -0500 Subject: [PATCH 036/144] improve preview for videos by showing the streaming response as it comes in --- frontend/src/components/preview/extractHtml.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/preview/extractHtml.ts b/frontend/src/components/preview/extractHtml.ts index f33b206..6e9c9ff 100644 --- a/frontend/src/components/preview/extractHtml.ts +++ b/frontend/src/components/preview/extractHtml.ts @@ -1,4 +1,16 @@ +// Not robust enough to support for instance export function extractHtml(code: string): string { - const htmlStartIndex = code.indexOf(""); - return htmlStartIndex !== -1 ? code.slice(htmlStartIndex) : ""; + const lastHtmlStartIndex = code.lastIndexOf(""); + let htmlEndIndex = code.indexOf("", lastHtmlStartIndex); + + if (lastHtmlStartIndex !== -1) { + // If "" is found, adjust htmlEndIndex to include the "" tag + if (htmlEndIndex !== -1) { + htmlEndIndex += "".length; + return code.slice(lastHtmlStartIndex, htmlEndIndex); + } + // If "" is not found, return the rest of the string starting from the last "" + return code.slice(lastHtmlStartIndex); + } + return ""; } From b69edb79485dcdde378d420d7a9b387d8f0f7636 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 8 Mar 2024 14:13:34 -0500 Subject: [PATCH 037/144] add information about Video to app --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f55fdf9..f962eea 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ See the [Examples](#-examples) section below for more demos. ## 🌟 Recent Updates +- Mar 8 - 🔥🎉🎁 Video-to-app: turn videos/screen recordings into functional apps - Mar 5 - Added support for Claude Sonnet 3 (as capable as or better than GPT-4 Vision, and faster!) ## 🛠 Getting Started @@ -50,13 +51,21 @@ For debugging purposes, if you don't want to waste GPT4-Vision credits, you can MOCK=true poetry run uvicorn main:app --reload --port 7001 ``` +## Video to app (experimental) + +[video] + +Record yourself using any website or app or even a Figma prototype, drag & drop in a video and in a few minutes, get a functional, similar-looking app. + +[You need an Anthropic API key for this functionality. Follow instructions here.](https://github.com/abi/screenshot-to-code/blob/main/blog/video-to-app.md) + ## Configuration - You can configure the OpenAI base URL if you need to use a proxy: Set OPENAI_BASE_URL in the `backend/.env` or directly in the UI in the settings dialog ## Using Claude 3 -We recently added support for Claude 3 Sonnet. It performs well, on par or better than GPT-4 vision for many inputs, and it tends to be faster. +We recently added support for Claude 3 Sonnet. It performs well, on par or better than GPT-4 vision for many inputs, and it tends to be faster. 1. Add an env var `ANTHROPIC_API_KEY` to `backend/.env` with your API key from Anthropic 2. When using the front-end, select "Claude 3 Sonnet" from the model dropdown From 451f6c399bed8b6cc3c825a56c0eeab8dcfdc213 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 8 Mar 2024 17:01:25 -0500 Subject: [PATCH 038/144] Update video-to-app.md --- blog/video-to-app.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/blog/video-to-app.md b/blog/video-to-app.md index 8e9d1ac..3561f6d 100644 --- a/blog/video-to-app.md +++ b/blog/video-to-app.md @@ -1,12 +1,17 @@ -* You can now capture a video of a web app in action and have the AI build it for you. +## Capture a screen recording of a web site in action and have the AI build it for you. + * Unlike screenshots, the app will not visually look exactly like the screen recording but it will be functional -* This uses Claude 3 by Anthropic. You need to add an Anthropic API key to your .env file. See the README.md for details -* IMPORTANT: This is very experimental and each call is expensive. I would recommend setting up usage limits on your Anthropic account to avoid excess charges. +* IMPORTANT: This is very experimental and each call is expensive (a few dollars). I would recommend setting up usage limits on your Anthropic account to avoid excess charges. + +## Setup + +This uses Claude 3 by Anthropic. Add an env var ANTHROPIC_API_KEY to backend/.env with your API key from Anthropic. + ## Examples -TBD +https://github.com/abi/screenshot-to-code/assets/23818/fe236c1e-ab92-4d84-b63d-e73e5be9a726 ## Tips for taking videos From 214163b0e02176333b5543740cf6262e5da99602 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 8 Mar 2024 17:01:42 -0500 Subject: [PATCH 039/144] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f962eea..f8b6795 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ MOCK=true poetry run uvicorn main:app --reload --port 7001 ## Video to app (experimental) -[video] +https://github.com/abi/screenshot-to-code/assets/23818/1468bef4-164f-4046-a6c8-4cfc40a5cdff Record yourself using any website or app or even a Figma prototype, drag & drop in a video and in a few minutes, get a functional, similar-looking app. From c08cf0ae571b1cd93d70f04a8f17b68c6452b35b Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Thu, 14 Mar 2024 15:57:44 -0400 Subject: [PATCH 040/144] support screen recording --- backend/poetry.lock | 26 ++-- frontend/package.json | 3 +- frontend/src/components/ImageUpload.tsx | 52 +++++--- .../components/recording/ScreenRecorder.tsx | 115 ++++++++++++++++++ frontend/src/components/recording/utils.ts | 31 +++++ frontend/src/types.ts | 6 + frontend/yarn.lock | 43 +++++++ 7 files changed, 247 insertions(+), 29 deletions(-) create mode 100644 frontend/src/components/recording/ScreenRecorder.tsx create mode 100644 frontend/src/components/recording/utils.ts diff --git a/backend/poetry.lock b/backend/poetry.lock index 61ff096..d6b5d02 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -592,13 +592,13 @@ files = [ [[package]] name = "openai" -version = "1.13.3" +version = "1.14.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.13.3-py3-none-any.whl", hash = "sha256:5769b62abd02f350a8dd1a3a242d8972c947860654466171d60fb0972ae0a41c"}, - {file = "openai-1.13.3.tar.gz", hash = "sha256:ff6c6b3bc7327e715e4b3592a923a5a1c7519ff5dd764a83d69f633d49e77a7b"}, + {file = "openai-1.14.0-py3-none-any.whl", hash = "sha256:5c9fd3a59f5cbdb4020733ddf79a22f6b7a36d561968cb3f3dd255cdd263d9fe"}, + {file = "openai-1.14.0.tar.gz", hash = "sha256:e287057adf0ec3315abc32ddcc968d095879abd9b68bf51c0402dab13ab5ae9b"}, ] [package.dependencies] @@ -615,13 +615,13 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] [[package]] name = "packaging" -version = "23.2" +version = "24.0" 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"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] @@ -825,13 +825,13 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pyright" -version = "1.1.352" +version = "1.1.354" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.352-py3-none-any.whl", hash = "sha256:0040cf173c6a60704e553bfd129dfe54de59cc76d0b2b80f77cfab4f50701d64"}, - {file = "pyright-1.1.352.tar.gz", hash = "sha256:a621c0dfbcf1291b3610641a07380fefaa1d0e182890a1b2a7f13b446e8109a9"}, + {file = "pyright-1.1.354-py3-none-any.whl", hash = "sha256:f28d61ae8ae035fc52ded1070e8d9e786051a26a4127bbd7a4ba0399b81b37b5"}, + {file = "pyright-1.1.354.tar.gz", hash = "sha256:b1070dc774ff2e79eb0523fe87f4ba9a90550de7e4b030a2bc9e031864029a1f"}, ] [package.dependencies] @@ -959,18 +959,18 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "setuptools" -version = "69.1.1" +version = "69.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, - {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, + {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, + {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, ] [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)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "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.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] diff --git a/frontend/package.json b/frontend/package.json index 8b4f0ee..7109443 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -45,7 +45,8 @@ "tailwind-merge": "^2.0.0", "tailwindcss-animate": "^1.0.7", "thememirror": "^2.0.1", - "vite-plugin-checker": "^0.6.2" + "vite-plugin-checker": "^0.6.2", + "webm-duration-fix": "^1.0.4" }, "devDependencies": { "@types/node": "^20.9.0", diff --git a/frontend/src/components/ImageUpload.tsx b/frontend/src/components/ImageUpload.tsx index f5f5fc7..366f104 100644 --- a/frontend/src/components/ImageUpload.tsx +++ b/frontend/src/components/ImageUpload.tsx @@ -5,6 +5,8 @@ import { useDropzone } from "react-dropzone"; import { toast } from "react-hot-toast"; import { URLS } from "../urls"; import { Badge } from "./ui/badge"; +import ScreenRecorder from "./recording/ScreenRecorder"; +import { ScreenRecorderState } from "../types"; const baseStyle = { flex: 1, @@ -61,16 +63,23 @@ interface Props { function ImageUpload({ setReferenceImages }: Props) { const [files, setFiles] = useState([]); + // TODO: Switch to Zustand + const [screenRecorderState, setScreenRecorderState] = + useState(ScreenRecorderState.INITIAL); + const { getRootProps, getInputProps, isFocused, isDragAccept, isDragReject } = useDropzone({ maxFiles: 1, maxSize: 1024 * 1024 * 20, // 20 MB accept: { + // Image formats "image/png": [".png"], "image/jpeg": [".jpeg"], "image/jpg": [".jpg"], + // Video formats "video/quicktime": [".mov"], "video/mp4": [".mp4"], + "video/webm": [".webm"], }, onDrop: (acceptedFiles) => { // Set up the preview thumbnail images @@ -154,21 +163,34 @@ function ImageUpload({ setReferenceImages }: Props) { return (
- {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} -
- -

- Drag & drop a screenshot here,
- or click to upload -

-
-
- New! Upload a screen recording in .mp4 or .mov format to - clone a whole app (experimental).{" "} - - Learn more. - -
+ {screenRecorderState === ScreenRecorderState.INITIAL && ( + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ +
+ +

+ Drag & drop a screenshot here,
+ or click to upload +

+
+ )} + {screenRecorderState === ScreenRecorderState.INITIAL && ( +
+ New! Upload a screen recording (.mp4, .mov) or record + your screen to clone a whole app (experimental).{" "} + + Learn more. + +
+ )} +
); } diff --git a/frontend/src/components/recording/ScreenRecorder.tsx b/frontend/src/components/recording/ScreenRecorder.tsx new file mode 100644 index 0000000..c2cc06d --- /dev/null +++ b/frontend/src/components/recording/ScreenRecorder.tsx @@ -0,0 +1,115 @@ +import { useState } from "react"; +import { Button } from "../ui/button"; +import { ScreenRecorderState } from "../../types"; +import { blobToBase64DataUrl } from "./utils"; +import fixWebmDuration from "webm-duration-fix"; +import toast from "react-hot-toast"; + +interface Props { + screenRecorderState: ScreenRecorderState; + setScreenRecorderState: (state: ScreenRecorderState) => void; + generateCode: ( + referenceImages: string[], + inputMode: "image" | "video" + ) => void; +} + +function ScreenRecorder({ + screenRecorderState, + setScreenRecorderState, + generateCode, +}: Props) { + const [mediaRecorder, setMediaRecorder] = useState( + null + ); + const [screenRecordingDataUrl, setScreenRecordingDataUrl] = useState< + string | null + >(null); + + const startScreenRecording = async () => { + try { + // Get the screen recording stream + const stream = await navigator.mediaDevices.getDisplayMedia({ + video: true, + audio: { echoCancellation: true }, + }); + + // TODO: Test across different browsers + // Create the media recorder + const options = { mimeType: "video/webm" }; + const mediaRecorder = new MediaRecorder(stream, options); + setMediaRecorder(mediaRecorder); + + const chunks: BlobPart[] = []; + + // Accumalate chunks as data is available + mediaRecorder.ondataavailable = (e: BlobEvent) => chunks.push(e.data); + + // When media recorder is stopped, create a data URL + mediaRecorder.onstop = async () => { + // TODO: Do I need to fix duration if it's not a webm? + const completeBlob = await fixWebmDuration( + new Blob(chunks, { + type: options.mimeType, + }) + ); + + const dataUrl = await blobToBase64DataUrl(completeBlob); + setScreenRecordingDataUrl(dataUrl); + setScreenRecorderState(ScreenRecorderState.FINISHED); + }; + + // Start recording + mediaRecorder.start(); + setScreenRecorderState(ScreenRecorderState.RECORDING); + } catch (error) { + toast.error("Could not start screen recording"); + throw error; + } + }; + + const stopScreenRecording = () => { + if (mediaRecorder) { + mediaRecorder.stop(); + setMediaRecorder(null); + } + }; + + const kickoffGeneration = () => { + if (screenRecordingDataUrl) { + generateCode([screenRecordingDataUrl], "video"); + } else { + toast.error("Screen recording does not exist. Please try again."); + throw new Error("No screen recording data url"); + } + }; + + return ( +
+ {screenRecorderState === ScreenRecorderState.INITIAL && ( + + )} + + {screenRecorderState === ScreenRecorderState.RECORDING && ( +
+
+ + Recording... +
+ +
+ )} + + {screenRecorderState === ScreenRecorderState.FINISHED && ( +
+
+ Screen Recording Captured. +
+ +
+ )} +
+ ); +} + +export default ScreenRecorder; diff --git a/frontend/src/components/recording/utils.ts b/frontend/src/components/recording/utils.ts new file mode 100644 index 0000000..b482ecf --- /dev/null +++ b/frontend/src/components/recording/utils.ts @@ -0,0 +1,31 @@ +export function downloadBlob(blob: Blob) { + // Create a URL for the blob object + const videoURL = URL.createObjectURL(blob); + + // Create a temporary anchor element and trigger the download + const a = document.createElement("a"); + a.href = videoURL; + a.download = "recording.webm"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + // Clear object URL + URL.revokeObjectURL(videoURL); +} + +export function blobToBase64DataUrl(blob: Blob): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => { + if (reader.result) { + resolve(reader.result as string); + } else { + reject(new Error("FileReader did not return a result.")); + } + }; + reader.onerror = () => + reject(new Error("FileReader encountered an error.")); + reader.readAsDataURL(blob); + }); +} diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 89440fe..fd46082 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -25,6 +25,12 @@ export enum AppState { CODE_READY = "CODE_READY", } +export enum ScreenRecorderState { + INITIAL = "initial", + RECORDING = "recording", + FINISHED = "finished", +} + export interface CodeGenerationParams { generationType: "create" | "update"; inputMode: "image" | "video"; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 5f09885..1edf79e 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1610,6 +1610,11 @@ base64-arraybuffer@^1.0.2: resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" @@ -1657,6 +1662,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + cac@^6.7.14: version "6.7.14" resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" @@ -1993,6 +2006,11 @@ dotenv@^16.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== +ebml-block@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/ebml-block/-/ebml-block-1.1.2.tgz#fd49951b0faf5a3049bdd61c851a76b5e679c290" + integrity sha512-HgNlIsRFP6D9VKU5atCeHRJY7XkJP8bOe8yEhd8NB7B3b4++VWTyauz6g650iiPmLfPLGlVpoJmGSgMfXDYusg== + ejs@^3.1.6: version "3.1.9" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" @@ -2186,6 +2204,11 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + execa@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" @@ -2467,6 +2490,11 @@ human-signals@^5.0.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== +ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" @@ -2498,6 +2526,11 @@ inherits@2: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +int64-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-1.0.1.tgz#c78d841b444cadf036cd04f8683696c740f15dca" + integrity sha512-+3azY4pXrjAupJHU1V9uGERWlhoqNswJNji6aD/02xac7oxol508AsMC5lxKhEqyZeDFy3enq5OGWXF4u75hiw== + invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -3765,6 +3798,16 @@ w3c-keyname@^2.2.4: resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== +webm-duration-fix@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/webm-duration-fix/-/webm-duration-fix-1.0.4.tgz#fef235cb3d3ed3363507f705a7577dbb9fdedae6" + integrity sha512-kvhmSmEnuohtK+j+mJswqCCM2ViKb9W8Ch0oAxcaeUvpok5CsMORQLnea+CYKDXPG6JH12H0CbRK85qhfeZLew== + dependencies: + buffer "^6.0.3" + ebml-block "^1.1.2" + events "^3.3.0" + int64-buffer "^1.0.1" + which@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" From a58c95c8c7c2494bd4ede937a8bddefe2095a9a4 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Thu, 14 Mar 2024 15:59:57 -0400 Subject: [PATCH 041/144] add more mock data --- backend/mock_llm.py | 1073 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1071 insertions(+), 2 deletions(-) diff --git a/backend/mock_llm.py b/backend/mock_llm.py index df0b8ed..0b903b7 100644 --- a/backend/mock_llm.py +++ b/backend/mock_llm.py @@ -4,14 +4,14 @@ from typing import Awaitable, Callable from custom_types import InputMode -STREAM_CHUNK_SIZE = 100 +STREAM_CHUNK_SIZE = 5 async def mock_completion( process_chunk: Callable[[str], Awaitable[None]], input_mode: InputMode ) -> str: code_to_return = ( - MORTGAGE_CALCULATOR_VIDEO_PROMPT_MOCK + GOOGLE_FORM_VIDEO_PROMPT_MOCK if input_mode == "video" else NO_IMAGES_NYTIMES_MOCK_CODE ) @@ -20,6 +20,16 @@ async def mock_completion( await process_chunk(code_to_return[i : i + STREAM_CHUNK_SIZE]) await asyncio.sleep(0.01) + if input_mode == "video": + # Extract the last block from code_to_return + # because we can have multiple passes + start = code_to_return.rfind("") + len("") + if start != -1 and end != -1: + code_to_return = code_to_return[start:end] + else: + code_to_return = "Error: HTML block not found." + return code_to_return @@ -445,4 +455,1063 @@ $(document).ready(function() { + + +More things to consider: +- Add validation to input fields to prevent non-numeric values +- Add a reset button to clear all inputs and reset to default values + + + + + + + + + +
+
Mortgage Calculator
+ +
+
Monthly payment
+
Purchase budget
+
+ +
+ + + + +
+ +
$1,696
+ +
+
Loan amount
+ +
+ +
+
Loan term
+ +
+ +
+
+
Interest
+
7.61 %
+
+ +
+ +
+
+
Home price
+ +
+
+
Down payment
+
+ +
20 %
+
+
+
+ +
+
+
State
+ +
+
+
Credit score
+ +
+
+ +
+
+
Property tax (yearly)
+ +
+
+
Home insurance (yearly)
+ +
+
+
Private mortgage insurance (monthly)
+ +
+
+
Homeowners association (monthly)
+ +
+
+ +
+
+
Total monthly payment
+
$2,036
+
+
+
Loan
+
$1,635
+
Taxes & fees
+
$401
+
+
+ +
+
Disclaimer • Feedback
+
+
+ + + + + +""" + +GOOGLE_FORM_VIDEO_PROMPT_MOCK = """ + +To build this: +- Create a search bar that allows typing and shows placeholder text +- Implement search suggestions that update as the user types +- Allow selecting a suggestion to perform that search +- Show search results with the query and an AI-powered overview +- Have filter tabs for different search verticals +- Allow clicking filter tabs to add/remove them, updating the URL +- Ensure the UI closely matches the Google style and colors + + + + + + + + + + + +
+
Gmail
+
Images
+ User avatar +
+ +
+ Google logo + +
+ + +
+ + + +
+ + +
+
+ + + + + + + +To further improve the app based on the original instructions: + +1. Adjust the font sizes, colors, and spacing to more closely match the Google style. +2. Add the hover effect on the search bar to expand its shadow. +3. Position the microphone icon correctly inside the search bar. +4. Implement the "I'm Feeling Lucky" functionality to redirect to the first search result. +5. Add the "Search by voice" and "Search by image" options that appear on hover. +6. Show the "Clear" button in the search bar when there's text entered. +7. Make the search suggestions clickable to perform the search. +8. Update the page title and URL when a search is performed. +9. Show a more realistic search result with a link, description, and URL. +10. Add more filter options and make them functional to update the search results. +11. Generate a more realistic AI-powered overview of the search topic. +12. Refine the responsive layout for different screen sizes. + + + + + Google + + + + + + + + + + +
+
Gmail
+
Images
+ User avatar +
+ +
+ Google logo + +
+ + + +
+ + + + + +
+ + +
+
+ + + + + + + +""" + +TALLY_FORM_VIDEO_PROMPT_MOCK = """ + +The user flow in the video seems to be: +1. The quiz starts by asking "What is the capital of Bulgaria?" with three multiple choice options. The user selects "Sofia". +2. The next question asks "What is the most famous dish in Belgium?" with two image-based options. The user selects "Steak with fries". +3. The next question asks "What is the name of this actor?" with an image of an older man with gray hair and beard. The user types in "Clooney" and submits the answer. +4. The quiz ends showing the user's score of 10/30 and a "Bummer, try again!" message with an arrow to restart. + +To build this as a functional web app: +1. Create the overall page structure with a title, question area, answer options area, and next/submit buttons. +2. Use jQuery to show/hide different questions and update the answer options based on the current question. +3. Store the questions, answer options, and correct answers in a Javascript array. +4. Keep track of the user's selections and compare them to the correct answers to calculate the score. +5. After the last question, show the final score and restart option. +6. Style everything to match the screenshots using Tailwind utility classes. + + + + + + + Online Trivia Quiz + + + + +

Online Trivia Quiz

+ +
+

+
+
+ + + + + + + + + + + + + + +This implements the online trivia quiz app as shown in the video. It uses jQuery to handle the interactions and Tailwind CSS for styling. The questions, options, and answers are defined in the quizData array. The app keeps track of the user's current question and score, allows selecting or entering answers, and displays the final score at the end with the option to restart. + + +To further improve the app to match the original video: + +1. Adjust the styling to more closely match the font, colors, spacing, and layout in the screenshots. +2. Add a cursor pointer when hovering over answer options. +3. Improve the styling of the selected answer option. +4. Make the "Next" button change to an arrow icon on hover. +5. Add a "Back" button to go to the previous question. +6. Show the current question number and total (e.g. "Question 1 of 3"). +7. Animate the transitions between questions. +8. Improve the responsive layout for different screen sizes. +9. Preload the images to avoid flickering. +10. Refactor the code to be more modular and maintainable. + + + + + + + Online Trivia Quiz + + + + + + +

Online Trivia Quiz

+ +
+
+
+ + +
+

+
+
+ + + + + + + + +
+ + + + + + +This updated version makes several improvements to match the original app more closely: + +1. Styling is refined to better match the font, colors, spacing, and layout of the screenshots. +2. Cursor changes to a pointer when hovering over answer options. +3. Selected answer option is highlighted. +4. "Next" button shows an animated arrow on hover. +5. "Back" button added to go to the previous question. +6. Current question number and total are displayed. +7. Fade transitions are added between questions. +8. Layout is made responsive for different screen sizes. +9. Images are preloaded to avoid flickering. +10. Code is refactored to be more modular and maintainable. + +The app should now be fully functional and closely resemble the look and behavior of the original video. Let me know if you would like any further adjustments! """ From cdbb78ea55f745bce6cc0ab2a7ae60ab92782591 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Thu, 14 Mar 2024 16:53:09 -0400 Subject: [PATCH 042/144] add browser tab indicator for coding and update favicon images --- frontend/index.html | 6 +--- frontend/public/favicon/coding.png | Bin 0 -> 2229 bytes frontend/public/favicon/main.png | Bin 0 -> 1669 bytes frontend/src/App.tsx | 4 +++ frontend/src/hooks/useBrowserTabIndicator.ts | 29 +++++++++++++++++++ 5 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 frontend/public/favicon/coding.png create mode 100644 frontend/public/favicon/main.png create mode 100644 frontend/src/hooks/useBrowserTabIndicator.ts diff --git a/frontend/index.html b/frontend/index.html index 2a7fa0e..39cfccc 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,11 +2,7 @@ - + diff --git a/frontend/public/favicon/coding.png b/frontend/public/favicon/coding.png new file mode 100644 index 0000000000000000000000000000000000000000..71a6a0b9622e55538e96ca08e2dd52c3d908215d GIT binary patch literal 2229 zcmV;m2uk;fP)6845~W9Rbi-#avfno!&q?WzF?}H=N5pEu_m)Q#u%Ln^r_*gp;#nFXncWH)|E|Yu7ABJn$CIa+k!@FbiuM= z`tkJ{3D5NZ&d6(~qR|_YTkpmevQY%$(S)GWV_ul53 zv(P`4IVICqL#|?j43-?P&GhO^w>x@FW5#qi)PM~tSaSSZM7;cqhTHC{8DPK$5v+8- z>FWzhbju6bfCA=yu;f&k72eAc%}{kM^s92fo5?nup7JHA#g zMIO>s>a{+_@&TdxB{OfH$!GO~JmgiyN{92+TLh{!@2ZzK70b?2YWWGFsw6V6ot!9d z7B^m0EZh1*U-30X@jl;M?NQHq4HjBoTl6_dxwYG8YwRsp*=DC6&~+JyHuO4o^)h4G z>!R#Dg;2G~{=>Debq_kPSoRy03qLyw>_Px37JN zI;&WyR6Xmsa&;0cgvt{ghO6~pVfE)36Ru?XpzYOSt1}itrSW>+06 zz+q@wI#N~;x&QF`;0>Yq*(p;`r?#`Z(?iiK)5ek{+ZqDmDD)hZyj z<2zMWh}9|(Fhxl?8i{o+M{u*vpa)>Y43S+dvP_aBLb-3$+XoFCu9hRtA z%Naq~N~{jugT+*OWdI|SBs14T%LHpi5Vi?9&sk2vO79kfw{!(hXTbFifo>HmLA8R6 zV-_4+VNzcqW0Tl}_Q~Z13 zAyf!^Sa4NwCcJzmGz_0Zus9-s%c7zKP_dvD2o|&g!GcyGSkMXt3tHi_kF_9x%N9ik zpkhHQT=uaR1aL`lCR{33t^0k$2qp5R=n#hs{iyH{fxxIHDi*8OpLh-fBgI|J<f=;Bk+vurZzUA93MZzYO=2(1NNJ zyqKJ4?GjfvC=BqSV|wR$sC%$(FAoU<7tdXfbPiT_KcqQ9p!kTliZAfXmAlXn`&j|w zH>Hj)b1+Wt1We+M)+!d-VW@asCR9d+Yu?p+g0P5`YJ*1UNB7!x_+xU0&C*~jcr)Ih zl1f)B+YS>xAP56j8-1!hY#c*;I7$xfxk~RV%!YX9U04_>ro$?sGOE8}RnQJcFNt4K zMU{iaTdO%?>tVcVRIBvUYmj4#h$ncQ?-*5_t#Y;Q-H?7^w8YV=S*_APq9*KXjm|<> ztDU;Rs(rT^yFwJHJmi6I)Ir@)8MP}`PEOP({m0?CJHu9Km;E};-5*QT9>K8b7Dx?g zELiFG0|xoQ5`NW03(Pgv3Ug?QgVb0H%r$?jNJ)0h+k^;)!ThWmY=ODPgJsJpR*AzQ zs?iphYvty)yqWA{-Wmcv;LYVewV?4C>mtlsKI0?aTs@$cw0k>lGjCDzWfp4F@`Ud#k0_x%RIVPF*E~^sU4<(Bk{v_k>R88>D%bf#nr!7VGZ$8i z?dvI2>6h+Fc3JZWa(HkxRMN@n?6wAeU>^}=SA42hZCyvz6z_v& z*y=@-m-=t}go4HgbbIwOyW5Y~1`(`WxB=t29?t*tZI1etK?N%pPQdt`o}&(Youhtb zkip7b2-rg!%M8ZZtvG*M?GK^l-DJ?gx=4;EPqPX(qe0T`j*|CuJ2@P3pmL)LRxaJ9 zP>=N%oer6J{zeWnJJH9XSEuWYE?5_3t#YQffgh2nWPM))#@PcHL_UtCGTmgTI6q1lexVXEf7Hk>pIyLt&tv>xAm*BPAqq`$`)mZL^piu z;^)V-5}n%s37^wM9no~T7;{Ae00000000007=!--noavgXVvH)00000NkvXXu0mjf DozWpm literal 0 HcmV?d00001 diff --git a/frontend/public/favicon/main.png b/frontend/public/favicon/main.png new file mode 100644 index 0000000000000000000000000000000000000000..4a32069c3e317a878c944799dd3b691c2e995bf1 GIT binary patch literal 1669 zcmV;027394P)@y{l4X2_h;J^_$9W5}G@&YUqf0Ednip8$LU)+fNu>}ZF~*)As-`2+(Y5!S2L z6uVoytL|3!j#~ZyRlO30+th+yz1M2BBnbcj00000n7=R7w!3|}J{}IgelQ&Ozpr!0 z%er&N_rvi+A7{g_&(yHi63Y^3C7(ccbSi6~WN-V}sPSZ5601DV`*_pGu8&U#j@|IH ztW`d#k(njaQ6DEdd?Jm-seIx)VV3rlsfJ{_E>7et8D?N#o#xQya>cff*P@CY#7cgy zQbVFliPs{*ZiQ3b?T!SS8EbhjRaY{QU^Dkj%bG(?jjjBJ3KFg4FUM;+;fF{tCq$v` zg~yRF%84qK5?fKduEktxTG+oGbFWKiefi?0K?9;y)})$PVk4J6l7kyM9DuBTckD%IhWYjKr}q$0VpbK>f6Zpr0TIVX~8jU!ge9=6#BYaVvK$r57D89TQhYVUq>230isi(F zeW|Q!bxQQ5ra2Op>a`}AQn8${4k|rQLUEmnG&T|EGMc}EAA=b3NiJn(5?j8 zXVr#}N@$0v6^m8&b|kH9rOdI`BB@$pH>cN%l#*Cah1UF_NU^QZiAj5_q9*aN6^p%A z?TXsM9Gh6Q!FD~48649JooIut3vP!o6>BB5ZXG%>Cb6Qg46rz>(19_n(4P6{k*>B` z8@v}XFtlRrKW&S)Sq=o<$_q`bl>l!QGAJ~$qAoSDXb(aLg(j90;H^Rig-+f-3HWSu zJu9SQvF{3(xv>EuiRA=bG+Jz^SV%Ft^c0d(JRA{#JVrwqS4iNDi%_FJvM;J zJ5so@0aG8b2d)&dK+57Mu5x4UP_a-1iG?CaEEGXvp$HNSMT8_)C*Y!PYycGtMNqMj zq8l3!l30`8_l-sRFg740u}1$8G^ZOI5RzDD0xlZuVzfe}7%etbETrhh288Txu#J8@ z0iTWLO=v6Z1b9ImEOdeNtpM-8kU^n|#XJjpn~*`FiM0{nZ9)d|;x)v`{^B*W5n2~K z*N#anCh+((bYM&?WIYSPpYx5WSk_-RLaS_LpbH%wyU~w!*secf2FHG+iFVkosIS_` zRxH|Kn>LEPt(UUr{V$<4QEkwqe7e`#;Z`h`wxW}7gHci{mbJs*g;s2oAoXM8Oyb)S zHEG{VeXh`|R`4n;s&%Zyy?Ch=%dGl15tb~eTA3+NLT|*KUnS@Jv3?P1_e7c8Q9hmgM*HuC8rgIv+oE^QR=7t?tWT#*EpV^It0I3AZ>Xt> zovUmua4%;o+_N9_V;|oOHTboUzg3Y}^55Yh<{Ph6NQpVtdCa%w%bvPt9Z5~pf7sm|l}PrfL2o2FLJ#iV{p?1q0s zT)~NXS4zx<+>^|~pnO09%sHgDLfu9IFem`i2k4P&4J^ zPf!i_AT60*h-9jcldohpSDC}@qTTSbEWR?8e*9~0Y;V%4kH_*!S9-5fC;vLlcf+xn zPawCJSXZ&+a>LQ8$o=8yhX3xa`Tx3e$M-|L8;)n^ejfk;00000z)bxQ#6J$i0#o&! P00000NkvXXu0mjf(%U0Q literal 0 HcmV?d00001 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index b9b528b..192720b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -38,6 +38,7 @@ import { Stack } from "./lib/stacks"; import { CodeGenerationModel } from "./lib/models"; import ModelSettingsSection from "./components/ModelSettingsSection"; import { extractHtml } from "./components/preview/extractHtml"; +import useBrowserTabIndicator from "./hooks/useBrowserTabIndicator"; const IS_OPENAI_DOWN = false; @@ -83,6 +84,9 @@ function App() { const wsRef = useRef(null); + // Indicate coding state using the browser tab's favicon and title + useBrowserTabIndicator(appState === AppState.CODING); + // When the user already has the settings in local storage, newly added keys // do not get added to the settings so if it's falsy, we populate it with the default // value diff --git a/frontend/src/hooks/useBrowserTabIndicator.ts b/frontend/src/hooks/useBrowserTabIndicator.ts new file mode 100644 index 0000000..aa8cadd --- /dev/null +++ b/frontend/src/hooks/useBrowserTabIndicator.ts @@ -0,0 +1,29 @@ +import { useEffect } from "react"; + +const CODING_SETTINGS = { + title: "Coding...", + favicon: "/favicon/coding.png", +}; +const DEFAULT_SETTINGS = { + title: "Screenshot to Code", + favicon: "/favicon/main.png", +}; + +const useBrowserTabIndicator = (isCoding: boolean) => { + useEffect(() => { + const settings = isCoding ? CODING_SETTINGS : DEFAULT_SETTINGS; + + // Set favicon + const faviconEl = document.querySelector( + "link[rel='icon']" + ) as HTMLLinkElement | null; + if (faviconEl) { + faviconEl.href = settings.favicon; + } + + // Set title + document.title = settings.title; + }, [isCoding]); +}; + +export default useBrowserTabIndicator; From 1a42f6a91bc1f9d53b39b157a822f88ec6f0f0a1 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 15 Mar 2024 13:35:15 -0400 Subject: [PATCH 043/144] add haiku claude constant --- backend/llm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/llm.py b/backend/llm.py index b9be56d..c7032c5 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -8,6 +8,7 @@ from utils import pprint_prompt MODEL_GPT_4_VISION = "gpt-4-vision-preview" MODEL_CLAUDE_SONNET = "claude-3-sonnet-20240229" MODEL_CLAUDE_OPUS = "claude-3-opus-20240229" +MODEL_CLAUDE_HAIKU = "claude-3-haiku-20240307" # Keep in sync with frontend (lib/models.ts) From 4e30b207c1ee9ddad05a37c31a11ac5a182490b7 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 15 Mar 2024 13:41:35 -0400 Subject: [PATCH 044/144] stop the screen sharing stream after it's recording is done --- frontend/src/components/recording/ScreenRecorder.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/src/components/recording/ScreenRecorder.tsx b/frontend/src/components/recording/ScreenRecorder.tsx index c2cc06d..4e8060a 100644 --- a/frontend/src/components/recording/ScreenRecorder.tsx +++ b/frontend/src/components/recording/ScreenRecorder.tsx @@ -19,6 +19,7 @@ function ScreenRecorder({ setScreenRecorderState, generateCode, }: Props) { + const [mediaStream, setMediaStream] = useState(null); const [mediaRecorder, setMediaRecorder] = useState( null ); @@ -33,6 +34,7 @@ function ScreenRecorder({ video: true, audio: { echoCancellation: true }, }); + setMediaStream(stream); // TODO: Test across different browsers // Create the media recorder @@ -55,6 +57,7 @@ function ScreenRecorder({ ); const dataUrl = await blobToBase64DataUrl(completeBlob); + setScreenRecordingDataUrl(dataUrl); setScreenRecorderState(ScreenRecorderState.FINISHED); }; @@ -69,10 +72,18 @@ function ScreenRecorder({ }; const stopScreenRecording = () => { + // Stop the recorder if (mediaRecorder) { mediaRecorder.stop(); setMediaRecorder(null); } + + // Stop the screen sharing stream + if (mediaStream) { + mediaStream.getTracks().forEach((track) => { + track.stop(); + }); + } }; const kickoffGeneration = () => { From 81c4fbe28d3f08320b56fc2defbad9587d43b48c Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Mon, 18 Mar 2024 17:44:05 -0400 Subject: [PATCH 045/144] identify exact llm being used during generation --- backend/llm.py | 28 +++++++++++++++++----------- backend/prompts/test_prompts.py | 12 ++++++------ backend/routes/generate_code.py | 10 ++++++++-- backend/video/utils.py | 6 +++--- backend/video_to_app.py | 5 ++--- 5 files changed, 36 insertions(+), 25 deletions(-) diff --git a/backend/llm.py b/backend/llm.py index c7032c5..dba5720 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -1,3 +1,4 @@ +from enum import Enum from typing import Any, Awaitable, Callable, List, cast from anthropic import AsyncAnthropic from openai import AsyncOpenAI @@ -5,13 +6,18 @@ from openai.types.chat import ChatCompletionMessageParam, ChatCompletionChunk from utils import pprint_prompt -MODEL_GPT_4_VISION = "gpt-4-vision-preview" -MODEL_CLAUDE_SONNET = "claude-3-sonnet-20240229" -MODEL_CLAUDE_OPUS = "claude-3-opus-20240229" -MODEL_CLAUDE_HAIKU = "claude-3-haiku-20240307" + +# Actual model versions that are passed to the LLMs and stored in our logs +class Llm(Enum): + GPT_4_VISION = "gpt-4-vision-preview" + CLAUDE_3_SONNET = "claude-3-sonnet-20240229" + CLAUDE_3_OPUS = "claude-3-opus-20240229" + CLAUDE_3_HAIKU = "claude-3-haiku-20240307" # Keep in sync with frontend (lib/models.ts) +# User-facing names for the models (for example, in the future, gpt_4_vision might +# be backed by a different model version) CODE_GENERATION_MODELS = [ "gpt_4_vision", "claude_3_sonnet", @@ -26,13 +32,13 @@ async def stream_openai_response( ) -> str: client = AsyncOpenAI(api_key=api_key, base_url=base_url) - model = MODEL_GPT_4_VISION + model = Llm.GPT_4_VISION # Base parameters params = {"model": model, "messages": messages, "stream": True, "timeout": 600} # Add 'max_tokens' only if the model is a GPT4 vision model - if model == MODEL_GPT_4_VISION: + if model == Llm.GPT_4_VISION: params["max_tokens"] = 4096 params["temperature"] = 0 @@ -59,12 +65,12 @@ async def stream_claude_response( client = AsyncAnthropic(api_key=api_key) # Base parameters - model = MODEL_CLAUDE_SONNET + model = Llm.CLAUDE_3_SONNET max_tokens = 4096 temperature = 0.0 # Translate OpenAI messages to Claude messages - system_prompt = cast(str, messages[0]["content"]) + system_prompt = cast(str, messages[0].get("content")) claude_messages = [dict(message) for message in messages[1:]] for message in claude_messages: if not isinstance(message["content"], list): @@ -91,7 +97,7 @@ async def stream_claude_response( # Stream Claude response async with client.messages.stream( - model=model, + model=model.value, max_tokens=max_tokens, temperature=temperature, system=system_prompt, @@ -111,7 +117,7 @@ async def stream_claude_response_native( api_key: str, callback: Callable[[str], Awaitable[None]], include_thinking: bool = False, - model: str = MODEL_CLAUDE_OPUS, + model: Llm = Llm.CLAUDE_3_OPUS, ) -> str: client = AsyncAnthropic(api_key=api_key) @@ -140,7 +146,7 @@ async def stream_claude_response_native( pprint_prompt(messages_to_send) async with client.messages.stream( - model=model, + model=model.value, max_tokens=max_tokens, temperature=temperature, system=system_prompt, diff --git a/backend/prompts/test_prompts.py b/backend/prompts/test_prompts.py index 9e60410..42e89c3 100644 --- a/backend/prompts/test_prompts.py +++ b/backend/prompts/test_prompts.py @@ -311,35 +311,35 @@ def test_prompts(): tailwind_prompt = assemble_prompt( "image_data_url", "html_tailwind", "result_image_data_url" ) - assert tailwind_prompt[0]["content"] == TAILWIND_SYSTEM_PROMPT + assert tailwind_prompt[0].get("content") == TAILWIND_SYSTEM_PROMPT assert tailwind_prompt[1]["content"][2]["text"] == USER_PROMPT # type: ignore react_tailwind_prompt = assemble_prompt( "image_data_url", "react_tailwind", "result_image_data_url" ) - assert react_tailwind_prompt[0]["content"] == REACT_TAILWIND_SYSTEM_PROMPT + assert react_tailwind_prompt[0].get("content") == REACT_TAILWIND_SYSTEM_PROMPT assert react_tailwind_prompt[1]["content"][2]["text"] == USER_PROMPT # type: ignore bootstrap_prompt = assemble_prompt( "image_data_url", "bootstrap", "result_image_data_url" ) - assert bootstrap_prompt[0]["content"] == BOOTSTRAP_SYSTEM_PROMPT + assert bootstrap_prompt[0].get("content") == BOOTSTRAP_SYSTEM_PROMPT assert bootstrap_prompt[1]["content"][2]["text"] == USER_PROMPT # type: ignore ionic_tailwind = assemble_prompt( "image_data_url", "ionic_tailwind", "result_image_data_url" ) - assert ionic_tailwind[0]["content"] == IONIC_TAILWIND_SYSTEM_PROMPT + assert ionic_tailwind[0].get("content") == IONIC_TAILWIND_SYSTEM_PROMPT assert ionic_tailwind[1]["content"][2]["text"] == USER_PROMPT # type: ignore vue_tailwind = assemble_prompt( "image_data_url", "vue_tailwind", "result_image_data_url" ) - assert vue_tailwind[0]["content"] == VUE_TAILWIND_SYSTEM_PROMPT + assert vue_tailwind[0].get("content") == VUE_TAILWIND_SYSTEM_PROMPT assert vue_tailwind[1]["content"][2]["text"] == USER_PROMPT # type: ignore svg_prompt = assemble_prompt("image_data_url", "svg", "result_image_data_url") - assert svg_prompt[0]["content"] == SVG_SYSTEM_PROMPT + assert svg_prompt[0].get("content") == SVG_SYSTEM_PROMPT assert svg_prompt[1]["content"][2]["text"] == SVG_USER_PROMPT # type: ignore diff --git a/backend/routes/generate_code.py b/backend/routes/generate_code.py index 2efc7a0..2d82aca 100644 --- a/backend/routes/generate_code.py +++ b/backend/routes/generate_code.py @@ -6,7 +6,7 @@ from config import ANTHROPIC_API_KEY, IS_PROD, SHOULD_MOCK_AI_RESPONSE from custom_types import InputMode from llm import ( CODE_GENERATION_MODELS, - MODEL_CLAUDE_OPUS, + Llm, stream_claude_response, stream_claude_response_native, stream_openai_response, @@ -88,6 +88,7 @@ async def stream_code(websocket: WebSocket): if code_generation_model not in CODE_GENERATION_MODELS: await throw_error(f"Invalid model: {code_generation_model}") raise Exception(f"Invalid model: {code_generation_model}") + exact_llm_version = None print( f"Generating {generated_code_config} code for uploaded {input_mode} using {code_generation_model} model..." @@ -238,9 +239,10 @@ async def stream_code(websocket: WebSocket): messages=prompt_messages, # type: ignore api_key=ANTHROPIC_API_KEY, callback=lambda x: process_chunk(x), - model=MODEL_CLAUDE_OPUS, + model=Llm.CLAUDE_3_OPUS, include_thinking=True, ) + exact_llm_version = Llm.CLAUDE_3_OPUS elif code_generation_model == "claude_3_sonnet": if not ANTHROPIC_API_KEY: await throw_error( @@ -253,6 +255,7 @@ async def stream_code(websocket: WebSocket): api_key=ANTHROPIC_API_KEY, callback=lambda x: process_chunk(x), ) + exact_llm_version = Llm.CLAUDE_3_SONNET else: completion = await stream_openai_response( prompt_messages, # type: ignore @@ -260,6 +263,7 @@ async def stream_code(websocket: WebSocket): base_url=openai_base_url, callback=lambda x: process_chunk(x), ) + exact_llm_version = Llm.GPT_4_VISION except openai.AuthenticationError as e: print("[GENERATE_CODE] Authentication failed", e) error_message = ( @@ -298,6 +302,8 @@ async def stream_code(websocket: WebSocket): if validated_input_mode == "video": completion = extract_tag_content("html", completion) + print("Exact used model for generation: ", exact_llm_version) + # Write the messages dict into a log so that we can debug later write_logs(prompt_messages, completion) # type: ignore diff --git a/backend/video/utils.py b/backend/video/utils.py index 94501a2..dc772fc 100644 --- a/backend/video/utils.py +++ b/backend/video/utils.py @@ -5,7 +5,7 @@ import mimetypes import os import tempfile import uuid -from typing import Union, cast +from typing import Any, Union, cast from moviepy.editor import VideoFileClip # type: ignore from PIL import Image import math @@ -17,7 +17,7 @@ TARGET_NUM_SCREENSHOTS = ( ) -async def assemble_claude_prompt_video(video_data_url: str): +async def assemble_claude_prompt_video(video_data_url: str) -> list[Any]: images = split_video_into_screenshots(video_data_url) # Save images to tmp if we're debugging @@ -28,7 +28,7 @@ async def assemble_claude_prompt_video(video_data_url: str): print(f"Number of frames extracted from video: {len(images)}") if len(images) > 20: print(f"Too many screenshots: {len(images)}") - return + raise ValueError("Too many screenshots extracted from video") # Convert images to the message format for Claude content_messages: list[dict[str, Union[dict[str, str], str]]] = [] diff --git a/backend/video_to_app.py b/backend/video_to_app.py index 597cbbf..c876804 100644 --- a/backend/video_to_app.py +++ b/backend/video_to_app.py @@ -17,8 +17,7 @@ from utils import pprint_prompt from config import ANTHROPIC_API_KEY from video.utils import extract_tag_content, assemble_claude_prompt_video from llm import ( - MODEL_CLAUDE_OPUS, - # MODEL_CLAUDE_SONNET, + Llm, stream_claude_response_native, ) @@ -87,7 +86,7 @@ async def main(): messages=prompt_messages, api_key=ANTHROPIC_API_KEY, callback=lambda x: process_chunk(x), - model=MODEL_CLAUDE_OPUS, + model=Llm.CLAUDE_3_OPUS, include_thinking=True, ) From 212aa228ab006c3a982f0f5781ba4e65c235b4e8 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 19 Mar 2024 10:30:58 -0400 Subject: [PATCH 046/144] fix bug with using enum as string --- backend/llm.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/llm.py b/backend/llm.py index dba5720..2c529e2 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -35,7 +35,12 @@ async def stream_openai_response( model = Llm.GPT_4_VISION # Base parameters - params = {"model": model, "messages": messages, "stream": True, "timeout": 600} + params = { + "model": model.value, + "messages": messages, + "stream": True, + "timeout": 600, + } # Add 'max_tokens' only if the model is a GPT4 vision model if model == Llm.GPT_4_VISION: From b57f34d73afaf85b6a95826457de2692cd6e3157 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 19 Mar 2024 12:09:25 -0400 Subject: [PATCH 047/144] close anthropic client after it's done streaming --- backend/llm.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/llm.py b/backend/llm.py index 2c529e2..e6e628c 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -113,6 +113,10 @@ async def stream_claude_response( # Return final message response = await stream.get_final_message() + + # Close the Anthropic client + await client.close() + return response.content[0].text @@ -177,6 +181,9 @@ async def stream_claude_response_native( f"Token usage: Input Tokens: {response.usage.input_tokens}, Output Tokens: {response.usage.output_tokens}" ) + # Close the Anthropic client + await client.close() + if not response: raise Exception("No HTML response found in AI response") else: From 62272e3a68361222e1f5da948f6ff5be7deeeee3 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 19 Mar 2024 13:51:30 -0400 Subject: [PATCH 048/144] update error message for video --- backend/routes/generate_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/routes/generate_code.py b/backend/routes/generate_code.py index 2d82aca..b2e55cc 100644 --- a/backend/routes/generate_code.py +++ b/backend/routes/generate_code.py @@ -230,7 +230,7 @@ async def stream_code(websocket: WebSocket): if validated_input_mode == "video": if not ANTHROPIC_API_KEY: await throw_error( - "No Anthropic API key found. Please add the environment variable ANTHROPIC_API_KEY to backend/.env" + "Video only works with Anthropic models. No Anthropic API key found. Please add the environment variable ANTHROPIC_API_KEY to backend/.env" ) raise Exception("No Anthropic key") From 7f44228a9582f6e39e8dbcee7598b86328ef7e74 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 19 Mar 2024 13:57:57 -0400 Subject: [PATCH 049/144] fix bug where open AI key is required to use it with Anthropic --- backend/routes/generate_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/routes/generate_code.py b/backend/routes/generate_code.py index b2e55cc..d9ea660 100644 --- a/backend/routes/generate_code.py +++ b/backend/routes/generate_code.py @@ -119,7 +119,7 @@ async def stream_code(websocket: WebSocket): if openai_api_key: print("Using OpenAI API key from environment variable") - if not openai_api_key: + if not openai_api_key and code_generation_model == "gpt_4_vision": print("OpenAI API key not found") await websocket.send_json( { From 6069c2a118592905fd8c0bc2b406b2e93891dfb1 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Wed, 20 Mar 2024 15:54:42 -0400 Subject: [PATCH 050/144] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f8b6795..b1dc247 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ For debugging purposes, if you don't want to waste GPT4-Vision credits, you can MOCK=true poetry run uvicorn main:app --reload --port 7001 ``` -## Video to app (experimental) +## Screen Recording to prototype (experimental) https://github.com/abi/screenshot-to-code/assets/23818/1468bef4-164f-4046-a6c8-4cfc40a5cdff From 6a0201a5c0916ad089c7555c019be5427fe01f36 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Fri, 22 Mar 2024 11:14:15 -0400 Subject: [PATCH 051/144] fix up mock data --- backend/mock_llm.py | 754 ++++++++++++++++++++++++-------------------- 1 file changed, 410 insertions(+), 344 deletions(-) diff --git a/backend/mock_llm.py b/backend/mock_llm.py index 0b903b7..b85b1b1 100644 --- a/backend/mock_llm.py +++ b/backend/mock_llm.py @@ -4,14 +4,14 @@ from typing import Awaitable, Callable from custom_types import InputMode -STREAM_CHUNK_SIZE = 5 +STREAM_CHUNK_SIZE = 20 async def mock_completion( process_chunk: Callable[[str], Awaitable[None]], input_mode: InputMode ) -> str: code_to_return = ( - GOOGLE_FORM_VIDEO_PROMPT_MOCK + TALLY_FORM_VIDEO_PROMPT_MOCK if input_mode == "video" else NO_IMAGES_NYTIMES_MOCK_CODE ) @@ -670,394 +670,460 @@ $(document).ready(function() { """ GOOGLE_FORM_VIDEO_PROMPT_MOCK = """ - -To build this: -- Create a search bar that allows typing and shows placeholder text -- Implement search suggestions that update as the user types -- Allow selecting a suggestion to perform that search -- Show search results with the query and an AI-powered overview -- Have filter tabs for different search verticals -- Allow clicking filter tabs to add/remove them, updating the URL -- Ensure the UI closely matches the Google style and colors + +User flow: +1. User starts on the Google search page and types in "times" in the search bar +2. As the user types, Google provides autocomplete suggestions related to "times" +3. User selects the "times" suggestion from the autocomplete dropdown +4. The search results page for "times" loads, showing various results related to The New York Times newspaper +5. User clicks the "Generate" button under "Get an AI-powered overview for this search?" +6. An AI-generated overview about The New York Times loads on the right side of the search results + +Code structure: +- HTML structure with header, search bar, autocomplete dropdown, search button +- Search results area to display search results +- Sidebar area to display the AI-generated overview +- Use Tailwind CSS utility classes for styling +- Use jQuery to handle user interactions: + - Typing in search bar to show/filter autocomplete suggestions + - Selecting autocomplete suggestion to populate search bar + - Clicking search button to display search results + - Clicking "Generate" button to display AI overview +- Hardcode search results and AI overview content for demo purposes - - - + + -
-
Gmail
-
Images
- User avatar -
- -
- Google logo +
+
+ Google logo +
+ + +
+ + Profile picture of the user +
-
- - -
+
+ +
+
The Crossword
+
Play the Daily New York Times Crossword puzzle edited by Will ...
+
-